docs(frontend): the canonical table and the ui README name the module shape; adding a page is written down

CLAUDE.md gains the header-content, module-store and saved-views rows. docs/development.md says what a
new page is and which tests hold it. components/ui/README.md names every file in the directory, and a
contract test keeps it that way: seventeen had slipped in unlisted. copyItems' comment still claimed it
was a feature file.
This commit is contained in:
2026-10-23 12:52:04 +02:00
parent 6ae7825a14
commit ad05932217
14 changed files with 63 additions and 18 deletions
+21
View File
@@ -36,6 +36,27 @@ The dev server also serves the **design lab** at `http://localhost:5173/design-l
decisions and the chosen patterns, rendered with the app's real CSS. It never reaches a production build
(see [`DESIGN.md`](DESIGN.md)).
## Adding a page
A frontend page is one folder, `frontend/src/modules/<id>/` (the layering is in
[`CLAUDE.md`](../CLAUDE.md) § Architecture shape):
1. The page itself, default-exported, and anything only it uses. Header content (a search box, a toggle) the
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.
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
nav-rail order. The rail, the palette, the `g` chords, the tab title and the root's stores all read from there.
5. Its strings in both locales (`src/i18n/locales`).
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
down, a module only through its face) and `chunkGuard` in the build (no lazy page on the boot path), plus the
i18n parity test.
## The gate
A change is done when this is green, from the repo root: