feat(nav): each module declares its URL keys; a page switch clears them and a page opens without another's

The shell cleared a hand-kept key list on a page switch (it missed the Files and feed keys) and cleared
nothing when a page opened from a link carrying another page's keys. ModuleDef.urlKeys now names every
key a page's view writes, the shell derives both from the registry, and moduleUrl.contract.test holds the
declarations true: one owner per key, no shell key, exactly what each codec writes, and no undeclared
key literal in the app.
This commit is contained in:
2026-10-25 20:48:14 +01:00
parent 4aff19a9a4
commit b30c60d1a5
24 changed files with 246 additions and 28 deletions
+5 -2
View File
@@ -45,7 +45,9 @@ A frontend page is one folder, `frontend/src/modules/<id>/` (the layering is in
page renders into the floating header itself through `PageHeader`; the shell knows no page.
2. `module.ts`: its `ModuleDef` (`lib/moduleDef.ts`): id, label keys, icon, an optional `gate`, the page as a
`lazyWithPreload(() => import(…))`, and optionally a rail and a `Store`. Of its own folder it imports statically
only its store (a `…Provider` / `…Store` file); the page and the rail load lazily.
only its store (a `…Provider` / `…Store` file); the page and the rail load lazily. `urlKeys` names every
address-bar key its view writes (the URL is the view: a bookmark reopens it), its list kept in `lib/` beside
its codec; the shell clears them on a page switch and opens a page without another page's keys.
3. `index.ts`, the module's public face: the definition, and only what other folders must read (its store's
hooks). Another folder reaches the module through this file or not at all.
4. The id in `lib/urlState`'s `PAGES`, and the definition in `features/navigation/moduleRegistry.ts`, in
@@ -58,7 +60,8 @@ A frontend page is one folder, `frontend/src/modules/<id>/` (the layering is in
returns to `ui/FilterRail`; tsc names each table still missing the new id.
What holds it: `moduleRegistry.contract.test.ts` (every id defined once, from its own folder, lazily),
`moduleStores.contract.test.tsx` (no store reads another module), the `local/layers` lint rule (imports only go
`moduleStores.contract.test.tsx` (no store reads another module), `moduleUrl.contract.test.ts` (each URL key
belongs to one module and matches what its codec writes; no undeclared key literal), the `local/layers` lint rule (imports only go
down, a module only through its face) and `chunkGuard` in the build (no lazy page on the boot path), plus the
i18n parity test.