Application map / References / Frontend contract

Frontend Platform Contract

The rules that keep one frontend serving five personas from degrading into many stitched-together apps — and the machinery that enforces them.

Why this exists

Left to grow organically, a data-dense multi-persona frontend degrades along two axes: architectural entropy (route files accrete domain logic, features import each other's internals, primitives get forked, data fetching scatters) and visual entropy (every page reinvents type, spacing, and color). The contract answers both with fixed layers, tokens, a canonical component vocabulary, named page anatomies, and — critically — mechanical enforcement: a rule that matters gets a lint rule, a ratchet entry, or a test, never review vigilance alone.

Four layers, one-way imports

Routes src/app/ thin — each delegates to exactly one feature entrypoint Feature slices src/features/ domain UI, hooks, data edge, types, tests — one per domain Shared product layer src/shared/ product wrappers with defaults baked in; cross-feature utilities Primitives src/components/ui/ vendored, registry-faithful shadcn/ui — regenerable imports flow one way: down. never upward; never sideways between features — shared code moves down instead
The sanctioned cross-cutting exception is the app shell and its selected-property context, which features read only through its defined interface (a server resolver and a client hook).
LayerOwnsMust not contain
Routes (src/app/)URLs, layouts, metadata, framework plumbing. A migrated route is thin: it delegates to exactly one feature entrypoint.Domain UI, business logic, deep component trees.
Feature slices (src/features/, one per domain)The domain's UI, hooks, data actions, types, formatters, tests.Imports of another feature's internals.
Shared product layer (src/shared/)Product wrappers with operational defaults baked in, plus cross-feature utilities.Anything belonging to a single feature.
Primitives (src/components/ui/)Vendored, registry-faithful shadcn/ui files — regenerable from the registry.Domain behavior, adapters, wrappers.

Data fetching belongs to a feature's data edge — server actions, route handlers, named client helpers — never arbitrary components.

Known delta: cross-feature hooks/lib/types live at top-level src/hooks|lib|types rather than under src/shared/; harmless, but src/shared/* is the target home if touched.

The design contract

The component vocabulary

Card is the primary building block — every distinct section of content is a card, with a table-card variant that wraps the data table flush. One data table implementation (shadcn table + TanStack engine behind a typed column API): sortable headers, text left / numbers right / badges centered, currency always with cents and separators, negatives in destructive color and parentheses, horizontal scroll inside the card on narrow viewports — per-column overrides, never forks. Status badges in the two light-tinted forms. Loading skeletons (pulse cards at final-layout spacing, never spinners) and error states (destructive-tinted card with a plain-language message and a retry action).

New pages compose one of four named archetypes instead of inventing a layout: A summary cards + detail table · B dashboard overview (two 2-column rows + full-width card) · C summary donuts + accordion lists · D stacked tables under a standalone heading. Everything renders inside one shell: 256px sidebar, breadcrumbs + assistance link, notifications bell and user button in the top bar.

The maintained wrapper inventory (metric tiles, page headers, charts, filter toolbar, StatusBadge, the data table and its hooks) is exported from src/shared/ui/index.ts — the code is the authoritative list.

Mechanical enforcement — what a migration must update

  1. Lint restricted-import rules block retired paths and wrong-direction imports at edit time.
  2. The ratchet script (pnpm check:frontend-architecture, run in CI) enumerates what has been cleaned and forbids regression: deleted legacy directories that may never reappear, routes proven thin that must stay thin, slices scanned for hardcoded colors / raw form-table-svg elements / stray fetch( — each with a small explicit allowlist. The philosophy: lists enumerate what is migrated rather than globbing the world, and every migration finishes by adding itself to the ratchet (its route to the thin list, its prefix to the primitive checks, its deleted paths to the legacy list).
  3. Architecture tests assert the product-source guards not worth encoding in the script.

Migration discipline: one vertical slice at a time — keep URLs, role behavior, and server contracts stable; delegate the route to a feature entrypoint; replace raw values with tokens and one-offs with canonical components; add tests; delete the legacy implementation only when zero imports remain; ratchet in the same change. Non-goals: big-bang rewrites, per-feature token/table forks, custom primitives where mature libraries exist, and fabricated placeholder data styled to look real.

Cleanup residue (verified 2026-07-08, still open unless closed since)