For an agent or a person bringing an app onto the kit, whether the app already exists or has not been started. It gives you the order of work that keeps a build or a migration from debugging its own seams, the pin and correction mechanics that decide whether a rule you are reading reaches the app you are in, and the retrospective on the two apps that did this first: what went in, what lapsed, and what would have caught each lapse sooner. The procedures themselves live in the install page, the app patterns and the contract docs; this file points at them where they exist.
Audience: an agent (or person) bringing an app onto the kit, migrating or new.
1. The four layers, and which one answers you#
The layer map is owned by docs/index.md (what each layer can do for you and
where truth lives for a component) and the ruled-against list by
docs/DO-NOTS.md. This guidebook does not carry a copy; a fifth copy is how
the copies drifted. One sentence survives because a migrator needs it before
anything else: existence is settled by the barrel export
(packages/ui/src/index.ts), never by intent or a plan doc.
2. Starting fresh, or bringing one over#
Two doors into the kit, and both procedures are already written down in
full: docs/app-patterns/05-starting-a-new-app.md for an app that does not
exist yet, docs/app-patterns/04-migrating-an-app.md for one that already
works. Read whichever door applies before writing a line. What this
section adds is what a procedure cannot state for itself: which door you are
actually in, and the traps that show up regardless of it.
Which door is decided by one question, not preference: does the app have
behaviour a user would notice losing? If yes, you are migrating, and the
migration guide's step zero is not optional, a parity ledger written before
the first replacement (docs/app-patterns/04-migrating-an-app.md:16). If
no, you are starting fresh, and you are exempt from that entire cost
(docs/app-patterns/05-starting-a-new-app.md:21-23).
Both doors share the same build order, and the same reason for it: shell
first, then page composition, then loading and empty and error states, then
the real screens, because a page built inside an unsettled shell inherits
two conflicting layout owners and you debug the seam instead of the page
(docs/app-patterns/04-migrating-an-app.md:100-116,
docs/app-patterns/05-starting-a-new-app.md:62-65).
Two traps neither doc can warn you about until you hit them. A local
component sharing a kit export's name makes every call site ambiguous until
it is renamed, and it is cheaper to write a second implementation than to
find the first one already shadowing yours, which is exactly why the
migration guide's step one exists (docs/app-patterns/04-migrating-an-app.md:57,
:64-66). And a kit export existing is not evidence it is the proven path:
FilterBar is complete, shipped, and used by nobody, because both existing
apps built their own toolbars instead
(docs/app-patterns/04-migrating-an-app.md:126-129). Check
packages/ui/docs/ for shipped usage before adopting a component on the
strength of it being in the barrel alone.
3. Reading order for an agent starting cold#
The governing copy of the cold-start reading order is AGENTS.md; read it
there, not here (four copies of this list existed and no two agreed). The one
stop the list cannot carry: after the reading, the awareness prompt on the
showcase page you are about to touch names that surface's own source and
contract doc (apps/playground/src/app/showcase-registry.ts:556), and it is
the fastest ground truth once you know where you are going.
4. Pins, and the upgrade you have to choose to take#
The kit publishes on the owner's bump, never as a ride-along on an unrelated
commit, and an app does not receive that bump automatically
(CLAUDE.md:39-41, :94-97). Both consumer apps stay unchanged until their
own deliberate upgrade; that is the union rule, the kit accumulating the
superset while apps hold their pin until they choose to move it
(docs/SCRIPTURE.md:93-96).
Where the pins stand and why they stay there, as of 2026-08-17: speedway pins
^0.0.14, walmart ^0.0.24, the kit is published at 0.2.0. Nothing built in
the week of 2026-08-10 runs in a shipping app yet. That is a decision, not a
lapse; the owner's ruling, verbatim: "I'm not updating speedway and walmart
right now because it serves us no biz value right now until there's actual
customer facing work to be done." The upgrade rides the next customer-facing
piece of work in each app, and section 6 below is what that upgrade costs.
The procedure itself, from pin to pin, is packages/ui/README.md, "Upgrading an app's pin", and
packages/ui/CHANGELOG.md says per release what changes on existing screens
and what an app can delete once it takes it.
The pin is the part that is easy to get wrong silently. Under npm semver, a
caret on a 0.0.x version is a patch pin, so ^0.0.14 resolves to exactly
0.0.14, not to whatever the kit has shipped since
(docs/design-language/00-overview.md:83,
docs/app-patterns/05-starting-a-new-app.md:31-33). A canon rule written
"as of kit 0.0.N" is unreachable in an app pinned below N regardless of what
the rule says. Check the app's lockfile against packages/ui/package.json
before relying on a prop, not after it fails silently at runtime.
What "deliberate" costs in practice, from a recent example: sidebar groups
became collapsible by default on 2026-08-16, so an app that upgrades past
0.2.0 sees carets on every labelled group unless it opts out with
collapsible: false (packages/ui/docs/app-shell.md:23). A version bump is
not free even when nothing in your own code changed; read the contract doc
for anything you use before taking the bump, not after.
5. How a correction becomes law#
A defect caught on a shipping screen is not fixed once, in place, and left for the next person to rediscover the same way. It has a path into something reusable, and the path is consistent enough now to name.
The worked example three docs already point at independently: a page
shipped a second theme toggle while the navbar already carried one, and the
owner named it an anti-pattern. The fix did not stop at removing the
toggle. It became a general rule in the AppShell contract doc's Banned
combinations, "do not mount a second toast stack, modal host, or
file-preview host per shell instance" (packages/ui/docs/app-shell.md:54),
and that doc's "Before you adopt this" checklist now opens by naming the
case directly (packages/ui/docs/app-shell.md:60).
docs/DO-NOTS.md picked both up automatically the next time it was
regenerated, because it reads every contract doc's Banned section by
construction (docs/DO-NOTS.md:22-26). And
docs/app-patterns/13-when-a-solution-is-unsuitable.md generalised the same
case into its first tell, something that only fits without a build because a
parent already renders it
(docs/app-patterns/13-when-a-solution-is-unsuitable.md:24-28).
The same lens also reads the canon directly, not only contract docs: any
sentence in the twelve design-language docs containing "never," "do not,"
"must not," "is a defect," or "banned" is picked up under docs/DO-NOTS.md's
"By canon doc" section without anyone filing it a second time, for example
"Mono is for identifiers... never decoration"
(docs/design-language/01-foundations.md:28, surfaced at
docs/DO-NOTS.md:230).
The two routes in, then: write the fix at its smallest true scope, a
component's Banned combinations section if it is one component's mistake, a
canon trait doc's own principle if it crosses components. Then regenerate
the lens, node scripts/build-do-nots.mjs (CLAUDE.md:37-38). Skipping
that step is the specific failure the scripture already names for the
general case: a rule nothing reads is not a rule
(docs/SCRIPTURE.md:399-400).
6. Validating a change#
Two typechecks and a real browser, at minimum, before any UI change is done.
Kit: pnpm typecheck and pnpm vitest run in packages/ui. Playground:
pnpm exec tsc --noEmit in apps/playground (both through fnm, AGENTS.md
rule 1). CI additionally runs pnpm lint:tokens, pnpm -r test, the citation
ratchet and the do-nots regeneration on every push
(.github/workflows/ci.yml), so a change that skips them locally reddens main. Then the page itself, in both themes, dark being the
literal theme name versable-dark (CLAUDE.md:14-19). This wave's own
landing build additionally gated at a 375px phone width alongside 1280px
desktop, with reduced motion emulated separately
(docs/plan/58-w2-1-landing-design.md:146-147); carry that habit into any
surface with real layout risk, not only the pages built under it.
Which review pass or skill fires for which kind of change is
docs/app-patterns/14-validating-a-ui-change.md: the categorical pass, the
adversary, the routing table, and, from the retrospective below, which checks
caught what and in which order. The two typechecks and the browser above are
the floor; that doc is the rest.
7. The retrospective: what went into speedway and walmart#
The owner asked for this half in the words "what went into speedway and walmart, the lapses, when each was noticed, what would have caught it sooner", and then ruled that no interview would feed it (doc 57 D8c): the material is gathered by agents from the record and checked by a second agent, with the owner's own review optional at the end. So everything below is cited or it is not here. The strata, in the order they were trusted: the three commit histories since 2026-06-20 (dated, attributable), the atone ledger of recorded mistakes with each event's precheck (the question that would have caught it), the sessions' own runtime notes, 235 owner messages that carry lapse-signal phrasing (quoted only verbatim, with their timestamp), and the plan docs. A machine digest of seventeen docs was used as a checklist and re-derived; five of its twenty-four candidates could not be grounded and are absent. A separate adversarial vet then checked every citation mechanically, trimmed thirteen lapse rows of a citation that did not hold, merged two timeline rows, and corrected four rows that had claimed a remedy postdated the lapse it would have caught when the remedy predated it (the run's most common draft defect, and it flattered the record in one direction). What the vet could not check is listed at the end of this section.
7.1 The timeline#
| Date | App | What happened | Kind | Evidence |
|---|---|---|---|---|
| 2026-07-06 | Shared | Owner scopes the kit as a "repo forge": the reskin's engineering cost is justified by getting a reusable system the next sweep builds on and the current MVPs draw back from | Plan | owner-signal ts 2026-07-06T23:13:43.518Z (hits 6) |
| 2026-07-07 | Shared | Monorepo bootstrapped and packages/toolkit ported the same day; the kit's own first wave hand-copies from the toolkit instead of importing it | Plan/Lapse | versable-builder@bab3536, @d5427d4, @cb1e557; docs/plan/43-toolkit-rca.md:27 |
| 2026-07-13 | Speedway | First full owner review, 30 hits: the workspace selector loads as a separate page before the app shell, and highlighting a sidebar row underlines all of its text | Lapse | owner-signal ts 2026-07-13T07:23:19.609Z (hits 30) |
| 2026-07-16 | Speedway | doc-22 surgery: R2 writes the jobs and parts pages fresh instead of porting the products surfaces; R4 purges the newly orphaned components as dead by import graph | Ship/Lapse | speedway@a1a62a3 (R2), @fe2656d (R4); atone mist-20260716-074938-54 |
| 2026-07-16 | Speedway | Owner opens the restored UI and finds it does not look restored; the restoration had been verified against its own rebuild, never against the mock | Lapse | atone mist-20260716-112917-31; .claude/skills/runtime-notes.md:266, session "UI-regression recovery + ipc forensics [catch-ipc-9e]" |
| 2026-07-17 | Speedway | Owner feedback batch captured verbatim and chunked into doc 25 under an explicit per-chunk review gate; the F1 finalization plan (owner directive 2026-07-18) then orders function before cosmetics | Lapse/Plan | docs/plan/25-ui-feedback-chunks.md:5, :34, :40 |
| 2026-07-18 | Speedway | Taxonomy and Attributes viewers ship without the DataTable pagination model every sibling list uses; TypePeekPanel is hand-rolled instead of reusing the sidebar peek-panel family, twice the same day | Lapse | atone mist-20260718-101437-d7, mist-20260718-204614-20 |
| 2026-07-20 | Shared | Archive sweep files the toolkit's design doc among 25 "shipped" plan docs without checking that doc's own canary rule, the one criterion of its four that was unmet | Lapse | versable-builder@86b4d12; docs/plan/43-toolkit-rca.md:29 |
| 2026-07-23 to 07-24 | Speedway | Design mocks never consulted: the doc-32 program ships legacy module labels instead of the mock's; WorkflowStageChain ships as a card grid instead of the mock's ruled banner and stepper | Lapse | atone mist-20260723-140712-be, mist-20260724-141200-12 |
| 2026-07-27 | Speedway | /bloop is invoked by name for three UI items; its mandatory adversarial gate is skipped for all three, and they are reported and board-marked done anyway | Lapse | atone mist-20260727-223728-20 |
| 2026-07-28 | Speedway | SetupHoldBanner ships with an em dash and default card padding, unrendered; the same-session remediation then deletes the title instead of rewriting it and reports it fixed with no render | Lapse/Correction | atone mist-20260728-180931-99, mist-20260728-182222-d6 |
| 2026-08-07 | Walmart | Release round, three lapses in one day: production runs Mona Sans and Red Hat Mono against the canon's Inter and JetBrains Mono with no favicon and "Loading…" text in about 15 files; the auth-boot skeleton draws bars over statically known chrome, contradicting the canon doc read that same session; the route-boot fallback renders the dashboard skeleton for every unmapped route including /import | Lapse | atone mist-20260807-111459-c5, mist-20260807-115856-c7, mist-20260807-142844-a2; fix walmart-mvp@41e1b0f |
| 2026-08-09 | Speedway | Product development frozen for stabilization; from here speedway gets UI and kit work only, no product behaviour changes | Ship | CLAUDE.md "Speedway is frozen for product work"; corroborated docs/plan/46-speedway-conventions-sweep.md:5 |
| 2026-08-12 to 08-13 | Walmart | Release handover doc assembled: four coexisting Select implementations, six local components shadowing kit export names, two search boxes on the Jobs page | Lapse | walmart-mvp/docs/04-technical-debt.md:67, :75, :105 |
| 2026-08-13 | Shared | Toolkit RCA (doc 43, task #107) roots the zero-consumer finding: about 82% of the pluralize-shaped duplication the toolkit exists to prevent was written after it already existed | Correction | docs/plan/43-toolkit-rca.md:31; versable-builder@d213014 |
| 2026-08-13 to 08-14 | Shared | Speedway conventions sweep (doc 46, task #91) finds the app-wide chip-suppression violation: every facet trigger renders count-only, activeChips is computed and never mapped to JSX | Lapse | docs/plan/46-speedway-conventions-sweep.md:33 |
| 2026-08-14 | Shared | FacetBar, the union of both apps' toolbars, ships without walmart's own documented design reasoning; owner calls out the toolbar coherence on sight, fixed the same day | Lapse/Correction | atone mist-20260813-192114-c8; fix versable-builder@f5cf96b |
| 2026-08-15 | Shared | Full adversarial usability audit of the kit (doc 50): 19 contract-doc blockers, a reverted button-contrast fix, three unreturned-focus blockers, a registry 401 with no token guidance in the README | Lapse | docs/plan/50-usability-audit-report.md:71, :83, :135 |
| 2026-08-16 | Shared | Kit 0.2.0 published, and the generated do-nots lens lands the same day; the citation checker had landed two days earlier | Correction | versable-builder@27d3616 (08-16), @4caf897 (08-16), @27a10f1 (08-14) |
| 2026-08-17 | Shared | An opus adversarial pass on the landing page catches what the author's own categorical pass had passed: the strip grid overflowing by 95px at a 375px viewport, a class that beat the Button's own colour rule, an inline animation delay the reduced-motion guard cannot reach, and five small-text inks under AA in light | Correction | .claude/skills/runtime-notes.md:7 (session "review round 1 wave 2b … [catch-fable-7c] · 2026-08-17"); docs/app-patterns/14-validating-a-ui-change.md:77 |
7.2 The lapses#
| # | What | App | How it was noticed | The fix | Recorded in | What would have caught it sooner |
|---|---|---|---|---|---|---|
| 1 | The toolkit's own spec gated "stable" on a real consumer (the canary rule); the 2026-07-20 archive sweep filed it as shipped with that criterion unmet, and the only doc carrying the consumption requirement left the live planning surface | Shared | audit/RCA, task #107 | RCA root-caused it; tasks #107 and #108 track promotion into the kit | docs/plan/43-toolkit-rca.md:13, :29, :32 | The archive sweep read each doc's status but never that doc's own definition of done. No check for this existed then and none exists now; the RCA is the first artifact that reads a spec's exit criteria back against its archive state |
| 2 | R2 rebuilt the jobs and parts pages fresh instead of porting the products surfaces; R4, in the same round, purged the newly orphaned components (ProductPeekPanel, ProductPreviewModal, ProductsToolbar, FilePreview drill) as dead by import graph | Speedway | owner (sidebar, built-up table, empty states, chip behavior gone) | git-history parity audit, restore and rewire onto job-scoped data | atone mist-20260716-074938-54 | Recorded precheck: was the component dead BEFORE the rebuild or orphaned BY it (git log its importers)? The instrument now exists: ~/.claude/rules/invariant-graduation.md and its "UI surface purge gate" were graduated directly from this incident, citing doc-22 and versable-builder@3669c11 |
| 3 | The restoration from row 2 was verified by re-exercising the restoration itself; the design mock ran untouched on pm2 and the reference worktree was never served | Speedway | owner (jobs and review pages "do not look restored") | mock adopted as the visual gate; see diff and /vis-compare run per page | atone mist-20260716-112917-31 | The instrument already existed and was not reached for: /vis-compare was added 2026-07-11, five days before this, and the atone records that it and see-diff "were never invoked". Recorded precheck: did I render BOTH the reference and my result and compare them visually? |
| 4 | Taxonomy (2,452 rows) and Attributes viewers shipped rendering their full row set with the plain kit Table while every sibling list page paginates via the DataTable model | Speedway | owner (page slowdown on the deployed preview, repeat feedback) | both viewers converted to the paginated DataTable model in the same owner batch | atone mist-20260718-101437-d7 | Recorded precheck: does the nearest sibling list page paginate? If yes and the new one does not, the model goes in before the commit. The sibling sweep was run for loader, PageTitle and nav, and skipped for the table specifically |
| 5 | TypePeekPanel hand-rolled a fresh attribute list and a copied footer escape link instead of auditing the sidebar family (ProductPeekPanel, ActivityRail) and reusing the attributes page's existing table | Speedway | owner ("one global sidebar shell") | shared PeekPanel wrapper extracted and consumed by every sidebar; AttributeSchemaView extracted and shared | atone mist-20260718-204614-20 | Recorded precheck: rg the surface family and diff its shells before adding a new member. The morning's same-slug atone (row 4) existed by then and its lesson had been generalized too narrowly, to tables and pagination rather than to all surface families |
| 6 | The doc-32 program shipped legacy module labels and a post-create scrape-setup surface; the owner's mocks, never requested or read across the whole engagement, specify different labels and sites-at-creation | Speedway | self-caught | same-hour relabel to the mock's names with nav reading one map; create modal gained per-module setup | atone mist-20260723-140712-be | Recorded precheck: does a design mock or spec exist for this surface, and has it been read? The instrument now exists as the standing [design-mocks] rule in ~/.claude/rules/testing.md, which mandates grepping for mocks before any user-facing UI work |
| 7 | WorkflowStageChain shipped as a card grid extracted from the existing StageRail instead of the mock's ruled banner, stepper and enumerated stage list, and was verified only by text presence | Speedway | self-caught (re-opening the mock at build time) | rebuilt to the mock's structure with screenshot-versus-mock comparison | atone mist-20260724-141200-12 | Recorded precheck: is the mock's markup for this exact view open right now, and does a screenshot show the same layout primitives? Same-session family repeat of row 6, which means the [design-mocks] rule was the instrument and the gap was reaching for it at build time rather than at plan time |
| 8 | SetupHoldBanner shipped to production with an em dash, a two-sentence explainer, and default card padding the owner has repeatedly called too large, deployed without rendering it | Speedway | owner | copy rewritten and padding tightened; view and setup affordances separated | atone mist-20260728-180931-99 | The instrument already existed and was skipped: /ui-categorical-check was added 2026-07-16, twelve days before this, and the atone records skipping "the one skill built from this owner's own feedback history". It is now named as a required pass in docs/app-patterns/14-validating-a-ui-change.md |
| 9 | The same-session remediation of row 8 deleted the bold title instead of rewriting its wording, changed padding on the inner div while the kit Card's own card-body padding stayed untouched, and was reported "fixed and live" with no render performed | Speedway | self-caught | read the Card padding contract, restored a short plain title, set padding on the owning element, screenshotted before reporting | atone mist-20260728-182222-d6 | Recorded precheck: do I have a screenshot of the changed element from this turn, and did I open the component that owns its padding? The atone notes an earlier atone that same session whose entire lesson was to render UI before claiming |
| 10 | Walmart shipped to production running Mona Sans and Red Hat Mono against the canon's Inter and JetBrains Mono, with no favicon or page titles, "Loading…" text in about 15 files, and a hand-rolled brand lockup | Walmart | owner's morning review | font token overrides deleted so the kit wins, speedway's logo lockup, AuthCard, favicon and titleFor() ported, three seats dispatched for the mono, em-dash and skeleton purge | atone mist-20260807-111459-c5; fix walmart-mvp@41e1b0f | Recorded precheck: DOM-inspect computed font-family and weight on body, heading and button in BOTH apps before the word parity is used. Parity had been scoped to the mock-parity ledger's enumerated features, and typography, loading language and brand were never ledger rows. Since 2026-08-19 the favicon, page titles, 404, error boundary, theme boot and build-info half of this row is a runnable check: npm run doctor (apps/_templates/next/scripts/app-doctor.mjs), carried by the app template and app-forge-v6 |
| 11 | Walmart's auth-boot fallback drew skeleton bars over statically known chrome (sidebar labels, brand, page title), contradicting the loading-and-states canon doc read the same session; the org-switcher new-org modal was restyled without ever being opened | Walmart | self-caught | auth-boot rebuilt to render real chrome instantly with skeletons only on data slots; the org modal opened and fixed in the browser | atone mist-20260807-115856-c7 | Recorded precheck: is this element's content known at build time (then render it as itself), and did I open every modal and dropdown the diff touched? The canon doc carrying the static-content exception was demonstrably in context and its exception was not applied |
| 12 | Walmart's routeBootSkeleton fallback returned HomeSkeleton for every unmapped route, so booting into /import, a primary nav destination, rendered the dashboard's loading layout; verified only by booting into / | Walmart | self-caught | per-route skeleton for Import, neutral title-and-card bone for unmapped routes, / alone mapped to HomeSkeleton | atone mist-20260807-142844-a2 | Recorded precheck: does the fallback ever show one page's layout on another page, and did I boot-test every primary nav route? The wrong-page fallback was consciously accepted as good enough for routes judged rarely hit |
| 13 | The kit-canon-tree proposal stated packages/ui/docs held 8 per-component docs and enumerated 8 filenames; it held 15, and the count was load-bearing for the doc's central argument that both candidate homes are already full | Shared | peer session review (vb-fable), which checked four claims and found this the only failure | corrected to 15 with the full filename list and a provenance note | atone mist-20260809-145934-d9 | Recorded precheck: did I run the producing command (ls | wc -l, npm view, git log, rg -c) in THIS turn for any number written into a doc others will cite? The atone names the root cause precisely: things framed as claims got verified, things framed as background were free-handed |
| 14 | formatNumber's fail-soft placeholder for a live NaN on walmart's admin metric grid was chosen as an em dash, which would have been the only violation of the zero-em-dash-in-rendered-UI rule across pages/, features/ and components/, on the first production deploy tag | Walmart | peer session review (vb-fable) | fixed to "n/a", matching the app's existing absent-value vocabulary | atone mist-20260809-212124-e5; fix walmart-mvp@7c16a88 | Recorded precheck: have I grepped how the app already spells this concept before writing any literal that renders, including a single character? The generalizable half was folded into docs/app-patterns/00-data-loading.md. The aggravating fact is that the same agent had written that constraint into three canon documents in the same session |
| 15 | The owner asked three times for the /admin and /spec-viewer UI to be improved; each answer delivered a measurement review, defect fixes and a content-honesty tweak instead of the visual design pass meant | Walmart | owner (repeated, then escalated, then /atone twice) | the actual visual pass built on both pages below the fold and verified rendered | atone mist-20260811-142119-a5 | Recorded precheck: has the user now asked for this twice? If so the CATEGORY of the delivery was wrong, not the amount. The clarifying question that was asked framed the fork in the agent's internal vocabulary, not the owner's |
| 16 | doc-12's second pass claimed no playground gallery exercises Table's skeleton mode; the table gallery demos it (apps/playground/src/app/components/table/gallery.tsx:192-196 today, the same lines in table/page.tsx before the gallery split). The claim was pushed to origin about an hour before the self-catch | Shared | self-caught about an hour later | same-day correction commit amending the FREE entry, the item-1 stub, the closing paragraph and task #6's description | atone mist-20260813-182753-08; defect shipped in versable-builder@f67357b, corrected in @e339924 (both 2026-08-13) | Recorded precheck: did the grep backing an absence claim carry ANY filter beyond the bare symbol? The compound grep's display filter silently excluded a bare JSX boolean prop at end of line. The atone names the asymmetry: presence claims in that session all got spot verification, absence claims did not |
| 17 | FacetBar, the union of speedway's and walmart's toolbars, shipped with speedway's Button trigger, checkboxes on the opposite end from Select's check, no option subtitle or count, a mismatched label scale and shorter chips | Shared | owner (toolbar coherence called out on sight) | same-day coherence pass: the trigger wears the Select skin, one menu anatomy, Select options gained description and count, heights, radius and fonts measured equal before commit | atone mist-20260813-192114-c8; fix versable-builder@f5cf96b | Recorded precheck: measure height, font size and border radius of each unioned control side by side before shipping a composed toolbar. Walmart's in-source comment stating the trigger must wear the Select skin was read during the audit and not carried into the union |
| 18 | Speedway's shared FilterBar renders every facet trigger count-only (FilterBar.tsx:210-222), computes activeChips (:177-189) and never maps it to JSX, so no list in the app shows what is currently filtered; the file's own docstring (:107) still claims chips render | Speedway | conventions sweep (doc 46, task #91) | fix is app-side, not kit-gated; walmart already ships the correct version as FacetChips in walmart-mvp@5fa032c | docs/plan/46-speedway-conventions-sweep.md:33, canon docs/design-language/07-toolbars-and-filters.md:25 | The sweep itself is the instrument, and it was the first app-wide audit of speedway against the canon; no per-commit check reads a canon ruling back against app code and none exists today. The timing is explanatory, not damning: the owner's 2026-08-12 refinement to doc 07 A4 postdates the 2026-08-09 freeze, so speedway could not have received it |
| 19 | Walmart shadows at least six kit component names with unrelated local implementations (StatusDot, DataTable, EmptyState, Tabs, Timestamp, SidePanel); Home.tsx:3 imports the kit's StatusDot while Workspace.tsx:9 and CatalogDetail.tsx:29 import the local one, so the tag name alone tells a reader nothing | Walmart | found while writing the kit component contracts, 2026-08-13 | not yet fixed; named as the first thing to resolve in any kit migration here, because every keep-or-replace decision depends on it | walmart-mvp/docs/04-technical-debt.md:75 | No adoption checklist existed on the contract docs at build time. One exists now: "Before you adopt this" carries a naming-collision question and is present on 32 of the 33 files in packages/ui/docs/ (all but patterns.md), added under the owner ruling of 2026-08-16 |
| 20 | Walmart runs four coexisting Select implementations (the kit Select, a hand-rolled MultiFacetControl at FacetToolbar.tsx:65, a 97-line Radix wrapper serving three call sites, and raw <select> elements) because the kit's Select had no multi-select mode when walmart needed one | Walmart | tech-debt review, found 2026-08-13 | kit Select gained a multiple mode 2026-08-16 by owner ruling, and FacetBar retired its private multi control | walmart-mvp/docs/04-technical-debt.md:43 ("Blocked on a kit version") and :67 ("Open in this repo, unclaimed"); fix versable-builder@bd3c1ae | This is a build-order gap rather than a decision error: select.types.ts recorded multi-select as deferred to "Week-2 presets" and it was never built. The instrument that closed it is the walmart tech-debt review of 2026-08-13, the first artifact routing app-blocked-on-kit items back to the kit; no such channel existed in the five weeks before it |
| 21 | The contract-doc audit found 19 blockers across two failure shapes, one of them citations drifted far enough to point at contradicting code (page-info's tone citations where four of five cited lines carry unrelated content; tabs and timestamp citing table markup) | Shared | usability audit (doc 50, contract-docs facet) | citations repaired; 12 of 25 docs were already perfectly clean | docs/plan/50-usability-audit-report.md:71 | No mechanical citation checker existed. One exists now: scripts/check-citations.mjs landed 2026-08-14 under ruling D11 (versable-builder@27a10f1, "666 citations clean") and runs on every canon and contract-doc edit |
| 22 | A white-on-amber contrast failure was fixed at the token level with the arithmetic recorded in a comment (theme/colors.css:168-172), then silently reverted for every filled Button by theme/components.css:19-30, which nobody diffed against the token layer | Shared | usability audit (doc 50, accessibility facet), called its sharpest single finding | disposition deferred to the owner; verification recommended raising it from major to blocker | docs/plan/50-usability-audit-report.md:83 | No drift guard compares a token-layer value against its component-layer consumer. The do-nots lens now catches canon "never" language automatically (scripts/build-do-nots.mjs, docs/DO-NOTS.md), but a numeric token-versus-component drift guard is still unbuilt |
| 23 | Select, FacetBar and InlineEdit all move DOM focus into a floating or swapped element and never return it on close; Modal is the only one that gets this right, through native <dialog> | Shared | usability audit (doc 50, accessibility facet) | one shared return-focus utility resolves all three blockers (E F2 to F4) | docs/plan/50-usability-audit-report.md:83 | No shared focus-management primitive existed when each floating component was built independently, so each one solved the same problem alone or not at all. The audit is the first review that read the three side by side |
| 24 | A fresh consumer following the kit's README hits a registry 401 with no token guidance anywhere, the README's own DataTable example does not typecheck (rows is required and absent), the docs directory both READMEs point at is not in the published files allowlist, and tailwindcss is not declared as a dependency at all | Shared | usability audit (doc 50, DX facet), five findings all confirmed | flagged, not fixed at audit time | docs/plan/50-usability-audit-report.md:135 | Nobody had exercised the README as a fresh consumer would, with a cold install and a copy-paste, in the weeks between the kit's first publish and this audit. The audit notes that until these land, the "fresh adopter composes without steering" criterion cannot be evaluated at all |
| 25 | Walmart's Jobs page renders two search boxes, one in the filter row and one below it | Walmart | spotted 2026-08-12 during unrelated verification; the doc itself records it as never confirmed as a defect | none yet | walmart-mvp/docs/04-technical-debt.md:105 | /ui-categorical-check measures exactly this class, two controls doing the same job on one screen, and it has existed since 2026-07-16, well before this surface was built. It is not run on walmart; docs/app-patterns/14-validating-a-ui-change.md names it as a pass for shared-surface changes in the builder repo |
| 26 | The workspace selector loaded as its own page before the app shell; the owner opened the 30-item feedback batch with it and wrote "Again", indicating it had been raised before | Speedway | owner, verbatim and repeated | resolved in the F-round rebuild; the shell loads first with the selector inside or over it | owner-signal ts 2026-07-13T07:23:19.609Z (hits 30) | No shell-first build order was written down at the time. Both app-patterns docs now carry one: docs/app-patterns/04-migrating-an-app.md:102 ("Work outward from the shell, because a page migrated inside an unmigrated shell…") and docs/app-patterns/05-starting-a-new-app.md:65-67, each putting AppShell, Sidebar and Topbar first in its build order |
| 27 | Dev backend credentials (gcloud ADC) expired mid-build and speedway rendered the failure as a raw grpc dump; the handoff to the owner named neither the prerequisite nor its one-line fix, though the same failure had been personally hit and diagnosed two days earlier in the same build | Speedway | owner opened the app to the raw dump | root ErrorBoundary now recognizes the GCP-reauth class, showing recovery commands in dev and admin copy in prod, verified against the live expired credential | atone mist-20260712-210531-07 | Recorded precheck: what did I have to fix or start this session for this surface to work? Every answer belongs in the handoff with its fix command. The atone names the deeper move: a failure personally hit during a build was routed around as an environment obstacle instead of fixed as a product defect |
| 28 | /bloop was invoked by name for three UI items, twice by name; its mandatory adversarial validation gate was silently skipped for all three and they were reported and board-marked done, while that same gate had already produced 23 findings including 3 ship-blockers on a different build in the same session | Speedway | self-caught | the gate run retroactively on the shipped diff before continuing to report the items done | atone mist-20260727-223728-20 | Recorded precheck: name each mandatory phase of a named process and state whether it ran. The omission was invisible because "gate skipped" was never written down anywhere, and partial rigor (live screenshots) was allowed to stand in for the missing phase |
7.3 The families that recur#
Family 1: a rebuild replaces accumulated UX without a parity audit. Rows 2,
3, 10, 11, 26. A page or an app gets rewritten from scratch instead of ported,
and the rewrite is verified against itself rather than against the reference the
owner remembers, so the gap surfaces only when the owner opens the page. The
instruments that exist now: ~/.claude/rules/invariant-graduation.md and its
"UI surface purge gate", graduated directly from row 2 and requiring a parity
ledger or explicit owner approval before any owner-reviewed surface is deleted
or replaced; /vis-compare for rendering the reference and the result side by
side, which already existed at the time of row 3 and was not invoked; and
docs/app-patterns/04-migrating-an-app.md:16, whose Step 0 is writing the
parity ledger before anything is replaced.
Family 2: a local surface diverges from the canon or a design mock and ships
unverified against it. Rows 6, 7, 8, 9, 14, 17, 18. A component or a piece of
copy is built from memory, from a nearby sibling, or from a cheaper extraction
instead of the mock or the canon rule that governs it, and the divergence is
caught only when the owner looks at the pixels. The instruments that exist now:
the [design-mocks] rule in ~/.claude/rules/testing.md; docs/DO-NOTS.md,
generated by scripts/build-do-nots.mjs over every contract doc's Banned
section and every canon "never" sentence; the "Before you adopt this" checklist
on 32 of the 33 files in packages/ui/docs/; and /ui-categorical-check for
the measurable classes of drift, which predates rows 8, 9, 14, 17 and 18 and was
reached for in none of them.
Family 3: the instrument was absent, not the judgment. Rows 1, 13, 16, 21,
22, 23, 24. These are not bad decisions but missing or unfindable checks: no
definition-of-done check on an archive sweep, no citation checker, no drift
guard between a token and its component-layer consumer, no fresh-eyes README
exercise. The instruments that exist now: scripts/check-citations.mjs (ruling
D11, versable-builder@27a10f1, 666 citations clean at that run) and the
do-nots lens (scripts/build-do-nots.mjs, docs/DO-NOTS.md). Still unbuilt: a
numeric token-versus-component drift guard (row 22) and any recurring
fresh-consumer exercise of the README (row 24).
Family 4: a new surface skips the sibling or naming sweep. Rows 4, 5, 19,
20, 25. A page or component is added without diffing the nearest sibling that
solves the same problem, producing a missing pagination model, a re-authored
panel shell, a shadowed component name, or a duplicated control on one screen.
The instruments that exist now: the sibling-diff prechecks recorded in rows 4
and 5; the naming-collision question that opens the "Before you adopt this"
checklist in packages/ui/docs/; and the walmart tech-debt review
(walmart-mvp/docs/04-technical-debt.md) as the channel that routes
app-blocked-on-kit findings to the kit. Row 20 is the family's exception: the
kit genuinely lagged the app's need, and its remedy is release cadence rather
than a check.
Family 5: a process declared complete without its own steps having run.
Rows 12, 15, 27, 28. A route set, a build gate, a handoff or a UI pass is
reported done because its name was invoked or its happy path was seen once,
while the steps that make it done (boot into every mapped route, run the gate
that was named, hand over the prerequisite you yourself hit two days earlier,
build the pass that was asked for three times) never ran. This is the shape the
atone ledger records most often across every project, sixteen events by
2026-08-17, and it graduated to a rule with a mechanical gate:
~/.claude/rules/exercise-based-verification.md (run the changed path in the
state that matters before saying done; a collect, a compile or a lint is not a
run) and the declared-ready Stop hook behind it. In this repo the same rule is
CLAUDE.md's own bar for done: exercised in the running playground, in both
themes, not merely typechecked.
Rows 12, 15, 27 and 28 were left unclaimed by the vet, which named their shared
shape and left the fifth family to the author; the atone slug behind them is
declared-ready-without-runtime-exercise.
7.4 What this half is built from, and what it could not check#
Method: /pyramid-sweep's spine (manifest, extract, mechanical cut, one
synthesis seat, one adversarial vet, human bake) with the instruments swapped
for this domain: the sessions had already distilled themselves into commits,
atone events and runtime notes, so raw transcripts were mined mechanically for
the owner's own words and nothing else. Run dir:
~/.claude/style/sweep/20260817-guidebook-retro/ (candidates, the draft, the
vetted output with its per-row log).
Not checked, stated so it is not mistaken for checked: whether the five-family
cut is the right taxonomy (families are an interpretive claim; the vet checked
only that each row is claimed once and every remedy cited exists); the 2026-07-13
owner batch beyond its opening 2,500 characters; and whether row 25 is a defect
at all (walmart-mvp/docs/04-technical-debt.md:105 states the two search boxes
as fact and records that the finding was never confirmed).
The owner's optional review of this half is task #92; if it happens, its corrections land here as further rows or as edits with their own citation.