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} - +