feat(frontend): the folders are layers — an import only goes down, a module is reached through its index.ts
`local/layers` (eslint.config.js) resolves every import to its file and refuses one that goes up (lib/ → components/ui/ → features/ → modules/ → shell/) or that reaches into another module's own files. A module's index.ts is its public face: the stores, mounted at the root and read app-wide, which features/ may read too; a page is still opened lazily by import(). Its contract test probes every edge, and three mutants of the rule (off, no face check, a door for everyone) each turn it red. Six modules got that face; the shared channel UI (about, tags, tag manager) moved to features/channel, the filter chips and the saved-views context to features/filters, and the prefs store's controller type moved next to the store instead of importing it from its own page. The header's per-module controls and the eagerly loaded rails keep eight inline suppressions, each naming why: they go when the header gets slots and every rail loads lazily. `chunkGuard` fails the production build when a lazily imported module lands in a boot chunk (the entry, the shell, what they import) — e.g. a page re-exported from an index.ts, which makes its import() a silent no-op. Proved on a build with that mutant; today's build has none.
This commit is contained in:
@@ -73,14 +73,19 @@ only along a declared edge and writes them only through that unit's service; cor
|
||||
`queries`. **Fence-held, with no exemptions** (`backend/tests/test_unit_fences.py`) — as far as an AST sees: what it
|
||||
cannot (an ORM write on another unit's loaded row, SQL in `text()`, a commit by `begin()`) is review's to catch, listed in
|
||||
[`backend/app/README.md`](backend/app/README.md) with the conventions.
|
||||
Frontend: `main.tsx` provider tree → `App.tsx` shell (a shared `/watch` link boots from its own
|
||||
entry, `watch.html` → `watchMain.tsx`, held app-free by a contract test) → `moduleRegistry` route-chunks → module
|
||||
components → the **platform layer `components/ui/`** (domain-free primitives: the popover engine and
|
||||
its menus, `Modal`/`Overlay`, `Button`/`form`, `DataTable`/`VirtualGrid`, `QueryState`, …). That
|
||||
boundary is one-way and **lint-enforced**, not just described: a platform component may not import a
|
||||
feature component or a domain API slice, and one layer down `lib/` may not import from `components/` at
|
||||
all, not even a type (`no-restricted-imports` in `eslint.config.js`, with its own test). A new generic
|
||||
component goes in `ui/`; see [`frontend/src/components/ui/README.md`](frontend/src/components/ui/README.md).
|
||||
Frontend, by folder under `src/`: `main.tsx` provider tree → `shell/` (`App`, header, nav rail, palette,
|
||||
boot; a shared `/watch` link boots from its own entry, `watch.html` → `watchMain.tsx` → `watch/`, held
|
||||
app-free by a contract test) → `shell/moduleRegistry` route-chunks → **`modules/<page>/`** (one folder per page: the page,
|
||||
its rail, its store, its tests) → **`features/<name>/`** (what several pages share: video cards, filters,
|
||||
downloads, the player, navigation) → the **platform layer `components/ui/`** (domain-free primitives: the
|
||||
popover engine and its menus, `Modal`/`Overlay`, `Button`/`form`, `DataTable`/`VirtualGrid`, `QueryState`,
|
||||
…) → `lib/`. The direction is one-way and **lint-enforced**, not just described (`local/layers`, with
|
||||
its own test): an import only goes down; another module is reached only through its **`index.ts`** (its
|
||||
public face — the stores, mounted at the root and read app-wide, which `features/` may read too) or
|
||||
lazily by `import()`; a platform component may not import a domain API slice, and `lib/` nothing above
|
||||
it, not even a type. A lazily imported page, rail or dialog that a static import drags onto the boot
|
||||
path fails the build (`chunkGuard.ts`). A new generic component goes in `ui/`; see
|
||||
[`frontend/src/components/ui/README.md`](frontend/src/components/ui/README.md).
|
||||
Data via react-query (`qk` factory), the signed-in account's `localStorage` (`storage.ts`), and server
|
||||
prefs; API through `lib/api/*` slices over `core.req<T>`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user