From 2c01a5eee2168ea5a32ccacbe983b3af1c05220e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 15:23:11 +0000 Subject: [PATCH 01/11] chore(issues): reconcile 9 queued ledger requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies the 9 pending outstanding-issues-inbox requests that had already landed on main via merged PRs but not yet been folded into the canonical ledger: - #231 (P1): incident addendum — rung-2 evidence from the 2026-08-14 live latency investigation (source-pinned #316 index restore). - #235, #237, #238: records that PR #1940's earlier `done` closures were reviewed and rejected — none of the three closures actually satisfied their row's evidence requirement (jsdom/DOM coverage standing in for a real-browser visual pass). - #316 (P1): 2026-08-14 incident findings — 2 of 21 missing indexes restored live, before/after latency, forensics pointer. - 2 cancellation decisions applied (duplicate #231 update requests superseded by the combined incident-addendum version above). Processed via `npm run issues:reconcile` on a fresh origin/main base per repo convention; the 9 source requests move to docs/outstanding-issues-inbox/applied/ as an audit trail. --- .../19fb70c0-1fd4-43a8-a4f4-efa9c31d16b1.json | 0 .../38c6095f-41f5-4925-b1ea-f5af5187885d.json | 0 .../606b1573-6217-457a-9183-7f8e550d3094.json | 0 .../7f5e6922-8d0b-424a-b359-12bed3a4e315.json | 0 .../81845ded-27a8-43d8-9a56-d85a5515935b.json | 0 .../bbf21714-0ef9-4c2f-942f-1b8d7e328ac8.json | 0 .../bd11cfe9-1627-425f-a8f7-e0e202b980a4.json | 0 .../d52dbc3d-6759-4f6e-9089-24059830ed5a.json | 0 .../ec6d0c23-2f19-4159-9c73-49bdc103b61e.json | 0 docs/outstanding-issues.md | 10 +++++----- 10 files changed, 5 insertions(+), 5 deletions(-) rename docs/outstanding-issues-inbox/{ => applied}/19fb70c0-1fd4-43a8-a4f4-efa9c31d16b1.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/38c6095f-41f5-4925-b1ea-f5af5187885d.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/606b1573-6217-457a-9183-7f8e550d3094.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/7f5e6922-8d0b-424a-b359-12bed3a4e315.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/81845ded-27a8-43d8-9a56-d85a5515935b.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/bbf21714-0ef9-4c2f-942f-1b8d7e328ac8.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/bd11cfe9-1627-425f-a8f7-e0e202b980a4.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/d52dbc3d-6759-4f6e-9089-24059830ed5a.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/ec6d0c23-2f19-4159-9c73-49bdc103b61e.json (100%) diff --git a/docs/outstanding-issues-inbox/19fb70c0-1fd4-43a8-a4f4-efa9c31d16b1.json b/docs/outstanding-issues-inbox/applied/19fb70c0-1fd4-43a8-a4f4-efa9c31d16b1.json similarity index 100% rename from docs/outstanding-issues-inbox/19fb70c0-1fd4-43a8-a4f4-efa9c31d16b1.json rename to docs/outstanding-issues-inbox/applied/19fb70c0-1fd4-43a8-a4f4-efa9c31d16b1.json diff --git a/docs/outstanding-issues-inbox/38c6095f-41f5-4925-b1ea-f5af5187885d.json b/docs/outstanding-issues-inbox/applied/38c6095f-41f5-4925-b1ea-f5af5187885d.json similarity index 100% rename from docs/outstanding-issues-inbox/38c6095f-41f5-4925-b1ea-f5af5187885d.json rename to docs/outstanding-issues-inbox/applied/38c6095f-41f5-4925-b1ea-f5af5187885d.json diff --git a/docs/outstanding-issues-inbox/606b1573-6217-457a-9183-7f8e550d3094.json b/docs/outstanding-issues-inbox/applied/606b1573-6217-457a-9183-7f8e550d3094.json similarity index 100% rename from docs/outstanding-issues-inbox/606b1573-6217-457a-9183-7f8e550d3094.json rename to docs/outstanding-issues-inbox/applied/606b1573-6217-457a-9183-7f8e550d3094.json diff --git a/docs/outstanding-issues-inbox/7f5e6922-8d0b-424a-b359-12bed3a4e315.json b/docs/outstanding-issues-inbox/applied/7f5e6922-8d0b-424a-b359-12bed3a4e315.json similarity index 100% rename from docs/outstanding-issues-inbox/7f5e6922-8d0b-424a-b359-12bed3a4e315.json rename to docs/outstanding-issues-inbox/applied/7f5e6922-8d0b-424a-b359-12bed3a4e315.json diff --git a/docs/outstanding-issues-inbox/81845ded-27a8-43d8-9a56-d85a5515935b.json b/docs/outstanding-issues-inbox/applied/81845ded-27a8-43d8-9a56-d85a5515935b.json similarity index 100% rename from docs/outstanding-issues-inbox/81845ded-27a8-43d8-9a56-d85a5515935b.json rename to docs/outstanding-issues-inbox/applied/81845ded-27a8-43d8-9a56-d85a5515935b.json diff --git a/docs/outstanding-issues-inbox/bbf21714-0ef9-4c2f-942f-1b8d7e328ac8.json b/docs/outstanding-issues-inbox/applied/bbf21714-0ef9-4c2f-942f-1b8d7e328ac8.json similarity index 100% rename from docs/outstanding-issues-inbox/bbf21714-0ef9-4c2f-942f-1b8d7e328ac8.json rename to docs/outstanding-issues-inbox/applied/bbf21714-0ef9-4c2f-942f-1b8d7e328ac8.json diff --git a/docs/outstanding-issues-inbox/bd11cfe9-1627-425f-a8f7-e0e202b980a4.json b/docs/outstanding-issues-inbox/applied/bd11cfe9-1627-425f-a8f7-e0e202b980a4.json similarity index 100% rename from docs/outstanding-issues-inbox/bd11cfe9-1627-425f-a8f7-e0e202b980a4.json rename to docs/outstanding-issues-inbox/applied/bd11cfe9-1627-425f-a8f7-e0e202b980a4.json diff --git a/docs/outstanding-issues-inbox/d52dbc3d-6759-4f6e-9089-24059830ed5a.json b/docs/outstanding-issues-inbox/applied/d52dbc3d-6759-4f6e-9089-24059830ed5a.json similarity index 100% rename from docs/outstanding-issues-inbox/d52dbc3d-6759-4f6e-9089-24059830ed5a.json rename to docs/outstanding-issues-inbox/applied/d52dbc3d-6759-4f6e-9089-24059830ed5a.json diff --git a/docs/outstanding-issues-inbox/ec6d0c23-2f19-4159-9c73-49bdc103b61e.json b/docs/outstanding-issues-inbox/applied/ec6d0c23-2f19-4159-9c73-49bdc103b61e.json similarity index 100% rename from docs/outstanding-issues-inbox/ec6d0c23-2f19-4159-9c73-49bdc103b61e.json rename to docs/outstanding-issues-inbox/applied/ec6d0c23-2f19-4159-9c73-49bdc103b61e.json diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 0ed58896bd..bf12e68b71 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -179,10 +179,10 @@ removed after current-main verification; it is not missing recommended work. | #212 | P3 | task | Replace as unknown as casts and unvalidated JSON.parse with Zod or runtime guards | **DEPRIORITISED 2026-08-12 (yield review against current main).** 40 casts at trust boundaries. Same reasoning as #211: worth doing, no measured defect traces to it, and it competes with clinical work for review attention. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: 40 `as unknown as` casts remain under src/ — the row's population is intact. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. 48 as unknown as casts and ~24 unvalidated JSON.parse calls across src/ trust Supabase, OpenAI, localStorage, file metadata and extraction boundaries. Start with src/lib/rag/rag.ts and src/app/api/* routes, mirroring existing Zod use in src/lib/validation/body.ts and src/lib/extractors/document.ts. See docs/review-findings-2026-08-02.md sections 2.2, 2.3 and 8. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | | #215 | P3 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | **Outcome:** two of the four image-only findings from the 2026-08-02 audit are shipped; two remain open for an explicit implementation-or-drop decision. **RESTATED 2026-08-13 after inspection against main 2d270392 — two of the four items already shipped and the row no longer describes them as open.** DONE: src/components/clinical-dashboard/image-lightbox.tsx carries decoding="async" (Phase 0, PR #1660), asserted by tests/signed-image.dom.test.tsx. DONE: SignedImage has the priority prop for above-fold evidence — it also skips the IntersectionObserver deferral entirely — and document viewer Phase 3 (PR #1772) added the other half of that pair: an explicit fetchPriority of high when priority is set and low otherwise, so a deferred rail figure does not contend with the page's own above-the-fold work. The document rail additionally passes a 240px observer root margin against the shared 640px default. REMAINING, both confirmed by inspection rather than inferred: (a) src/components/pwa-lifecycle.tsx still has no decoding attribute; (b) public/demo-documents/ still contains no .webp — the PNGs are ~80 KB each and served as-is, so the conversion with a PNG fallback has not been done. **Next:** apply decoding=async in pwa-lifecycle.tsx, and either convert the demo PNGs to WebP with a PNG fallback or record that an ~80 KB synthetic demo asset is not worth the build step. **Stop:** do not treat this row as covering the broader performance findings — those live under #016, #013, #117 and #147. | session 2026-08-02 /ledger sweep — docs/audit/performance-image-cwv-audit-2026-08-02.md | 2026-08-02 | | #222 | P3 | task | Headers surface only partially converged in PR-J: mode-home-template and search-results-header-band untouched | VERIFIED CORRECT 2026-08-12 — re-checked against merged main and left open: Still unconverged: src/components/mode-home-template.tsx defines ModeHomeStatusNotice locally (:232) and imports neither PageHeader nor the DS EmptyState; search-results-header-band.tsx is likewise untouched. Note the adjacency — in-flight PR #1842 delegates ModeHomeStatusNotice to the DS EmptyState under #221, which is a different conversion from the PageHeader question this row asks. Re-check after #1842 merges. Builder A converged DsmPageHeader, InformationPageHeader and InformationPageBreadcrumbs onto PageHeader plus Breadcrumb, and declined two files with reasons. mode-home-template.tsx ModeHomeHero is a centred display hero on the fluid text-hero token and is the slot the in-flow phone composer sits in, so converging it onto a left-aligned PageHeader is a redesign of 13 mode homes that collides with the one-composer-per-page contract. search-results-header-band.tsx is a results spine carrying status, counts and filters, not a page-title stack, so its pin tests/search-results-header-band.dom.test.tsx remains unflipped. Both are defensible; both leave the headers surface partially adopted. Next action: decide whether either is in scope at all, or record them as permanently out of the PageHeader vocabulary. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder A) | 2026-08-02 | -| #231 | P1 | issue | Generation fallbacks no longer stick in answer cache; lithium generation quality still falls back safely | PARTIAL 2026-08-12: This PR fixes the clinically consequential stale-fallback path: every answer whose routing or degraded reason contains generation_fallback is excluded from rag_response_cache. Offline evidence: 96 focused answer-route tests and 574 RAG fixture/contract tests passed. Approved live baseline/final canaries preserved 36/36 document and content recall at 1.0 with zero per-case reciprocal-rank regressions; the final 44-case answer gate had zero citation or numeric-grounding failures. A budget extension was tested and rejected: four cache-bypassed 'Lithium dosing?' probes remained grounded, cited safe extractive fallbacks at 35-40 second candidate budgets; the decisive 40-second probe completed generation in 25.272 seconds and 27.237 seconds total with route_deadline_exceeded=false, but failed generation quality. Therefore OPENAI_ANSWER_TIMEOUT_MS and the route budget are not the current residual binding cause. Next: instrument and reproduce the structured generation-quality failure using provider-safe metadata, then make a separate bounded output-quality fix with an offline fixture and live canary. Stop: do not increase route/provider timeouts or cache any generation fallback. | session 2026-08-04 (production triage, live /api/search + /api/answer) | 2026-08-04 | -| #235 | P3 | task | ADOPTION.md section 7 proof shots exist for only four of the adopted surfaces | IN FLIGHT 2026-08-12 in PR #1842 (records adoption evidence). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. The adoption contract asks for a proof shot per adopted surface. The Wave 5 adoption captured four - DSM header, settings rows, patient panel, answer surface - and none for the forms fold, the catalogue and docs surfaces, the headers convergence, or the empty states adopted since. Section 7 therefore reads as complete while most of the adoption is unevidenced, which matters because the proof shot is what a later reader uses to tell an intended restyle from a regression (the #229 DSM eyebrow was almost rediscovered as a defect for exactly this reason). Next action: capture the missing shots against a warmed local server and attach them to section 7. Cheap and mechanical - no gate, no provider access. Stop: this is not the visual-baseline harness (#118) - do not commit Playwright snapshot PNGs or flip that job to blocking. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | -| #237 | P2 | rec | Eyeball low-confidence AccessibleTable densities at 320px before freezing Linux visual baselines | IN FLIGHT 2026-08-12 in PR #1841 (renders empty dense cells wrapping rather than truncated, with a 320px jsdom assertion). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. PR #1616 clinical MissingValue phrases increase text volume in sparse OCR grids. Contract forbids abbreviating to a dash. Next: open one real lowConfidence extraction at 320px phone width and accept or adjust dense preview column widths before committing Linux screenshots (#118). | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | -| #238 | P2 | rec | Visual pass for Sheet portal default on settings, sidebar, and answer overlays | IN FLIGHT 2026-08-12 in PR #1842 (exercises the Sheet portal default and adds tests/sheet.dom.test.tsx). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. PR #1616 flips Sheet portal default to true, moving ~10 product overlays into OverlayRoot. Token inheritance is safe; residual risk is ancestor-scoped CSS / contain / transform. Next: one visual pass of settings-dialog, ClinicalSidebar, answer-result sheets, launcher sheet, section-nav. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #231 | P1 | issue | Generation fallbacks no longer stick in answer cache; lithium generation quality still falls back safely | PARTIAL 2026-08-12: This PR fixes the clinically consequential stale-fallback path: every answer whose routing or degraded reason contains generation_fallback is excluded from rag_response_cache. Offline evidence: 96 focused answer-route tests and 574 RAG fixture/contract tests passed. Approved live baseline/final canaries preserved 36/36 document and content recall at 1.0 with zero per-case reciprocal-rank regressions; the final 44-case answer gate had zero citation or numeric-grounding failures. A budget extension was tested and rejected: four cache-bypassed 'Lithium dosing?' probes remained grounded, cited safe extractive fallbacks at 35-40 second candidate budgets; the decisive 40-second probe completed generation in 25.272 seconds and 27.237 seconds total with route_deadline_exceeded=false, but failed generation quality. Therefore OPENAI_ANSWER_TIMEOUT_MS and the route budget are not the current residual binding cause. INSTRUMENT NOW EXISTS 2026-08-14: the "Next: instrument" half of this row is done. Commit a3bc4da adds scripts/probe-generation-quality.ts — one cache-bypassed live answer reporting the structured generation_quality_gate_reasons, provider-backed, refusing demo mode, never caching or logging the probe. The same commit adjudicates PR #1861: superseded for phase 1, close recommended, with the numeric-retry half deferred to phase 2 pending probe evidence. So do not review #1861 as though it were the live fix, and do not re-implement the probe. Next: run scripts/probe-generation-quality.ts in an environment that has OPENAI and Supabase credentials — it is blocked in offline containers, which is why it has not been run yet — then make a separate bounded output-quality fix with an offline fixture and live canary. Stop: do not increase route/provider timeouts or cache any generation fallback. INCIDENT ADDENDUM 2026-08-14 (later the same day): rung-2 evidence was then measured live - supabase_rpc_latency_ms 31610 on a semantic query (route budget 25000 starved generation), caused by the #316 dropped trigram indexes; after their owner-approved restore, 1535 (text fast path) / 8519 (hybrid). Pre-generation latency was the binding residual cause of semantic-query source-only fallbacks in that window; evidence in docs/audit/live-drift-forensics-2026-08.md. S1 (A1 phase 2) must re-verify generation_quality_gate:* dominance on healthy latency (run the probe with node --env-file=.env.local, which the probe does not load itself) before choosing a code mitigation rung. The route-budget stop condition stands unchanged. | sessions 2026-08-14: instrument adjudication + live incident probes (owner-authorized Supabase connector) | 2026-08-04 | +| #235 | P3 | task | ADOPTION.md section 7 proof shots exist for only four of the adopted surfaces | CLOSURE ATTEMPTED AND REJECTED 2026-08-14 — read this before closing again. PR #1940 queued a `done` for this row citing ADOPTION.md section 7.1's per-surface executable-evidence table; the closure was cancelled on review with the reason "executable evidence does not replace the requested desktop and phone proof shots". The cancellation is correct, and the trap is worth naming: section 7.1 opens with "This PR records executable evidence RATHER THAN committing image baselines", so the very section that looks like the evidence says in its first line that it is not. A test that proves a component is mounted is not a picture of the surface, and this row asks for the picture. IN FLIGHT note retired: PR #1842 merged, so the do-not-start warning no longer applies. The requirement is unchanged. The adoption contract asks for a proof shot per adopted surface. Wave 5 captured four - DSM header, settings rows, patient panel, answer surface - and none for the forms fold, the catalogue and docs surfaces, the headers convergence, or the empty states adopted since. Section 7 therefore reads as complete while most of the adoption is unevidenced, which matters because the proof shot is what a later reader uses to tell an intended restyle from a regression (the #229 DSM eyebrow was almost rediscovered as a defect for exactly this reason). Next action: capture the missing shots against a warmed local server (npm run ensure) and attach them to section 7. Cheap and mechanical - no gate, no provider access. Stop: this is not the visual-baseline harness (#118) - do not commit Playwright snapshot PNGs or flip that job to blocking. Stop: do not close this row on unit, DOM or contract evidence of any kind. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | +| #237 | P2 | rec | Eyeball low-confidence AccessibleTable densities at 320px before freezing Linux visual baselines | CLOSURE ATTEMPTED AND REJECTED 2026-08-14 — read this before closing again. PR #1940 queued a `done` for this row citing tests/accessible-table.dom.test.tsx:110 ("keeps the full missing-value phrase readable in the dense 320px preview"); the closure was cancelled on review with the reason "the jsdom assertion does not verify the requested real 320px browser visual pass". The cancellation is correct. jsdom does not lay out text, so a 320px assertion there proves the string is present, not that it fits, wraps or stays legible at that width — which is the whole question for a low-confidence extraction in a sparse grid. IN FLIGHT note retired: PR #1841 merged, so the do-not-start warning no longer applies. The requirement is unchanged. PR #1616 clinical MissingValue phrases increase text volume in sparse OCR grids; the contract forbids abbreviating to a dash. Next: open one real lowConfidence extraction at 320px phone width in an actual browser (npm run ensure, then a phone viewport) and accept or adjust dense preview column widths before committing Linux screenshots (#118). Stop: do not close this row on a jsdom, DOM-testing-library or snapshot-string assertion — only a real browser at 320px settles it. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | +| #238 | P2 | rec | Visual pass for Sheet portal default on settings, sidebar, and answer overlays | CLOSURE ATTEMPTED AND REJECTED 2026-08-14 — read this before closing again. PR #1940 queued a `done` for this row citing tests/sheet.dom.test.tsx and the `portal = true` default; the closure was cancelled on review with the reason "generic Sheet coverage does not verify the requested product-overlay browser journeys". The cancellation is correct. The residual risk this row exists for is ancestor-scoped CSS, `contain` and `transform` on the specific product overlays — a property of where each overlay sits in the real page, which a component-level unit test cannot see no matter how thorough it is. IN FLIGHT note retired: PR #1842 merged, so the do-not-start warning no longer applies. The requirement is unchanged. PR #1616 flips the Sheet portal default to true, moving ~10 product overlays into OverlayRoot; token inheritance is safe. Next: one visual pass in a real browser over settings-dialog, ClinicalSidebar, answer-result sheets, launcher sheet and section-nav. Stop: do not close this row on Sheet component coverage — the question is about the five host surfaces, not about Sheet. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | | #239 | P3 | rec | Manual phone rotation check for ResizeObserver-only phone chrome reserve | PR #1616 phone overlay reserve publishes only from ResizeObserver quiet-window deliveries. Desktop↔phone and late-mount recovery are covered; orientation that does not change stack height is a narrower trigger. Next: rotate a physical phone on a chrome-overlay route and confirm --phone-overlay-chrome-h updates. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | | #240 | P3 | rec | Confirm tooltip visual hard-clip asymmetry with design owner | Tooltip keeps overflow-hidden visual clamp while sr-only/aria-label retain full text. Design contract says supplementary-only. Next: design-owner confirmation that sighted users losing the clipped tail is acceptable, or allow overflow-y-auto for long clinical strings. | PR #1616 review findings; session 2026-08-05 | 2026-08-05 | | #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 | @@ -208,7 +208,7 @@ removed after current-main verification; it is not missing recommended work. | #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 | | #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 | Phase 0 delivered — drift routing + post-migration trigger + evidence scaffold, PR #1938. live-drift.yml now creates/updates one pinned issue 'Live drift check failing' (label live-drift-failure) with the captured finding lines and run URL on failure, and comments+closes it on the next green run; issues: write is scoped to a separate drift-routing job so the job running npm ci keeps contents: read. The workflow also runs on pushes to main touching supabase/migrations/** or supabase/schema.sql. docs/audit/live-drift-forensics-2026-08.md now carries dated empty Phase 1-5 evidence sections anchored here. Still outstanding: a forced workflow_dispatch failure to observe the pinned issue end-to-end (provider-backed, operator to run), SUPABASE_ACCESS_TOKEN per #183, and Phases 1-5, which all need approved hosted windows. Note: the Phase 0 task prompt named #312 as the anchor; that is the unrelated Playwright-browser P3, and the anchor was resolved to #316 by exact title per the playbook. | session 2026-08-13 / Actions runs 30763871562 + 31330856982 / open #248 | 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 | 2026-08-14 incident session: two of the 21 missing indexes (documents_title_trgm_idx, document_chunks_content_trgm_idx) were the retrieval-critical pair; restored live via owner-approved CREATE INDEX CONCURRENTLY + ANALYZE. Before/after supabase_rpc_latency_ms 31610 -> 1535 (text) / 8519 (hybrid). Forensics: indexes existed 2026-08-04 (guard 20260804110240 passed) and were dropped by 2026-08-09; no app code drops indexes - owner to check dashboard audit for manual/advisor DROP INDEX. Evidence: docs/audit/live-drift-forensics-2026-08.md. Remaining drift findings + 10 diverged match_* RPC bodies stay with docs/database-remediation-plan.md. Drift routing: PR #1939. | session 2026-08-14 live incident (owner-authorized Supabase connector) | 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 | From b1ca83e910d9002243d5dcb7069d3abb02077b26 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:28:38 +0800 Subject: [PATCH 02/11] docs(issues): queue #316 evidence correction --- .../1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json diff --git a/docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json b/docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json new file mode 100644 index 0000000000..fe19e0445a --- /dev/null +++ b/docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "id": "1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7", + "createdOn": "2026-08-15", + "action": "update", + "payload": { + "id": "#316", + "summary": "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", + "detail": "2026-08-14 incident session: two of the 21 missing indexes (documents_title_trgm_idx, document_chunks_content_trgm_idx) were the retrieval-critical pair; restored live via owner-approved CREATE INDEX CONCURRENTLY + ANALYZE. Current 2026-08-14 inventory is 20 missing indexes (the inventory result, not an arithmetic inference from the prior 21 baseline). Before/after supabase_rpc_latency_ms 31610 -> 1535 (text) / 8519 (hybrid). Forensics: the pair was created 2026-07-05 and reported missing by 2026-08-02, so the drop is bounded between 2026-07-05 and 2026-08-02 (likely by 2026-07-26); guard 20260804110240 validates four other indexes and gives no existence bound for this pair. No app code drops indexes; owner dashboard-audit check for manual/advisor DROP INDEX remains pending. Evidence: docs/audit/live-drift-forensics-2026-08.md. Remaining drift findings plus 10 diverged match_* RPC bodies stay with docs/database-remediation-plan.md. Drift routing: PR #1939.", + "source": "PR #1968 review against docs/audit/live-drift-forensics-2026-08.md, 2026-08-15" + } +} From 457804b1b10d73e16bf9e0f530318e43549f6808 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:28:40 +0800 Subject: [PATCH 03/11] docs(issues): consolidate #316 drift correction --- .../3156e1c9-82aa-46c3-84e6-0e119015acc6.json | 10 ++++++++++ .../b621e863-8304-4331-bf94-da1596dcfa28.json | 12 ++++++++++++ .../d50512f3-d977-49fd-ba8f-8720f1fc276c.json | 10 ++++++++++ 3 files changed, 32 insertions(+) create mode 100644 docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json create mode 100644 docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json create mode 100644 docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json diff --git a/docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json b/docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json new file mode 100644 index 0000000000..a9274bf0f0 --- /dev/null +++ b/docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "id": "3156e1c9-82aa-46c3-84e6-0e119015acc6", + "createdOn": "2026-08-15", + "action": "cancel", + "payload": { + "requestId": "1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7", + "reason": "Superseded by the consolidated #316 update so all Phase 0 evidence and the corrected current inventory land as one audited mutation." + } +} diff --git a/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json b/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json new file mode 100644 index 0000000000..90e57d1f40 --- /dev/null +++ b/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "id": "b621e863-8304-4331-bf94-da1596dcfa28", + "createdOn": "2026-08-15", + "action": "update", + "payload": { + "id": "#316", + "summary": "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", + "detail": "Combined 2026-08-14 update, superseding two partial requests cancelled in this same batch. PHASE 0 CLOSED including its forced-dispatch proof: live-drift dispatched on main, failed at the drift step, the always() capture still ran, the migration-history step correctly skipped, and the drift-routing job created issue #1963 with the label, run URL, job result, trigger, and full findings. Routing is also covered offline by tests/live-drift-workflow.test.ts. 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 by an independent read-only query. Before/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 definitions were already codified. CORRECTED FIGURES measured 2026-08-14: 10 match_* def_hash mismatches unchanged, 20 missing_live indexes (the observed inventory, not an arithmetic inference from the previous 21 baseline), and 2 unexpected_live. ATTRIBUTION STILL OPEN: migration 20260705180000 recorded 14 executed statements; guard 20260804110240 validates four other indexes and gives no existence bound for this pair. The drop window is 2026-07-05 to 2026-08-02, likely by 2026-07-26; dashboard audit-history pairing remains owner action and #248 stays open. NEXT: Phase 3 RPC reconciliation before Phase 4, per plan ordering. Evidence: docs/audit/live-drift-forensics-2026-08.md.", + "source": "PR #1968 review against docs/audit/live-drift-forensics-2026-08.md, 2026-08-15" + } +} diff --git a/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json b/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json new file mode 100644 index 0000000000..d128495be7 --- /dev/null +++ b/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "id": "d50512f3-d977-49fd-ba8f-8720f1fc276c", + "createdOn": "2026-08-15", + "action": "cancel", + "payload": { + "requestId": "3d0adf39-ec7a-4fa3-9309-057a193410de", + "reason": "Superseded by a consolidated #316 update that preserves this Phase 0 evidence and corrects the headline to the verified current inventory of 20." + } +} From 0a9d3ad33c096b8bfc7795bda23137620d2c52a5 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:28:52 +0800 Subject: [PATCH 04/11] chore(issues): reconcile current ledger queue --- .../0bbea6d5-4d5e-4e9a-bcba-bc129beeba3f.json | 0 .../19762e55-fa25-41de-a5a2-7f4932a545fe.json | 0 .../1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json | 0 .../21b6525a-dd2d-4dcc-95d5-c3c777420adf.json | 0 .../2b7856a5-fe32-49b8-9e3c-819d961784c6.json | 0 .../3156e1c9-82aa-46c3-84e6-0e119015acc6.json | 0 .../3d0adf39-ec7a-4fa3-9309-057a193410de.json | 0 .../3dda04ff-1ae9-4153-80ed-ad081931e396.json | 0 .../3f8797e0-dffa-4d26-8019-15147b9af397.json | 0 .../4b85e2d3-963d-46fe-a9c3-b234b4de14b0.json | 0 .../4bc449a1-ec81-426d-aadd-8a78abec21cf.json | 0 .../5db479fa-07fc-4e9e-a377-93b4e27797ee.json | 0 .../6b09c1df-5f7c-4103-af3c-aead33eafb00.json | 0 .../9792c896-78d8-46ab-b194-6a52b7fd7cab.json | 0 .../9b67dd0e-f11e-423e-be63-7e4d91f4dd82.json | 0 .../b621e863-8304-4331-bf94-da1596dcfa28.json | 0 .../c3d91fce-52e5-41ed-8648-b2bf5e95b32c.json | 0 .../c63161f6-21b8-40ec-927c-684e22791066.json | 0 .../d226dce3-76a2-4ccc-9723-5e7ca03a6c5d.json | 0 .../d2e60a94-b12a-4502-a8b9-3dc8406cc8c8.json | 0 .../d50512f3-d977-49fd-ba8f-8720f1fc276c.json | 0 .../ddef5391-d476-417f-b0c8-662e44c54f8b.json | 0 .../ef62d13b-6852-4a12-a1d3-4d7e22ec1232.json | 0 .../f3b63187-c295-4f6a-8ecd-602f348835c4.json | 0 .../f86a3002-5019-4ae4-93fb-d01a05a7bae6.json | 0 .../fbfe982f-cd2f-49c9-a94e-908a99efa6b8.json | 0 .../fd548180-f031-44d8-bd70-24c3b03c5f21.json | 0 docs/outstanding-issues.md | 18 ++++++++++-------- 28 files changed, 10 insertions(+), 8 deletions(-) rename docs/outstanding-issues-inbox/{ => applied}/0bbea6d5-4d5e-4e9a-bcba-bc129beeba3f.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/19762e55-fa25-41de-a5a2-7f4932a545fe.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/21b6525a-dd2d-4dcc-95d5-c3c777420adf.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/2b7856a5-fe32-49b8-9e3c-819d961784c6.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/3156e1c9-82aa-46c3-84e6-0e119015acc6.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/3d0adf39-ec7a-4fa3-9309-057a193410de.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/3dda04ff-1ae9-4153-80ed-ad081931e396.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/3f8797e0-dffa-4d26-8019-15147b9af397.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/4b85e2d3-963d-46fe-a9c3-b234b4de14b0.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/4bc449a1-ec81-426d-aadd-8a78abec21cf.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/5db479fa-07fc-4e9e-a377-93b4e27797ee.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/6b09c1df-5f7c-4103-af3c-aead33eafb00.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/9792c896-78d8-46ab-b194-6a52b7fd7cab.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/9b67dd0e-f11e-423e-be63-7e4d91f4dd82.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/b621e863-8304-4331-bf94-da1596dcfa28.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/c3d91fce-52e5-41ed-8648-b2bf5e95b32c.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/c63161f6-21b8-40ec-927c-684e22791066.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/d226dce3-76a2-4ccc-9723-5e7ca03a6c5d.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/d2e60a94-b12a-4502-a8b9-3dc8406cc8c8.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/d50512f3-d977-49fd-ba8f-8720f1fc276c.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/ddef5391-d476-417f-b0c8-662e44c54f8b.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/ef62d13b-6852-4a12-a1d3-4d7e22ec1232.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/f3b63187-c295-4f6a-8ecd-602f348835c4.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/f86a3002-5019-4ae4-93fb-d01a05a7bae6.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/fbfe982f-cd2f-49c9-a94e-908a99efa6b8.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/fd548180-f031-44d8-bd70-24c3b03c5f21.json (100%) diff --git a/docs/outstanding-issues-inbox/0bbea6d5-4d5e-4e9a-bcba-bc129beeba3f.json b/docs/outstanding-issues-inbox/applied/0bbea6d5-4d5e-4e9a-bcba-bc129beeba3f.json similarity index 100% rename from docs/outstanding-issues-inbox/0bbea6d5-4d5e-4e9a-bcba-bc129beeba3f.json rename to docs/outstanding-issues-inbox/applied/0bbea6d5-4d5e-4e9a-bcba-bc129beeba3f.json diff --git a/docs/outstanding-issues-inbox/19762e55-fa25-41de-a5a2-7f4932a545fe.json b/docs/outstanding-issues-inbox/applied/19762e55-fa25-41de-a5a2-7f4932a545fe.json similarity index 100% rename from docs/outstanding-issues-inbox/19762e55-fa25-41de-a5a2-7f4932a545fe.json rename to docs/outstanding-issues-inbox/applied/19762e55-fa25-41de-a5a2-7f4932a545fe.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/21b6525a-dd2d-4dcc-95d5-c3c777420adf.json b/docs/outstanding-issues-inbox/applied/21b6525a-dd2d-4dcc-95d5-c3c777420adf.json similarity index 100% rename from docs/outstanding-issues-inbox/21b6525a-dd2d-4dcc-95d5-c3c777420adf.json rename to docs/outstanding-issues-inbox/applied/21b6525a-dd2d-4dcc-95d5-c3c777420adf.json diff --git a/docs/outstanding-issues-inbox/2b7856a5-fe32-49b8-9e3c-819d961784c6.json b/docs/outstanding-issues-inbox/applied/2b7856a5-fe32-49b8-9e3c-819d961784c6.json similarity index 100% rename from docs/outstanding-issues-inbox/2b7856a5-fe32-49b8-9e3c-819d961784c6.json rename to docs/outstanding-issues-inbox/applied/2b7856a5-fe32-49b8-9e3c-819d961784c6.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/3d0adf39-ec7a-4fa3-9309-057a193410de.json b/docs/outstanding-issues-inbox/applied/3d0adf39-ec7a-4fa3-9309-057a193410de.json similarity index 100% rename from docs/outstanding-issues-inbox/3d0adf39-ec7a-4fa3-9309-057a193410de.json rename to docs/outstanding-issues-inbox/applied/3d0adf39-ec7a-4fa3-9309-057a193410de.json diff --git a/docs/outstanding-issues-inbox/3dda04ff-1ae9-4153-80ed-ad081931e396.json b/docs/outstanding-issues-inbox/applied/3dda04ff-1ae9-4153-80ed-ad081931e396.json similarity index 100% rename from docs/outstanding-issues-inbox/3dda04ff-1ae9-4153-80ed-ad081931e396.json rename to docs/outstanding-issues-inbox/applied/3dda04ff-1ae9-4153-80ed-ad081931e396.json diff --git a/docs/outstanding-issues-inbox/3f8797e0-dffa-4d26-8019-15147b9af397.json b/docs/outstanding-issues-inbox/applied/3f8797e0-dffa-4d26-8019-15147b9af397.json similarity index 100% rename from docs/outstanding-issues-inbox/3f8797e0-dffa-4d26-8019-15147b9af397.json rename to docs/outstanding-issues-inbox/applied/3f8797e0-dffa-4d26-8019-15147b9af397.json diff --git a/docs/outstanding-issues-inbox/4b85e2d3-963d-46fe-a9c3-b234b4de14b0.json b/docs/outstanding-issues-inbox/applied/4b85e2d3-963d-46fe-a9c3-b234b4de14b0.json similarity index 100% rename from docs/outstanding-issues-inbox/4b85e2d3-963d-46fe-a9c3-b234b4de14b0.json rename to docs/outstanding-issues-inbox/applied/4b85e2d3-963d-46fe-a9c3-b234b4de14b0.json diff --git a/docs/outstanding-issues-inbox/4bc449a1-ec81-426d-aadd-8a78abec21cf.json b/docs/outstanding-issues-inbox/applied/4bc449a1-ec81-426d-aadd-8a78abec21cf.json similarity index 100% rename from docs/outstanding-issues-inbox/4bc449a1-ec81-426d-aadd-8a78abec21cf.json rename to docs/outstanding-issues-inbox/applied/4bc449a1-ec81-426d-aadd-8a78abec21cf.json diff --git a/docs/outstanding-issues-inbox/5db479fa-07fc-4e9e-a377-93b4e27797ee.json b/docs/outstanding-issues-inbox/applied/5db479fa-07fc-4e9e-a377-93b4e27797ee.json similarity index 100% rename from docs/outstanding-issues-inbox/5db479fa-07fc-4e9e-a377-93b4e27797ee.json rename to docs/outstanding-issues-inbox/applied/5db479fa-07fc-4e9e-a377-93b4e27797ee.json diff --git a/docs/outstanding-issues-inbox/6b09c1df-5f7c-4103-af3c-aead33eafb00.json b/docs/outstanding-issues-inbox/applied/6b09c1df-5f7c-4103-af3c-aead33eafb00.json similarity index 100% rename from docs/outstanding-issues-inbox/6b09c1df-5f7c-4103-af3c-aead33eafb00.json rename to docs/outstanding-issues-inbox/applied/6b09c1df-5f7c-4103-af3c-aead33eafb00.json diff --git a/docs/outstanding-issues-inbox/9792c896-78d8-46ab-b194-6a52b7fd7cab.json b/docs/outstanding-issues-inbox/applied/9792c896-78d8-46ab-b194-6a52b7fd7cab.json similarity index 100% rename from docs/outstanding-issues-inbox/9792c896-78d8-46ab-b194-6a52b7fd7cab.json rename to docs/outstanding-issues-inbox/applied/9792c896-78d8-46ab-b194-6a52b7fd7cab.json diff --git a/docs/outstanding-issues-inbox/9b67dd0e-f11e-423e-be63-7e4d91f4dd82.json b/docs/outstanding-issues-inbox/applied/9b67dd0e-f11e-423e-be63-7e4d91f4dd82.json similarity index 100% rename from docs/outstanding-issues-inbox/9b67dd0e-f11e-423e-be63-7e4d91f4dd82.json rename to docs/outstanding-issues-inbox/applied/9b67dd0e-f11e-423e-be63-7e4d91f4dd82.json diff --git a/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json b/docs/outstanding-issues-inbox/applied/b621e863-8304-4331-bf94-da1596dcfa28.json similarity index 100% rename from docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json rename to docs/outstanding-issues-inbox/applied/b621e863-8304-4331-bf94-da1596dcfa28.json diff --git a/docs/outstanding-issues-inbox/c3d91fce-52e5-41ed-8648-b2bf5e95b32c.json b/docs/outstanding-issues-inbox/applied/c3d91fce-52e5-41ed-8648-b2bf5e95b32c.json similarity index 100% rename from docs/outstanding-issues-inbox/c3d91fce-52e5-41ed-8648-b2bf5e95b32c.json rename to docs/outstanding-issues-inbox/applied/c3d91fce-52e5-41ed-8648-b2bf5e95b32c.json diff --git a/docs/outstanding-issues-inbox/c63161f6-21b8-40ec-927c-684e22791066.json b/docs/outstanding-issues-inbox/applied/c63161f6-21b8-40ec-927c-684e22791066.json similarity index 100% rename from docs/outstanding-issues-inbox/c63161f6-21b8-40ec-927c-684e22791066.json rename to docs/outstanding-issues-inbox/applied/c63161f6-21b8-40ec-927c-684e22791066.json diff --git a/docs/outstanding-issues-inbox/d226dce3-76a2-4ccc-9723-5e7ca03a6c5d.json b/docs/outstanding-issues-inbox/applied/d226dce3-76a2-4ccc-9723-5e7ca03a6c5d.json similarity index 100% rename from docs/outstanding-issues-inbox/d226dce3-76a2-4ccc-9723-5e7ca03a6c5d.json rename to docs/outstanding-issues-inbox/applied/d226dce3-76a2-4ccc-9723-5e7ca03a6c5d.json diff --git a/docs/outstanding-issues-inbox/d2e60a94-b12a-4502-a8b9-3dc8406cc8c8.json b/docs/outstanding-issues-inbox/applied/d2e60a94-b12a-4502-a8b9-3dc8406cc8c8.json similarity index 100% rename from docs/outstanding-issues-inbox/d2e60a94-b12a-4502-a8b9-3dc8406cc8c8.json rename to docs/outstanding-issues-inbox/applied/d2e60a94-b12a-4502-a8b9-3dc8406cc8c8.json diff --git a/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json b/docs/outstanding-issues-inbox/applied/d50512f3-d977-49fd-ba8f-8720f1fc276c.json similarity index 100% rename from docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json rename to docs/outstanding-issues-inbox/applied/d50512f3-d977-49fd-ba8f-8720f1fc276c.json diff --git a/docs/outstanding-issues-inbox/ddef5391-d476-417f-b0c8-662e44c54f8b.json b/docs/outstanding-issues-inbox/applied/ddef5391-d476-417f-b0c8-662e44c54f8b.json similarity index 100% rename from docs/outstanding-issues-inbox/ddef5391-d476-417f-b0c8-662e44c54f8b.json rename to docs/outstanding-issues-inbox/applied/ddef5391-d476-417f-b0c8-662e44c54f8b.json diff --git a/docs/outstanding-issues-inbox/ef62d13b-6852-4a12-a1d3-4d7e22ec1232.json b/docs/outstanding-issues-inbox/applied/ef62d13b-6852-4a12-a1d3-4d7e22ec1232.json similarity index 100% rename from docs/outstanding-issues-inbox/ef62d13b-6852-4a12-a1d3-4d7e22ec1232.json rename to docs/outstanding-issues-inbox/applied/ef62d13b-6852-4a12-a1d3-4d7e22ec1232.json diff --git a/docs/outstanding-issues-inbox/f3b63187-c295-4f6a-8ecd-602f348835c4.json b/docs/outstanding-issues-inbox/applied/f3b63187-c295-4f6a-8ecd-602f348835c4.json similarity index 100% rename from docs/outstanding-issues-inbox/f3b63187-c295-4f6a-8ecd-602f348835c4.json rename to docs/outstanding-issues-inbox/applied/f3b63187-c295-4f6a-8ecd-602f348835c4.json diff --git a/docs/outstanding-issues-inbox/f86a3002-5019-4ae4-93fb-d01a05a7bae6.json b/docs/outstanding-issues-inbox/applied/f86a3002-5019-4ae4-93fb-d01a05a7bae6.json similarity index 100% rename from docs/outstanding-issues-inbox/f86a3002-5019-4ae4-93fb-d01a05a7bae6.json rename to docs/outstanding-issues-inbox/applied/f86a3002-5019-4ae4-93fb-d01a05a7bae6.json diff --git a/docs/outstanding-issues-inbox/fbfe982f-cd2f-49c9-a94e-908a99efa6b8.json b/docs/outstanding-issues-inbox/applied/fbfe982f-cd2f-49c9-a94e-908a99efa6b8.json similarity index 100% rename from docs/outstanding-issues-inbox/fbfe982f-cd2f-49c9-a94e-908a99efa6b8.json rename to docs/outstanding-issues-inbox/applied/fbfe982f-cd2f-49c9-a94e-908a99efa6b8.json diff --git a/docs/outstanding-issues-inbox/fd548180-f031-44d8-bd70-24c3b03c5f21.json b/docs/outstanding-issues-inbox/applied/fd548180-f031-44d8-bd70-24c3b03c5f21.json similarity index 100% rename from docs/outstanding-issues-inbox/fd548180-f031-44d8-bd70-24c3b03c5f21.json rename to docs/outstanding-issues-inbox/applied/fd548180-f031-44d8-bd70-24c3b03c5f21.json diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index bf12e68b71..043c402838 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -111,7 +111,7 @@ removed after current-main verification; it is not missing recommended work. | 56 | `#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. @@ -198,36 +198,36 @@ removed after current-main verification; it is not missing recommended work. | #281 | P2 | rec | The phone document route renders two clinical-summary surfaces and neither is canonical | **Outcome:** one clinical summary on the document route, chosen deliberately. **Detail:** a phone reader gets the gradient 'High-yield clinical summary' card (DocumentClinicalSummary, built by buildDocumentClinicalSummaryModel) and, further down, the rail's '#source-summary' / 'high-yield-summary' disclosure (DocumentSectionSummary + FormattedHighYieldSummary + BadgeCluster). They render the same document.summary row two different ways. The rail is not hidden on phones — only its DocumentSectionIndexCard is lg:block — so both appear. Only the rail panel carries the section anchor, so the more prominent card is the unnavigable one. Note the two disagree about emptiness as well: the card now renders nothing when the model yields no usable text, while the rail panel still renders for its label badges, which is why 'hasStoredSummary' was deliberately left keyed to the stored row rather than to card content. **Next:** decide which rendering is canonical — this is a clinical-content judgement about how a summary should read, not a layout fix — then delete the other and give the survivor the 'source-summary' anchor. If the rail's badges are the part worth keeping, they can move without the second summary body. **Stop:** do not merge the two renderings mechanically; they format clinical text differently and the difference is the decision. | session 2026-08-08 document-viewer optimisation; document-rail-panels.tsx; document-clinical-summary.tsx | 2026-08-08 | | #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 | **Outcome:** picking up a queued ledger item cannot silently duplicate work another session already has in flight. **Detail:** on 2026-08-09 two assistants took the same queued `/issues` item roughly four hours apart and independently built the same in-page-nav conversion — PR #1766 (merged) and PR #1767 (closed as duplicate). Neither had any way to see the other: the ledger row was the only shared state. Correcting an earlier version of this row after CodeRabbit's review on PR #1773: it is not true that the ledger "has no in-progress state" — some rows do carry a progress marker in their prose (`IN PROGRESS` appears on two, and `IMPLEMENTED in PR #1766` on another). The accurate gap is narrower and worse: there is no structured status field and no atomic claim, so a marker is written by whoever did the work, usually after the fact, and nothing requires or checks one — which means the ABSENCE of a marker carries no information at all. Both sessions read it, both correctly concluded it was open, both built it. The wasted effort is the smaller cost; the larger one is that the two implementations diverged in shape, which is what forced the separate `PageSection` ownership decision recorded in `docs/search-chrome-behaviour.md`. Distinct from `#156`/`#168`, which are about two branches colliding on an **id** while appending; this is two sessions colliding on the **work** a row describes, and a collision-free id scheme would leave it untouched. **Mitigation landed 2026-08-09 (same PR as this row):** the check is now written into the three places an assistant actually reads before starting queued work — `.claude/skills/newtask/SKILL.md` "Before you start" (which already performed an open-PR read for PR bundling, so this asks that same list a second question and costs no extra call), `.claude/skills/issues/SKILL.md` after the read-only flow, and the `/issues` section of `AGENTS.md` so Codex and Cursor get it too rather than Claude Code only. All three say to scan for the **route, component or surface**, not the ledger id, because a duplicate PR rarely quotes the id; all three degrade to a warning when GitHub is unreachable so an offline session can still start work. **Next:** leave open for one or two queued-item cycles to see whether prose is enough. If a second duplicate lands anyway, this becomes the same class as `#258` — a rule enforced for one tool by prose with no gate — and the answer is a check, not more wording. **Stop:** do not implement a claim marker written back into the row when a session starts an item; that reintroduces exactly the read-modify-write contention `#168` exists to remove. Do not make the open-PR read a hard blocker. | session 2026-08-09; PR #1766 (merged); PR #1767 (closed duplicate) | 2026-08-09 | +| #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 | **CORRECTS this row's original text, which was wrong on its central claim.** FINDING 1 IS REFUTED (2026-08-12). As first written it asserted that controls carrying `min-h-tap` have their declaration "overridden to 0", blamed "likely an unlayered component class in globals.css", and treated the six shapes as a live 48px-floor defect. All of that is wrong, and acting on it would have caused a regression. **What actually zeroes the min-height is the source itself, deliberately:** the sites carry an explicit `sm:` step-down beside `min-h-tap`. The two 36px shapes are exact matches — `services-navigator-page.tsx:217` is `grid min-h-tap min-w-tap … sm:h-9 sm:min-h-0 sm:w-9 sm:min-w-0` and `:286` is `inline-flex min-h-tap min-w-[94px] … sm:h-9 sm:min-h-0`. `sm:min-h-0` IS the computed `min-height: 0px`, and `sm:h-9` IS the rendered 36px. Seven `min-h-tap` sites carry `sm:min-h-0`; the wider pattern is larger still — `inline-flex min-h-tap items-center` alone appears with `sm:min-h-0` (4), `sm:min-h-7` (2), `sm:min-h-8` (2), `sm:min-h-9` (4), `sm:min-h-10` (8) and `sm:min-h-12` (1). **`min-h-tap` is a PHONE floor that desktop deliberately releases**, which is why the audit only sees it below the floor: `tests/ui-style-contract.spec.ts:97` navigates at the project's desktop viewport, so every `sm:`-and-up override is in force at measurement time. The audit was measuring intended design and reporting it as an overridden floor. **Do NOT "fix" these** — removing the step-downs would pin every desktop control to 48px and is a visual regression across the app, not a WCAG improvement (the phone contract already exceeds both AA 2.5.8 and AAA 2.5.5). The `declared < tapFloor - 0.5 continue` skip at `:116` is therefore correct at desktop width and is NOT the same structural blind spot as the `h-10` case in `#265`. FINDING 2 STANDS UNCHANGED and is the whole of the remaining work: a rendered-interactive enumeration on `/services?q=CMHT&run=1` is NOT DETERMINISTIC — six runs against one production build returned 6, 5, 4, 3, 3 and 9 distinct control shapes, largely disjoint; `waitForLoadState('networkidle')` plus deduplication to distinct shapes did not fix it, and two consecutive agreeing runs were coincidence. The enumeration was written, shown to find genuine defects, and REVERTED rather than landed, because that spec runs in the required Production UI job via `productionSpecPattern` and `scripts/playwright-pr-shards.mjs`, so an intermittent version would block every merge. **Next, revised:** (1) build the deterministic surface — a static route with no async search, or a fixed seeded state; (2) run the tap enumeration **at a phone viewport**, where `min-h-tap` is unreleased and the measurement is meaningful, rather than at desktop where the floor is intentionally lifted; a phone layout is also the simpler, more deterministic surface, so (1) and (2) push the same way. Step (2) of the original row — "find what zeroes min-height on the min-h-tap carriers" — is CLOSED by this correction: the answer is `sm:min-h-0`, and it is intended. **Stop:** do not re-land the enumeration on a live-search route; do not quarantine a brand-new test to get it merged (quarantine is for flaky tests already trusted, and policy needs three reproductions on one SHA via `tests/flake-ledger.json`); do not lower any production tap target, and never to `min-h-11` (known `ui-smoke` sub-pixel flake; production uses the 48px token). | session 2026-08-09 — M2 gate 2 enumeration (#265); finding 1 refuted session 2026-08-12 against `origin/main` 4587f78 (`services-navigator-page.tsx:217,286`; `tests/ui-style-contract.spec.ts:97,116`) | 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. Next: attribute it the way #147 was attributed — drive Chromium against the offline production build with a PerformanceObserver on layout-shift reading entry.sources[].node, at DESKTOP emulation this time. Do not assume it is the same phone-overlay reserve cause as #147; that reserve publishes 0px above the phone breakpoint by construction, so this is a different shifter. Stop: do not raise the budget to accommodate it, and do not read local LCP or TBT from that harness (loopback has no network latency). | Local offline verify:lighthouse runs 2026-08-12 (two runs, identical CLS); #147 close-out; lighthouse-budget.json | 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 | Raised by the Codex reviewer on PR #1858 and correct. docs/filter-contract.md section 5 sets density by option count: <=5 chips, 6-20 dense full-width list with a right-aligned count column and group headings, >20 or >3 groups adds find-a-filter and collapse-by-default. **PARTIALLY DELIVERED 2026-08-13, and the part this row was opened for is NOT done.** PR F (#1910) ported documents' implementation up into the shared ResultFilterSheet, so the >20-or->3-groups tier now exists there: find-a-filter, per-group collapse-by-default, a group opening itself when it holds a selection, and a live needle owning openness. That is the tier documents needed. **The 6-20 band is still unimplemented.** result-filter-control.tsx computes `const dense = facetGroups.length > 3 \|\| totalFacetOptions > 20`, so a mode with one facet group of nine options — formulation, the exact case that opened this row — evaluates dense=false and still renders ResultFilterFacetChips as a wrapping chip row. Verified 2026-08-13 on main 2d27039: formulation passes one group with formulationDomainsInUse.length === 9, so neither condition fires. An earlier attempt to close this row as delivered was wrong and was caught in review on PR #1925; the mistake was conflating "the dense tier landed" with "this row's band landed" — section 5 has two thresholds and only the upper one shipped. **Next:** either implement the 6-20 full-width renderer with the right-aligned count column and add the nine-option DOM assertion this row already asked for, or amend section 5 to drop the middle band deliberately and record that the contract was reversed rather than satisfied. **Stop:** do not close this row on the strength of the >20 tier, and do not add a per-mode dense list — a second hand-rolled facet layout is the drift the shared renderer was extracted to remove. | Codex review on PR #1858; docs/filter-contract.md section 5 | 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 | | #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 | 2026-08-14 incident session: two of the 21 missing indexes (documents_title_trgm_idx, document_chunks_content_trgm_idx) were the retrieval-critical pair; restored live via owner-approved CREATE INDEX CONCURRENTLY + ANALYZE. Before/after supabase_rpc_latency_ms 31610 -> 1535 (text) / 8519 (hybrid). Forensics: indexes existed 2026-08-04 (guard 20260804110240 passed) and were dropped by 2026-08-09; no app code drops indexes - owner to check dashboard audit for manual/advisor DROP INDEX. Evidence: docs/audit/live-drift-forensics-2026-08.md. Remaining drift findings + 10 diverged match_* RPC bodies stay with docs/database-remediation-plan.md. Drift routing: PR #1939. | 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 two partial requests cancelled in this same batch. PHASE 0 CLOSED including its forced-dispatch proof: live-drift dispatched on main, failed at the drift step, the always() capture still ran, the migration-history step correctly skipped, and the drift-routing job created issue #1963 with the label, run URL, job result, trigger, and full findings. Routing is also covered offline by tests/live-drift-workflow.test.ts. 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 by an independent read-only query. Before/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 definitions were already codified. CORRECTED FIGURES measured 2026-08-14: 10 match_* def_hash mismatches unchanged, 20 missing_live indexes (the observed inventory, not an arithmetic inference from the previous 21 baseline), and 2 unexpected_live. ATTRIBUTION STILL OPEN: migration 20260705180000 recorded 14 executed statements; guard 20260804110240 validates four other indexes and gives no existence bound for this pair. The drop window is 2026-07-05 to 2026-08-02, likely by 2026-07-26; dashboard audit-history pairing remains owner action and #248 stays open. NEXT: Phase 3 RPC reconciliation before Phase 4, per plan ordering. 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 | | #321 | P3 | task | Four follow-up groups cover nine controls after #291 | Six controls in the differential comparison page stay coupled to its planned rewrite and pinned density test. The filmstrip Page unknown control is a later mechanical change. DocumentViewer needs its persistent access reason split from transient loading before classification. The pin-limit control remains a capacity-state judgement. These are four source groups and nine controls, not four controls. | PR #1778 body; verified against main 2d27039 | 2026-08-14 | | #322 | P2 | issue | Two catalogue records are both named Warfarin and share no interaction rows, so which one a clinician opens changes the warnings | data/medications-snapshot.json holds warfarin-vka and warfarin-anticoagulant, both displayed as 'Warfarin', both class Anticoagulant / subclass Vitamin K Antagonist. They carry three interaction rows each with ZERO in common, so the alerts a clinician sees depend on which record they happened to open, and nothing on screen distinguishes them. A lexicon class term resolves to both. This is a catalogue DATA defect, not a lexicon fault - merging, deleting one, or relabelling them is a clinical content decision, which is why it is reported rather than patched. Surfaced automatically by duplicateCatalogueNames in scripts/build-medication-lexicon-report.ts, which compares the row sets and states the divergence rather than asking about it, and pinned by a test in tests/medication-interaction-lexicon-coverage.test.ts that goes red when the records are reconciled so the flag can be retired with it. Next: a named clinical owner decides the disposition. Stop: do not de-duplicate by display name in the report or the UI - that hides the divergence rather than resolving it. | PR #1923; docs/medication-interaction-lexicon-review.md flag section; tests/medication-interaction-lexicon-coverage.test.ts | 2026-08-13 | | #323 | P2 | task | 35 of 328 catalogue medications sit outside the resolved interaction graph, so the tool can never warn about them | Measured 2026-08-13 from data/medication-interaction-index.json using both endpoints of every row with a resolved counterparty: 35 of the catalogue's 328 medications sit outside the resolved interaction graph. They are concentrated in aperients (8), antibiotics (5), antidiabetics (4) and vitamins (3); psychiatry-relevant examples include topiramate and zolpidem. The former 127 count considered only inbound counterparty references and wrongly labelled source-only drugs such as celecoxib unreachable even though their own rows emit alerts. This is primarily CORPUS coverage: widening it requires authoring an interaction row or making existing source content machine-resolvable with clinical review, not indiscriminately widening lexicon selectors. PR #1923 closed the safety half - evaluateMedicationInteractions now reports unreachableCounterparties, composeMedicationVerdict treats it as incomplete so green is unreachable, and MedicationInteractionBlock names the uncovered drugs and says the absence of a warning is not evidence of safety. The generated list by class is the 'What this tool can never warn about' section of docs/medication-interaction-lexicon-review.md and refreshes with the report. Next: prioritise clinically relevant gaps on the prescribing surface. Stop: do not close this by loosening the matcher; that reintroduces the false-positive class (Sodium content, Vitamin K, hyperkalaemia prose) that was deliberately rejected. | PR #1923; docs/medication-interaction-lexicon-review.md coverage section; src/lib/medication-interactions.ts UNREACHABLE_SLUGS | 2026-08-13 | -| #324 | P1 | rec | No gate detects a merged PR whose content is silently reverted by a later merge resolution | MEASURED 2026-08-13 by blob comparison against origin/main, not by reading merge messages. Method: for every PR merge into main since 2026-08-06, for each file the PR changed, compare origin/main's blob against the file's PRE-merge parent blob. Equality means the PR's change to that file is gone. Seven merged PRs came back positive: #1800 (fuzzy catalogue search plus its tests, 8 files), #1803 (the --shadow-tight to --e1 token retirement, 49 files), #1809 (2 specifier pages), #1811 (2 secondary-navigation test files), #1804 (4 test files), #1796 (worker/validate-runtime.ts), #1815 (1 command-surface file). Six of the seven first show the wiped state at ONE commit, acf78bf 2026-08-11 'Merge remote-tracking branch origin/main into probe2-1815', part of the PR #1815 babysit and unblock chain whose manual conflict resolutions took the stale branch side and reverted whatever had landed on main meanwhile. Confirmation that does not rely on blob identity: --shadow-tight is still referenced in 67 files on main after the PR that retired it merged, and open row #302 independently records legacyShadowAliases pinned at 220 while measuring 193, which is what a lost retirement looks like. WHY NOTHING WENT RED: the reverts took each PR's tests in the same stroke, so no gate had an assertion left to fail. Commit 6f8c70d 'fix(pr-1815) resolve main merge conflict and keep shadow-tight switch migration' shows a human trying to preserve #1803 and a later merge in the same chain undoing it anyway, so care at the keyboard is not the control. Note 55f51ab 'docs(issues) repair three merge losses' repaired the DOCS casualties of this same event; the source-code casualties were never noticed. This is #311's ledger loss-detector generalised from docs to source, which is where it actually bit. Next: add the blob-comparison sweep as a script plus a test, run it against origin/main post-merge or on a schedule, and fail on any file that reverts to its pre-PR blob. Keep the window bounded (for example 14 days) so the check stays cheap. Stop: do not treat a positive as automatically a defect, because a deliberate later revert looks identical at blob level. The check should name the PR and the file and require a human to confirm, exactly as the branch-review ledger does. | session 2026-08-13; local blob sweep over origin/main at 63526ee; acf78bf; 6f8c70d; 55f51ab; row #302; row #311 | 2026-08-13 | +| #324 | P1 | rec | No gate detects a merged PR whose content is silently reverted by a later merge resolution | **Outcome:** the file-level merge-loss detector is delivered; one authoritative row now tracks its remaining operational decision. **Delivered:** PR #1944 added scripts/audit-merge-loss.mjs through npm run audit:merge-loss and focused tests. It compares every changed file in a bounded main-history window with the landing commit's first parent, then reports possible reverts for human review. The implementation independently rediscovered the acf78bf casualties, including the #1803 token-retirement loss, and deliberately remains advisory because blob equality cannot distinguish a deliberate revert from an accidental merge-resolution loss. **Remaining:** decide whether it runs after merges or on a schedule, who triages positive findings, and whether the separate branch-versus-squash inbox-request-loss case should be a second detector or a mode of the same tool. A scheduled or required check without a named human disposition path would become ignorable noise. **Stop:** do not reimplement the delivered script, and do not make either detector blocking or auto-close findings until that ownership decision exists. | session 2026-08-13 blob sweep; PR #1944 audit implementation and tests; PR #1937 inbox-loss case; consolidated by PR #1956 review follow-up | 2026-08-13 | | #325 | P3 | rec | A queued update request can silently clobber a row that changed after the request was written | **Outcome:** the inbox cannot apply a stale rewrite over someone else's newer content without anyone noticing. **Detail:** the inbox intake fixed ID allocation — ids are assigned at reconciliation, so two branches can no longer collide on a number, which was the sharper of the two hazards. It does not address content staleness. An 'update' request carries a full replacement '--detail' string written against whatever the author read at queue time; reconciliation applies it verbatim. If the target row changed on main between queueing and reconciling, the newer content is overwritten with no signal. The multiple-pending-mutations guard does not catch this: it fires only when two requests target the same id, not when one request is simply old. **Live near-miss, 2026-08-13:** a document-viewer ledger pass was drafted against a base four days stale, and its '#215' restatement was composed from that stale reading. It was caught only because the author re-read every row against current main before queueing — a discipline, not a gate. The same pass had already had to discard a directly-allocated '#295' because main had since claimed it; that half is now structurally impossible, this half is not. **Next:** consider fingerprinting the target row at queue time — the request schema is versioned ('version: 1'), so a 'baseRow' hash could be added to add/update/done payloads and compared at reconcile, refusing (or requiring an explicit override) when the row moved underneath. Weigh against just documenting the re-read discipline: this costs a schema bump plus writer, reconcile and self-test changes, and the failure needs a multi-day-stale base to bite. **Stop:** do not make reconciliation merge or three-way-diff detail text — a replacement that silently becomes a merge is harder to reason about than one that refuses. | session 2026-08-13 document-viewer ledger truth pass, PR #1930; scripts/ledger-inbox.mjs request schema | 2026-08-13 | | #326 | P3 | task | Keep post-restore environment recovery controls visible in the universal ledger | **Consolidated survivor for #188 and #196–#200 before their source rows are archived by PR #1920.** A schema restore is not operationally complete until all five environment-owned controls have been re-created and verified: (1) restore the ingestion, retention, and related `pg_cron` schedules and confirm they are active; (2) re-add required Supabase Vault secrets, including `cron_ingestion_jwt`, and verify names only without printing values; (3) re-set the required custom `app.*` database GUCs and verify them with read-only settings checks; (4) redeploy the required Supabase edge functions with the Deno v2.x toolchain and confirm the function list and health, only in an explicitly approved hosted-change window; and (5) re-enter dashboard-owned configuration, including auth providers and SSO redirect URLs, connection-pool caps, per-project keys, and `E2E_USER_*`, without committing secret values. **Next:** after every schema-restore drill or real restore, follow the disaster-recovery checklist in `docs/operator-backlog.md` and `docs/disaster-recovery-runbook.md`, record the verification outcome here, and keep the row open until all five controls are green. **Stop:** the runbooks are the execution procedure, not a substitute for this universal-ledger status row; do not treat a restored schema alone as recovered, expose secret values, or perform hosted writes without the required approval. | docs/operator-backlog.md disaster-recovery checklist; docs/disaster-recovery-runbook.md; #188/#196–#200; PR #1920 review | 2026-08-13 | | #327 | P3 | task | The recommended queue's Outcome cells are now unrendered dead text | **Residual of the queue-misdirection fix (PR #1902).** Both consumers — .claude/hooks/issues-surface.sh and scripts/issues-report.mjs — now derive each queue row's prose from the cited row's Detail cell, so the Outcome column reaches no reader through tooling. The stale prose still sits in the file, where a human opening it can read and act on it; for #231 that prose pointed at an approach the row had already recorded as refuted. **Implementation, established by building it 2026-08-13 — three findings that are not obvious:** (1) It cannot be a direct edit. check-ledger-write-discipline compares the canonical ledger against exactly applyRequestBatch(base, movedRequests), and no request type reaches the queue, so a hand edit is unlandable by construction. The rewrite has to live INSIDE applyRequestBatch — the function the checker itself imports — so checker and reconciler compute the same result; make it run for an empty batch and be idempotent so ordinary PRs are byte-identical. (2) It must land in the SAME commit as a reconcile. Code alone makes the checker compute normalise(base) while canonical stays un-normalised, failing every PR until a reconcile normalises it. (3) Do NOT drop the column, and do NOT blank composite rows. issues-report skips any queue row whose cells.length !== 7, so removing the column makes the queue vanish from /issues; and derivation deliberately skips composite ID(s) rows, so those still fall back to the Outcome cell and blanking it leaves them with no prose at all — filter to rows citing exactly one id. **Stop:** do not delete the queue table; order, acuity, capability, when and estimate exist nowhere else. | PR #1902; implementation attempt 2026-08-13 | 2026-08-13 | | #328 | P2 | issue | A row can outlive its own completion — nothing closes a ledger row when its work merges | **Found during the 2026-08-12 yield review; re-confirmed on main 2026-08-13.** The then-#304 row described a ranking-snapshot freshness fuse due to trip around 2026-08-19 and sat in the recommended queue as time-critical, but its work had already landed as commit d182844 (PR #1876) — the snapshot's generatedAt and sourceRunId no longer matched anything the row said. Nothing closes a row when its work merges: `issues:done` is a manual call, and the session that ships the work is often not the session that owns the row. This is the mirror of #292, which covers duplication BEFORE work starts; this is staleness AFTER it finishes, and it is more dangerous because the row keeps advertising urgency to every session that reads the queue. **Next:** the cheapest useful guard is a periodic re-verification pass that re-measures each open row against current main and flags rows whose stated evidence no longer reproduces — several rows already carry a hand-written VERIFIED CORRECT stamp, which shows the need but does it manually and unevenly. A stronger version has the handoff skill close the row in the same commit that lands the work. **Stop:** do not auto-close on keyword match; a row can be partially delivered (#215, #231) and auto-closing those would lose real remaining work. | session 2026-08-12 ledger yield review; re-verified 2026-08-13 | 2026-08-13 | | #329 | P2 | issue | All live mobile routes breach LCP; shared CSS delivery and JavaScript are the current bottleneck | PR #1927 is merged and deployed to Railway production at exact SHA f2abf5baf3f449a1803bedef9dc107f30b70db93. Three-sample live medians on that SHA are Documents 3374 ms, DSM 3961 ms, Forms 3507 ms, root 3819 ms, Therapy 3422 ms, and Services 3793 ms; desktop LCP is 580-679 ms and mobile CLS remains within the rule. The production CSS split is retained and reduced four canonical medians modestly, but every mobile route still breaches 2500 ms. Root trace attribution is now concrete: TTFB 283 ms, LCP render delay 3449 ms, the 46,724-byte transferred shared stylesheet completes at 3644 ms under the throttled critical-request contention, total main-thread work is 1785 ms, script evaluation is 1030 ms, and shared chunk 8322 alone consumes 870 ms CPU. This is separate from canonical #117, which continues to track the unresolved Therapy catalogue payload and per-field safety decision. Next: split the 4,251-line global stylesheet by route ownership and reduce the shared search-shell/root client boundary before repeating the same bounded live matrix. Therapy field safety review remains required for search/pathways. INP remains unverified because Lighthouse does not measure it and no usable CrUX result exists. Stop: do not strip clinical fields, weaken the Lighthouse budget, refresh a passing baseline to hide latency, or claim an INP pass. | PR #1927; Railway deployments 1224ed55-210d-443b-94e5-20f87475468c and 810cc8b3-e39a-493f-b18f-8c63d150d53f; live Web Vitals runs 31719448766 and 31719451951; PR #1933 review | 2026-08-13 | | #330 | P2 | task | Re-land PR #1800 (fuzzy catalogue search), applying the #310 one-edit cap in the same commit | PR #1800 squash-merged as 022c83b on 2026-08-10 and its entire content is absent from main: git show origin/main:src/lib/catalog-search.ts \| grep -c typoDistanceLimit returns 0, eight of its 11 source and test files are byte-identical to their pre-#1800 state. The remaining three (`src/components/therapy-compass/data/select.ts`, `src/lib/formulation.ts`, and `tests/formulation.test.ts`) contain later unrelated changes, but the fuzzy-search hunks are absent from them too; preserve those newer changes during the re-land. Cause and evidence in the merge-loss detector row filed alongside this one. Consequence today is a MISSING FEATURE, not a live hazard: because the matcher is gone, the #310 cross-drug defect is not reachable on main. Do not close #310 on that basis, and do not re-land #1800 unchanged. RE-LAND WITH THE FIX: #310 measured that the tier term.length >= 8 -> 2 edits is the problem, because Damerau scores an adjacent transposition as one edit, so fluoxetine to duloxetine is distance 2 and both are ten characters. Re-run 2026-08-13 against the algorithm confirms it, and confirms prednisone to prednisolone as the second real cross-drug hit. Capping that tier at 1 edit removes both while preserving sertraline to sertralin style recovery. The row's other claims also held on re-run: citalopram and escitalopram do not fuzzy-match, because the substring guard fires first, and clozapine/clonazepam and quetiapine/olanzapine are correctly out of range. Next: cherry-pick 022c83b onto current main, change typoDistanceLimit's >= 8 tier from 2 to 1, and add a test over real catalogue drug names with both the exact and the near-match record present, asserting the wrong drug is excluded while the exact drug remains. Gate: focused Vitest on `tests/catalog-search.test.ts` plus the other four test files #1800 touched. Stop: this path is clinicalRisk true under classifyPullRequestFiles because catalog-search.ts feeds medications.ts and prescribing, so the PR needs a complete Clinical Governance Preflight and must not be bundled with unrelated chores. ragRanking is correctly false; this is catalogue ranking, not pgvector retrieval. | session 2026-08-13; 022c83b; origin/main at 63526ee; row #310; algorithm re-run locally against real drug-name pairs | 2026-08-13 | -| #331 | P2 | issue | check:medication-lexicon-report fails on 3 independent branches despite zero diff on the flagged file or its inputs | Reproduced identically across three independently-authored branches on 2026-08-14 (PR #1947 archive-backfill-scripts, PR #1949 visual-layout-polish, PR #1950 search-round-trip-budget) during otherwise-unrelated verify:pr-local runs. Each session confirmed via git diff origin/main --name-only that docs/medication-interaction-lexicon-review.md and its generator inputs (src/lib/medication-interaction-lexicon, the medication snapshot, the medication interaction index) were untouched on their branch, yet check:medication-lexicon-report still reported the file stale. This is a tooling/process finding distinct from #1bfaf0ef (the lexicon's clinical content has never been signed off) -- this row is about the staleness CHECK itself firing on unchanged files, which suggests a bug in how the generator's staleness comparison works (timestamp vs content hash, or a comparison against the wrong base) rather than a real content drift. Next: investigate scripts/medications-lexicon-report.mjs (or equivalent) staleness-detection logic directly against origin/main; if it is a comparison bug, fix it; if the report genuinely is stale on main independent of these branches, regenerate it. Stop: do not treat repeated non-fixes of this check across unrelated PRs as acceptable long-term -- three independent confirmations is enough to act on. | PR #1947, PR #1949, PR #1950 verify:pr-local runs, 2026-08-14 | 2026-08-14 | +| #331 | P2 | issue | check:medication-lexicon-report fails on 3 independent branches despite zero diff on the flagged file or its inputs | **Outcome:** one authoritative owner for the medication-report staleness problem, including its missing CI coverage. **Evidence:** on 2026-08-14, check:medication-lexicon-report reported the review document stale on three independently authored branches (#1947, #1949, #1950) although the document, lexicon sources, medication snapshot, and interaction index were untouched. The symptom must therefore be investigated against a clean current main rather than fixed opportunistically in unrelated work. **Scope:** this row also carries the CI evidence formerly duplicated in #333: the check is reached only at the end of verify:pr-local and no workflow invokes it, so CI can stay green while a local PR preflight fails. **Next:** inspect the generator and its staleness comparison against current main; if the report is genuinely stale, regenerate it in a dedicated clinical-document change, otherwise fix the comparison. In the same decision, either make the validated check part of the appropriate CI contract or move it out of the local preflight so its enforcement matches its ownership. **Stop:** do not delete or weaken the check merely to green an unrelated preflight, and do not regenerate a clinical-facing artifact without checking whether the diff changes clinical content. | PR #1947, PR #1949 and PR #1950 clean-branch reproductions; PR #1942 preflight; session 2026-08-14; consolidated by PR #1956 review follow-up | 2026-08-14 | | #332 | P3 | task | Three mode-nav icon glyphs sit at 17px, off the --spacing-icon-* scale, and no gate flags them | Split out of #275 rather than folded into its badge-box token. mode-nav/mode-nav.tsx:64 and :214 and mode-nav/nav-slot-ink.tsx:44 size their with h-[1.0625rem] w-[1.0625rem] — 17px against an icon scale of 12/14/16/20/24 (--spacing-icon-xs..xl in the globals.css @theme block). #275 counted these among its five files because they share the badge's number, but they are a different role: the badge is a text-bearing box sized around its own --text-2xs numeral, these are glyphs. They are now the only consumers of that value, since the badge moved to --spacing-search-band-badge. Nothing gates this: check-icon-scale.mjs enforces only the retired 4.5 (18px) half-step and its header states it deliberately does NOT flag arbitrary h-[Nrem], because non-icon boxes legitimately use that form. So this is unguarded and will not self-report. Why it was not just fixed: snapping to size-icon-md (16px) or size-icon-lg (20px) visibly changes nav chrome at every breakpoint, and 17px is close enough to 16 that the choice looks arbitrary without seeing it rendered — a design call, not a token swap. Next: get a Chromium look at mode-nav at phone and desktop widths with the icon at 16 and at 20, pick one, then migrate all three together. If 17px turns out to be deliberate, say so in a comment at the call site and consider whether check:icon-scale should flag off-scale arbitrary icon sizes on -typed elements specifically, which would have surfaced this. Stop: do not add a 17px step to --spacing-icon-* to make the problem go away — that token block's own comment argues against widening the scale off the 4px grid, and it would sanction the drift rather than resolve it. | session 2026-08-14; split from #275; check-icon-scale.mjs header | 2026-08-14 | -| #333 | P2 | issue | check:medication-lexicon-report has been failing on main for every local verify:pr-local, and no CI job runs it | Found 2026-08-14 while running the PR preflight for an unrelated design-token change. 'npm run check:medication-lexicon-report' reports 'docs/medication-interaction-lexicon-review.md is stale. Run npm run medications:lexicon-report and commit the result.' and exits 1. Two things make this worth a row rather than a quick fix in a passing PR. FIRST, it is on main, not on any branch: reproduced in a clean worktree checked out at pristine origin/main (both d47aa6d and, after a merge, 79b01b3), with a diff touching zero medication, lexicon or data/ files. SECOND, and this is the part that explains why it went unnoticed, NOTHING IN CI RUNS IT — a grep for medication-lexicon-report across .github/workflows/ returns nothing. It is reached only through the local verify:pr-local chain, where it is the LAST step, so it fails every local PR preflight while every CI run stays green. The failure mode is therefore self-concealing in the direction that matters: the gate is invisible to the required checks and visible only to whoever is about to hand off, who then has to decide whether an unrelated stale generated doc is theirs to fix. It was not fixed in the design-token PR that found it, deliberately: the report is a clinical-facing generated document and regenerating it inside a CSS-token PR would bundle a clinical-risk artefact with unrelated chores, which AGENTS.md PR bundling explicitly forbids. Next: run 'npm run medications:lexicon-report', read the resulting diff to confirm it is a pure regeneration and not a content change needing clinical review, and commit it in its own PR. Then decide the real question this exposes — either wire the check into CI so it cannot silently rot again, or move it out of verify:pr-local so it stops failing preflights it does not gate. A check in the local chain but not in CI is the worst of both. Stop: do not simply delete the check or drop it from verify:pr-local to get a green preflight; the staleness is real and the generated file is a clinical artefact. | session 2026-08-14; PR #1942 preflight; reproduced on pristine origin/main d47aa6d and 79b01b3; grep over .github/workflows | 2026-08-14 | | #334 | P3 | issue | Claude Code web containers can ship Node 22 with no node_modules, so npm ci fails engine-strict before any work starts | Hit 2026-08-14 at the start of a Claude Code on the web session, and it blocks a session completely until worked around, so it is worth recording even though the cause is the container image rather than this repo. The container provided /opt/node20, /opt/node21 and /opt/node22 with node22 on PATH, no nvm, and no node_modules in either the primary checkout or a fresh worktree. package.json requires node >=24.15.0 <25 with engine-strict, so 'npm ci --include=dev' aborts immediately with 'notsup Required: {node: >=24.15.0 <25, npm: 11.x} Actual: {npm: 10.9.7, node: v22.22.2}'. Nothing in the repo can fix this from inside, because the failure happens before any repo script can run — .nvmrc correctly says 24 and is simply not consulted, and there is no nvm for it to drive. Workaround used, which took about a minute and is safe: fetch the current 24.x from the nodejs.org dist index, untar to /opt/node24, and prefix subsequent commands with 'export PATH=/opt/node24/bin:/opt/node24/bin:/root/.local/bin:/root/.cargo/bin:/usr/local/go/bin:/opt/node22/bin:/opt/maven/bin:/opt/gradle/bin:/opt/rbenv/bin:/root/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'. Everything downstream then behaved normally — npm ci, the full unit suite, build, and the Playwright-free gates all passed. Worth knowing that this is a DIFFERENT surface from the Codex Cloud provisioning path: scripts/setup-codex-cloud.sh and scripts/setup-codex-worktree.mjs cover Codex, and docs/codex-cloud.md is explicit that Cloud mirrors the tracked toolchain, but neither runs for a Claude Code web session, so that hardening does not carry over. Next: decide whether this deserves repo-side help at all. Options are a short note in the AGENTS.md or CLAUDE.md orientation telling an agent to install Node 24 to /opt/node24 and re-export PATH rather than concluding the environment is broken, or a small bootstrap script equivalent to the Codex ones that a web session can run first. Prefer the note: a bootstrap script that downloads a runtime is a bigger surface than the problem. Stop: do not relax the engines range, drop engine-strict, or pass --force to get npm ci through — the Node 24 floor is enforced deliberately in several places (preinstall, check:runtime, scripts/dev-free-port.mjs) and loosening it to accommodate a bad container would disable a real guard. | session 2026-08-14; Claude Code web container for PR #1942 | 2026-08-14 | -| #335 | P2 | rec | Merge-loss detection covers file-level reverts and inbox-request loss separately; neither covers the other, and the scheduled run is undecided | **Outcome:** one decision about how merge loss is detected on this repo, rather than two half-overlapping checks and an undecided schedule. **Detail.** Two detectors now exist for the same underlying hazard — content that reached main and then stopped being there — and they measure different things. (1) PR #1944 added scripts/audit-merge-loss.mjs (npm run audit:merge-loss): for every PR landing on origin/main in a bounded window it compares the ref's current blob for each file that landing changed against the blob at the landing's first parent, so it catches a landing whose CONTENT was reverted by a later merge resolution. Validated by independently rediscovering the acf78bf casualties (#1803 with 53 files, #1800, #1804, #1796, #1811). (2) PR #1937 filed a request about a queued inbox request that existed on a branch and never reached main through that branch's squash — a file that never landed at all, which detector (1) cannot see, because it only ever examines what a landing actually contributed. Conversely #1937's own cancel request warns that comparing all historical branch additions against the squash produces FALSE losses when a PR deliberately removes a file during review; detector (1) avoids that by construction (it diffs merge^1 against merge, not the branch's whole history), which is worth reusing rather than rediscovering. **Three things to decide, ideally together.** (a) Whether detector (1) gets a scheduled or post-merge run. PR #1944 deliberately shipped script-plus-test only: scheduling is an operational change needing its own PR and explicit approval, and joining verify:cheap:internal would force a matching static-pr step in ci.yml via check-gate-manifest. Until something runs it, it only helps whoever remembers to type it. (b) Whether the branch-versus-squash case becomes a second check or a mode of the same script. (c) What a positive costs a human: detector (1) is advisory and exits 0 on purpose, because a deliberate revert is byte-identical to an accidental one at blob level — a scheduled run therefore needs a named owner to triage it, or it becomes ignorable noise. **Next:** decide (a) first; it is the cheapest and it is what turns an existing script into an actual control. **Stop:** do not make either detector auto-fail without deciding (c) — an advisory check flipped to blocking on a signal that cannot distinguish intent will be silenced rather than triaged. | PR #1944 (scripts/audit-merge-loss.mjs); PR #1937 and its cancel request 63419f06; inbox request 829597d4; acf78bf; session 2026-08-14 | 2026-08-14 | | #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 | ## Resolved / archive @@ -477,3 +477,5 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | #234 | task | answer-copy-payload.ts is the single clipboard payload builder for three surfaces and has no documentation | DELIVERED — verified on main 2026-08-14. answer-copy-payload.ts (now src/components/clinical-dashboard/) carries a header documenting the single-builder contract, the three consuming surfaces, and why it sits outside src/lib and outside the design system. PR #1842 merged. | 2026-08-14 | | #213 | task | Stop swallowing fetch and stream errors with empty catch handlers | Closed 2026-08-14. The 2026-08-12 re-measure counted correctly but described the wrong thing: the 3 remaining bare catches under src/ were not fetch/stream swallowing at all. All 3 lived inside render-blocking inline bootstrap script strings — src/lib/theme.ts:46 (localStorage.getItem, then document.cookie) and src/app/layout.tsx:149 (JSON.parse of stored preferences) — where a throw means storage/cookies are unavailable and the correct behaviour is the documented fallback chain (cookie, then OS preference; defaults for density/motion). Each now carries an inline comment stating the throwing condition and the fallback that covers it; no behaviour changed, because there is no logger or toast before React mounts and surfacing the error would trade a correct default appearance for a broken first paint. The genuine fetch/stream catches this row was opened against were already dispositioned by earlier passes (api/answer/stream/route.ts:178,291 and api/search/universal/route.ts:102 carry comments and propagate via controller.error). Added tests/empty-catch-disposition.test.ts, a raw source-text scan asserting every empty catch under src/ carries a comment — raw text rather than an AST because ESLint's no-empty cannot see catches inside template-literal script strings, which is exactly where these 3 hid. Population is 21 empty catches, all dispositioned, 0 bare. | 2026-08-14 | | #245 | rec | responsive-compact CrossModeLinks keeps duplicate rails in the DOM | RESOLVED AS INTENTIONAL — verified on main 2026-08-14. The premise still holds literally (both rails are mounted) but it is now a documented decision, not a defect: cross-mode-links.tsx:220-224 states both rails stay mounted so SSR and first paint agree, hidden/md:hidden use display:none which removes the inactive rail from the accessibility tree, and distinct test ids stop phone vs wide selectors double-counting. Removing a rail would reintroduce the hydration mismatch this comment exists to prevent. PR #1842 merged. | 2026-08-14 | +| #335 | rec | Merge-loss detection covers file-level reverts and inbox-request loss separately; neither covers the other, and the scheduled run is undecided | Closed 2026-08-15 as duplicate follow-up of #324. The delivered file-level audit, the remaining schedule/owner decision, and the distinct inbox-request-loss scope are all retained in #324; no scheduling or CI policy was changed by this consolidation. | 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 | From c307605a2f00ef1170ce57705dba82b838d66d9b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:28:55 +0800 Subject: [PATCH 05/11] fix(issues): preserve reconciliation transaction boundary --- .../{applied => }/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json | 0 .../{applied => }/3156e1c9-82aa-46c3-84e6-0e119015acc6.json | 0 .../{applied => }/b621e863-8304-4331-bf94-da1596dcfa28.json | 0 .../{applied => }/d50512f3-d977-49fd-ba8f-8720f1fc276c.json | 0 docs/outstanding-issues.md | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename docs/outstanding-issues-inbox/{applied => }/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json (100%) rename docs/outstanding-issues-inbox/{applied => }/3156e1c9-82aa-46c3-84e6-0e119015acc6.json (100%) rename docs/outstanding-issues-inbox/{applied => }/b621e863-8304-4331-bf94-da1596dcfa28.json (100%) rename docs/outstanding-issues-inbox/{applied => }/d50512f3-d977-49fd-ba8f-8720f1fc276c.json (100%) diff --git a/docs/outstanding-issues-inbox/applied/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json b/docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json similarity index 100% rename from docs/outstanding-issues-inbox/applied/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json rename to docs/outstanding-issues-inbox/1da43f2b-4b52-4fb7-b39a-e5aa5bf7c7e7.json diff --git a/docs/outstanding-issues-inbox/applied/3156e1c9-82aa-46c3-84e6-0e119015acc6.json b/docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json similarity index 100% rename from docs/outstanding-issues-inbox/applied/3156e1c9-82aa-46c3-84e6-0e119015acc6.json rename to docs/outstanding-issues-inbox/3156e1c9-82aa-46c3-84e6-0e119015acc6.json diff --git a/docs/outstanding-issues-inbox/applied/b621e863-8304-4331-bf94-da1596dcfa28.json b/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json similarity index 100% rename from docs/outstanding-issues-inbox/applied/b621e863-8304-4331-bf94-da1596dcfa28.json rename to docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json diff --git a/docs/outstanding-issues-inbox/applied/d50512f3-d977-49fd-ba8f-8720f1fc276c.json b/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json similarity index 100% rename from docs/outstanding-issues-inbox/applied/d50512f3-d977-49fd-ba8f-8720f1fc276c.json rename to docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 043c402838..461fe2372d 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -208,7 +208,7 @@ removed after current-main verification; it is not missing recommended work. | #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 | | #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 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 two partial requests cancelled in this same batch. PHASE 0 CLOSED including its forced-dispatch proof: live-drift dispatched on main, failed at the drift step, the always() capture still ran, the migration-history step correctly skipped, and the drift-routing job created issue #1963 with the label, run URL, job result, trigger, and full findings. Routing is also covered offline by tests/live-drift-workflow.test.ts. 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 by an independent read-only query. Before/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 definitions were already codified. CORRECTED FIGURES measured 2026-08-14: 10 match_* def_hash mismatches unchanged, 20 missing_live indexes (the observed inventory, not an arithmetic inference from the previous 21 baseline), and 2 unexpected_live. ATTRIBUTION STILL OPEN: migration 20260705180000 recorded 14 executed statements; guard 20260804110240 validates four other indexes and gives no existence bound for this pair. The drop window is 2026-07-05 to 2026-08-02, likely by 2026-07-26; dashboard audit-history pairing remains owner action and #248 stays open. NEXT: Phase 3 RPC reconciliation before Phase 4, per plan ordering. 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 | +| #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 | | #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 | From bf3ca220776083197ede07b5ec32675e0b0542c7 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:28:57 +0800 Subject: [PATCH 06/11] docs(ledger): record PR #1968 review --- ...600dd0d8e9f229c6c9f3d2e2d2709bee0f3d2e35384751e8358.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/652d642c8900c600dd0d8e9f229c6c9f3d2e2d2709bee0f3d2e35384751e8358.record.md diff --git a/docs/branch-review-records/652d642c8900c600dd0d8e9f229c6c9f3d2e2d2709bee0f3d2e35384751e8358.record.md b/docs/branch-review-records/652d642c8900c600dd0d8e9f229c6c9f3d2e2d2709bee0f3d2e35384751e8358.record.md new file mode 100644 index 0000000000..8966365449 --- /dev/null +++ b/docs/branch-review-records/652d642c8900c600dd0d8e9f229c6c9f3d2e2d2709bee0f3d2e35384751e8358.record.md @@ -0,0 +1 @@ +| 2026-08-15 | PR #1968 / claude/ledger-reconcile-batch-4 | 10beb5ea191043e3c425aa7ff32533e93e9f68de | unblocking PR review-and-fix | Fixed #316 forensic-bound and inventory correction through the serialized ledger queue; merged current main cleanly. | ledger-inbox and outstanding-issues self-tests plus checks; ledger write discipline; diff --check (npm 11.17 setup blocked by sandbox cache) | From 6633be6ac127c3f513158f8c92ba34927da7c2d1 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:29:00 +0800 Subject: [PATCH 07/11] fix(issues): queue valid #316 headline correction --- .../60fea622-a448-4e8f-a5d3-0122ab08b2d0.json | 11 +++++++++++ .../b621e863-8304-4331-bf94-da1596dcfa28.json | 12 ------------ .../d50512f3-d977-49fd-ba8f-8720f1fc276c.json | 10 ---------- 3 files changed, 11 insertions(+), 22 deletions(-) create mode 100644 docs/outstanding-issues-inbox/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json delete mode 100644 docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json delete mode 100644 docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json diff --git a/docs/outstanding-issues-inbox/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json b/docs/outstanding-issues-inbox/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json new file mode 100644 index 0000000000..5238072cb4 --- /dev/null +++ b/docs/outstanding-issues-inbox/60fea622-a448-4e8f-a5d3-0122ab08b2d0.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "id": "60fea622-a448-4e8f-a5d3-0122ab08b2d0", + "createdOn": "2026-08-15", + "action": "update", + "payload": { + "id": "#316", + "summary": "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", + "source": "PR #1968 review against docs/audit/live-drift-forensics-2026-08.md, 2026-08-15" + } +} diff --git a/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json b/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json deleted file mode 100644 index 90e57d1f40..0000000000 --- a/docs/outstanding-issues-inbox/b621e863-8304-4331-bf94-da1596dcfa28.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "version": 1, - "id": "b621e863-8304-4331-bf94-da1596dcfa28", - "createdOn": "2026-08-15", - "action": "update", - "payload": { - "id": "#316", - "summary": "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", - "detail": "Combined 2026-08-14 update, superseding two partial requests cancelled in this same batch. PHASE 0 CLOSED including its forced-dispatch proof: live-drift dispatched on main, failed at the drift step, the always() capture still ran, the migration-history step correctly skipped, and the drift-routing job created issue #1963 with the label, run URL, job result, trigger, and full findings. Routing is also covered offline by tests/live-drift-workflow.test.ts. 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 by an independent read-only query. Before/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 definitions were already codified. CORRECTED FIGURES measured 2026-08-14: 10 match_* def_hash mismatches unchanged, 20 missing_live indexes (the observed inventory, not an arithmetic inference from the previous 21 baseline), and 2 unexpected_live. ATTRIBUTION STILL OPEN: migration 20260705180000 recorded 14 executed statements; guard 20260804110240 validates four other indexes and gives no existence bound for this pair. The drop window is 2026-07-05 to 2026-08-02, likely by 2026-07-26; dashboard audit-history pairing remains owner action and #248 stays open. NEXT: Phase 3 RPC reconciliation before Phase 4, per plan ordering. Evidence: docs/audit/live-drift-forensics-2026-08.md.", - "source": "PR #1968 review against docs/audit/live-drift-forensics-2026-08.md, 2026-08-15" - } -} diff --git a/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json b/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json deleted file mode 100644 index d128495be7..0000000000 --- a/docs/outstanding-issues-inbox/d50512f3-d977-49fd-ba8f-8720f1fc276c.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": 1, - "id": "d50512f3-d977-49fd-ba8f-8720f1fc276c", - "createdOn": "2026-08-15", - "action": "cancel", - "payload": { - "requestId": "3d0adf39-ec7a-4fa3-9309-057a193410de", - "reason": "Superseded by a consolidated #316 update that preserves this Phase 0 evidence and corrects the headline to the verified current inventory of 20." - } -} From a7be5f4f4cbd98249fb3f884da815e0a9262be09 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:29:02 +0800 Subject: [PATCH 08/11] docs(ledger): refresh PR #1968 review record --- ...2f1ef7b6b9a3b4188835f16432fe3243bb89a199885f6a09730.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/5bffd23d66a7e2f1ef7b6b9a3b4188835f16432fe3243bb89a199885f6a09730.record.md diff --git a/docs/branch-review-records/5bffd23d66a7e2f1ef7b6b9a3b4188835f16432fe3243bb89a199885f6a09730.record.md b/docs/branch-review-records/5bffd23d66a7e2f1ef7b6b9a3b4188835f16432fe3243bb89a199885f6a09730.record.md new file mode 100644 index 0000000000..18c329c702 --- /dev/null +++ b/docs/branch-review-records/5bffd23d66a7e2f1ef7b6b9a3b4188835f16432fe3243bb89a199885f6a09730.record.md @@ -0,0 +1 @@ +| 2026-08-15 | PR #1968 / claude/ledger-reconcile-batch-4 | 0a49842ccf12ea4508533b43bb0b7d7a2086eac6 | unblocking PR review-and-fix | Synced current main and reconciled the base inbox; queued the verified #316 headline correction for the next serialized transaction. | ledger inbox dry-run/check; outstanding-issues and ledger write-discipline checks; diff --check (npm setup blocked by sandbox cache) | From ead94afd0c4b63bad1aec2f8ee05bd0c704482ca Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:33:48 +0800 Subject: [PATCH 09/11] chore(issues): reconcile latest base queue --- .../0b6a19d6-c586-4ea3-8fc6-fa8ee941d922.json | 0 .../1860498b-2e89-41cf-85f1-e62dc3cca057.json | 0 .../256e9f44-2c1d-4068-a3e6-74baf3de271d.json | 0 .../3d861de3-4402-4e40-8dec-8a84e10093c2.json | 0 .../42dd600a-b032-45a5-8c53-cb185f08cf13.json | 0 .../5cf082dd-60c3-42ef-95dc-82b9c255e9be.json | 0 .../97e7beeb-97e5-43a6-b238-065b42070969.json | 0 .../b53c5e20-2c6f-41da-a202-bcf5c7609938.json | 0 .../b935dad0-394c-435a-acc5-21df750c7e52.json | 0 .../bcea44bc-5738-41b8-96f1-e96b1488ab29.json | 0 .../c5e84848-9a84-442e-a818-1b53df5693fa.json | 0 docs/outstanding-issues.md | 55 +++++++++---------- 12 files changed, 26 insertions(+), 29 deletions(-) rename docs/outstanding-issues-inbox/{ => applied}/0b6a19d6-c586-4ea3-8fc6-fa8ee941d922.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/1860498b-2e89-41cf-85f1-e62dc3cca057.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/256e9f44-2c1d-4068-a3e6-74baf3de271d.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/3d861de3-4402-4e40-8dec-8a84e10093c2.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/42dd600a-b032-45a5-8c53-cb185f08cf13.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/5cf082dd-60c3-42ef-95dc-82b9c255e9be.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/97e7beeb-97e5-43a6-b238-065b42070969.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/b53c5e20-2c6f-41da-a202-bcf5c7609938.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/b935dad0-394c-435a-acc5-21df750c7e52.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/bcea44bc-5738-41b8-96f1-e96b1488ab29.json (100%) rename docs/outstanding-issues-inbox/{ => applied}/c5e84848-9a84-442e-a818-1b53df5693fa.json (100%) diff --git a/docs/outstanding-issues-inbox/0b6a19d6-c586-4ea3-8fc6-fa8ee941d922.json b/docs/outstanding-issues-inbox/applied/0b6a19d6-c586-4ea3-8fc6-fa8ee941d922.json similarity index 100% rename from docs/outstanding-issues-inbox/0b6a19d6-c586-4ea3-8fc6-fa8ee941d922.json rename to docs/outstanding-issues-inbox/applied/0b6a19d6-c586-4ea3-8fc6-fa8ee941d922.json diff --git a/docs/outstanding-issues-inbox/1860498b-2e89-41cf-85f1-e62dc3cca057.json b/docs/outstanding-issues-inbox/applied/1860498b-2e89-41cf-85f1-e62dc3cca057.json similarity index 100% rename from docs/outstanding-issues-inbox/1860498b-2e89-41cf-85f1-e62dc3cca057.json rename to docs/outstanding-issues-inbox/applied/1860498b-2e89-41cf-85f1-e62dc3cca057.json diff --git a/docs/outstanding-issues-inbox/256e9f44-2c1d-4068-a3e6-74baf3de271d.json b/docs/outstanding-issues-inbox/applied/256e9f44-2c1d-4068-a3e6-74baf3de271d.json similarity index 100% rename from docs/outstanding-issues-inbox/256e9f44-2c1d-4068-a3e6-74baf3de271d.json rename to docs/outstanding-issues-inbox/applied/256e9f44-2c1d-4068-a3e6-74baf3de271d.json diff --git a/docs/outstanding-issues-inbox/3d861de3-4402-4e40-8dec-8a84e10093c2.json b/docs/outstanding-issues-inbox/applied/3d861de3-4402-4e40-8dec-8a84e10093c2.json similarity index 100% rename from docs/outstanding-issues-inbox/3d861de3-4402-4e40-8dec-8a84e10093c2.json rename to docs/outstanding-issues-inbox/applied/3d861de3-4402-4e40-8dec-8a84e10093c2.json diff --git a/docs/outstanding-issues-inbox/42dd600a-b032-45a5-8c53-cb185f08cf13.json b/docs/outstanding-issues-inbox/applied/42dd600a-b032-45a5-8c53-cb185f08cf13.json similarity index 100% rename from docs/outstanding-issues-inbox/42dd600a-b032-45a5-8c53-cb185f08cf13.json rename to docs/outstanding-issues-inbox/applied/42dd600a-b032-45a5-8c53-cb185f08cf13.json diff --git a/docs/outstanding-issues-inbox/5cf082dd-60c3-42ef-95dc-82b9c255e9be.json b/docs/outstanding-issues-inbox/applied/5cf082dd-60c3-42ef-95dc-82b9c255e9be.json similarity index 100% rename from docs/outstanding-issues-inbox/5cf082dd-60c3-42ef-95dc-82b9c255e9be.json rename to docs/outstanding-issues-inbox/applied/5cf082dd-60c3-42ef-95dc-82b9c255e9be.json diff --git a/docs/outstanding-issues-inbox/97e7beeb-97e5-43a6-b238-065b42070969.json b/docs/outstanding-issues-inbox/applied/97e7beeb-97e5-43a6-b238-065b42070969.json similarity index 100% rename from docs/outstanding-issues-inbox/97e7beeb-97e5-43a6-b238-065b42070969.json rename to docs/outstanding-issues-inbox/applied/97e7beeb-97e5-43a6-b238-065b42070969.json diff --git a/docs/outstanding-issues-inbox/b53c5e20-2c6f-41da-a202-bcf5c7609938.json b/docs/outstanding-issues-inbox/applied/b53c5e20-2c6f-41da-a202-bcf5c7609938.json similarity index 100% rename from docs/outstanding-issues-inbox/b53c5e20-2c6f-41da-a202-bcf5c7609938.json rename to docs/outstanding-issues-inbox/applied/b53c5e20-2c6f-41da-a202-bcf5c7609938.json diff --git a/docs/outstanding-issues-inbox/b935dad0-394c-435a-acc5-21df750c7e52.json b/docs/outstanding-issues-inbox/applied/b935dad0-394c-435a-acc5-21df750c7e52.json similarity index 100% rename from docs/outstanding-issues-inbox/b935dad0-394c-435a-acc5-21df750c7e52.json rename to docs/outstanding-issues-inbox/applied/b935dad0-394c-435a-acc5-21df750c7e52.json diff --git a/docs/outstanding-issues-inbox/bcea44bc-5738-41b8-96f1-e96b1488ab29.json b/docs/outstanding-issues-inbox/applied/bcea44bc-5738-41b8-96f1-e96b1488ab29.json similarity index 100% rename from docs/outstanding-issues-inbox/bcea44bc-5738-41b8-96f1-e96b1488ab29.json rename to docs/outstanding-issues-inbox/applied/bcea44bc-5738-41b8-96f1-e96b1488ab29.json diff --git a/docs/outstanding-issues-inbox/c5e84848-9a84-442e-a818-1b53df5693fa.json b/docs/outstanding-issues-inbox/applied/c5e84848-9a84-442e-a818-1b53df5693fa.json similarity index 100% rename from docs/outstanding-issues-inbox/c5e84848-9a84-442e-a818-1b53df5693fa.json rename to docs/outstanding-issues-inbox/applied/c5e84848-9a84-442e-a818-1b53df5693fa.json diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 461fe2372d..1b1f4cf883 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -87,28 +87,25 @@ removed after current-main verification; it is not missing recommended work. | 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 | `#189` | A2 | Specialist — search/RAG budgets | After #098 route residual; before collapsing RPCs | 2–4 hours + canary if behaviour | Pin /api/search route-level round trips and disposition the x3 text RPC probes — a counting-proxy budget drives `POST` `/api/search` (auth/ratelimit/scope/enrichment/telemetry), and the retrieval-core finding that `matc… | -| 36 | `#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… | -| 37 | `#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. | -| 38 | `#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. | -| 39 | `#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. | -| 40 | `#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. | -| 41 | `#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. | -| 42 | `#194` | A3 | High — scripts/docs hygiene | Next scripts archive pass | 1–2 hours | L1: Archive retired backfill one-shots and dead ci-change-scope token — retired `backfill:*` one-shots and the dead `ci-change-scope` token are archived/removed with docs/script index updated. | -| 43 | `#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. | -| 44 | `#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. | -| 45 | `#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. | -| 46 | `#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. | -| 47 | `#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. | -| 48 | `#215` | Optional | High — image perf | Next image/PWA pass | 2–4 hours | Image-optimization basics for lightbox, PWA lifecycle, demo PNGs. **Stop:** optional until measured need. | -| 49 | `#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. | -| 50 | `#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. | -| 51 | `#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. | -| 52 | `#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. | -| 53 | `#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. | -| 54 | `#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. | -| 55 | `#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. | -| 56 | `#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. | +| 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. | @@ -166,18 +163,14 @@ removed after current-main verification; it is not missing recommended work. | #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 | | #175 | P2 | task | Therapy modality is now null on all 205 records and needs curation or removal | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: src/data/therapies-source.json holds 205 records and 0 carry a modality value, exactly as described. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. **Detail:** the source catalogue derived `modality` from each record's own tag list — all 205 records had one, every value was also present in that record's `tags`, and the whole catalogue collapsed to CBT/ACT/DBT. It mislabelled the treatments it could not describe: ECT and rTMS as "ACT", Psychoanalysis and Psychodynamic Psychotherapy as "CBT", MBT and TFP as "DBT". Pre-existing on main, surfaced by the PR #1489 review. The generator emits it only when the source curates a value that is not already a tag, which today means null for 205/205 on the index projections *and* the full catalogue the detail/recommend screens load (`catalogue: "full"`), so the two chips (`detail-screen.tsx:49`, `recommend-screen.tsx:115`) never render and `select.ts:117` contributes no same-modality point. Removal was provably search-neutral: `src/lib/therapies.ts` scores with boolean `haystack.includes(token)`, not term frequency, and every modality value was already contributed by `tags.join(" ")` in the same haystack. **Next:** one of two — curate real modality values in `src/data/therapies-source.json` (clinical work, needs the psychiatrist), or drop the field from `types.ts`, `src/lib/therapies.ts`, the two chips and `select.ts`. **Stop:** do not reinstate the tag-derived value to make the chips reappear; a guess rendered as curated fact is the defect. `tests/therapy-compass-pathways.test.ts` pins the echo invariant on both the index and the full catalogue asset. Renumbered from this PR's original `#169` because `main` claimed `#169`–`#174` while the branch was open. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | | #183 | P3 | task | Create Sentry metric alert for production DB span p95 > 500ms | **DEPRIORITISED 2026-08-12 (yield review against current main).** A production DB p95 latency alert for a system with one user; the alert has nobody to wake. Revisit alongside #027 when real usage exists. Still blocked 2026-08-01 closeout: SUPABASE_ACCESS_TOKEN and SENTRY_AUTH_TOKEN missing from session env; Sentry MCP OAuth can list/get alerts but has no create tool; browser hits login wall; no metric rules exist yet on clinibase-xz. Create Metric Alert: p95(span.duration), filter span.op:db, environment production, threshold >500ms, notify Active Members. Provide SENTRY_AUTH_TOKEN in session to finish via sentry alert metrics create. | session 2026-07-31 db-query-perf follow-up | 2026-07-31 | -| #189 | P2 | task | Pin /api/search route-level round trips and disposition the x3 text RPC probes | **Outcome:** a counting-proxy budget drives `POST` `/api/search` (auth/ratelimit/scope/enrichment/telemetry), and the retrieval-core finding that `match_document_chunks_text_v2` and `match_document_table_facts_text_v2` each issue three times per search is either documented as intentional or collapsed under the RAG canary gate. **Source:** residual next actions on `#098` after answer-path and retrieval-core budgets landed. **Next:** (a) route-level budget following `tests/answer-route-preamble.test.ts`; (b) decide probe vs collapse — behaviour change needs RAG flag + canary. **Stop:** do not change retrieval assembly without approval. | session 2026-07-31; #098 residual; tests/search-round-trip-budget.test.ts | 2026-07-31 | | #190 | P3 | task | X3: Finish rag.ts monolith decomposition | **DEPRIORITISED 2026-08-12 (yield review against current main).** Structural churn on the most safety-critical and most protected file in the repo, with no user-facing benefit and real behaviour-drift risk on a live-validated clinical answer path. Do the extractions opportunistically when a feature change already requires being inside a region, not as a standalone project. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: src/lib/rag/rag.ts measures 4,362 lines — still the monolith this row describes; the decomposition has not started. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** `src/lib/rag/rag.ts` is decomposed into focused modules per `docs/maturity-backlog-workorders.md` X3, with existing offline RAG contracts green. **Status:** IN PROGRESS (DocumentViewer/Dashboard extractions done; rag.ts remains). **Next:** continue safe extractions only with the RAG flag before editing protected surfaces; one verified draft PR per unit. **Stop:** no behaviour change without canary when retrieval/answer paths move. | docs/maturity-backlog-workorders.md X3; #086 | 2026-07-31 | | #191 | P3 | task | X5: ACL-migration consolidation (provider-gated) | **Outcome:** ACL-related migrations are consolidated per maturity work-order X5 without weakening owner-scope/RLS. **Next:** DB-owner approved window only; live-DB provider confirmation required before apply. **Stop:** no hosted apply from an agent session without explicit approval. | docs/maturity-backlog-workorders.md X5; #086 | 2026-07-31 | | #192 | P3 | task | X6: Raise clinical/retrieval/answer coverage floors | **Outcome:** coverage floors for clinical, retrieval, and answer domains meet the maturity X6 targets with CI enforcing them. **Next:** set floors from current honest baselines; expand tests only where gaps are real. **Stop:** do not lower floors to pass. | docs/maturity-backlog-workorders.md X6; #086 | 2026-07-31 | | #193 | P3 | task | X7: Complete the remaining src/lib domain-directory reorg | **DEPRIORITISED 2026-08-12 (yield review against current main).** Mechanical directory moves with import-graph risk and no user-facing benefit. Same reasoning as #190: fold into work already touching the files. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: Six domain directories exist under src/lib (extractors, observability, rag, supabase, validation, webhooks); the reorg is genuinely partial, as the row says. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** remaining `src/lib` clusters sit in their domain directories per X7 follow-on to X2. **Next:** move non-protected clusters first; answer/retrieval clusters need the RAG flag. **Stop:** no drive-by behaviour edits inside moves. | docs/maturity-backlog-workorders.md X7; #086 | 2026-07-31 | -| #194 | P3 | task | L1: Archive retired backfill one-shots and dead ci-change-scope token | VERIFIED CORRECT 2026-08-12 — re-checked against merged main and left open: Still open: five backfill one-shots remain under scripts/ (backfill-document-covers.mjs, backfill-document-tags.ts, backfill-enrichment.ts, backfill-gold-document-labels.ts, backfill-smart-index.ts). No dead ci-change-scope token was found, so that half may already be gone — confirm before archiving the row. **Outcome:** retired `backfill:*` one-shots and the dead `ci-change-scope` token are archived/removed with docs/script index updated. **Status:** IN PROGRESS (#1033 archived m13/july8; backfills still open). **Next:** finish backfill archive + token cleanup in a docs/scripts PR. **Stop:** do not break CI classifiers. | docs/maturity-backlog-workorders.md L1; #086 | 2026-07-31 | | #195 | P3 | task | M1: Repo-host hardening (branch protection and required checks) | **Outcome:** GitHub branch-protection rulesets and required checks match audit §8 / maturity M1. **Next:** maintainer GitHub UI work; not a repo-file change. Record evidence in the ledger when done. **Stop:** agents must not weaken required checks. | docs/maturity-backlog-workorders.md M1; #086 | 2026-07-31 | | #206 | P2 | task | AnswerState partial_retrieval has no app-facing producer | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: `partial_retrieval` is declared in src/lib/answer-state-types.ts:63 and handled in answer-clipboard.ts:75, but nothing in src/app or the retrieval path produces it — still no app-facing producer, as the row says. Do not synthesise it from candidate counts. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. PR-E step 0 found nothing in the client payload names which expected sources were unavailable (retrievalDiagnostics = candidate counts; conflictsOrGaps = prose). RetrievalStateBanner supports the state but PR-J adoption can only emit ready/stale_evidence/source_only. Next action: decide whether a separate RAG contract PR should add a named missing-source signal (governance preflight + RAG impact line + offline eval); until then do not synthesise the state from counts. Pinned by tests/answer-state-contract.test.ts and SPEC 13 / COMPONENTS 2. | PR-E step 0, session 2026-08-02 | 2026-08-02 | -| #210 | P2 | task | npm run ensure generates .next/dev types that break typecheck and every Playwright build | RE-SCOPED AGAIN 2026-08-13 (re-filed: the 2026-08-12 correction was lost when PR #1880 landed under the inbox architecture without a request being written for it). Half of this row is already fixed and its prescribed fix is REFUTED — do not apply the first suggestion. (1) FIXED: `npm run typecheck` runs `tsconfig.typecheck.json` (added in 450690f citing this row), which sets its own include and excludes `.next/**`; verified green with `.next/dev/types/validator.ts` present. (2) REFUTED: dropping `.next/dev/types/**/*.ts` from tsconfig.json does NOT hold. Next 16 emits that glob itself — `getTypeDefinitionGlobPatterns` (node_modules/next/dist/lib/typescript/type-paths.js) adds both `.next/types` and `.next/dev/types` deliberately 'to avoid tsconfig churn when switching between dev/build modes', and `writeConfigurationDefaults` adds a missing glob back when Next reads the root config directly. Deleting the line only re-creates an uncommitted change. (3) STILL OPEN, narrower than originally written: `scripts/run-playwright.mjs` writes an isolated tsconfig with `extends: '../../tsconfig.json'` and no include of its own, so it inherits the repo-root globs. The recorded `tsc --showConfig` probe resolved `../../.next/dev/types/**/*.ts`, and `--listFilesOnly` pulled in the root dev types including validator.ts. Next's API checker filters dev types with `getDevTypesPath`, but the default `experimental.useTypeScriptCli: true` path uses `runTypeCheckCli` to invoke `tsc --project` against the child config, so it honours the inherited include verbatim. Next: give the isolated tsconfig its own include/exclude (its run root is `.next-playwright/`, not under `.next/`, so excluding the repo-root `.next` keeps the run's own dist types). NOT PROVEN end-to-end: the failing Playwright build was not reproduced. Correcting the previous explanation, `next build` does not mutate this child config: Next 16.3 `writeConfigurationDefaults` returns immediately when the parsed config contains `extends` or `references`, and this config always contains `extends`. Confirm the remaining inherited-include hypothesis with one focused `verify:ui` build before and after the child include/exclude change, and hash the child tsconfig immediately before and after the build to prove it remains byte-identical. Stop: do not remove typecheck from the gate, and do not retry the include deletion. | session 2026-08-02 /ledger sweep; docs/review-findings-2026-08-02.md | 2026-08-02 | | #211 | P3 | task | Plan and start the noUncheckedIndexedAccess migration | **DEPRIORITISED 2026-08-12 (yield review against current main), and that judgment still holds** — each site is a local judgment, no open ledger row traces a defect to unchecked indexed access, and the diff conflicts with every open PR. Do it in scoped batches after the clinical and CI-trust work. This update carries that conclusion forward rather than replacing it; what has changed is that the batches now exist on paper and the count was wrong. **RE-MEASURED AND PLANNED 2026-08-14 in PR #1944.** The staged plan is docs/no-unchecked-indexed-access-migration-plan.md; the migration has NOT started and tsconfig.json is unchanged, so this row stays open and stays deprioritised. Measured against main at d47aa6d rather than reusing the 2026-08-02 figure: **1,445 errors across 269 files, up from 1,266**. The drift is itself a finding — the flag is off, so nothing stops new unchecked indexing landing, and any plan built on the stale count under-scopes. The measurement also reshapes the job in a way that supports doing it in batches: tests/ (713) plus design-scratch mockups (237) are two-thirds of the population and carry no production consequence, so the genuinely risky remainder is about 500 errors, not 1,445. Shape is 71 percent TS2532/TS18048, which a guard fixes; the 368 TS2345/TS2322 need a real decision about what the absent case means. Hot spots unchanged and confirmed: answer-verification.ts (41), rag-extractive-answer.ts (23), worker/main.ts (23), evidence.ts (19). Six stages, cheapest first, each flagged mechanical or manual with its own gate. Key constraint the plan records: noUncheckedIndexedAccess is a whole-project option and narrowing include does not isolate a directory, because TypeScript still reports errors in every transitively imported file — so the flag flips exactly once in the final PR and intermediate stages are verified by a baseline ratchet in the shape of scripts/design-system-contract-baseline.json. Stage 6 touches src/lib/rag/**, so the plan writes out the flag-before-editing, RAG impact line, and live-canary obligations. Stop unchanged: do not flip the flag on main ahead of the final stage. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | -| #212 | P3 | task | Replace as unknown as casts and unvalidated JSON.parse with Zod or runtime guards | **DEPRIORITISED 2026-08-12 (yield review against current main).** 40 casts at trust boundaries. Same reasoning as #211: worth doing, no measured defect traces to it, and it competes with clinical work for review attention. VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: 40 `as unknown as` casts remain under src/ — the row's population is intact. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. 48 as unknown as casts and ~24 unvalidated JSON.parse calls across src/ trust Supabase, OpenAI, localStorage, file metadata and extraction boundaries. Start with src/lib/rag/rag.ts and src/app/api/* routes, mirroring existing Zod use in src/lib/validation/body.ts and src/lib/extractors/document.ts. See docs/review-findings-2026-08-02.md sections 2.2, 2.3 and 8. | session 2026-08-02 /ledger sweep — docs/review-findings-2026-08-02.md | 2026-08-02 | -| #215 | P3 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | **Outcome:** two of the four image-only findings from the 2026-08-02 audit are shipped; two remain open for an explicit implementation-or-drop decision. **RESTATED 2026-08-13 after inspection against main 2d270392 — two of the four items already shipped and the row no longer describes them as open.** DONE: src/components/clinical-dashboard/image-lightbox.tsx carries decoding="async" (Phase 0, PR #1660), asserted by tests/signed-image.dom.test.tsx. DONE: SignedImage has the priority prop for above-fold evidence — it also skips the IntersectionObserver deferral entirely — and document viewer Phase 3 (PR #1772) added the other half of that pair: an explicit fetchPriority of high when priority is set and low otherwise, so a deferred rail figure does not contend with the page's own above-the-fold work. The document rail additionally passes a 240px observer root margin against the shared 640px default. REMAINING, both confirmed by inspection rather than inferred: (a) src/components/pwa-lifecycle.tsx still has no decoding attribute; (b) public/demo-documents/ still contains no .webp — the PNGs are ~80 KB each and served as-is, so the conversion with a PNG fallback has not been done. **Next:** apply decoding=async in pwa-lifecycle.tsx, and either convert the demo PNGs to WebP with a PNG fallback or record that an ~80 KB synthetic demo asset is not worth the build step. **Stop:** do not treat this row as covering the broader performance findings — those live under #016, #013, #117 and #147. | session 2026-08-02 /ledger sweep — docs/audit/performance-image-cwv-audit-2026-08-02.md | 2026-08-02 | +| #212 | P3 | task | Replace as unknown as casts and unvalidated JSON.parse with Zod or runtime guards | RAG-surface tranche implemented via PR #1946: new src/lib/rag/rag-row-contracts.ts (assertRetrievalRows, Zod-backed, RetrievalRowShapeError) replaces 4 'as SearchResult[]' casts in rag.ts (hybrid telemetry/merge, vector-fallback, document-summary context) with runtime shape assertion -- strict on id/document_id/content (not-null in schema) and the 4 score fields (nullish), loose via z.looseObject on everything else so RPC-version column differences don't break. Assertion-only (no transform), so object identity/key order is unchanged on success; errors carry only Zod issue paths, never row content. tests/rag-retrieval-row-contract.test.ts (8 cases) plus tests/rag-imputation-contract.test.ts kept green as evidence no ranking/comparator logic moved. Deliberately untouched: query_embedding casts (a deliberate repo-wide convention satisfying Supabase-generated RPC types) and outbound Json serialization casts. Remaining #212 population (~11 as-unknown-as casts) confirmed via grep to live OUTSIDE rag.ts's import graph: src/app/api/documents/route.ts (4), src/app/api/ingestion/{batches,jobs,quality}/route.ts (6), src/app/api/jobs/route.ts (1) -- legitimate non-RAG-flagged tranche-2 candidates, not yet started. | PR #1946, session 2026-08-14 | 2026-08-02 | | #222 | P3 | task | Headers surface only partially converged in PR-J: mode-home-template and search-results-header-band untouched | VERIFIED CORRECT 2026-08-12 — re-checked against merged main and left open: Still unconverged: src/components/mode-home-template.tsx defines ModeHomeStatusNotice locally (:232) and imports neither PageHeader nor the DS EmptyState; search-results-header-band.tsx is likewise untouched. Note the adjacency — in-flight PR #1842 delegates ModeHomeStatusNotice to the DS EmptyState under #221, which is a different conversion from the PageHeader question this row asks. Re-check after #1842 merges. Builder A converged DsmPageHeader, InformationPageHeader and InformationPageBreadcrumbs onto PageHeader plus Breadcrumb, and declined two files with reasons. mode-home-template.tsx ModeHomeHero is a centred display hero on the fluid text-hero token and is the slot the in-flow phone composer sits in, so converging it onto a left-aligned PageHeader is a redesign of 13 mode homes that collides with the one-composer-per-page contract. search-results-header-band.tsx is a results spine carrying status, counts and filters, not a page-title stack, so its pin tests/search-results-header-band.dom.test.tsx remains unflipped. Both are defensible; both leave the headers surface partially adopted. Next action: decide whether either is in scope at all, or record them as permanently out of the PageHeader vocabulary. Found during PR-J adoption, 2026-08-03. | session 2026-08-03 (PR-J Wave 5, Builder A) | 2026-08-02 | | #231 | P1 | issue | Generation fallbacks no longer stick in answer cache; lithium generation quality still falls back safely | PARTIAL 2026-08-12: This PR fixes the clinically consequential stale-fallback path: every answer whose routing or degraded reason contains generation_fallback is excluded from rag_response_cache. Offline evidence: 96 focused answer-route tests and 574 RAG fixture/contract tests passed. Approved live baseline/final canaries preserved 36/36 document and content recall at 1.0 with zero per-case reciprocal-rank regressions; the final 44-case answer gate had zero citation or numeric-grounding failures. A budget extension was tested and rejected: four cache-bypassed 'Lithium dosing?' probes remained grounded, cited safe extractive fallbacks at 35-40 second candidate budgets; the decisive 40-second probe completed generation in 25.272 seconds and 27.237 seconds total with route_deadline_exceeded=false, but failed generation quality. Therefore OPENAI_ANSWER_TIMEOUT_MS and the route budget are not the current residual binding cause. INSTRUMENT NOW EXISTS 2026-08-14: the "Next: instrument" half of this row is done. Commit a3bc4da adds scripts/probe-generation-quality.ts — one cache-bypassed live answer reporting the structured generation_quality_gate_reasons, provider-backed, refusing demo mode, never caching or logging the probe. The same commit adjudicates PR #1861: superseded for phase 1, close recommended, with the numeric-retry half deferred to phase 2 pending probe evidence. So do not review #1861 as though it were the live fix, and do not re-implement the probe. Next: run scripts/probe-generation-quality.ts in an environment that has OPENAI and Supabase credentials — it is blocked in offline containers, which is why it has not been run yet — then make a separate bounded output-quality fix with an offline fixture and live canary. Stop: do not increase route/provider timeouts or cache any generation fallback. INCIDENT ADDENDUM 2026-08-14 (later the same day): rung-2 evidence was then measured live - supabase_rpc_latency_ms 31610 on a semantic query (route budget 25000 starved generation), caused by the #316 dropped trigram indexes; after their owner-approved restore, 1535 (text fast path) / 8519 (hybrid). Pre-generation latency was the binding residual cause of semantic-query source-only fallbacks in that window; evidence in docs/audit/live-drift-forensics-2026-08.md. S1 (A1 phase 2) must re-verify generation_quality_gate:* dominance on healthy latency (run the probe with node --env-file=.env.local, which the probe does not load itself) before choosing a code mitigation rung. The route-budget stop condition stands unchanged. | sessions 2026-08-14: instrument adjudication + live incident probes (owner-authorized Supabase connector) | 2026-08-04 | | #235 | P3 | task | ADOPTION.md section 7 proof shots exist for only four of the adopted surfaces | CLOSURE ATTEMPTED AND REJECTED 2026-08-14 — read this before closing again. PR #1940 queued a `done` for this row citing ADOPTION.md section 7.1's per-surface executable-evidence table; the closure was cancelled on review with the reason "executable evidence does not replace the requested desktop and phone proof shots". The cancellation is correct, and the trap is worth naming: section 7.1 opens with "This PR records executable evidence RATHER THAN committing image baselines", so the very section that looks like the evidence says in its first line that it is not. A test that proves a component is mounted is not a picture of the surface, and this row asks for the picture. IN FLIGHT note retired: PR #1842 merged, so the do-not-start warning no longer applies. The requirement is unchanged. The adoption contract asks for a proof shot per adopted surface. Wave 5 captured four - DSM header, settings rows, patient panel, answer surface - and none for the forms fold, the catalogue and docs surfaces, the headers convergence, or the empty states adopted since. Section 7 therefore reads as complete while most of the adoption is unevidenced, which matters because the proof shot is what a later reader uses to tell an intended restyle from a regression (the #229 DSM eyebrow was almost rediscovered as a defect for exactly this reason). Next action: capture the missing shots against a warmed local server (npm run ensure) and attach them to section 7. Cheap and mechanical - no gate, no provider access. Stop: this is not the visual-baseline harness (#118) - do not commit Playwright snapshot PNGs or flip that job to blocking. Stop: do not close this row on unit, DOM or contract evidence of any kind. | session 2026-08-04 (DS V2 Wave 5 close-out capture) | 2026-08-04 | @@ -199,11 +192,11 @@ 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 | **CORRECTS this row's original text, which was wrong on its central claim.** FINDING 1 IS REFUTED (2026-08-12). As first written it asserted that controls carrying `min-h-tap` have their declaration "overridden to 0", blamed "likely an unlayered component class in globals.css", and treated the six shapes as a live 48px-floor defect. All of that is wrong, and acting on it would have caused a regression. **What actually zeroes the min-height is the source itself, deliberately:** the sites carry an explicit `sm:` step-down beside `min-h-tap`. The two 36px shapes are exact matches — `services-navigator-page.tsx:217` is `grid min-h-tap min-w-tap … sm:h-9 sm:min-h-0 sm:w-9 sm:min-w-0` and `:286` is `inline-flex min-h-tap min-w-[94px] … sm:h-9 sm:min-h-0`. `sm:min-h-0` IS the computed `min-height: 0px`, and `sm:h-9` IS the rendered 36px. Seven `min-h-tap` sites carry `sm:min-h-0`; the wider pattern is larger still — `inline-flex min-h-tap items-center` alone appears with `sm:min-h-0` (4), `sm:min-h-7` (2), `sm:min-h-8` (2), `sm:min-h-9` (4), `sm:min-h-10` (8) and `sm:min-h-12` (1). **`min-h-tap` is a PHONE floor that desktop deliberately releases**, which is why the audit only sees it below the floor: `tests/ui-style-contract.spec.ts:97` navigates at the project's desktop viewport, so every `sm:`-and-up override is in force at measurement time. The audit was measuring intended design and reporting it as an overridden floor. **Do NOT "fix" these** — removing the step-downs would pin every desktop control to 48px and is a visual regression across the app, not a WCAG improvement (the phone contract already exceeds both AA 2.5.8 and AAA 2.5.5). The `declared < tapFloor - 0.5 continue` skip at `:116` is therefore correct at desktop width and is NOT the same structural blind spot as the `h-10` case in `#265`. FINDING 2 STANDS UNCHANGED and is the whole of the remaining work: a rendered-interactive enumeration on `/services?q=CMHT&run=1` is NOT DETERMINISTIC — six runs against one production build returned 6, 5, 4, 3, 3 and 9 distinct control shapes, largely disjoint; `waitForLoadState('networkidle')` plus deduplication to distinct shapes did not fix it, and two consecutive agreeing runs were coincidence. The enumeration was written, shown to find genuine defects, and REVERTED rather than landed, because that spec runs in the required Production UI job via `productionSpecPattern` and `scripts/playwright-pr-shards.mjs`, so an intermittent version would block every merge. **Next, revised:** (1) build the deterministic surface — a static route with no async search, or a fixed seeded state; (2) run the tap enumeration **at a phone viewport**, where `min-h-tap` is unreleased and the measurement is meaningful, rather than at desktop where the floor is intentionally lifted; a phone layout is also the simpler, more deterministic surface, so (1) and (2) push the same way. Step (2) of the original row — "find what zeroes min-height on the min-h-tap carriers" — is CLOSED by this correction: the answer is `sm:min-h-0`, and it is intended. **Stop:** do not re-land the enumeration on a live-search route; do not quarantine a brand-new test to get it merged (quarantine is for flaky tests already trusted, and policy needs three reproductions on one SHA via `tests/flake-ledger.json`); do not lower any production tap target, and never to `min-h-11` (known `ui-smoke` sub-pixel flake; production uses the 48px token). | session 2026-08-09 — M2 gate 2 enumeration (#265); finding 1 refuted session 2026-08-12 against `origin/main` 4587f78 (`services-navigator-page.tsx:217,286`; `tests/ui-style-contract.spec.ts:97,116`) | 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 | Raised by the Codex reviewer on PR #1858 and correct. docs/filter-contract.md section 5 sets density by option count: <=5 chips, 6-20 dense full-width list with a right-aligned count column and group headings, >20 or >3 groups adds find-a-filter and collapse-by-default. **PARTIALLY DELIVERED 2026-08-13, and the part this row was opened for is NOT done.** PR F (#1910) ported documents' implementation up into the shared ResultFilterSheet, so the >20-or->3-groups tier now exists there: find-a-filter, per-group collapse-by-default, a group opening itself when it holds a selection, and a live needle owning openness. That is the tier documents needed. **The 6-20 band is still unimplemented.** result-filter-control.tsx computes `const dense = facetGroups.length > 3 \|\| totalFacetOptions > 20`, so a mode with one facet group of nine options — formulation, the exact case that opened this row — evaluates dense=false and still renders ResultFilterFacetChips as a wrapping chip row. Verified 2026-08-13 on main 2d27039: formulation passes one group with formulationDomainsInUse.length === 9, so neither condition fires. An earlier attempt to close this row as delivered was wrong and was caught in review on PR #1925; the mistake was conflating "the dense tier landed" with "this row's band landed" — section 5 has two thresholds and only the upper one shipped. **Next:** either implement the 6-20 full-width renderer with the right-aligned count column and add the nine-option DOM assertion this row already asked for, or amend section 5 to drop the middle band deliberately and record that the contract was reversed rather than satisfied. **Stop:** do not close this row on the strength of the >20 tier, and do not add a per-mode dense list — a second hand-rolled facet layout is the drift the shared renderer was extracted to remove. | Codex review on PR #1858; docs/filter-contract.md section 5 | 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 | | #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 | @@ -479,3 +472,7 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | #245 | rec | responsive-compact CrossModeLinks keeps duplicate rails in the DOM | RESOLVED AS INTENTIONAL — verified on main 2026-08-14. The premise still holds literally (both rails are mounted) but it is now a documented decision, not a defect: cross-mode-links.tsx:220-224 states both rails stay mounted so SSR and first paint agree, hidden/md:hidden use display:none which removes the inactive rail from the accessibility tree, and distinct test ids stop phone vs wide selectors double-counting. Removing a rail would reintroduce the hydration mismatch this comment exists to prevent. PR #1842 merged. | 2026-08-14 | | #335 | rec | Merge-loss detection covers file-level reverts and inbox-request loss separately; neither covers the other, and the scheduled run is undecided | Closed 2026-08-15 as duplicate follow-up of #324. The delivered file-level audit, the remaining schedule/owner decision, and the distinct inbox-request-loss scope are all retained in #324; no scheduling or CI policy was changed by this consolidation. | 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 | +| #210 | task | npm run ensure generates .next/dev types that break typecheck and every Playwright build | Closed after PR #1953 landed on main: scripts/run-playwright.mjs now gives the generated isolated child tsconfig its own include/exclude, preventing stale repo-root .next/dev types from leaking into the Playwright build while retaining the run's generated types. | 2026-08-14 | +| #215 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | Closed after PR #1949 landed on main: the PWA install-sheet icon now uses decoding="async". Demo PNG-to-WebP conversion was explicitly declined as a low-value synthetic-demo build step, preserving the row's requested implementation-or-drop decision. | 2026-08-14 | +| #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 | From 8308c633c1f6f09021fccd22d9353804ed7e18f5 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:33:50 +0800 Subject: [PATCH 10/11] fix(issues): preserve canonical reconciliation order --- docs/outstanding-issues.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 1b1f4cf883..07a0e25f69 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -470,9 +470,9 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | #234 | task | answer-copy-payload.ts is the single clipboard payload builder for three surfaces and has no documentation | DELIVERED — verified on main 2026-08-14. answer-copy-payload.ts (now src/components/clinical-dashboard/) carries a header documenting the single-builder contract, the three consuming surfaces, and why it sits outside src/lib and outside the design system. PR #1842 merged. | 2026-08-14 | | #213 | task | Stop swallowing fetch and stream errors with empty catch handlers | Closed 2026-08-14. The 2026-08-12 re-measure counted correctly but described the wrong thing: the 3 remaining bare catches under src/ were not fetch/stream swallowing at all. All 3 lived inside render-blocking inline bootstrap script strings — src/lib/theme.ts:46 (localStorage.getItem, then document.cookie) and src/app/layout.tsx:149 (JSON.parse of stored preferences) — where a throw means storage/cookies are unavailable and the correct behaviour is the documented fallback chain (cookie, then OS preference; defaults for density/motion). Each now carries an inline comment stating the throwing condition and the fallback that covers it; no behaviour changed, because there is no logger or toast before React mounts and surfacing the error would trade a correct default appearance for a broken first paint. The genuine fetch/stream catches this row was opened against were already dispositioned by earlier passes (api/answer/stream/route.ts:178,291 and api/search/universal/route.ts:102 carry comments and propagate via controller.error). Added tests/empty-catch-disposition.test.ts, a raw source-text scan asserting every empty catch under src/ carries a comment — raw text rather than an AST because ESLint's no-empty cannot see catches inside template-literal script strings, which is exactly where these 3 hid. Population is 21 empty catches, all dispositioned, 0 bare. | 2026-08-14 | | #245 | rec | responsive-compact CrossModeLinks keeps duplicate rails in the DOM | RESOLVED AS INTENTIONAL — verified on main 2026-08-14. The premise still holds literally (both rails are mounted) but it is now a documented decision, not a defect: cross-mode-links.tsx:220-224 states both rails stay mounted so SSR and first paint agree, hidden/md:hidden use display:none which removes the inactive rail from the accessibility tree, and distinct test ids stop phone vs wide selectors double-counting. Removing a rail would reintroduce the hydration mismatch this comment exists to prevent. PR #1842 merged. | 2026-08-14 | -| #335 | rec | Merge-loss detection covers file-level reverts and inbox-request loss separately; neither covers the other, and the scheduled run is undecided | Closed 2026-08-15 as duplicate follow-up of #324. The delivered file-level audit, the remaining schedule/owner decision, and the distinct inbox-request-loss scope are all retained in #324; no scheduling or CI policy was changed by this consolidation. | 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 | | #210 | task | npm run ensure generates .next/dev types that break typecheck and every Playwright build | Closed after PR #1953 landed on main: scripts/run-playwright.mjs now gives the generated isolated child tsconfig its own include/exclude, preventing stale repo-root .next/dev types from leaking into the Playwright build while retaining the run's generated types. | 2026-08-14 | +| #335 | rec | Merge-loss detection covers file-level reverts and inbox-request loss separately; neither covers the other, and the scheduled run is undecided | Closed 2026-08-15 as duplicate follow-up of #324. The delivered file-level audit, the remaining schedule/owner decision, and the distinct inbox-request-loss scope are all retained in #324; no scheduling or CI policy was changed by this consolidation. | 2026-08-14 | | #215 | task | Add image-optimization basics for lightbox, PWA lifecycle and demo PNGs | Closed after PR #1949 landed on main: the PWA install-sheet icon now uses decoding="async". Demo PNG-to-WebP conversion was explicitly declined as a low-value synthetic-demo build step, preserving the row's requested implementation-or-drop decision. | 2026-08-14 | | #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 | From ddacb50802fcc5967984819399987d0d44bebc03 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:33:53 +0800 Subject: [PATCH 11/11] docs(ledger): record PR #1968 latest-base review --- ...938115ff0ea2b99458078ba349cd9e9f625efb537215cbf1227.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/4e9b4d92a67f8938115ff0ea2b99458078ba349cd9e9f625efb537215cbf1227.record.md diff --git a/docs/branch-review-records/4e9b4d92a67f8938115ff0ea2b99458078ba349cd9e9f625efb537215cbf1227.record.md b/docs/branch-review-records/4e9b4d92a67f8938115ff0ea2b99458078ba349cd9e9f625efb537215cbf1227.record.md new file mode 100644 index 0000000000..abb7f83e30 --- /dev/null +++ b/docs/branch-review-records/4e9b4d92a67f8938115ff0ea2b99458078ba349cd9e9f625efb537215cbf1227.record.md @@ -0,0 +1 @@ +| 2026-08-15 | PR #1968 / claude/ledger-reconcile-batch-4 | 31cd550141e66806e53ade04965151b960964c6d | unblocking PR review-and-fix | Merged the latest base and reconciled its complete ledger batch; retained the #316 headline correction as a valid next-transaction request. | ledger write discipline; ledger inbox dry-run/check; outstanding-issues; branch-review-ledger; diff --check |