mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
chore(app-registry): polish 4 small wins — TOC + AppId-derive + route-drift test + 3 MANA_APPS
§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 `<RoutePage appId="…">` 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) <noreply@anthropic.com>
This commit is contained in:
parent
907a3add49
commit
6d193a9fa7
8 changed files with 205 additions and 37 deletions
|
|
@ -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') },
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 <RoutePage appId="…"> 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<string> {
|
||||
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(/<RoutePage[^>]+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([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@
|
|||
import { RoutePage } from '$lib/components/shell';
|
||||
</script>
|
||||
|
||||
<RoutePage appId="ai-agents">
|
||||
<RoutePage appId="agents">
|
||||
<ListView />
|
||||
</RoutePage>
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@
|
|||
</button>
|
||||
{/snippet}
|
||||
|
||||
<RoutePage appId="ai-agents" backHref="/agents">
|
||||
<RoutePage appId="agents" backHref="/agents">
|
||||
<div class="page">
|
||||
<header class="header">
|
||||
<button type="button" class="back" onclick={() => goto('/')}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue