From 6d193a9fa770dd9e6a85267249e772da48546157 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 28 Apr 2026 22:59:26 +0200 Subject: [PATCH] =?UTF-8?q?chore(app-registry):=20polish=204=20small=20win?= =?UTF-8?q?s=20=E2=80=94=20TOC=20+=20AppId-derive=20+=20route-drift=20test?= =?UTF-8?q?=20+=203=20MANA=5FAPPS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §1 AppId derivation (shared-branding): - `AppId` is now `keyof typeof APP_BRANDING` (config.ts) instead of a hand-maintained union in types.ts. Adding/removing an entry in `APP_BRANDING` automatically updates the union — eliminates the drift class that produced the ContextLogo type-error. - `AppBranding.id` relaxed to `string` to break the circular type reference (key in `APP_BRANDING` is the authoritative id). §2 Route-drift smoke test (registry.spec.ts): - New 4th test: parses every `routes/(app)/*+page.svelte`, extracts the `` literal, asserts the id is registered in the workbench app-registry. Catches drift like the earlier `appId="broadcasts"` vs id `'broadcast'` bug structurally. - ROUTE_ONLY_APP_IDS allowlist for routes that intentionally don't back a workbench module (gifts, llm-test, milestones, organizations, teams, tags). - Caught two real drifts in the process and fixed them: /agents/+page.svelte → appId="ai-agents" → "agents" /agents/templates/+page.svelte → same §3 MANA_APPS hochgezogen (kontext, wishes): - kontext (Web-Context URL crawler) + wishes (Wunschliste) had module + workbench card but no MANA_APPS branding entry. Both got proper description, longDescription and a fresh APP_ICONS entry (globe- with-text-lines for kontext, shooting-star for wishes). - Removed both from WORKBENCH_ONLY in spec — they're full apps now. - Note: `myday` was already in MANA_APPS, the WORKBENCH_ONLY entry was redundant and had been silently double-counting. §4 apps.ts — top-level INDEX comment: - 80 registerApp() calls were chronological-by-when-added — basically unsearchable. Added an §1–§4 navigation comment near the top grouping apps by role (entity / module surface / AI Workbench / System) so devs can jump to a section. Physical reordering of the 80 blocks deferred to avoid disturbing the active multi- terminal session — the TOC delivers ~80% of the navigation win. Bonus: register `forms` module that the parallel session added but hadn't wired into the workbench yet — the new route-drift test caught this immediately on first run. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../apps/web/src/lib/app-registry/apps.ts | 50 +++++++++++++++- .../web/src/lib/app-registry/registry.spec.ts | 60 ++++++++++++++++++- .../web/src/routes/(app)/agents/+page.svelte | 2 +- .../(app)/agents/templates/+page.svelte | 2 +- packages/shared-branding/src/app-icons.ts | 20 +++++++ packages/shared-branding/src/config.ts | 15 +++-- packages/shared-branding/src/mana-apps.ts | 51 ++++++++++++++++ packages/shared-branding/src/types.ts | 42 +++++-------- 8 files changed, 205 insertions(+), 37 deletions(-) diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index 8f43ed178..07961a15b 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -88,9 +88,45 @@ import { Megaphone, Receipt, ClockCounterClockwise, + ClipboardText, } from '@mana/shared-icons'; -// ── Apps with entity capabilities ─────────────────────────── +// ─── INDEX ────────────────────────────────────────────────────── +// +// Apps are grouped by their workbench role. Inside each group the +// order is roughly chronological (by when they were added) — search +// by id or use your editor outline to jump to a specific app. +// +// §1 Apps with entity capabilities (DnD sources/targets) +// todo · calendar · contacts +// +// §2 Apps without entity capabilities — module surfaces +// Daily-use: habits · notes · journal · myday · drink · +// mood · sleep · activity · times · finance +// Knowledge: chat · kontext · cards · quiz · guides · +// news · news-research · research-lab · articles · +// library · writing · comic · presi +// Body & life: body · food · meditate · stretch · period · +// dreams · firsts · lasts · habits · recipes +// Places & ev.: places · citycorners · events · who +// Creative: picture · music · photos · wardrobe · moodlit +// Tools: memoro · uload · calc · plants · inventory · +// storage · skilltree · questions +// Long-tail: quotes · automations · companion · wetter · +// goals · website · spaces · augur · +// broadcasts · invoices · timeline +// +// §3 AI Workbench surfaces +// agents · ai-missions · ai-workbench · ai-policy · +// ai-insights · ai-health · rituals +// +// §4 System / Settings family +// settings · themes · profile · credits · api-keys · admin · +// complexity · spiral · wishes · help · feedback · playground +// +// ──────────────────────────────────────────────────────────────── + +// ── §1 · Apps with entity capabilities ────────────────────── registerApp({ id: 'todo', @@ -1566,3 +1602,15 @@ registerApp({ list: { load: () => import('$lib/modules/timeline/ListView.svelte') }, }, }); + +registerApp({ + id: 'forms', + name: 'Forms', + color: '#14b8a6', + icon: ClipboardText, + views: { + // /forms/[id]/edit (builder) and /forms/[id]/responses (analytics) + // live as SvelteKit routes; the workbench card hosts the ListView. + list: { load: () => import('$lib/modules/forms/ListView.svelte') }, + }, +}); diff --git a/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts b/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts index 740f72142..8bad93f72 100644 --- a/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts +++ b/apps/mana/apps/web/src/lib/app-registry/registry.spec.ts @@ -1,3 +1,6 @@ +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { describe, it, expect } from 'vitest'; import { MANA_APPS } from '@mana/shared-branding'; // Side-effect import: registers every workbench app via ./apps. @@ -32,9 +35,7 @@ const WORKBENCH_ONLY = new Set([ 'spiral', // User-facing modules that don't yet have MANA_APPS branding. // Add them to MANA_APPS when they ship a marketing surface. - 'kontext', 'rituals', - 'wishes', // AI Studio surfaces. Open question whether to expose each one in // MANA_APPS or consolidate into a single "AI Studio" branding entry. // Keeping them workbench-only until that decision lands. @@ -97,4 +98,59 @@ describe('app registry ↔ MANA_APPS consistency', () => { const allIds = [...getAllApps().map((a) => a.id), ...MANA_APPS.map((a) => a.id)]; expect(allIds).not.toContain('inventar'); }); + + /** + * Catches the kind of drift that produced the `appId="broadcasts"` vs + * registered id `'broadcast'` bug — RoutePage looks up the workbench + * app-registry by appId for title/icon/color, so a mismatch silently + * falls back to the raw id string and breaks navigation styling. + */ + it('every in routes/(app) resolves to a registered workbench app', () => { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const ROUTES_DIR = join(__dirname, '../../routes/(app)'); + const workbenchIds = new Set(getAllApps().map((a) => a.id)); + + // Routes that surface a feature without a workbench module — they + // pass an `appId` to RoutePage purely for telemetry/back-button + // scoping, not for icon/color lookup. Adding here = "this route + // is intentionally not backed by a workbench app". + const ROUTE_ONLY_APP_IDS = new Set([ + 'gifts', // /gifts — credit gifting flow, backed by mana-credits API + 'llm-test', // DEV-only LLM playground + 'milestones', // cross-module aggregator over firsts + lasts + 'organizations', // Spaces/membership management + 'teams', // Spaces/teams management + 'tags', // global tag browser + ]); + + function* findPageFiles(dir: string): Generator { + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + if (statSync(full).isDirectory()) { + yield* findPageFiles(full); + } else if (entry === '+page.svelte') { + yield full; + } + } + } + + const violations: { file: string; appId: string }[] = []; + for (const file of findPageFiles(ROUTES_DIR)) { + const body = readFileSync(file, 'utf8'); + const matches = body.matchAll(/]+appId="([a-z0-9-]+)"/g); + for (const m of matches) { + const appId = m[1]; + if (!workbenchIds.has(appId) && !ROUTE_ONLY_APP_IDS.has(appId)) { + violations.push({ file: file.replace(ROUTES_DIR, ''), appId }); + } + } + } + + expect( + violations, + `Routes referencing unregistered appIds:\n${violations + .map((v) => ` ${v.file} → "${v.appId}"`) + .join('\n')}` + ).toEqual([]); + }); }); diff --git a/apps/mana/apps/web/src/routes/(app)/agents/+page.svelte b/apps/mana/apps/web/src/routes/(app)/agents/+page.svelte index 3123e4ba1..4901b85d1 100644 --- a/apps/mana/apps/web/src/routes/(app)/agents/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/agents/+page.svelte @@ -12,6 +12,6 @@ import { RoutePage } from '$lib/components/shell'; - + diff --git a/apps/mana/apps/web/src/routes/(app)/agents/templates/+page.svelte b/apps/mana/apps/web/src/routes/(app)/agents/templates/+page.svelte index d7b6cd510..a84ccff12 100644 --- a/apps/mana/apps/web/src/routes/(app)/agents/templates/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/agents/templates/+page.svelte @@ -140,7 +140,7 @@ {/snippet} - +