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
11 changes: 5 additions & 6 deletions .design-sync/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

> The system of record is `docs/design-system/` (SPEC · TOKENS · COMPONENTS · DECISIONS ·
> GATES). This file is the designer-facing build idiom; where it disagrees with that set,
> treat the disagreement as a defect here and follow the set. Known nuance: the
> `--shadow-*` role aliases below are **legacy-compat only** — new code uses the `--e0`…
> `--e4` tiers directly; the aliases are scheduled for retirement
> (`docs/design-system/TOKENS.md` §6).
> treat the disagreement as a defect here and follow the set. New code uses the `--e0`…
> `--e4` tiers directly; the remaining `--shadow-*` roles are legacy-compat aliases
> documented in `docs/design-system/TOKENS.md` §6.

## Setup

Expand All @@ -31,8 +30,8 @@ arbitrary-value form — never hardcoded colours:
`--info-solid`. For a filled non-danger status use `-bg` + `-text`.
- Elevation: the `--e0` … `--e4` ladder — `shadow-[var(--e2)]`, `hover:shadow-[var(--e3)]`.
`--e0` flush · `--e1` resting hairline · `--e2` cards/popovers · `--e3` hover/lifted chrome ·
`--e4` modals/sheets/drawers. The role names are aliases onto tiers:
`--shadow-tight`→`--e1`, `--shadow-card`/`--shadow-soft`→`--e2`, `--shadow-hover`→`--e3`,
`--e4` modals/sheets/drawers. The remaining role names are aliases onto tiers:
`--shadow-card`/`--shadow-soft`→`--e2`, `--shadow-hover`→`--e3`,
`--shadow-elevated`/`--shadow-lux`→`--e4`. `--shadow-inset` stays bespoke.
Never hand-roll a `shadow-[0_…]` literal.
- Focus ring: `focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| 2026-08-14 | claude/design-token-relands | f94fa299a61f1337825083fe1178c66f65680329 | design tokens: --shadow-tight retirement reland, contract ratchet re-pin, search-band badge token, DS Track A3 closeout | self-review; 4 commits, value-preserving, gates green except a pre-existing main failure | verify:pr-local (all stages pass except pre-existing check:medication-lexicon-report, reproduced on pristine origin/main); vitest 601 files/6510 passed; design-token-contract + tailwind-merge 69 passed; check:design-system-contract zero slack, mutation-verified; tailwind emission probe for the new utilities |
4 changes: 2 additions & 2 deletions docs/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Icon **glyphs** use the parallel `--spacing-icon-*` scale in `@theme`:
instead of bleeding. `--e0` flush · `--e1` resting hairline · `--e2` cards/popovers ·
`--e3` hover/lifted chrome · `--e4` modals/sheets/drawers. Dark lifts with a top highlight
rather than more black.
- The role names are **aliases onto tiers**, not independent values: `--shadow-tight` → `--e1`;
- The remaining role names are **aliases onto tiers**, not independent values:
`--shadow-card` / `--shadow-soft` → `--e2`; `--shadow-hover` → `--e3`; `--shadow-elevated` /
`--shadow-lux` → `--e4`. `--shadow-inset`, `--shadow-rail-active`, `--shadow-focus` and
`--glow-primary/soft` stay bespoke. All are removed under forced-colors, ladder included.
Expand Down Expand Up @@ -220,7 +220,7 @@ image"}` — never a possibly-empty variable alone.
| `Number(query.page ?? 1)` | `parseInt` + `Number.isFinite` + `>= 1` clamp |
| `alt={caption}` | `alt={caption?.trim() \|\| "Clinical document image"}` |
| new `z-[73]` for a popover | an existing ladder rung, or `Sheet` |
| `shadow-[0_5px_12px_rgba(0,122,120,0.16)]` | `shadow-[var(--shadow-tight)]` |
| `shadow-[0_5px_12px_rgba(0,122,120,0.16)]` | `shadow-[var(--e1)]` |

## 9. Verification gates — Definition of Done for UI PRs

Expand Down
8 changes: 4 additions & 4 deletions docs/design-system/GATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ theme-list parity, and remote design-project publication remain separate concern
| Put `--decoration-soft`/`--text-soft` on a text node | Tier pinned both sides in the token contract; recipe AST (`decoration-on-text.contract.test.ts`) | implemented-blocking (recipes); broader AST follow-on |
| Uppercase a dose unit | `Quantity` pins `normal-case` + unit test | implemented-blocking (branch) |
| Invert a PDF, diagram or clinical image in any theme | `check:design-system-contract` — `imageInversions`, pinned at **zero**, not ratcheted | **implemented-blocking** (9 Aug 2026) — CSS `filter`/`backdrop-filter` plus the Tailwind `invert`/`hue-rotate` utilities; see §5 |
| Border **and** ring on one surface, or a 1px spread in a drop shadow | `check:design-system-contract` — `edgeOwnershipConflicts` (27) + `onePixelShadowSpreads` (2) | **implemented-blocking for new use** — AST/CSS ratchets with per-path pins; the recorded debt itself is Gate 8's remaining half |
| Border **and** ring on one surface, or a 1px spread in a drop shadow | `check:design-system-contract` — `edgeOwnershipConflicts` (25) + `onePixelShadowSpreads` (2) | **implemented-blocking for new use** — AST/CSS ratchets with per-path pins; the recorded debt itself is Gate 8's remaining half |
| A child shadow heavier than its parent's | Gate 7 | implemented-partial |
| Use `--shadow-tight`/any alias in new code | `check:design-system-contract` — `legacyShadowAliases`, ratcheted at 224 with per-path pins | **implemented-blocking for new use** — a new alias in any file fails; retiring the existing 224 is `#262` |
| Raw pixel size, padding, radius, gap or line-height in markup | `check:design-system-contract` — `rawPaddingLiterals` (67), `rawRadiusLiterals` (24), `rawGapLiterals` (34), `rawLineHeightLiterals` (3) | **implemented-blocking for new use** (9 Aug 2026) — per-path ratchets over both the utility and the CSS-declaration spelling, so a literal cannot move into `globals.css` to escape. Values containing a CSS function (`env(`, `clamp(`, `max(`, `calc(`) are sanctioned computed forms and exempt. Raw _size_ is still covered only for tap/shadow/colour |
| Animate `width`, `height`, `grid-template-*`, `top`, `left`, `gap` | `check:design-system-contract` — `layoutTransitionExceptions`, ratcheted at 12 with per-path pins | **implemented-blocking for new use** — `SAFE_TRANSITION_PROPERTIES` carries the compositor-only allowlist; phone chrome's deliberate `grid-template-rows` is in the recorded 12 |
| Use `--shadow-tight`/any alias in new code | `check:design-system-contract` — `legacyShadowAliases`, ratcheted at 119 with per-path pins | **implemented-blocking for new use** — a new alias in any file fails. `--shadow-tight` itself is retired onto `--e1` and additionally pinned by a tracked-tree sweep in `tests/design-token-contract.test.ts` (it was silently reverted once by the `acf78bf` merge); the remaining 119 are the other six aliases, still `#262` |
| Raw pixel size, padding, radius, gap or line-height in markup | `check:design-system-contract` — `rawPaddingLiterals` (63), `rawRadiusLiterals` (24), `rawGapLiterals` (32), `rawLineHeightLiterals` (3) | **implemented-blocking for new use** (9 Aug 2026) — per-path ratchets over both the utility and the CSS-declaration spelling, so a literal cannot move into `globals.css` to escape. Values containing a CSS function (`env(`, `clamp(`, `max(`, `calc(`) are sanctioned computed forms and exempt. Raw _size_ is still covered only for tap/shadow/colour |
| Animate `width`, `height`, `grid-template-*`, `top`, `left`, `gap` | `check:design-system-contract` — `layoutTransitionExceptions`, ratcheted at 11 with per-path pins | **implemented-blocking for new use** — `SAFE_TRANSITION_PROPERTIES` carries the compositor-only allowlist; phone chrome's deliberate `grid-template-rows` is in the recorded 11 |
| Hardcode a transition duration | `check:design-system-contract` — `hardcodedMotionClasses` (**zero**) + `hardcodedCssMotionDurations` (42) | **implemented-blocking** for the Tailwind `duration-*`/`delay-*`/`transition-all` form; the CSS form is a ratchet, so its 42 are debt |
| A `z-` value outside the named rungs | `require-z-index-ladder` (ESLint) | implemented-blocking |
| Ship a registered component without a direct publication test | Gate 12 | implemented-blocking |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"id": "04a0b8d6-8af9-4779-8af8-8e3b6e9a3b2b",
"createdOn": "2026-08-14",
"action": "done",
"payload": {
"id": "#262",
"outcome": "CLOSED 2026-08-14 — all three parts settled, verified against code rather than rows. (1) The --shadow-tight retirement is re-landed: 130 call sites across 67 files onto var(--e1) and both declarations deleted. It had closed 2026-08-10 via PR #1803 and was silently reverted by the acf78bf merge on 2026-08-11; two stranded comments (globals.css 'the resting-hairline role is gone', and the token test's 'unlike the --shadow-tight assertion above') survived that merge while the code they describe did not, which is how the loss stayed invisible. The alias was a pure pass-through in both themes, and the forced-colors block scopes ':root, .dark' — the same html element the alias is declared on — so it already resolved through the flattened '--e1: none'; value-preserving in light, dark and forced-colors. tests/design-token-contract.test.ts now sweeps the tracked src tree for BOTH spellings (declaration and var() consumer) rather than asserting the declaration alone, so the gate no longer depends on which half of a bad merge lands. Mutation-verified in both directions. (2) NOT ACTIONABLE BY DESIGN, and this was already adjudicated — docs/design-system/GATES.md section 3 records that the decidable half of step selection shipped 9 Aug inside check:design-system-contract (a declared @theme step that no production surface selects fails the build; it caught --text-2xl-compact, retired 10 Aug, closing #297, and the exemption list is empty by design and gated from both sides). The remaining half — which existing step a component picks — is explicitly documented there as something 'nothing mechanical can' gate, being a judgement about the rendered design rather than a property of the source, with a standing instruction not to write an ESLint rule duplicating the arbitrary-value check that already ships via check:type-scale. So this part needs no work and should not be re-attempted. (3) SHIPPED in PR #1780 per #301 — rawPaddingLiterals, rawRadiusLiterals and rawLineHeightLiterals are all live baseline keys enforced at check-design-system-contract.mjs:215-232 over both the class and CSS-declaration spellings, plus rawGapLiterals beyond the original ask. Note for anyone re-measuring part 2: a raw grep for text-<step> overcounts, because it matches the --text-*: declarations and doc comments too — that is the 733-vs-705 discrepancy GATES.md line 66 already warns about, and it reproduces today (a naive sweep returns 773). Use the AST class-root pass."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"id": "11e56221-8d46-4c9a-a89c-78621f18e754",
"createdOn": "2026-08-14",
"action": "add",
"payload": {
"pri": "P3",
"type": "task",
"summary": "Three mode-nav icon glyphs sit at 17px, off the --spacing-icon-* scale, and no gate flags them",
"detail": "Split out of #275 rather than folded into its badge-box token. mode-nav/mode-nav.tsx:64 and :214 and mode-nav/nav-slot-ink.tsx:44 size their <Icon> with h-[1.0625rem] w-[1.0625rem] — 17px against an icon scale of 12/14/16/20/24 (--spacing-icon-xs..xl in the globals.css @theme block). #275 counted these among its five files because they share the badge's number, but they are a different role: the badge is a text-bearing box sized around its own --text-2xs numeral, these are glyphs. They are now the only consumers of that value, since the badge moved to --spacing-search-band-badge. Nothing gates this: check-icon-scale.mjs enforces only the retired 4.5 (18px) half-step and its header states it deliberately does NOT flag arbitrary h-[Nrem], because non-icon boxes legitimately use that form. So this is unguarded and will not self-report. Why it was not just fixed: snapping to size-icon-md (16px) or size-icon-lg (20px) visibly changes nav chrome at every breakpoint, and 17px is close enough to 16 that the choice looks arbitrary without seeing it rendered — a design call, not a token swap. Next: get a Chromium look at mode-nav at phone and desktop widths with the icon at 16 and at 20, pick one, then migrate all three together. If 17px turns out to be deliberate, say so in a comment at the call site and consider whether check:icon-scale should flag off-scale arbitrary icon sizes on <Icon>-typed elements specifically, which would have surfaced this. Stop: do not add a 17px step to --spacing-icon-* to make the problem go away — that token block's own comment argues against widening the scale off the 4px grid, and it would sanction the drift rather than resolve it.",
"source": "session 2026-08-14; split from #275; check-icon-scale.mjs header"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"id": "25f6b53a-86e2-42e9-9b38-1c2daae28892",
"createdOn": "2026-08-14",
"action": "done",
"payload": {
"id": "#319",
"outcome": "RESOLVED 2026-08-14 — the retirement is re-landed. 130 var(--shadow-tight) call sites across 67 files now read var(--e1), and both role-alias declarations are deleted; a tracked-tree grep for the token returns zero. This row was queued as inbox request 210e3db5 and reconciled into the ledger by PR #1936 while the work was already in flight, which is why the fix arrives as a done rather than a cancel. Value-preservation was confirmed before editing rather than inherited from #1803: the alias was a pure pass-through in both themes, and the forced-colors block at globals.css:3613 scopes ':root, .dark' — the same html element the alias is declared on — so --shadow-tight already resolved through the flattened '--e1: none' there. Identical in light, dark and forced-colors. The .ckb-v2 redeclaration hazard this row's neighbours warn about does not bite for the same reason: .ckb-v2 sits on <html> and .ckb-v2.ckb-v2 outspecifies :root, so both spellings substitute against the winning v2 tier. Guard added, and it is deliberately stronger than the one #1803 shipped: tests/design-token-contract.test.ts now sweeps the tracked src tree for BOTH spellings (a '--shadow-tight:' declaration and a 'var(--shadow-tight)' consumer) rather than asserting only that the declaration points at --e1. A declaration-only assertion would have caught the acf78bf revert, but only because the declarations happened to come back alongside the call sites; the tree sweep is independent of which half of a bad merge lands. Mutation-verified in both directions. Two artefacts confirm the original loss and are now consistent again: the globals.css comment 'the resting-hairline role is gone' and this test file's 'unlike the --shadow-tight assertion above' both survived acf78bf while the code they describe did not. This row's stop rule was honoured — the contract baseline was NOT refreshed to absorb the change; it was tightened DOWN to the moved measurement (legacyShadowAliases 220 -> 119) in its own commit, so the movement is pinned rather than hidden. #302 was re-measured after the reland rather than actioned on its stale numbers, and is closed in the same PR. Not bundled with the #1800 re-land, per this row's other stop rule."
}
}
Loading
Loading