## Design system All UI work follows `packages/ui/DESIGN.md` — tokens, primitives, and the readable-row/mono/caps contracts. Compose pages from @sage/ui primitives; never inline card shells, arbitrary font sizes, or off-token radii. Forms use `Field` / `FieldRow` from @sage/ui. Never name a control with only a placeholder — it disappears on first keystroke. `label-caps` is a caps eyebrow over a figure, never a form label. ## Verification gates `pnpm check` runs lint + typecheck + prettier. **It does not run tests.** Before committing, run the whole package suite, not just the file you touched — `cd apps/web && npx vitest run`. A change that renames a label or a query can turn another suite red while `pnpm check` stays green; this has happened. Before a merge: `pnpm test` (whole monorepo). Also before a merge: update `docs/roadmap.md`. It is git-ignored and stays that way — a working journal, not published documentation; a fresh clone will not have it. Move the **Now** paragraph to what actually just landed, and tick the boxes the work closed. A stale entry is a false statement, not a gap. **It lists open work only** (recut 2026-09-19, from 2,479 lines to ~400). Do not add narrative about work already merged: `git log` records that, more reliably and already in public. Do not restate decisions either — those live in Claude's memory, which loads every session without anyone opening a file. The old file mixed all three, and one audit pass found nine entries claiming undone things that had shipped weeks earlier, the repo going public among them. Size was the cause. The pre-cut file is archived locally at `docs/roadmap-archive-*.md`, also git-ignored. ## Test fixtures Never paste a real broker export into a test. This repo is public. Fixtures seeded from one spread further than anyone expected: importer tests, unrelated suites, even a docblock, and individually none of them looked like a problem. Together they described one person's portfolio. Invent a ticker that keeps the shape under test and says what it is for: `THAMES.L` for the LSE suffix, `DUOMO.MI` for Milan, `NORDA-B` for a hyphenated Copenhagen listing. Universal placeholders (AAPL, MSFT, KO, O) are fine and stay — they reveal nothing. `sage-hygiene/no-identifying-tickers` enforces this for any symbol carrying an exchange suffix: they must appear in the `ALLOWED` set in the rule, comments included. It is an allowlist, not a list of banned symbols — a denylist would have to reprint the very tickers the sweep removed, and would not catch the next export somebody pastes in. So adding `KRONIX.CO` means adding it to the rule too; that friction is the point. If a real symbol is genuinely load-bearing, as in `yahoo.live.ts`, exempt the file and say why. Bare US-style tickers are not machine-checked — nothing distinguishes a real holding from the universal placeholders — so that part is on you. ## Running the app `pnpm dev` from the repo root starts Postgres in Docker, the API (:3001) and the web app (:3000) together. `curl localhost:3001/health` returns `{"status":"ok"}` once the API is up; a 503 means it is running without a database. ## Worktrees Create feature worktrees with the **EnterWorktree** tool, not `git worktree add`. A session that starts inside a manually-created worktree is isolated to it and cannot clean itself up afterwards — worktree removal has to run from outside the worktree, and the isolation guard blocks that. `EnterWorktree` pairs with `ExitWorktree(action: "remove")`, which removes the worktree and its branch and restores the original directory in one step. If a manually-created worktree does need removing, it is usually `locked` while a session is attached: `git worktree unlock ` first, then `git worktree remove `. **Git-ignored files do not exist inside a worktree.** A worktree is populated from the index, so `docs/roadmap.md`, `docs/superpowers/` and anything else in `.gitignore` are simply absent there. Since the roadmap has to be updated as the final step of every task, that update belongs in the main checkout — editing it from a worktree fails with "no such file", and creating it there would produce a second, divergent copy. The same applies to `.env`. A worktree also starts with no `node_modules`; run `pnpm install` before any `vitest` or `pnpm check` in one.