Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 275 additions & 0 deletions docs/design-system/HANDOVER-2026-08-07.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
# Design system — handover, 7 August 2026

**Read `AGENTS.md` first — it is the highest-priority source of truth for rules and gates.
Then point a fresh session at this file.** It carries the measured state, the work that
landed, the places the previous backlog was **wrong**, and the ordered plan. It exists so
the next session does not re-derive any of it — several of the corrections below cost a
full session to establish.

- **Companion documents:** [README.md](README.md) · [SPEC.md](SPEC.md) ·
[TOKENS.md](TOKENS.md) · [COMPONENTS.md](COMPONENTS.md) · [GATES.md](GATES.md) ·
[DECISIONS.md](DECISIONS.md) · [ADOPTION.md](ADOPTION.md)
- **Machine truth:** [`adoption-manifest.json`](adoption-manifest.json) (generated — never
hand-edit) · [`adoption-contract.json`](adoption-contract.json)
- **Adjacent contracts:** [`docs/search-chrome-behaviour.md`](../search-chrome-behaviour.md) ·
[`docs/wiring-conventions.md`](../wiring-conventions.md) ·
[`docs/outstanding-issues.md`](../outstanding-issues.md)

---

## 1 · Measured state

All figures measured on `origin/main` at handover, not recalled.

| Metric | Value |
| ------------------------------------------ | ------------------------------------------------ |
| Registered visual components | 53 |
| Product-adopted | **29** |
| Unadopted (zero product imports) | **24** |
| Open rows in COMPONENTS §0.4 defect ledger | **13** open, 11 done |
| Gates | **7 implemented-blocking**, 6 partial, 2 planned |
| Prohibitions with no gate at all | **6** |
| Committed visual baselines | **0** (deliberate — see §5) |
| Specified but not built | 23 P1 + 20 P2, outline only |

The 24 unadopted: `AnswerFooter` `Button` `Checkbox` `Citation` `CitationList`
`ConfirmDialog` `Disclosure` `DisclosureGroup` `DoseLine` `DownloadLink` `ErrorSummary`
`ExternalTextLink` `FieldError` `FieldHint` `LinkAction` `Pagination` `Progress`
`RadioGroup` `SearchField` `StageList` `Tabs` `TextLink` `ToastRegion` `Tooltip`.

Regenerate any of this with:

```bash
npm run design-system:adoption:update && npm run check:design-system-contract
```

---

## 2 · What landed on 7 August 2026

| PR | Commit | What |
| ------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [#1655](https://github.com/BigSimmo/Database/pull/1655) | `cc349d130` | Gate 3's false `--focus` evidence, GATES §1's false "type-step unenforced" note, three stale §0.4 rows, ToggleSwitch knob onto `transform` |
| [#1658](https://github.com/BigSimmo/Database/pull/1658) | `d9221389c` | `AnswerCard` adopted on the live answer surface; `#207`-vs-`#227` resolved. Adoption 27 → 29 |
| [#1663](https://github.com/BigSimmo/Database/pull/1663) | `82b6f5a4c` | Five named tracking tokens + `arbitraryTracking` contract ratchet |
| [#1669](https://github.com/BigSimmo/Database/pull/1669) | open at handover | 68 tracking sites across 40 files migrated; ratchet baseline 68 → **0** |

### The clinical decision recorded in #1658

`AnswerCard` and the live surface disagreed about degraded answers, and **both sides were
documented and measured**, neither referencing the other:

- `#207` required a retrieval banner on **every** degraded state — "the adoption failure
this guards is a silent one … the caution the product shows today is simply gone."
- `#227` suppressed it for `ungrounded`/`source_only`, having measured the duplicate:
three renderings of one warning, eleven lines of caution around one line of answer,
147px of scroll against a phone budget of 8.

**Resolved in favour of `#227`, on the owner's decision.** `#207` protects that the caution
_survives_, not that a _banner_ carries it — `VerificationNotice` states it in words and the
card carries `data-state`. The banner now renders only for `stale_evidence` and
`partial_retrieval`, the two kinds that say something the notice cannot (_which_ sources are
overdue, _how much_ was missed). The `#207` test now pins the duplicate **out**, so a banner
reappearing for `ungrounded` fails as the `#227` regression it would be.

Do not re-litigate this without the owner.

---

## 3 · Corrections — the previous backlog is wrong in six measured places

Verify before acting on any inherited design-system list. These were each established by
measurement and cost real time.

1. **Letterspacing is not 371 sites / 31 values.** That counts `*-mockups.tsx`, which is
design scratch and gate-exempt. Production was **110 / 18**; the contract's AST pass saw
**68 across 40 files**. All three numbers are true of different things; only the last is
actionable. Now **0** (#1669).
2. **`--shadow-focus` is not dead.** Live consumer: `.chat-composer-shell-delta:focus-within`
in `globals.css`. It _is_ the forbidden companion focus ring Gate 3 prohibits, so removing
it is a **visible focus-state change on the search composer**, not a dead-code sweep.
A `--include=*.tsx` grep will tell you it has zero consumers. It does not.
3. **`check:type-scale` and `check:icon-scale` exist** and are hard-zero gates inside
`verify:cheap` since 30 July. Only the ESLint _rules_ are absent. Arbitrary sizes
(`text-[12px]`) are blocked; **step selection among named steps is not** — that is the
real gap (1 318 sites across 8 non-standard steps). Do not write a lint duplicating the
half that ships.
4. **Local duplicates already converge.** All three "duplicate" local `Chip`s and
therapy-compass `EmptyState`/`LoadingState` are already thin wrappers over the DS
components.
5. **Unbounded "log once" Sets are closed.** `verification-notice.tsx` is bounded at 32 with
FIFO eviction; four other modules use `createBoundedDiagnosticRecorder`.
6. **Roughly five of fourteen component-defect rows are stale.** Already resolved:
ToggleSwitch motion, TextField/SearchField/Select hint+`describedBy` (fixed via
`FormField`), Checkbox/RadioGroup ids (`useId`), Tabs `aria-controls`, Links `tone` leak.

---

## 4 · The plan

### Track A — finish the kit

**A1 · `cn()` + tailwind-merge (`#218`).** The keystone. Blocks Chip/metadataPill
convergence and any reliable size override; `cn()` is plain concat today and two source
sites already carry workaround comments. **Its own PR** — it adds a dependency
(`operationalRisk: true`) and changes class resolution across ~200 importing files, so it
must not be bundled or landed alongside a visual change whose Chromium job you still need to
read.

**A2 · `--shadow-focus`.** Replace the composer's companion ring with the sanctioned
`outline` / `--focus` treatment used everywhere else, then delete the token (both theme
declarations). Visible focus-state change on the search composer — read
[`docs/search-chrome-behaviour.md`](../search-chrome-behaviour.md) first and get a Chromium
look.

**A3 · Remaining token debt.** `--shadow-tight`'s 155 consumers onto the `--eN` ladder; a
step-_selection_ lint for the eight non-standard type steps; extend the contract ratchet to
raw padding / radius / line-height literals (it covers colour, shadow, tap and tracking
today).

**A4 · The 13 open §0.4 rows.** Button `forwardRef` · Progress/StageList (hardcoded `1.4s`
shimmer, `step 0 of N` reachable, whole-list live region) · StatusMark app-type coupling ·
PageHeader title starve · Disclosure print · AccessibleTable dense headers and Button-based
expander · Tabs invalid-value tab order · Pagination focus/announce · Links `download`
overridable by spread and `gap` animation · Citation static-span labelling ·
Checkbox/RadioGroup raw dimensions and group hint/error.

**A5 · Close the six ungated prohibitions.** Highest value in Track A, because a prohibition
with no gate row is a suggestion — that is the failure mode GATES.md exists to prevent.
Colour-boundary lint (no numeral painted with a status colour) · PDF/diagram invert refusal ·
layout-property animation lint · border+ring co-occurrence · `--shadow-tight` alias lint ·
`dark:` override lint (3 live instances).

**A6 · Gates 2/4/7/8 from partial to blocking.** Gate 4 needs a repo-wide status-indicator
enumeration — today a new colour-only indicator slips through. Gate 2 needs the fixed-height
`h-10` case and `test:e2e:style-contract` wired into `verify:cheap`.

### Track B — update the live design

**B1 · Adopt demand-driven, never as a race to 53/53.** Pick a surface and let it pull, the
way #1658 did. Forms are the largest single tranche: `FieldError` `FieldHint` `ErrorSummary`
`SearchField` `Checkbox` `RadioGroup` all land together on one form conversion.

**B2 · `AnswerFooter` and `DoseLine`** need a provenance/dose payload the answer surface does
not yet produce. That is backend-shaped work, not a component swap. Do not stub one to make
the adoption count look better.

**B3 · The 19 genuine bare-dash sites** onto `MissingValue` (therapy-compass getters,
specifier `sourceFamily`, favourites counts when untrusted). **Leave the ~5 calculator
`derived.started ? score : "—"` sites permanently** — "not started" is not a missing clinical
value, and `MissingValueReason` has no member for it; converting them would render "Not
recorded" for a score the clinician simply has not entered.

**B4 · The visual state matrix** — hover / active / disabled / busy / invalid / 320px / dark /
forced-colours / print, per component. Currently proven for none. Blocked on §5.

---

## 5 · The decision that unblocks the most

**The visual baseline hold, issue `#118`.** Zero baselines committed, harness
`continue-on-error`. Until the design is declared final:

- there is **no visual regression protection at all** — every change to the live look ships
unverified;
- Gate 2's per-surface geometry, the 320px proofs and the whole state matrix (B4) stay
unprovable;
- the adoption contract cannot demand real proof.

Nothing in Track B is safe at scale until baselines exist. The first step is a CI run
producing `linux` / `ubuntu-24.04` baselines with approved provenance —
**they cannot be generated on Windows**: `snapshotPathTemplate` carries `{platform}`, so
win32 PNGs are invisible to the ubuntu CI job and only `linux/` gates anything.

`--spacing-tap` is **48px** and pinned from both sides. Production tap targets are `min-h-12`.
Do **not** "fix" them to `min-h-11` to satisfy generic WCAG advice — that reintroduces a known
`ui-smoke` sub-pixel flake.

---

## 6 · Scope discipline — what "perfected" should mean

**Do not build the 43 outlined P1/P2 components.** That is the failure the audit named:
documentation describing a system roughly 2.8× the size of the one a designer can use.
`FilterBar` and `DataTable` are already retired _names_, not future contracts, for exactly
this reason.

Freeze the outline as a catalogue. Build on demand. Treat "perfected" as:

> 13 defect rows closed · 6 prohibitions gated · token debt finished · baselines committed

On that definition it is roughly four or five focused PRs plus the baseline decision. On the
"build everything outlined" definition it is months, and it makes the kit worse.

---

## 7 · Repo gotchas that cost the previous session

- **The local `main` ref is stale.** Measured 111 commits behind `origin/main`. A first-turn
glob against the worktree reported "the design system is unmerged branch work"; it had
fully landed. Run `git rev-parse main origin/main` **first** and use `origin/main` for every
containment question.
- **The twelve `codex/v2-*` branches are superseded snapshots**, 14 000–28 000 lines _behind_
main. `git rev-list --count` reads them as "ahead" because the content landed via
squash-merge. Use `git diff --shortstat origin/main..<branch>` — a large deletion count
means it would revert main. Merging one throws ~45 conflicts. Cherry-pick if anything is
wanted; never merge.
- **Background-task exit codes lie.** Append `; echo "X_EXIT=$?"`, log to a file, and grep the
"N passed" tally. Exit 0 alone is never proof.
- **`verify:pr-local` fails closed** at `check:installed-lock-parity` on playwright drift
(issue `#149`) before running anything. `npm run test` directly is the way to close the real
gap; do not `npm ci` on a box with dozens of node processes — concurrent installs corrupt
`node_modules` here.
- **Chromium runs are unreliable under contention** on the dev box; they produce false reds,
not signal. Let CI's job be the authority for UI verification.
- **Three test files fail on Windows on main itself**, unrelated to design-system work:
`installed-lock-parity` (`#149`), `pr-handoff-stop` (simulated `jq` absence, from #1649),
`mode-nav-addon-slot` (absolute Windows vs POSIX-relative paths, from #1647).

---

## 8 · Verification recipes

```bash
# smallest correct gate for design-system work
npm run check:design-system-contract # tokens + adoption + design-sync parity
npm run check:type-scale # hard zero: no arbitrary text-[12px]
npx vitest run tests/ckb-v2-token-contract.test.ts tests/design-system-adoption.test.ts

# component behaviour
npx vitest run tests/ui-primitives.dom.test.tsx tests/ui-v2-components.dom.test.tsx tests/ui-v2-answer-safety.dom.test.tsx

# before any push — format is in CI but NOT in verify:cheap
npm run format
git status && git diff # review changes; stage only the reviewed paths, e.g. git add <path>
npm run typecheck && npm run lint && npm run test
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

Regenerate the adoption manifest after **any** change to component imports:
`npm run design-system:adoption:update`. It is generated; never hand-edit it.

The contract ratchet is per-path. To re-baseline after clearing debt, set only the metric you
cleared and assert every other metric is unchanged first — the previous session scripted that
check so a rewrite could not silently move an unrelated number.

---

## 9 · Prompt for the next session

> Read `AGENTS.md` first — it is the highest-priority source of truth for rules and gates.
> Then read `docs/design-system/HANDOVER-2026-08-07.md` — it carries the measured state, the
> corrections to the old backlog, and the ordered plan. Confirm `git rev-parse main origin/main`
> agree before reasoning about what has landed.
>
> Start with **Track A1: give `cn()` tailwind-merge** (`#218`). Own PR, no bundling: it adds a
> dependency and changes class resolution across ~200 importing files. Verify with
> `npm run check:design-system-contract`, `npm run typecheck`, `npm run lint`, `npm run test`,
> and the component DOM suites in §8. Expect three pre-existing Windows test failures listed in
> §7 — confirm they are unchanged rather than treating them as yours.
>
> Do not commit visual baselines, do not merge any `codex/v2-*` branch, do not build the
> outlined P1/P2 components, and do not write a lint duplicating `check:type-scale`.

Register the Track A and Track B items in the durable ledger with `/issues capture` rather
than hand-writing rows into [`docs/outstanding-issues.md`](../outstanding-issues.md) — ids are
allocated read-modify-write and hand-edits have collided before (`#156`).
5 changes: 5 additions & 0 deletions docs/design-system/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,11 @@ only in the token files.** Source-of-truth ranking: `AGENTS.md` → `ckb-v2-toke
committed tests → `.design-sync/conventions.md` → this set. Where this set contradicts a
higher source, the higher source wins and the contradiction is a defect here.

**Picking the work up cold?** Start at
[HANDOVER-2026-08-07.md](HANDOVER-2026-08-07.md) — measured state, the ordered plan, and the
six places the older design-system backlog is provably wrong. It exists so a fresh session
does not re-derive what a previous one already measured.

Reading order:

1. [SPEC.md](SPEC.md) — principles, foundations, patterns, degraded states, accessibility,
Expand Down
Loading