Repository navigation
Description
Three rules the SPA look-and-feel pass paid for. Filed rather than folded into #661, whose simplicity ceiling is web/src/styles.css plus two guard files.
Full retro: docs/designs/651-652-spa-elevation-and-caps.md and PR #661's review history.
1. A selector an issue names is dead until its call sites are counted
Three selectors named across #651 and #652 had moved on before the work started:
| Selector | Reality at 396ba233 |
|---|---|
.stat / .stat-label |
did not exist — #654 replaced them with .capture-tile* |
.eyebrow |
zero call sites — deleted as dead in #661 |
.toolbar |
zero call sites — restyled anyway, so that half of #651 changes nothing on screen |
The first two were caught by reading the shipped code before designing. The third was not, and only surfaced when the owner compared before/after screenshots and said they looked the same.
Proposed rule, near the guard-writing section: before styling a selector an issue names, run grep -rn "<class>" web/src --include='*.tsx' and record the count in the design. Zero is a legitimate answer — .toolbar is deliberate groundwork for #653 — but it has to be a stated decision, not a discovery after merge.
2. Removing a text transform makes every string it transformed a caller
.badge applied text-transform: uppercase, so two i18n strings had been authored lower case at source because the stylesheet capitalised them. With the transform gone they rendered no entry and yes — typos, in all three locales. Now pinned by web/src/i18n/badgeCase.test.ts.
Proposed rule, in the i18n conventions: a string rendered inside a .badge starts with a capital; and more generally, when a presentational transform is removed, every string it was transforming is a caller in the #394 sense and must be read.
3. A screenshot comparison is a verification step, not a PR decoration
It is the only check in this repo that reads the rendered result. On #661 it found the one product defect that four review seats, three CI Playwright runs and the driver's own verification all passed. Two mechanics matter:
- Capture at 1:1. The first comparison was downscaled to 900px wide, which destroys exactly the 1px hairlines and 4–5% alpha shadows a styling change is about — it made a real change look like no change.
- Rebuild first. A long-running sim stack serves the bytes it was built from, not the branch you are on.
Proposed rule, beside the SPA E2E section: any PR whose purpose is visual attaches a 1:1 before/after comparison, captured from a stack rebuilt at the head under review.
4. Runbook note — reset.sh from a git worktree
tools/simulation/reset.sh fails immediately in a worktree with tools/simulation/.env.sim not found: that file and .sim-cast.json are gitignored and exist only in the main checkout. Copy both into the worktree first. tools/simulation/ui/ also has no node_modules there — but the screenshot specs drive the stack over HTTP, so they can be run from the main checkout against a worktree-built stack.
Belongs in docs/runbooks/simulation-fixture-on-a-dev-database.md.
Activity
docs(sim): commit the dashboard screenshot, capture the palette matri…
mforce commentedon Sep 2, 2026
Shipped in #665, merged as
930ea308. All three conventions are inAGENTS.md, in the file's own form — one paragraph each, bolded lead, reasoning inline, issue number in the text..toolbarwas restyled with zero consumers, so half of SPA: elevation hierarchy — stop using one card treatment for stat cards, panels, toolbars and entry panes #651 changed nothing on screen..badge's uppercase had let two i18n strings ship lower-case at source; removing it renderedno entryandyes.The runbook note also landed in
docs/runbooks/simulation-fixture-on-a-dev-database.md:reset.shfrom a git worktree fails on the gitignored.env.sim, andtools/simulation/ui/has nonode_modulesthere, but the capture specs drive the stack over HTTP so they can run from the main checkout.fix(i18n): tl help text names the saleable flag and unit-system setti…
fix(web): centre the picker glyph on any field height and harden the …
docs(720): 1:1 before/after of the sales order lines table (#662)
feat(sales): snapshot the list price on the order line and show the d…
feat(sales): mark discounted lines, total the discount, and show it i…
feat(audit): show the sales-line audit payload as a readable Details …
fix(sales): keep a line's discount markers agreeing while its price i…
test(web): raise the paging suites' per-test budget, not the run's (#750
docs(agents): require screenshots on any PR that changes the UI
docs(agents): drop the commit and push gate, and require screenshots …
chore(main): release 0.1.0 (#542)
fix(i18n): tl glossary uses the standard passive of ilagay (#813)
test(e2e): run the smoke suite at phone width, not just 1280 (#817)