diff --git a/docs/branch-review-records/bc83ff5f4c4f9421ec32cf4351708e95ce45c035738a63aaa40ea6ca6afcfa3d.record.md b/docs/branch-review-records/bc83ff5f4c4f9421ec32cf4351708e95ce45c035738a63aaa40ea6ca6afcfa3d.record.md
new file mode 100644
index 0000000000..3637257188
--- /dev/null
+++ b/docs/branch-review-records/bc83ff5f4c4f9421ec32cf4351708e95ce45c035738a63aaa40ea6ca6afcfa3d.record.md
@@ -0,0 +1 @@
+| 2026-08-15 | claude/issues-reconcile-2026-08-15 | 3381a69cba662c7dd4083c0fe8747aa04d7c9097 | Canonical ledger reconcile of 17 queued requests after the #1982/#1983/#1984/#1985 merges | Applied cleanly; inbox 0 pending / 189 applied | issues:reconcile applied 17 requests with 3 cancellation decisions; check:outstanding-issues 341 rows (97 open, 244 archived), unique ids, next-id 344 above highest, no ids deleted from base; check:ledger-write-discipline passed for 2e3ac494b8b7..HEAD (canonical diff equals the recorded transaction); verify:pr-local docs-scoped route, 11 gates, none failed |
diff --git a/docs/branch-review-records/fec8d16693dafc5f5c400815e091e8d8565e99f53e2979f061dd3cc68c012315.record.md b/docs/branch-review-records/fec8d16693dafc5f5c400815e091e8d8565e99f53e2979f061dd3cc68c012315.record.md
new file mode 100644
index 0000000000..992b15ca07
--- /dev/null
+++ b/docs/branch-review-records/fec8d16693dafc5f5c400815e091e8d8565e99f53e2979f061dd3cc68c012315.record.md
@@ -0,0 +1 @@
+| 2026-08-15 | claude/issues-reconcile-2026-08-15 | 2c0ca2c5c65065b2d39ab5465fce50c50eec5bb2 | review-and-fix | Found P2 stale #265 measurements in the reconciliation; queued an immutable correction with current 40/16 tap-floor and 19/10 edge-conflict counts; merged latest main | design-system contract; outstanding-issues and ledger-write-discipline guards; branch-review ledger; format; docs links |
diff --git a/docs/outstanding-issues-inbox/7ad31d10-aeef-4635-863d-d6ce9340d916.json b/docs/outstanding-issues-inbox/7ad31d10-aeef-4635-863d-d6ce9340d916.json
new file mode 100644
index 0000000000..d7c4ba29de
--- /dev/null
+++ b/docs/outstanding-issues-inbox/7ad31d10-aeef-4635-863d-d6ce9340d916.json
@@ -0,0 +1,11 @@
+{
+ "version": 1,
+ "id": "7ad31d10-aeef-4635-863d-d6ce9340d916",
+ "createdOn": "2026-08-15",
+ "action": "update",
+ "payload": {
+ "id": "#265",
+ "detail": "CORRECTION QUEUED 2026-08-15 during PR #1987 review. Gate 2 is closed for new use, but the just-reconciled detail inherited stale measurements from PR #1984's pre-sync tree. On the exact current tree after PRs #1982-#1986, check:design-system-contract measures interactiveTapFloorDeclarations=40 across 16 production files, not 43 across 17, and edgeOwnershipConflicts=19 across 10 files, not 25 across 12. The strengthened tap-floor parser removed three old false-positive counts from calculators/search-page.tsx; favourites-library-nav and pwa-lifecycle no longer contribute edge conflicts after the merged UI work. The baseline and GATES.md still permit/report the older 43 and 25 values, so they carry three and six units of stale ratchet slack respectively and need a focused tightening follow-up. The detector still correctly evaluates comparable arbitrary lengths and reachable conditional/composed branches, and component-wrapper tags such as Link remain its known blind spot. Gate 7 remains open: add a shared deterministic render-tree traversal plus child/parent elevation-tier check. Gate 8 remains open: decide ring-versus-outline focus ownership, widen onePixelShadowSpreads to all relevant token families, then retire conflicts with browser focus proof. Do not edit the applied 8c133c4e request in place; it is immutable audit history.",
+ "source": "PR #1987 review; exact tree after #1986; check:design-system-contract; scripts/design-system-contract-baseline.json; docs/design-system/GATES.md"
+ }
+}
diff --git a/docs/outstanding-issues-inbox/0948fcd2-2e51-47bc-8990-7ec002934e43.json b/docs/outstanding-issues-inbox/applied/0948fcd2-2e51-47bc-8990-7ec002934e43.json
similarity index 100%
rename from docs/outstanding-issues-inbox/0948fcd2-2e51-47bc-8990-7ec002934e43.json
rename to docs/outstanding-issues-inbox/applied/0948fcd2-2e51-47bc-8990-7ec002934e43.json
diff --git a/docs/outstanding-issues-inbox/0a052268-97e1-4d4a-9ad7-033003aa486c.json b/docs/outstanding-issues-inbox/applied/0a052268-97e1-4d4a-9ad7-033003aa486c.json
similarity index 100%
rename from docs/outstanding-issues-inbox/0a052268-97e1-4d4a-9ad7-033003aa486c.json
rename to docs/outstanding-issues-inbox/applied/0a052268-97e1-4d4a-9ad7-033003aa486c.json
diff --git a/docs/outstanding-issues-inbox/0e5573e3-425d-4253-9b28-0c1c81a4f3fe.json b/docs/outstanding-issues-inbox/applied/0e5573e3-425d-4253-9b28-0c1c81a4f3fe.json
similarity index 100%
rename from docs/outstanding-issues-inbox/0e5573e3-425d-4253-9b28-0c1c81a4f3fe.json
rename to docs/outstanding-issues-inbox/applied/0e5573e3-425d-4253-9b28-0c1c81a4f3fe.json
diff --git a/docs/outstanding-issues-inbox/1376cc64-a753-41a0-9c54-30a5cca7f896.json b/docs/outstanding-issues-inbox/applied/1376cc64-a753-41a0-9c54-30a5cca7f896.json
similarity index 100%
rename from docs/outstanding-issues-inbox/1376cc64-a753-41a0-9c54-30a5cca7f896.json
rename to docs/outstanding-issues-inbox/applied/1376cc64-a753-41a0-9c54-30a5cca7f896.json
diff --git a/docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json b/docs/outstanding-issues-inbox/applied/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json
similarity index 100%
rename from docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json
rename to docs/outstanding-issues-inbox/applied/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json
diff --git a/docs/outstanding-issues-inbox/24408f86-38ff-4715-affa-55d6c7bc1bb7.json b/docs/outstanding-issues-inbox/applied/24408f86-38ff-4715-affa-55d6c7bc1bb7.json
similarity index 100%
rename from docs/outstanding-issues-inbox/24408f86-38ff-4715-affa-55d6c7bc1bb7.json
rename to docs/outstanding-issues-inbox/applied/24408f86-38ff-4715-affa-55d6c7bc1bb7.json
diff --git a/docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json b/docs/outstanding-issues-inbox/applied/3156e1c9-82aa-46c3-84e6-0e119015acc6.json
similarity index 100%
rename from docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json
rename to docs/outstanding-issues-inbox/applied/3156e1c9-82aa-46c3-84e6-0e119015acc6.json
diff --git a/docs/outstanding-issues-inbox/397a3588-b8cf-404b-a3d4-6060b84f7d60.json b/docs/outstanding-issues-inbox/applied/397a3588-b8cf-404b-a3d4-6060b84f7d60.json
similarity index 100%
rename from docs/outstanding-issues-inbox/397a3588-b8cf-404b-a3d4-6060b84f7d60.json
rename to docs/outstanding-issues-inbox/applied/397a3588-b8cf-404b-a3d4-6060b84f7d60.json
diff --git a/docs/outstanding-issues-inbox/568597bd-fc9f-47d6-b582-443812115e67.json b/docs/outstanding-issues-inbox/applied/568597bd-fc9f-47d6-b582-443812115e67.json
similarity index 100%
rename from docs/outstanding-issues-inbox/568597bd-fc9f-47d6-b582-443812115e67.json
rename to docs/outstanding-issues-inbox/applied/568597bd-fc9f-47d6-b582-443812115e67.json
diff --git a/docs/outstanding-issues-inbox/5cf244c3-1b68-4214-8097-767b11a49e13.json b/docs/outstanding-issues-inbox/applied/5cf244c3-1b68-4214-8097-767b11a49e13.json
similarity index 100%
rename from docs/outstanding-issues-inbox/5cf244c3-1b68-4214-8097-767b11a49e13.json
rename to docs/outstanding-issues-inbox/applied/5cf244c3-1b68-4214-8097-767b11a49e13.json
diff --git a/docs/outstanding-issues-inbox/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json b/docs/outstanding-issues-inbox/applied/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json
similarity index 100%
rename from docs/outstanding-issues-inbox/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json
rename to docs/outstanding-issues-inbox/applied/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json
diff --git a/docs/outstanding-issues-inbox/6e354901-7694-40e0-b03d-8c398cec7b05.json b/docs/outstanding-issues-inbox/applied/6e354901-7694-40e0-b03d-8c398cec7b05.json
similarity index 100%
rename from docs/outstanding-issues-inbox/6e354901-7694-40e0-b03d-8c398cec7b05.json
rename to docs/outstanding-issues-inbox/applied/6e354901-7694-40e0-b03d-8c398cec7b05.json
diff --git a/docs/outstanding-issues-inbox/8c133c4e-23e7-427f-b1b8-6d6a64dfd381.json b/docs/outstanding-issues-inbox/applied/8c133c4e-23e7-427f-b1b8-6d6a64dfd381.json
similarity index 100%
rename from docs/outstanding-issues-inbox/8c133c4e-23e7-427f-b1b8-6d6a64dfd381.json
rename to docs/outstanding-issues-inbox/applied/8c133c4e-23e7-427f-b1b8-6d6a64dfd381.json
diff --git a/docs/outstanding-issues-inbox/c3622ee1-872f-4476-8b12-20e97057c5d5.json b/docs/outstanding-issues-inbox/applied/c3622ee1-872f-4476-8b12-20e97057c5d5.json
similarity index 100%
rename from docs/outstanding-issues-inbox/c3622ee1-872f-4476-8b12-20e97057c5d5.json
rename to docs/outstanding-issues-inbox/applied/c3622ee1-872f-4476-8b12-20e97057c5d5.json
diff --git a/docs/outstanding-issues-inbox/d194f4ec-568c-4689-a411-22447c59fb53.json b/docs/outstanding-issues-inbox/applied/d194f4ec-568c-4689-a411-22447c59fb53.json
similarity index 100%
rename from docs/outstanding-issues-inbox/d194f4ec-568c-4689-a411-22447c59fb53.json
rename to docs/outstanding-issues-inbox/applied/d194f4ec-568c-4689-a411-22447c59fb53.json
diff --git a/docs/outstanding-issues-inbox/d656dcb5-3228-4ab8-b40c-b0377e63cb94.json b/docs/outstanding-issues-inbox/applied/d656dcb5-3228-4ab8-b40c-b0377e63cb94.json
similarity index 100%
rename from docs/outstanding-issues-inbox/d656dcb5-3228-4ab8-b40c-b0377e63cb94.json
rename to docs/outstanding-issues-inbox/applied/d656dcb5-3228-4ab8-b40c-b0377e63cb94.json
diff --git a/docs/outstanding-issues-inbox/fd42e1f9-4012-4296-b7c2-102c7d199738.json b/docs/outstanding-issues-inbox/applied/fd42e1f9-4012-4296-b7c2-102c7d199738.json
similarity index 100%
rename from docs/outstanding-issues-inbox/fd42e1f9-4012-4296-b7c2-102c7d199738.json
rename to docs/outstanding-issues-inbox/applied/fd42e1f9-4012-4296-b7c2-102c7d199738.json
diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md
index 07a0e25f69..3b89830149 100644
--- a/docs/outstanding-issues.md
+++ b/docs/outstanding-issues.md
@@ -78,37 +78,35 @@ removed after current-main verification; it is not missing recommended work.
| 23 | `#102` | A3 | Operator — Supabase + Specialist | Next approved index window, after the ordering question is settled | 1–2 hours plus apply | Author the migration (operator SQL alone never reaches staging/DR/local replay), then apply → mirror `schema.sql` → regenerate drift manifest → register `required_indexes`. **Stop:** the RAG-path index is canary-gated, and ordering `fetchDocumentTitleAliasRows`'s unordered `.limit(12)` does not lift that — an imposed order can select a different twelve, so it is a second canary-gated change, not a way out of the first. The byte-identical claim was retracted. |
| 24 | `#099` | A3 | Specialist — answer path | After `#098` | Half a day per sub-item | Remaining fixed per-request round trips: the 8 `setCachedSearch` deferrals (abort semantics + mutation window), the anonymous subject+global limiter pair (needs a new atomic RPC first), and proxy→route identity duplication. Stop before hand-authoring locking SQL. |
| 25 | `#162` | A3 | High — frontend/UI | When starting the mode search redesign package | 0.5–1.5 days | Redesign `/tools?q=` as Compact Results Instrument (direction A): query-as-H1, one composer, dense tool rows, demote cross-mode cards, remove success-green filter banner and home hero on results. Comps in `public/mockups/mode-page-redesign-2026-07/tools-search/`. Verify phone+desktop chrome ownership and `verify:phone-chrome` / focused UI. Stop if scope expands into Tools home redesign without an explicit ask. |
-| 26 | `#163` | A3 | High — frontend/UI | After or with `#162` | 0.5–1.5 days | Redesign `/services?q=` as Progressive Referral Workflow (direction B): H1 = query (not match count), progressive shortlist/compare (no always-on decision panel or giant step rail). Comps in `public/mockups/mode-page-redesign-2026-07/services-search/`. Verify referral shortlist still works; stop before changing Services home ModeHome. |
-| 27 | `#164` | A3 | High — frontend/UI | Product confirmed Favourites is hybrid dashboard+search (no ModeHome) | 1–2 days | Redesign Favourites as one dashboard + search page: recommended Search-Led Workspace (direction B) — persistent search, sets as chips, Continue + recent + table on empty query, in-place filter on typed query. Comps in `public/mockups/mode-page-redesign-2026-07/favourites-hybrid/`. Do not reintroduce ModeHome for Favourites. Verify desktop+phone; stop before splitting into separate ModeHome routes. |
-| 28 | `#090` | A3 | High — eslint toolchain | When ESLint 10 plugin peers are compatible | blocked; revisit monthly | Upgrade the eslint ecosystem to clear remaining dev-scoped high advisories — full `npm audit` reports zero high advisories from the eslint toolchain. |
-| 29 | `#100` | A3 | Specialist — answer streaming | After offline Phase 0/1 design proof | provider-gated rollout | Buffered answer generation has no incremental verified delivery — [`verified-answer-incremental-delivery-design.md`](verified-answer-incremental-delivery-design.md) records the clinical-governance decision and staged co… |
-| 30 | `#150` | Optional | Operator — review tooling | Next CodeRabbit billing/policy decision | 30–60 min decision | CodeRabbit reviewed none of a full day's PRs; spending cap reached — the repo's second automated reviewer is either funded or acknowledged as absent, rather than appearing to review while skipping. |
-| 31 | `#165` | A2 | High — clinical UI | Next answer-home UX pass | 0.5–1 day | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them — the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. |
-| 32 | `#168` | A3 | High — ledger architecture | With #156 / id-scheme redesign | design first | Sequential issue ids force every concurrent append to conflict — two sessions can append to this ledger at the same time without conflicting. |
-| 33 | `#169` | A3 | High — git hygiene | Next branch cleanup batch | 1–2 hours | Local branches carry work that exists on no remote — committed work is not lost when a machine or worktree is reclaimed. |
-| 34 | `#175` | A2 | Operator — clinical data + Standard | Next therapy catalogue curation window | 2–4 hours | Therapy modality is now null on all 205 records and needs curation or removal — the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. |
-| 35 | `#036` | Optional | Specialist — privacy/schema | When visibility model is redesigned | design + migration | No explicit `is_public` visibility flag on documents — Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the prom… |
-| 36 | `#101` | A3 | Specialist — RAG/retrieval | After #098 harness + canary approval | canary-gated | Canary-gated retrieval parallelisation candidates — metadata and memory hydration shipped in PR #1474; visual hydration, scope enumeration, typeahead caching, and universal-search coalescing remain, each behind the RAG flag and live-canary criteria. |
-| 37 | `#190` | A3 | Specialist — RAG structure | On explicit X3 go-ahead | 1 PR per extraction unit | X3: Finish rag.ts monolith decomposition — `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. |
-| 38 | `#191` | A3 | Operator — DB + Specialist | Approved live-DB window only | provider-gated | X5: ACL-migration consolidation (provider-gated) — ACL-related migrations are consolidated per maturity work-order X5 without weakening owner-scope/RLS. |
-| 39 | `#192` | A3 | High — test coverage | Next coverage-floor pass | 0.5–1 day | X6: Raise clinical/retrieval/answer coverage floors — coverage floors for clinical, retrieval, and answer domains meet the maturity X6 targets with CI enforcing them. |
-| 40 | `#193` | A3 | High — src/lib structure | After/with X3 non-protected clusters | 1 PR per cluster | X7: Complete the remaining src/lib domain-directory reorg — remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. |
-| 41 | `#195` | A3 | Operator — GitHub maintainer | Maintainer UI window | 30–60 min | M1: Repo-host hardening (branch protection and required checks) — GitHub branch-protection rulesets and required checks match audit §8 / maturity M1. |
-| 42 | `#183` | A2 | Operator — Sentry + Specialist | Next approved observability window with SENTRY_AUTH_TOKEN | 1–2 hours | Create Sentry metric alert for production DB span p95 > 500ms (`span.op:db`, environment production). **Stop:** no secret printing; blocked until token/env available. |
-| 43 | `#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. |
-| 44 | `#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. |
-| 45 | `#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. |
-| 46 | `#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. |
-| 47 | `#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. |
-| 48 | `#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. |
-| 49 | `#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. |
-| 50 | `#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. |
-| 51 | `#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. |
-| 52 | `#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. |
-| 53 | `#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. |
+| 26 | `#090` | A3 | High — eslint toolchain | When ESLint 10 plugin peers are compatible | blocked; revisit monthly | Upgrade the eslint ecosystem to clear remaining dev-scoped high advisories — full `npm audit` reports zero high advisories from the eslint toolchain. |
+| 27 | `#100` | A3 | Specialist — answer streaming | After offline Phase 0/1 design proof | provider-gated rollout | Buffered answer generation has no incremental verified delivery — [`verified-answer-incremental-delivery-design.md`](verified-answer-incremental-delivery-design.md) records the clinical-governance decision and staged co… |
+| 28 | `#150` | Optional | Operator — review tooling | Next CodeRabbit billing/policy decision | 30–60 min decision | CodeRabbit reviewed none of a full day's PRs; spending cap reached — the repo's second automated reviewer is either funded or acknowledged as absent, rather than appearing to review while skipping. |
+| 29 | `#165` | A2 | High — clinical UI | Next answer-home UX pass | 0.5–1 day | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them — the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. |
+| 30 | `#168` | A3 | High — ledger architecture | With #156 / id-scheme redesign | design first | Sequential issue ids force every concurrent append to conflict — two sessions can append to this ledger at the same time without conflicting. |
+| 31 | `#169` | A3 | High — git hygiene | Next branch cleanup batch | 1–2 hours | Local branches carry work that exists on no remote — committed work is not lost when a machine or worktree is reclaimed. |
+| 32 | `#175` | A2 | Operator — clinical data + Standard | Next therapy catalogue curation window | 2–4 hours | Therapy modality is now null on all 205 records and needs curation or removal — the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. |
+| 33 | `#036` | Optional | Specialist — privacy/schema | When visibility model is redesigned | design + migration | No explicit `is_public` visibility flag on documents — Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the prom… |
+| 34 | `#101` | A3 | Specialist — RAG/retrieval | After #098 harness + canary approval | canary-gated | Canary-gated retrieval parallelisation candidates — metadata and memory hydration shipped in PR #1474; visual hydration, scope enumeration, typeahead caching, and universal-search coalescing remain, each behind the RAG flag and live-canary criteria. |
+| 35 | `#190` | A3 | Specialist — RAG structure | On explicit X3 go-ahead | 1 PR per extraction unit | X3: Finish rag.ts monolith decomposition — `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. |
+| 36 | `#191` | A3 | Operator — DB + Specialist | Approved live-DB window only | provider-gated | X5: ACL-migration consolidation (provider-gated) — ACL-related migrations are consolidated per maturity work-order X5 without weakening owner-scope/RLS. |
+| 37 | `#192` | A3 | High — test coverage | Next coverage-floor pass | 0.5–1 day | X6: Raise clinical/retrieval/answer coverage floors — coverage floors for clinical, retrieval, and answer domains meet the maturity X6 targets with CI enforcing them. |
+| 38 | `#193` | A3 | High — src/lib structure | After/with X3 non-protected clusters | 1 PR per cluster | X7: Complete the remaining src/lib domain-directory reorg — remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. |
+| 39 | `#195` | A3 | Operator — GitHub maintainer | Maintainer UI window | 30–60 min | M1: Repo-host hardening (branch protection and required checks) — GitHub branch-protection rulesets and required checks match audit §8 / maturity M1. |
+| 40 | `#183` | A2 | Operator — Sentry + Specialist | Next approved observability window with SENTRY_AUTH_TOKEN | 1–2 hours | Create Sentry metric alert for production DB span p95 > 500ms (`span.op:db`, environment production). **Stop:** no secret printing; blocked until token/env available. |
+| 41 | `#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. |
+| 42 | `#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. |
+| 43 | `#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. |
+| 44 | `#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. |
+| 45 | `#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. |
+| 46 | `#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. |
+| 47 | `#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. |
+| 48 | `#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. |
+| 49 | `#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. |
+| 50 | `#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. |
+| 51 | `#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. |
-
+
## 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.
@@ -156,8 +154,6 @@ removed after current-main verification; it is not missing recommended work.
| #118 | P2 | task | Adopt the remaining visual baselines; Lighthouse now gates regressions | Lighthouse half resolved in PR #1915: authorized CI refresh run 31697669596 on current main produced all 10 route/strategy cells with one pinned HeadlessChrome/151 identity. The reviewed artifact was committed, lighthouse-budget.json enforce is true, the job no longer uses continue-on-error, merge_group coverage is restored, and pr-required now fails on a selected Lighthouse failure. The 2026-08-08 and 2026-08-13 complete baselines stayed within tolerance; the latter puts mobile LCP at 2357-2388 ms and Therapy is no longer an outlier. This relative local-production gate does not close #117 deployed-origin LCP work. Remaining #118 scope: adopt the CI-generated Linux visual snapshots and promote visual-baseline only after design-owner review and stable reruns. Stop: never use developer-machine snapshots or let a workflow update its own gate. | PR #1915; CI run 31697669596 artifact lighthouse-baseline-refresh-31697669596 | 2026-07-30 |
| #150 | P2 | issue | CodeRabbit reviewed none of a full day's PRs; spending cap reached | IN FLIGHT annotation retired 2026-08-14: PR #1836 has merged, so the do-not-start note is stale and was blocking rather than protecting. The row itself is NOT code-verifiable from a container — CodeRabbit's spending cap is an account/billing state, so confirming whether the cap still suppresses reviews needs the operator's CodeRabbit dashboard. Next: check the subscription's review quota and either raise it or record the accepted coverage gap. Keeping open pending that operator read. | PRs #1404/#1430/#1444/#1445/#1479; `.coderabbit.yaml` | 2026-07-30 |
| #162 | P2 | task | Redesign Tools search results state (Compact Results Instrument) | IN FLIGHT confirmed still accurate 2026-08-14: PR #1839 is the one PR in this cluster that has NOT merged (no merge commit on origin/main; refs/pull/1839/merge still exists, which GitHub keeps only for open PRs). Every sibling in the same sweep — #1835 #1836 #1837 #1840 #1841 #1842 — has landed and their rows are archived or re-scoped. Do not start this row; it is genuinely in flight. | session 2026-07-31 mode-page design audit | 2026-07-31 |
-| #163 | P2 | task | Redesign Services search results (Progressive Referral Workflow) | **Outcome:** `/services?q=` uses query-as-H1 (not match-count), progressive shortlist/compare, no always-on decision panel or giant step rail. **Product pick:** direction B from comps in `public/mockups/mode-page-redesign-2026-07/services-search/`. **Next:** implement B; keep referral shortlist behaviour. **Stop:** do not change Services ModeHome in the same PR. | session 2026-07-31 mode-page design audit | 2026-07-31 |
-| #164 | P2 | task | Redesign Favourites as hybrid dashboard + search (no ModeHome) | **Outcome:** `/favourites` is one dashboard+search workspace; empty query shows Continue/recent/sets/table; typed query filters in place; no ModeHome hero. **Product pick:** Search-Led Workspace (direction B) from comps in `public/mockups/mode-page-redesign-2026-07/favourites-hybrid/`. User rejected ModeHome for Favourites. **Next:** implement B; retire command-library marketing H1 and redundant dual search. **Stop:** do not reintroduce ModeHome or a separate Favourites home route. | session 2026-07-31 mode-page design audit; user Favourites hybrid decision | 2026-07-31 |
| #165 | P2 | task | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them | **Outcome:** the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. **Detail:** `/mockups/warning-consolidation` (PR #1437) diagnoses today's three stacked notices — the APP-5 privacy warning at 11px muted, a bare `/privacy` link, and an accent-blue `ShieldCheck` capability claim at 14px semibold — and shows the hierarchy is inverted: the least important line is the loudest, and two shields with opposite meanings sit ~40px apart. Three consolidations are drawn at 1440px and 390px. Recommended: **02 Safety card** on the hero (obligation on a warning-tinted top row, everything descriptive in one grey voice below) and **01 Assurance bar** on the docked composer — the same content model at two densities, so one component with a `density` prop covers both. **This is a governance change, not just a design one:** `PrivacyInputNotice` is the single site-wide APP-5 line and renders on the answer, documents and calculators composers, so all three move together; `tests/privacy-ui.test.ts`, `tests/ui-accessibility.spec.ts` and the phone-chrome reserve coverage all assert against the current markup and must change in the same commit; and the PR will need a full `## Clinical Governance Preflight` (the mockup PR correctly did not). **Third study (before/after):** `/mockups/answer-home-proposal` draws the concrete D-direction proposal as a full hero before/after rather than an isolated notice. **Second study (words only):** `/mockups/warning-line` answers a narrower brief — no icon, border, tint or background, one line where width allows. Six variants A-F; line counts measured from the rendered DOM, not asserted. Only B (middot clauses), D (obligation + verify) and F (compressed obligation) hold one line at desktop width, and **none fit one line on a 390px phone while the pinned APP-5 sentence stays verbatim** — 46 characters of obligation plus the 27-character link exceeds the ~60 available at 11px. Recommended there: **D**, the only compliant variant that is both one line and keeps weight-only hierarchy, reached by dropping the scope claim (a capability statement already visible on the answer itself). F fits best but rewrites the pinned obligation to \|No patient-identifiable information.\| and so needs the same privacy sign-off as `#166` plus a matching `tests/privacy-ui.test.ts` update. **Status:** PR #1437 was closed unmerged on 2026-07-30 as a deliberate pause during an owner-authorized ordered merge sweep, to be reopened at its queued place; branch `claude/warning-consolidation-mockups-09jyj7` is preserved and merged onto current `main`; these follow-up rows have been renumbered on each sync because `main` kept claiming the next ids while the PR was paused; the superseded numbers are deliberately not listed, since they now belong to unrelated rows. **Next:** decide block (02 + 01) versus line (D) direction, get wording sign-off for `#166`, then implement behind one component and run `verify:phone-chrome` before `verify:ui`. | session 2026-07-30; PR #1437; `/mockups/warning-consolidation`; `/mockups/warning-line` | 2026-07-30 |
| #168 | P2 | rec | Sequential issue ids force every concurrent append to conflict | DESIGNED 2026-08-14 in PR #1944 — docs/ledger-id-scheme-proposal.md. Design only, nothing implemented, so this row stays open. Recommends a ULID as the durable id with a short derived display form, the property that matters being that the display form is derived rather than stored: a clash there is a rendering fix (take one more character) rather than a renumber. UUIDv7 noted as an equally good fit. Records why timestamp-plus-slug and content hashes were rejected — the slug wants to change when a row is re-scoped, which is renumbering under another name, and a content hash is neither sortable nor stable. Migration is additive because the 314 existing sequential ids keep their numbers permanently: they are cited across the ledger, docs/branch-review-records/, AGENTS.md, the skills and the commit history, so renumbering would invalidate every citation while producing exactly the churn this row exists to end. Four steps, widening validators before allocation changes, with every current #NNN assumption enumerated by file and symbol (ledger-inbox.mjs validateRequest twice; check-outstanding-issues.mjs ID_CELL, the MARKER parse, the nextId-above-highest assertion and its padStart formatting; outstanding-issues.mjs allocator; issues-report.mjs and the issues-surface hook). Stop unchanged and now load-bearing on step ordering: do not reinstate merge=union while ids are sequential — it only becomes safe after the marker is gone. | session 2026-07-31; .gitattributes; #154/#155; PR #1524 sync | 2026-07-31 |
| #169 | P2 | issue | Machine-local branches, snapshots, worktrees, and dev servers remain at risk | **CONSOLIDATED 2026-08-13 from #152, #236, and #260 before those source rows are archived by PR #1920. Outcome:** every branch, snapshot, worktree, or process that exists on only one machine remains recoverable and receives an explicit owner disposition before machine or worktree cleanup. **Original unpushed branches:** `claude/clinical-kb-design-system-333a69` was verified to contain 57 files / +4069 at tip `feat(design-system): v2 token layer, 26 components, browser-crash fix`, including `.design-sync/previews/*.tsx` absent from main. Also inspect `design-sync-db0a54`, `fable-implementation-fc937c`, `frosty-mayer-2c6167`, and `issues-133-evidence`. **Preserved WIP snapshots from #152, all unpushed, unreviewed, and unverified:** `codex/reconcile-immediate-20260730` at `748ef018f` (21 files, +395/-200 across 19 tracked, including `.github/workflows/ci.yml`, `package.json`, and `docs/scripts-index.md`); `codex/document-results-mockup-20260730` at `5dbd9f965` (8 tracked files, +13/-3, plus an untracked `document-search-results/page.tsx` mockup); `codex/chat-ledger-triage-d344` at `b7eae51a4` (`docs/outstanding-issues.md` +59/-61); and `claude/section-spy-browser-coverage` at `d949859c3` (`tests/ui-smoke.spec.ts` +51). **Wave-5 inventory from #236:** content-compare `claude/ds-v2-builder-a` and `claude/ds-v2-builder-b` with current `origin/main` because squash merges make ancestry checks unreliable; retain the associated process evidence for ports 3258 (`Database-wt-ds-v2-capture`), 3135 (`Database-wt-ds-v2-correctness`), and 3672 (`Database-wt-ds-v2-empty-state-heading`) until the owner confirms each process is no longer needed. **Stranded Sentry work from #260:** on the originating Windows machine, inspect branch `claude/cloud-pr-loop-prevention-bc052b` commits `c3c9d6a31` and `abbcdc8e9` (~389 lines across `src/sentry.*.config.ts`, `src/lib/env.ts`, `src/lib/supabase/client.tsx`, and `src/components/ui-primitives.tsx`) plus the same four uncommitted files in `.claude/worktrees/pensive-borg-6be2f0`; content-compare them with remote branches `claude/sentry-nextjs-sdk-setup-2v24q5` and `cursor/sentry-nextjs-sdk-7cee`, then record whether the work is unique, remotely preserved, or proven superseded. **Verification rule:** do not use `git rev-list` counts, three-dot diff, or ancestry alone to declare squash-merged work represented; verify the branch-added files or content against current main. **Cloud-session stop:** fresh cloud containers cannot observe the originating machine's local branches, worktrees, or processes, so never close this row from a cloud inventory that reports them absent. **Next:** complete and record each disposition from the originating machine. **Stop:** retain every listed branch, snapshot, worktree, and process record until content proof and owner disposition exist. | sessions 2026-07-30/31 and 2026-08-04/07; #152/#169/#236/#260; PR #1920 review | 2026-07-31 |
@@ -181,7 +177,7 @@ removed after current-main verification; it is not missing recommended work.
| #242 | P2 | task | Commit approved Linux visual baselines and promote adoption not-committed → committed | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: Six linux/ PNGs are committed, but the adoption manifest still carries 68 `not-committed` entries — the surfaces flip is the remaining work, as stated. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. Baselines and provenance are DONE as of PR #1729 (branch claude/ds-adopt-visual-baselines): all six linux/ PNGs committed from ubuntu artifact visual-baseline-31251091603 (main @ bc33d414e), AWAITING_BASELINE emptied, and tests/__screenshots__/linux/provenance.json written with per-candidate SHA-256 + dimensions and an approved human review. Proven by that PR's own run: visual-junit tests=9 failures=0 skipped=0, and no visual-candidates/ directory, i.e. all six compared rather than skipped. REMAINING: only the surfaces flip to baseline.status committed. Blocked on ordering, measured 2026-08-08: validateLinuxVisualBaselineSet short-circuits on declaredPaths.length===0, so declaring files activates its rule that no non-allowlisted path may change since candidateSourceHead — and PR #1729 necessarily changed tests/design-system-adoption.test.ts, whose initialiseCandidateRepository seeded fixtures from the LIVE spec and so failed the moment AWAITING_BASELINE emptied. The two cannot land together. Next: after #1729 merges, re-capture candidates from a main run that already contains that fixture fix, then flip the surfaces against that head. Note this does not affect whether pixels compare — Playwright compares because the goldens exist on disk. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 |
| #248 | P2 | issue | Investigate why 20260705180000 search-health indexes were missing on live despite applied history | APPEND 2026-08-13: the prior closure is withdrawn. Repository and live-drift evidence establishes that 20260705180000_reconcile_search_health_indexes.sql is recorded as applied while documents_title_trgm_idx and document_chunks_content_trgm_idx are missing on live. Supabase transaction semantics exclude a persisted partial migration, but the present record does not distinguish skipped DDL/history repair from indexes created and later dropped. In an approved read-only window, query supabase_migrations.schema_migrations for the 20260705180000 statements fingerprint and inspect the relevant audit/history evidence; retain both hypotheses until that evidence establishes the cause. Separately, scheduled check:drift did detect the missing indexes, but red runs were not routed. | PR #1614 review / session 2026-08-05 (renumbered on main merge) | 2026-08-05 |
| #258 | P2 | rec | The PR-handoff stop rule is enforced for Claude Code only; Codex and Cursor get prose with no gate | GAP RECORDED 2026-08-14 in PR #1944 — docs/pr-handoff-stop-cross-agent-gap.md. This is the row's own stated fallback ("If no mechanism exists at all, record that explicitly here so the gap is a known limit rather than an open task"), so the row stays open but is no longer unexamined. Checked, not assumed: .claude/settings.json is read only by Claude Code; plugins/clinical-kb/.codex-plugin/plugin.json declares name/version/description/author/repository/keywords/skills and an interface block with NO hook, event, or pre-tool-interception field, shipping exactly one skill; .cursor/ holds settings.json (plugin enablement only), mcp.json, agents/ and skills/ with no deny path. So the cheapest-first option the row proposed is currently unavailable in both tools. Worth noting because it sharpens the cost: .cursor/agents/pr-babysit.md exists, meaning Cursor ships a documented agent for exactly the PR-following behaviour this rule restricts, with nothing bounding it. The doc records the Claude Code mechanism in enough detail to reimplement (session-scoped marker under the absolute git dir, fail-open on an unidentifiable session id, never pruning a sibling's marker, post-mode scanning only the request half so a command that merely prints a PR URL cannot arm it, and the CLAUDE_ALLOW_PR_FOLLOW=1 prefix unlock that a mention alone cannot trigger), plus the three questions any parity mechanism must answer. It is explicit that the wrapper fallback is advisory only — it cannot touch the MCP-connector or loop-machinery classes, so it makes a violation detectable after the fact rather than prevented. Next: re-check the Codex and Cursor manifests when either ships hook support; close only when a mechanism exists or the limit is accepted deliberately. Stop unchanged: do not weaken the Claude Code hook for symmetry, and do not keep a second copy of the deny list. | PR #1649; .claude/hooks/pr-handoff-stop.sh; .claude/settings.json; AGENTS.md "Stop when the pull request is open"; session 2026-08-07 | 2026-08-07 |
-| #265 | P2 | task | DS Track A6: move design-system gates 2, 4, 7 and 8 from partial to blocking | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: docs/design-system/GATES.md still carries 10 `implemented-partial` rows; gates 2, 7 and 8 remain unclosed. Gate 2 is blocked behind #293, whose finding 1 is refuted — see that row before attempting the enumeration. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. RE-MEASURED AND PART-CLOSED 2026-08-09 against origin/main 8db1e53937. GATE 4 CLOSED: colourOnlyStatusIndicators in check:design-system-contract is the repository-wide enumeration this row asked for - a status hue on a box with no children, no aria-label/aria-labelledby/title on it or any ancestor, no sibling text, and not a StatusMark. It also flags shared swatch recipes, because the analyzer is per-file and cannot follow an imported statusDotReady to its call sites. Ratcheted at 4 with per-path pins (the two bare statusDot recipes GATES.md named, a calculator risk band, a therapy meter fill); a new colour-only indicator anywhere in src now fails. Mutation-verified. GATE 2 NOT CLOSED, and this row's description of it was wrong in a way that cost a session. It is NOT true that test:e2e:style-contract needs wiring into verify:cheap: the npm script is only an alias for running that one spec, the spec matches productionSpecPattern in playwright.config.ts and is listed in scripts/playwright-pr-shards.mjs, so it ALREADY runs in the required Production UI job. It must NOT be added to verify:cheap:internal, because check:gate-manifest then demands a matching step in static-pr, which has no browser and no server. The real gap is the h-10 blind spot inside the audit itself, and an enumeration for it was written, shown to find genuine defects, and then reverted rather than landed because it is not deterministic on a live-search route - see #293 for the six-run evidence and the follow-up. REMAINING: gate 2's enumeration (needs a deterministic surface first, #293), gate 7 (elevation child/parent, needs a render-tree check, untouched), and gate 8's recorded debt only - its two checks already ship and ratchet per path, so that work is retiring 27 edge conflicts across 15 files and 2 globals.css spreads, then pinning both at zero. | session 2026-08-07 — design-system HANDOVER-2026-08-07 Track A1 handoff (PR #1678) | 2026-08-07 |
+| #265 | P2 | task | DS Track A6: move design-system gates 2, 4, 7 and 8 from partial to blocking | GATE 2 CLOSED 2026-08-15 by PR #1984; gates 7 and 8 remain, and gate 8 is materially larger than this row said. Corrections first, because two figures here were stale: the debt for gate 8 is 25 conflicts across 12 files, not 27 across 15 — PR #1942 paid three files down without updating the prose, and both GATES.md section 3 and scripts/design-system-contract-baseline.json already said 25, which outranks the prose per docs/design-system/README.md. GATES.md carries 9 implemented-partial rows, not 10; the tenth grep hit is the legend. And #293 is resolved: commit 885c613 (PR #1962) IS on main — PR #1977 is already open to close that row, so do not queue another. GATE 2, what closed it: the row named two gaps, the rendered enumeration and the fixed-height h-10 case. The first landed in #1962. The second is closed by a new interactiveTapFloorDeclarations metric in check:design-system-contract — an interactive element (a, button, input, select, summary, textarea) declaring its OWN unprefixed min-h-* below the 48px token, ratcheted at 43 across 17 files with per-path pins; comparable arbitrary lengths and mutually exclusive/composed branches are evaluated independently, so a new sub-floor control anywhere in src/** fails today. Mutation-verified: lowering one shortlist button to min-h-9 produced both the total and matching per-path regression. Scoped to min-h-* and NOT h-*/size-* on purpose: a short h-4 on an interactive element is routinely the visible box of a control whose hit area belongs to a tap-sized wrapper (SelectionCheckbox in differentials-home.tsx, whose label ui-smoke asserts still meets the floor), and flagging those would pad the baseline with non-defects — the exact section 5 failure mode. One remaining limit recorded rather than hidden: the walker sees intrinsic lowercase tags only, so a floor on or another component wrapper is invisible to it (the pre-existing legacyTapClasses check shares that blind spot). The 43 recorded sites are real debt still owed. Also tightened legacyShadowAliases 119 to its measured 118 — one unit of stale slack found while working #163. GATE 8 — STOPPED DELIBERATELY, and the reason matters for whoever picks it up. The 25 conflicts are not 25 mechanical one-line edits. Inspected every site: the large majority are focus:ring-4 focus indicators co-existing with a border on inputs, selects and textareas (master-search-header 5, formulation-builder 3, and singles across formulation-compare, dashboard-nav, favourites, specifiers, DocumentTagCloud, ui-primitives), and only pwa-lifecycle's shared cardClassName is the decorative persistent double edge the rule was written for — one recipe counted 5 times. Retiring the focus-ring class means restyling focus indicators from ring to outline across roughly 17 controls, which is an accessibility-visible change needing focus-state proof in a browser. Three further constraints: this container cannot run Chromium (ships chromium-1194 against a pinned 1234, #255/#312) so that proof was unavailable; 8 of the 25 sit in files that open PRs #1976, #1982 and #1983 are editing; and pinning at zero without first widening the onePixelShadowSpreads property filter (design-system-contract-utils.mjs:1320 matches --e[0-4] and --shadow-* but not --glow-primary, --glow-soft or --ring-hairline, all of which carry 0 0 0 1px) would be a partly false close. Next for gate 8: decide the focus-indicator question first as a design-system ruling (ring vs outline for focus), then widen the spread filter, then retire and pin — with browser focus proof. GATE 7 untouched, unchanged from this row's description: no child/parent elevation check exists and no shared render-tree traversal helper exists to build one on; every spec inlines its own page.evaluate walk. The template is #1962's determinism scaffolding plus a computed-box-shadow to --e0..--e4 tier lookup built with the probe technique at ui-style-contract.spec.ts:265-289. | session 2026-08-15; PR #1984; GATES.md; scripts/design-system-contract-baseline.json | 2026-08-07 |
| #266 | P3 | task | DS Track B1: adopt the 23 unadopted components demand-driven, never as a race to 53/53 | **DEPRIORITISED 2026-08-12 (yield review against current main).** Adoption counting toward 53/53 while a clinical P1 is open. The row's own title says never as a race to 53/53; the queue has been running the race anyway. Demand-driven means it activates when a surface needs a component, not on a schedule. COUNTS RE-MEASURED 2026-08-12 from docs/design-system/adoption-manifest.json on merged main: **54 registered, 31 adopted, 23 UNADOPTED**. (This supersedes the 2026-08-08 figures of 53/30/23, which a main-merge briefly restored over this correction.) The total held at 23 but the membership moved — DisclosureGroup joined the adopted set, and the newly built ErrorState joined the unadopted set; ErrorState's enforcement is closed (archived #298) but its adoption is still open under #299. Today's 23: AnswerFooter, Checkbox, Citation, CitationList, ConfirmDialog, Disclosure, DoseLine, DownloadLink, ErrorState, ErrorSummary, ExternalTextLink, FieldError, FieldHint, LinkAction, Pagination, Progress, RadioGroup, SearchField, StageList, Tabs, TextLink, ToastRegion, Tooltip. Approach unchanged and still correct: demand-driven adoption — pick a surface and let it pull, the way AccessibleTable pulled Button and the answer surface pulled AnswerCard (#216) — never a race to 54/54. Forms remain the largest single tranche: FieldError, FieldHint, ErrorSummary, SearchField, Checkbox and RadioGroup land together on one form conversion. Do not stub a component to move the count. Regenerate with npm run design-system:adoption:update AND npm run design-system:design-sync:update; both manifests are generated, never hand-edited. | session 2026-08-07 — design-system HANDOVER-2026-08-07 Track A1 handoff (PR #1678) | 2026-08-07 |
| #267 | P3 | task | DS Track B2: AnswerFooter and DoseLine need a provenance/dose payload the answer surface does not produce | **DEPRIORITISED 2026-08-12 (yield review against current main).** Blocked on a provenance/dose payload the answer surface does not emit, which is backend work nobody has scoped. Cannot start. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: Neither AnswerFooter nor DoseLine has a product importer; the provenance/dose payload the answer surface would need still does not exist. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. 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 | **DEPRIORITISED 2026-08-12 (yield review against current main).** 19 bare-dash sites with no reported clinical misreading. Cosmetic consistency on a prototype with an open P1. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: MissingValue is imported in 5 component files; the bare-dash conversion is partial. The ~5 calculator 'not started' sites stay permanently, per this row's own stop rule. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. 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 |
@@ -192,16 +188,15 @@ removed after current-main verification; it is not missing recommended work.
| #282 | P3 | task | Probe the corpus for JBIG2/JPX before deciding whether pdf.js needs its decoder assets shipped | **DEPRIORITISED 2026-08-12 (yield review against current main).** A probe to decide whether pdf.js decoder assets are needed. Worth doing eventually, but no reported rendering failure traces to JBIG2/JPX today, so it is speculative. **Outcome:** a measured decision about pdf.js's cMap/standard-font/WASM assets rather than an assumption either way. **Detail:** getDocument is configured with url plus the on-demand fetch flags and nothing else, so 'wasmUrl', 'standardFontDataUrl', 'cMapUrl' and 'iccUrl' are all unset. pdfjs-dist ships those assets (wasm 1.5 MB, standard_fonts 804 KB, cmaps 1.7 MB) and nothing copies them into public/. With wasmUrl null, 'useWorkerFetch' resolves false and the WASM image decoders cannot load, so JBIG2 and JPEG2000 images fall back to the JS decoders or fail; those are exactly the encodings a scanned guideline uses, and this repo runs an OCR pipeline, which implies scanned sources exist. Non-embedded standard-14 fonts fall back to system fonts, which is a fidelity risk on a clinical document rather than a failure. **Next:** sample the real corpus for JBIG2/JPX-encoded images and for PDFs relying on the standard 14 before shipping ~2 MB of static assets; if the corpus does use them, copy into public/pdfjs, set the URLs, and add immutable cache headers in next.config.ts (public/ is not counted by check:bundle-budget, so there is no budget risk — the cost is bytes over the wire on first use). **Stop:** do not ship the assets on the assumption alone. | session 2026-08-08 document-viewer optimisation; node_modules/pdfjs-dist/types/src/display/api.d.ts | 2026-08-08 |
| #283 | P3 | rec | The 100-id batch signed-URL route still has no caller | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: No caller for src/app/api/images/signed-urls/route.ts anywhere outside app/api — the batch route is still unused. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** either the batch minter is used or it is retired, rather than sitting as an untested, unreachable privileged surface. **Detail:** src/app/api/images/signed-urls/route.ts POSTs up to 100 image ids and returns their signed URLs, with its own rate limit, owner scoping and committed-generation filter. Nothing in src/ calls it — only tests/private-access-routes.test.ts imports it. **DEFERRED AGAIN, DELIBERATELY, 2026-08-09 (document viewer Phase 3, Task 3).** The user chose deferral over wiring when asked. Two reasons beyond cost: (a) wiring it puts a privileged owner-scoped API route into a diff that is otherwise confined to src/components/document-viewer/**, and it matches clinicalRiskPatterns (/^src\/app\/api\//) so pr-policy hard-blocks the merge without a complete Clinical Governance Preflight; (b) Phase 3 Task 2 windowed the rail to six rows and tightened its IntersectionObserver root margin from 640px to 240px, so the many-distinct-images case the batch route was meant to serve is now materially smaller — a page of N figures no longer mounts N rows at once. The batching win should be re-measured against the windowed rail before it is wired at all, rather than assumed from the pre-window numbers. **Next:** decide deliberately — measure concurrent distinct-image requests on a figure-heavy document with the windowed rail, then either wire the batch route in its own PR or delete it and its tests. **Stop:** if wiring it, keep the per-image endpoint for the lightbox's retry path; do not make the batch the only way to mint a URL. | session 2026-08-08 document-viewer optimisation; src/app/api/images/signed-urls/route.ts | 2026-08-08 |
| #292 | P2 | rec | Two assistants built the same queued conversion twice because neither workflow checks the open-PR list before starting | Recurred 2026-08-14 on the database remediation plan, this time with two assistants building Phase 0: PR #1938 and PR #1939 both implemented live-drift failure routing and the post-migration trigger, merged four hours apart. Both landed and no harm resulted — #1939 built on #1938's commit and improved it, moving the findings capture after the migration-history step so a migration-history failure is visible instead of a clean drift result being published as its explanation. The cost was still two full authoring sessions and two CI cycles for one deliverable. This matters more for the phases still ahead than it did here: Phase 1 consumes an approved read-only production window, and Phases 3 and 4 consume approved mutation windows and live eval-canary budget, so a duplicate there wastes an operator-gated resource rather than just tokens. Concrete ask for the remediation work specifically: check the open-PR list for the surface before starting any of Phases 1-5, per docs/database-remediation-playbook.md. | session 2026-08-09; PR #1766 (merged); PR #1767 (closed duplicate) | 2026-08-09 |
-| #293 | P2 | issue | Gate 2 needs a phone-viewport deterministic surface; the `min-h-tap` 0px finding is REFUTED | CORRECTION 2026-08-14: an earlier queued request for this row claimed `done`, citing PR #1962. That was premature -- PR #1962 is still open, not merged; tests/ui-style-contract.spec.ts on main does not yet carry the new test. Do not close this row until PR #1962 actually merges and the test is confirmed present on main. What PR #1962 contains, once it lands: Finding 1 (min-height override) reconfirmed already refuted/intentional, untouched -- no code change needed there. Finding 2 (nondeterministic enumeration): confirmed via git history it was written once but reverted rather than landed, so there was no code to fix, only a missing regression test to build. The new Playwright test runs on /forms's static home (avoids the original live-search route race), at a 390x844 phone viewport (avoids the sm: release that refuted Finding 1), polls until 3 consecutive enumeration reads agree instead of trusting networkidle, explicitly sorts the shape list, and repeats 3 full navigate-and-enumerate cycles inside the test asserting an exact match. Full spec file passed 10/10 in that session's own run -- but that proof is local to the branch until the PR merges. | session 2026-08-14 ledger correction; PR #1962 (open, not yet merged as of this correction) | 2026-08-09 |
| #299 | P3 | task | Adopt ErrorState at the three surfaces that genuinely hand-roll the failed-request guard | **DEPRIORITISED 2026-08-12 (yield review against current main).** Three surfaces hand-roll a guard that works. Converting them is consistency, not a fix. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: ErrorState has no product importer beyond src/components/ui/error-state.tsx, so the three hand-rolled surfaces are still unconverted. (Its ENFORCEMENT is closed — see archived #298.) This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. Three surfaces hand-roll the guard and their comments state the rule outright: src/components/clinical-dashboard/search-results-header-band.tsx:210 ('no number may reach the DOM'), src/components/services/services-navigator-page.tsx:634 ('a blocked registry must not reach the band as 0 matches'), src/components/clinical-dashboard/favourites-command-library-page.tsx:1182. They are CORRECT today, just not shared, so this is convergence rather than a bug fix. The band's fault panel is the richest existing implementation (role=alert, warning tokens, AsyncButton retry with busy state, faultAction slot) and ErrorState was modelled on it, so the shapes already line up. Live-look change: own PR, Chromium pass. Per the M4 brief it sits DOWNSTREAM of design decisions the owner has not made, so doing it before the site-wide redesign risks redoing it. Do NOT bundle with the enforcement check. Stop: only these three - see the sibling row for three sites that were miscarried as guards. | session 2026-08-09 M4 - ErrorState build | 2026-08-09 |
| #305 | P3 | rec | Canary has no latency-mode coverage and its cost readout is a known lower bound | Two informational gaps from the 2026-08-12 canary review, deferred by scope decision. (1) eval:retrieval:latency (p90 20s gate) is never wired into eval-canary.yml, so live retrieval latency regressions are invisible to the weekly canary while the answer step relaxes its own gates via EVAL_LATENCY_CONTEXT=cross-region-runner. (2) estimated_cost_usd applies one rate set (gpt-5.6-terra) to all usage including 2x-priced strong-model retries, so any cost trend understates strong-retry runs — the workflow comments say so, but eval:trend consumers may not read them. Also noted: the workflow-wide concurrency group (eval-canary, cancel-in-progress false) can queue a dispatched pair run behind a scheduled run, interleaving pair evidence; and fixture coverage gaps tracked in #018 remain uncatchable by the canary. Next: decide whether a monthly latency-mode dispatch is worth the spend; add a strong-usage split to the estimator if cost trends start driving decisions. | session 2026-08-12 RAG canary review | 2026-08-12 |
| #308 | P3 | issue | Desktop /documents/search CLS is 0.119, above threshold and stable across runs and baselines | Measured 2026-08-12 during the #147 close-out, twice, on the offline Lighthouse harness (Chromium 141): desktop /documents/search CLS **0.119**, against a committed baseline that also reads **0.119**. So this is long-standing and deterministic, not a regression — and it is above the 0.1 threshold. It sits outside #147's scope, which was mobile only, and it contradicts that row's claim that 'desktop passes everywhere: 0.016-0.097' — that range is stale. Companion desktop values from the same runs, all passing: /dsm 0.014, /forms 0.059-0.064, / 0.006, /therapy-compass 0.000. Desktop attribution completed 2026-08-14: a Playwright + PerformanceObserver(layout-shift) harness against an offline production build at 1350x940 DPR 1 recorded **0.118** CLS. This is a separate attribution measurement, not a replacement for the canonical 0.119 Lighthouse value. One first-paint+~0.3-0.5s event contributed ~99.98% of that harness total: MasterSearchHeader's composer-adoption effect portals the search composer into GlobalSearchShell's desktop slot, while the header shrinks 184px and the slot grows 0 -> 184px. This is shared desktop search-chrome timing, not page-local. Next: reserve the settled height at the adoption boundary under the one-composer/hidden-means-zero-reserve contracts, then re-measure with the same harness. Stop: do not raise the CLS budget; do not read local LCP or TBT from the loopback harness; and do not use a blanket min-height that hides the shift without matching the header reserve. | Local offline verify:lighthouse runs 2026-08-12 (two runs, identical CLS); #147 close-out; lighthouse-budget.json. Attribution: session 2026-08-14, PR branch codex/visual-layout-polish; desktop CLS script adapted from scripts/measure-cls-attribution.mjs (offline, not committed). | 2026-08-12 |
| #309 | P2 | task | Facet groups of 6-20 options render as chips, not the dense list docs/filter-contract.md section 5 requires | Attempted 2026-08-14: an implementation task for chips-for-6-20 was stopped before any code was written, because it directly contradicts this row's own current, still-open text, which requires a full-width DENSE LIST (right-aligned count column, group headings) for the 6-20 band, and explicitly says chips-for-6-20 does not satisfy this row. Confirmed chips-for-6-20 is ALREADY the live behaviour (dense = facetGroups.length > 3 \|\| totalFacetOptions > 20 in result-filter-control.tsx), and that closing this row on that basis was already tried once and explicitly reverted (PR #1925, 'correct #309 to partially delivered'). No code changed, no PR opened. Needs a product/design decision between: (1) build the genuine full-width dense-list renderer plus the nine-option DOM assertion this row asks for, or (2) formally amend docs/filter-contract.md section 5 to deliberately drop the middle band with reviewer sign-off -- different from what already happened (a silent merge-conflict resolution the row says didn't count). | session 2026-08-14, agent stop per contract contradiction | 2026-08-12 |
| #311 | P3 | task | Promote the derived ledger loss-detector into scripts/ — it has now earned its place twice | During the 2026-08-12 sweep, two main-merges silently reverted edits to `docs/outstanding-issues.md`, including the ENTIRE #293 refutation (a `grep sm:min-h-0` returned 0; the text survived only in commit a6bfc6f). It went unnoticed because the recovery script was HAND-ENUMERATED — it listed 15 archives and 8 updates from one commit and could therefore only restore what the author remembered. The replacement is derived rather than listed: read every row id this branch has ever stamped out of `git rev-list ..HEAD` plus `git show :docs/outstanding-issues.md`, then assert each of those ids that is still OPEN carries its stamp text, and exit non-zero listing any that lost it. It has now proved itself twice — it caught the intentional #262 divergence (main's version was newer than the branch's, correctly left alone) and would have caught the #293 loss the hand-written list missed. The plan that created it said it should stay a scratch script 'unless it proves useful more than once'; that condition is met. Next: port it to scripts/ (suggested `check-ledger-stamp-retention.mjs`), generalise the stamp token from the hard-coded 2026-08-12 date to a `--since` or marker argument, add a self-test in the style of the other ledger scripts, and document it beside `ledger:dedupe` for use after any main sync that touches the ledger. Stop: do NOT wire it into verify:cheap or CI — it is a branch-local safety net for a human or agent mid-sweep, and it has no meaning on a branch that has not stamped rows. Related: #156 and #168, which track the id-allocation race that produces these merges in the first place. | session 2026-08-12 ledger sweep; scratch loss-check.mjs; #293 restoration from a6bfc6f | 2026-08-12 |
-| #312 | P3 | issue | check:playwright-browser-revision reporting OK does NOT mean browsers are installed — and installing the matching revision is a cheap first option | Two corrections learned the expensive way on 2026-08-12, both about browser proof in a cloud container. (1) **The check is easy to misread.** `npm run check:playwright-browser-revision` returned 'Playwright browser revision check OK (managed-or-unconstrained): No designated container browser root is forced; use the Playwright-managed cache or install matching browsers.' That reports that no browser root is FORCED — it does not assert any browser exists. It was read as a green light for `verify:ui`, and two subsequent Playwright runs died at preflight instead: the container carried chromium-1194 while Playwright 1.62.1 requires chromium_headless_shell-1234, with firefox-1538 and webkit-2336 absent entirely. Suggested fix: have the check say plainly which browsers are present and which the locked Playwright version requires, so 'OK' cannot be mistaken for 'ready'. (2) **Installing the matching revision works and is fast**, which archived #255's 'delegate browser proof to CI Production UI' guidance does not mention. `npx playwright install chromium` fetched 114.7 MiB in about a minute and made local Chromium proof possible — three full ui-smoke runs then completed at 2.8-3.0m each (this is how #290 was settled). It is a cheaper first option than deferring to CI. Two things that matter alongside it: `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` was EMPTY in this container, so the download was never blocked despite the environment note implying otherwise; and only Chromium is needed, because `scripts/playwright-browser-preflight.mjs:127-152` honours `--project`, so `--project=chromium` skips the firefox/webkit requirement rather than forcing two unused ~100MB downloads. Stop: do NOT set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH at the stale 1194 binary to get past the preflight — archived #255 warns against forcing a mismatched path, and the preflight's own message warns that a later 'N failed' summary must not then be read as a product regression. | session 2026-08-12; scripts/playwright-browser-preflight.mjs:127-152; scripts/run-playwright.mjs:50-53; #290 close-out; archived #255 | 2026-08-12 |
+| #312 | P3 | issue | check:playwright-browser-revision reporting OK does NOT mean browsers are installed — and installing the matching revision is a cheap first option | Progress 2026-08-15: PR #1965 landed on main (commit 3ec6116) and closes the false-OK gap for the pinned Chromium revision by resolving the effective cache and requiring a launchable binary. Keep this issue open: the unscoped test:e2e and release matrix also require Firefox and WebKit, and the check does not yet report whether their locked revisions are installed. Next: enumerate required and installed revisions for all browser families, with a Chromium-only-cache regression; project-scoped --project=chromium runs may continue to require Chromium alone. | session 2026-08-12; scripts/playwright-browser-preflight.mjs:127-152; scripts/run-playwright.mjs:50-53; #290 close-out; archived #255 | 2026-08-12 |
| #314 | P2 | issue | Ship compact compressed registry projections and verify live transfer | Next: land the existing view=summary/search and gzip implementation, deploy it, then verify /api/registry/records on the exact deployment SHA returns counts-only home responses and compressed compact search responses. Why: the live full payloads measured on 2026-08-13 were 482786 bytes for Forms and 1096689 bytes for Services and were downloaded by count/search-only consumers without Content-Encoding. The local projections reduce raw search data by about 91.3% and 82.0%, with gzip responses about 4.9 KB and 27.3 KB. Context: latency and Sentry review. Owner: assistant. Confidence: high. Depends on: #013 and #016. Gate: focused registry/consumer tests, production build and bundle budget, then post-deploy headers/bytes and live LCP rerun. Stop: do not close from local-only payload measurements or deploy without explicit authorization. | session 2026-08-13 latency review; src/app/api/registry/records/route.ts | 2026-08-13 |
| #315 | P3 | rec | If the ui-smoke scroll-hide flake (archived #290) recurs, start from the reporter-stranding mechanism — and treat the old regression window as unconfirmed | Independent verification on 2026-08-13 (second session, fresh cloud container, pinned Chromium 1234 installed per #312) measured the archived #290 flake at BOTH ends of its recorded window and corrects the archive's causal story: the bad SHA 9ab3b73ad itself passed 16 recorded executions — reproducer isolated --repeat-each=5 (5 passed, ~1.0s each), one full tests/ui-smoke.spec.ts --project=chromium run (98 tests passed, 2.5m, 0 flaky), and reproducer x10 under deliberate CPU contention (6 busy-loop processes on 4 cores, run times 1.2-1.5s: 10 passed). Current main a76f280 also 5/5. So the recovery was NOT drift — the exact commit that measured 2/5-3/5 failures passes cleanly here — and the e8adde1b9..9ab3b73a window is unconfirmed; the failure was specific to the original machine's environment/load profile. Recorded as a comment on PR #1884 (issuecomment-5272932999). On recurrence, do not re-bisect first: test the stranding mechanism. computeScrollHideUpdate (src/components/clinical-dashboard/use-hide-on-scroll.ts) re-evaluates only on scroll/resize events, and its viewportHeightChanged / maxOffset-range-change guards deliberately zero accumulated down-travel (contract-asserted in tests/use-hide-on-scroll.test.ts) — so geometry churn consuming the final steps of a gesture strands the not-hidden state permanently until the next event, matching the recorded ~11.5s toHaveAttribute timeout signature (the assertion DOES auto-retry for 10s; the attribute genuinely never flips). Fastest confirmation: a diagnostic page.on('console') trace logging which guard fires per evaluation. The window itself was one PR (#1744 mode-routing, true merge a503c22) whose net diff touched no scroll-hide code — content-bisect axes, if ever needed: tests/ vs src/ split, use-home-mode-seed/use-last-app-mode neutralized, prefetchModeDestination reverted, positional heading click restored to a settle wait. Stop: any guard change is a behaviour change to protected phone chrome — needs a failing trace first, never speculatively; do not weaken the assertion or tap targets. | session 2026-08-13; PR #1884 comment; archived #290; #312 | 2026-08-13 |
-| #316 | P1 | issue | Live DB is missing 21 repo-defined indexes and 10 retrieval RPC bodies diverge; weekly live-drift has been red since 2026-07-26 with no routing | Combined 2026-08-14 update, superseding the two partial requests cancelled in this same batch. PHASE 0 CLOSED including the forced-dispatch proof its definition of done required: live-drift dispatched on main (Actions run 31813064485) failed at the drift step, the always() capture step still ran, the migration-history step correctly skipped, and the separate drift-routing job then created issue #1963 "Live drift check failing" carrying the label, run URL, job result, trigger and the full findings block. Routing is now also covered offline by tests/live-drift-workflow.test.ts, mutation-verified. INCIDENT REPAIR, owner-approved in-session: the two retrieval-critical indexes documents_title_trgm_idx and document_chunks_content_trgm_idx were restored with CREATE INDEX CONCURRENTLY plus ANALYZE, both indisvalid and indisready at 648 kB and 68 MB, re-verified afterwards by an independent read-only query. Before and after supabase_rpc_latency_ms 31610 to 1535 on the text fast path and 8519 hybrid, with match_document_chunks_text_v2 at 14 ms. No repo schema change was needed because the definitions were already codified. CORRECTED FIGURES measured 2026-08-14, superseding the 2026-08-09 numbers this row was opened with: 10 match_* def_hash mismatches (unchanged), 20 missing_live indexes rather than 21, and the same 2 unexpected_live. ATTRIBUTION STILL OPEN: migration 20260705180000 recorded 14 executed statements so it was not mark-applied, and the 20260804110240 guard validates four other indexes and never checks this pair, so it gives no existence bound for 2026-08-04. The drop window is therefore 2026-07-05 to 2026-08-02 and the dashboard audit-history pairing remains owner action; #248 stays open. NEXT: Phase 3 RPC reconciliation before Phase 4, per the plan's ordering that the change which can alter clinical answers precedes the ones that only speed them up. Evidence: docs/audit/live-drift-forensics-2026-08.md. | session 2026-08-14 live incident (owner-authorized Supabase connector) | 2026-08-13 |
+| #316 | P1 | issue | Live DB has 20 currently missing repo-defined indexes and 10 retrieval RPC bodies diverge; weekly live-drift has been red since 2026-07-26 with no routing | Combined 2026-08-14 update, superseding the two partial requests cancelled in this same batch. PHASE 0 CLOSED including the forced-dispatch proof its definition of done required: live-drift dispatched on main (Actions run 31813064485) failed at the drift step, the always() capture step still ran, the migration-history step correctly skipped, and the separate drift-routing job then created issue #1963 "Live drift check failing" carrying the label, run URL, job result, trigger and the full findings block. Routing is now also covered offline by tests/live-drift-workflow.test.ts, mutation-verified. INCIDENT REPAIR, owner-approved in-session: the two retrieval-critical indexes documents_title_trgm_idx and document_chunks_content_trgm_idx were restored with CREATE INDEX CONCURRENTLY plus ANALYZE, both indisvalid and indisready at 648 kB and 68 MB, re-verified afterwards by an independent read-only query. Before and after supabase_rpc_latency_ms 31610 to 1535 on the text fast path and 8519 hybrid, with match_document_chunks_text_v2 at 14 ms. No repo schema change was needed because the definitions were already codified. CORRECTED FIGURES measured 2026-08-14, superseding the 2026-08-09 numbers this row was opened with: 10 match_* def_hash mismatches (unchanged), 20 missing_live indexes rather than 21, and the same 2 unexpected_live. ATTRIBUTION STILL OPEN: migration 20260705180000 recorded 14 executed statements so it was not mark-applied, and the 20260804110240 guard validates four other indexes and never checks this pair, so it gives no existence bound for 2026-08-04. The drop window is therefore 2026-07-05 to 2026-08-02 and the dashboard audit-history pairing remains owner action; #248 stays open. NEXT: Phase 3 RPC reconciliation before Phase 4, per the plan's ordering that the change which can alter clinical answers precedes the ones that only speed them up. Evidence: docs/audit/live-drift-forensics-2026-08.md. | PR #1968 review against docs/audit/live-drift-forensics-2026-08.md, 2026-08-15 | 2026-08-13 |
| #317 | P2 | task | Verify registry-backed service records preserve facet metadata | #1878 introduced the services filter-contract tree and #1882 later merged the identical tree, so no merge-conflict audit is required. Current main uses ServiceRecord.catalogPayload.tags and fixture coverage verifies 219 records. Add focused offline tests that recordToRow and rowToServiceRecord preserve all six tag dimensions and degrade safely when payloads are malformed or absent. Do not add a second facets carrier unless a failing test proves the current contract inadequate. | PR #1921 review; #1878/#1882 tree comparison; service-facets.ts; registry-records.ts | 2026-08-13 |
| #318 | P1 | task | The medication interaction lexicon has never been clinically reviewed and its sign-off block is empty | docs/medication-interaction-lexicon-review.md is generated by npm run medications:lexicon-report and expands every lexicon term to the catalogue drugs it resolves to, with how many CRITICAL/HIGH rows depend on it, sorted by severe usage. It is marked UNREVIEWED and its sign-off table is unfilled, so every red and amber drug-drug interaction alert is currently an unvalidated mapping over source-backed text. The wording shown to a clinician is always verbatim catalogue prose; what is unreviewed is which drugs a phrase like 'NSAIDs' or 'CNS depressants' was taken to mean. The sheet has already produced three defects on generation alone (ARB matching Carbapenem across 16 CRITICAL/HIGH rows; two divergent Warfarin records; lithium unreachable from eight HIGH rows), which is a fair indication of what reading it would still find. Next: a clinician reads the term table top-down (it is sorted so the top ten terms carry most of the severe usage) and fills in the sign-off block. Stop: do not treat check:medication-lexicon-report passing as review - that check only proves the sheet describes the current lexicon, not that the mappings are correct. | PR #1923; docs/medication-interaction-lexicon-review.md; docs/samd-classification-medication-considerations.md | 2026-08-13 |
| #320 | P3 | task | Crop-to-page overlay remains unbuilt; bbox already reaches viewer state at runtime but is untyped, unvalidated, and unused | **Outcome:** selecting an indexed table or diagram can highlight its region on the PDF page, or the capability is deliberately retired — either way it stops living only in a plan document. **Detail:** this is the one Phase 3 capability never built (docs/plans/document-viewer-redesign-plan.md, Phase 3 table, 'Out of scope'). It had no ledger row until now, which is how work disappears between sessions: the plan doc marks it out of scope and nothing in durable memory says it remains owed. **The data path is partially live, not dropped.** src/lib/document-detail.ts SELECTs bbox alongside the other image columns, and withImageTableMetadata spreads every selected field except metadata. bbox therefore survives the runtime response and reaches DocumentViewer's image state. The gap is static and behavioural: DocumentDetailImage in src/lib/document-detail-contract.ts does not declare bbox, ImageRow in src/components/document-viewer/types.ts aliases that contract, no normalisation validates the stored value, and no viewer code renders it. Verified against exact PR head 2ac0f48a820be62947112efbb5d0845a702dad8e on 2026-08-13. **Shape of the work, in order:** (1) establish the ingestion coordinate space and stored shape, add a normalised bbox field to DocumentDetailImage, and add a focused loader or route-serialization test proving bbox survives with the promised shape. Do not change the selected-field mapping unless that test demonstrates an actual loss. (2) Only then draw the highlight over the rendered page when a figure is selected, accounting for the virtualized page column, the per-page raster scale from resolveViewportScale, and rotation. **Why it was scoped out rather than overlooked:** the contract and normalisation work has a wider blast radius than the component-only Phase 3 diff, and crop geometry quality from ingestion is separate debt — the redesign plan's residual-risk section says not to block viewer UX on perfect crops. **Stop:** do not land the typed-contract and normalisation half inside a viewer-only PR; it changes what the document-detail API promises and needs its own review and governance preflight. Do not render raw, unvalidated bbox values — a highlight over the wrong region of a clinical source is worse than no highlight. | session 2026-08-13 document-viewer remaining-work inventory; docs/plans/document-viewer-redesign-plan.md Phase 3 table; src/lib/document-detail.ts bbox projection | 2026-08-13 |
@@ -221,6 +216,11 @@ removed after current-main verification; it is not missing recommended work.
| #336 | P3 | rec | Decide whether responsive breakpoint windows get named tokens, or stay raw min-[]/max-[] everywhere | Split out of #275 rather than guessed at. The repo defines ZERO --breakpoint-* tokens, and at least nine sites hand-write the arbitrary form: min-[414px]:max-[429px] at clinical-dashboard/result-filter-control.tsx:231, plus max-[359px] (search-heading-mockups, differentials/diagnosis-map-panel.tsx:1036, clinical-dashboard/account-setup-dialog.tsx:98) and max-[389px] (factsheets/factsheets-search-page.tsx:176, clinical-dashboard/search-results-header-band.tsx:532, factsheets-compact-view-mockups). #275 asked for the 414-429 window to be tokenised alongside the badge box; that was deliberately NOT done, because naming one window while eight peers stay raw reintroduces exactly the one-call-site drift #275 exists to stop, just on a different axis. This is a real decision with two defensible answers and it should be made once, for all of them. (a) Stay raw and say so in docs/design-system/GATES.md: the values are per-device band edges carrying measured justifications in their own comments, they are not a scale, and a Tailwind 4 --breakpoint-* entry adds BOTH the min and max variant to every utility in the build for a single consumer. (b) Name them: Tailwind 4 --breakpoint- generates : and max-:, so the 414-429 window needs two entries (414px and 430px, since max-[429px] is inclusive and max- is exclusive), and 359/389 would want their own. Note the mockup hits are design scratch and out of scope for any gate. Next: pick (a) or (b), record it in GATES.md section 3 so the next session does not re-derive it, and only then migrate. Stop: do not migrate one window ahead of the decision. | session 2026-08-14; split from #275 during the design-token relands PR | 2026-08-14 |
| #337 | P3 | rec | npm run format in an uninstalled worktree runs a different Prettier than the lockfile pins and manufactures false drift | MEASURED 2026-08-14 in a Claude-on-web container during PR #1943, by running the commands rather than reasoning about them. The repo pins prettier ^3.9.6 in package.json with 3.9.6 in package-lock.json, but the container had no node_modules, so 'npm run format' (prettier --write .) resolved Prettier through npx and got 3.8.1. The older Prettier disagreed with files that are correctly formatted under the pinned version and REWROTE 31 files nobody had touched, including src/lib/rag/rag-cache.ts, src/lib/rag/rag-provider.ts, src/lib/openai.ts, src/lib/types.ts, tests/route-reachability.test.ts and several docs. Committing that output would have turned a docs-only PR into one classifyPullRequestFiles scores as ragRanking and clinicalRisk, pulling in a Clinical Governance Preflight and a RAG impact line for changes that were pure formatting noise, and would have collided with four sibling sessions working the same tree. Proof it was an artifact and not real drift: 'npx prettier@3.9.6 --check' on the same files returns 'All matched files use Prettier code style!' -- main is clean. This is the same failure class as archived row #087 (never act on a knip finding from a worktree that has not been installed) but strictly worse, because knip only reports while format WRITES, and the false result arrives already applied to the working tree. Next: make the version explicit rather than incidental -- either pin the binary in the format and format:changed scripts, or fail closed when the resolved Prettier version does not match the lockfile, so the command cannot silently run the wrong one. A pre-push guard already reconstructs an exact-lock environment for this reason (scripts/guard-push.mjs), so the precedent for refusing to trust an unpinned local Prettier exists. Stop: do not commit the output of npm run format from a worktree that has not been installed, and do not conclude formatting drift exists on main without re-checking under the pinned version. | session 2026-08-14 PR #1943; package.json ^3.9.6; package-lock.json 3.9.6; npx prettier --version 3.8.1 vs npx prettier@3.9.6 | 2026-08-14 |
| #338 | P3 | issue | The visual ISSUES-LIST.html register cannot be refreshed from any non-Windows session, so it drifts silently as work moves to cloud sessions | **Outcome:** either the rendered register is refreshable from any session that can reconcile, or it is retired and the Markdown ledger is the only artifact. **Detail, observed 2026-08-14 during the reconciliation in PR #1956.** `.claude/skills/issues/SKILL.md` refreshes the register by invoking `refresh-issues-list.ps1` under the operator's Windows `.codex\scripts` directory and writing `ISSUES-LIST.html` into their OneDrive folder — both absolute Windows paths. A Linux, container, or Codex/Claude Cloud session can run `npm run issues:reconcile` perfectly well (it did: 35 requests, write-discipline verified) but cannot run the refresh and cannot even check how stale the artifact is. The skill already handles this correctly for a single run — it says a stale visual artifact must not invalidate a valid canonical transaction, which is the right call — so this is not a correctness bug. The problem is cumulative: every cloud reconciliation widens the gap, and nothing measures it, so a reader opening the HTML has no way to tell whether it is an hour or a month behind. **Why it is P3 and not higher:** `docs/outstanding-issues.md` is the canonical rendered source and is always current; only the convenience artifact drifts. **Next, cheapest first:** decide whether the register is still wanted. If yes, the smallest fix is a stamp rather than a port — have the refresh write the reconciliation commit SHA into the HTML so staleness is visible at a glance, and have reconcile print a reminder naming the commit that needs it. A full cross-platform port (a Node renderer under `scripts/`) is the larger option and probably only worth it if the register is load-bearing for someone. If nobody reads it, retiring it and deleting that skill section is cheaper than either. **Stop:** do not improvise a substitute renderer or hand-write the HTML from a cloud session — an artifact that looks refreshed but was produced by a different generator is worse than one that is visibly stale. | PR #1956 reconciliation; .claude/skills/issues/SKILL.md refresh section; session 2026-08-14 | 2026-08-14 |
+| #339 | P2 | task | Favourites Continue and Recent are driven by hard-coded demo timestamps; real saved items have no last-opened data | Surfaced while shipping #164 (PR #1983), which made both surfaces prominent. src/components/clinical-dashboard/favourites-command-library-page.tsx derives 'most recently used' from lastUsedScore(item.lastUsed), and item.lastUsed comes from lastUsedByItemId — a hard-coded five-entry literal keyed to demo slugs ('Today 08:44', 'Yesterday 16:12', ...). Anything else, including every real registry favourite, falls back to the literal string 'Saved', which lastUsedScore buckets at 1000. pinnedItemIds is likewise a hard-coded two-item Set. The consequence after #164: for a signed-in user with real favourites, the Continue card and the Recent panel are effectively arbitrary — every item ties at the same score and the order is whatever the source array happened to be. Note that recentQueries in the shell is search-query history, not viewed-item history, so it cannot back this. Next: add a per-favourite last-opened timestamp. Cheapest is a client-side recents store keyed by favourite id written on open; the durable version is a column on the account favourites record so it survives a device change, which is a schema plus /api/account/favourites change and needs the usual migration review. Either way, pinning should stop being a hard-coded id set. Stop: do not fabricate a timestamp at render time from anything other than a recorded open event — an invented 'last used' on a clinical reference list is worse than an honest absence. | session 2026-08-15; PR #1983; favourites-command-library-page.tsx lastUsedByItemId/pinnedItemIds | 2026-08-15 |
+| #340 | P3 | rec | The mode-page comps and the results-band weighting contract disagree about query vs count emphasis | Found while shipping #163 (PR #1982). The perfected-combined comps draw the search query large and bold with the match count small and muted beside it, on both /tools and /services. Production does the reverse: SearchResultsHeaderBand renders the count first at font-weight 600 with the query at 450 and muted, and that is not an accident — docs/search-chrome-behaviour.md 'Results band' rules 1 and 2 argue for it explicitly (the query is the sole heading and the count is never one; nothing in the band is bold; the two weights are deliberately near-adjacent steps of one scale separated by tabular numerals and a hairline rather than by shouting). The band is shared by twelve modes and is a visual-baseline target captured from /services?q=CMHT&run=1 (tests/ui-visual-baseline.spec.ts), so changing it is a repo-wide change with a baseline refresh, not a per-mode tweak. #163 was closed without touching it because the outcome that row asked for — query-as-H1 rather than a match-count heading — is already true either way. Next: decide deliberately which artefact is authoritative. If the comps win, the change is a shared-band edit plus a rewrite of Results band rules 1-2 plus refreshed search-results-band and search-results-band-phone baselines, and it should be one PR covering all twelve modes. If the contract wins, the comps should carry a note so the next implementer does not re-open this. Stop: do not add a per-mode variant prop to make services alone read query-dominant — that makes shared chrome mode-conditional to settle a question that has one answer. | session 2026-08-15; PR #1982; docs/search-chrome-behaviour.md Results band rules 1-2 | 2026-08-15 |
+| #341 | P2 | task | Route the remaining ~22 unguarded source-slice test windows through the guarded helper | PARTIALLY DONE 2026-08-15 by PR #1985, which added tests/helpers/source-contract.ts and migrated the three worst files. The hazard this closes is a silent pass, not fragility: the idiom source.slice(source.indexOf(start), source.indexOf(end)) returns -1 for a missing end marker, and slice(n, -1) does not throw — it returns the rest of the file bar one character. A renamed end marker therefore converts a scoped assertion into a whole-file assertion and every positive toContain in it keeps passing for the wrong reason. The mirror case, a missing start, yields slice(-1, n) so every negative assertion passes vacuously. Neither shows up as a failure. The helper throws on a missing start, a missing end, and an AMBIGUOUS start (a window anchored on a string that appears twice silently covers only the first hit). MIGRATED: search-route-ownership.test.ts (three windows, including one whose end marker is an indentation depth and one anchored on a comment string), document-detail-performance.test.ts (end marker was the next literal 'useEffect' token, of which DocumentViewer has several), therapy-compass-responsive-contract.test.ts. REMAINING, roughly 22 windows across ~11 files, none migrated: audit-navigation-auth-regressions.test.ts is the densest and was deliberately skipped because PR #1983 edits the same file and the anti-churn rule prefers one late sync to a merge fight — do it once #1983 lands. Also tools-search-directions-mockups.test.ts, in-page-nav-playwright-contract.test.ts and document-section-nav-contract.test.ts (the last two slice ui-smoke.spec.ts between Playwright test titles, so renaming OR reordering an unrelated spec silently rescopes them), and rag-retrieval-parallelism.test.ts, which is left for a session that flags the RAG surface first per AGENTS.md. A REAL COVERAGE HOLE was found while surveying and is NOT yet fixed: audit-navigation-auth-regressions.test.ts around line 285 anchors on '{showUniversalAlsoMatches &&', which occurs TWICE in ClinicalDashboard.tsx (the second around line 3832 sits outside the window), so its not.toContain check does not enforce the named contract across the file. The new helper would reject that anchor outright, which is how it was found. Fix it in the same pass as that file's migration. STOP: do not loosen the demo-data boundary pins in favourites-demo-boundary.test.ts. The exact conditional-spread form '...(demoMode ? prototypeFavouriteItems : [])' with its paired negative is the live-vs-demo privacy contract, and its strictness is the point. Likewise leave the SQL windows ending on '$$;' and header-scroll-hide-contract.test.ts anchoring on the matching '' closing tag — those are true structural terminators and are the model the rest should move toward. | session 2026-08-15; PR #1985; tests/helpers/source-contract.ts | 2026-08-15 |
+| #342 | P2 | issue | Recurring 'Unhandled server request error' on /api/search and /api/search/universal is untriaged | Three Sentry issue groups in clinibase-xz over 24h (JAVASCRIPT-NEXTJS-Y, -Z, -10), 17 events, 0 users impacted, all titled 'Error: Unhandled server request error' with culprit chunk 1261.js:2:4801. Top frames are /api/search/route.js and /api/search/universal/route.js. First seen 2026-08-14T08:44:37Z on release c9b089c92c975297c10649b005401d5ae337cf48, roughly six hours BEFORE PR #1946 merged, so it is not caused by the retrieval row contract; the post-merge group is the same error refingerprinted by the release change. The error string does not appear anywhere in repo source, so it likely originates in a dependency or an instrumentation wrapper — origin unidentified. Nobody owns this. Next step: identify what throws it, then decide whether it is a bot/scanner artefact or a real request-handling gap. | Sentry clinibase-xz, reviewed 2026-08-15 | 2026-08-15 |
+| #343 | P3 | task | Make the retrieval row contract's source_metadata pin structural, not data-guaranteed | rag-row-contracts.ts pins source_metadata to a JSON object via z.record(...), but documents.metadata is bare jsonb and permits arrays and scalars. Measured against the live project (sjrfecxgysukkwxsowpy) on 2026-08-15: all 2851 documents are object-typed, so nothing breaks today and no live errors exist. The guarantee is data, not schema — a future ingest path could violate it and take retrieval down for that document's chunks. Fix is either a check (jsonb_typeof(metadata) = 'object') constraint on public.documents, or loosening the pin. Every other required field in that contract is backed by a not-null constraint. | PR #1946 review + live Supabase verification 2026-08-15 | 2026-08-15 |
## Resolved / archive
@@ -476,3 +476,6 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th
| #194 | task | L1: Archive retired backfill one-shots and dead ci-change-scope token | 3 of 5 named backfill one-shots archived via PR #1947 (backfill-document-covers.mjs, backfill-document-tags.ts, backfill-enrichment.ts moved to scripts/archive/, npm scripts repointed, one test import updated). The other 2 (backfill-gold-document-labels.ts, backfill-smart-index.ts) were found already classified [live] ongoing tooling in docs/scripts-index.md and docs/codebase-index.md, not one-shot candidates -- archiving them would have contradicted the repo's own documented convention, so they were correctly left in place. The 'dead ci-change-scope token' claim was confirmed FALSE by grep: scripts/ci-change-scope.mjs is live, central CI tooling wired into ci.yml and four other verify scripts. No removal made. | 2026-08-14 |
| #189 | task | Pin /api/search route-level round trips and disposition the x3 text RPC probes | Route-level budget pinned via PR #1950 (16 round trips: auth/ratelimit/scope/enrichment/telemetry). The x3 text-RPC fan-out (match_document_chunks_text_v2 / match_document_table_facts_text_v2) is disposed as INTENTIONAL: src/lib/rag/rag-candidate-sources.ts fans out maxTextRpcQueryVariants (3) lexical query-variant phrasings for recall, with an existing PT-02 early exit (firstVariantPoolIsStrong) that skips sibling RPCs when the primary pool is already strong. No retrieval-behaviour change made; collapsing further would need a RAG flag plus a live canary, out of scope for this PR. | 2026-08-14 |
| #333 | issue | check:medication-lexicon-report has been failing on main for every local verify:pr-local, and no CI job runs it | Closed 2026-08-15 as a duplicate of #331. Its clean-main and missing-CI evidence is preserved in #331, which is now the single owner of both the staleness diagnosis and the CI/local-preflight enforcement decision; no medication report was regenerated or clinical content changed. | 2026-08-14 |
+| #163 | task | Redesign Services search results (Progressive Referral Workflow) | CLOSED 2026-08-15 by PR #1982, but the row was half-stale when picked up and the split is worth recording. ALREADY ON MAIN before this PR, verified against e60b49a: the query is the
via SearchResultsHeaderBand headingLevel={1} (the count is separate neutral text, never a heading), the shortlist bar is already conditional on selected.length, Compare already exists, and the four-card numbered walkthrough (ServiceReferralFlow) is mounted only on /services/[slug] — tests/ui-tools.spec.ts:1394 already asserted it absent from results. The current/ baseline PNG the row was written against is from 2026-07-31 and no longer reflects main, which is why the row read as untouched work. DELIVERED BY THIS PR: the tiny Search/Shortlist/Compare/Refer dot rail (new ServiceReferralProgress, accessible name Referral progress — deliberately not Referral workflow, so the absence assertion cannot be satisfied by renaming the old component back onto the route); row compaction dropping the Catchment/Eligibility/Cost strip and the confidence pill (both values remain untruncated on the record behind Review referral); a per-row bookmark wired to account favourites and kept distinct from the non-persisted shortlist, with a visible polite status because sign-in-required is the common guest outcome; and the shortlist banner moved below the heading it qualifies. NOT DONE, deliberately: the comp draws the query dominant and the count small, which is the reverse of the shipped band. That weighting is a documented contract (docs/search-chrome-behaviour.md Results band rules 1-2), shared by twelve modes, and visual-baselined from /services?q=CMHT&run=1, so inverting it is a repo-wide change and not this row. The outcome this row asked for holds either way. Captured separately. Verification: verify:pr-local all ten selected gates passed; UI proof delegated to CI Production UI because the container ships chromium-1194 against a pinned 1234 (#255/#312). | 2026-08-15 |
+| #164 | task | Redesign Favourites as hybrid dashboard + search (no ModeHome) | CLOSED 2026-08-15 by PR #1983. /favourites is one dashboard+search workspace; no ModeHome was reintroduced and no separate Favourites home route exists. DELIVERED: the 'Favourites command library' marketing H1, its heart icon tile and its explanatory subtitle are retired for a plain Favourites heading with the item count beside it as non-heading text; the desktop FavouritesSidebar plus the phone FavouritesMobileQuickViews and FavouritesMobileBrowseRail collapse into one chip rail carrying sets, Pinned/Source-backed and types with counts, after which favourites-library-nav.tsx had no callers and was deleted (529 lines, and it paid down one gate-8 edge conflict as a side effect: 25 to 24); the empty query shows Continue then Recent and Your sets side by side, a typed query demotes that band to a collapsed disclosure and filters the table in place with an 'N matches for ...' header; and the redundant filter computation is gone — filteredItems and the table's own tableRows were derived independently from identical inputs, so the band count and the table count were two answers to one question, and the page now derives rows once and passes them down. IN-PLACE FILTERING IS REAL, not just same-surface: the page reads the shared composer's live draft via useSearchCommand seeded from the route's submitted ?q=, the same pattern tools-search-results-page already uses, so typing filters without navigating and without a second input and the one-composer contract is untouched. NOT DONE, deliberately and worth knowing: Continue and Recent still render from the existing derivation, where lastUsedByItemId and pinnedItemIds are hard-coded five-entry literals for demo slugs and real registry items fall back to the literal string 'Saved'. Direction B leads with both surfaces, so they want a genuine per-item last-opened timestamp; that is a data-layer change and building it here would have silently rescoped this row. Captured as its own row. Verification: verify:pr-local all ten selected gates passed, unit suite 607 files / 6584 passed; UI proof delegated to CI Production UI (chromium-1194 vs pinned 1234, #255/#312). | 2026-08-15 |
+| #293 | issue | Gate 2 needs a phone-viewport deterministic surface; the `min-h-tap` 0px finding is REFUTED | Closed after PR #1962 landed on main (commit 885c613): tests/ui-style-contract.spec.ts now has a deterministic Gate 2 tap-carrier enumeration test at a 390x844 phone viewport on /forms's static home, polling until 3 consecutive reads agree. Finding 1 (min-height override) was already refuted as intentional (sm: desktop release of the phone-only floor); this closes Finding 2, the last open part. | 2026-08-15 |