diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index f352976a6..c5ad7eedf 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -700,10 +700,20 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-07 | cursor/viewer-phase0-gesture-a11y-1db8 (PR #1660) | 097dfd245f798f8105eeb6c1cf4fc077f969496f | prlanded | MERGED; squash tip empty vs branch tip 810cfc9b4a1c476a0dcc995bffb00d7329a85686; Phase 0 gesture INP, preview a11y, image decode | content tree empty vs squash; no provider-backed checks run | | 2026-08-07 | claude/pr-handoff-loop-prevention-54y5zr (PR #1670) | dfe2946110e0ff93bd4acc571ae79c26b79a7a85 | PR #1670 heavy review-and-fix | synced origin/main (behind-but-clean DIRTY cleared); fixed CodeRabbit checks-cell to name exact #1649 gates + incomplete verify:pr-local/ui + no provider checks; Bugbot none; no P0/P1; #258/#ledger delta accurate; merge-tree clean; threads cleared | verify:cheap 519 files/5493 passed; verify:pr-local docs scope (format+docs+ledger+outstanding-issues); check:branch-review-ledger; check:outstanding-issues; no provider gates | | 2026-08-07 | claude/handover-review-nlhuln | 978623337c12dc1721fe5236eadbf9a5ad929f03 | mode nav remaining modes: factsheets adoption (PR #1674) | Adopted the shared ModeNav for factsheets (Topics + Search); replaced the action-only entry, added the activeId branch, q/category/run carry, BookOpenText icon; three pinned adopted-mode lists updated together; record-route protection pinned at render now the item-count protection has expired | lint clean; typecheck clean; test 518/519 files (pr-handoff-stop failure confirmed pre-existing via stashed re-run); focused 5 files 95 tests; ui-mode-nav-density 55 passed incl 7 new factsheets rows; two mutation checks confirmed red; format committed; verify:pr-local blocked at check:installed-lock-parity (playwright 1.62.0 vs 1.62.1) | +| 2026-08-07 | cursor/privacy-live-signal-variants-bc81 (PR #1676) | f63eba10f5e9b6db047302c34412ea5261cd410b | PR #1676 unblock | before: PR policy fail (missing Clinical Governance Preflight; privacy* mockup paths trip clinicalRisk), behind-but-clean then main advanced; after: PR body preflight completed (policy green), tip synced to main via merge f63eba10 (duplicate local merge discarded), merge-tree clean, 0 unresolved threads, required CI in progress on synced tip; no code fix needed | local evaluatePullRequestPolicy ok after body; PR policy run 31172248093/31172327100 success; merge-tree clean; no provider-backed checks run | | 2026-08-07 | claude/handover-review-nlhuln | 4ff613c10fbf734b1e740a31611296c17c791ec7 | mode nav remaining modes: vestigial strip removal (PR #1679) | Removed the single-button action strip from answer/documents/services/forms/favourites/prescribing/tools; deleted the registry index-0 fallback (TS2493-forced) and the dead documents clause; stripped modeItems/onSearch/modeAriaLabel/stickyTop from PageSecondaryNavigation, keeping the empty-registry return below the information-section branch; kept the action kind with a no-live-consumer note. Completes the 13-mode navigation rollout. | lint exit 0; typecheck clean; focused 5 files 97 tests; test 518/519 files (pr-handoff-stop re-confirmed pre-existing on this base via stashed re-run); ui-mode-nav-density + ui-accessibility 71 passed (landmark scan green); branch-order guard mutation-checked (hoisting it fails 2 tests); format committed; verify:pr-local blocked at check:installed-lock-parity (playwright 1.62.0 vs 1.62.1) | +| 2026-08-07 | cursor/pr-1676-unblock-ledger-ef51 (PR #1677) | dec8f7489b4e5492e924af30dec85859932af3bb | review-and-fix | synced origin/main (behind-but-clean DIRTY; merge-tree clean); Bugbot mid-table finding dispositioned (row was tip-append before #1679; post-merge order correct; ledger guard passed); no P0/P1; 0 threads; no code fix; merge left to user | check:branch-review-ledger pass; ledger:dedupe none; merge-tree clean vs origin/main; format unchanged; no provider gates | +| 2026-08-07 | cursor/pr-1676-unblock-ledger-ef51 (PR #1677) | 574702a681cbb4d455151da023428d16c22fb460 | review-and-fix | late-synced origin/main after CI green (brought #1678 cn/tailwind-merge; remote merge 574702a6); prior sync cleared DIRTY; Bugbot mid-table finding dispositioned (tip-append before #1679; post-merge order correct); no P0/P1; 0 threads; no code fix; merge left to user | check:branch-review-ledger pass; ledger:dedupe none; merge-tree clean; prior tip required CI green; format unchanged; no provider gates | | 2026-08-05 | HEAD | 2a8881afad230880166de60a533e17588d9920ec | seven-report repo-wide audit | P2 confirmed: live drift and live-only migration; mobile CLS; ACL runner; OOXML declared-size robustness; assertion false positives. Numerous report claims stale, narrowed, or unsafe. | verify:cheap (5088 pass, 3 skip, 1 timeout); focused retry 21/21 pass; build pass; bundle budget pass; offline RAG 574/574; assertions 98%; live drift 34; browser CLS 0.228/0.218 | | 2026-08-07 | codex/consolidated-ledger-updates (PR #1683) | 413e679bb92cb19717d6d8301764df44694eb73e | review-and-fix PR #1683 | synced origin/main (behind-but-clean DIRTY cleared); restored main ledger order + sole seven-report row; Bugbot none; no P0/P1; merge-tree clean | verify:pr-local docs scope PASS (format:changed Prettier; check:branch-review-ledger 648; docs links 1650; outstanding-issues 258); merge-tree clean | +| 2026-08-07 | cursor/pr-1676-unblock-ledger-ef51 (PR #1677) | fddf495b5176f570a5238b5c17326f64f333ecff | review-and-fix | synced origin/main (behind-but-clean DIRTY; merge-tree clean; main advanced with #1684/#1666); 0 review threads; no P0/P1; no code fix; merge left to user | merge-tree clean vs origin/main; ledger:dedupe none; prior tip required CI green except stale PR mergeability; format unchanged; no provider gates | | 2026-08-07 | cursor/inpage-nav-default-235a | c6d72e406c03e205bac86d23e84813c41332c205 | docs: default in-page nav DocumentViewer chrome + PhoneHeaderCollapsePortal | docs-only; verify:pr-local passed (low-risk docs scope) | verify:pr-local --files AGENTS.md,docs/search-chrome-behaviour.md | +| 2026-08-07 | claude/issues-256-section-nav-clean | 169323053db5c572d183d59c113ecd0c76e7aca5 | issues #256: forms section anchors + differentials presentation set (PR #1697) | Wired all six formSections anchors in form-detail-page.tsx (four direct ids, two breakpoint pairs via existing mobile wrappers and single-child desktop wrappers, no component signature change); deleted differentialPresentationSections and declared /differentials/presentations/ locally-owned instead, since three of its six sections declared a -mobile targetId ReviewPanels can never satisfy and the page owns MobileTabs below xl plus the xl review sidebar. Added a registered browser spec because source-text and jsdom guards both structurally cannot see breakpoint-variant resolution. | lint exit 0; typecheck clean; test 519/520 files (pr-handoff-stop confirmed pre-existing via stashed re-run); check:gate-manifest and check:ci-scope pass with the new spec in both playwright allowlists; ui-forms-section-nav + ui-accessibility 18 passed incl real-record nav with 6 links and exactly one variant per pair visible at 390px and 1280px; binding guard mutation-checked red on one removed id; browser spec observed failing when nav genuinely absent; format clean. Environment: npm ci blocked (main lockfile needs Node >=24.15, container has 24.13), tailwind-merge@3.6.0 materialised from tarball only | +| 2026-08-07 | claude/handover-review-nlhuln | de8b74e2fb94d1ec9b1982c15a2dea43421c3eee | outstanding-issues ledger capture after the mode-nav rollout (PR #1685) | Confirmed #256's two remaining suspected section sets are dead (/forms/ and /differentials/presentations/ draw no section nav; form-decision-context-mobile is a testId not an id; ruled out sectionId indirection in both files). Added #261 (delete-or-keep the consumer-less action kind) and #262 (addon-slot single-owner rule held by two lists agreeing by coincidence). #207/#226/#231 reviewed and deliberately left untouched as existing P1 rows. | check:outstanding-issues passed (260 rows, 119 open, unique ids, no ids deleted from base 1ff9ed206456); prettier --check clean on the changed file; rows written via scripts/outstanding-issues.mjs, never hand-edited; no code gates run - docs-only diff | +| 2026-08-07 | claude/handover-review-nlhuln | de8b74e2fb94d1ec9b1982c15a2dea43421c3eee | outstanding-issues ledger capture after the mode-nav rollout (PR #1685) (supersedes 2026-08-07) | Confirmed #256's two remaining suspected section sets are dead (/forms/ and /differentials/presentations/ draw no section nav; form-decision-context-mobile is a testId not an id; ruled out sectionId indirection in both files). Added #271 (delete-or-keep the consumer-less action kind) and #272 (addon-slot single-owner rule held by two lists agreeing by coincidence) — renumbered from this PR's original #261/#262 because main claimed #261-#270 via PR #1678 design-system tracks in the interim. #207/#226/#231 reviewed and deliberately left untouched as existing P1 rows. | check:outstanding-issues passed (270 rows, 129 open, 141 archived, unique ids, next-id=273 above the highest, no ids deleted from base d32dd549a3dd); prettier --check clean on the changed file; rows written via scripts/outstanding-issues.mjs, never hand-edited; no code gates run - docs-only diff | +| 2026-08-07 | cursor/document-citation-landing-7bc3 | 82378a2bb4b875f1b610ef60c0ec3c94ee461f10 | document-viewer citation landing | ship: PDF-first citation landing; excerpt chip; indexed text collapsed until inspect/search; phone overview condensed; rail pin removed | unit 5539 pass; playwright critical citation+mobile PDF-first 2 pass; browser QA desktop/phone pass; typecheck; lint; build ALLOW_BUILD_WITH_DEV_SERVER=1; eval:rag:offline 36 golden; verify:pr-local stages green (first run flaked design-system-adoption timeout, retry green) | +| 2026-08-07 | claude/search-bar-mobile-layout-buu0io | 9d64388c0ce530d0c20bb7efe8ffb32cd928319c | phone results-filter idiom: 7 modes off MobileResultFilterControl onto ResultFilterTrigger + ResultFilterSheet; band, docs, tests | changes-shipped | typecheck; lint; test 5538 passed (1 pre-existing pr-handoff-stop failure, baselined on unmodified tree); build; check:rag:fixtures; check:bundle-budget +6.3% within tolerance; targeted Playwright: ui-accessibility 16, ui-specifiers+ui-formulation 12, ui-tools 5, ui-smoke 2, ui-stress 3 | +| 2026-08-08 | claude/differentials-inpage-navigation-h2u8fq (PR #1715) | c67c4927d8a088be267b8bd280a06f300aad60bc | differentials detail: adopt PR #1688 default in-page navigation template | Header rebuilt to the four template slots (back / title + active-section chevron sheet / ellipsis actions / weighted segment track); new detail-section-index.ts shapes the five tabs as DocumentSections reusing DocumentSectionTrack + DocumentSectionList; labelled strip gated to sm+; max-sm:static -> relative so the absolutely-positioned track keeps a positioned ancestor; tab panel renamed via aria-label since the sm-hidden strip cannot label it; sheets kept as siblings of PhoneHeaderCollapsePortal; no scroll spy (discrete panels). Adoption manifest regenerated - the dropped Tabs/ui-tools association was a coincidental capital-T comment match, not lost coverage. | typecheck exit 0; lint clean --max-warnings 0; verify:pr-local 5557 passed, stops only at pre-existing pr-handoff-stop (confirmed by stashed re-run on clean base); verify:phone-chrome lock-parity+runtime PASS, contracts 119 passed, changed-browser ui-tools passed, focused-browser 7 passed; new differential-section-nav.dom 9 passed; 40 passed re-run post-format; live browser 0 h-overflow at 320/390/768, collapse matches DocumentViewer (data-scroll-hidden=true, stack bottom 0) | | 2026-08-07 | cursor/phone-mode-dense-production-05c0 (PR #1648) | 4e0cca2ccbc19ed676765b029642da0afea6215a | Run PR sweep: CI fix + threads + drift | before: behind 17, checks green, 0 threads → after: merged origin/main (conflicts: none), CI re-running; 0 threads | merge-tree clean; git merge origin/main + push; no provider-backed checks run | | 2026-08-07 | cursor/tools-search-mockups-72e1 (PR #1653) | a7fbc26a917b347d90c6bab1e6c1b2ede6422263 | Run PR sweep: CI fix + threads + drift | before: behind 17, checks green, 0 threads → after: merged origin/main (conflicts: none), CI re-running; 0 threads | merge-tree clean; git merge origin/main + push; no provider-backed checks run | | 2026-08-07 | cursor/ship-first-redesign-mockups-2398 (PR #1654) | 9d9eb0be47073a7f051a5885359b82f2ff978a85 | Run PR sweep: CI fix + threads + drift | before: behind 17, checks green, 0 threads → after: merged origin/main (conflicts: none), CI re-running; 0 threads | merge-tree clean; git merge origin/main + push; no provider-backed checks run | @@ -717,14 +727,6 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-07 | cursor/viewer-phase2a-frame-controls-1db8 (PR #1687) | 5c10730be1641a386ee8c8476778933588a822fc | Run PR sweep: CI fix + threads + drift | before: up to date with main, Static PR fail (format:changed pdf-canvas-viewer), 1 outdated CodeRabbit thread (ref sync) already fixed on head → after: prettier format fix pushed; thread left open (no review-write API as cursor[bot]); CI re-running | format:changed fail→prettier --write pdf-canvas-viewer.tsx; format:changed PASS locally; no provider-backed checks run | | 2026-08-07 | claude/search-bar-mobile-layout-buu0io (PR #1689) | a152ffd89c962e3589509c0c3740dc429264063a | Run PR sweep: CI fix + threads + drift | before: behind 1, required CI green (advisory lighthouse fail ignored), 1 CodeRabbit lighthouse baseline thread (human disagreement in progress) → after: waited for CI settle, disabled automerge, merged origin/main, pushed, re-enabled automerge; thread left open for human | settled CI then merge+push; no provider-backed checks run; advisory lighthouse not chased | | 2026-08-07 | claude/issues-256-section-nav-clean (PR #1697) | 2b99db1f00c42fd7c11c4bf8acbad904ef5003e1 | Run PR sweep: CI fix + threads + drift | before: DIRTY/PR mergeability fail, behind 3, merge-tree CLEAN, 0 threads → after: merged origin/main (conflicts: none), CI re-running; 0 threads | merge-tree clean; git merge origin/main + push; no provider-backed checks run | -| 2026-08-07 | cursor/privacy-live-signal-variants-bc81 (PR #1676) | f63eba10f5e9b6db047302c34412ea5261cd410b | PR #1676 unblock | before: PR policy fail (missing Clinical Governance Preflight; privacy* mockup paths trip clinicalRisk), behind-but-clean then main advanced; after: PR body preflight completed (policy green), tip synced to main via merge f63eba10 (duplicate local merge discarded), merge-tree clean, 0 unresolved threads, required CI in progress on synced tip; no code fix needed | local evaluatePullRequestPolicy ok after body; PR policy run 31172248093/31172327100 success; merge-tree clean; no provider-backed checks run | -| 2026-08-07 | cursor/pr-1676-unblock-ledger-ef51 (PR #1677) | dec8f7489b4e5492e924af30dec85859932af3bb | review-and-fix | synced origin/main (behind-but-clean DIRTY; merge-tree clean); Bugbot mid-table finding dispositioned (row was tip-append before #1679; post-merge order correct; ledger guard passed); no P0/P1; 0 threads; no code fix; merge left to user | check:branch-review-ledger pass; ledger:dedupe none; merge-tree clean vs origin/main; format unchanged; no provider gates | -| 2026-08-07 | cursor/pr-1676-unblock-ledger-ef51 (PR #1677) | 574702a681cbb4d455151da023428d16c22fb460 | review-and-fix | late-synced origin/main after CI green (brought #1678 cn/tailwind-merge; remote merge 574702a6); prior sync cleared DIRTY; Bugbot mid-table finding dispositioned (tip-append before #1679; post-merge order correct); no P0/P1; 0 threads; no code fix; merge left to user | check:branch-review-ledger pass; ledger:dedupe none; merge-tree clean; prior tip required CI green; format unchanged; no provider gates | -| 2026-08-07 | cursor/pr-1676-unblock-ledger-ef51 (PR #1677) | fddf495b5176f570a5238b5c17326f64f333ecff | review-and-fix | synced origin/main (behind-but-clean DIRTY; merge-tree clean; main advanced with #1684/#1666); 0 review threads; no P0/P1; no code fix; merge left to user | merge-tree clean vs origin/main; ledger:dedupe none; prior tip required CI green except stale PR mergeability; format unchanged; no provider gates | -| 2026-08-07 | claude/issues-256-section-nav-clean | 169323053db5c572d183d59c113ecd0c76e7aca5 | issues #256: forms section anchors + differentials presentation set (PR #1697) | Wired all six formSections anchors in form-detail-page.tsx (four direct ids, two breakpoint pairs via existing mobile wrappers and single-child desktop wrappers, no component signature change); deleted differentialPresentationSections and declared /differentials/presentations/ locally-owned instead, since three of its six sections declared a -mobile targetId ReviewPanels can never satisfy and the page owns MobileTabs below xl plus the xl review sidebar. Added a registered browser spec because source-text and jsdom guards both structurally cannot see breakpoint-variant resolution. | lint exit 0; typecheck clean; test 519/520 files (pr-handoff-stop confirmed pre-existing via stashed re-run); check:gate-manifest and check:ci-scope pass with the new spec in both playwright allowlists; ui-forms-section-nav + ui-accessibility 18 passed incl real-record nav with 6 links and exactly one variant per pair visible at 390px and 1280px; binding guard mutation-checked red on one removed id; browser spec observed failing when nav genuinely absent; format clean. Environment: npm ci blocked (main lockfile needs Node >=24.15, container has 24.13), tailwind-merge@3.6.0 materialised from tarball only | -| 2026-08-07 | claude/handover-review-nlhuln | de8b74e2fb94d1ec9b1982c15a2dea43421c3eee | outstanding-issues ledger capture after the mode-nav rollout (PR #1685) | Confirmed #256's two remaining suspected section sets are dead (/forms/ and /differentials/presentations/ draw no section nav; form-decision-context-mobile is a testId not an id; ruled out sectionId indirection in both files). Added #261 (delete-or-keep the consumer-less action kind) and #262 (addon-slot single-owner rule held by two lists agreeing by coincidence). #207/#226/#231 reviewed and deliberately left untouched as existing P1 rows. | check:outstanding-issues passed (260 rows, 119 open, unique ids, no ids deleted from base 1ff9ed206456); prettier --check clean on the changed file; rows written via scripts/outstanding-issues.mjs, never hand-edited; no code gates run - docs-only diff | -| 2026-08-07 | claude/handover-review-nlhuln | de8b74e2fb94d1ec9b1982c15a2dea43421c3eee | outstanding-issues ledger capture after the mode-nav rollout (PR #1685) (supersedes 2026-08-07) | Confirmed #256's two remaining suspected section sets are dead (/forms/ and /differentials/presentations/ draw no section nav; form-decision-context-mobile is a testId not an id; ruled out sectionId indirection in both files). Added #271 (delete-or-keep the consumer-less action kind) and #272 (addon-slot single-owner rule held by two lists agreeing by coincidence) — renumbered from this PR's original #261/#262 because main claimed #261-#270 via PR #1678 design-system tracks in the interim. #207/#226/#231 reviewed and deliberately left untouched as existing P1 rows. | check:outstanding-issues passed (270 rows, 129 open, 141 archived, unique ids, next-id=273 above the highest, no ids deleted from base d32dd549a3dd); prettier --check clean on the changed file; rows written via scripts/outstanding-issues.mjs, never hand-edited; no code gates run - docs-only diff | -| 2026-08-07 | claude/search-bar-mobile-layout-buu0io | 9d64388c0ce530d0c20bb7efe8ffb32cd928319c | phone results-filter idiom: 7 modes off MobileResultFilterControl onto ResultFilterTrigger + ResultFilterSheet; band, docs, tests | changes-shipped | typecheck; lint; test 5538 passed (1 pre-existing pr-handoff-stop failure, baselined on unmodified tree); build; check:rag:fixtures; check:bundle-budget +6.3% within tolerance; targeted Playwright: ui-accessibility 16, ui-specifiers+ui-formulation 12, ui-tools 5, ui-smoke 2, ui-stress 3 | | 2026-08-07 | cursor/remove-specifiers-back-arrow-f1c4 | 095791235d58a6309b21b139911a1aad9fe9086b | specifiers-search-results-breadcrumb | removed lone ← Specifiers crumb from search results; deep pages unchanged | format,typecheck,lint,ensure+phone-spot-check | | 2026-08-07 | cursor/phone-mode-dense-production-05c0 (PR #1648) | 1091b17933beba655dac3e37f0e5c1bc4cdfb679 | Run PR sweep: CI fix + threads + drift | No action needed: PR required green, no unresolved review threads, not behind main. Only advisory Lighthouse job failing (never chased). | get_check_runs (PR required: success), get_review_comments (0 unresolved threads) | | 2026-08-07 | cursor/tools-search-mockups-72e1 (PR #1653) | 1df72ba6119226aee92b203db8188f58851a6d3c | Run PR sweep: CI fix + threads + drift | No action needed: PR required green, no unresolved review threads, not behind main. Only advisory Lighthouse job failing (never chased). | get_check_runs (PR required: success), get_review_comments (0 unresolved threads) | @@ -744,28 +746,48 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-08 | claude/ds-a4-component-defects | a029a543f744eb80e608ec482aacdbdc5f5599c2 | unblock PR #1712 | Merged origin/main (ef28960e) to clear dirty mergeable_state: real conflict in docs/branch-review-ledger.md auto-merged via merge=ledger driver. Prior tip 9ba483d3 was 1 behind main. Static PR and PR required failures were dirty-state blockers (GitHub could not build refs/pull/1712/merge). Proved post-merge: merge-tree clean, check:branch-review-ledger, check:design-system-contract. | merge-tree clean; ledger:dedupe; check:branch-review-ledger; check:design-system-contract | | 2026-08-08 | claude/ds-a4-component-defects | d3a697aa8784c9cbdecfba24402cf2269bfe15d4 | heavy review-and-fix PR #1712 | Lint blocker fixed (react-hooks/refs in Pagination); CodeRabbit threads dispositioned; synced main (4a9d81d3 Lighthouse pin); merge-tree clean; verify:cheap 5567 passed; verify:pr-local green; check:design-system-contract passed | lint; typecheck; prettier --check .; verify:cheap (5567 passed); verify:pr-local; check:design-system-contract; check:branch-review-ledger; vitest ui-v2-components.dom (75 passed) | | 2026-08-08 | cursor/run-pr-sweep-ledger-d56c (PR #1698) | ccf7284cbbe2315e5dc6a1bf126403a8a2205fa7 | Run PR sweep: CI fix + threads + drift | before: DIRTY/CONFLICTING, PR mergeability fail, behind 33, 0 threads, fake single-parent merge tip → after: real merge origin/main (conflicts resolved: docs/outstanding-issues.md + lighthouse-budget.json took main), merge-tree clean, unique diff ledger-only, 0 threads | check:outstanding-issues pass; check:branch-review-ledger pass; ledger:dedupe none; merge-tree clean; format; no provider-backed checks run | -| 2026-08-08 | claude/ds-doc-corrections | 534405600dca67317b4d60266cda03ec95f028e7 | M1 stranded doc corrections (docs/outstanding-issues.md #262/#266, docs/design-system/COMPONENTS.md TextField row + section 4) | authored and handed off as PR #1719; every inherited figure re-measured against origin/main rather than copied forward, and the stranded version's 'eight shadow tokens, focus 2' claim was found wrong — LEGACY_SHADOW_ALIAS matches seven tokens and has never included focus | check:outstanding-issues pass (274 rows, unique ids, no ids deleted from base); prettier --check . pass whole-tree; legacyShadowAliases re-measured 228 via the contract's own analyzers; docs-only diff so no unit/lint/typecheck/browser gate applies | -| 2026-08-08 | claude/differentials-inpage-navigation-h2u8fq (PR #1715) | c67c4927d8a088be267b8bd280a06f300aad60bc | differentials detail: adopt PR #1688 default in-page navigation template | Header rebuilt to the four template slots (back / title + active-section chevron sheet / ellipsis actions / weighted segment track); new detail-section-index.ts shapes the five tabs as DocumentSections reusing DocumentSectionTrack + DocumentSectionList; labelled strip gated to sm+; max-sm:static -> relative so the absolutely-positioned track keeps a positioned ancestor; tab panel renamed via aria-label since the sm-hidden strip cannot label it; sheets kept as siblings of PhoneHeaderCollapsePortal; no scroll spy (discrete panels). Adoption manifest regenerated - the dropped Tabs/ui-tools association was a coincidental capital-T comment match, not lost coverage. | typecheck exit 0; lint clean --max-warnings 0; verify:pr-local 5557 passed, stops only at pre-existing pr-handoff-stop (confirmed by stashed re-run on clean base); verify:phone-chrome lock-parity+runtime PASS, contracts 119 passed, changed-browser ui-tools passed, focused-browser 7 passed; new differential-section-nav.dom 9 passed; 40 passed re-run post-format; live browser 0 h-overflow at 320/390/768, collapse matches DocumentViewer (data-scroll-hidden=true, stack bottom 0) | | 2026-08-08 | claude/differentials-inpage-navigation-h2u8fq (PR #1715) | f0b27ec857a70130d1616ddc1ccae1c9952c697b | heavy review-and-fix PR #1715 differentials in-page navigation | merge-blocker cleared (origin/main sync); no P0/P1 findings; phone-chrome + verify:pr-local green on f0b27ec8 | merge-tree clean post-sync; verify:phone-chrome lock-parity+runtime PASS, contracts 119 passed, changed-browser ui-tools passed, focused-browser 7 passed (19.4s); verify:pr-local Test Files 523 passed (523), Tests 5547 passed \| 4 skipped (5551); lint+typecheck+build+rag fixtures green; 0 unresolved review threads | +| 2026-08-08 | claude/ds-doc-corrections | 534405600dca67317b4d60266cda03ec95f028e7 | M1 stranded doc corrections (docs/outstanding-issues.md #262/#266, docs/design-system/COMPONENTS.md TextField row + section 4) | authored and handed off as PR #1719; every inherited figure re-measured against origin/main rather than copied forward, and the stranded version's 'eight shadow tokens, focus 2' claim was found wrong — LEGACY_SHADOW_ALIAS matches seven tokens and has never included focus | check:outstanding-issues pass (274 rows, unique ids, no ids deleted from base); prettier --check . pass whole-tree; legacyShadowAliases re-measured 228 via the contract's own analyzers; docs-only diff so no unit/lint/typecheck/browser gate applies | | 2026-08-08 | claude/ds-doc-corrections | b4051d21f38755f7d37dbc2b49994f689af801b5 | M1 stranded doc corrections, final reviewed head (adds the review-response commit: COMPONENTS.md section 4 integration-vs-adoption split and the re-measured ui-primitives row) | merged to main as 8cffad59a. Supersedes the 534405600 record, which was accurate at that head but predates the review pass. Three findings, all valid and all fixed: Codex caught four future-dated 2026-08-09 records (corrected to the 2026-08-08 authoring date by f3a91c67c, verified none remain); CodeRabbit caught 'Select/choice controls remain separate adoption work', wrong on both axes since select.tsx consumes FormField and Select has 2 production importers while SearchField has zero; CodeRabbit caught a stale '27 adopted', and re-measuring that row also corrected 686 to 698 lines and 200 to 157 production importers of ui-primitives (200 was close to the 202 mockup-inclusive figure) | prettier --check . pass whole-tree; check:outstanding-issues pass (274 rows, unique ids, no ids deleted from base); adoption figures read from the generated adoption-manifest.json; docs-only diff so no unit, lint, typecheck or browser gate applies to it | | 2026-08-08 | claude/ds-tap-and-linkaction | 6916c80526603514d91bd29d224959dd420af59c | M5 LinkAction tone refusal plus re-measured corrections to outstanding-issues #270, #118 and #269 — final reviewed head, adds the tone?: never fix, its type-contract test and both regenerated manifests | PR #1720, superseding the 824c1b74a record. Codex found the Omit form still accepted tone through a spread; verified with a focused tsc probe before changing anything (Omit accepted the spread with no diagnostic, tone?: never rejected it with TS2345), because excess-property checking only fires on object literals. Fixed with tone?: never plus a type-level contract test that stops compiling if the prop widens back. CodeRabbit's future-dated finding fixed in ff307cc5b. CodeRabbit's ledger-scope finding does not apply: that row records a different ref and head and was accurate as written, but a superseding row for the final #1719 head was appended anyway since its scope grew after the review pass | tsc -p tsconfig.typecheck.json --noEmit exit 0 zero diagnostics; lint exit 0; check:design-system-contract exit 0 (676 production files, legacy shadow aliases 228 confirming the #262 re-measure, adoption 53 components 55 roots, design-sync 53 components and 7 guidelines); check-icon-scale.mjs --strict exit 0; vitest threads pool 3 files 164 tests passed; check:outstanding-issues pass; check:branch-review-ledger pass; prettier --check . pass whole-tree; main merged in with merge-tree proven clean first and an id-set proof over both merge parents showing 274 ids each side, none lost, none invented | -| 2026-08-08 | cursor/safety-plan-phone-safe-area-624a (PR #1711) | ad1b1f5db24ed68ee4c0d5963620e4562829884e | heavy review-and-fix PR #1711 | fixed CodeRabbit sm:py guard parity; late-synced #1720 behind-but-clean; no P0/P1; Bugbot none; threads cleared; merge-tree clean; required CI green on 78c14205 pre-sync | vitest safety-plan+standalone 18p; verify:cheap 523/5582; verify:pr-local format+lint+typecheck+test+build+rag-fixtures; Production UI critical+(1)(2)(3)+PR required SUCCESS on 78c14205; no provider gates | -| 2026-08-07 | cursor/document-citation-landing-7bc3 | 82378a2bb4b875f1b610ef60c0ec3c94ee461f10 | document-viewer citation landing | ship: PDF-first citation landing; excerpt chip; indexed text collapsed until inspect/search; phone overview condensed; rail pin removed | unit 5539 pass; playwright critical citation+mobile PDF-first 2 pass; browser QA desktop/phone pass; typecheck; lint; build ALLOW_BUILD_WITH_DEV_SERVER=1; eval:rag:offline 36 golden; verify:pr-local stages green (first run flaked design-system-adoption timeout, retry green) | | 2026-08-08 | claude/ds-close-276 (PR #1724) | 75c89993f3ea23b70a250f605b21437b4ea9aac8 | PR #1724 review-and-fix | fixed Codex P2 wrong #118 Lighthouse cause (150 overwrite vs 151 pin); dispositioned CodeRabbit #276 archive claim as false (issues:done move); merge-tree clean; required CI was green on prior tip 8ae8c48f; no Bugbot findings | check:outstanding-issues pass; prettier --check docs/outstanding-issues.md pass; no provider-backed checks | | 2026-08-08 | claude/ds-close-276 (PR #1724) | 4baa9a1b42fa05731a6f983b3e0d0ebbd37f5271 | PR #1724 review-and-fix | synced origin/main (#1725 conflict on outstanding-issues resolved by preferring main queue then re-applying #276 done + corrected #118 diagnosis); Codex P2 fixed; CodeRabbit #276 archive claim dispositioned false; merge-tree clean after sync | check:outstanding-issues pass; prettier --check docs/outstanding-issues.md pass; merge-tree clean vs origin/main; no provider-backed checks | -| 2026-08-08 | claude/document-viewer-optimization-tu8tnj | 98b799a372b1e341c86e8807d5cf37e987413e49 | document viewer phone/PWA rework: CSP-blocked native reader removed, one toolbar, fit-mode pinch, canvas pixel budget, source-first phone order, in-window detail-refetch guard, pdf.js on-demand fetch + teardown, image/signed-URL wins | ship: PR #1741 | lint, typecheck, test 5625 pass (1 pre-existing root-container failure), build, check:rag:fixtures, check:bundle-budget 1499.8 KiB vs base 1500.0 KiB, check:runtime, check:installed-lock-parity, format:changed; verify:ui not run (container Chromium 141 cannot raster pdfjs 6, see #278) | -| 2026-08-08 | claude/document-viewer-optimization-tu8tnj | 2359e158cb7bca5954e9c5ee84ca0766964ad901 | PR #1741 document-viewer phone/PWA review-and-fix | supersede: fixed Production UI phone Zoom/section-trigger; handlePdfLoadSuccess clamp; prior P1/P2 fixes retained; merge-tree clean | prior verify:cheap+pr-local green; ui-smoke selectors fixed for overflow Zoom + revealPhoneHeaderControl; no provider gates | +| 2026-08-08 | claude/mode-routing-search-pages-jabe17 | 3a0bdd62466080ad713873cdd690ae600635a979 | mode routing: one shared home page at /, mode pill retargets the composer, /documents + /medications mode homes | handoff — PR #1744 opened; 2 pre-existing failures verified at base bc33d41 | test:e2e:pr 406 passed/2 failed (both fail at base); vitest 5608 passed/1 failed (pre-existing); lint clean; tsc clean; sitemap:check, docs:check-index, docs:check-inventory, check:design-system-contract, check:outstanding-issues pass; verify:pr-local and verify:ui blocked by pre-existing installed-lock-parity (playwright 1.62.0 vs locked 1.62.1) | +| 2026-08-08 | claude/mode-routing-search-pages-jabe17 | 468cc3fce85726a66098af0600d2b5d5951e3213 | bug-hunt | findings: P1 documents home autoRun on keystroke; P2 stale PWA /?mode=prescribing; P2 landing vs lastAppMode race; P2 /medications?q&run deep-link lost | vitest app-modes+search-route-ownership 36 pass; static ownership/ask-routing proof; no browser/UI/provider | | 2026-08-08 | claude/document-image-mobile-view-30xzw8 | 2394d903a6ca1ba7a84e380c9ed5cada038fa5c0 | document-viewer phone image layout + lightbox geometry (PR #1737) | implemented: capped rail/body grid tracks, removed aspect-ratio min-height transfer, rebuilt phone image viewer (legible open scale, rotation re-fit, clamped pan, double-tap, footer controls) | lint, typecheck, test (5647 pass / 1 pre-existing fail), build, eval:rag:offline, check:bundle-budget, all verify:pr-local static steps by hand; browser gates blocked by #255 | | 2026-08-08 | claude/document-image-mobile-view-30xzw8 | d257df7e11913db1d367535171fac726f47e7f1c | PR #1737 document-viewer phone image review-and-fix | fixed P1 expand fixture/threshold + P2 double-tap stage coords/pointer-up + resize re-clamp; Production UI timeout root cause cleared; merge-tree clean | verify:pr-local PASS (525 files/5653 tests); lint; typecheck; focused vitest 64/64; Production UI delegated to CI | | 2026-08-08 | PR #1740 / claude/inpage-nav-info-pages-v8rhnd | b67f33f65e00529eb0dd1682d6925e708243ee93 | Extract InPageNavHeader (default in-page nav template) + convert differentials detail; PR 1 of 3 | HANDOFF. Template extracted from the duplicated DocumentViewer/differential-detail markup into src/components/in-page-nav/ (InPageNavHeader, PageSection/toDocumentSections, usePageSectionWeights); differential-detail-page converted (-207 lines), behaviour-neutral. section-index.ts untouched so document tests unaffected. DocumentViewer deliberately NOT converged (owns h1, edge-glass-header, visual baselines) - follow-up. Anchor-offset hook generalisation deferred to PR 2 where it is consumed. 3 source-scanning contracts + addon-slot guard updated to follow the markup and additionally assert adoption; addon-slot scan widened to InPageNavHeader or it would go silent for every future adopter. Single failing test (pr-handoff-stop) is a root-uid artifact: chmod 0555 does not block root, reproduced with work stashed on clean tree. | verify:cheap 5618 passed/1 failed (root artifact); verify:pr-local same, short-circuits at test so build not reached; build run separately - Compiled successfully in 53s + client bundle secret check passed; verify:phone-chrome EXIT=0 (stage1 119 passed, stage2 7 passed 23.5s, full UI policy auto not selected); lint/typecheck/prettier --check . clean. No provider-backed gates. Deps installed with engine check relaxed (user-approved; Node 24.13.0 vs jsdom floor 24.15) - lockfile untouched. | -| 2026-08-08 | claude/document-viewer-optimization-tu8tnj | 9a5f79ab133c6ab9ea2a47e93b0101df8db44607 | docs-only: one outstanding-issues row (#285) recording the lowercase authorizationHeader trap surfaced by PR #1741 review | ship: PR #1754 | check:outstanding-issues (283 rows, unique ids, next-id above highest), prettier --check on the changed file; no source touched so lint/typecheck/test/build have no changed failure path | -| 2026-08-08 | claude/mode-routing-search-pages-jabe17 | 3a0bdd62466080ad713873cdd690ae600635a979 | mode routing: one shared home page at /, mode pill retargets the composer, /documents + /medications mode homes | handoff — PR #1744 opened; 2 pre-existing failures verified at base bc33d41 | test:e2e:pr 406 passed/2 failed (both fail at base); vitest 5608 passed/1 failed (pre-existing); lint clean; tsc clean; sitemap:check, docs:check-index, docs:check-inventory, check:design-system-contract, check:outstanding-issues pass; verify:pr-local and verify:ui blocked by pre-existing installed-lock-parity (playwright 1.62.0 vs locked 1.62.1) | -| 2026-08-08 | claude/mode-routing-search-pages-jabe17 | 468cc3fce85726a66098af0600d2b5d5951e3213 | bug-hunt | findings: P1 documents home autoRun on keystroke; P2 stale PWA /?mode=prescribing; P2 landing vs lastAppMode race; P2 /medications?q&run deep-link lost | vitest app-modes+search-route-ownership 36 pass; static ownership/ask-routing proof; no browser/UI/provider | +| 2026-08-08 | claude/document-viewer-optimization-tu8tnj | 98b799a372b1e341c86e8807d5cf37e987413e49 | document viewer phone/PWA rework: CSP-blocked native reader removed, one toolbar, fit-mode pinch, canvas pixel budget, source-first phone order, in-window detail-refetch guard, pdf.js on-demand fetch + teardown, image/signed-URL wins | ship: PR #1741 | lint, typecheck, test 5625 pass (1 pre-existing root-container failure), build, check:rag:fixtures, check:bundle-budget 1499.8 KiB vs base 1500.0 KiB, check:runtime, check:installed-lock-parity, format:changed; verify:ui not run (container Chromium 141 cannot raster pdfjs 6, see #278) | +| 2026-08-08 | claude/document-viewer-optimization-tu8tnj | 2359e158cb7bca5954e9c5ee84ca0766964ad901 | PR #1741 document-viewer phone/PWA review-and-fix | supersede: fixed Production UI phone Zoom/section-trigger; handlePdfLoadSuccess clamp; prior P1/P2 fixes retained; merge-tree clean | prior verify:cheap+pr-local green; ui-smoke selectors fixed for overflow Zoom + revealPhoneHeaderControl; no provider gates | | 2026-08-08 | claude/mode-routing-search-pages-jabe17 | 6d1099b479358caa05c92f236848117feb920d4e | shared-home mode-routed search navigation | no high-confidence P0-P2 PR-introduced defects; prior bug-hunt P1/P2s appear fixed on tip; residual: prescribing submit-from-shared-home URL omits run=1 (pre-existing path), seed effect untested behaviourally, no browser/UI proof this pass | vitest app-modes+search-route-ownership+audit-navigation+pwa-manifest 61 pass; static read of focus files vs origin/main; ledger:lookup NOT REVIEWED; no provider/UI | +| 2026-08-08 | cursor/safety-plan-phone-safe-area-624a (PR #1711) | ad1b1f5db24ed68ee4c0d5963620e4562829884e | heavy review-and-fix PR #1711 | fixed CodeRabbit sm:py guard parity; late-synced #1720 behind-but-clean; no P0/P1; Bugbot none; threads cleared; merge-tree clean; required CI green on 78c14205 pre-sync | vitest safety-plan+standalone 18p; verify:cheap 523/5582; verify:pr-local format+lint+typecheck+test+build+rag-fixtures; Production UI critical+(1)(2)(3)+PR required SUCCESS on 78c14205; no provider gates | +| 2026-08-09 | claude/inpage-nav-pr-2-6d32f9 | 249526988ea3d65c54e69ee7ca05e514bff50ed8 | in-page-nav PR 2: convert six information routes onto InPageNavHeader; delete the shell-owned pill rail | Shipped as PR #1766. Seven components converted; actions API widened for Server Components; two DSM routes' missing anchors wired; rail and section kind removed; new per-route rendered-DOM section contract added. | verify:pr-local 527/530 files 5708 tests; verify:cheap 529/530 5710 tests; verify:phone-chrome escalated to full Chromium 398 passed then 13/13 updated specs pass; typecheck + prettier clean; Playwright production build compiled. Residual failures proven pre-existing on pristine base 9ab3b73a (issues #285, pr-handoff-stop env). | | 2026-08-08 | dependabot/npm_and_yarn/js-yaml-4.3.1 | 072b83f79a70037a04a8412844c041db43c9ce48 | PR #1668 unblock | synced main; merge-tree clean; required CI was green on prior tip e9516021; js-yaml 4.3.1 + nanoid 3.3.18 preserved; no unresolved threads; CI re-run after sync | pre-sync PR required pass; Production UI skipped (deps); post-sync pending | | 2026-08-08 | claude/planning-build-intelligence-9ot0nm | 1ebc84bb288b516bb322c09cde2889e981d302a4 | AGENTS.md reasoning-effort calibration section (docs-only) | Authored and handed off as PR #1730; docs-only, pr-policy classifier returns clinicalRisk/operationalRisk/ragRanking false | prettier --check . (repo-wide, pass); docs:check-links (1665 refs resolve, pass); pr-policy classifyPullRequestFiles(AGENTS.md) | | 2026-08-08 | claude/planning-build-intelligence-9ot0nm | 2b0ad7d41d841c13515f10de7c41e449470dfa78 | pr-1730 review-and-fix | Deep review + Bugbot: no P0/P1; fixed 2 scoped P2 clarity risks (version-bump under-planning; live-state vs provider boundary). Residual: OPENAI_*_REASONING_EFFORT vocab overlap. Merge-tree clean; required CI was green pre-push. | prettier --check AGENTS.md; docs:check-links (1667); verify:pr-local (docs route pass); verify:cheap (524 files / 5607 tests pass); pr-policy classify clinical/operational/rag false; Bugbot no P0-P2 | | 2026-08-08 | dependabot/npm_and_yarn/js-yaml-4.3.1 | a79943df33e653d2a65d4db2f192ee77c22ab75a | PR #1668 unblock | late-synced main after CI green on f04a96c3; merge-tree clean (GitHub DIRTY was stale); js-yaml 4.3.1 + nanoid 3.3.18 preserved; no unresolved threads; CI re-run after push | pre-late-sync: PR required pass on f04a96c3; Production UI skipped; post-sync pending | +| 2026-08-08 | cursor/safety-plan-copy-timer-a650 | cf57b34a36b768e150cd776f7e19acfd984245f7 | PR #1717 unblock | fixed missing it() closer from Copilot autofix; merged origin/main after #1668; merge-tree clean; no unresolved threads | local: vitest patient-safety-plan.dom.test.tsx (8/8); format ok; pending hosted CI after push | +| 2026-08-08 | cursor/safety-plan-copy-timer-a650 | 3142eb9a93275ce2c2435523560b4ed6624d8f53 | PR #1717 unblock | fixed parse + no-explicit-any from Copilot autofix; merged origin/main after #1668; merge-tree clean; 0 threads | local: vitest 8/8; eslint file clean; format ok; pending hosted CI | +| 2026-08-08 | cursor/specifiers-builder-mobile-f72a | 894677891e2793cadc721b106e5abb715ff918e3 | specifiers-builder-pathway-mobile | pass-pathway-strip-and-mobile-overflow | npm run test:e2e -- tests/ui-specifiers.spec.ts --project=chromium: 6 passed | +| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 | bed84986742ca85b3724dd3ccc0758d4b9649934 | differentials diagnoses query-lit stream | implemented query-lit Diagnoses stream with match jump, related clusters, compare select, browse chapters; PR #1757 | unit:pass;lint:pass;typecheck:pass;verify:ui:not-run | +| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 | 106124d8084a1eab2eddab828076d10f96d3cedc | differentials diagnoses query-lit stream | implemented query-lit Diagnoses stream with match jump, related clusters, compare select, browse chapters; PR #1757 | unit:pass;lint:pass;typecheck:pass;verify:ui:not-run | +| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 (PR #1757) | e7529dd2dd847b0bfd4b53daa723a8f5a329a50e | heavy review-and-fix | synced main; fixed P1 compare id drop + P2 mobile threshold + P2 query normalize; 3 threads need reply (API 403) | vitest differential-stream+differentials-navigation+differentials 42 passed; no provider-backed checks | +| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 (PR #1757) | d76c4cc8b8633a65df66ea46b090c2864a2c1592 | heavy review-and-fix | CI fix on tip: type-scale text-3xs; presentations redirect lowercases+drops unknown while preserving valid cross-workflow ids; prior P1/P2 fixes retained | check:type-scale; vitest audit-nav+differentials-nav+stream; no provider | +| 2026-08-08 | cursor/fix-differentials-compare-5c66 | fd4801b07309625780b06afef718f93799655885 | differentials-compare-selection-handoff | fixed: preserve cross-presentation compare ids via ad-hoc /differentials/compare; URL ids sync; ModeNav Compare wired | verify:pr-local:5709 passed; test:focused:255 passed | +| 2026-08-08 | cursor/fix-differentials-compare-5c66 (PR #1756) | c739708981083b816843ceec5e50ea00818996b5 | heavy review-and-fix | merged origin/main (docs/adoption/sitemap regenerated); fixed P1 cold-load URL wipe + P2 unsupported criterion + lowercase ids; CodeRabbit empty-state/auto-seed left as intentional ModeNav handoff; threads unreplied (403) | vitest differentials+navigation+compare-selection DOM 37 pass; related nav tests 44 pass; eslint touched files; no provider-backed checks | +| 2026-08-08 | cursor/fix-differentials-compare-5c66 (PR #1756) | bd62d3a23b888d30112fdc11e86fe1811f1919bc | heavy review-and-fix | merged origin/main (docs/adoption/sitemap regenerated); fixed P1 cold-load URL wipe (state-captured ids + defer sync while loading) + P2 unsupported criterion + lowercase ids; CodeRabbit empty-state/auto-seed left as intentional ModeNav handoff; threads unreplied (403) | vitest differentials+navigation+compare-selection DOM 37 pass; related nav tests 44 pass; eslint touched files clean; no provider-backed checks | +| 2026-08-08 | cursor/forms-info-disclosure-68d6 | f5dd1dea495e8d6e9bd5106dcf0a4d062ee02292 | forms-info-disclosure | fixed Form information tick rows to expand via DisclosureGroup | verify:pr-local; forms-information-disclosure.dom.test; check:design-system-adoption | +| 2026-08-08 | cursor/forms-info-disclosure-68d6 | 8f25e6c482d8e4cd879098d7cfd73b7f8603e478 | forms-info-disclosure | fixed Form information tick rows to expand via DisclosureGroup | verify:pr-local; forms-information-disclosure.dom.test; check:design-system-adoption | +| 2026-08-08 | cursor/forms-info-disclosure-68d6 (PR #1735) | 9e1390d73ebbae0bbfc0f81bf3b3921dadf24577 | heavy review-and-fix | CONFLICT merge-tree on docs/design-system/adoption-manifest.json resolved by regenerating (DisclosureGroup form-detail import + main documents/medications routes); product forms DisclosureGroup intent preserved; 0 unresolved threads; no ambiguous clinical/auth conflicts | check:design-system-adoption PASS (53 components, 57 roots); vitest forms-information-disclosure.dom 2/2 PASS; no provider-backed checks | +| 2026-08-08 | cursor/confirm-checklist-polish-195c | 32474bcd20d5fa39097a3f75b85d3af81b404320 | form-detail Confirm checklist polish | shipped spacing/typography polish + DOM guard | vitest form-confirm-callout.dom; visual Form 1A Confirm | +| 2026-08-08 | cursor/confirm-checklist-polish-195c | 3cf0ed99a1a90f46cb7c6aff7e8b7f7bfd6212b8 | form-detail Confirm checklist polish | shipped spacing/typography polish + DOM guard | vitest form-confirm-callout.dom; visual Form 1A Confirm | +| 2026-08-08 | cursor/confirm-checklist-polish-195c | 3cf0ed99a1a90f46cb7c6aff7e8b7f7bfd6212b8 | form-detail Confirm checklist polish (supersedes 2026-08-08) | shipped spacing/typography polish + DOM guard | vitest form-confirm-callout.dom; visual Form 1A Confirm | +| 2026-08-08 | cursor/confirm-checklist-polish-195c | 96c4d3a3a1a46efedfa5b43c4bf1de227c1d19a6 | PR #1734 confirm checklist | clean; no P0/P1/P2 in ConfirmCalloutText/confirmCheckParts/Avoid row | diff vs main; form-1a catalog wiring; vitest form-confirm-callout.dom.test.tsx PASS | +| 2026-08-08 | cursor/confirm-checklist-polish-195c | 89cc8711dd0536c32818cbbd493edff860763a61 | PR #1734 unblock | synced origin/main (behind-but-clean DIRTY; merge-tree clean); no product conflict; advisory lighthouse ignored | merge-tree clean vs origin/main; ledger:dedupe none | +| 2026-08-08 | cursor/compact-services-result-text-9b7d (PR #1731) | f07828041199458bd756090d04fb5105f41e3ca4 | PR #1731 unblock | before: MERGEABLE/BEHIND(1) merge-tree CLEAN tip 14fd8aa9; required CI green (PR required + Production UI 1/2/3 + critical); 0 threads; autoMerge SQUASH armed. after: late-synced origin/main (aa6cf68c from #1668/#1717) via worktree merge (update-branch 403); merge-tree clean; 0 behind; CI will re-run on sync tip; autoMerge left armed; no product code change | gh pr checks --watch: PR required SUCCESS; Production UI (1)(2)(3)+critical SUCCESS; merge-tree clean; ledger:dedupe none; no provider gates | +| 2026-08-08 | cursor/services-content-cleanup-1c73 | 1b62fdabbabcfbb05ccbbae08b070bf7740426d5 | services content cleanup: compact catalogue fields + hide empty detail sections | APPROVE pending required CI; verify:pr-local passed; UI spot-check recommended | verify:pr-local (lint/typecheck/test/build/rag-fixtures) | +| 2026-08-08 | claude/ds-visual-advisory-off-prs (PR #1755) | e6d24190eb1e02c435003d429d88f2d293b14867 | heavy review-and-fix | merged origin/main; fixed Bugbot/Codex P2 merge_group exclusion via event allowlist; synced docs/testing.md + #118 note; CodeRabbit date nit dispositioned (owner +0800); threads unreplied (403) | vitest ci-cache-safety; check:github-actions; no provider-backed checks | +| 2026-08-08 | claude/document-viewer-optimization-tu8tnj | 9a5f79ab133c6ab9ea2a47e93b0101df8db44607 | docs-only: one outstanding-issues row (#285) recording the lowercase authorizationHeader trap surfaced by PR #1741 review | ship: PR #1754 | check:outstanding-issues (283 rows, unique ids, next-id above highest), prettier --check on the changed file; no source touched so lint/typecheck/test/build have no changed failure path | | 2026-08-08 | claude/document-viewer-optimization-tu8tnj (PR #1754) | 41b4bccf7d7229920c33344bec0d46e0f2e48b97 | heavy review-and-fix | CONFLICT merge-tree on docs/outstanding-issues.md resolved: kept main #285 (Node/jsdom floor) + renumbered authorizationHeader trap to #286, next-id=287; merged origin/main; 1 unresolved review thread (comments 403 — skipped); no product code change | check:outstanding-issues PASS (284 rows, next-id=287); prettier --check docs/outstanding-issues.md PASS; ledger:dedupe none; no provider-backed checks | | 2026-08-08 | cursor/fix-diagnosis-back-nav-cd3d | 5f637fcf6c4c4c42513f0dd79899eeff96f8cc9e | differentials diagnosis detail back nav | fixed back to /differentials/diagnoses; phone-chrome green | test:focused 16p; verify:phone-chrome 21p+7p; verify:pr-local pending | | 2026-08-08 | cursor/fix-diagnosis-back-nav-cd3d | 5f637fcf6c4c4c42513f0dd79899eeff96f8cc9e | differentials diagnosis detail back nav (supersedes 2026-08-08) | fixed back to /differentials/diagnoses; phone-chrome + pr-local green | test:focused 16p; verify:phone-chrome ui-phone-scroll 21p + focused 7p; verify:pr-local 5704p | @@ -787,36 +809,14 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-08 | codex/lighthouse-hardening (PR #1746) | d4af6a6356de3327906936a237405e4e7ccba0cb | Run PR sweep: CI fix + threads + drift | DIRTY + 3 Codex P2 + Static contract fail → main sync; process-group kill; suite deadline; normalized samples; contract test updated; threads unreplied (API 403) | vitest live-web-vitals-inputs+check-lighthouse-budget 61; ci-cache-safety 32; no provider-backed checks | | 2026-08-08 | cursor/phone-mode-dense-production-05c0 (PR #1648) | d2a0c8b12bcf3ea6b56c3d8291e4aa09da3e603e | Run PR sweep: CI fix + threads + drift | post-merge Unit coverage: restored usesPhoneSearchLayout min-h ternary for audit-navigation contract; CI re-running | vitest audit-navigation+mode-nav+header-scroll-hide 66 passed; no provider-backed checks | | 2026-08-08 | cursor/site-testing-speed-08c1 (PR #1686) | 11e35727a6a9b3b776e890e6a63e43fea9b0a437 | Run PR sweep: CI fix + threads + drift | post-merge Unit coverage: added ui-phone-scroll-document-rail.spec.ts to playwright PR shard 1; CI re-running | vitest playwright-pr-shards 4 passed; playwright-pr-shards --validate OK; no provider-backed checks | -| 2026-08-08 | cursor/safety-plan-copy-timer-a650 | cf57b34a36b768e150cd776f7e19acfd984245f7 | PR #1717 unblock | fixed missing it() closer from Copilot autofix; merged origin/main after #1668; merge-tree clean; no unresolved threads | local: vitest patient-safety-plan.dom.test.tsx (8/8); format ok; pending hosted CI after push | -| 2026-08-08 | cursor/safety-plan-copy-timer-a650 | 3142eb9a93275ce2c2435523560b4ed6624d8f53 | PR #1717 unblock | fixed parse + no-explicit-any from Copilot autofix; merged origin/main after #1668; merge-tree clean; 0 threads | local: vitest 8/8; eslint file clean; format ok; pending hosted CI | | 2026-08-08 | origin/main (PR #1722 follow-up) | eda8fe872de040e304621bce49535e1dfebb091e | Lighthouse testing thorough review | P1 fixed locally: stale Chrome 150 baseline, unbounded runner phases, and live input/process limits hardened; advisory policy retained | offline review; check:ci-scope PASS; test:ci-workflows 13 files 248 passed 11 skipped; check:github-actions PASS; verify:lighthouse dry-run PASS; no provider-backed checks run | | 2026-08-08 | codex/lighthouse-hardening (PR #1746) | 038058ea63837e7c4a90e84b7f8f860aacc51e13 | heavy review-and-fix | CONFLICT merge-tree on lighthouse-budget.json resolved: kept main baseline measurements + this PR Lighthouse hardening (scripts/workflows/ci-change-scope); visual-baseline policy matches main; 3 unresolved threads (comments 403 — skipped) | ci-change-scope --self-test PASS; vitest check-lighthouse-budget+ci-cache-safety 84/84 PASS; no provider-backed checks | -| 2026-08-08 | cursor/forms-info-disclosure-68d6 | f5dd1dea495e8d6e9bd5106dcf0a4d062ee02292 | forms-info-disclosure | fixed Form information tick rows to expand via DisclosureGroup | verify:pr-local; forms-information-disclosure.dom.test; check:design-system-adoption | -| 2026-08-08 | cursor/forms-info-disclosure-68d6 | 8f25e6c482d8e4cd879098d7cfd73b7f8603e478 | forms-info-disclosure | fixed Form information tick rows to expand via DisclosureGroup | verify:pr-local; forms-information-disclosure.dom.test; check:design-system-adoption | -| 2026-08-08 | cursor/forms-info-disclosure-68d6 (PR #1735) | 9e1390d73ebbae0bbfc0f81bf3b3921dadf24577 | heavy review-and-fix | CONFLICT merge-tree on docs/design-system/adoption-manifest.json resolved by regenerating (DisclosureGroup form-detail import + main documents/medications routes); product forms DisclosureGroup intent preserved; 0 unresolved threads; no ambiguous clinical/auth conflicts | check:design-system-adoption PASS (53 components, 57 roots); vitest forms-information-disclosure.dom 2/2 PASS; no provider-backed checks | -| 2026-08-08 | cursor/confirm-checklist-polish-195c | 32474bcd20d5fa39097a3f75b85d3af81b404320 | form-detail Confirm checklist polish | shipped spacing/typography polish + DOM guard | vitest form-confirm-callout.dom; visual Form 1A Confirm | -| 2026-08-08 | cursor/confirm-checklist-polish-195c | 3cf0ed99a1a90f46cb7c6aff7e8b7f7bfd6212b8 | form-detail Confirm checklist polish | shipped spacing/typography polish + DOM guard | vitest form-confirm-callout.dom; visual Form 1A Confirm | -| 2026-08-08 | cursor/confirm-checklist-polish-195c | 3cf0ed99a1a90f46cb7c6aff7e8b7f7bfd6212b8 | form-detail Confirm checklist polish (supersedes 2026-08-08) | shipped spacing/typography polish + DOM guard | vitest form-confirm-callout.dom; visual Form 1A Confirm | -| 2026-08-08 | cursor/confirm-checklist-polish-195c | 96c4d3a3a1a46efedfa5b43c4bf1de227c1d19a6 | PR #1734 confirm checklist | clean; no P0/P1/P2 in ConfirmCalloutText/confirmCheckParts/Avoid row | diff vs main; form-1a catalog wiring; vitest form-confirm-callout.dom.test.tsx PASS | -| 2026-08-08 | cursor/confirm-checklist-polish-195c | 89cc8711dd0536c32818cbbd493edff860763a61 | PR #1734 unblock | synced origin/main (behind-but-clean DIRTY; merge-tree clean); no product conflict; advisory lighthouse ignored | merge-tree clean vs origin/main; ledger:dedupe none | -| 2026-08-08 | cursor/compact-services-result-text-9b7d (PR #1731) | f07828041199458bd756090d04fb5105f41e3ca4 | PR #1731 unblock | before: MERGEABLE/BEHIND(1) merge-tree CLEAN tip 14fd8aa9; required CI green (PR required + Production UI 1/2/3 + critical); 0 threads; autoMerge SQUASH armed. after: late-synced origin/main (aa6cf68c from #1668/#1717) via worktree merge (update-branch 403); merge-tree clean; 0 behind; CI will re-run on sync tip; autoMerge left armed; no product code change | gh pr checks --watch: PR required SUCCESS; Production UI (1)(2)(3)+critical SUCCESS; merge-tree clean; ledger:dedupe none; no provider gates | -| 2026-08-08 | cursor/services-content-cleanup-1c73 | 1b62fdabbabcfbb05ccbbae08b070bf7740426d5 | services content cleanup: compact catalogue fields + hide empty detail sections | APPROVE pending required CI; verify:pr-local passed; UI spot-check recommended | verify:pr-local (lint/typecheck/test/build/rag-fixtures) | | 2026-08-08 | cursor/presentations-catalogue-tab-fb39 | 3872ea0854da2ce4e3b99ec182bb94a4cb807958 | differentials presentations catalogue ModeNav tab | shipped Presentations catalogue at /differentials/presentations; Compare entry moved to /differentials/compare; verify:pr-local passed; UI smoke confirmed 4 tabs | verify:pr-local; vitest design-system-adoption; curl presentations+compare; browser ModeNav QA | | 2026-08-08 | cursor/presentations-catalogue-tab-fb39 | 59dceae612315e95a1114a215d2d8319e439880d | differentials presentations catalogue ModeNav tab | shipped Presentations catalogue at /differentials/presentations; Compare entry moved to /differentials/compare; verify:pr-local passed; UI smoke confirmed 4 tabs | verify:pr-local; vitest design-system-adoption; curl presentations+compare; browser ModeNav QA | | 2026-08-08 | cursor/forms-results-bar-documents-style-13dc | fd148ca931ab9c023619deed98bae5af787e4253 | sync | PR #1751 unblock: CONFLICTING→MERGEABLE; 11 behind main; merge-tree clean; merge commit fd148ca9 pushed; all static+unit+build+critical CI green; Production UI (1/2/3) pending | merge-tree:clean,static-pr:pass,unit-coverage:pass,build:pass,production-ui-critical:pass,safety:pass,pr-policy:pass,production-ui-1/2/3:pending | | 2026-08-08 | cursor/form-1a-priority-facts-dc36 | e965d96258cdd3d5ad6f520616de0f32bf02498e | Form 1A priority facts: condense cards + Act section detail sheets | implemented; Form 1A Source status card replaced with Act sections 26/31/36/37/41/42; condensed clock/maker/criteria with tap sheets | typecheck pass; lint pass; npm run test 530 files / 5704 passed | | 2026-08-08 | cursor/form-1a-priority-facts-dc36 | 7040b850e655dc7ba9a23ad3e3db765cc1ad7755 | Form 1A priority facts: condense cards + Act section detail sheets | implemented; Form 1A Source status card replaced with Act sections 26/31/36/37/41/42; condensed clock/maker/criteria with tap sheets | typecheck pass; lint pass; npm run test 530 files / 5704 passed | | 2026-08-08 | cursor/form-1a-priority-facts-dc36 | e965d96258cdd3d5ad6f520616de0f32bf02498e | Form 1A priority facts: condense cards + Act section detail sheets (supersedes 2026-08-08) | implemented; Form 1A Source status card replaced with Act sections 26/31/36/37/41/42; condensed clock/maker/criteria with tap sheets | typecheck pass; lint pass; npm run test 530 files / 5704 passed | -| 2026-08-09 | claude/document-viewer-optimization-tu8tnj | b8c94dff2345a7d50c7bbce0c9c344740e6b92b1 | docs: document-viewer Phase 3 handover brief (PR #1765) | Docs-only. Adds docs/plans/document-viewer-phase3-handover.md scoping Phase 3 to all capabilities except crop-to-page overlay (bbox absent from DocumentDetailImage; plumbing crosses src/lib/**document** and forces a governance preflight). Corrects ledger #279: measured playwright@1.62.1 expects Chromium 151.0.7922.34, container ships 141.0.7390.37, CI runs HeadlessChrome/151.0.0.0, and pdfjs-dist 6.2.108 needs Map.getOrInsertComputed which ships in 151 not 141 - so the raster failure is container-only and neither proposed remedy (bump Playwright / pin pdfjs down) is needed. Cited #286 for the authorizationHeader casing trap after initially writing #285. | verify:pr-local all ten gates completed, none failed; docs:check-links 1688 references resolve; line refs re-verified against main 8db1e53 | -| 2026-08-08 | cursor/specifiers-builder-mobile-f72a | 894677891e2793cadc721b106e5abb715ff918e3 | specifiers-builder-pathway-mobile | pass-pathway-strip-and-mobile-overflow | npm run test:e2e -- tests/ui-specifiers.spec.ts --project=chromium: 6 passed | -| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 | bed84986742ca85b3724dd3ccc0758d4b9649934 | differentials diagnoses query-lit stream | implemented query-lit Diagnoses stream with match jump, related clusters, compare select, browse chapters; PR #1757 | unit:pass;lint:pass;typecheck:pass;verify:ui:not-run | -| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 | 106124d8084a1eab2eddab828076d10f96d3cedc | differentials diagnoses query-lit stream | implemented query-lit Diagnoses stream with match jump, related clusters, compare select, browse chapters; PR #1757 | unit:pass;lint:pass;typecheck:pass;verify:ui:not-run | -| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 (PR #1757) | e7529dd2dd847b0bfd4b53daa723a8f5a329a50e | heavy review-and-fix | synced main; fixed P1 compare id drop + P2 mobile threshold + P2 query normalize; 3 threads need reply (API 403) | vitest differential-stream+differentials-navigation+differentials 42 passed; no provider-backed checks | -| 2026-08-08 | cursor/differentials-query-lit-stream-8bc0 (PR #1757) | d76c4cc8b8633a65df66ea46b090c2864a2c1592 | heavy review-and-fix | CI fix on tip: type-scale text-3xs; presentations redirect lowercases+drops unknown while preserving valid cross-workflow ids; prior P1/P2 fixes retained | check:type-scale; vitest audit-nav+differentials-nav+stream; no provider | -| 2026-08-08 | cursor/fix-differentials-compare-5c66 | fd4801b07309625780b06afef718f93799655885 | differentials-compare-selection-handoff | fixed: preserve cross-presentation compare ids via ad-hoc /differentials/compare; URL ids sync; ModeNav Compare wired | verify:pr-local:5709 passed; test:focused:255 passed | -| 2026-08-08 | cursor/fix-differentials-compare-5c66 (PR #1756) | c739708981083b816843ceec5e50ea00818996b5 | heavy review-and-fix | merged origin/main (docs/adoption/sitemap regenerated); fixed P1 cold-load URL wipe + P2 unsupported criterion + lowercase ids; CodeRabbit empty-state/auto-seed left as intentional ModeNav handoff; threads unreplied (403) | vitest differentials+navigation+compare-selection DOM 37 pass; related nav tests 44 pass; eslint touched files; no provider-backed checks | -| 2026-08-08 | cursor/fix-differentials-compare-5c66 (PR #1756) | bd62d3a23b888d30112fdc11e86fe1811f1919bc | heavy review-and-fix | merged origin/main (docs/adoption/sitemap regenerated); fixed P1 cold-load URL wipe (state-captured ids + defer sync while loading) + P2 unsupported criterion + lowercase ids; CodeRabbit empty-state/auto-seed left as intentional ModeNav handoff; threads unreplied (403) | vitest differentials+navigation+compare-selection DOM 37 pass; related nav tests 44 pass; eslint touched files clean; no provider-backed checks | -| 2026-08-08 | claude/ds-visual-advisory-off-prs (PR #1755) | e6d24190eb1e02c435003d429d88f2d293b14867 | heavy review-and-fix | merged origin/main; fixed Bugbot/Codex P2 merge_group exclusion via event allowlist; synced docs/testing.md + #118 note; CodeRabbit date nit dispositioned (owner +0800); threads unreplied (403) | vitest ci-cache-safety; check:github-actions; no provider-backed checks | | 2026-08-08 | claude/mode-routing-search-pages-jabe17 (PR #1760) | ce880f23f7ae5cbf9bc28a8a87f1de6585a4343e | home-mode seed navigation context, its contract test, and three outstanding-issues rows | handoff: PR #1760 opened; carries focus/scope context through the cold-/ replaceState, repoints the contract test at behaviour, records #285/#286 and a third #255 reproduction | lint clean; tsc --noEmit clean; unit 5710 passed/1 pre-existing pr-handoff-stop failure (#286); maintainability budgets passed; check:outstanding-issues 284 rows unique; format committed; UI delegated to CI (mismatched Chromium, #255) | | 2026-08-08 | cursor/safety-snapshot-mobile-4ab3 | 63be5e932dc0410f172375edf779190c6a1aadae | differentials Safety Snapshot mobile density redesign | ship; phone visual PASS at ~400px (compact labels, equal 3-col metrics, no redundant summary); unit 21/21; verify:pr-local tests+fixtures+format PASS; build PASS with ALLOW_BUILD_WITH_DEV_SERVER=1 | test:differential-detail,verify:pr-local(partial-build-retry),phone-visual | | 2026-08-08 | cursor/safety-snapshot-mobile-4ab3 | d7fcbc2095ae3cfbefabfad1333b85de0ca42a4b | differentials Safety Snapshot mobile density redesign | ship; tightened Watch-for wrap; phone 390 screenshot + unit 21/21 | test:differential-detail,phone-visual-390 | @@ -825,23 +825,27 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-07 | cursor/site-testing-speed-08c1 | 91bac89827ae2f4f0e59aeed7de6344fe8779a95 | PR #1686 Autopilot+Bugbot review-and-fix: conflicts, threads, Static PR checks, CI/testing selection | fixed: merged origin/main (outstanding-issues #167/#255 archive + #256 keep); removed unused pathToFileURL; added ui-forms-section-nav to PR UI shards (21 specs); no unresolved threads; Bugbot unavailable (usage limit). Local: eslint file max-warnings0, vitest 36/36 focused, shard --validate OK, check:outstanding-issues OK. verify:cheap/pr-local blocked by foreign worktree heavy lock (PID 26228). | eslint scripts/playwright-pr-shards.mjs --max-warnings 0; vitest 36 passed; playwright-pr-shards --validate 21; check:outstanding-issues; verify:cheap/pr-local lock-blocked | | 2026-08-08 | cursor/more-modes-popup-2f4b | 04e6a80653c3ccf103577f6c3886b162498621ed | sidebar more-modes sheet popup | pass | focused-pw tablet rail; test:focused ClinicalSidebar; favourites+therapy wiring; verify:pr-local stages+build+rag-fixtures | | 2026-08-08 | cursor/more-modes-popup-2f4b | bea4b0c09b74368cf6d63e944bac9c1eec6b0c93 | sidebar more-modes sheet popup | pass | focused-pw tablet rail; test:focused ClinicalSidebar; favourites+therapy wiring; verify:pr-local stages+build+rag-fixtures | +| 2026-08-09 | claude/document-viewer-optimization-tu8tnj | b8c94dff2345a7d50c7bbce0c9c344740e6b92b1 | docs: document-viewer Phase 3 handover brief (PR #1765) | Docs-only. Adds docs/plans/document-viewer-phase3-handover.md scoping Phase 3 to all capabilities except crop-to-page overlay (bbox absent from DocumentDetailImage; plumbing crosses src/lib/**document** and forces a governance preflight). Corrects ledger #279: measured playwright@1.62.1 expects Chromium 151.0.7922.34, container ships 141.0.7390.37, CI runs HeadlessChrome/151.0.0.0, and pdfjs-dist 6.2.108 needs Map.getOrInsertComputed which ships in 151 not 141 - so the raster failure is container-only and neither proposed remedy (bump Playwright / pin pdfjs down) is needed. Cited #286 for the authorizationHeader casing trap after initially writing #285. | verify:pr-local all ten gates completed, none failed; docs:check-links 1688 references resolve; line refs re-verified against main 8db1e53 | | 2026-08-09 | claude/document-viewer-optimization-tu8tnj | 5a0d6be02bc92fa2615d2141b338ec8f7c1143b1 | docs: document-viewer Phase 3 handover brief (PR #1765) | Supersedes the earlier row, whose 'all ten gates completed' wording could read as all executable checks having run. Correct scope: verify:pr-local ran the ten gates APPLICABLE to docs-only changes (check:runtime, check:installed-lock-parity, format:changed, sitemap:check, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues); the risk router SKIPPED lint, typecheck, the full unit suite, RAG fixture validation, and build as recognised low-risk documentation scope. Also records the merge resolution: duplicate #286 (main's in-page-nav series vs this branch's authorizationHeader row) resolved by renumbering the branch row to #289, next-id 290, after the auto-merge silently dropped that detail row rather than conflicting. Review findings addressed: governance preflight now required by behaviour per AGENTS.md:257 rather than inferred from pr-policy path classification; API-route scope contradiction resolved; signed-URL warning corrected to state both identity bugs are already fixed on main with regression coverage. | verify:pr-local ten docs-scope gates passed, none failed; check:outstanding-issues 287 rows unique ids next-id=290 no ids deleted; ledger:dedupe 771 unique rows; git merge-tree vs origin/main exit 0; viewer line refs re-verified against 50ef12e | -| 2026-08-09 | claude/document-viewer-phase-3-bj5k5v | 0436c3b495bee05777efc2ea1709230044e26b42 | document viewer Phase 3: page virtualization, rail windowing, signed-URL/decode priority, keyboard reading mode, first canvas browser gate | PR #1772 opened. Self-reviewed during authorship; two real races found and fixed (route effect overriding reader scroll position when pdf.js reports its page count; in-flight scroll gate armed a frame too late). #279 closed by tests/ui-document-canvas.spec.ts; #283 batch-route deferral recorded with the measurement that should decide it; #290 added for the OffscreenCanvas number; #291 added for a pre-existing root-only pr-handoff-stop failure; #252 updated with measured bundle headroom (+9.4% of 10%). Crop-to-page overlay out of scope by design. | verify:pr-local (1 pre-existing root-only failure: pr-handoff-stop, reproduced on origin/main worktree; 5839 passed), build OK, eval:rag:offline 36 golden cases 574 tests, check:bundle-budget within tolerance, check:playwright-pr-shards 23 specs, canvas gate skip-with-reason verified locally and fail-closed verified with CI=1. Browser gates unrunnable here (Chromium 141 vs pdfjs-dist 6 needing 151) - delegated. | +| 2026-08-09 | claude/breadcrumb-header-mockups-cei6lw | 8effa5abe77e9008fb12f6ff996a51aa6d406ab5 | mockups: breadcrumb header study (3 directions) + sitemap/README | self-reviewed; design-scratch only, no production surface changed | typecheck, eslint(changed), prettier --check, sitemap:check, vitest(site-map/mockup-boundary/env-mockups/docs-inventory/route-reachability) 23 passed | +| 2026-08-09 | claude/breadcrumb-header-mockups-cei6lw | ca5e4e7ae9b53af49b362ad11ca988fdd1c3a9d0 | breadcrumb header shipped: InPageNavHeader breadcrumb shape + factsheet detail adoption | self-reviewed; browser-verified at 390/700/834/1280; phone-chrome gate blocked by #255 playwright drift | typecheck, lint, test (5806 passed, 1 pre-existing unrelated fail), build, check:bundle-budget, check:design-system-contract, format:changed, sitemap:check, docs+ledger checks | | 2026-08-09 | claude/m2-ds-gates-blocking | 8ed66a0570c95c2cc8597364467e67966b04854d | M2 design-system gates: #264 + gate 4 of #265 | ready-to-merge; gate 2 enumeration deliberately reverted as non-deterministic (#289) | ds-contract PASS (colour-only 4, numerals 2, inversions 0); mutation-verified x4; lint 0; tsc 0 errors; icon+type scale PASS; focused vitest 126p/3 files; verify:cheap 5777p with 10 pre-existing failures proven identical on pristine origin-main; format:check clean | -| 2026-08-09 | claude/inpage-nav-pr-2-6d32f9 | 249526988ea3d65c54e69ee7ca05e514bff50ed8 | in-page-nav PR 2: convert six information routes onto InPageNavHeader; delete the shell-owned pill rail | Shipped as PR #1766. Seven components converted; actions API widened for Server Components; two DSM routes' missing anchors wired; rail and section kind removed; new per-route rendered-DOM section contract added. | verify:pr-local 527/530 files 5708 tests; verify:cheap 529/530 5710 tests; verify:phone-chrome escalated to full Chromium 398 passed then 13/13 updated specs pass; typecheck + prettier clean; Playwright production build compiled. Residual failures proven pre-existing on pristine base 9ab3b73a (issues #285, pr-handoff-stop env). | | 2026-08-09 | claude/m2-ds-gates-blocking | d204c6f7c84a5e7de3f28061121fda68e7d28670 | M2 design-system gates: #264 + gate 4 of #265 | ready-to-merge; supersedes the 8ed66a05 row — the tap-floor defect renumbered #289 to #291 after main claimed #289/#290, and main was merged in | post-merge ds-contract PASS (colour-only 4, numerals 2, inversions 0) against main's new #1765/#1766 component code; outstanding-issues guard PASS 289 rows next-id=292; ledger guard PASS 774 rows; format clean | | 2026-08-09 | claude/m2-ds-gates-blocking | e8447b042088998d34af750df72a946b1f074b97 | M2 design-system gates: #264 + gate 4 of #265 | ready-to-merge; supersedes the d204c6f7 row — seven review findings fixed, copilot-swe-agent commits merged keeping the safer numeral classifier, tap-floor defect renumbered #291 to #293 after main claimed #291/#292 | ds-contract PASS (colour-only 4, numerals 2, inversions 0); 11 reviewer cases probe-verified; mutation-verified incl. opacity and arbitrary-filter forms; lint 0; tsc 0 errors; format clean; outstanding-issues guard PASS 291 rows next-id=294 no ids deleted; ledger guard PASS 775 rows | +| 2026-08-09 | claude/document-viewer-phase-3-bj5k5v | 0436c3b495bee05777efc2ea1709230044e26b42 | document viewer Phase 3: page virtualization, rail windowing, signed-URL/decode priority, keyboard reading mode, first canvas browser gate | PR #1772 opened. Self-reviewed during authorship; two real races found and fixed (route effect overriding reader scroll position when pdf.js reports its page count; in-flight scroll gate armed a frame too late). #279 closed by tests/ui-document-canvas.spec.ts; #283 batch-route deferral recorded with the measurement that should decide it; #290 added for the OffscreenCanvas number; #291 added for a pre-existing root-only pr-handoff-stop failure; #252 updated with measured bundle headroom (+9.4% of 10%). Crop-to-page overlay out of scope by design. | verify:pr-local (1 pre-existing root-only failure: pr-handoff-stop, reproduced on origin/main worktree; 5839 passed), build OK, eval:rag:offline 36 golden cases 574 tests, check:bundle-budget within tolerance, check:playwright-pr-shards 23 specs, canvas gate skip-with-reason verified locally and fail-closed verified with CI=1. Browser gates unrunnable here (Chromium 141 vs pdfjs-dist 6 needing 151) - delegated. | | 2026-08-09 | claude/document-viewer-phase-3-bj5k5v | 2cd72f111414935d4028a19932992c4ab475dcea | document viewer Phase 3 review-and-fix | PR #1772 deep review+fix: merged origin/main (renumber OffscreenCanvas #290->#294, drop dup #291=#284); fixed key-repeat pageRef, maxObservedCanvasPixels budget, rail remount-via-key, reserved-slot shadow-inset for DS ratchet; dispositioned Bugbot/Codex/Copilot canvas+rotation as already fixed at 8397aeb; #252 tip-only wording; #294 aggregate budgets; CodeRabbit ledger nit deferred to this superseding row | verify:cheap: Test Files 545 passed (545), Tests 5856 passed \| 4 skipped (5860); verify:pr-local completed check:runtime check:installed-lock-parity format:changed sitemap:check docs:check-index docs:check-inventory docs:check-scripts docs:check-links check:branch-review-ledger check:outstanding-issues lint typecheck test build eval:rag:offline failed:(none); focused vitest 47 passed (keyboard+rail+budget+virtualization); design-system-contract legacy shadow aliases 220; merge-tree vs origin/main exit 0; Production UI delegated (pdfjs Map.getOrInsertComputed needs Chromium 151) | -| 2026-08-09 | cursor/dsm-search-header-fix-15d6 | df088c766f1761496189ec09146aa54c23b1c012 | dsm-search-header | pass: removed catalogue page strip; ribbon + category filter match target | vitest dsm-search-empty-state; npm test 5857 passed; lint; typecheck; ensure phone /dsm/search?q=Delirium | -| 2026-08-09 | claude/breadcrumb-header-mockups-cei6lw | 8effa5abe77e9008fb12f6ff996a51aa6d406ab5 | mockups: breadcrumb header study (3 directions) + sitemap/README | self-reviewed; design-scratch only, no production surface changed | typecheck, eslint(changed), prettier --check, sitemap:check, vitest(site-map/mockup-boundary/env-mockups/docs-inventory/route-reachability) 23 passed | -| 2026-08-09 | claude/breadcrumb-header-mockups-cei6lw | ca5e4e7ae9b53af49b362ad11ca988fdd1c3a9d0 | breadcrumb header shipped: InPageNavHeader breadcrumb shape + factsheet detail adoption | self-reviewed; browser-verified at 390/700/834/1280; phone-chrome gate blocked by #255 playwright drift | typecheck, lint, test (5806 passed, 1 pre-existing unrelated fail), build, check:bundle-budget, check:design-system-contract, format:changed, sitemap:check, docs+ledger checks | | 2026-08-09 | cursor/differentials-diagnosis-links-9f18 | 0e77e7bc0842bef4ffc045c6dbea4626152490d1 | differentials diagnosis term links | implemented exact+alias termLinks chips on diagnosis+presentation pages; vitest 58/58; verify:pr-local green | vitest differential-diagnosis-links+detail+section-nav+route; verify:pr-local; ensure spot-check | | 2026-08-09 | cursor/differentials-diagnosis-links-9f18 | 0daa9e2f9fc84e879fd661da94203568309234a6 | PR #1768 Autopilot+Bugbot review-and-fix | Merged origin/main (DIRTY was ledger+detail-page staleness; merge-tree clean). Fixed SEGMENT_SPLIT to spaced-slash only so Delirium / medical psychosis links while alcohol/benzo, DVT/PE, food/fluid stay intact. Dispositioned: Copilot termLinks ??{} + Fragment key already fixed; CodeRabbit clean-keys moot (visibleSectionItems already cleans); CodeRabbit bare-slash split rejected (clinical harm). No Bugbot findings. Threads cleared on push. Merge left to user. | vitest differential-diagnosis-links+detail+route 49/49; verify:cheap exit 0 (543 files, 5828 passed/4 skipped); verify:pr-local exit 0 (lint/typecheck/test/build/rag-fixtures); merge-tree clean vs origin/main; no provider gates | | 2026-08-09 | cursor/differentials-diagnosis-links-9f18 | f784e81bcc0b53ef76b3da07a8e81f96d9bf0c71 | pr-1768 unblock | merged origin/main onto ba590f9; merge-tree clean; DIRTY mergeability cleared; push tip follows amend with this ledger | merge-tree clean; threads resolved; auto-merge was armed | | 2026-08-09 | claude/document-viewer-phase-3-bj5k5v | 156db63f1b60f09791e426b043ea90d427b789ab | post-#1772 test simplification: replace the viewer perf source-text grep with behavioural coverage; de-literalise rail window and keyboard label assertions | PR #1777 opened. Self-review of #1772's own tests against an excessive-strictness challenge. Finding: the client-performance-boundaries grep for resolveLiveCanvasWindow / resolveRenderAheadPages / liveCanvasLimit / requestIdleCallback was not merely brittle, it was INEFFECTIVE - replacing the budget call with a hardcoded 3 leaves every identifier in the file, so it stayed green while the viewer retained three full-zoom canvases (measured both ways). Replaced by a DOM case that binds the budget (VIEWER_MAX_ZOOM at dpr 3 gives ~16.8M backing px against the 24M budget, window collapses to 1) and fails on exactly that substitution. Also exported RAIL_IMAGE_WINDOW so the rail test derives its counts (verified by tuning 6->8: all 7 still pass), and relaxed the keyboard aria-label assertions from exact prose to the key names. Pre-existing greps for disableAutoFetch / canvas.width = 0 / pageToCleanup left alone deliberately - two are now redundant but they are another author's guard. | verify:pr-local (1 pre-existing root-only failure: pr-handoff-stop #291; 5872 passed), build OK 80s + client bundle secret check, eval:rag:offline 36 golden cases / 574 tests, lint + typecheck clean. Sabotage-verified in both directions. Browser gates unrunnable here (#279) - unchanged by this diff. | | 2026-08-09 | claude/planning-build-intelligence-9ot0nm | 3df3cb3993f73cda4dbbc4ac7549f84b3c6ea7ed | Node 24.15 engine floor: engines.node, preinstall hook, check:runtime, session-start provisioning, codex-cloud assertion | Authored and handed off as PR #1771; closes #285; operationalRisk true, clinicalRisk/ragRanking false | test 5800 passed/1 pre-existing root-uid failure (pr-handoff-stop, confirmed on stashed clean tree); lint 0; typecheck 0; prettier --check . pass; check:runtime pass; check:codex-cloud pass; check:outstanding-issues pass; preinstall boundary proof 24.13/24.14.9 reject, 24.15/24.19 accept, 25.0.0 reject; contract test mutation-checked red | | 2026-08-09 | pull/1771 | 466ec4216272c31c5f754db213dbdc529583b167 | PR 1771 runtime floor enforcement | P2: Cloud and Desktop setup paths remain major-only; do not merge until range-aware | static review; check:runtime PASS; check:codex-cloud PASS; ledger PASS; outstanding issues PASS; focused Vitest blocked by active Playwright lease | -| 2026-08-09 | claude/disabled-button-accessibility-piclvr | 722abdb780c715c0a89df268ed48f6c741ffd569 | disabled-placeholder buttons -> aria-disabled + inert handler (25 sites, 13 components); controlDisabled/therapy recipe aria-disabled styling; require-button-wiring redundantDisabledPair gate; wiring-conventions contract rewrite (settles #291) | authored — PR #1778 opened | lint (uncached, exit 0); typecheck; test 5878 passed/1 pre-existing root-env failure in pr-handoff-stop; build; check:rag:fixtures 36 golden cases; prettier --check clean; verify:ui not run (no browser in container) | +| 2026-08-09 | claude/m3-token-debt-262-261 | c6e1fe7fc42ec6f286eb5a3d8f7ddad7dfad2724 | design-system contract: raw padding/radius/line-height ratchets + type-step selection gate (#262 parts 2/3); closed #218/#270 | Authored and self-verified; PR #1780 open, auto-merge deliberately not armed (gate change). Baseline additive: all 15 pre-existing metrics and every debtByPath entry byte-identical; 94/94 new findings verified present at their cited line. Mutation-tested both halves of part 3 and three failure modes of part 2. | check:design-system-contract, check:icon-scale, check:type-scale, check:outstanding-issues, vitest design-system-contract-utils (31 passed), format:check whole-tree, verify:cheap (exit 1 from 5 pre-existing failures, none in this diff; 3 cleared by merging main, remaining 2 byte-identical to origin/main) | | 2026-08-09 | claude/documentviewer-nav-convergence-oddhjx | 1395d533cb13eadc705e47f76aa9f39a7a11c058 | DocumentViewer / in-page-nav convergence (#288): non-adoption decision recorded in docs/search-chrome-behaviour.md; merged duplicated visible-element predicate into resolveVisibleElement; new convergence guard test | Converged what was duplicated; DocumentReviewer header adoption declined on the merits with four blocking reasons recorded. No contract test edited. | verify:pr-local (546/547 files, 5883 tests pass; sole failure tests/pr-handoff-stop.test.ts reproduced on pristine origin/main), verify:phone-chrome (contracts 123 pass; focused Chromium 7 pass), contract set 12 files/151 tests pass, lint, typecheck, format | +| 2026-08-09 | claude/disabled-button-accessibility-piclvr | 722abdb780c715c0a89df268ed48f6c741ffd569 | disabled-placeholder buttons -> aria-disabled + inert handler (25 sites, 13 components); controlDisabled/therapy recipe aria-disabled styling; require-button-wiring redundantDisabledPair gate; wiring-conventions contract rewrite (settles #291) | authored — PR #1778 opened | lint (uncached, exit 0); typecheck; test 5878 passed/1 pre-existing root-env failure in pr-handoff-stop; build; check:rag:fixtures 36 golden cases; prettier --check clean; verify:ui not run (no browser in container) | | 2026-08-09 | cursor/differentials-four-page-nav-5ebf | 93ea437610c1f1b681c3a5cbdc72fe8b9b178710 | differentials four-page nav | implemented Search/Diagnoses/Presentations/Compare equal pages; compare queue; kind labels; Search q+run restore | vitest nav+differentials-navigation; typecheck; lint; full unit 5814 passed | | 2026-08-09 | cursor/differentials-four-page-nav-5ebf | 384a1bedd8dd1064fb2fcf26ac845224e2cafdc4 | PR #1774 differentials four-page nav heavy review-and-fix | fixed P1 ids+Playwright; ModeNav route gate; RSC queue clears bundle+shadow; Copilot ModeNav-on-detail dispositioned (info page); ledger reorder dispositioned (merge=ledger) | vitest nav 47p; design-system-contract; typecheck; lint; test 5897p; build+bundle-budget 1543.7 within tol; focused pw compare queue 1p | +| 2026-08-09 | claude/m3-token-debt-262-261 | 95221ef4235abd9544158b07b8b8569f00c9ec78 | PR #1780 review-and-fix | fixed P2 ratchet bypasses (arbitrary-property classes, CSS-consumer exemption anti-rot, modern CSS zero units); Bugbot clean; merge-tree clean; required CI was green on prior tip | vitest design-system-contract-utils 32/32; check:design-system-contract; mutation CSS-exemption fail→restore; verify:cheap PASS (549 files / 5933 tests); verify:pr-local stages PASS (test flake in design-system-adoption timed out once then 51/51 + full test 549/549 + check:rag:fixtures PASS); no provider gates | +| 2026-08-09 | claude/m3-token-debt-262-261 | 7bac3bd762b381cb25c9b2a15ef3bb7223d15b16 | PR #1780 review-and-fix | fixed P2 ratchet bypasses (arbitrary-property classes, CSS-consumer exemption anti-rot, modern CSS zero units); Bugbot clean; merge-tree clean; required CI was green on prior tip | vitest design-system-contract-utils 32/32; check:design-system-contract; mutation CSS-exemption fail→restore; verify:cheap PASS (549 files / 5933 tests); verify:pr-local stages PASS (test flake in design-system-adoption timed out once then 51/51 + full test 549/549 + check:rag:fixtures PASS); no provider gates | +| 2026-08-09 | cursor/dsm-search-header-fix-15d6 | df088c766f1761496189ec09146aa54c23b1c012 | dsm-search-header | pass: removed catalogue page strip; ribbon + category filter match target | vitest dsm-search-empty-state; npm test 5857 passed; lint; typecheck; ensure phone /dsm/search?q=Delirium | +| 2026-08-09 | claude/m3-token-debt-262-261 | fe75e6acade008e68f953e235cc035f2e5d9d216 | PR #1780 review-and-fix | fixed P2 ratchet bypasses; synced origin/main (#1775); Bugbot clean; merge-tree clean | vitest design-system-contract-utils 32/32; check:design-system-contract; mutation CSS-exemption; verify:cheap PASS 549/5933; verify:pr-local stages PASS after adoption flake retest; check:rag:fixtures PASS; no provider gates | diff --git a/docs/design-system/GATES.md b/docs/design-system/GATES.md index fcd72fbae..4096b46f6 100644 --- a/docs/design-system/GATES.md +++ b/docs/design-system/GATES.md @@ -16,19 +16,19 @@ than CI, two of them by the second reader; review does not scale past two carefu **[verified against this worktree and the export]** -| Check | What it enforces | Label | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| `npm run check:design-system-contract` (token baseline + adoption + design-sync contracts) | Raw colour literals (ratchet against `design-system-contract-baseline.json`), literal `shadow-[…]` classes, the legacy tap class, interactive tap-size literals, therapy-compass `tc-btn` base-class use; border+ring edge conflicts, 1px shadow spreads, layout-property transitions and legacy shadow aliases (all ratcheted **per path**, so new use fails anywhere); status-coloured numerals and colour-only status indicators; `dark:` colour overrides, legacy palette utilities and image-inversion filters (all pinned at **zero**, not ratcheted); deterministic adoption truth; local source/export/preview/design-sync parity. Mockups exempt. | implemented-blocking | -| `tests/ckb-v2-token-contract.test.ts` | v2 layer stays class-scoped (no `:root` leak) · `--border-lux` solid and no lighter than `--border`, both themes · dark surface ramp monotonic and separated · `--surface-subtle` aliases up · text/muted/heading ≥4.5:1 on the light shell, muted ≥4.5:1 dark · `--text-soft` pinned **below** 4.5:1 and ≥3:1 (both sides, so the tier cannot be "fixed" away) · command pair ≥4.5:1 both themes · `--shadow-inset` true inset · elevation ladder carries no baked 1px hairline · tap floor ≠ chip height ≠ compact row · reduced-motion zeroes durations · every type step has its own line-height and tracking | implemented-blocking | -| `tests/design-token-contract.test.ts` | Live-layer token relationships (companion to the v2 contract) | implemented-blocking | -| `tests/source-badges-off-vocab.dom.test.tsx` | Enum resilience: off-vocabulary `clinical_validation_status` degrades to the neutral triad, logs once, never throws | implemented-blocking | -| `tests/source-metadata-browser-safety.test.ts` | The `process is not defined` client-bundle crash stays fixed (server logger never ships to the browser) | implemented-blocking | -| `tests/accessible-table.dom.test.tsx`, `tests/accessible-table-alignment.dom.test.tsx` | Table semantics, numeric alignment, expander `aria-controls` | implemented-blocking | -| `tests/ui-primitives.dom.test.tsx`, `tests/ui-v2-components.dom.test.tsx` | Behavioural coverage for the tested subset (COMPONENTS §0) | implemented-blocking (subset) | -| ESLint repo rules: `no-hardcoded-hex`, `require-button-wiring`, `require-lucide-icon-aria`, `require-z-index-ladder`, `restrict-suppress-hydration-warning` | Raw hex, un-wired buttons, icon aria, z-ladder discipline, hydration-warning abuse | implemented-blocking | -| `tests/route-reachability.test.ts` | No orphan production routes | implemented-blocking | -| `npm run verify:ui` (+ `verify:phone-chrome`) | Chromium production journeys; phone-chrome owners | implemented-blocking (journey scope) | -| Visual-baseline harness (PR #1404) | Screenshot baselines exist but ship **zero committed baselines with `continue-on-error` on, deliberately** — a held gate until the design is declared final (issue `#118`). Includes the `--spacing-tap` probe floor. | implemented-partial (non-blocking by hold) | +| Check | What it enforces | Label | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `npm run check:design-system-contract` (token baseline + adoption + design-sync contracts) | Raw colour literals (ratchet against `design-system-contract-baseline.json`), literal `shadow-[…]` classes, the legacy tap class, interactive tap-size literals, therapy-compass `tc-btn` base-class use; border+ring edge conflicts, 1px shadow spreads, layout-property transitions and legacy shadow aliases (all ratcheted **per path**, so new use fails anywhere); status-coloured numerals and colour-only status indicators; raw padding, radius and line-height literals in both classes and CSS declarations (ratcheted **per path**; named utilities and Tailwind arbitrary-property forms such as `[padding:22px]` both count; values computed with `var()`/`env()`/`calc()`/`clamp()`/`max()`, zero with any CSS unit identifier, and the CSS-wide keywords are exempt); declared-but-unselected type steps (**hard rule** with a gated exemption list); `dark:` colour overrides, legacy palette utilities and image-inversion filters (all pinned at **zero**, not ratcheted); deterministic adoption truth; local source/export/preview/design-sync parity. Mockups exempt. | implemented-blocking | +| `tests/ckb-v2-token-contract.test.ts` | v2 layer stays class-scoped (no `:root` leak) · `--border-lux` solid and no lighter than `--border`, both themes · dark surface ramp monotonic and separated · `--surface-subtle` aliases up · text/muted/heading ≥4.5:1 on the light shell, muted ≥4.5:1 dark · `--text-soft` pinned **below** 4.5:1 and ≥3:1 (both sides, so the tier cannot be "fixed" away) · command pair ≥4.5:1 both themes · `--shadow-inset` true inset · elevation ladder carries no baked 1px hairline · tap floor ≠ chip height ≠ compact row · reduced-motion zeroes durations · every type step has its own line-height and tracking | implemented-blocking | +| `tests/design-token-contract.test.ts` | Live-layer token relationships (companion to the v2 contract) | implemented-blocking | +| `tests/source-badges-off-vocab.dom.test.tsx` | Enum resilience: off-vocabulary `clinical_validation_status` degrades to the neutral triad, logs once, never throws | implemented-blocking | +| `tests/source-metadata-browser-safety.test.ts` | The `process is not defined` client-bundle crash stays fixed (server logger never ships to the browser) | implemented-blocking | +| `tests/accessible-table.dom.test.tsx`, `tests/accessible-table-alignment.dom.test.tsx` | Table semantics, numeric alignment, expander `aria-controls` | implemented-blocking | +| `tests/ui-primitives.dom.test.tsx`, `tests/ui-v2-components.dom.test.tsx` | Behavioural coverage for the tested subset (COMPONENTS §0) | implemented-blocking (subset) | +| ESLint repo rules: `no-hardcoded-hex`, `require-button-wiring`, `require-lucide-icon-aria`, `require-z-index-ladder`, `restrict-suppress-hydration-warning` | Raw hex, un-wired buttons, icon aria, z-ladder discipline, hydration-warning abuse | implemented-blocking | +| `tests/route-reachability.test.ts` | No orphan production routes | implemented-blocking | +| `npm run verify:ui` (+ `verify:phone-chrome`) | Chromium production journeys; phone-chrome owners | implemented-blocking (journey scope) | +| Visual-baseline harness (PR #1404) | Screenshot baselines exist but ship **zero committed baselines with `continue-on-error` on, deliberately** — a held gate until the design is declared final (issue `#118`). Includes the `--spacing-tap` probe floor. | implemented-partial (non-blocking by hold) | ⚠️ **Type-scale and icon-scale enforcement — corrected 6 Aug 2026.** The previous wording here ("no such rule files exist in `eslint-rules/`… currently **unenforced**") was true about @@ -42,11 +42,27 @@ ratcheting design-system contract. fail the build. `text-[color:var(--…)]` is the sanctioned token form and is deliberately not flagged. -**Not enforced:** which named step a component picks. Nothing stops a surface choosing -`text-sm-minus` over `text-sm` — 1 318 call sites across the eight non-standard steps -**[verified: grep]**. That is a step-_selection_ lint and it does not exist. Do not describe -the scale as ungated, and do not write an ESLint rule duplicating the arbitrary-value check -that already ships. +**Partly enforced, corrected 9 Aug 2026.** The decidable half of step _selection_ now ships +inside `check:design-system-contract`: a step declared in the `globals.css` `@theme` block +that no production surface selects fails the build, naming the step. It found one on the day +it landed — `--text-2xl-compact`, zero consumers, carried as a documented exemption with its +retirement tracked as `#297`. The exemption itself is gated: the build also fails if an +exempted step stops being declared or gains a consumer (class utility or direct +`var(--text-*)` across walked production sources), so the list cannot rot. + +**Still not enforced:** which of the _existing_ steps a component picks. Nothing stops a +surface choosing `text-sm-minus` over `text-sm`, and nothing mechanical can — that is a +judgement about the rendered design, not a property of the source. Do not describe the scale +as ungated, and do not write an ESLint rule duplicating the arbitrary-value check that +already ships. + +⚠️ **The "1 318 call sites" figure this section used to quote was a repo-wide grep including +`src/app/mockups/**`, which every one of these gates excludes** — 1 360 at `7aaf9349c`. +Production consumers of the nine non-standard steps total **705** **[verified: AST class-root +pass over the contract check's own walk, 9 Aug 2026]**: `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. A third figure of 733 has also circulated; that one counts the `@theme` +declarations and doc comments as if they were usages. Nine steps, not eight. ## 2 · The twelve system gates, labelled diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index c6be8bd60..40aed37c4 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -133,44 +133,43 @@ removed after current-main verification; it is not missing recommended work. | 78 | `#206` | A2 | Specialist — answer UI contract | With AnswerState producer work (`#207`) | 2–4 hours | `partial_retrieval` has no app-facing producer — decide RAG contract vs UI-only mapping before AnswerCard. **Stop:** no retrieval behaviour change without RAG flag. | | 79 | `#208` | A2 | Specialist — clinical copy | With answer clipboard / PR-13 work | 1–2 hours | `answerClipboardText` must not replace `formatAnswerRenderCopyText` — compose render-policy warnings. **Gate:** focused clipboard/copy tests. **Stop:** do not drop render-policy caveats. | | 80 | `#216` | A2 | High — design-system answer shell | After `#207` and clinical surface decision | 0.5–1 day | Adopt AnswerCard on the answer surface (deferred from PR-J). Own PR, own `verify:ui`. **Stop:** not before `#207`; show both surface treatments before choosing. | -| 81 | `#218` | A2 | High — design tokens / cn() | Before Chip/metadataPill convergence | 2–4 hours | `cn()` lacks tailwind-merge — decide merge vs explicit size variants. **Stop:** do not fix by stacking more className overrides. | -| 82 | `#230` | A2 | High — PR policy / CI | Next ci.yml / pr-policy change | 1–2 hours | PR-policy body sync must no-op unless `PR_POLICY_BODY.md` is new in that PR's own diff (or move body out of repo). **Gate:** `check:github-actions` / workflow self-test. **Stop:** do not re-commit scratch bodies to main. | -| 83 | `#232` | A2 | High — review ledger hygiene | Next ledger touch for PR-J | 30–60 min | Supersede the PR-J clinical-governance ledger row so it describes the merged head (`ledger:append --supersede`). **Stop:** append-only — never edit/delete the old row. | -| 84 | `#209` | A3 | High — design tokens / contrast | Next Gate 1 / warning-token pass | 1–2 hours | Add contrast pair for `--warning` used as body text (VerificationNotice / DoseLine). **Gate:** design-system contrast checks. **Stop:** do not invent a new status token without TOKENS.md. | -| 85 | `#211` | A3 | High — TypeScript strictness | Dedicated migration branch | multi-PR | Plan and start `noUncheckedIndexedAccess` migration (1266 errors); highest-risk files first. **Stop:** do not flip the flag on main without a staged plan. | -| 86 | `#212` | A3 | High — runtime validation | After highest-risk cast inventory | multi-PR | Replace `as unknown as` and unvalidated `JSON.parse` with Zod/guards at trust boundaries. **Stop:** RAG/provider boundaries need clinical/privacy care. | -| 87 | `#213` | A3 | High — error handling | Next fetch/stream hardening pass | 0.5–1 day | Stop swallowing fetch/stream errors with empty catches; check `response.ok`. **Stop:** do not change telemetry contracts silently. | -| 88 | `#215` | Optional | High — image perf | Next image/PWA pass | 2–4 hours | Image-optimization basics for lightbox, PWA lifecycle, demo PNGs. **Stop:** optional until measured need. | -| 89 | `#221` | A3 | High — design-system convergence | After `#218` cn() decision | 0.5–1 day | Converge remaining local EmptyState/LoadingState/Chip duplicates. **Stop:** not piecemeal before cn()/Chip decisions. | -| 90 | `#222` | A3 | High — headers / search chrome | During headers redesign decision | 2–4 hours | Decide whether mode-home-template / search-results-header-band are in PageHeader scope or permanently out. **Stop:** do not flatten phone composer ownership. | -| 91 | `#233` | A3 | High — design-system docs | Next COMPONENTS.md docs PR | 1–2 hours | Refresh section 0 maturity matrix and document FormField optionality-marker contract. **Gate:** docs checks. **Stop:** docs-only; no product behaviour change. | -| 92 | `#234` | A3 | High — design-system docs | With answer-surface docs | 30–60 min | Document `answer-copy-payload.ts` as the clipboard contract for three surfaces. **Stop:** do not add a second copy builder. | -| 93 | `#235` | A3 | High — design-system evidence | Next warmed local proof-shot pass | 1–2 hours | Capture missing ADOPTION.md §7 proof shots for adopted surfaces. **Stop:** not visual-baseline PNGs (`#118`); no Playwright snapshot commit. | -| 94 | `#236` | Optional | High — branch hygiene | Next cleanup batch with `#079` | 30–60 min | Dispose orphan DS V2 builder branches and leftover wave-5 dev servers. **Stop:** content-verify before delete; no force-clean. | -| 95 | `#237` | A3 | High — design-system a11y | Before freezing Linux visual baselines (#242) | 30–60 min | Eyeball low-confidence AccessibleTable densities at 320px; MissingValue phrases must remain readable. **Gate:** visual spot-check only. **Stop:** do not abbreviate MissingValue to a dash. | -| 96 | `#238` | A3 | High — overlays/UI | After Sheet portal default change (#1616) | 30–60 min | Visual pass for Sheet portal default on settings, sidebar, and answer overlays under OverlayRoot. **Stop:** do not revert portal default without evidence. | -| 97 | `#239` | Optional | High — phone chrome | When phone orientation QA is available | 15–30 min | Manual phone rotation check for ResizeObserver-only phone chrome reserve. **Gate:** `verify:phone-chrome` still owns automated coverage. **Stop:** do not widen reserve heuristics without reproduction. | -| 98 | `#240` | Optional | High — design tokens | Next design-owner review | 15–30 min | Confirm tooltip visual hard-clip asymmetry with design owner (sr-only keeps full text). **Stop:** no product change without that confirmation. | -| 99 | `#241` | A3 | High — therapy catalogue | Standing; with any therapy-home change | 15–30 min | Therapy home summary count/slugs remain build-time; keep `build-therapies-index --check` load-bearing. **Stop:** do not bypass the check. | -| 100 | `#242` | A2 | High — design-system baselines | After human review of Linux baselines | 1–2 hours | Commit approved Linux visual baselines and promote adoption not-committed → committed. **Stop:** never commit baselines from an unreviewed machine run. | -| 101 | `#244` | A3 | High — design tokens / forced-colors | With any ckb-v2 forced-colours edit | 15–30 min | Keep grouped dark selectors in the forced-colours media block so specificity matches dark rules. **Stop:** do not trim to a single `.ckb-v2.ckb-v2` selector. | -| 102 | `#245` | A3 | High — cross-mode links | Next CrossModeLinks / analytics pass | 30–60 min | responsive-compact CrossModeLinks keeps duplicate rails in the DOM; prefer one mount or accept test double-counts. **Stop:** do not break phone-only rail contract. | -| 103 | `#248` | A2 | Operator — Supabase + Specialist | After PR #1614 symptom repair; approved live/history window | 1–2 hours | Investigate why 20260705180000 search-health indexes were missing on live despite applied history; decide if drift checks should catch this class. **Stop:** no hosted mutation without approval. | -| 104 | `#249` | A3 | High — agent process | Next issues-skill / plan touch | 1–2 hours | Extend issues/plan with an agent-safe wins classifier (optional filter; no new skill unless reused thrice). **Stop:** do not outrank A1 operator work. | -| 105 | `#250` | A2 | High — multi-agent execution | After Wave 0 queue repair on main (done in this capture); run remaining Wave 0/#202 process gates next on the engineering track | multi-wave | Execute the fastest-wins multi-wave plan (Waves 0–4 + operator track) with parallel agents and per-PR gates. Waves do not outrank A1 acuity. **Stop:** provider/RAG approvals still required where flagged. | -| 106 | `#251` | Optional | High — agent process | Next handoff/gates doc touch | 15–30 min | Handoff checklist pairs gates skill with verification-router; paste decisive proof line. **Stop:** do not stack broad gates by default. | -| 107 | `#253` | A2 | High — phone results UI | Next open-PR sweep | 15–30 min | Decide #1606's fate: the `MobileResultFilterControl` it rewrites was deleted by #247, so there is nothing left to hand-merge. Verify keyboard parity of the replacement sheet on a real device, then close #1606 as superseded. **Stop:** the decision is a human's; do not close #1606 automatically. | -| 108 | `#254` | A2 | Operator — Codex Cloud | Before #1617 leaves draft | 1–2 hours | Re-run Codex Cloud acceptance at the exact current head or mark head-independent evidence. **Stop:** do not treat stale pins as coverage. | -| 109 | `#255` | A2 | High — Cloud/browser gates | Next environment image update | 2–4 hours | Align Cloud Playwright browser builds with lockfile pin; document CI delegation until then. **Stop:** do not force mismatched Chromium revisions. | -| 110 | `#256` | A2 | High — mode section nav | Next information-page / mode-nav pass | 2–4 hours | Declared information-page section sets whose target ids nothing renders — verify each set against the rendered DOM per route; render anchors or delete the set. **Stop:** do not audit by grepping for `id=` alone (sectionId props exist). | -| 111 | `#257` | Optional | High — formulation/specifiers flake | Standing until second reproduction | 15–30 min | Single unreproduced ui-formulation flake when run with ui-specifiers — record a second sighting only; do not quarantine until three on the same SHA. **Stop:** do not weaken assertions. | -| 112 | `#286` | A3 | High — in-page nav + frontend | After owner go-ahead for the information-page series | 1–2 days | Convert the six pill-rail information pages onto `InPageNavHeader`, widen Server Component–safe actions, then delete `informationPageSectionDefinitions`. **Gate:** focused DOM/contract tests + `verify:phone-chrome` for touched owners. **Stop:** do not convert DocumentViewer here; do not verify anchors by grepping `id=` alone. | -| 113 | `#287` | A3 | High — in-page nav + clinical owner | After `#286`; medications needs an owner product call | 0.5–1 day design + convert | Decide medications tab model, presentations MobileTabs vs `InPageNavHeader`, and factsheets heading→id scheme; convert or record lasting exceptions. **Stop:** do not port medications mechanically. | -| 114 | `#288` | Optional | High — document chrome | After `#286`/`#287`, or when declaring the series complete | 30–60 min | Confirm DocumentViewer non-adoption (already noted in `docs/search-chrome-behaviour.md`) as the final end state, or schedule a separate convergence PR that leaves pinned `--document-*` CSS names untouched. | -| 115 | `#289` | A2 | High — auth/identity | Next auth module touch | 1–2 hours | Export a named helper (e.g. `authorizationIdentity(headers)`) from the auth module and use it at every property-access call site; consider a lint rule or branded type so `.Authorization` stops type-checking at all. **Stop:** do not change `authorizationHeadersForAccessToken` to emit uppercase — lowercase is the correct Fetch/Headers convention and callers that pass the object wholesale to `fetch` depend on it. | +| 81 | `#230` | A2 | High — PR policy / CI | Next ci.yml / pr-policy change | 1–2 hours | PR-policy body sync must no-op unless `PR_POLICY_BODY.md` is new in that PR's own diff (or move body out of repo). **Gate:** `check:github-actions` / workflow self-test. **Stop:** do not re-commit scratch bodies to main. | +| 82 | `#232` | A2 | High — review ledger hygiene | Next ledger touch for PR-J | 30–60 min | Supersede the PR-J clinical-governance ledger row so it describes the merged head (`ledger:append --supersede`). **Stop:** append-only — never edit/delete the old row. | +| 83 | `#209` | A3 | High — design tokens / contrast | Next Gate 1 / warning-token pass | 1–2 hours | Add contrast pair for `--warning` used as body text (VerificationNotice / DoseLine). **Gate:** design-system contrast checks. **Stop:** do not invent a new status token without TOKENS.md. | +| 84 | `#211` | A3 | High — TypeScript strictness | Dedicated migration branch | multi-PR | Plan and start `noUncheckedIndexedAccess` migration (1266 errors); highest-risk files first. **Stop:** do not flip the flag on main without a staged plan. | +| 85 | `#212` | A3 | High — runtime validation | After highest-risk cast inventory | multi-PR | Replace `as unknown as` and unvalidated `JSON.parse` with Zod/guards at trust boundaries. **Stop:** RAG/provider boundaries need clinical/privacy care. | +| 86 | `#213` | A3 | High — error handling | Next fetch/stream hardening pass | 0.5–1 day | Stop swallowing fetch/stream errors with empty catches; check `response.ok`. **Stop:** do not change telemetry contracts silently. | +| 87 | `#215` | Optional | High — image perf | Next image/PWA pass | 2–4 hours | Image-optimization basics for lightbox, PWA lifecycle, demo PNGs. **Stop:** optional until measured need. | +| 88 | `#221` | A3 | High — design-system convergence | After `#218` cn() decision | 0.5–1 day | Converge remaining local EmptyState/LoadingState/Chip duplicates. **Stop:** not piecemeal before cn()/Chip decisions. | +| 89 | `#222` | A3 | High — headers / search chrome | During headers redesign decision | 2–4 hours | Decide whether mode-home-template / search-results-header-band are in PageHeader scope or permanently out. **Stop:** do not flatten phone composer ownership. | +| 90 | `#233` | A3 | High — design-system docs | Next COMPONENTS.md docs PR | 1–2 hours | Refresh section 0 maturity matrix and document FormField optionality-marker contract. **Gate:** docs checks. **Stop:** docs-only; no product behaviour change. | +| 91 | `#234` | A3 | High — design-system docs | With answer-surface docs | 30–60 min | Document `answer-copy-payload.ts` as the clipboard contract for three surfaces. **Stop:** do not add a second copy builder. | +| 92 | `#235` | A3 | High — design-system evidence | Next warmed local proof-shot pass | 1–2 hours | Capture missing ADOPTION.md §7 proof shots for adopted surfaces. **Stop:** not visual-baseline PNGs (`#118`); no Playwright snapshot commit. | +| 93 | `#236` | Optional | High — branch hygiene | Next cleanup batch with `#079` | 30–60 min | Dispose orphan DS V2 builder branches and leftover wave-5 dev servers. **Stop:** content-verify before delete; no force-clean. | +| 94 | `#237` | A3 | High — design-system a11y | Before freezing Linux visual baselines (#242) | 30–60 min | Eyeball low-confidence AccessibleTable densities at 320px; MissingValue phrases must remain readable. **Gate:** visual spot-check only. **Stop:** do not abbreviate MissingValue to a dash. | +| 95 | `#238` | A3 | High — overlays/UI | After Sheet portal default change (#1616) | 30–60 min | Visual pass for Sheet portal default on settings, sidebar, and answer overlays under OverlayRoot. **Stop:** do not revert portal default without evidence. | +| 96 | `#239` | Optional | High — phone chrome | When phone orientation QA is available | 15–30 min | Manual phone rotation check for ResizeObserver-only phone chrome reserve. **Gate:** `verify:phone-chrome` still owns automated coverage. **Stop:** do not widen reserve heuristics without reproduction. | +| 97 | `#240` | Optional | High — design tokens | Next design-owner review | 15–30 min | Confirm tooltip visual hard-clip asymmetry with design owner (sr-only keeps full text). **Stop:** no product change without that confirmation. | +| 98 | `#241` | A3 | High — therapy catalogue | Standing; with any therapy-home change | 15–30 min | Therapy home summary count/slugs remain build-time; keep `build-therapies-index --check` load-bearing. **Stop:** do not bypass the check. | +| 99 | `#242` | A2 | High — design-system baselines | After human review of Linux baselines | 1–2 hours | Commit approved Linux visual baselines and promote adoption not-committed → committed. **Stop:** never commit baselines from an unreviewed machine run. | +| 100 | `#244` | A3 | High — design tokens / forced-colors | With any ckb-v2 forced-colours edit | 15–30 min | Keep grouped dark selectors in the forced-colours media block so specificity matches dark rules. **Stop:** do not trim to a single `.ckb-v2.ckb-v2` selector. | +| 101 | `#245` | A3 | High — cross-mode links | Next CrossModeLinks / analytics pass | 30–60 min | responsive-compact CrossModeLinks keeps duplicate rails in the DOM; prefer one mount or accept test double-counts. **Stop:** do not break phone-only rail contract. | +| 102 | `#248` | A2 | Operator — Supabase + Specialist | After PR #1614 symptom repair; approved live/history window | 1–2 hours | Investigate why 20260705180000 search-health indexes were missing on live despite applied history; decide if drift checks should catch this class. **Stop:** no hosted mutation without approval. | +| 103 | `#249` | A3 | High — agent process | Next issues-skill / plan touch | 1–2 hours | Extend issues/plan with an agent-safe wins classifier (optional filter; no new skill unless reused thrice). **Stop:** do not outrank A1 operator work. | +| 104 | `#250` | A2 | High — multi-agent execution | After Wave 0 queue repair on main (done in this capture); run remaining Wave 0/#202 process gates next on the engineering track | multi-wave | Execute the fastest-wins multi-wave plan (Waves 0–4 + operator track) with parallel agents and per-PR gates. Waves do not outrank A1 acuity. **Stop:** provider/RAG approvals still required where flagged. | +| 105 | `#251` | Optional | High — agent process | Next handoff/gates doc touch | 15–30 min | Handoff checklist pairs gates skill with verification-router; paste decisive proof line. **Stop:** do not stack broad gates by default. | +| 106 | `#253` | A2 | High — phone results UI | Next open-PR sweep | 15–30 min | Decide #1606's fate: the `MobileResultFilterControl` it rewrites was deleted by #247, so there is nothing left to hand-merge. Verify keyboard parity of the replacement sheet on a real device, then close #1606 as superseded. **Stop:** the decision is a human's; do not close #1606 automatically. | +| 107 | `#254` | A2 | Operator — Codex Cloud | Before #1617 leaves draft | 1–2 hours | Re-run Codex Cloud acceptance at the exact current head or mark head-independent evidence. **Stop:** do not treat stale pins as coverage. | +| 108 | `#255` | A2 | High — Cloud/browser gates | Next environment image update | 2–4 hours | Align Cloud Playwright browser builds with lockfile pin; document CI delegation until then. **Stop:** do not force mismatched Chromium revisions. | +| 109 | `#256` | A2 | High — mode section nav | Next information-page / mode-nav pass | 2–4 hours | Declared information-page section sets whose target ids nothing renders — verify each set against the rendered DOM per route; render anchors or delete the set. **Stop:** do not audit by grepping for `id=` alone (sectionId props exist). | +| 110 | `#257` | Optional | High — formulation/specifiers flake | Standing until second reproduction | 15–30 min | Single unreproduced ui-formulation flake when run with ui-specifiers — record a second sighting only; do not quarantine until three on the same SHA. **Stop:** do not weaken assertions. | +| 111 | `#286` | A3 | High — in-page nav + frontend | After owner go-ahead for the information-page series | 1–2 days | Convert the six pill-rail information pages onto `InPageNavHeader`, widen Server Component–safe actions, then delete `informationPageSectionDefinitions`. **Gate:** focused DOM/contract tests + `verify:phone-chrome` for touched owners. **Stop:** do not convert DocumentViewer here; do not verify anchors by grepping `id=` alone. | +| 112 | `#287` | A3 | High — in-page nav + clinical owner | After `#286`; medications needs an owner product call | 0.5–1 day design + convert | Decide medications tab model, presentations MobileTabs vs `InPageNavHeader`, and factsheets heading→id scheme; convert or record lasting exceptions. **Stop:** do not port medications mechanically. | +| 113 | `#288` | Optional | High — document chrome | After `#286`/`#287`, or when declaring the series complete | 30–60 min | Confirm DocumentViewer non-adoption (already noted in `docs/search-chrome-behaviour.md`) as the final end state, or schedule a separate convergence PR that leaves pinned `--document-*` CSS names untouched. | +| 114 | `#289` | A2 | High — auth/identity | Next auth module touch | 1–2 hours | Export a named helper (e.g. `authorizationIdentity(headers)`) from the auth module and use it at every property-access call site; consider a lint rule or branded type so `.Authorization` stops type-checking at all. **Stop:** do not change `authorizationHeadersForAccessToken` to emit uppercase — lowercase is the correct Fetch/Headers convention and callers that pass the object wholesale to `fetch` depend on it. | - + ## Open items > **Merged-main canary update (2026-07-23, run `30018289898`):** the new structured report correctly recorded evaluated tree `c24f2e8f2d30d0c59fc1eba025d3dcd63478137e`, run/attempt identity and `cross-region-runner` latency context. Golden retrieval remained 36/36 with document/content recall 1.0 and no failed cases. The 44-case answer gate had grounded-supported and unsupported-correct rates of 1.0, but failed because `neuroleptic-side-effect-escalation` again returned one citation where two are required (citation-failure rate 0.0227). `admission-discharge-comparison` again omitted the specific AKG admission document after `comparison_source_extractive_fallback`; `admission-discharge-coverage-paraphrase` was advisory-only at 24,870 ms. Answer cost was reported as `$0.234736`. Do not retry immediately: retain this as the first structured datapoint, compare it with the scheduled 2026-07-26 report, and keep retrieval/ranking unchanged. @@ -273,7 +272,6 @@ removed after current-main verification; it is not missing recommended work. | #213 | P2 | task | Stop swallowing fetch and stream errors with empty catch handlers | 51 call sites use response.json().catch(() => ({})) or void fetch(...).catch(() => undefined), hiding API, JSON, auth, telemetry and stream failures. Add explicit response.ok checks and a typed safeFetch wrapper that preserves the failure signal for telemetry and user feedback. See docs/review-findings-2026-08-02.md sections 3.1-3.4 and 8. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | | #215 | P3 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | image-lightbox.tsx and pwa-lifecycle.tsx lack decoding=async; public/demo-documents/*.png are ~80 KB each and not served in WebP/AVIF; SignedImage lacks a priority prop for above-fold evidence images. Apply decoding=async, add an optional priority prop, and convert demo PNGs with a PNG fallback. Most performance image findings are already tracked under #016, #013, #117 and #147; this row captures the new image-only observations from the 2026-08-02 audit. | session 2026-08-02 /ledger sweep — docs/audit/performance-image-cwv-audit-2026-08-02.md | 2026-08-02 | | #216 | P2 | task | Adopt the AnswerCard container on the answer surface (deferred from PR-J) | PR-J adopted the answer safety components (VerificationNotice, RetrievalStateBanner, AnswerState projection, composed clipboard) but NOT AnswerCard itself, so it stays at zero product imports and PR 13's answer surface is adopted in substance, not in shell. The swap replaces the answerSurface wrapper with AnswerCard's article, its query echo with UserQuestionBubble, wraps NaturalLanguageAnswer in its --measure-clamped prose div, maps copy/feedback/follow-up onto its structured actions array, and adds AnswerFooter (needs publisher/version/reviewDate/generatedAt threaded). Deferred deliberately: two competing surface treatments must be resolved by the design owner (SPEC 2.4 border-or-ring), the measure clamp reflows every answer and moves the phone scroll-runway pins that cost PR-V two CI cycles (finding L), and bundling it would make a red verify:ui unattributable across PR-J's other five surfaces. Reasons recorded in docs/design-system/ADOPTION.md 2.6. Next action: own PR after PR-J lands and soaks, own verify:ui pass, own glance; show the user both surface treatments before choosing. | session 2026-08-03 (PR-J Wave 5 controller) | 2026-08-02 | -| #218 | P2 | issue | cn() has no tailwind-merge, so className size overrides resolve by stylesheet order rather than intent | cn() in src/components/ui-primitives.tsx is a plain join. Overriding a design-system component's text-xs with text-2xs via className emits two text-* utilities whose winner is stylesheet order, not the call site's intent. Concrete live instance: src/components/clinical-dashboard/document-search-results.tsx lines around 830, 870, 924 and 1341 do cn(metadataPill, ... text-2xs) while metadataPill already carries text-xs. This also blocked convergence of the local Chip in differentials-home.tsx, whose own doc comment bans the pattern. Next action: decide between adopting tailwind-merge in cn() (repo-wide blast radius, needs its own PR and gate) or adding explicit size variants to metadataPill and Chip. Do not fix by adding more className overrides. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder B) | 2026-08-02 | | #221 | P3 | task | Local EmptyState, LoadingState and Chip duplicates still unconverged after PR-J | PR-J converged what it could inside its allowlists and left four known duplicates, each blocked for a stated reason rather than missed. therapy-compass/ui.tsx defines its own LoadingState AND its own EmptyState used across nine screens (whole-module job, not a one-call-site conversion). mode-home-template.tsx ModeHomeStatusNotice is an EmptyState duplicate that four catalogue homes delegate to, which is why those four files show no diff. differentials-home.tsx has a local two-density Chip blocked by the cn() tailwind-merge gap. favourites-command-library-page.tsx SmallChip is driven by an eight-entry type-token map that Chip's five-tone vocabulary cannot express. Next action: take these as one convergence PR after the cn() decision lands, not piecemeal. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder B) | 2026-08-02 | | #222 | P3 | task | Headers surface only partially converged in PR-J: mode-home-template and search-results-header-band untouched | Builder A converged DsmPageHeader, InformationPageHeader and InformationPageBreadcrumbs onto PageHeader plus Breadcrumb, and declined two files with reasons. mode-home-template.tsx ModeHomeHero is a centred display hero on the fluid text-hero token and is the slot the in-flow phone composer sits in, so converging it onto a left-aligned PageHeader is a redesign of 13 mode homes that collides with the one-composer-per-page contract. search-results-header-band.tsx is a results spine carrying status, counts and filters, not a page-title stack, so its pin tests/search-results-header-band.dom.test.tsx remains unflipped. Both are defensible; both leave the headers surface partially adopted. Next action: decide whether either is in scope at all, or record them as permanently out of the PageHeader vocabulary. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder A) | 2026-08-02 | | #226 | P1 | task | PR-J: VerificationNotice pushes the phone short-answer runway past the in-flow activation band | CI run 30820496984 reported the real numbers: ui-smoke phantom-scroll 97 against an 8px budget, short-runway maxOffset 251 against a 200px ceiling. Local readings for the same tree were 29 and passing, confirming finding L's 41-81px offset and the rule never to pin from this machine. Re-pinned from CI per the user decision: bare phantom budget 8 to 112, maxOffset ceiling 200 to 280, postCollapseMaxOffset ceiling 72 to 160. State plainly what that means: the 72px in-flow activation band was a real contract and the post-collapse runway no longer fits inside it, because every answer now carries an unconditional verification notice above the prose. The collapse budget is untouched - chrome height did not change, only the content below it. OPEN QUESTION for the clinical owner, separate from the pin: a one-sentence answer now carries roughly 97px of notice above it on a phone, which is what the phantom-scroll guard was originally written to prevent. Widening the pin accepts that as intended product behaviour. | session 2026-08-03 (PR-J Wave 5, gate window, verify:phone-chrome) | 2026-08-03 | @@ -310,7 +308,6 @@ removed after current-main verification; it is not missing recommended work. | #267 | P3 | task | DS Track B2: AnswerFooter and DoseLine need a provenance/dose payload the answer surface does not produce | Backend-shaped work, not a component swap: the two components cannot be adopted until the answer surface emits the provenance and dose data they render. Do not stub one to make the adoption count look better. Sequence after the payload exists, then adopt via the Track B1 demand-driven route. | session 2026-08-07 — design-system HANDOVER-2026-08-07 Track A1 handoff (PR #1678) | 2026-08-07 | | #268 | P3 | task | DS Track B3: move the 19 genuine bare-dash sites onto MissingValue | Therapy-compass getters, specifier sourceFamily, favourites counts when untrusted. Leave the roughly 5 calculator 'derived.started ? score : dash' sites PERMANENTLY — 'not started' is not a missing clinical value, MissingValueReason has no member for it, and converting them would render 'Not recorded' for a score the clinician simply has not entered. | session 2026-08-07 — design-system HANDOVER-2026-08-07 Track A1 handoff (PR #1678) | 2026-08-07 | | #269 | P2 | task | DS Track B4: prove the per-component visual state matrix (blocked on the baseline hold) | hover / active / disabled / busy / invalid / 320px / dark / forced-colours / print, per component. Currently proven for none. Blocked on #118: zero visual baselines are committed and the harness is continue-on-error, so nothing in Track B is safe at scale until baselines exist. CORRECTION 2026-08-08: the claim that baselines cannot be generated on Windows is half true and led to the wrong conclusion. It is true that snapshotPathTemplate carries {platform}, so win32 PNGs are invisible to the ubuntu CI job — but the CI job already produces the ubuntu ones. .github/workflows/ci.yml job visual-baseline runs on ubuntu-24.04 whenever ui_changed, runs npm run test:e2e:visual, and uploads tests/__screenshots__/ as artifact visual-baseline-; playwright.visual.config.ts records that on a missing baseline Playwright writes the golden and fails the first attempt, which is why retries are pinned at 0. So the mechanism exists and adoption is mechanical — see #118. Stop rule unchanged: do not commit baselines until the owner declares the design final, and do not adopt them from a developer machine. | session 2026-08-07 — design-system HANDOVER-2026-08-07 Track A1 handoff (PR #1678) | 2026-08-07 | -| #270 | P2 | task | Declaring tap in tailwind-merge is no longer blocked by dead numeric heights — the 22-site premise did not survive re-measurement | Follow-up from #218 / PR #1678. RE-MEASURED 2026-08-08 against origin/main 00826bca2 and the row's premise does not hold at this HEAD. Method: scan every string literal in src/**.ts(x) (mockups excluded, comments blanked — backticks inside JSX comments otherwise make a literal scanner span lines and produce false hits), group height utilities by variant prefix AND property, and flag a numeric only where a tap token shares its group. Result: ZERO same-variant pairs in components, and 84 cross-variant ones. THREE CORRECTIONS. (1) The named breakdown is stale: DocumentManagerPanel.tsx and settings-dialog.tsx now contain no tap token at all, document-admin's two tap sites carry no numeric height, and service-detail-page's carry none either. (2) The surviving pairs are responsive step-downs, not dead classes — min-h-tap with sm:min-h-9 / lg:min-h-9 / md:min-h-9, and h-10.5 with sm:h-tap in account-setup-dialog. A later-emitted variant wins at its breakpoint, so deleting the numeric RAISES the control (36px to 48px at that breakpoint, or 42px to the recipe default on phones for the h-10.5 case). That is a visual change, not the no-op this row promised. (3) The stated blocker is measured false: tailwind-merge groups by variant, so declaring tap changes nothing at any cross-variant pair. Probe over the real pairings — min-h-tap sm:min-h-9, sm:min-h-10, lg:min-h-9, md:min-h-9, sm:min-h-0, and h-10.5 sm:h-tap — returned identical output with and without tap declared; only the synthetic same-variant controls (min-h-tap min-h-9, min-h-9 min-h-tap, h-tap h-5) changed. REMAINING RISK, and the reason this is not simply done: the scan is per string literal, so it cannot see a conflict composed across cn() arguments — cn(metadataPill.standard, 'min-h-tap') pairs min-h-7 with min-h-tap through a recipe. Order decides the outcome there: recipe-then-tap raises to 48px, tap-then-recipe DROPS to 28px, which is the forbidden direction. Next action: a composition-aware sweep that resolves constant recipe identifiers at each cn() call site before grouping; if it also finds zero same-variant drops, declare tap in CLINICAL_TWMERGE_THEME.spacing and delete the pinning test in tests/tailwind-merge-config.test.ts, and update the long comment in src/lib/tailwind-merge.ts, which still records the 22-site/18-drop figure as the reason for the omission. Do NOT delete the cross-variant numerics as 'dead' — they are live responsive steps. Do NOT lower any target, and never to min-h-11. Gate: npm run check:design-system-contract, npm run test, and a Chromium look. | session 2026-08-07 — design-system HANDOVER-2026-08-07 Track A1 handoff (PR #1678) | 2026-08-07 | | #271 | P3 | task | Decide whether to delete the now-consumer-less action kind in SecondaryNavigation | PR #1679 removed the last live consumer of SecondaryNavigationActionItem. UPDATE 2026-08-09 (in-page-nav PR 2): the question is now the whole component, not one kind. That PR removed the section kind — SecondaryNavigationSectionItem, useActiveSection, sectionFragmentId, nearestScrollOwner, sectionActivationClearance and the internal secondary-navigation:section-select event — because the six information routes that fed it now mount InPageNavHeader. That was the last PRODUCTION constructor of any kind: RegistryModeNav renders ModeNav, not SecondaryNavigation, so the surviving route and action kinds have no caller in src/ at all and the only file that builds either is tests/secondary-navigation.dom.test.tsx. Kept rather than deleted in that PR deliberately: deleting a component plus its test file alongside a seven-route conversion is a second unrelated change, and this row already tracks the same keep-or-delete call. Next: decide keep-or-delete for src/components/secondary-navigation.tsx as its own change. If delete, also remove tests/secondary-navigation.dom.test.tsx and the therapy-compass action entries in the registry, and check tests/mode-nav-contract.test.ts, which string-matches source lines in page-secondary-navigation.tsx. Stop: do not do half of each — a deleted branch with its tests left behind, or vice versa, is worse than either. | session 2026-08-07; PR #1679; in-page-nav PR 2 2026-08-09 | 2026-08-07 | | #272 | P3 | rec | Header addon-slot single-owner rule is enforced by two lists agreeing by coincidence, not a guard | The universal header's addon slot must hold exactly ONE page-owned header. Nothing in PageSecondaryNavigation states that rule. What actually enforces it is that every route claiming the slot (DocumentViewer, differentials/differential-detail-page) also happens to be hasLocalInformationPageNavigation, which returns null before the mode branch is reached — two independently maintained lists agreeing by accident. tests/mode-nav-addon-slot.dom.test.tsx asserts the agreement route-for-route and is what will go red when a future claimant falls outside that cover. The original incidental protection (a claimant mode had fewer than MODE_NAV_MIN_ITEMS destinations so ModeNav rendered nothing) has already expired twice: for differentials in PR #1647 and for factsheets in PR #1674. Next: no action required while the lists agree; if documents or another slot claimant ever gains a second routed destination, add an explicit isHeaderAddonSlotOwnedRoute guard at the mode branch in PageSecondaryNavigation rather than widening either list. Stop: do not delete isHeaderAddonSlotOwnedRoute as unused — no production code calls it, but it is the named claimant list the test asserts against. Renumbered from this PR's original #262 → #272 because main claimed #261–#270 via PR #1678 design-system tracks. | session 2026-08-07; PRs #1647, #1674, #1679 | 2026-08-07 | | #273 | P2 | task | The results band's max-[413px] wrap threshold is stale now that Sort and the phone selects are both gone | **Outcome:** the one-line phone results bar extends down to 320px instead of stopping at 414px, so the Filter trigger stops taking an otherwise-empty second row on the most common phone widths. **Detail:** `search-results-header-band.tsx` wraps the band's first line with `max-[413px]:flex-wrap max-[413px]:py-2`, justified in its own comment as "below 414px one line provably cannot hold count + query + sort + filter even with the query fully truncated". Both halves of that premise have since been removed: Sort became `sm`-and-up (PR #1689), and every mode's phone control became a compact trigger rather than a `w-full` select (#247). Measured in a real browser on the differentials band with the wrap class stripped at runtime, `scrollWidth - clientWidth` is **0 at 320, 360, 375, 390 and 402px** — the line fits at every width with room to spare. Left unchanged deliberately: the fix also changes documents, which is the reference layout the user approved, and it is a shared-geometry change with a 320-540px clip sweep asserting against it in `ui-smoke`. **Next:** delete the two `max-[413px]:` utilities, re-run the `ui-smoke` clip sweep and `ui-tools` phone rail assertions, and re-measure band height at 320/360/390 (expect 60px everywhere, versus 89px today below 414). **Stop:** do not raise the threshold instead of deleting it — it is not a narrower window now, it is an empty one. Do not re-measure with Sort mentally re-added; that control is gone below `sm` and is not coming back. | browser measurement 2026-08-07 on claude/search-bar-mobile-layout-buu0io; band comment; #247 | 2026-08-07 | @@ -332,6 +329,7 @@ removed after current-main verification; it is not missing recommended work. | #293 | P2 | issue | Controls declare min-h-tap and compute min-height 0px; a rendered-interactive tap audit needs a deterministic surface first | Two findings, one robust and one that blocked the gate. FINDING 1 (robust, reproduced in ALL SIX runs): controls that carry min-h-tap compute min-height 0px and render far below the 48px floor. Six distinct shapes seen across runs - 'a.inline-flex min-h-tap items-center' 16px, 'a.inline-flex min-h-tap shrink-0' 16.5px, 'button.flex min-h-tap w-full' 26.6px, 'button.grid min-h-tap min-w-tap' 36px, 'button.inline-flex min-h-tap items-center' 16px, 'button.inline-flex min-h-tap min-w-[94px]' 36px. min-h-tap works in general (the existing declared-carrier audit still measures carriers at or above 48px), so these elements have the declaration overridden to 0 rather than the utility being absent; likely an unlayered component class in globals.css, which by design outranks Tailwind utilities here. This was invisible because the pre-existing audit in tests/ui-style-contract.spec.ts only measures elements whose COMPUTED min-height is already at or above the floor (declared < tapFloor - 0.5 continue), so a floor overridden downward is skipped rather than flagged - the same structural blind spot as the h-10 case #265 named. FINDING 2 (why gate 2 did NOT land 2026-08-09): a rendered-interactive enumeration on /services?q=CMHT&run=1 is NOT DETERMINISTIC. Six runs against one production build returned 6, 5, 4, 3, 3 and 9 distinct control shapes, largely disjoint - one run saw answer-suggestion chips and a sort band, another a settled services results list. waitForLoadState('networkidle') plus deduplication to distinct shapes (instance counts measure how many results the query returned, and gave 9 vs 39) did NOT fix it; two consecutive agreeing runs were coincidence, and the next run differed again. The enumeration was written, proven to find real defects, and then REVERTED rather than landed, because tests/ui-style-contract.spec.ts runs in the required Production UI job via productionSpecPattern and scripts/playwright-pr-shards.mjs, so an intermittent version of it would block every merge in the repo. Next, in order: (1) find a deterministic surface for the audit - a static route with no async search, or a fixed seeded state - before re-attempting the enumeration; (2) separately, find what zeroes min-height on the min-h-tap carriers and fix or write a stated exception. Stop: do not re-land the enumeration on a live-search route, do not quarantine a brand-new test to get it merged (quarantine is for keeping flaky tests we already trust, and repo policy needs three reproductions on one SHA via tests/flake-ledger.json), do not lower any production tap target, and never to min-h-11 (known ui-smoke sub-pixel flake; production uses min-h-12). | session 2026-08-09 — M2 gate 2 enumeration (#265) | 2026-08-09 | | #294 | P3 | rec | OffscreenCanvas for the PDF raster is unjustified until the page-flip cost is read from CI | **Outcome:** the worker-raster question is settled by a number rather than left as a standing 'optional' item in the redesign plan. **Detail:** docs/plans/document-viewer-redesign-plan.md conditions OffscreenCanvas on 'measured main-thread paint cost'. Phase 3 (Task 5) did not implement it, deliberately: virtualization now keeps the reader's page and one neighbour already rastered, so the cold-render-per-flip cost that motivated a worker raster is largely gone before any threading work starts, and moving pdf.js rendering off the main thread would put the canvas the clinical source is drawn into behind a transfer boundary — a real risk on the one surface where a blank page is a clinical failure. **No number exists yet and none could be produced locally:** pdfjs-dist@6 needs Map.prototype.getOrInsertComputed, which this container's Chromium 141 lacks and Node 24.13.0 also lacks, so neither a browser nor a headless harness here can raster a page (see #279). **Next:** read the measurement the gate already captures. tests/ui-document-canvas.spec.ts attaches page-flip-raster-cost.json (flipToPaintedMs, longTaskCount, longTaskTotalMs, longestTaskMs, canvasBackingPixels) and logs a '[viewer-canvas] page flip painted in Nms' line, on every Production UI run and on any host with the pinned Chromium 151 build: npm ci --include=dev && npx playwright install chromium && npm run ensure && npm run test:e2e -- tests/ui-document-canvas.spec.ts --project=chromium. Close this as not-worth-doing and strike the row from the plan's Phase 3 table only when a Production UI (or equivalent Chromium 151) run records decisive log lines for all three: longestTaskMs comfortably under ~50ms, plus explicit flipToPaintedMs and longTaskTotalMs budgets agreed for that host class and met on the same run. Do not close on longestTaskMs alone. **Stop:** do not implement OffscreenCanvas on principle because the plan lists it — the plan conditions it on the measurement, and the measurement is now cheap to obtain. | session 2026-08-09 document viewer Phase 3, Task 5; docs/plans/document-viewer-phase3-handover.md | 2026-08-09 | | #296 | P3 | issue | tests/pr-handoff-stop.test.ts fails whenever the unit suite runs as root | The case 'emits handoff context only when the marker file exists' chmods the fixture git dir to 0o555 to force the marker write to fail, then asserts the hook failed open with no marker. Root ignores permission bits, so the write succeeds and the assertion flips: 'expected true to be false' at tests/pr-handoff-stop.test.ts:180. Confirmed environmental and pre-existing, not diff-induced — reproduced on a clean checkout of af85cbc with every working change stashed (1 failed \| 10 passed), and 'id -u' returns 0 in the remote container. Cost is that 'npm run test' and therefore 'npm run verify:pr-local' cannot reach a clean exit in any root container, so a real regression later in the run is masked by a known-red file and the gate has to be interpreted by hand every time. CI is unaffected because its runner is non-root, which is why this has not surfaced there. Next action: make the test skip or change technique when 'process.getuid?.() === 0' — either skip with an explicit reason, or force the write failure a way root cannot bypass (point the marker path at a directory that does not exist, or at a path whose parent is a file), which is portable and keeps the assertion meaningful for every user. Stop: do not delete the case or relax it to 'marker may or may not exist' — failing open without telling the model that tools are denied is the actual contract it guards. | session 2026-08-09; verify:pr-local run on af85cbc | 2026-08-09 | +| #297 | P3 | task | Retire the --text-2xl-compact type step, which no production surface selects | Measured 2026-08-09 against origin/main 7aaf9349c while adding the step-selection gate (#262 part 2). --text-2xl-compact is declared at src/app/globals.css:112 and selected by ZERO production surfaces: the only other mentions anywhere are src/lib/tailwind-merge.ts:20 (a docstring), its entry in CLINICAL_TWMERGE_THEME.text at src/lib/tailwind-merge.ts:50, tests/tailwind-merge-config.test.ts:49 (which asserts the @theme block and the twMerge config agree), and a row in docs/design-system/TOKENS.md. No var(--text-2xl-compact) consumer exists either. For contrast, the next-rarest step, text-hero, does have one real consumer (src/components/mode-home-template.tsx:87). The new gate in scripts/check-design-system-contract.mjs fails on any declared-but-unconsumed step; this one is carried in UNUSED_TYPE_STEP_EXEMPTIONS with outstanding-issues.md #297 named as its tracking task, and the gate additionally fails if the exemption ever goes stale (step undeclared, or step gains a consumer). Next action: delete --text-2xl-compact from the globals.css @theme block, drop "2xl-compact" from CLINICAL_TWMERGE_THEME.text and from tests/tailwind-merge-config.test.ts, update the TOKENS.md row, then delete the exemption entry - the gate will hold the line afterwards. Deleting a step with zero consumers renders identically, but it edits @theme, so give it its own revertible PR rather than bundling it with a gate change. Gate: npm run check:design-system-contract plus npm run test. | session 2026-08-09 - M3 design-token debt (#262 parts 2 and 3) | 2026-08-09 | ## Resolved / archive @@ -497,4 +495,6 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | #277 | issue | docs/design-system/HANDOVER-2026-08-07.md is cited as provenance by nine ledger rows but is measurably wrong | CLOSED 2026-08-09 as already satisfied — verified against origin/main 8db1e53937, not inferred. Both halves of this row's own 'cheapest fix' are present: docs/design-system/HANDOVER-2026-08-07.md carries a SUPERSEDED IMPORTANT callout naming docs/outstanding-issues.md as the current source of truth, listing the nine citing rows (#261, #262, #264-#270) and enumerating each disproved figure; and docs/design-system/README.md line 12 already reads 'superseded and must not be used to ...'. The file was correctly kept rather than deleted, preserving the nine Source citations as provenance. One note for whoever reads the banner next: its quoted figures have themselves drifted, which is precisely why this row insisted corrections live in the rows and not in the document. Measured today at 8db1e53937: legacyShadowAliases total 224, not the banner's 228, and the manifest reports 53 registered components with 31 product-imported, i.e. 22 unadopted rather than 23. Do NOT edit those numbers into the banner - re-stating live figures in a superseded document is the drift this row exists to stop. Also note the banner did still mislead one session despite existing: the M2 session of 2026-08-09 was scoped from a handover that repeated its claims, and four of #264's six prohibitions turned out to be already gated. The remaining risk is downstream documents copying the figures, not this file. | 2026-08-09 | | #252 | issue | check:bundle-budget counts mockup chunks, contradicting #013's initial-bundle position | RESOLVED 2026-08-09. Reconciled with #013 by splitting the one blurred number into two named budgets in scripts/check-bundle-budget.mjs and bundle-budget.json: production (every chunk a non-mockup route reaches, plus chunks no route manifest claims — framework, polyfills, runtime) at 10% tolerance, and mockups (chunks reachable ONLY from /mockups/**) at a deliberately looser 25% as a runaway detector rather than a per-mockup gate. A chunk shared by both counts as production because it would be built either way. Attribution reads the per-route *_client-reference-manifest.js files under .next/server/app (Next 16 webpack emits no app-build-manifest.json) and fails closed when that tree is missing or resolves no routes, so the buckets can never silently collapse. Chose the split over option (a) exclude-and-lose-back-pressure or option (b) rename-and-keep-one-number because the measurement made both inadequate: on a clean build of main at af85cbc the repo-wide total was 1546.5 KiB gzip = +9.96% of the 1406.4 KiB baseline, i.e. 576 bytes from failing Build, while production-only was 1279.1 KiB = 9.06% BELOW that same baseline. Every byte of the apparent regression was design scratch (267.5 KiB across 76 chunks exclusive to 66 mockup routes, 17.29% of the measured total) and production had actually shrunk. Corroborated by docs/audit/latency-audit-2026-07-28.md, which recorded 1,309,274 gzip bytes on 2026-07-28 against 1,309,772 production-only today — production weight is flat to +0.04% over that period, so the 2026-08-04 bump of the single baseline to 1,440,201 had absorbed mockup growth as though it were production growth. Raising the ceiling again would have hidden that permanently. New baselines: production 1,309,772, mockups 273,873. Both fail paths proven to exit 1 against the real build by temporarily lowering each baseline; attribution fail-closed proven by tests/bundle-budget.test.ts. Docs updated in AGENTS.md (new Bundle budget section, incl. the stale-.next measurement trap), CLAUDE.md, docs/plans/document-viewer-phase3-handover.md. | 2026-08-09 | | #295 | issue | Factsheet detail renders two h1 elements — hero plus the portaled print sheet | RESOLVED 2026-08-09 as WONTFIX-by-design, with the invariant now asserted instead. Investigated and concluded the print sheet's

is correct and must stay. The two headings are mutually exclusive by construction, so no state ever exposes both to the accessibility tree: on screen `.factsheet-print-sheet { display: none }` removes the print subtree entirely, and in print `html.factsheets-printing body > *:not(.factsheet-print-portal)` is `display: none !important`, which removes the entire shell that owns the hero heading (src/app/globals.css ~3409-3427). Demoting the print sheet to

would be a regression, not a fix: the printed PDF is a separate document whose section headings are already

, so it would ship with no top-level heading and an outline starting at level 2. The real residual was the one this row named — the DOM test had been scoped to the page testid, so the document-level invariant was asserted nowhere and a genuinely stray third

would not have been caught. Closed that instead: tests/factsheet-detail-header.dom.test.tsx now censuses every

in the document, pinning exactly two, one inside the page shell and one inside .factsheet-print-portal, both carrying the factsheet title, plus at least one

in the printed outline. jsdom applies no stylesheet, so a census is the right shape of guard rather than a visibility assertion. No component was moved: FactsheetPrintSheet stays in factsheet-detail-page.tsx, as scripts/design-system-contract-utils.mjs scopes its raw-colour exemption to the literal factsheet-print-sheet marker and fails closed if it moves. 6 tests pass. | 2026-08-09 | +| #218 | issue | cn() has no tailwind-merge, so className size overrides resolve by stylesheet order rather than intent | CLOSED 2026-08-09 as already shipped - verified in source at origin/main 7aaf9349c, not inferred. cn() adopted tailwind-merge in PR #1678 (aeba5a254 feat(design-system): give cn() tailwind-merge (#218)). Evidence: src/components/ui-primitives.tsx:37 is now twMergeClinical(classes.filter(Boolean).join(" ")) rather than a plain join, importing twMergeClinical from @/lib/tailwind-merge at line 13; package.json:267 carries tailwind-merge ^3.6.0; src/lib/tailwind-merge.ts (119 lines) declares this repo's @theme text/leading/tracking/spacing/ease/animate scales to twMerge and documents the two silent failure modes it exists to stop (misclassification deleting the text-sm-minus family, and non-recognition of size-icon-md / tracking-label / leading-prose / ease-out-soft / animate-shimmer / pt-safe). The row's own concrete instance - cn(metadataPill, ... text-2xs) in document-search-results.tsx - now resolves by call-site intent rather than stylesheet order. The follow-up that #218 spawned, #270, is also closed. Note for the next reader: the docstring at src/lib/tailwind-merge.ts:34-36 still says present-tense that the --spacing-tap family 'is held back', which its own line 66 and the spacing array at line 104 contradict; that stale sentence is cosmetic and does not affect behaviour. | 2026-08-09 | +| #270 | task | Declaring tap in tailwind-merge is no longer blocked by dead numeric heights — the 22-site premise did not survive re-measurement | CLOSED 2026-08-09 as already shipped - verified in source at origin/main 7aaf9349c, not inferred. The composition-aware sweep this row asked for was run and its conclusion acted on in PR #1738 (80cf78139 fix(tailwind-merge): declare the tap spacing token now its blocker is disproved), confirmed an ancestor of origin/main by git merge-base --is-ancestor. Evidence: 'tap' is now present in CLINICAL_TWMERGE_THEME.spacing at src/lib/tailwind-merge.ts:104, and the long comment at lines 66-93 has been rewritten from the stale 22-site/18-drop justification into a dated record of the disproof - zero same-variant tap/numeric pairs, the 84 survivors identified as cross-variant responsive step-downs that twMerge groups apart, the named call sites confirmed stale, and a composition-aware sweep over 1418 cn() call sites that resolves constant recipe identifiers finding zero same-variant pairs in either direction, mutation-tested against synthetic cn("h-tap","h-4") and cn("min-h-tap", recipe) probes so the zero is a measurement rather than a pattern that never matches. tests/tailwind-merge-config.test.ts was not deleted as the row proposed but inverted, which is better: line 116 now asserts cn("min-h-9","min-h-tap") === "min-h-tap", lines 125-127 assert the cross-variant pairs survive untouched, and line 161 asserts every --spacing-* token including tap is declared. No production target was lowered. Residual cosmetic drift: the module docstring at lines 34-36 still says present-tense that the --spacing-tap family 'is held back', contradicted by line 66 and the array itself. | 2026-08-09 | diff --git a/scripts/check-design-system-contract.mjs b/scripts/check-design-system-contract.mjs index 4569313e6..190993f90 100644 --- a/scripts/check-design-system-contract.mjs +++ b/scripts/check-design-system-contract.mjs @@ -10,6 +10,7 @@ import { findDebtPathRegressions, findInteractiveTapLiteralsInSource, findTextSoftConsumersInSource, + findTypeStepCssUsagesInSource, LEGACY_TAP_CLASS, hasLegacyTapClass, jsxClassText, @@ -120,6 +121,9 @@ const metrics = { darkColorOverrides: 0, legacyShadowAliases: 0, arbitraryTracking: 0, + rawPaddingLiterals: 0, + rawRadiusLiterals: 0, + rawLineHeightLiterals: 0, layoutTransitionExceptions: 0, textSoftConsumers: 0, }; @@ -129,7 +133,21 @@ const recordDebt = (metric, relativePath, count) => { if (count > 0) debtByPath[metric][relativePath] = (debtByPath[metric][relativePath] ?? 0) + count; }; +/** + * Type steps that are declared but deliberately unconsumed, with the reason. + * + * Retiring a step edits the `@theme` block, so it belongs in its own revertible + * change rather than riding along with a gate. Anything listed here is recorded + * debt with a ledger row, not a permanent licence — an entry should leave this + * list by being deleted from `globals.css`, not by being forgotten. + */ +const UNUSED_TYPE_STEP_EXEMPTIONS = new Map([ + ["2xl-compact", "no consumer since it was added; retirement tracked as docs/outstanding-issues.md #297"], +]); + const densityOverrideFindings = []; +const typeStepUsage = new Set(); +const typeStepCssUsage = new Set(); const hardcodedMotionClassFindings = []; const imageInversionFindings = []; const layoutTransitionFindings = []; @@ -176,6 +194,11 @@ for (const file of files) { recordDebt("darkColorOverrides", file.relativePath, classAnalysis.darkColorOverrides.length); recordDebt("legacyShadowAliases", file.relativePath, classAnalysis.legacyShadowAliases.length); recordDebt("arbitraryTracking", file.relativePath, classAnalysis.arbitraryTracking.length); + recordDebt("rawPaddingLiterals", file.relativePath, classAnalysis.rawPaddingLiterals.length); + recordDebt("rawRadiusLiterals", file.relativePath, classAnalysis.rawRadiusLiterals.length); + recordDebt("rawLineHeightLiterals", file.relativePath, classAnalysis.rawLineHeightLiterals.length); + for (const step of classAnalysis.typeStepUsages) typeStepUsage.add(step); + for (const step of findTypeStepCssUsagesInSource(source, file.relativePath)) typeStepCssUsage.add(step); densityOverrideFindings.push(...classAnalysis.densityOverrides); hardcodedMotionClassFindings.push(...classAnalysis.hardcodedMotionClasses); layoutTransitionFindings.push(...classAnalysis.layoutTransitions); @@ -186,6 +209,9 @@ for (const file of files) { recordDebt("hardcodedCssMotionDurations", file.relativePath, cssAnalysis.hardcodedMotionDurations.length); recordDebt("rawCssZIndices", file.relativePath, cssAnalysis.rawZIndices.length); recordDebt("legacyShadowAliases", file.relativePath, cssAnalysis.legacyShadowAliases.length); + recordDebt("rawPaddingLiterals", file.relativePath, cssAnalysis.rawPaddingLiterals.length); + recordDebt("rawRadiusLiterals", file.relativePath, cssAnalysis.rawRadiusLiterals.length); + recordDebt("rawLineHeightLiterals", file.relativePath, cssAnalysis.rawLineHeightLiterals.length); imageInversionFindings.push(...cssAnalysis.imageInversions); layoutTransitionFindings.push(...cssAnalysis.layoutTransitions); } @@ -335,6 +361,49 @@ assert( const globals = textAt("src/app/globals.css"); assert(!/^\s*--space-\d+\s*:/m.test(globals), "unused --space-* tokens returned"); + +// Step SELECTION, the half `check:type-scale` cannot cover. That gate blocks +// arbitrary `text-[12px]` values; nothing has stopped the scale itself growing +// a step no surface ever picks. A declared-but-unconsumed step is the shape of +// that drift which IS mechanically decidable — whether a heading should have +// picked `text-sm` over `text-sm-minus` is not, and no lint here pretends +// otherwise. +const themeBlockStart = globals.indexOf("@theme {"); +assert(themeBlockStart >= 0, "globals.css @theme block is missing"); +if (themeBlockStart >= 0) { + const themeBlock = globals.slice(themeBlockStart, globals.indexOf("\n}", themeBlockStart)); + const declaredTypeSteps = [...themeBlock.matchAll(/^\s*--text-([a-z0-9-]+)\s*:/gm)] + .map((match) => match[1]) + // `--text---line-height` and `--text--tr` are companions that + // mark and accompany a step; they are not steps and generate no utility. + .filter((step) => !step.includes("--") && !step.endsWith("-tr")); + assert(declaredTypeSteps.length > 0, "no --text-* type steps found in the globals.css @theme block"); + + const typeStepIsConsumed = (step) => typeStepUsage.has(step) || typeStepCssUsage.has(step); + const unusedTypeSteps = declaredTypeSteps.filter( + (step) => !typeStepIsConsumed(step) && !UNUSED_TYPE_STEP_EXEMPTIONS.has(step), + ); + assert( + unusedTypeSteps.length === 0, + `type steps are declared in globals.css @theme but no production surface selects them: ${unusedTypeSteps + .map((step) => `--text-${step} (text-${step})`) + .join(", ")}. Retire the step or use it; do not leave the scale carrying a step nobody picks.`, + ); + // An exemption for a step that has since gained a consumer is stale, and a + // stale exemption is how a list like this starts lying to the next reader. + // Class utilities and direct `var(--text-*)` consumers both count — the same + // predicate as the unused-step filter above. + for (const [step, reason] of UNUSED_TYPE_STEP_EXEMPTIONS) { + assert( + declaredTypeSteps.includes(step), + `--text-${step} is exempted as unused but is no longer declared in @theme — drop the exemption (${reason})`, + ); + assert( + !typeStepIsConsumed(step), + `--text-${step} is exempted as unused but production now selects text-${step} / var(--text-${step}) — drop the exemption (${reason})`, + ); + } +} const primitives = textAt("src/components/ui-primitives.tsx"); assert( primitives.includes('export const chatComposerInput = "chat-composer-input"'), @@ -406,5 +475,8 @@ console.log( console.log( `Status-colour boundary: colour-only status indicators ${metrics.colourOnlyStatusIndicators}; status-coloured numerals ${metrics.statusColouredNumerals}; image inversions ${imageInversionFindings.length}.`, ); +console.log( + `Scale ratchets: raw padding literals ${metrics.rawPaddingLiterals}; raw radius literals ${metrics.rawRadiusLiterals}; raw line-height literals ${metrics.rawLineHeightLiterals}.`, +); console.log(`Text-role ratchet: --text-soft consumers ${metrics.textSoftConsumers}.`); console.log(`Raw-color exemptions: ${RAW_COLOR_EXEMPTIONS.map(({ category }) => category).join(", ")}.`); diff --git a/scripts/design-system-contract-baseline.json b/scripts/design-system-contract-baseline.json index eb373a67f..147e60b1c 100644 --- a/scripts/design-system-contract-baseline.json +++ b/scripts/design-system-contract-baseline.json @@ -13,6 +13,9 @@ "darkColorOverrides": 0, "legacyShadowAliases": 220, "arbitraryTracking": 0, + "rawPaddingLiterals": 67, + "rawRadiusLiterals": 24, + "rawLineHeightLiterals": 3, "layoutTransitionExceptions": 12, "textSoftConsumers": 0 }, @@ -145,6 +148,31 @@ "src/components/ui-primitives.tsx": 6 }, "arbitraryTracking": {}, + "rawPaddingLiterals": { + "src/app/globals.css": 17, + "src/components/clinical-dashboard/document-search-results.tsx": 1, + "src/components/clinical-dashboard/result-filter-control.tsx": 1, + "src/components/clinical-record-panels.tsx": 2, + "src/components/differentials/differential-detail-page.tsx": 1, + "src/components/services/service-detail-page.tsx": 1, + "src/components/therapy-compass/bindings.tsx": 1, + "src/components/therapy-compass/controls.ts": 2, + "src/components/therapy-compass/screens/brief-screen.tsx": 7, + "src/components/therapy-compass/screens/compare-screen.tsx": 5, + "src/components/therapy-compass/screens/detail-screen.tsx": 7, + "src/components/therapy-compass/screens/pathways-screen.tsx": 4, + "src/components/therapy-compass/screens/recommend-screen.tsx": 6, + "src/components/therapy-compass/screens/sheets-screen.tsx": 9, + "src/components/therapy-compass/therapy-card.tsx": 3 + }, + "rawRadiusLiterals": { + "src/app/globals.css": 22, + "src/components/clinical-dashboard/search-results-header-band.tsx": 1, + "src/components/mode-nav/mode-nav.tsx": 1 + }, + "rawLineHeightLiterals": { + "src/app/globals.css": 3 + }, "layoutTransitionExceptions": { "src/app/globals.css": 4, "src/components/calculators/guided-flow.tsx": 1, diff --git a/scripts/design-system-contract-utils.mjs b/scripts/design-system-contract-utils.mjs index 3998e3573..71314d234 100644 --- a/scripts/design-system-contract-utils.mjs +++ b/scripts/design-system-contract-utils.mjs @@ -289,6 +289,69 @@ const INVERSION_FUNCTION = /(?:invert|hue-rotate)\(/; // the sanctioned token form and is deliberately NOT counted, exactly as // `text-[color:var(--…)]` is exempt from the type-scale check. const ARBITRARY_TRACKING_UTILITY = /^tracking-\[(?!var\()[^\]]+\]$/; +/** + * Spacing, radius and line-height written as a bare literal — `px-[22px]`, + * `rounded-[7px]`, `leading-[1.15]` — rather than picked off the scale. + * + * These deliberately exempt any arbitrary value containing a CSS *function*, + * not just `var(`. `tracking-[var(--…)]` above can use the narrower `(?!var\()` + * because letterspacing is only ever a token or a literal, but padding is not: + * production carries `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 are + * sanctioned. A `(?!var\()` lookahead would flag every one of them, because + * they open with `max(`, `clamp(`, `env(` or `calc(` rather than `var(`. + * + * So the rule is: a value with no function call in it at all is a raw literal. + * That keeps the sanctioned computed forms out without enumerating them. + */ +const RAW_LITERAL_VALUE = String.raw`\[(?![^\]]*\w\()[^\]]+\]`; +const RAW_PADDING_UTILITY = new RegExp(String.raw`^p[xytrbles]?-${RAW_LITERAL_VALUE}$`); +const RAW_RADIUS_UTILITY = new RegExp( + String.raw`^rounded(?:-(?:[trblse]|[tb][lr]|ss|se|ee|es))?-${RAW_LITERAL_VALUE}$`, +); +const RAW_LINE_HEIGHT_UTILITY = new RegExp(String.raw`^leading-${RAW_LITERAL_VALUE}$`); +/** + * The CSS-declaration half of the same three rules, so a literal cannot simply + * move from a class into `globals.css` to escape the ratchet — the same reason + * `legacyShadowAliases` and the colour ratchet count both sides. + * + * `0` in any unit carries no design decision and is exempt, as are the CSS-wide + * keywords and `line-height: normal`. The zero matcher takes any CSS unit + * identifier (`0dvh`, `0svw`, `0cqw`, `0lh`, …), not a finite allowlist — a + * closed list falsely counted those as raw debt. + * + * Tailwind's arbitrary-property spelling (`[padding:22px]`, + * `[border-radius:7px]`, `[line-height:1.35]`) reaches the same properties as + * the named utilities above and is counted with the same raw-literal predicate, + * or the ratchet could be bypassed by changing syntax alone. + */ +const RAW_PADDING_PROPERTY = /^padding(?:-(?:top|right|bottom|left|inline|block)(?:-(?:start|end))?)?$/; +const RAW_RADIUS_PROPERTY = /^border(?:-(?:top|bottom)-(?:left|right)|-(?:start|end)-(?:start|end))?-radius$/; +const CSS_WIDE_KEYWORD = /^(?:inherit|initial|unset|revert|revert-layer|normal|auto)$/; +const CSS_ZERO_VALUE = /^-?0(?:\.0+)?(?:[a-z%]+)?$/i; +const ARBITRARY_PROPERTY_UTILITY = /^\[([a-z-]+):([^\]]+)\]$/i; + +function isRawScaleLiteralValue(value) { + const trimmed = value.trim(); + if (!trimmed || /\w\(/.test(trimmed) || CSS_WIDE_KEYWORD.test(trimmed)) return false; + return !trimmed.split(/\s+/).every((part) => CSS_ZERO_VALUE.test(part)); +} + +function recordRawScaleLiteralProperty(result, relativePath, line, prop, value, token) { + if (!isRawScaleLiteralValue(value)) return; + const label = token ?? `${prop}: ${value}`; + if (RAW_PADDING_PROPERTY.test(prop)) { + result.rawPaddingLiterals.push(`${relativePath}:${line} (${label})`); + } + if (RAW_RADIUS_PROPERTY.test(prop)) { + result.rawRadiusLiterals.push(`${relativePath}:${line} (${label})`); + } + if (prop === "line-height") { + result.rawLineHeightLiterals.push(`${relativePath}:${line} (${label})`); + } +} const LEGACY_SHADOW_ALIAS = /var\(--shadow-(?:tight|card|soft|hover|elevated|lux|lift)\)/g; const LEGACY_PALETTE_UTILITY = /^(?:bg|text|border|ring|outline|fill|stroke|placeholder|from|via|to)-(?:white|black|(?:slate|gray|zinc|neutral|stone)-\d{2,3})(?:\/\d{1,3})?$/; @@ -974,7 +1037,11 @@ export function analyzeClassContractsInSource(relativePath, sourceText) { legacyTapClasses: [], legacyPaletteUtilities: [], literalShadowClasses: [], + rawLineHeightLiterals: [], + rawPaddingLiterals: [], + rawRadiusLiterals: [], statusColouredNumerals: [], + typeStepUsages: [], unapprovedZIndices: [], }; if (!analyzer) return result; @@ -1057,6 +1124,27 @@ export function analyzeClassContractsInSource(relativePath, sourceText) { } if (LITERAL_SHADOW_UTILITY.test(base)) result.literalShadowClasses.push(`${relativePath}:${line} (${token})`); if (ARBITRARY_TRACKING_UTILITY.test(base)) result.arbitraryTracking.push(`${relativePath}:${line} (${token})`); + if (RAW_PADDING_UTILITY.test(base)) result.rawPaddingLiterals.push(`${relativePath}:${line} (${token})`); + if (RAW_RADIUS_UTILITY.test(base)) result.rawRadiusLiterals.push(`${relativePath}:${line} (${token})`); + if (RAW_LINE_HEIGHT_UTILITY.test(base)) result.rawLineHeightLiterals.push(`${relativePath}:${line} (${token})`); + const arbitraryProperty = base.match(ARBITRARY_PROPERTY_UTILITY); + if (arbitraryProperty) { + recordRawScaleLiteralProperty( + result, + relativePath, + line, + arbitraryProperty[1].toLowerCase(), + arbitraryProperty[2], + token, + ); + } + // Every bare `text-` this file uses, whatever `` turns out to + // mean. The caller decides which of these are type steps by reading the + // `@theme` block, so the scale is never spelled out twice — writing the + // step list here would make this module a second source of truth that + // globals.css could drift away from silently. + const bareTextUtility = base.match(/^text-([a-z0-9][a-z0-9-]*)$/); + if (bareTextUtility) result.typeStepUsages.push(bareTextUtility[1]); if (hasLegacyTapClass(token)) result.legacyTapClasses.push(`${relativePath}:${line} (${token})`); for (const match of token.matchAll(LEGACY_SHADOW_ALIAS)) { result.legacyShadowAliases.push(`${relativePath}:${line} (${match[0]})`); @@ -1186,11 +1274,19 @@ export function analyzeCssContractsInSource(relativePath, sourceText) { layoutTransitions: [], legacyShadowAliases: [], onePixelShadowSpreads: [], + rawLineHeightLiterals: [], + rawPaddingLiterals: [], + rawRadiusLiterals: [], rawZIndices: [], }; for (const declaration of cssDeclarations(sourceText)) { const line = declaration.source?.start?.line ?? 1; const prop = declaration.prop.toLowerCase(); + // Custom-property declarations are the token definitions themselves — the + // scale has to be written down somewhere — so only real properties count. + if (!prop.startsWith("--")) { + recordRawScaleLiteralProperty(result, relativePath, line, prop, declaration.value); + } if (/^(?:-webkit-)?(?:backdrop-)?filter$/.test(prop)) { for (const match of declaration.value.matchAll(/\b(invert|hue-rotate)\(/g)) { result.imageInversions.push(`${relativePath}:${line} (${prop}: ${match[1]}())`); @@ -1237,6 +1333,62 @@ export function countRawCssZIndicesInSource(sourceText) { return analyzeCssContractsInSource("source.css", sourceText).rawZIndices.length; } +export function findRawScaleLiteralClassesInSource(relativePath, sourceText) { + const analysis = analyzeClassContractsInSource(relativePath, sourceText); + return { + padding: analysis.rawPaddingLiterals, + radius: analysis.rawRadiusLiterals, + lineHeight: analysis.rawLineHeightLiterals, + }; +} + +export function findTypeStepUsagesInSource(relativePath, sourceText) { + return analyzeClassContractsInSource(relativePath, sourceText).typeStepUsages; +} + +/** + * Direct `var(--text-)` consumers in any production source. The unused-step + * gate and its exemption anti-rot check must share this predicate — a class-only + * check lets an exemption survive once a CSS consumer appears. + * + * CSS sources are walked declaration-by-declaration so a quoted `content:` + * string cannot fake a consumer. Non-CSS sources strip comments first, then + * match `var(--text-*)` in remaining text (covers inline style strings). + */ +export function findTypeStepCssUsagesInSource(sourceText, relativePath = "source.css") { + const steps = []; + const record = (step) => { + if (!step.includes("--") && !step.endsWith("-tr")) steps.push(step); + }; + + if (relativePath.endsWith(".css")) { + for (const declaration of cssDeclarations(sourceText)) { + if (declaration.prop.startsWith("--")) continue; + // Drop CSS string tokens so `content:"var(--text-…)"` cannot count. + const value = declaration.value.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'(?:\\.|[^'\\])*'/g, "''"); + for (const match of value.matchAll(/var\(\s*--text-([a-z0-9-]+)\s*[,)]/g)) { + record(match[1]); + } + } + return steps; + } + + const withoutComments = sourceText.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1"); + for (const match of withoutComments.matchAll(/var\(\s*--text-([a-z0-9-]+)\s*[,)]/g)) { + record(match[1]); + } + return steps; +} + +export function findRawScaleLiteralDeclarationsInSource(sourceText) { + const analysis = analyzeCssContractsInSource("source.css", sourceText); + return { + padding: analysis.rawPaddingLiterals, + radius: analysis.rawRadiusLiterals, + lineHeight: analysis.rawLineHeightLiterals, + }; +} + export function findDebtPathRegressions(metric, currentByPath, baselineByPath) { return Object.entries(currentByPath) .filter(([relativePath, count]) => count > (baselineByPath?.[relativePath] ?? 0)) diff --git a/tests/design-system-contract-utils.test.ts b/tests/design-system-contract-utils.test.ts index e9202e1c4..93c836054 100644 --- a/tests/design-system-contract-utils.test.ts +++ b/tests/design-system-contract-utils.test.ts @@ -13,7 +13,11 @@ import { findInteractiveTapLiteralsInSource, findJsxEdgeOwnershipConflictsInSource, findLayoutTransitionClassesInSource, + findRawScaleLiteralClassesInSource, + findRawScaleLiteralDeclarationsInSource, findTextSoftConsumersInSource, + findTypeStepCssUsagesInSource, + findTypeStepUsagesInSource, findUnapprovedZIndexClassesInSource, hasLegacyTapClass, rawColorContractSource, @@ -362,6 +366,83 @@ describe("design-system contract helpers", () => { expect(countRawCssZIndicesInSource(".a{z-index: 95;}.b{z-index:-1;}")).toBe(2); }); + it("counts bare padding, radius and line-height literals in classes but not computed values", () => { + const found = findRawScaleLiteralClassesInSource( + "src/probe.tsx", + 'export const probe =
;', + ); + expect(found.padding).toEqual(["src/probe.tsx:1 (px-[22px])"]); + expect(found.radius).toEqual(["src/probe.tsx:1 (rounded-[7px])"]); + expect(found.lineHeight).toEqual(["src/probe.tsx:1 (leading-[1.35])"]); + + // Arbitrary-property spellings reach the same CSS properties and must not + // bypass the named-utility matchers. + const arbitraryProperty = findRawScaleLiteralClassesInSource( + "src/probe.tsx", + 'export const probe =
;', + ); + expect(arbitraryProperty.padding).toEqual(["src/probe.tsx:1 ([padding:22px])"]); + expect(arbitraryProperty.radius).toEqual(["src/probe.tsx:1 ([border-radius:7px])"]); + expect(arbitraryProperty.lineHeight).toEqual(["src/probe.tsx:1 ([line-height:1.35])"]); + + // The sanctioned computed forms production actually ships. A `(?!var\()` + // lookahead would flag every one of these, because they open with `env(`, + // `max(`, `clamp(` or `calc(` rather than `var(`. + const exempt = findRawScaleLiteralClassesInSource( + "src/probe.tsx", + 'export const probe =
;', + ); + expect(exempt.padding).toEqual([]); + expect(exempt.radius).toEqual([]); + expect(exempt.lineHeight).toEqual([]); + }); + + it("reports bare text-* selections without deciding which names are type steps", () => { + const usages = findTypeStepUsagesInSource( + "src/probe.tsx", + 'export const probe =
;', + ); + // Variant prefixes are stripped, so `sm:text-2xs` counts as selecting the + // step. Arbitrary and colour forms are not bare names and are left to + // check:type-scale, which is the half that already ships. + expect(usages).toContain("sm-minus"); + expect(usages).toContain("2xs"); + expect(usages).not.toContain("[color:var(--text)]"); + // Non-size `text-*` utilities share the namespace; the caller filters them + // out by intersecting with the @theme block rather than this module + // guessing, so they are reported here unfiltered. + expect(usages).toContain("balance"); + }); + + it("counts the same three literals in CSS declarations, exempting zero, keywords and tokens", () => { + const found = findRawScaleLiteralDeclarationsInSource(".a{padding:13px 9px;border-radius:7px;line-height:1.35;}"); + expect(found.padding).toEqual(["source.css:1 (padding: 13px 9px)"]); + expect(found.radius).toEqual(["source.css:1 (border-radius: 7px)"]); + expect(found.lineHeight).toEqual(["source.css:1 (line-height: 1.35)"]); + + const exempt = findRawScaleLiteralDeclarationsInSource( + ".a{padding:0;padding-inline:0dvh;border-radius:0svw;line-height:0lh;padding-block:var(--pad-card);border-top-left-radius:inherit;line-height:normal;margin:5px;--radius-xs:0.25rem;}", + ); + expect(exempt.padding).toEqual([]); + expect(exempt.radius).toEqual([]); + expect(exempt.lineHeight).toEqual([]); + }); + + it("reports direct var(--text-*) consumers for the shared unused-step predicate", () => { + expect( + findTypeStepCssUsagesInSource( + '.a{font-size:var(--text-2xl-compact)}.b{font-size:var(--text-hero, 2rem)}.c{--text-sm-minus:1rem;content:"var(--text-sm-minus)"}', + "src/probe.css", + ), + ).toEqual(["2xl-compact", "hero"]); + expect( + findTypeStepCssUsagesInSource( + 'const style = { fontSize: "var(--text-2xl-compact)" };\n// var(--text-hero)\n', + "src/probe.tsx", + ), + ).toEqual(["2xl-compact"]); + }); + it("rejects debt moved to a new path even when its global total is unchanged", () => { expect(findDebtPathRegressions("legacy", { "src/new.tsx": 1 }, { "src/old.tsx": 1 })).toEqual([ "legacy at src/new.tsx increased from 0 to 1",