Agent docs

Starting a new app on the kit

The mirror of 04-migrating-an-app.md.

The mirror of 04-migrating-an-app.md. Same evidence, read the other direction: that guide is a list of things two apps had to undo, and this one is a list of the same things not done in the first place.

Audience: an agent (or person) scaffolding a new app.

Install the kit first: packages/ui/README.md has the registry line, the token, the install and pin, the theme import, and the bundler contract. Then start from the template that matches the app's shape under apps/_templates/ (next, react-router-ssr for the speedway shape, vite-spa for the walmart shape; the folder's README says which is which) and follow its TEMPLATE.md for scaffolding. This doc covers what happens after the app runs and before it has any screens.

What you get free by starting here#

Worth naming, because each of these is a whole section of the migration guide that does not apply to you.

No parity ledger. There is no accumulated behaviour to preserve, so nothing can be silently purged. This is the single largest cost in a migration and you are exempt from all of it.

No name collisions. Import the kit's StatusDot and it is the only StatusDot. Keep it that way: the rule that prevents the migration guide's step 1 from ever applying to you is that a local component never takes a kit export's name. If you need a variant, name it for what makes it different.

A pin you chose. Take the current kit version deliberately, and know that a caret on a 0.0.x version is a patch pin, so you are choosing an exact version whether you meant to or not. Every canon rule is reachable at the latest; that stops being true the day you stop bumping.

Three decisions to make before the first screen#

None of these are reversible cheaply, and all three are decided by accident if nobody decides them.

Where filter and detail state lives. URL or component. The canon says URL params for a browsable list's filters (07 §A3, AP-10 row 15) and the two existing apps both do it by hand: speedway wires useSearchParams directly, walmart wrote a local useQSync hook. The kit's sync seam exists and works, and ships only a no-op adapter, so writing a real one is a genuine option that would also make the atomic-write rule structural instead of remembered. Pick one of the three, and write it down.

Whether you adopt ?hl=. The convention of returning from a create back to its list without losing position. Speedway owns the ?hl= name; walmart drives the same behaviour under ?job=. The canon calls ?hl= app-wide and it is not, which is a live inconsistency rather than settled law. Decide, and if you adopt it, say so somewhere a reader will find.

Which toolbar. The kit now ships FacetBar, the union of both product toolbars per the owner's 2026-08-14 ruling; its contract lives in packages/ui/docs/table.md. A greenfield app reaches for it first and becomes its first product caller; know that you are the proving ground. The older kit FilterBar and the two divergent app-local toolbars remain in the tree; the call-site ledger for all of them lives in 12-primitives-and-rules.md, cite it rather than restating counts here.

Build order#

The same outward-in order as a migration, for the same reason: a page built inside an unsettled shell has two layout owners.

  1. Shell. AppShell, Sidebar, Topbar.
  2. Page composition. Card, PageTitle, and the one-owner-of-inset rule (05 §A4, and reach for DataTable's bleed rather than hand-rolling negative margins).
  3. States, before any real page needs them. PageInfo, EmptyState, PageLoading, PageError, skeletons. Build the three-way branch once, truly empty against filtered-to-nothing against still-loading, and every later surface inherits it.
  4. The first real screen, from a recipe rather than from scratch.
  5. The app's own completeness, before anyone reviews a screen: a favicon (src/app/icon.svg), a title per route (metadata on every page.tsx, the app's name as the layout's title template), a not-found.tsx and an error.tsx in the kit's page-state idiom, the theme boot, and GET /build-info. None of these is a design decision and every one of them has been called out in an owner review after the fact (docs/GUIDEBOOK.md lapse row 10: no favicon or page titles at a release). The template carries them, and npm run doctor (scripts/app-doctor.mjs) names any that are missing with the fix, so an agent building the tenth screen is pushed to finish the app rather than reminded by this paragraph. Run it before the first review round and in CI. Its sibling npm run canon-check (the kit's bin) holds the canon feedback seam: a change touching UI paths adds a dated line to CANON-NOTES.md or carries a human overrule in the PR body, and the added lines are read for the deviation classes the retrospective names. The template ships both; keep both in CI (docs/plan/71-canon-feedback-loop-plan.md).

Build screens from the recipes, not from the component list#

The component docs answer "what props does this take". They do not answer "what does this screen need", and the gap between those two questions is where incompleteness comes from.

  • Asked for a list, table, queue, or index: 10-recipe-browsable-list.md. It is 27 rows of things that request brings along whether or not anyone said them.
  • Asked for a detail, preview, or drill-in: 11-recipe-record-detail.md. Answer its row zero, peek against modal against page, before writing anything.

Both recipes end in a do-not list mined from corrections on shipped code. Every entry is something that was built, shipped, and caught by a human looking at the screen. Reading those is the cheapest hour available to a new app.

Keep the canon reachable#

Two habits that cost nothing now and a lot later.

Bump the kit deliberately and regularly. The gap between what the canon documents and what your pin can reach is invisible until someone follows a rule that does not work. Speedway is the cautionary case: it declares ^0.0.14 against a kit at 0.0.25, and its dev tree hides that behind a symlink, so the divergence only appears in a clean install.

When you deviate, record why in your own surface's doc. Owner ruling D1 says a surface may break the canon's overlay tree when the break buys better and more consistent UX, and that the reason belongs in that surface's doc. Deviation is legitimate and never free. What the ruling retired was silent discretion, not discretion.

When you hit a rule the canon does not have, that is the canon's bug. Of 73 mined corrections across the two existing apps, zero were retrieval failures: every gap was law that had never been written down, not law someone failed to find. A third app will find more, and the docs only get cheaper for the fourth if what you learn goes back into docs/design-language/ rather than staying in your repo.

@versable-git/ui · reference, canon, and method, read in place