Skip to content

fix: restore answer thread on guest reload and stabilize UI specs - #262

Merged
BigSimmo merged 3 commits into
mainfrom
codex/pr-258-header-only-final
Jul 5, 2026
Merged

fix: restore answer thread on guest reload and stabilize UI specs#262
BigSimmo merged 3 commits into
mainfrom
codex/pr-258-header-only-final

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

  • Fix guest auth loading → signed_out clearing persisted answer threads on every page reload
  • Align Playwright specs with ModeHomeTemplate actions and differential presentation routing

Context

Follow-up to #261 (already merged). Only 3 files differ from main.

Test plan

  • npm run verify:cheap passed locally (1059 tests)
  • npm run verify:ui passed locally (97 Chromium tests)

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@supabase

supabaseBot commented Jul 5, 2026

Copy link
Copy Markdown

Updates to Preview Branch (codex/pr-258-header-only-final) ↗︎

DeploymentsStatusUpdated
DatabaseSun, 05 Jul 2026 11:07:47 UTC
ServicesSun, 05 Jul 2026 11:07:47 UTC
APIsSun, 05 Jul 2026 11:07:47 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

TasksStatusUpdated
ConfigurationsSun, 05 Jul 2026 11:07:49 UTC
MigrationsSun, 05 Jul 2026 11:07:51 UTC
SeedingSun, 05 Jul 2026 11:07:52 UTC
Edge FunctionsSun, 05 Jul 2026 11:07:53 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

BigSimmoand others added 2 commits July 5, 2026 18:58
Stop clearing persisted answer threads when auth resolves loading to signed_out (guest/demo). Align Playwright expectations with ModeHomeTemplate actions and differential catalogue counts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add segment-level loading skeletons and error recovery UI for differential diagnosis, presentation workflow, and medication detail pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
@BigSimmo
BigSimmoforce-pushed the codex/pr-258-header-only-final branch from cca7ade to 270d4cfCompareJuly 5, 2026 11:01
Co-authored-by: Cursor <cursoragent@cursor.com>
@BigSimmo
BigSimmo enabled auto-merge (squash) July 5, 2026 11:13
@BigSimmo
BigSimmo merged commit 7cc86d3 into mainJul 5, 2026
5 checks passed
@BigSimmo
BigSimmo deleted the codex/pr-258-header-only-final branch July 5, 2026 11:44
BigSimmo pushed a commit that referenced this pull request Aug 7, 2026
BigSimmo added a commit that referenced this pull request Aug 8, 2026
…sured (#1719)
* docs(issues): correct the re-measured A3 scope and B1 adoption count
Replays a correction stranded in a local-only worktree when PR #1712 squash-merged
and its branch was deleted before the commit could be pushed. Both figures were
re-measured today against origin/main 2675e6e rather than copied forward, and one
claim in the stranded version turned out to be wrong.
#262: legacyShadowAliases counts SEVEN tokens, not one and not eight — the regex has
matched tight|card|soft|hover|elevated|lux|lift since PR #1616 and has never included
focus. Measured total 228 = tight 100, soft 72, elevated 17, hover 17, card 12, lux 8,
lift 2. So --shadow-tight is 100 production sites across 55 files rather than the 155
or 229 previously recorded, clearing all 100 will not zero the ratchet, and #261's
--shadow-focus does not appear in this metric at all.
#266: adoption is 23 unadopted, not 24, since Button moved via #263 — its sole
production importer is src/components/AccessibleTable.tsx.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(design-system): mark the TextField/SearchField/Select row done, it was stale
Replays a correction stranded in a local-only worktree when PR #1712 squash-merged
and its branch was deleted before the commit could be pushed.
Re-measured today against origin/main 2675e6e: all three fold onto FormField, which
renders hint AND error unconditionally (form-field.tsx:131-132) and merges
aria-describedby caller -> hint -> error (form-field.tsx:107-109); TextField and
SearchField each take an external id and a Ref<HTMLInputElement>, Select an external id
and a Ref<HTMLSelectElement>. Landed in PR 13, not the PR 7 the row pointed at.
Section 4's 'Do: fold TextField/SearchField/Select/Checkbox/RadioGroup onto this shell
(PR 7)' was stale in a second way and is corrected too: Checkbox and RadioGroup
deliberately do NOT fold onto FormField — a group keeps fieldset/legend and composes
FieldHint/FieldError directly (choice.tsx:207-210). Left as written, that line asks a
future session to 'finish' a fold that is finished and to break one that was a
deliberate decision.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct future-dated re-measurement records to 2026-08-08
Codex review on PR #1719 flagged SCOPE/COUNT RE-MEASURED and COMPONENTS.md
rows dated 2026-08-09 while the recording commits were authored on 2026-08-08.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs(design-system): separate FormField integration from product adoption, re-measure the ui-primitives row
Two review findings on PR #1719, both valid.
Section 4 said 'Select/choice controls remain separate adoption work', which
contradicted the section above it and was wrong on both axes for Select:
select.tsx consumes FormField (folded in PR 13), and adoption-manifest.json gives
Select 2 production importers, so it is adopted. The genuinely unadopted member of
the trio is SearchField, at zero, which the sentence did not mention. Rewritten to
state the two axes explicitly — integration is complete for TextField, SearchField
and Select, and deliberately declined for Checkbox/RadioGroup; adoption is measured
TextField 3, Select 2, SearchField/Checkbox/RadioGroup 0 — so a control cannot be
read as pending on one axis because it is pending on the other.
The ui-primitives row carried three stale figures. Re-measured on this head: the
module is 698 lines, not 686; 157 production files import it, not 200 — that
figure is 202 including mockups, the same mockup-inclusive mislabelling this PR
already corrects for the shadow aliases; and 30 of the 53 registered components
are adopted, not 27.
Future-dated 2026-08-09 records were already corrected to 2026-08-08 by f3a91c6
before this commit; verified none remain under docs/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo added a commit that referenced this pull request Aug 8, 2026
* docs(design-system): stamp the 7 August handover superseded
Nine open ledger rows (#261, #262, #264-#270) cite
docs/design-system/HANDOVER-2026-08-07.md as their Source, and
docs/design-system/README.md sent anyone picking the work up cold straight to it as
"measured state, the ordered plan". Four of its figures have since been disproved,
and the corrections were written into the ledger rows rather than the document, so
the document still asserts the originals. A session scoping from it re-derives work
that is already known wrong. Recorded as #277.
A banner rather than a rewrite, which is what #277 asks for: the corrections already
live in the rows, and duplicating them re-creates the drift this fixes. The banner
names the four measured errors -- the "229 --shadow-tight aliases" that is really a
seven-token total mislabelled as one token (100 sites across 55 files, 228 total),
the 24-vs-23 unadopted count, the "baselines cannot be generated on Windows"
conclusion that overlooked the ubuntu CI job now used under #118, and #270's 22-site
premise that has zero same-variant pairs -- then points at the rows.
The README now sends a cold start to docs/outstanding-issues.md and marks the
handover superseded in the link text itself, where the misdirection was.
Deliberately not done: the document is not deleted, moved, or corrected in place.
The nine Source citations, the PR and commit record, and its verification and gotcha
sections are provenance the ledger is meant to preserve, and silently correcting it
would leave those rows citing a document that no longer says what they were derived
from.
Verified: npm run docs:check-links -- "docs link check passed: 1667 repo path
references resolve"; npm run docs:check-inventory -- current; npm run format:check --
"All matched files use Prettier code style!".
Refs #277
* docs(design-system): drop false #118 baseline-adoption claim
The superseded banner said all six visual baselines were adopted under
#118, but the ledger still has #118 open and tests/__screenshots__/
holds only README.md. Keep the accurate Ubuntu-CI generation point
without implying visual-regression protection exists yet.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo added a commit that referenced this pull request Aug 9, 2026
…1770)
* feat(design-system): gate the ungated prohibitions and close gate 4
Closes#264 and the gate-4 half of #265. Measured against origin/main
8db1e53 rather than scoped from the handover, which mattered: four of
#264's six prohibitions were already gated while GATES.md §3 read
`planned`, and that understatement is what deferred this task twice.
Tighten two ratchets carrying stale slack to their measured values:
edgeOwnershipConflicts 28 -> 27 and legacyShadowAliases 231 -> 224. Seven
files had paid debt down without a baseline refresh, so up to seven new
violations would have passed. Every other metric and debtByPath entry was
asserted unchanged first. The remaining 224 aliases are #262's cleanup.
Add three checks to check:design-system-contract:
- statusColouredNumerals (ratcheted 2) — a text-* status hue on an element
whose children are all figures.
- colourOnlyStatusIndicators (ratcheted 4) — gate 4's repository-wide
enumeration. A status hue on a box with no children, no accessible name
on it or any ancestor, no sibling text, and not a StatusMark. Also flags
shared swatch recipes, since the analyzer is per-file and cannot follow
an imported statusDotReady to its call sites.
- imageInversions (pinned at zero) — CSS filter/backdrop-filter and the
Tailwind invert/hue-rotate utilities.
All three are mutation-verified, as is the tightening: reintroducing one
var(--shadow-tight) now fails with `legacyShadowAliases increased from 224
to 225` plus the per-path assertion, where the same edit passed at 231.
The two status metrics were re-measured three times before the baseline was
written. The first draft found 19 colour-only indicators and 3 numerals; 15
were false positives — a name test loose enough that `size` read as a
numeral, an ancestor walk checking for JsxOpeningElement when ancestors are
JsxElement, and no sibling-text rule, which condemned the ordinary legend
pattern. All six recorded entries were read in source and confirmed real.
Gate 2 is NOT closed. A rendered-interactive tap enumeration was written,
shown to find genuine defects, and reverted: six runs against one
production build returned 6, 5, 4, 3, 3 and 9 distinct sub-floor shapes
because the audit races the async render, and networkidle plus shape
deduplication did not settle it. ui-style-contract.spec.ts runs in the
required Production UI job, so an intermittent version would have blocked
every merge. Recorded with the defect it found as #289.
Correct GATES.md throughout, including the gate 2 row's claim that
test:e2e:style-contract needs wiring into verify:cheap — the spec already
runs in required CI via productionSpecPattern and playwright-pr-shards, and
adding it to verify:cheap:internal would trip check:gate-manifest, which
requires every gate in that chain to also run in the browser-less static-pr
job. Close#277, verified already satisfied on main.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(ledger): record the M2 design-system gates review
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(ledger): supersede the M2 row after renumbering #289 to #291
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: expand NUMERAL_TEXT regex to accept clinical unit suffixes
Co-authored-by: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
* fix(design-system): close seven gaps found in review of the new gates
All seven review findings reproduced against a probe before any change, and
every one was real. Two mattered:
- Neither status pattern accepted Tailwind's `/NN` opacity modifier, and 83
status-token utilities in `src` carry one. The anchored patterns rejected
every one before the semantic checks ran, so a colour-only indicator written
`bg-[color:var(--danger)]/90` walked past a ratchet described as
repository-wide.
- `NUMERAL_TEXT` rejected every letter while its own comment claimed units were
covered, so a dose painted in a status colour — the case the rule exists for
— passed silently. Replaced with `isNumeralTextFragment`, which accepts an
explicit unit list and still rejects prose. It carries no digit requirement,
because `{dose} mg` splits into an expression and a unit-only text child.
Also:
- `isNumeralExpression` now requires an arithmetic operator, rejects any
string/template operand anywhere in the tree, and follows only `+`/`-` prefix
unaries, so `{count + " errors"}` is no longer a numeral. The previous comment
claiming a concatenation "would have a string literal rather than a numeric
one" was wrong: one numeric side was enough.
- `INVERSION_FUNCTION` matches the `invert(`/`hue-rotate(` call itself, covering
`filter-[invert(1)]`, `[filter:invert(1)]` and both `backdrop-` forms.
- `IMAGE_INVERSION_UTILITY` excludes `-0`; `invert-0` disables inversion and a
hard-zero gate must not reject the reset.
- `rendersVisibleText` no longer counts `{null}`, `{false}`, `{undefined}` or a
bare self-closing element as a text channel, and judges markup-building
expressions by that markup.
- `hasNonEmptyAccessibleName` replaces the attribute-presence test, so
`aria-label=""` no longer exempts an indicator.
- The Gate 9 row said a layout-property lint was "planned" while
`layoutTransitionExceptions` already ships and ratchets per path — the exact
contradiction this branch exists to remove.
Fixing these surfaced two false positives of my own, both caught by
re-verifying every baseline entry in source rather than trusting the count: the
sibling walk stopped at the first non-JSX parent, so a conditional badge with a
label was reported colour-only; correcting that then let a sibling
`{started ? <div/> : null}` — another coloured div — count as text.
Baseline unchanged at 4 colour-only / 2 numerals / 0 inversions, and unchanged
by the unit widening, so nothing in the repo paints a dose in a status colour.
`legacyShadowAliases` 224 -> 220 from the main merge. Every new form is
mutation-verified.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(ledger): supersede the M2 row after the review round and main merge
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
BigSimmo added a commit that referenced this pull request Aug 9, 2026
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BigSimmo added a commit that referenced this pull request Aug 9, 2026
…lection (#262 parts 2 and 3) (#1780)
* docs(issues): close#218 and #270, both shipped before this session
Both rows were still open in docs/outstanding-issues.md while their work was
already live on main, which had scoped a third session from them.
#218 (cn() lacks tailwind-merge) shipped in PR #1678, aeba5a2.
src/components/ui-primitives.tsx:37 is twMergeClinical(...) rather than a plain
join, package.json carries tailwind-merge ^3.6.0, and src/lib/tailwind-merge.ts
declares the repo's @theme scales to twMerge.
#270 (declare the tap spacing token) shipped in PR #1738, 80cf781, an ancestor
of origin/main. "tap" is present in CLINICAL_TWMERGE_THEME.spacing, and
tests/tailwind-merge-config.test.ts was inverted rather than deleted so the
merge behaviour is now asserted rather than pinned out.
Verified in source at origin/main 7aaf934, not inferred from the handover.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(design-system): ratchet raw padding, radius and line-height literals (#262 part 3)
The design-system contract ratcheted colour, shadow, tap and tracking but not
spacing, radius or line-height, so a value could bypass the scale as a bare
literal in either a class or a stylesheet and nothing objected.
Adds three per-path ratchets, covering both halves the way the colour and
legacy-shadow metrics already do:
rawPaddingLiterals 67 (17 CSS declarations, 50 class utilities)
rawRadiusLiterals 24 (22 CSS declarations, 2 class utilities)
rawLineHeightLiterals 3 (3 CSS declarations)
The exemption is deliberately "contains no CSS function", not the narrower
`(?!var\()` the tracking rule uses. Padding is not only ever a token or a
literal: production ships pb-[env(safe-area-inset-bottom)],
pt-[max(0.75rem,var(--safe-area-top))], pt-[clamp(1.5rem,5vh,3rem)] and
pb-[calc(7rem+env(safe-area-inset-bottom))]. Those are computed from the
viewport or the safe-area inset, cannot be spelled as a scale step, and a
`var(`-only lookahead would have flagged every one of them. On the CSS side,
zero in any unit, the CSS-wide keywords and custom-property declarations (the
token definitions themselves) are exempt for the same reason.
Every one of the 94 baseline entries was verified present at its cited line
before pinning, and the baseline change is additive: all fifteen pre-existing
metrics and every pre-existing debtByPath entry are byte-identical.
Mutation-tested rather than assumed. Class side, in a file with no prior debt:
- rawPaddingLiterals increased from 67 to 68
- rawPaddingLiterals at src/components/ui-primitives.tsx increased from 0 to 1
- rawRadiusLiterals increased from 24 to 25
- rawRadiusLiterals at src/components/ui-primitives.tsx increased from 0 to 1
- rawLineHeightLiterals increased from 3 to 4
- rawLineHeightLiterals at src/components/ui-primitives.tsx increased from 0 to 1
The CSS half fails the same way. Both probes also carried the sanctioned
computed forms, and each count rose by exactly one, so the exemptions are
proved by the same runs rather than argued.
No new npm script: the metrics live inside check:design-system-contract, so
docs:check-inventory and check:gate-manifest are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(design-system): gate type-step selection on the decidable half (#262 part 2)
check:type-scale blocks arbitrary text-[12px] values. Nothing has stopped the
scale itself growing a step no surface ever picks, which is the drift that
makes a wrong selection possible in the first place.
Whether a heading should have chosen text-sm over text-sm-minus is not
mechanically decidable, and this does not pretend otherwise. A step that is
declared and consumed by nobody is decidable, and there is one today:
--text-2xl-compact (globals.css:112) has zero consumers -- no utility use, no
var() use -- while the next-rarest step, text-hero, has one real consumer.
The analyzer reports every bare text-<name> it sees and does not decide which
names are steps; the checker intersects that against the @theme block it parses
from globals.css. So the scale is never written down twice, and a step added to
globals.css is covered without touching this gate.
Retiring the dead step edits @theme, so it gets its own revertible PR rather
than riding along here: it is carried in UNUSED_TYPE_STEP_EXEMPTIONS and
tracked as docs/outstanding-issues.md #295. The exemption cannot rot silently --
the gate also fails if an exempted step stops being declared or gains a
consumer.
Mutation-tested, three ways:
- type steps are declared in globals.css @theme but no production surface
selects them: --text-2xl-compact (text-2xl-compact). Retire the step or use
it; do not leave the scale carrying a step nobody picks.
- (a newly added --text-probe-step fails identically, so this catches future
drift rather than only today's known case)
- --text-2xl-compact is exempted as unused but production now selects
text-2xl-compact -- drop the exemption
Measurement note, since three different figures were in circulation for this
row: the "1318 sites" is a repo-wide grep INCLUDING mockups, which the gate
excludes (1360 at this HEAD). Production consumers of the nine non-standard
steps total 705 -- text-2xs 421, sm-minus 160, base-minus 57, 3xs 42,
2xl-minus 9, 3xl-minus 9, lg-minus 6, hero 1, 2xl-compact 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(design-system): correct GATES.md for the two new scale gates
GATES.md's own §1 is the list of what actually runs, and this series' recurring
failure is that list lagging the code: four of #264's six prohibitions were
already gated while it said "planned".
Records the padding/radius/line-height ratchets and the type-step selection
rule in the contract row, and rewrites the type-scale callout, which claimed a
step-selection lint "does not exist". The decidable half now ships; the half
that asks whether text-sm-minus was the right pick over text-sm still does not,
and cannot.
Also corrects the "1 318 call sites" figure quoted there. It was a repo-wide
grep including src/app/mockups/**, which every one of these gates excludes
(1 360 at 7aaf934). Production consumers total 705, and there are nine
non-standard steps, not eight.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(ledger): record the #262 parts 2/3 gate work (PR #1780)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(design-system): close scale-ratchet and unused-step review gaps
Cover Tailwind arbitrary-property forms and modern CSS zero units in the
raw scale ratchets, and validate unused-step exemptions against the same
class-or-CSS consumer predicate used for ordinary steps.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo added a commit that referenced this pull request Aug 9, 2026
…1786)
* feat(design-system): build ErrorState, the gate with nothing behind it
GATES.md §3 lists the prohibition "Render '0 matches' after a failed
request" with the gate "ErrorState adoption + check", status planned.
Measured at origin/main 199b303, ErrorState existed nowhere in src or
tests — only in COMPONENTS.md, GATES.md and SPEC.md. This builds it.
The invariant is clinical, not cosmetic. A search that failed has no count
to report, so reporting zero is a false statement about the corpus: on the
services page "0 matches" asserts there are no crisis services when the
search never ran, and on favourites it reads as "you have saved nothing"
rather than "we could not load them". COMPONENTS.md:322 draws the same line
from the other side — "no result count is available" is not a MissingValue.
The component therefore takes no count and no children. There is no prop
through which a number can arrive, and the generated dtsPropsFor entry now
records that as the published API. The one remaining route, a caller writing
a count into title or body, is covered by a development-time tripwire that
matches a figure against a counted noun ("0 matches", "no results") so an
error code or a duration does not trip it. It warns and never throws: on the
one screen already reporting a failure, a thrown error is a blank page.
Requirements came from the surfaces that hand-roll this guard today. Three
do, and their comments state the rule outright:
search-results-header-band.tsx:210 ("no number may reach the DOM"),
services-navigator-page.tsx:634 ("a blocked registry must not reach the band
as '0 matches'") and favourites-command-library-page.tsx:1182. They are
correct, just not shared; converting them is a live-look change and
deliberately not here.
Three further sites carried into this task as hand-rolled guards are not
that, measured at this HEAD, and are recorded so the next reader does not
convert them: differentials-home.tsx:716,729 renders "0 matches"/"No
matches" when sourcesChecked is true, i.e. a legitimate zero after a search
that SUCCEEDED; specifiers-home-page.tsx:211 is a comment about not showing
a stale zero above real catalogue results, and lives in
src/components/specifiers/, not clinical-dashboard/; document-search-results
gates on recordStatus for loading, not for a failed count.
Registered per gates 11 and 12: source, design-sync export, preview, prop
contract, publication test entry, behavioural DOM tests, adoption-contract
family, and both generators regenerated (54 components, 59 roots).
Uses the shared floatingControl recipe rather than a hand-rolled control, so
the tap floor, focus ring and forced-colors border come from one owner. Adds
zero arbitrary padding/gap/radius/line-height, so the ratchet landed in the
previous commit is unaffected and the two stay separately revertible.
Scope: ErrorState only. OfflineState, PermissionDeniedState, NotFoundState
and UnavailableState share the pattern but have no gate pointing at them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(design-system): ratchet raw gap literals, the family #1780 left uncovered
#1780 landed rawPaddingLiterals, rawRadiusLiterals and rawLineHeightLiterals
for #262 part 3. Gap was the one remaining spacing surface a hand-picked
value could hide in: gap-[9px] and `gap: 18px` were counted by no ratchet at
all. This adds rawGapLiterals on that commit's own predicate and wiring.
Measured against origin/main ef9bb51: 34 sites across 9 files — 21 Tailwind
utilities, every one under src/components/therapy-compass/, plus 13 CSS
declarations in globals.css that a utility-only scan misses. Covering both
spellings is the same reason #1780 counts both: otherwise a literal escapes by
moving from a class into globals.css.
Kept as its own metric rather than folded into rawPaddingLiterals so the
therapy-compass cleanup can be paid down and re-pinned independently of the
padding debt, which is spread across fifteen unrelated files.
Reuses RAW_LITERAL_VALUE unchanged, so a value containing a CSS function
(env(, clamp(, max(, calc() stays a sanctioned computed form and is exempt.
Also corrects the §3 prohibition row, which #1780 left reading
"implemented-partial (colour/shadow/tap literals only)" and which named none
of the metrics it had just shipped. A row that understates shipped work is
what sends the next session to rebuild it — this change was itself started as
a duplicate of #262 part 3 for exactly that reason.
Mutation-verified in both halves, each naming the metric and the path:
a gap-[3px] utility gives "rawGapLiterals increased from 34 to 35" plus
"at src/components/ui/missing-value.tsx increased from 0 to 1"; a
`gap: 19px` declaration gives the same total plus "at src/app/globals.css
increased from 13 to 14".
Baseline diff is additive only. legacyShadowAliases measures 218 against its
pinned 220 on main; that slack is left exactly as found.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(issues): capture the ErrorState and duplicate-work follow-ups
Four rows (#298-#301), added with npm run issues:add. Ids 295-297 were
claimed by main while this branch was open, which is ledger #156's
read-modify-write race behaving exactly as recorded.
- #298 (P2 task) the ErrorState enforcement check. GATES.md still reads
"planned" for the 0-matches prohibition and nothing in scripts/ or
eslint-rules/ references ErrorState, so the component exists but is not
required. Deliberately not flipped to implemented.
- #299 (P3 task) adopting ErrorState at the three surfaces that genuinely
hand-roll the guard. Live-look change, downstream of the redesign.
- #300 (P2 issue) three sites miscarried into M4 as guards that are not,
so the next reader does not convert them. differentials-home renders its
zero after a search that SUCCEEDED.
- #301 (P3 issue) two sessions built #262 part 3 in parallel because the
§3 row understated what had shipped. Proposes asserting that every
baseline metric key appears in GATES.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(design-system): restrict the ErrorState copy tripwire to development
Codex review on #1786 (P2). The doc comment described the tripwire as
development-only, but the emitter only silenced NODE_ENV === "test", so a
production caller supplying count-bearing title/body copy had the full
caller-provided string written to console.warn. On a clinical surface that
copy can quote the query — "0 results for <query>" — which turns a copy
defect into a disclosure risk. Nobody reads a production browser console
for design-system warnings, so the emit is now development-only and an
unset NODE_ENV is treated as production: fail quiet.
The gate is an exported predicate rather than an inline comparison because
an inline comparison is untestable here. Vite statically replaces
process.env.NODE_ENV inside src/ modules, so under Vitest the check
compiles to `"test" === "development"` and no stubEnv can move it. The
review asked for a production console-spy check; written that way it would
have passed while proving nothing, staying silent for the wrong reason and
continuing to pass even if the guard were deleted. shouldEmitErrorStateDiagnostic
is asserted directly instead — development true; production, test and unset
false — with the console spy kept alongside as the weaker check that catches
an emitter which warns unconditionally.
Verified: typecheck 0 errors, lint 0, format:check 0,
check:design-system-contract 0, Tests 70 passed (70) across
error-state.dom and design-sync-visual-exports.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
…system rows
Archived as verified-delivered or duplicate: #303, #284 (dup of #296), #149,
#159, #286, #287, #288, #256, #261, #291.
Re-scoped with re-measured evidence: #210 (gate was never missing; the defect
is the .next/dev types include), #262 (aliases 228 -> 193), #266 (counts moved,
23 unadopted holds), #275 (premise 'only one implementation' is false — the
values leaked to five files).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
… rows
Repairs. Two main-merges silently reverted edits from this branch, and the
re-apply script missed them because it was hand-enumerated from round 1's
second commit only:
- #293 the entire min-h-tap refutation was gone (grep sm:min-h-0 returned 0);
the row was back to asserting a defect that does not exist. Restored from
a6bfc6f. This was the live hazard — as reverted it directed a reader to
strip desktop step-downs and pin every control to 48px.
- #266 restored the 2026-08-12 counts (54/31/23) over main's older 53/30/23.
- #262 deliberately NOT restored: main's version is newer than mine and
records --shadow-tight retired outright on 2026-08-10.
- #289 archived; verified fixed in round 1 and never actually closed.
Stamps. Sixteen rows were checked during the sweep, found correct, and left
unchanged — which left no trace, so they were indistinguishable from rows
nobody had opened. Each now carries VERIFIED CORRECT 2026-08-12 plus the
measurement that settled it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
Archived #303: probed the real CLI rather than reading it — ledger:append now
accepts a --scope value beginning with a double dash. (The probe row it wrote
was removed immediately and the guard re-run clean.)
#302 re-measured and WORSE, which strengthens it: the legacyShadowAliases
ceiling is still pinned at 220 while the gate measures 193 — 27 units of
unguarded slack, up from 3 when the row was written. Paying #262's debt down
bought headroom instead of tightening the gate, which is exactly the row's
thesis.
#215 re-scoped: one of its three items is done (image-lightbox has
decoding=async) and the row's path was misleading — the file is under
clinical-dashboard/, so a root-level search reads as 'already fixed'.
Twenty stamped VERIFIED CORRECT with the measurement that settled each.
Coverage: 58 of 119 open rows now carry sweep evidence, up from 34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
…d rows
The conflict was one hunk. Resolved by keeping this branch's #302 (re-measured
2026-08-12, newer than main's) and all four of main's genuinely new rows
(#304, #305, #306, #307), dropping only main's older #302 and its #303, which
this branch had already archived as delivered after probing the CLI.
Correcting an earlier claim in this branch's history: #302 and #303 were NOT
lost by main. Main renumbered them to #306 and #307 after the id collision,
which is exactly what the guard advises. Both are re-filings of work already
done, and both are archived here with evidence — style-contracts.ts:352-354 is
clean, and the marker reads 308 against a highest declared id of 307.
The derived loss detector reports one exception, #262, which is intentional:
main's version of that row is newer than this branch's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
BigSimmo added a commit that referenced this pull request Aug 14, 2026
…bt rows (#1942)
* refactor(tokens): re-land the --shadow-tight retirement onto --e1
PR #1803 retired the --shadow-tight role alias in favour of the --e1
elevation tier across 49 files and squash-merged as 9d8370a on
2026-08-10. The acf78bf merge on 2026-08-11 silently reverted it, along
with six other PRs. This re-applies the retirement against current main:
130 call sites across 67 files, plus both declarations.
The alias was a pure pass-through -- `--shadow-tight: var(--e1)` in the
light and dark role blocks -- so the substitution is value-preserving.
Confirmed for forced-colors too rather than assumed: the
`@media (forced-colors: active)` block scopes `:root, .dark`, the same
`html` element the alias is declared on, so `--shadow-tight` already
resolved through the flattened `--e1: none` there. The .ckb-v2
redeclaration hazard 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.
Two comments survived acf78bf while the code they describe did not: the
globals.css note that "the resting-hairline role is gone", and the token
test's "unlike the --shadow-tight assertion above". Both are accurate
again.
The token contract test now sweeps the tracked src tree for both
spellings (declaration and var() consumer) instead of only asserting the
declaration. A declaration-only check would have caught this particular
revert, but only because the declarations happened to come back with the
call sites; sweeping both makes the gate independent of which half of a
bad merge lands. Mutation-verified in both directions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsBPUoxpsvTwvFMJzrNoyz
* chore(design-system): re-pin the contract ratchets to their measured values
`scripts/design-system-contract-baseline.json` is a ceiling, so paying
debt down leaves silent headroom behind. Ledger #302 records that
pattern: legacyShadowAliases was pinned at 220 against a measured 193,
27 units of unguarded slack, up from 3 units on 2026-08-10.
With the previous commit's --shadow-tight retirement applied the gap is
wider still -- 220 pinned against 119 measured -- because the reland pays
down the debt the acf78bf revert had re-hidden. Four other ratchets had
accumulated slack from unrelated work in the same window.
legacyShadowAliases 220 -> 119
edgeOwnershipConflicts 27 -> 25
rawPaddingLiterals 67 -> 63
rawGapLiterals 34 -> 32
layoutTransitionExceptions 12 -> 11
Regenerated with --print-debt-baseline rather than hand-edited, so the
per-path debtByPath counts move with the totals -- those are what
findDebtPathRegressions compares, and the retirement moved them
wholesale. Every metric in the diff decreases; nothing is absorbed
upward.
This is not the baseline refresh #262 warns against. That stop rule
forbids refreshing to hide the movement; this pins the movement in so it
cannot silently drift back a second time.
Mutation-verified: reintroducing one alias in button.tsx now fails at
both the total (119 -> 120) and the per-path level. Under the old 220
ceiling the same addition passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsBPUoxpsvTwvFMJzrNoyz
* refactor(tokens): hold the search-band count bubble in a spacing token
The active-filter badge sized itself with a raw `h-[1.0625rem]
min-w-[1.0625rem]` pair. Ledger #275 tracks that value as leaked debt:
it had reached five files, so the fix has always been to tokenise once
rather than edit a call site.
Re-measured on merged main, the badge role is down to a single call
site. #170's convergence landed in the meantime -- document-search-
results.tsx now renders the shared control and therapy-compass/
filter-sheet.tsx was deleted outright -- so the leak this row was
written about has already been reabsorbed by the extraction. Holding
the value in @theme is what stops it leaving again.
Two arbitrary values in the same component are deliberately left raw:
pr-[0.6875rem] and min-[414px]:max-[429px] -- the repo defines no
--breakpoint-* tokens at all, and eight peer sites use the same raw
min-[]/max-[] form (359px, 389px, 414px). Naming one window while the
peers stay raw is the same drift #275 warns about on another axis, and
Tailwind named breakpoints would add variants across the whole utility
surface. That belongs in a repo-wide decision, filed separately.
The three remaining 1.0625rem hits in mode-nav.tsx and nav-slot-ink.tsx
are NOT this token. They size <Icon> glyphs -- a 17px icon against a
12/14/16/20/24 --spacing-icon-* scale -- so folding them under a badge
token would merge two roles that only happen to share a number.
check:icon-scale deliberately does not flag arbitrary h-[Nrem], so they
are a real but separate finding, filed rather than guessed at.
The token is also registered in CLINICAL_TWMERGE_THEME.spacing, which
tests/tailwind-merge-config.test.ts asserts against the @theme block --
without it `cn()` cannot resolve a conflict on the new utility. Safe by
that file's own `tap` reasoning: the single call site is a static string
carrying no competing h-*/min-w-* class and never passes through `cn()`,
so there is no same-variant pair for declaration to hand to the later
class. The entry is protective for future use, not load-bearing today.
Value-preserving, and proven rather than inferred: compiling globals.css
through @tailwindcss/postcss emits
.h-search-band-badge { height: var(--spacing-search-band-badge) }
.min-w-search-band-badge { min-width: var(--spacing-search-band-badge) }
No ratchet moved, so the ceilings pinned in the previous commit still sit
at zero slack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsBPUoxpsvTwvFMJzrNoyz
* docs(design-system): close out DS Track A3 and refresh the stale gate rows
Track A3 is `#262`. Its three parts are now all settled, each checked
against code rather than against the row that describes it.
Part 1 is the --shadow-tight retirement re-landed earlier in this PR.
Part 3 shipped in PR #1780 per `#301`: rawPaddingLiterals,
rawRadiusLiterals and rawLineHeightLiterals are live baseline keys
enforced over both the class and CSS-declaration spellings, plus
rawGapLiterals beyond the original ask.
Part 2 needs no work, and that had already been adjudicated -- GATES.md
section 3 records it, which is why nothing here builds it. The decidable
half of step selection shipped on 9 Aug inside check:design-system-
contract: a declared @theme step no production surface selects fails the
build. The remaining half -- which existing step a component picks -- is
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 duplicate the arbitrary-value check
check:type-scale already ships. Reading `#262` alone would have sent a
session to build it; that is the `#301` failure mode, so the closure
record says so explicitly.
Section 3's live status rows carried numbers this PR moved. `#301`'s
lesson is that a row understating shipped work is a duplicate-work
generator, so they are corrected in the same change:
legacyShadowAliases 224 -> 119, and the alias is now retired
outright rather than "224 left to retire"
edgeOwnershipConflicts 27 -> 25
rawPaddingLiterals 67 -> 63
rawGapLiterals 34 -> 32
layoutTransitionExceptions 12 -> 11
Section 5 is left alone deliberately: it is a dated record measured
against 8db1e53, not a live status surface, and rewriting its figures
would destroy the provenance it exists to hold.
Ledger records are queued as immutable inbox requests: `#262`, `#302` and
`#275` closed; two carve-outs split out of `#275` filed as their own rows
(the repo-wide breakpoint-token decision, and three 17px mode-nav icon
glyphs that sit off the --spacing-icon-* scale with no gate covering
them). The queued re-land request 210e3db5 is cancelled rather than
reconciled -- its headline "67 files on main still use the retired alias"
is false as of this branch, so it would open a row wrong on arrival. The
request file and the cancellation both survive as provenance for the
acf78bf merge loss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsBPUoxpsvTwvFMJzrNoyz
* chore(ledger): record the design-token relands review
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsBPUoxpsvTwvFMJzrNoyz
* fix(issues): retarget the reland record after main reconciled it mid-flight
CI failed `docs:check-links` on this branch with
Error: cancel request 2e791c01... targets missing pending request
210e3db5...
`check-docs-links.mjs` replays the inbox batch to resolve link targets, so
an unresolvable request fails it. The cause was a race, not a bad record:
PR #1936 reconciled 75 queued requests -- 210e3db5 among them -- while
this branch was already in flight. Reconciling moves the request file
into `docs/outstanding-issues-inbox/applied/` and allocates it a canonical
row, so by the time this branch merged main there was no pending request
left for the cancellation to name.
Cancelling was the right call against a pending request and is the wrong
one against a reconciled row. The cancel is dropped and replaced with a
`done` against `#319`, the row 210e3db5 became. That is also the better
record: the work is finished rather than withdrawn, so the ledger should
carry its outcome and its guard, which a cancellation would have thrown
away.
Also merges origin/main (this branch was 3 behind) and files two findings
the PR preflight surfaced, both deliberately not fixed here:
- `check:medication-lexicon-report` has been failing on main for every
local `verify:pr-local`, and no CI job runs it -- a grep over
.github/workflows finds nothing. It is the last step of the local
chain, so it fails preflights while CI stays green. The stale file is
a clinical-facing generated document; regenerating it inside a
CSS-token PR would bundle a clinical-risk artefact with unrelated
chores.
- Claude Code web containers can ship Node 22 with no node_modules,
which fails `npm ci` on engine-strict before any repo script can run.
Re-verified after the merge: the tracked tree still holds zero
`--shadow-tight` references, and every pinned ratchet still measures
exactly its baseline, so the merge moved no metric and the pins stay
honest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsBPUoxpsvTwvFMJzrNoyz
* docs(design): retire shadow-tight guidance
* docs(design): retire shadow-tight guidance
* docs(design): retire shadow-tight guidance
* docs(design): retire shadow-tight guidance
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BigSimmo