diff --git a/.cursorindexingignore b/.cursorindexingignore index 6ba7ac17bb..aa8ab12ab3 100644 --- a/.cursorindexingignore +++ b/.cursorindexingignore @@ -32,21 +32,6 @@ public/therapy-compass-data/ docs/branch-review-ledger.md docs/archive/ -# Design-scratch mockup routes/components — keep out of @Codebase discovery. -# Do not use a repo-wide *mockup* glob: tests/env-mockups-gate and -# tests/production-mockup-boundary must stay indexed. -src/app/mockups/ -mockups/ -# note: src/components/calculators/ is production (promoted from mockups) — keep indexed -src/components/favourites-page-mockups/ -src/components/settings-search-mockups/ -src/components/tools-page-mockups/ -src/components/*mockups.tsx -src/components/clinical-dashboard/global-mockup-search-shell.tsx - -# One-off Codex review prompt dumps (not product code) -codex-cloud-review/ - # Generated schema-drift inventory (large JSON) supabase/drift-manifest.json diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index e933c487ec..e65c8fdb5b 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -954,13 +954,6 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | open-pr-babysit-continuation-20260725 | multipass | Babysit continuation after 14 merges: #1177 landed; #1174/#1178/#1153 in progress; drafts #1187/#1192 skipped; large cluster #1162/#1185/#1186/#1188/#1190 content-conflicted (skip). | merge-tree inventory; no provider-backed checks. | | 2026-07-25 | audit-remediation (PR #1153) | 5a731df5c25fed9b07fd2321a0ad4b6519471f4b | PR babysit: CodeRabbit thread fixes + merge | Before: MERGEABLE/BLOCKED on required_review_thread_resolution + pending CI; 6 CodeRabbit threads. After: fixed sync-skills pad/YAML escape, PDF temp cleanup, squash-aware rollback wording; dispositioned ledger mid-table + retained false-positive; approved CI; merged to main `191b17d2f` (merge commit); branch deleted; tip is ancestor of main. | Hosted CI green on tip; no provider-backed checks. | | 2026-07-25 | cursor/local-presence-054-7cf3 (PR #1178) | 9135891bfd194394549cb480a7ec86de12b23ee7 | PR babysit: local-presence + /tools + CI/UI fixes + squash merge | Before: flaky Safety audit on package.json scripts, Production UI Sources autofocus flake, CodeRabbit short-env duplicate thread. After: ci-change-scope lockfile-only; strip stale short env keys; sheet open-focus retries + skip focus=1 reclaim under modal; squash-merged `d08ec2e8e`; branch deleted; key-file content-diff empty. | Hosted PR required SUCCESS (Production UI green on tip); focused local-presence vitest; no provider-backed checks. | - -| 2026-07-25 | PR #1185 / `execute-typography-audit-fixes` | `dd641579f4cf54f82de89ef268ac8aa6acb439b5` | Fresh Cursor review + Bugbot + prlanded | DO NOT MERGE / CLOSE+REDO. Confirms prior ledger: intentional tip commit is safe typography-only (5 files), but branch tree still carries committed conflict markers from `faa50e6e3` (answer/upload routes, docs, tests) so the PR cannot build. Codex P1 markers + upload publisher spoof + P2 mobile back regression are real on that dirty tree. `prlanded`: not merged (`state=OPEN`, `mergedAt=null`). Replacement: cherry-pick tip onto current main as `cursor/typography-audit-fixes-1c1e`. | Bugbot on PR head; `git show` of tip; marker scan; `git merge-tree`/diff vs main. No provider calls. Hosted PR policy FAIL. | -| 2026-07-25 | `cursor/typography-audit-fixes-1c1e` (supersedes #1185) | pending-push | Clean cherry-pick of typography audit tip onto `origin/main` | APPROVE-ready local. Scope is only font-stack + mockup heading/truncation/tabular-nums. No conflict markers. Highest residual risk: visual-only mockup/CSS change; unrelated Vitest flakes seen locally (pdf-extractor SIGKILL, reconciliation-preflight timeout) not tied to this delta. | `npm run verify:cheap`: lint+typecheck+static gates pass; Vitest 3379 pass / 2 env flakes. No provider/UI matrix. | -| 2026-07-25 | `cursor/typography-audit-fixes-1c1e` (supersedes #1185) | `c78271bae02ddf82792bb0625f479483240e76b8` | Clean cherry-pick published | APPROVE-ready. Published replacement for contaminated #1185; 5-file typography delta + format + ledger. | Local `verify:cheap` as above; pushed to origin. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | `6bc05690a966e3e8aebdc8ea0460b60899201c52` | Explicit merge-readiness review (typography supersede of #1185) | NOT READY. Clean 5-file product delta vs main (font-stack + mockups). Confirmed P2: answer-evidence sheet/modal titles promoted h3->h2 while nested under Section h2 (hierarchy regression). P2 process: PR body describes unrelated audit-remediation work. Process blockers: draft; tip CI/SAST/Secret Scan `action_required` (green only on older `3cc6fa0cd`). No P0/P1 product defects. Font-stack/min-w-0/tabular-nums OK. | `origin/main...HEAD` 6 files; merge-tree clean; marker scan clean; no provider/UI matrix. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | pending-push | Follow-up perfection pass | FIXED: reverted nested mockup titles to `h3` under Section `h2`; restored correct `PR_POLICY_BODY.md` after stale overwrite; remaining product delta is font-stack + recent-search `min-w-0` + bedside `leading-tight` drop + clinical-console `tabular-nums` + ledger. | Heading hierarchy re-check; Prettier check on touched files. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | `e7d93cddd75a71a4d7c4deff48e0038c98f6706f` | Follow-up perfection published | APPROVE once tip CI green. Heading hierarchy fixed; correct policy body template on branch; product scope reduced to 4 source files + ledger/template. | Prettier check; pushed tip. | | 2026-07-25 | cursor/fix-mobile-composer-edge-scroll-5b1d (PR #1192) | 3b5ef43f1825dd8cf11dd767069569ba1c701c45 | Bugbot branch review: mobile Answer edge dock, synchronized hide/reveal, focus safety, reserve-collapse and fractional-clamp safeguards | No bugs found. Highest residual risk is physical iOS Safari toolbar/visual-viewport behavior beyond Chromium emulation. | `npm run verify:cheap` (3,357 passed); `npm run verify:ui` (272 passed); focused clamp/reserve Vitest (28 passed); focused production Chromium regression passed; clean headed-phone video proof; no provider-backed checks run. | | 2026-07-25 | cursor/fix-mobile-composer-edge-scroll-5b1d (PR #1192) | 69dc0dbfb46586f54f5934199d4a65b9f6a0aba8 | User-requested Bugbot review of current PR head after geometry-aware clamp handling and CI formatting fix | No bugs found. | Bugbot branch review; prior focused unit/Chromium/manual proofs retained; no provider-backed checks run. | | 2026-07-25 | cursor/fix-mobile-composer-edge-scroll-5b1d (PR #1192) | c08ed37c5e8c7b62cf7d547089afff7a63c9122c | Live local document-detail scroll/ownership review at 390x844, 768x1024, and 1440x900 | FINDINGS: P2 canonical phone detail renders shared mobile header plus DocumentViewer header; P2 expanded desktop sticky rail scrolls its section navigation off-screen; P3 390px in-flow section nav fully hides Images with no overflow cue. Composer focus pinning, actions sheet, endpoint clearance, safe-area gap, and single composer/content reserve ownership otherwise held. | `npm run workflow:design-sweep -- --write-evidence`; `npm run ensure` + `/api/local-project-id` identity; live local Chromium natural down/up, anchors, focus, sheet, endpoint and geometry probes; focused document-viewer Playwright 3/3; reduced-motion + forced-colors visibility at 390/1440; no OpenAI/Supabase/GitHub/hosted CI/provider calls. | @@ -971,14 +964,11 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | execute-audit-code-remediation (PR #1162) | f07c711a0e1ee853b128733a51db182d8192c3c4 | CI unblock after Bugbot | Fixed Prettier (11 files), restored package-lock/.npmrc to main so blocking npm audit is advisory (lockfile_changed=false), updated mobile-composer-reserve contract for answer-home hero breakpoint. Prior tip e5c8c49c had Static/Safety/Unit failures. | format:check; focused Vitest 41; ci-change-scope lockfile_changed=false; no provider-backed checks. | | 2026-07-25 | execute-audit-code-remediation (PR #1162) | 1de1b32f562c0a972997750a5dc97a02ab1a9c15 | Production UI fix | Fixed ui-tools services referral header test (H1/quick-filters contract). Prior tip 09c6eb2d had Static/Safety/Unit/Migration green; only Production UI failed. | Local Playwright chromium services referral test PASS; no provider-backed checks. | | 2026-07-25 | execute-audit-code-remediation (PR #1162) | b9b56c140eb14cbba5a2c2230e3fa28d3a791add | CI unblock after bot sync | Tip 96188eca had PR required SUCCESS (Static/Safety/Unit/Build/Migration/Production UI). Hosted pr-branch-sync then merged main (a420b86b/b9b56c14), leaving CI action_required for bot-authored runs. Pushing agent commit to re-trigger non-bot CI. | Prior tip 96188eca hosted CI green; local services referral Playwright PASS; no provider-backed checks. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | `80421c38c54c5960bf51210d6bc29109b52afb53` | Main sync after CONFLICTING + perfection | MERGE-READY product scope. Merged `origin/main` cleanly; no markers; product delta remains font-stack + 3 mockup tweaks + policy body/ledger. Heading hierarchy fix retained. | merge-tree clean; marker scan clean; type-scale + design-system-contract + Prettier pass. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | pending-push | Production UI flake fix | FIXED reproducible Sources Find autofocus: Sheet open retries now upgrade from close-button fallback to late-mounted `data-sheet-autofocus` (was leaving focus on Close after drawer children mount). Typography product delta unchanged. Removed temporary `PR_POLICY_BODY.md`. | `node scripts/run-vitest.mjs run tests/sheet.dom.test.tsx` 5/5. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | `7a94877745c652b7bb7144cf9be02a22a9a0dbdf` | Autofocus fix published | Awaiting exact-head Production UI green. | Sheet DOM 5/5; pushed. | -| 2026-07-25 | `cursor/ledger-066-067-519b` | f04392a408eceee14215c15169cbd6b70ac2041c | Close stale ledger #066/#067 (+ drop resolved #030/#075 from queue) | READY. #066 proven on main via #1174; #067 already fixed in #1191 in-process preflight. Docs-only ledger sync; no code change. | Local proof: `git show`/`log` for #1174/#1191; preflight test already in-process on main; ledger integrity asserts. No providers. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | `5e22eb4c4c7f717f94e32b545f31c0d0f6374a96` | Final merge-readiness after perfection | APPROVE / MERGE-READY. Product: typography delta + Sheet late-autofocus upgrade. Hosted tip green: PR policy, Static, Unit, Build, Safety, Production UI, PR required, SAST, Secret Scan. mergeStateStatus CLEAN. Residual: human approving review if branch protection requires it. | Hosted CI success on `5e22eb4c4c7f717f94e32b545f31c0d0f6374a96`; Sheet DOM 5/5; no provider-backed evals. | | 2026-07-25 | PR supersede #1186 / `cursor/pr1186-audit-remediation-c94c` | `a38e83860510a4229d5658960657cd7448aff278` | Clean main-based port of intentional #1186 audit fixes | SUPERSEDE #1186 (do not merge old PR). Ported intentional 16-file delta onto current main; dropped conflicted checkpoint tree and placeholder skills. Fixed eval single results binding; async run-heavy so lock heartbeat fires; branch:cleanup dry-run default + argv-safe deletes; skill-create interface YAML. Close #1186. | Focused Vitest tooling+lock 6/6; check:skills 33; prettier on touched files; no provider/live eval runs. | -| 2026-07-25 | PR #1200 / `cursor/typography-audit-fixes-1c1e` | pending-push | CORRECTION: remove exact-duplicate ledger row | Removed a merge-union exact duplicate of the `cursor/ledger-066-067-519b` / `f04392a40` READY row (kept the earlier copy). No product change. | `npm run check:branch-review-ledger` | -| 2026-07-25 | PR #1190 / `remediate-dark-mode-audit` | `00eca49b9b0d7e5fbfa5703a15e9e930963984a6` | Cursor review+Bugbot+prlanded+debug (fresh pass, same HEAD) | DO NOT MERGE; NOT LANDED (OPEN, mergeable=CONFLICTING/DIRTY, 468 behind / 2 ahead). Reconfirmed P0: conflict resolution deleted `trustGatedAnswerForClinicalNotes` (0 hits on head; main L584/632/659/1075) — Clinical Notes consumes ungated answer. P1: `src/app/api/answer/route.ts:6` imports nonexistent `@/lib/rag` (tsc TS2307; stream correctly uses `@/lib/rag/rag`). P1: merge-tree conflicts on answer/upload/evidence-panels + 10 paths; literal `<<<<<<<` in docs audit plan; migration timestamp collision risk vs main. Intentional dark-mode delta is only commit `363672602` (~10 files). Salvage: `cursor/pr1190-dark-mode-salvage-f453` cherry-picks that commit onto current main, restores unused-manifest-import cleanup, keeps clinical gate. Close #1190 after salvage lands. | Bugbot; `git grep` gate/import/markers; merge-tree; `tsc` TS2307 proof; gh pr view/checks (PR policy fail). Salvage: tsc clean; visual-evidence+overlay tests 13/13; eslint on changed files. No provider/UI matrix. | +| 2026-07-25 | `cursor/ledger-066-067-519b` | f04392a408eceee14215c15169cbd6b70ac2041c | Close stale ledger #066/#067 (+ drop resolved #030/#075 from queue) | READY. #066 proven on main via #1174; #067 already fixed in #1191 in-process preflight. Docs-only ledger sync; no code change. | Local proof: `git show`/`log` for #1174/#1191; preflight test already in-process on main; ledger integrity asserts. No providers. | +| 2026-07-25 | PR #1196 / codex/fix-p2-audit-20260719 | 6d59b038514a92566e986d302b0c75707f13ea30 | fresh bug/regression review + Bugbot (clinical/RAG/search/auth/privacy) | Changes requested. Not merge-ready. No P0. P1: search/embedding last-waiter abort leaves dying inflight map entry so healthy same-key retry can coalesce onto aborted work (HTTP 500 / AbortError); also present on main — fixed forward in cursor/pr1196-coalesce-main-4711 / PR #1212. P2: fractional PDF render dimensions rejected by Number.isSafeInteger(pixels), aborting JS fallback — also fixed in #1212. Cleared after inspection: public storage_path omission, document chunk UUID fail-closed schema, factsheet save persistence, therapy capability flags, extractive section-dedup exemption, auth definitive-vs-retryable handling. Blockers: GitHub mergeable CONFLICTING; ~680 commits behind main; ~23 content conflicts including openai.ts, rag.ts, semantic-rerank, supabase client, package.json, therapies-index. PR body RAG impact understates clinical-search / answer-ranking / retrieval-variant edits. prlanded: state OPEN, not merged. | Bugbot + offline static/diff review + pure-JS race/fractional-pixel proofs; focused Vitest on #1212 fix (163 passed). Full PR #1196 Vitest/UI not re-run on stale tip. No OpenAI/Supabase/provider writes. Hosted CI for #1196 only showed PR policy pass + GitGuardian fail; required suite not green on this head. | +| 2026-07-25 | cursor/pr1196-coalesce-main-4711 / PR #1212 | fbe4f46ee | fix-forward from #1196 review: coalesce poison + fractional PDF pixels | Immediate map delete on last-waiter abort for search/embedding coalescing; ceil fractional render dims before safe-integer pixel budget. Regression tests + repro script. | Vitest openai-cache + pdf-extraction-budget + private-access-routes: 163 passed; node repro script. No provider-backed checks. | +| 2026-07-25 | PR #1196 / codex/fix-p2-audit-20260719 | 6d59b038514a92566e986d302b0c75707f13ea30 | closeout: superseded by #913 / current main | CLOSED without merge. Content proof: tip ~680 behind, CONFLICTING; remediation family already on main via #913 (01040d2c). Tip would regress docs admin gate, factsheet governance, PDF/RAG/auth advances. Live residual coalesce/PDF bugs fixed-forward in PR #1212. Remote branch retained (no delete). | Content diff vs origin/main + #913 path overlap; no provider-backed checks; no branch delete. | | 2026-07-25 | PR #1195 / `subagent-Asset-Optimization-Implementer-self-b295a5bb` | `f3d90eccf78a5e8c6569c0889d1932230588f1d2` | Bugbot triage of asset-optimization PR (supersedes d63682c7 READY row) | NOT READY previously: tip carried committed conflict markers from dirty checkpoint `faa50e6e` (answer/upload routes, services UI, tests), was DIRTY vs main, and GitHub three-dot showed ~117 polluted files. Fixed: merged `origin/main`; removed SignedImage client transform rewrite (undeclared width/height/resize + max-h-52 heuristic on object/sign URLs = silent no-op/risk); replaced mutating non-recursive `svgo -f` check:assets with `scripts/check-assets.mjs` stability gate; multipass-optimized `src/app/icon.svg`; added signed-image regression test. | merge-tree + tsc conflict-marker scan; `vitest run tests/signed-image.dom.test.tsx` (4 pass); `npm run check:assets` OK. No provider-backed checks. | | 2026-07-25 | PR #1195 / `subagent-Asset-Optimization-Implementer-self-b295a5bb` | `8888bf87cbdc5a061bb6d3a2cf46b638af773b1b` | Cursor review+Bugbot+/debug (supersedes f3d90ecc Bugbot row) | CONDITIONAL READY after favicon fix. Prior tip DO NOT MERGE (conflict-marker pollution) cleared by Bugbot main-merge; SignedImage transform silent no-op removed; check:assets now compare-only. Remaining P1 found+fixed: SVGO-stripped `icon.svg` failed `brand:check` and removed dark-mode favicon styles � restored `brandIconSvg()` and excluded that file from SVGO gate. Residual P2: orphan AVIF/WebP binaries unused by demo/mockup PNG refs; year-long immutable Cache-Control on unversioned `/icons/*`; `minimumCacheTTL: 86400` still a long lower bound for any optimized next/image. NOT LANDED (OPEN). | Bugbot; marker scan clean; brand:check + check:assets PASS; signed-image vitest 7/7; hosted Static/Safety were red on pre-fix tip (brand:check). No provider-backed app checks. | @@ -988,6 +978,7 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | PR #1209 / `cursor/pr1186-audit-remediation-c94c` | `4cb22e45dfe46b1975fa15ddd028c7e14ceb5fab` | Close #1186 + babysit #1209 CI | DONE. Closed #1186 as superseded. Synced origin/main (MERGEABLE/CLEAN). Fixed PR-policy RAG impact line. Hosted PR required SUCCESS (Static/Safety/Unit/Build/Production UI/Advisory UI/containers) on pre-ledger tip; docs-only follow-up pushed. Ready to merge; auto-merge not enabled. | gh pr checks; local policy ok; no provider/eval runs. | | 2026-07-25 | PR #1195 / `subagent-Asset-Optimization-Implementer-self-b295a5bb` | 963a9a0b4acb40659118eb1160712c0b99ab8bb1 | Cursor /debug CI unblock | Dropped svgo/sharp/check:assets lockfile delta (exceljs brace-expansion highs become blocking when lockfile_changed). Kept runtime asset fixes + themed favicon. Orphan AVIF/WebP remain unused. | brand:check/knip/prettier/signed-image vitest local PASS; no provider checks. | +| 2026-07-25 | PR #1190 / `remediate-dark-mode-audit` | `00eca49b9b0d7e5fbfa5703a15e9e930963984a6` | Cursor review+Bugbot+prlanded+debug (fresh pass, same HEAD) | DO NOT MERGE; NOT LANDED (OPEN, mergeable=CONFLICTING/DIRTY, 468 behind / 2 ahead). Reconfirmed P0: conflict resolution deleted `trustGatedAnswerForClinicalNotes` (0 hits on head; main L584/632/659/1075) — Clinical Notes consumes ungated answer. P1: `src/app/api/answer/route.ts:6` imports nonexistent `@/lib/rag` (tsc TS2307; stream correctly uses `@/lib/rag/rag`). P1: merge-tree conflicts on answer/upload/evidence-panels + 10 paths; literal `<<<<<<<` in docs audit plan; migration timestamp collision risk vs main. Intentional dark-mode delta is only commit `363672602` (~10 files). Salvage: `cursor/pr1190-dark-mode-salvage-f453` cherry-picks that commit onto current main, restores unused-manifest-import cleanup, keeps clinical gate. Close #1190 after salvage lands. | Bugbot; `git grep` gate/import/markers; merge-tree; `tsc` TS2307 proof; gh pr view/checks (PR policy fail). Salvage: tsc clean; visual-evidence+overlay tests 13/13; eslint on changed files. No provider/UI matrix. | | 2026-07-25 | `cursor/fix-mobile-composer-edge-scroll-5b1d` (PR #1192) | 333e67b8 | pr-ci-fix: Static PR checks / Maintainability hotspot budgets | Main merge (e688c6e2) expanded a JSX comment from 2→3 lines while restructuring heroComposerBreakpoint/heroOwnsPhoneComposer declarations, netting +2 lines vs budget-fix commit (ae77f8c3). ClinicalDashboard.tsx hit 4141 vs 4140 budget. Fix: compressed 3-line comment back to 2 lines. Zero behaviour change. | `npm run check:maintainability-budgets` → PASS (4140/4140). No provider-backed checks. | | 2026-07-25 | PR #1192 / `cursor/fix-mobile-composer-edge-scroll-5b1d` | `70abb74f7ceee5a50748c4c1e6baa730d7225cf4` | User-requested /review + Bugbot + /debug + /prlanded on current tip | APPROVE with notes. Prior P1 focus latch and P2 near-bottom reserve-only clamp confirmed fixed on `5b5ecf405` and retained through main merge. No new P0/P1. Residual P2s: PR body was wrong audit-remediation paste (fixing); non-answer `focus=1` autofocus still broad; earlier document-detail double-header findings unchanged/out of Answer-dock scope. /prlanded: still OPEN, not merged. GitHub CONFLICTING was staleness (merge-tree clean) � merged origin/main. | Bugbot; focused Vitest use-hide-on-scroll + mobile-composer-reserve 28/28 before and after main merge; merge-tree clean; no provider/UI browser matrix this pass. | | 2026-07-25 | PR #1192 / `cursor/fix-mobile-composer-edge-scroll-5b1d` | `73e87da63f5a9eca4162534074755891f952a4ee` | CORRECTION/supersede: final head after Bugbot fixes + main sync + review ledger push | APPROVE with notes retained from prior row. Product fixes from `5b5ecf405` still present; branch now 0 behind / mergeable (BLOCKED on CI). Wrong audit-remediation PR body corrected. /prlanded: still OPEN � do not delete branch. | Focused Vitest 28/28 on pre-sync tip; merge-tree clean; CI re-queued on tip; no provider-backed checks. | @@ -1008,7 +999,6 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | cursor/pr1190-dark-mode-salvage-f453 (PR #1214) | pending-ci-retrigger-2 | CI unblock + skip-branch-sync | Repeated pr-branch-sync bot merges left CI `action_required`. Applied `skip-branch-sync` label and agent retrigger so required checks can finish for squash-merge; then close #1190. | Hosted CI pending on tip; no provider-backed checks. | | 2026-07-25 | cursor/pr1190-dark-mode-salvage-f453 (PR #1214) | `e19442240afbd7f28c321e399c6b4dcb0a7c9fdf` / squash `bb6b394617cbd906285ebe19e0e452912793320a` | prlanded after squash merge | MERGED. Two-dot content diff vs `origin/main` empty; clinical gate preserved; `no-hardcoded-hex` + theme CSS on main; PWA manifest theme colours intentionally absent. Hosted PR required SUCCESS (Unit/Build/Static/Production UI). Remote salvage branch deleted by squash `--delete-branch`. | `gh pr view` MERGED; `git diff origin/main e19442240` empty; trustGated grep on main; no provider-backed checks. | | 2026-07-25 | PR #1190 / `remediate-dark-mode-audit` | `00eca49b9b0d7e5fbfa5703a15e9e930963984a6` | Close without merge after #1214 salvage | CLOSED (not merged). Unsafe tip superseded by #1214. Remote branch `remediate-dark-mode-audit` retained pending optional cleanup; do not merge. | `gh pr view` state=CLOSED mergedAt=null; no provider-backed checks. | -| 2026-07-25 | execute-system-audit-remediation (PR #1197) | `12ab1af1a353b4e3d0aa54a7d05145e296d3b2f0` / squash `8ec2a3cb698df98785ac33c12040475ceb980542` | prlanded + merge-ready closeout | MERGED (squash). Content two-dot tip vs main empty. Kept SQL URL/grant remediation; restored clinical trust gate/summaryMode; dropped RAG pre-classifier. Closed twin #1218 as duplicate. Local branch delete skipped (checked out in another worktree). | Hosted PR required SUCCESS; Migration replay/Unit/Static/PR policy SUCCESS; Semgrep fail was Docker Hub pull timeout (infra). `npm run check:branch-review-ledger` pass. No provider-backed checks. | | 2026-07-25 | PR #1195 / `subagent-Asset-Optimization-Implementer-self-b295a5bb` | `15a8093ec4fb6abd73ddead7c0f436e633359ab8` | Cursor review+Bugbot+/debug closeout | CONDITIONAL READY / CI green on tip before final main sync. Supersedes polluted DO NOT MERGE tip. Runtime asset opts retained; themed favicon restored; SVGO/sharp lockfile delta dropped (stub `check:assets`). Residual P2: orphan unused AVIF/WebP; year-long immutable `/icons/*` Cache-Control; `minimumCacheTTL: 86400`. NOT LANDED (OPEN). | Hosted PR required PASS (Static/Safety/Unit/Build/Production UI); brand:check + signed-image vitest local; no provider app checks. | | 2026-07-26 | cursor/global-header-scroll-hide-4fd7 (PR #1222) | `a7f6d81f8b1dd5613dda94a3ddef78d480de876e` | Cross-breakpoint header hide/reveal + tablet/desktop scroll coverage | Header now hides on scroll down and returns on scroll up at every breakpoint; bottom search dock stays phone-only. Two root causes fixed: GlobalSearchShell had no scroll source above phones (`#main-content` onScroll never fires there) and its sticky rule sat on `header#search`, which has zero travel inside two header-height parents; ClinicalDashboard's collapse row was `max-sm`-gated so it never hid. Red/green proof captured: with the four source files reverted to base `1aa64e94`, all 12 new Playwright tests and 8/10 static contract assertions fail. | `npm run verify:cheap` pass except pre-existing local `tests/pdf-extractor.test.ts` Python-OCR failure (reproduced identically at base `1aa64e94`); `npm run verify:ui` 284/284 Chromium on the main-synced tree; `check:migration-role`, `check:function-grants`, `check:branch-review-ledger` pass after the #1197 SQL sync; no provider-backed checks. | @@ -1040,22 +1030,12 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | PR #1186 / `remediate-repository-audit-findings` | `8637fec36dea6534c02e5b3f12e5a913c10bc455` | Cursor Bugbot+review+prlanded (fresh pass, same HEAD) | DO NOT MERGE; NOT LANDED (state=OPEN, mergeable=CONFLICTING, DIRTY). Supersedes same-HEAD Antigravity/Bugbot rows with runtime proof: `tsc` TS1185 on answer/upload routes; head 436 behind / 2 ahead of main; PR policy FAIL. P0 conflict markers in 8 src + 2 tests + scripts/docs; P0 duplicate `const results` eval-retrieval.ts:905/932 (RAG; no RAG impact line); P0 skills catalog 36 vs AGENTS/tests 32. P1 spawnSync blocks lock heartbeat + 30m reclaim steals locks; branch:cleanup no dry-run + shell interpolation; skill-create wrong openai.yaml shape. Do not delete branch. | Bugbot; tsc sample; marker/catalog grep; gh pr view mergeable; no provider/eval/UI runs. | | 2026-07-25 | PR #1090 / `cursor/fix-phone-dock-edge-1b1d` | `c993975f6b18393a9ff09f9145fd42dbb35df942` (merged as `09028ef217209fceb53f1122ac7738b509bce323`) | prlanded — phone dock edge-to-edge / white-strip fix | MERGED. Content verified on `origin/main` (empty three-dot diff; spot-check dock CSS, ClinicalDashboard inset-0 shell, edge-to-edge Playwright test). No orphaned late commits. Remote branch already deleted; local branch removed. Codex review found no major issues; required CI green at merge. | Hosted required + Production UI green at merge; local maintainability budget + Prettier follow-ups landed in tip `c993975f`. | | 2026-07-25 | execute-system-audit-remediation (PR #1197) | 16c3184743002af037ee4899b7b8e46e574b8457 | Bugbot + local high-risk review + runtime summaryMode repro; prlanded check | DO NOT MERGE. Not merged (prlanded skipped). Branch ~468 behind main; merge-tree dirty (outstanding-issues + audit plan). P1: removed rustGatedAnswerForClinicalNotes + deleted clinical-notes boundary tests in visual-evidence-tabs; summaryMode test expects 200 vs route 400 (summary_mode_stream_required) runtime-proven; RAG deterministic pre-classifier in rag.ts without RAG impact:/canary (PR policy FAIL + clinical preflight missing); stale outstanding-issues next-id 064 vs main 081. P2: audit-plan conflict residue (=======); dead summarizeDocument path; est:standalone heavy-lock bypass. | Bugbot; static three-dot vs origin/main; vitest private-access summaryMode ? expected 200 got 400; visual-evidence remaining tests 2/2 pass; gh PR policy fail logs; no provider-backed checks. | +| 2026-07-25 | execute-system-audit-remediation (PR #1197) | 12ab1af1a353b4e3d0aa54a7d05145e296d3b2f0 ? merge 8ec2a3cb698df98785ac33c12040475ceb980542 | prlanded + merge-ready closeout | MERGED (squash). Content two-dot tip vs main empty. Kept SQL URL/grant remediation; restored clinical trust gate/summaryMode; dropped RAG pre-classifier. Closed twin #1218 as duplicate. Local branch delete skipped (checked out in another worktree). | Hosted PR required SUCCESS; Migration replay/Unit/Static/PR policy SUCCESS; Semgrep fail was Docker Hub pull timeout (infra). No provider-backed checks. | | 2026-07-26 | PR #1192 / cursor/fix-mobile-composer-edge-scroll-5b1d | `771683af` + main `584b8045` | Reconcile against the #1222 cross-breakpoint header and run the gates the branch never re-ran | APPROVE. The bot's earlier sync of #1222 into this branch resolved correctly: `useScrollHideReporter(false, true[, searchMode])`, `useDocumentScrollHideReporter`, `wide: "collapse" \| "sticky"`, `sm:contents` and the hidden-only `sm:-translate-y-full` are all intact, every `readChromeCollapseBudget` caller migrated to `readChromeCollapseMetrics`, and the two models compose: `collapseKind` only refines the in-flow path, while the sticky path still reports a zero budget because `readChromeCollapseMetrics` keeps the `display === "grid"` test. One real blocker found and fixed: merging main let the union driver re-append two records both sides already held (940 rows / 938 unique), failing `check:branch-review-ledger`; the later copy of each was dropped after proving zero records lost and all non-record text byte-identical. | `npm run verify:cheap` pass except pre-existing `tests/pdf-extractor.test.ts` SIGKILL case, which needs local Python OCR prerequisites and whose subject is absent from this diff (3437/3439 otherwise). `npm run verify:ui` 285/285 Chromium on the production build. Focused: `ui-chrome-scroll` + `ui-phone-scroll` 30/30; `use-hide-on-scroll` + `header-scroll-hide-contract` + `mobile-composer-reserve` 39/39; `npm run typecheck` clean. No provider-backed checks. | | 2026-07-25 | PR #1217 / `cursor/sheet-focus-hardening-c6d3` | head `65f7580ce4f4821f8d1ad55aa2c84b7e11ca62fe` / squash `20300bb9f60dee09632fdce27d1191dde19e92b5` | prlanded after squash merge | LANDED. Two-dot content diff of `src`/`tests` vs `origin/main` empty; `sheet-focus.ts` on main carries `canRestoreFocusTo`, `MAX_CONSECUTIVE_RECLAIMS`, the 10s event-driven settle window and no `setInterval`; `sheet.tsx` restores through `canRestoreFocusTo` at both call sites. Hosted PR required SUCCESS (Static/Safety/Unit/Build/Production UI/Advisory UI); post-merge push CI on `20300bb9` SUCCESS (CI, Docker image build, SAST, Secret Scan). Remote feature branch deleted by merge. The one red rollup entry was a `pull_request_target` PR Policy run cancelled by its own successor, which reported SUCCESS. | `gh pr view` state=MERGED mergedBy=BigSimmo; `git diff origin/main HEAD -- src tests` empty; `git show origin/main:...` symbol checks; `gh run list --branch main` on the squash commit. No provider-backed checks. | | 2026-07-26 | PR #1192 / `cursor/fix-mobile-composer-edge-scroll-5b1d` | head `2597dd305fd61e4e16166cccd109ff6f8eae0a33` / squash `c4d5f47a39` | prlanded after squash merge | LANDED. Phone Answer edge-dock hide/reveal, layout-clamp versus upward-intent detection, and composer focus binding, reconciled against the #1222 cross-breakpoint header. `git diff c4d5f47a 2597dd30` empty, so the content landed intact; remote branch deleted at merge; required contexts Gitleaks, PR required and PR policy all SUCCESS on the merged head. Two things worth keeping: merging `main` made the ledger's `union` driver re-append records both sides already carried, failing `check:branch-review-ledger` twice in one hour (see #082), and the final head was a `coderabbitai[bot]` auto-fix swapping an inline two-step upward scroll for the shared `scrollPrimarySurface` helper in `tests/ui-smoke.spec.ts` � an identical earlier auto-fix had been reverted on this branch, so it was re-proven rather than trusted. | `npm run verify:ui` 285/285 Chromium on the production build; `npm run verify:cheap` pass except the pre-existing `tests/pdf-extractor.test.ts` Python-OCR case, whose subject is absent from the diff (3437/3439 otherwise); `ui-chrome-scroll` + `ui-phone-scroll` 30/30 across phone, tablet and desktop; `use-hide-on-scroll` + `header-scroll-hide-contract` + `mobile-composer-reserve` 39/39; the CodeRabbit-modified in-flow collapse test re-run 3/3. No provider-backed checks. | | 2026-07-25 | origin/execute-audit-remediation-plan (PR #1188 closed tip) | `8b8639113925601e1687bfe4f1f29c44a4308b61` | branch-cleanup | DELETED remote. Tip rejected (conflict markers + parse breakers); intentional maintainability work already on main via #1213 (`8e3a49d0`). IMP-04 mockup/export prune from tip commit `3bc391dff` was not ported (knip-only unexports; optional follow-up). Local Antigravity worktrees left untouched. | Content proof: notices/utils/Sheet autofocus on origin/main; tip marker count 12; `git push origin --delete execute-audit-remediation-plan`. No provider calls. | | 2026-07-25 | PR #1195 / `subagent-Asset-Optimization-Implementer-self-b295a5bb` | `c612c88aae386a29715cadfb5dbd2b95163e1dc9` | prlanded after squash merge | MERGED. Squash tip `c612c88aa` on main; remote feature branch deleted. Product content verified on main (`check:assets`, icon Cache-Control, SignedImage/non-PDF a11y). Local review worktree retained for optional cleanup. | gh pr view MERGED; two-dot product-file diff empty vs origin/main; no provider checks. | -| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `b51ee15e6961d46a14c288f621d25ab30a434c7d` | Explicit CI-failure review and focused repair | APPROVE pending hosted required CI. All three completed CI failures were the same new Playwright assertion: `/formulation/worry` legitimately omits the optional legacy dock backdrop, but the test required `display: none` and received `missing`; the downstream `PR required` failure was only the aggregate. Updated the test to accept absence or require `none` when rendered; no high-confidence product defect remains. | Focused Vitest 11/11; Prettier, ESLint and `git diff --check` pass; exact local Chromium rerun blocked by the shared heavyweight lock owned by another worktree, so hosted Production UI is the merge gate. | - -| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `e1f7dad583465a10231abc058ee4320177998894` | Post-review main sync and stable-CI retrigger | APPROVE pending hosted required CI. The automated branch-sync merge brought current `main` (`b91b4600171be08198e92bcf19b7d67e8207cb2f`) into the reviewed repair without content conflicts; `skip-branch-sync` was applied to prevent another bot-head cancellation while required checks run. | `git merge-tree --write-tree` clean; three-dot PR scope unchanged except the required ledger record; hosted CI retrigger pending. | - -| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `de4864ef06626931ecd6bd22b97387f80f529cb2` | Production UI second-failure repair | APPROVE pending final hosted required CI. The first repair exposed a second invalid assumption in the same new test: `/formulation/worry` does not own a fixed phone dock, so geometry used the `-1` missing sentinel. Replaced it with the established submitted Forms result route, explicitly waiting for the dock and unfocused composer before asserting paint and scroll-hide geometry. | Exact focused production Chromium test pass 1/1 (isolated Next build); Prettier, ESLint and `git diff --check` pass; prior hosted run had 290/291 Production UI tests pass with this single invalid-route assertion. | - -| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `547d3a100c73333895554cf66eb0efd8d8dde8da` | Late unresolved review-thread verification and fix | APPROVE pending final hosted required CI. Confirmed the open P2 despite a bot summary claiming it was fixed: opaque phone `.edge-glass-header` / `.universal-header` still inherited `backdrop-blur-xl`. Added standard and WebKit `backdrop-filter: none` overrides and static/computed-style guards. | Prettier, ESLint and `git diff --check` pass; focused local Vitest/browser reruns blocked by consecutive legitimate shared-lock owners, so hosted Static/Unit/Production UI remain the merge gate. | - -| 2026-07-26 | PR #1256 / `codex/pr1246-header-blur-followup` | correction to `547d3a100c73333895554cf66eb0efd8d8dde8da` | CSS prefix contract correction | FIXED. Hosted Unit coverage exposed the repository guard forbidding hand-authored `-webkit-backdrop-filter`; retained the standard `backdrop-filter: none` source rule and removed the duplicate vendor declaration, which the CSS toolchain owns. This supersedes only the prior row's claim that both declarations should be authored. | Targeted `clinical-dashboard-merge-artifacts` and mobile paint contract rerun required; Production UI continued separately. | - | 2026-07-26 | PR #1238 / `cursor/header-hide-top-bar-only-4fd7` | `fdc20bfedb61a2c267c22a3d78ccc8214e6c0087` | Continue-executing: top-bar-only hide + CI green | APPROVE / MERGEABLE. Root cause fixed: collapse wraps only `header#search` (+ Therapy addon); sticky hosts pin outer [top bar \| search] below `chrome-safe-area-top` without translating search away; sticky-stack composers stay `relative`. Services rail overlay hardened (testids + center scrollIntoView); ui-tools accepts sticky ancestor; Therapy nav assert uses collapse-host top under safe-area spacer. Merged main safe-area + submitted-result focus rules. | Hosted PR required + Production UI SUCCESS on tip; contract 14/14; focused Playwright services/desktop composers 6/6, chrome-scroll 12/12, therapy-nav 1/1. No provider-backed checks. | | 2026-07-26 | PR #1238 / `cursor/header-hide-top-bar-only-4fd7` | head `db4390b3814910d0210497e2982414904f2e0704` / squash `cdbe0e662366f9308813e8d5fe8951ca11a47d6b` | prlanded after squash merge | LANDED. Top-bar-only hide-on-scroll with sticky search stack below `chrome-safe-area-top`; two-dot content diff empty vs `origin/main`. Remote feature branch deleted at merge. Required CI green at merge (PR policy, PR required, Production UI). | `gh pr view` MERGED; `git diff origin/main db4390b3` empty; no provider-backed checks. | -| 2026-07-26 | cursor/formulation-a11y-linear2-14d4 (PR #1250) | head `14b4e80ee41b16a80c974b6a1f8201407a0df05b` / squash `b91b4600171be08198e92bcf19b7d67e8207cb2f` | prlanded after squash merge | LANDED. Formulation disabled-state accessibility (#064) on main; product two-dot diff empty vs pre-merge tip. Superseded conflicted PRs #1219, #1223, #1226, #1231, #1249 closed. Remote feature branch deleted at merge. | Focused Chromium formulation 7/7; verify:cheap 3473 tests; hosted Production UI + PR required SUCCESS; `git diff 14b4e80e origin/main -- formulation-builder-page.tsx ui-formulation.spec.ts` empty. No provider-backed checks. | diff --git a/docs/codebase-index.md b/docs/codebase-index.md index 428378e242..e6ac8cce05 100644 --- a/docs/codebase-index.md +++ b/docs/codebase-index.md @@ -50,30 +50,29 @@ Structured map for AI agents and onboarding. For live routes, see `docs/site-map ### Product pages (`src/app/`) -| Route | File | -| --------------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `/` | `src/app/(search-app)/page.tsx` | -| Shared mode-home route group (`/(search-app)`) | `src/app/(search-app)/` | -| Mode homes (`/services`, `/dsm`, `/documents/…`, …) | `src/app/(search-app)/` shared shell group | -| `/applications` | `src/app/applications/route.ts` | -| `/differentials`, `/diagnoses`, `/presentations` | `src/app/(search-app)/differentials/` | -| `/dsm`, `/dsm/search`, `/dsm/compare`, `/dsm/diagnoses/[slug]` | `src/app/(search-app)/dsm/` | -| `/documents/search`, `/source`, `/evidence`, `/[id]` | `src/app/(search-app)/documents/` | -| `/factsheets`, `/factsheets/search`, `/factsheets/[slug]` | `src/app/(search-app)/factsheets/` | -| `/favourites` | `src/app/(search-app)/favourites/page.tsx` | -| `/forms`, `/forms/[slug]` | `src/app/(search-app)/forms/` | -| `/medications`, `/medications/[slug]` | `src/app/(search-app)/medications/` | -| `/privacy` | `src/app/privacy/page.tsx` | -| `/reference/colour-coding` | `src/app/reference/` | -| `/safety-plan` | `src/app/safety-plan/page.tsx` | -| `/calculators` | `src/app/(search-app)/calculators/page.tsx` | -| `/services`, `/services/[slug]` | `src/app/(search-app)/services/` | -| `/therapy-compass` | `src/app/(search-app)/therapy-compass/` | -| `/tools` | `src/app/(search-app)/tools/` | -| `/specifiers`, `/specifiers/[slug]`, `/specifiers/builder`, `/specifiers/compare`, `/specifiers/map` | `src/app/(search-app)/specifiers/` | -| `/formulation`, `/formulation/[slug]`, `/formulation/builder`, `/formulation/compare`, `/formulation/map` | `src/app/(search-app)/formulation/` | -| `/mockups/*` | `src/app/mockups/` (404 in production) | -| `/auth/callback` | `src/app/auth/callback/route.ts` | +| Route | File | +| --------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `/` | `src/app/(search-app)/page.tsx` | +| Shared mode-home route group (`/(search-app)`) | `src/app/(search-app)/` | +| Mode homes (`/services`, `/dsm`, `/documents/…`, …) | `src/app/(search-app)/` shared shell group | +| `/applications` | `src/app/applications/route.ts` | +| `/differentials`, `/diagnoses`, `/presentations` | `src/app/(search-app)/differentials/` | +| `/dsm`, `/dsm/search`, `/dsm/compare`, `/dsm/diagnoses/[slug]` | `src/app/(search-app)/dsm/` | +| `/documents/search`, `/source`, `/evidence`, `/[id]` | `src/app/(search-app)/documents/` | +| `/factsheets`, `/factsheets/search`, `/factsheets/[slug]` | `src/app/(search-app)/factsheets/` | +| `/favourites` | `src/app/(search-app)/favourites/page.tsx` | +| `/forms`, `/forms/[slug]` | `src/app/(search-app)/forms/` | +| `/medications`, `/medications/[slug]` | `src/app/(search-app)/medications/` | +| `/privacy` | `src/app/privacy/page.tsx` | +| `/reference/colour-coding` | `src/app/reference/` | +| `/safety-plan` | `src/app/safety-plan/page.tsx` | +| `/services`, `/services/[slug]` | `src/app/(search-app)/services/` | +| `/therapy-compass` | `src/app/(search-app)/therapy-compass/` | +| `/tools` | `src/app/(search-app)/tools/` | +| `/specifiers`, `/specifiers/[slug]`, `/specifiers/builder`, `/specifiers/compare`, `/specifiers/map` | `src/app/(search-app)/specifiers/` | +| `/formulation`, `/formulation/[slug]`, `/formulation/builder`, `/formulation/compare`, `/formulation/map` | `src/app/(search-app)/formulation/` | +| `/mockups/*` | `src/app/mockups/` (404 in production) | +| `/auth/callback` | `src/app/auth/callback/route.ts` | ### API routes (`src/app/api/`) diff --git a/docs/operator-backlog.md b/docs/operator-backlog.md index 7985b677e6..ff7618411b 100644 --- a/docs/operator-backlog.md +++ b/docs/operator-backlog.md @@ -42,14 +42,14 @@ Findings inventory for handover: [audit-handover-2026-07-14.md](audit-handover-2 Each environment gets **separate** service-role + OpenAI keys (per-env blast radius). Placement is a dashboard/CLI action, never committed. -| Secret / config | Status | Where | Notes | -| ------------------------------------------ | ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Local fillable HMAC/probe secrets | ✅ local | `.env.local` (gitignored) | Use `npm run check:local-presence` / `--fill` for distinct local `OPENAI_SAFETY_IDENTIFIER_SECRET`, `RAG_QUERY_HASH_SECRET`, and `HEALTH_DEEP_PROBE_SECRET`. Never commit values. Hosted stores are separate. | -| `RAG_QUERY_HASH_SECRET` (prod) | 🔎 verify | Railway runtime secret | GitHub repo secret present since 2026-07-10 (CI green); confirm the SAME value is set in Railway runtime. PIA-2 fail-closed guard requires it at boot (min 16 chars) | -| `HEALTH_DEEP_PROBE_SECRET` (prod + GitHub) | ⚠️ partial | Railway runtime + GitHub repo secret | Railway production was set and the authorized deep probe returned healthy on 2026-07-19. GitHub remains pending: set the same value as a repo secret, set `PROD_HEALTH_URL`, then enable the ops-digest schedule. Cloud Agent re-probe 2026-07-26 blocked (GitHub secrets/vars 403; no Railway CLI/token; Railway MCP unavailable). | -| `SUPABASE_SERVICE_ROLE_KEY` (per env) | ⏳ pending | Railway runtime secret | accepts the `sb_secret_…` key | -| `OPENAI_API_KEY` (per env) | ⏳ pending | Railway runtime secret | `RAG_PROVIDER_MODE=auto` | -| OpenAI DPA / ZDR execution | ⏳ pending | OpenAI account + legal | app endpoints are ZDR-eligible; execution is operator + legal — see [openai-cross-border-basis.md](openai-cross-border-basis.md) | +| Secret / config | Status | Where | Notes | +| ------------------------------------------ | ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Local fillable HMAC/probe secrets | ✅ local | `.env.local` (gitignored) | Use `npm run check:local-presence` / `--fill` for distinct local `OPENAI_SAFETY_IDENTIFIER_SECRET`, `RAG_QUERY_HASH_SECRET`, and `HEALTH_DEEP_PROBE_SECRET`. Never commit values. Hosted stores are separate. | +| `RAG_QUERY_HASH_SECRET` (prod) | 🔎 verify | Railway runtime secret | GitHub repo secret present since 2026-07-10 (CI green); confirm the SAME value is set in Railway runtime. PIA-2 fail-closed guard requires it at boot (min 16 chars) | +| `HEALTH_DEEP_PROBE_SECRET` (prod + GitHub) | ⚠️ partial | Railway runtime + GitHub repo secret | Railway production was set and the authorized deep probe returned healthy on 2026-07-19. GitHub remains pending: set the same value as a repo secret, set `PROD_HEALTH_URL`, then enable the ops-digest schedule. | +| `SUPABASE_SERVICE_ROLE_KEY` (per env) | ⏳ pending | Railway runtime secret | accepts the `sb_secret_…` key | +| `OPENAI_API_KEY` (per env) | ⏳ pending | Railway runtime secret | `RAG_PROVIDER_MODE=auto` | +| OpenAI DPA / ZDR execution | ⏳ pending | OpenAI account + legal | app endpoints are ZDR-eligible; execution is operator + legal — see [openai-cross-border-basis.md](openai-cross-border-basis.md) | ## Disaster-recovery re-creation (does NOT survive a schema restore) diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index a7b8bb02fa..8542471eef 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -61,23 +61,24 @@ removed after current-main verification; it is not missing recommended work. | 9 | `#029` | A2 | Specialist — answer quality/clinical safety | After `#051`/`#023` and `#018` | 0.5–1 day inventory; 1–3 days per fix | Re-enumerate current fallback stubs and fix one causal cluster at a time without weakening grounding/citation gates. Stop if a change merely makes the metric easier to pass. | | 10 | `#001` | A2 | Specialist — retrieval/ranking | After `#051`/`#023` and rollout approval | 0.5–1 day plus canary | Keep semantic reranking off unless an approved ambiguity comparison preserves 36/36, recall 1.0, zero per-case regressions, and shows measured gain; otherwise record keep-off and stop. | | 11 | `#025` | A2 | Operator — Railway/GitHub/chat/Supabase | Next approved observability window | 1–3 hours/channel | Choose owned deployment, CI, ingestion, and SLO alerts; mock first, then one approved controlled provider event/channel. The merged Supabase trigger remains inert until its verified inputs are configured. Stop without an accountable responder. | -| 12 | `#055` | A2 | Specialist release owner + Operator | Before next full-confidence release/handoff | 2–4 hours plus runtime | On one exact SHA, run local/provider gates, Firefox/WebKit, required hosted CI, and close actionable GitHub threads. Stop at first failure and rerun only the repaired smallest gate. | -| 13 | `#056` | A2 | Operator — Supabase/Railway + Specialist | After cost/ownership approval | 0.5–1 day | Provision isolated `Clinical KB Staging` with synthetic data and distinct secrets. Verify identity, schema, indexing, health, and data boundary; never copy production clinical documents. | -| 14 | `#057` | A2 | High — release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | -| 15 | `#058` | A2 | Operator — production data + Specialist | Next approved production verification window | 30–60 min read-only; 1–2 hours if needed | Verify registry/differentials/medications are non-empty before writing; seed only confirmed gaps idempotently. Stop when healthy or owner/project identity is ambiguous. | -| 16 | `#011` | A3 | Operator — Supabase capacity | Immediately before first compute scale-up | 30–60 min plus observation | Switch Auth to percentage allocation, record before/after, and run approved advisor/health checks. Stop if no scale-up is planned. | -| 17 | `#017` | A3 | High — performance/browser | Before `#012`/`#013`/`#016`; approved live-site window | 1–2 hours | Capture reproducible mobile/desktop Lighthouse/Web-Vitals evidence and decide whether payload work is justified. Stop if metrics are acceptable or evidence is too noisy. | -| 18 | `#033` | A3 | Specialist — prompt/source governance | After `#022` and `#051`/`#023` | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | -| 19 | `#037` | A3 | Operator — clinical/product + Standard | Next trust-policy review | 30–60 min; up to 0.5 day | Decide whether routine claims cap at medium trust. Record policy; if accepted, change only the flag/expectations and run focused tests. | -| 20 | `#012`, `#013`, `#016` | A3 | High — bundling/runtime performance | After `#017` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. | -| 21 | `#035` | A3 | Specialist — evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. | -| 22 | `#027` | Optional | Operator — SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. | -| 23 | `#028` | Optional | Specialist privacy/observability + Operator | After privacy/ownership/cost approval | 1–3 days | Define vendor/region/retention/redaction/sampling/source-map envelope before SDK work. Prove no clinical text, identifiers, or secrets leave; stop if unacceptable. | -| 24 | `#038` | Optional | High — product/design architecture | When a new comparison surface is approved | 0.5–1 day | Define a shared interaction contract without flattening mode-specific content. Stop when no concrete new surface exists. | -| 25 | `#040` | Optional | High — visual QA/accessibility | When baseline owner/update workflow exists | 1–2 days | Establish a small stable desktop/mobile/accessibility baseline set. Do not make it blocking if flake or maintenance cost outweighs detection value. | -| 26 | `#039` | Optional | High — frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. | -| 27 | `#065` | A2 | High — document-viewer UI | Only when the user explicitly resumes the paused task | 0.5–1.5 days | Finish the compact source-text accordion, citation/search auto-open, print restoration, and 320/390/1280 px coverage. Keep the preserved branch untouched until explicit resume; no provider calls. | -| 28 | `#079` | Optional | High — repository hygiene | In explicitly scheduled batches | 30–60 minutes per batch | Disposition at most ten retained worktrees per pass using owner, PR, review-ledger, ancestry, and patch evidence. Preserve every dirty, active, secret-bearing, post-freeze, or ambiguous worktree and stop rather than broad-cleaning. | +| 12 | `#064` | A2 | High — frontend/browser | After higher-acuity local fixes; before the release UI gate | 4–8 hours | Preserve the isolated dirty formulation/contrast patch, reconcile its intent against current `main`, and run focused Playwright plus `verify:ui`. Stop rather than overwriting unrelated work or weakening access-control assertions. | +| 13 | `#055` | A2 | Specialist release owner + Operator | Before next full-confidence release/handoff | 2–4 hours plus runtime | On one exact SHA, run local/provider gates, Firefox/WebKit, required hosted CI, and close actionable GitHub threads. Stop at first failure and rerun only the repaired smallest gate. | +| 14 | `#056` | A2 | Operator — Supabase/Railway + Specialist | After cost/ownership approval | 0.5–1 day | Provision isolated `Clinical KB Staging` with synthetic data and distinct secrets. Verify identity, schema, indexing, health, and data boundary; never copy production clinical documents. | +| 15 | `#057` | A2 | High — release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | +| 16 | `#058` | A2 | Operator — production data + Specialist | Next approved production verification window | 30–60 min read-only; 1–2 hours if needed | Verify registry/differentials/medications are non-empty before writing; seed only confirmed gaps idempotently. Stop when healthy or owner/project identity is ambiguous. | +| 17 | `#011` | A3 | Operator — Supabase capacity | Immediately before first compute scale-up | 30–60 min plus observation | Switch Auth to percentage allocation, record before/after, and run approved advisor/health checks. Stop if no scale-up is planned. | +| 18 | `#017` | A3 | High — performance/browser | Before `#012`/`#013`/`#016`; approved live-site window | 1–2 hours | Capture reproducible mobile/desktop Lighthouse/Web-Vitals evidence and decide whether payload work is justified. Stop if metrics are acceptable or evidence is too noisy. | +| 20 | `#033` | A3 | Specialist — prompt/source governance | After `#022` and `#051`/`#023` | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | +| 21 | `#037` | A3 | Operator — clinical/product + Standard | Next trust-policy review | 30–60 min; up to 0.5 day | Decide whether routine claims cap at medium trust. Record policy; if accepted, change only the flag/expectations and run focused tests. | +| 22 | `#012`, `#013`, `#016` | A3 | High — bundling/runtime performance | After `#017` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. | +| 23 | `#035` | A3 | Specialist — evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. | +| 24 | `#027` | Optional | Operator — SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. | +| 25 | `#028` | Optional | Specialist privacy/observability + Operator | After privacy/ownership/cost approval | 1–3 days | Define vendor/region/retention/redaction/sampling/source-map envelope before SDK work. Prove no clinical text, identifiers, or secrets leave; stop if unacceptable. | +| 26 | `#038` | Optional | High — product/design architecture | When a new comparison surface is approved | 0.5–1 day | Define a shared interaction contract without flattening mode-specific content. Stop when no concrete new surface exists. | +| 27 | `#040` | Optional | High — visual QA/accessibility | When baseline owner/update workflow exists | 1–2 days | Establish a small stable desktop/mobile/accessibility baseline set. Do not make it blocking if flake or maintenance cost outweighs detection value. | +| 28 | `#039` | Optional | High — frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. | +| 29 | `#065` | A2 | High — document-viewer UI | Only when the user explicitly resumes the paused task | 0.5–1.5 days | Finish the compact source-text accordion, citation/search auto-open, print restoration, and 320/390/1280 px coverage. Keep the preserved branch untouched until explicit resume; no provider calls. | +| 30 | `#079` | Optional | High — repository hygiene | In explicitly scheduled batches | 30–60 minutes per batch | Disposition at most ten retained worktrees per pass using owner, PR, review-ledger, ancestry, and patch evidence. Preserve every dirty, active, secret-bearing, post-freeze, or ambiguous worktree and stop rather than broad-cleaning. | @@ -89,44 +90,45 @@ removed after current-main verification; it is not missing recommended work. > > **RAG reconciliation correction (2026-07-23):** fresh current-main live evidence supersedes the broad diagnosis in #018. The three named misses are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD still retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose; #019 remains post-retrieval comparison source selection. A narrow lithium subject-evidence guard improved its targeting result from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but was reverted and rejected because the required full canary failed. Keep #029 open for the remaining fallback-stub cases. Do not combine these residuals or change ranking scores, comparator ordering, aliases, clamps, or semantic reranking without a separate reproducer and passing canary pair. -| ID | Pri | Type | Summary | Detail / next action | Source | Added | -| ---- | --- | ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| #059 | P1 | task | Verify containment of every credential reported exposed in chat | **Outcome:** every reported exposed credential is rejected or retired. **Next:** in approved security windows, verify and revoke or rotate the GitHub token, OpenAI key, Supabase service-role JWT, database password, and E2E credential; create replacements only when required and update only intended secret stores. **Success:** provider evidence confirms the old credentials cannot authenticate, replacements are distinct and minimally scoped, presence/readiness checks pass, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never print or paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation; AI Agent Target Manifest | 2026-07-24 | -| #065 | P2 | task | Complete the paused compact document source-text accordion | **Outcome:** the document viewer uses compact nested disclosures while retaining complete text, citation/search navigation, print behavior, and composer clearance. **Next:** only when the user explicitly resumes, reconcile `codex/chat-document-text-accordion-7cb4` with current `main` and complete the focused 320/390/1280 px tests. **Success:** default disclosures are closed; deep links and search open only the active passage; printing expands/restores state; no overflow. **Verify:** focused document-viewer Playwright, `verify:cheap`, `verify:ui`, and static production-readiness. **Stop:** remain paused until explicit user return; no provider calls. | paused document-viewer task; `codex/chat-document-text-accordion-7cb4` | 2026-07-24 | -| #051 | P2 | task | Stabilise the live answer-quality canary before more RAG tuning | Diagnostics landed in PR #1095: structured JSON/Markdown artifacts now record the actual checked-out SHA, run identity and latency context, and the offline trend tool separates content, provider-route and latency outcomes. First validating run `30018289898` recorded the expected tree and cost, with 36/36 retrieval green, but one report cannot establish variability; PR #1097 prevents a single failure being mislabeled as repeated. Next: compare the scheduled 2026-07-26 structured report with this run. Do not spend on an immediate retry or reapply the archived lithium guard before that comparison. | PR #1095; run `30018289898`; PR #1097; archive ref `refs/archive/rejected-rag/20260723/monitoring-subject-gate` | 2026-07-23 | -| #001 | P2 | task | Semantic reranking still gated off | `RAG_SEMANTIC_RERANK_ENABLED=false` from PR #901. Do not enable until the provider-backed 36/36 retrieval-quality gate **and** an ambiguity-focused canary are explicitly approved and recorded. | `docs/process-hardening.md` (Semantic reranking rollout debt); PR #901 | 2026-07-21 | -| #069 | P3 | task | Live-profile table-facts plpgsql+EXECUTE latency | Migration `20260724120000_table_facts_plpgsql_execute.sql` plus P3 follow-ups (`20260724130000_*`, `20260724130100_*`) are in PR #1133. Hosted apply is blocked in this environment (no `SUPABASE_DB_URL`; Supabase MCP `needsAuth`). A live `profile:retrieval --rpc match_document_table_facts_text --analyze` attempt returned `Unregistered API key` against the injected service-role secret. **Next:** operator applies the three pending migrations on the live target project, then re-runs approval-gated `profile:retrieval` / `explain_retrieval_rpc` and confirms ~70ms-class plans with no ranking change. **Stop** without mutating ranking. | session 2026-07-24 Database interface audit; PR #1133 | 2026-07-24 | -| #053 | P1 | task | Execute cross-border privacy/legal package | Execute OpenAI and Railway DPAs; decide ZDR and Australian data residency; obtain prompt-cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not represent the release as privacy-approved or alter final public privacy wording before sign-off. | `docs/openai-cross-border-basis.md`; `docs/privacy-impact-assessment.md` | 2026-07-24 | -| #054 | P2 | task | Reconcile local and hosted secrets/config | **Local (2026-07-24):** `npm run check:local-presence` (+ optional `--fill`) presence-checks safety-identifier / query-hash / deep-probe (names + length buckets only), fails closed on ambiguous project identity, and fills only those three local HMAC/probe gaps into gitignored `.env.local`. Production-readiness now warns on the same local gaps. Provider keys and hosted Railway/GitHub stores were not touched. **Hosted probe (2026-07-26):** blocked in Cloud Agent — GitHub Actions secrets/variables API returns 403 (`Resource not accessible by integration`); Railway CLI/`RAILWAY_API_TOKEN` absent; Railway MCP discovery failed. No hosted secret values were read or written. **Next (operator):** from an account with repo-secret + Railway project access, presence-check names only then set confirmed gaps with distinct per-environment values: `RAG_QUERY_HASH_SECRET` (Railway must match GitHub if already set), `HEALTH_DEEP_PROBE_SECRET` + `PROD_HEALTH_URL` (enable/verify ops-digest), `SUPABASE_SERVICE_ROLE_KEY`, `OPENAI_API_KEY`, optional `OPENAI_SAFETY_IDENTIFIER_SECRET`, and schedule/Vault settings. Never record secret values. Stop on ambiguous project identity. | `.env.example`; `scripts/check-local-presence.mjs`; production-readiness; `docs/operator-backlog.md` | 2026-07-24 | -| #055 | P2 | task | Run one exact-SHA full release and PR gate | Before the next full-confidence release/handoff, record the candidate/PR SHA and run the local/provider release gates, Firefox/WebKit, required hosted CI, and actionable GitHub review-thread closure once. Stop at the first actionable failure and rerun only the repaired smallest gate. | `docs/launch-operator-runbook.md`; `docs/codex-review-protocol.md` | 2026-07-24 | -| #056 | P2 | task | Provision isolated staging environment | After explicit cost/ownership approval, provision `Clinical KB Staging` Supabase and Railway tiers with distinct secrets and synthetic/non-clinical data. Verify identity, schema, indexing, health, and the production-data boundary. | `docs/staging-setup.md`; `docs/operator-backlog.md` | 2026-07-24 | -| #057 | P2 | task | Complete staging soak and rollback rehearsal | After #056, run the documented soak and rollback against an exact candidate; retain latency/error/rollback evidence. Stop on unsafe data, identity mismatch, or an unowned rollback decision. | `docs/launch-operator-runbook.md`; `docs/capacity-review.md` | 2026-07-24 | -| #058 | P2 | task | Verify production content before any seed write | Against `the live target project`, verify registry, differentials, and medications surfaces are non-empty before writing. Seed only confirmed gaps idempotently with approved owner/project identity and confirmation flags. | `docs/launch-operator-runbook.md`; `docs/operator-backlog.md` | 2026-07-24 | -| #005 | P3 | rec | `finalScore` saturates at clamp ceiling | Base + ~40 stacked boosts routinely exceed 1.0, so strong matches tie at 1.0 and order by an arbitrary `document_id` tiebreak. If ranking is ever revisited, break ties by the **pre-clamp** score rather than raising the `[0,1]` ceiling (downstream gates assume `[0,1]`). Ordering already sorts by the unbounded pre-clamp `rankScore` (`clinical-search.ts:1735,1927,1950-1955`), so the clamp confines only the reported confidence value, not result order. Not a defect on the current golden set; any change here is a protected RAG surface (canary required). | `docs/rag-hybrid-findings-and-todo.md` P1 item 4; `src/lib/clinical-search.ts:1735` | 2026-07-21 | -| #011 | P3 | task | Auth DB-connection allocation is operator-only | Supabase Auth (GoTrue) is capped at ~10 absolute DB connections (Supabase perf advisor). Switch to **percentage-based** allocation in the Supabase **dashboard** before the first compute scale-up — **not settable via SQL/MCP** (operator-owned). Verify via a staging soak + an approval-gated read-only advisor re-check. | `docs/auth-connection-cap-runbook.md`; `docs/process-hardening.md` (Known follow-up debts) | 2026-07-21 | -| #012 | P3 | rec | Slim the lazy cross-mode differentials chunk | `cross-mode-differentials.ts` is dynamically imported (correctly code-split **out** of the initial/dashboard bundle — verified), but it pulls the full ~860 KB differentials snapshot (~125 KB gzip lazy chunk) just to build a tiny `{slug,title,clinicalHinge}` + presentations + aliases catalog. A precomputed lightweight index (generator + drift check, like the `specifiers-content` split / medications `fields=index`) would cut that lazy chunk ~5–10├ù. Not a bundle leak — an M-effort slim. | `src/lib/cross-mode-differentials.ts`; `src/components/clinical-dashboard/cross-mode-links.tsx:150`; session 2026-07-21 (build:analyze) | 2026-07-21 | -| #013 | P3 | rec | Route-chunk + mockup catalogue JSON weight | `build:analyze`: `/specifiers` ships `specifiers-search-index.json` (~180 KB parsed), `/forms` ships `forms-catalog.json` (~132 KB), `/formulation` ships `formulation-content.json` (~52 KB, client-side local search — needs index/full split or a search endpoint, architectural). All route-scoped (not initial bundle). Also `*-mockups.tsx` (~100 KB across chunks) build though `/mockups` 404s in prod — exclude from the prod artifact. | session 2026-07-21 (build:analyze) | 2026-07-21 | -| #016 | P3 | rec | "Big but not easy" structural + motion perf | Deferred larger levers: (a) nonce-CSP forces every product route to `╞Æ Dynamic` (zero static generation) — evaluate Partial Prerendering / static shells for the static clinical catalogues (DSM/differentials/therapy/specifiers/formulation); (b) sidebar expand/collapse animates `grid-template-columns` (biggest smoothness cost, motion-gated — needs a transform-overlay rethink); (c) Therapy Compass fetches 692 KB / 2.5 MB JSON client-side (defer until interaction + confirm brotli); (d) settings/setup/admin dialogs static-imported into the home chunk (`next/dynamic` them). | session 2026-07-21 (build route table + design audit) | 2026-07-21 | -| #017 | P3 | task | Field Web-Vitals baseline via live Lighthouse | In-sandbox runtime vitals were blocked (prod server hard-requires Supabase secrets; dev-mode CLS measured excellent at 0.00–0.04, content-first pages 0.000). Run Lighthouse against `psychiatry.tools` for real LCP/INP/CLS to prioritize #012–#016 by measured impact rather than reasoning. | session 2026-07-21 (measurement pass) | 2026-07-21 | -| #018 | P2 | task | Split the lithium, ADHD and metabolic residuals by mechanism | Revalidated on current main 2026-07-23: these are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose. The narrow lithium subject-evidence guard improved targeting from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but it was reverted because the full canary failed. After #051 stabilises the canary, add independent current-main reproducers and assess each mechanism separately. Do not widen the matcher or combine these into a broad ranking/composer change. | runs `30007833352` and `30009207429`; PR #1093; session 2026-07-23 | 2026-07-21 | -| #019 | P2 | task | Admission doc dropped after deterministic comparison packing | Reconfirmed on merged-main run `30018289898`: golden retrieval remained 36/36 and retrieved `MHSP.AdmissionCommunityPts.pdf`, but the answer's top five sources retained only `MHSP.Discharge.pdf` after `generation_fallback:generation_quality_failed; comparison_source_extractive_fallback`. PR #1096 replays the live score/order shape and proves deterministic answer ranking plus cross-document packing retain both admission and discharge evidence, so retrieval scores, aliases and comparator ordering are not the fix. Next: create a red fallback-layer unit reproducer using the live source shape; any behavior change still needs the existing baseline and a passing post canary. | run `30018289898`; PR #1096; session 2026-07-23 | 2026-07-21 | -| #021 | P3 | rec | E-3d H2 residual: strong/comparison generation discards | approx. 6 generation attempts per full 44-case run still fail the final quality gate and fall to extractive on strong-route comparison/complex shapes (the designed-conservative outcome). PARKED: weakest cost/benefit on the queue — a wave (approx. $2-4 pair + reviewer cycle) to shave seconds off a few hard cases. Revisit only if latency/waste complaints or a cheaper lever appears. | E-3c design record; runs #59-#61 diagnostics | 2026-07-21 | -| #022 | P2 | task | Source-governance metadata refresh (operator) | **Worklist generated 2026-07-22 ($0, read-only): `docs/source-governance-refresh-worklist-2026-07-22.md`.** Reframed - this is NOT 59 clinical reviews. Of the 124 documents surfacing in canary top results, 59 are review-required, and **38 (64 pct) are the BMJ published-reference tier all sitting at `clinical_validation_status: unverified`** - one attestation-policy decision, not 38 reviews. The remaining 21 are genuine local WA health-service reviews (FSH 7, NMHS 4, CAMHS 3, AKG 2, KEMH 2, RPBG 2, RKPG 1), mostly `document_status: review_due`. Burn-down: top-10 documents clear 44 pct of flagged slots, top-20 clear 66 pct. Next: decide the BMJ attestation policy, then attest local docs by visibility (start `Clozapine Management by GP (NMHS)`, 22 slots at rank 1). | runs #61/#57 Source Governance data; `docs/source-governance-refresh-worklist-2026-07-22.md` | 2026-07-21 | -| #023 | P2 | task | Read Sunday 2026-07-26 scheduled-run artifacts | The 18:00 UTC scheduled runs deliver three free datapoints at once: first full-44 weekly canary (validates the #1044 ANSWER_CASE_LIMIT raise), browser-matrix flake second datapoint (webkit ui-route-coverage now reproduced + root-caused 2026-07-22 ΓåÆ see #024; firefox ui-formulation:91 still awaits a datapoint), and the irrelevant@10 labeling-audit artifact (┬º3.1 human-decision class). Read all three, then disposition. | sessions 2026-07-20/21; branch-review-ledger convergence notes | 2026-07-21 | -| #025 | P2 | task | Activate the three webhooks (operator secrets) | Merged (#968/#1100) + deployed but inert — verified live: `POST /api/webhooks/railway` returns `503 webhook_not_configured`; the Supabase document-change trigger exists but lacks both activation inputs. To turn on: (1) Railway ΓåÆ set `RAILWAY_WEBHOOK_SECRET` + add the `?token=…` webhook URL; (2) set `SLACK_WEBHOOK_URL`/`DISCORD_WEBHOOK_URL` in BOTH the Railway **app/server env** and **GitHub repo secrets**; (3) set one matching document-change secret in the Railway app env as `SUPABASE_INGESTION_WEBHOOK_SECRET` and in Supabase Vault as `ingestion_webhook_secret`, then set the per-environment database GUC `app.ingestion_webhook_base_url` to the deployed app origin. Each path fails closed until fully configured, so this is pure ops. See `docs/webhooks.md` for verification and rotation. | sessions 2026-07-22/24; PRs #968/#1100; docs/webhooks.md | 2026-07-22 | -| #027 | P3 | rec | External uptime monitor independent of GitHub/Railway | `live-domain-monitor.yml` runs on GitHub's cron, so it won't run in exactly the outage it should catch (Actions or the deploy itself down). Add an off-platform synthetic monitor (UptimeRobot / Better Stack / Checkly) hitting `/api/health` with a webhook alert. Provider setup, not code. | session 2026-07-22 webhook review | 2026-07-22 | -| #028 | P3 | rec | Runtime error tracking (Sentry or similar) | No error tracking in the repo — production exceptions on `psychiatry.tools`, including how often `RAG_PROVIDER_MODE=auto` silently degrades to source-only, are invisible. Weigh adding `@sentry/nextjs` (dependency + DSN secret + instrumentation) vs cost; alert ΓåÆ chat/issue. Provider-backed; needs explicit sign-off before adding the dependency. | session 2026-07-22 webhook review | 2026-07-22 | -| #029 | P2 | issue | 12 of 30 answer-quality cases return the fallback stub | run #61 --dump-answers: 12/30 quality cases emit the source_backed_review_fallback boilerplate with answer_sections: [], all grounded with 4-6 citations. Some still PASS targeting because the stub echoes query keywords (the contraindication/document_lookup matchers need only a keyword), so the targeting metric MASKS the problem for those intents. Superset of #018 — fix in the extractive composer, validate with the provider-backed answer eval. | run #61 dump artifact; session 2026-07-22 | 2026-07-22 | -| #033 | P3 | rec | Source governance metadata absent from the LLM prompt | `buildRagSourceBlock` omits `document_status`, `clinical_validation_status`, and `extraction_quality`, so the model cannot self-caveat during generation and governance is enforced only post-hoc. Generation-surface change: needs `eval:rag` plus `eval:quality --rag-only` (grounded-supported must not drop, citation-failure 0) and explicit approval. Carries the same "unknown Γëá bad" hazard as #032 — on a partially-enriched corpus the model would likely over-caveat correct sources, so design the prompt wording before spending an eval. | `src/lib/rag/rag-source-block.ts:126-198`; PR #1051 audit item 8 | 2026-07-22 | -| #035 | P3 | rec | Threshold-conflict detection covers only 3 params | `detectThresholdDisagreements` checks only ANC, WBC, and platelets paired with withholding verbs, so cross-source conflicts on medication doses, lithium/thyroid levels, or vital signs go undetected. Deliberately narrow (see the comment at `:469-474`). Broadening changes when an answer is classified `conflicting` and adds warnings — real false-positive risk. Needs new fixtures plus a behaviour review before any change. | `src/lib/evidence.ts:469-574`; PR #1051 audit item 7 | 2026-07-22 | -| #036 | P3 | rec | 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 promotion migrations but never used as a retrieval filter. Promotion is unconditional on `clinical_validation_status`, so unverified documents are publicly searchable — compensated by keeping `unverified_source` in the frontend-visible warning set. A hard schema flag touches RLS and the clinical-risk-gated retrieval RPCs; weigh against the existing compensating control before acting. | `supabase/schema.sql:61-108`; `src/lib/search-scope.ts:181-236`; PR #1051 audit item 3 | 2026-07-22 | -| #037 | P3 | rec | D5 trust-cap-all-claims flag parked OFF | `NEXT_PUBLIC_RAG_TRUST_CAP_ALL_CLAIMS` extends authority gating from high-risk claims to **all** supported claims (`deriveTrust`). Ships OFF by design; flipping it caps trust to `medium` for routine claims across the board — a product/clinical-UX decision, not a defect. Both states are test-pinned. Next action: product decision, then flip and re-baseline the UI expectations. | `src/lib/answer-render-policy.ts:159-177`; PR #1051 audit item 11 | 2026-07-22 | -| #038 | P3 | rec | Consolidate shared comparison behavior | Several clinical modes expose comparison workflows with similar selection, empty-state and mobile-dock needs. Define one shared behavioral contract before another comparison surface is added; keep mode-specific clinical content separate. This is a design-system recommendation, not a current defect. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #079 | P3 | task | Disposition retained worktrees in bounded cleanup batches | **Outcome:** the retained reconciliation tail is gradually classified without another disruptive all-worktree sweep. **Next:** process no more than ten worktrees per explicitly scheduled pass using current owner/process metadata, open-PR state, exact review-ledger coverage, ancestry, and cherry-pick-aware content proof. **Success:** remove only clean, inactive, bundled worktrees whose content is merged or explicitly rejected; record every disposition and retain recovery evidence. **Stop:** preserve dirty, active, secret-bearing, post-freeze, paused, or ambiguous work and never use reset, force deletion, broad clean, or process killing. | final reconciliation inventory retained 104 independent worktrees; session 2026-07-24 | 2026-07-24 | -| #080 | P3 | rec | Re-test the removed admission-to-discharge alias widening | **Outcome:** decide with measured evidence whether the user-approved 2026-07-21 widening (NMHS admission-to-discharge titles satisfying the admission expectation) belongs back on the wide-tier `AdmissionCommunityPts` list. #030 removed it so one document could not fill both comparison slots, but coverage now enforces distinct document identities by maximum matching, so the widening is no longer a false-pass route and its removal may instead cost legitimate admission credit. **Next:** with approval, run an eval-canary baseline/post pair over the admission-discharge comparison cases with and without those two titles. **Success:** restore only on measured non-regression (recall pinned 1.0, zero per-case reciprocal-rank regressions); otherwise record keep-removed and close. **Stop:** never restore on offline reasoning alone. | `src/lib/eval-document-matching.ts`; #030 archive row; session 2026-07-25 | 2026-07-25 | -| #082 | P2 | issue | Bot branch-sync heads leave required checks unapproved | **Outcome:** an open PR stops stalling when the `github-actions[bot]` sync merge updates its branch. Every bot-authored head produces `action_required` runs for CI, SAST and Secret Scan, so the three required checks never report: PR #1220 was blocked through five successive syncs, and both a normal and an `--admin` merge were refused. Heads pushed by an agent or human still run normally, so the mitigation is the `skip-branch-sync` label plus an own-authored head (cleared PR #1217). **Next:** pick one fix — allow the sync bot in the repository Actions approval setting, push the sync with a token whose runs auto-execute, or drop auto-sync. **Success:** a bot-synced PR reaches green required checks with no manual push. **Stop:** never widen approval settings for fork/untrusted actors or clear the block by bypassing required checks. | GitHub #1235; PR #1220/#1217 2026-07-25 (bot heads action_required); session 2026-07-25 | 2026-07-25 | +| ID | Pri | Type | Summary | Detail / next action | Source | Added | +| ---- | --- | ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| #059 | P1 | task | Verify containment of every credential reported exposed in chat | **Outcome:** every reported exposed credential is rejected or retired. **Next:** in approved security windows, verify and revoke or rotate the GitHub token, OpenAI key, Supabase service-role JWT, database password, and E2E credential; create replacements only when required and update only intended secret stores. **Success:** provider evidence confirms the old credentials cannot authenticate, replacements are distinct and minimally scoped, presence/readiness checks pass, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never print or paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation; AI Agent Target Manifest | 2026-07-24 | +| #064 | P2 | task | Reconcile the preserved browser and contrast patch | **Outcome:** the isolated dirty formulation/contrast patch is safely landed or explicitly dispositioned. **Next:** rebase its intent against current `main` without overwriting the worktree, then run focused Playwright coverage and `verify:ui`. **Success:** intended disabled/contrast behavior is accessible, browser assertions remain meaningful, and unrelated work is preserved. **Stop:** do not discard or auto-merge the dirty worktree; pause on ambiguous ownership or scope. | `agent/formulation-disabled-contrast`; session 2026-07-24 | 2026-07-24 | +| #065 | P2 | task | Complete the paused compact document source-text accordion | **Outcome:** the document viewer uses compact nested disclosures while retaining complete text, citation/search navigation, print behavior, and composer clearance. **Next:** only when the user explicitly resumes, reconcile `codex/chat-document-text-accordion-7cb4` with current `main` and complete the focused 320/390/1280 px tests. **Success:** default disclosures are closed; deep links and search open only the active passage; printing expands/restores state; no overflow. **Verify:** focused document-viewer Playwright, `verify:cheap`, `verify:ui`, and static production-readiness. **Stop:** remain paused until explicit user return; no provider calls. | paused document-viewer task; `codex/chat-document-text-accordion-7cb4` | 2026-07-24 | +| #051 | P2 | task | Stabilise the live answer-quality canary before more RAG tuning | Diagnostics landed in PR #1095: structured JSON/Markdown artifacts now record the actual checked-out SHA, run identity and latency context, and the offline trend tool separates content, provider-route and latency outcomes. First validating run `30018289898` recorded the expected tree and cost, with 36/36 retrieval green, but one report cannot establish variability; PR #1097 prevents a single failure being mislabeled as repeated. Next: compare the scheduled 2026-07-26 structured report with this run. Do not spend on an immediate retry or reapply the archived lithium guard before that comparison. | PR #1095; run `30018289898`; PR #1097; archive ref `refs/archive/rejected-rag/20260723/monitoring-subject-gate` | 2026-07-23 | +| #001 | P2 | task | Semantic reranking still gated off | `RAG_SEMANTIC_RERANK_ENABLED=false` from PR #901. Do not enable until the provider-backed 36/36 retrieval-quality gate **and** an ambiguity-focused canary are explicitly approved and recorded. | `docs/process-hardening.md` (Semantic reranking rollout debt); PR #901 | 2026-07-21 | +| #069 | P3 | task | Live-profile table-facts plpgsql+EXECUTE latency | Migration `20260724120000_table_facts_plpgsql_execute.sql` plus P3 follow-ups (`20260724130000_*`, `20260724130100_*`) are in PR #1133. Hosted apply is blocked in this environment (no `SUPABASE_DB_URL`; Supabase MCP `needsAuth`). A live `profile:retrieval --rpc match_document_table_facts_text --analyze` attempt returned `Unregistered API key` against the injected service-role secret. **Next:** operator applies the three pending migrations on the live target project, then re-runs approval-gated `profile:retrieval` / `explain_retrieval_rpc` and confirms ~70ms-class plans with no ranking change. **Stop** without mutating ranking. | session 2026-07-24 Database interface audit; PR #1133 | 2026-07-24 | +| #053 | P1 | task | Execute cross-border privacy/legal package | Execute OpenAI and Railway DPAs; decide ZDR and Australian data residency; obtain prompt-cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not represent the release as privacy-approved or alter final public privacy wording before sign-off. | `docs/openai-cross-border-basis.md`; `docs/privacy-impact-assessment.md` | 2026-07-24 | +| #054 | P2 | task | Reconcile local and hosted secrets/config | **Local (2026-07-24):** `npm run check:local-presence` (+ optional `--fill`) presence-checks safety-identifier / query-hash / deep-probe (names + length buckets only), fails closed on ambiguous project identity, and fills only those three local HMAC/probe gaps into gitignored `.env.local`. Production-readiness now warns on the same local gaps. Provider keys and hosted Railway/GitHub stores were not touched. **Next (hosted/operator):** presence-check Railway runtime + GitHub repo secrets for query-hash, deep-probe (+ `PROD_HEALTH_URL` / ops-digest), service-role, OpenAI, and schedule/Vault settings; set only confirmed gaps with distinct per-environment values. Never record secret values. Provider reads/writes require approval. | `.env.example`; `scripts/check-local-presence.mjs`; production-readiness; `docs/operator-backlog.md` | 2026-07-24 | +| #055 | P2 | task | Run one exact-SHA full release and PR gate | Before the next full-confidence release/handoff, record the candidate/PR SHA and run the local/provider release gates, Firefox/WebKit, required hosted CI, and actionable GitHub review-thread closure once. Stop at the first actionable failure and rerun only the repaired smallest gate. | `docs/launch-operator-runbook.md`; `docs/codex-review-protocol.md` | 2026-07-24 | +| #056 | P2 | task | Provision isolated staging environment | After explicit cost/ownership approval, provision `Clinical KB Staging` Supabase and Railway tiers with distinct secrets and synthetic/non-clinical data. Verify identity, schema, indexing, health, and the production-data boundary. | `docs/staging-setup.md`; `docs/operator-backlog.md` | 2026-07-24 | +| #057 | P2 | task | Complete staging soak and rollback rehearsal | After #056, run the documented soak and rollback against an exact candidate; retain latency/error/rollback evidence. Stop on unsafe data, identity mismatch, or an unowned rollback decision. | `docs/launch-operator-runbook.md`; `docs/capacity-review.md` | 2026-07-24 | +| #058 | P2 | task | Verify production content before any seed write | Against `the live target project`, verify registry, differentials, and medications surfaces are non-empty before writing. Seed only confirmed gaps idempotently with approved owner/project identity and confirmation flags. | `docs/launch-operator-runbook.md`; `docs/operator-backlog.md` | 2026-07-24 | +| #005 | P3 | rec | `finalScore` saturates at clamp ceiling | Base + ~40 stacked boosts routinely exceed 1.0, so strong matches tie at 1.0 and order by an arbitrary `document_id` tiebreak. If ranking is ever revisited, break ties by the **pre-clamp** score rather than raising the `[0,1]` ceiling (downstream gates assume `[0,1]`). Ordering already sorts by the unbounded pre-clamp `rankScore` (`clinical-search.ts:1735,1927,1950-1955`), so the clamp confines only the reported confidence value, not result order. Not a defect on the current golden set; any change here is a protected RAG surface (canary required). | `docs/rag-hybrid-findings-and-todo.md` P1 item 4; `src/lib/clinical-search.ts:1735` | 2026-07-21 | +| #011 | P3 | task | Auth DB-connection allocation is operator-only | Supabase Auth (GoTrue) is capped at ~10 absolute DB connections (Supabase perf advisor). Switch to **percentage-based** allocation in the Supabase **dashboard** before the first compute scale-up — **not settable via SQL/MCP** (operator-owned). Verify via a staging soak + an approval-gated read-only advisor re-check. | `docs/auth-connection-cap-runbook.md`; `docs/process-hardening.md` (Known follow-up debts) | 2026-07-21 | +| #012 | P3 | rec | Slim the lazy cross-mode differentials chunk | `cross-mode-differentials.ts` is dynamically imported (correctly code-split **out** of the initial/dashboard bundle — verified), but it pulls the full ~860 KB differentials snapshot (~125 KB gzip lazy chunk) just to build a tiny `{slug,title,clinicalHinge}` + presentations + aliases catalog. A precomputed lightweight index (generator + drift check, like the `specifiers-content` split / medications `fields=index`) would cut that lazy chunk ~5–10├ù. Not a bundle leak — an M-effort slim. | `src/lib/cross-mode-differentials.ts`; `src/components/clinical-dashboard/cross-mode-links.tsx:150`; session 2026-07-21 (build:analyze) | 2026-07-21 | +| #013 | P3 | rec | Route-chunk + mockup catalogue JSON weight | `build:analyze`: `/specifiers` ships `specifiers-search-index.json` (~180 KB parsed), `/forms` ships `forms-catalog.json` (~132 KB), `/formulation` ships `formulation-content.json` (~52 KB, client-side local search — needs index/full split or a search endpoint, architectural). All route-scoped (not initial bundle). Also `*-mockups.tsx` (~100 KB across chunks) build though `/mockups` 404s in prod — exclude from the prod artifact. | session 2026-07-21 (build:analyze) | 2026-07-21 | +| #016 | P3 | rec | "Big but not easy" structural + motion perf | Deferred larger levers: (a) nonce-CSP forces every product route to `╞Æ Dynamic` (zero static generation) — evaluate Partial Prerendering / static shells for the static clinical catalogues (DSM/differentials/therapy/specifiers/formulation); (b) sidebar expand/collapse animates `grid-template-columns` (biggest smoothness cost, motion-gated — needs a transform-overlay rethink); (c) Therapy Compass fetches 692 KB / 2.5 MB JSON client-side (defer until interaction + confirm brotli); (d) settings/setup/admin dialogs static-imported into the home chunk (`next/dynamic` them). | session 2026-07-21 (build route table + design audit) | 2026-07-21 | +| #017 | P3 | task | Field Web-Vitals baseline via live Lighthouse | In-sandbox runtime vitals were blocked (prod server hard-requires Supabase secrets; dev-mode CLS measured excellent at 0.00–0.04, content-first pages 0.000). Run Lighthouse against `psychiatry.tools` for real LCP/INP/CLS to prioritize #012–#016 by measured impact rather than reasoning. | session 2026-07-21 (measurement pass) | 2026-07-21 | +| #018 | P2 | task | Split the lithium, ADHD and metabolic residuals by mechanism | Revalidated on current main 2026-07-23: these are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose. The narrow lithium subject-evidence guard improved targeting from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but it was reverted because the full canary failed. After #051 stabilises the canary, add independent current-main reproducers and assess each mechanism separately. Do not widen the matcher or combine these into a broad ranking/composer change. | runs `30007833352` and `30009207429`; PR #1093; session 2026-07-23 | 2026-07-21 | +| #019 | P2 | task | Admission doc dropped after deterministic comparison packing | Reconfirmed on merged-main run `30018289898`: golden retrieval remained 36/36 and retrieved `MHSP.AdmissionCommunityPts.pdf`, but the answer's top five sources retained only `MHSP.Discharge.pdf` after `generation_fallback:generation_quality_failed; comparison_source_extractive_fallback`. PR #1096 replays the live score/order shape and proves deterministic answer ranking plus cross-document packing retain both admission and discharge evidence, so retrieval scores, aliases and comparator ordering are not the fix. Next: create a red fallback-layer unit reproducer using the live source shape; any behavior change still needs the existing baseline and a passing post canary. | run `30018289898`; PR #1096; session 2026-07-23 | 2026-07-21 | +| #021 | P3 | rec | E-3d H2 residual: strong/comparison generation discards | approx. 6 generation attempts per full 44-case run still fail the final quality gate and fall to extractive on strong-route comparison/complex shapes (the designed-conservative outcome). PARKED: weakest cost/benefit on the queue — a wave (approx. $2-4 pair + reviewer cycle) to shave seconds off a few hard cases. Revisit only if latency/waste complaints or a cheaper lever appears. | E-3c design record; runs #59-#61 diagnostics | 2026-07-21 | +| #022 | P2 | task | Source-governance metadata refresh (operator) | **Worklist generated 2026-07-22 ($0, read-only): `docs/source-governance-refresh-worklist-2026-07-22.md`.** Reframed - this is NOT 59 clinical reviews. Of the 124 documents surfacing in canary top results, 59 are review-required, and **38 (64 pct) are the BMJ published-reference tier all sitting at `clinical_validation_status: unverified`** - one attestation-policy decision, not 38 reviews. The remaining 21 are genuine local WA health-service reviews (FSH 7, NMHS 4, CAMHS 3, AKG 2, KEMH 2, RPBG 2, RKPG 1), mostly `document_status: review_due`. Burn-down: top-10 documents clear 44 pct of flagged slots, top-20 clear 66 pct. Next: decide the BMJ attestation policy, then attest local docs by visibility (start `Clozapine Management by GP (NMHS)`, 22 slots at rank 1). | runs #61/#57 Source Governance data; `docs/source-governance-refresh-worklist-2026-07-22.md` | 2026-07-21 | +| #023 | P2 | task | Read Sunday 2026-07-26 scheduled-run artifacts | The 18:00 UTC scheduled runs deliver three free datapoints at once: first full-44 weekly canary (validates the #1044 ANSWER_CASE_LIMIT raise), browser-matrix flake second datapoint (webkit ui-route-coverage now reproduced + root-caused 2026-07-22 ΓåÆ see #024; firefox ui-formulation:91 still awaits a datapoint), and the irrelevant@10 labeling-audit artifact (┬º3.1 human-decision class). Read all three, then disposition. | sessions 2026-07-20/21; branch-review-ledger convergence notes | 2026-07-21 | +| #025 | P2 | task | Activate the three webhooks (operator secrets) | Merged (#968/#1100) + deployed but inert — verified live: `POST /api/webhooks/railway` returns `503 webhook_not_configured`; the Supabase document-change trigger exists but lacks both activation inputs. To turn on: (1) Railway ΓåÆ set `RAILWAY_WEBHOOK_SECRET` + add the `?token=…` webhook URL; (2) set `SLACK_WEBHOOK_URL`/`DISCORD_WEBHOOK_URL` in BOTH the Railway **app/server env** and **GitHub repo secrets**; (3) set one matching document-change secret in the Railway app env as `SUPABASE_INGESTION_WEBHOOK_SECRET` and in Supabase Vault as `ingestion_webhook_secret`, then set the per-environment database GUC `app.ingestion_webhook_base_url` to the deployed app origin. Each path fails closed until fully configured, so this is pure ops. See `docs/webhooks.md` for verification and rotation. | sessions 2026-07-22/24; PRs #968/#1100; docs/webhooks.md | 2026-07-22 | +| #027 | P3 | rec | External uptime monitor independent of GitHub/Railway | `live-domain-monitor.yml` runs on GitHub's cron, so it won't run in exactly the outage it should catch (Actions or the deploy itself down). Add an off-platform synthetic monitor (UptimeRobot / Better Stack / Checkly) hitting `/api/health` with a webhook alert. Provider setup, not code. | session 2026-07-22 webhook review | 2026-07-22 | +| #028 | P3 | rec | Runtime error tracking (Sentry or similar) | No error tracking in the repo — production exceptions on `psychiatry.tools`, including how often `RAG_PROVIDER_MODE=auto` silently degrades to source-only, are invisible. Weigh adding `@sentry/nextjs` (dependency + DSN secret + instrumentation) vs cost; alert ΓåÆ chat/issue. Provider-backed; needs explicit sign-off before adding the dependency. | session 2026-07-22 webhook review | 2026-07-22 | +| #029 | P2 | issue | 12 of 30 answer-quality cases return the fallback stub | run #61 --dump-answers: 12/30 quality cases emit the source_backed_review_fallback boilerplate with answer_sections: [], all grounded with 4-6 citations. Some still PASS targeting because the stub echoes query keywords (the contraindication/document_lookup matchers need only a keyword), so the targeting metric MASKS the problem for those intents. Superset of #018 — fix in the extractive composer, validate with the provider-backed answer eval. | run #61 dump artifact; session 2026-07-22 | 2026-07-22 | +| #033 | P3 | rec | Source governance metadata absent from the LLM prompt | `buildRagSourceBlock` omits `document_status`, `clinical_validation_status`, and `extraction_quality`, so the model cannot self-caveat during generation and governance is enforced only post-hoc. Generation-surface change: needs `eval:rag` plus `eval:quality --rag-only` (grounded-supported must not drop, citation-failure 0) and explicit approval. Carries the same "unknown Γëá bad" hazard as #032 — on a partially-enriched corpus the model would likely over-caveat correct sources, so design the prompt wording before spending an eval. | `src/lib/rag/rag-source-block.ts:126-198`; PR #1051 audit item 8 | 2026-07-22 | +| #035 | P3 | rec | Threshold-conflict detection covers only 3 params | `detectThresholdDisagreements` checks only ANC, WBC, and platelets paired with withholding verbs, so cross-source conflicts on medication doses, lithium/thyroid levels, or vital signs go undetected. Deliberately narrow (see the comment at `:469-474`). Broadening changes when an answer is classified `conflicting` and adds warnings — real false-positive risk. Needs new fixtures plus a behaviour review before any change. | `src/lib/evidence.ts:469-574`; PR #1051 audit item 7 | 2026-07-22 | +| #036 | P3 | rec | 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 promotion migrations but never used as a retrieval filter. Promotion is unconditional on `clinical_validation_status`, so unverified documents are publicly searchable — compensated by keeping `unverified_source` in the frontend-visible warning set. A hard schema flag touches RLS and the clinical-risk-gated retrieval RPCs; weigh against the existing compensating control before acting. | `supabase/schema.sql:61-108`; `src/lib/search-scope.ts:181-236`; PR #1051 audit item 3 | 2026-07-22 | +| #037 | P3 | rec | D5 trust-cap-all-claims flag parked OFF | `NEXT_PUBLIC_RAG_TRUST_CAP_ALL_CLAIMS` extends authority gating from high-risk claims to **all** supported claims (`deriveTrust`). Ships OFF by design; flipping it caps trust to `medium` for routine claims across the board — a product/clinical-UX decision, not a defect. Both states are test-pinned. Next action: product decision, then flip and re-baseline the UI expectations. | `src/lib/answer-render-policy.ts:159-177`; PR #1051 audit item 11 | 2026-07-22 | +| #038 | P3 | rec | Consolidate shared comparison behavior | Several clinical modes expose comparison workflows with similar selection, empty-state and mobile-dock needs. Define one shared behavioral contract before another comparison surface is added; keep mode-specific clinical content separate. This is a design-system recommendation, not a current defect. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | +| #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | +| #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | +| #079 | P3 | task | Disposition retained worktrees in bounded cleanup batches | **Outcome:** the retained reconciliation tail is gradually classified without another disruptive all-worktree sweep. **Next:** process no more than ten worktrees per explicitly scheduled pass using current owner/process metadata, open-PR state, exact review-ledger coverage, ancestry, and cherry-pick-aware content proof. **Success:** remove only clean, inactive, bundled worktrees whose content is merged or explicitly rejected; record every disposition and retain recovery evidence. **Stop:** preserve dirty, active, secret-bearing, post-freeze, paused, or ambiguous work and never use reset, force deletion, broad clean, or process killing. | final reconciliation inventory retained 104 independent worktrees; session 2026-07-24 | 2026-07-24 | +| #080 | P3 | rec | Re-test the removed admission-to-discharge alias widening | **Outcome:** decide with measured evidence whether the user-approved 2026-07-21 widening (NMHS admission-to-discharge titles satisfying the admission expectation) belongs back on the wide-tier `AdmissionCommunityPts` list. #030 removed it so one document could not fill both comparison slots, but coverage now enforces distinct document identities by maximum matching, so the widening is no longer a false-pass route and its removal may instead cost legitimate admission credit. **Next:** with approval, run an eval-canary baseline/post pair over the admission-discharge comparison cases with and without those two titles. **Success:** restore only on measured non-regression (recall pinned 1.0, zero per-case reciprocal-rank regressions); otherwise record keep-removed and close. **Stop:** never restore on offline reasoning alone. | `src/lib/eval-document-matching.ts`; #030 archive row; session 2026-07-25 | 2026-07-25 | +| #082 | P2 | issue | Bot branch-sync heads leave required checks unapproved | **Outcome:** an open PR stops stalling when the `github-actions[bot]` sync merge updates its branch. Every bot-authored head produces `action_required` runs for CI, SAST and Secret Scan, so the three required checks never report: PR #1220 was blocked through five successive syncs, and both a normal and an `--admin` merge were refused. Heads pushed by an agent or human still run normally, so the mitigation is the `skip-branch-sync` label plus an own-authored head (cleared PR #1217). **Next:** pick one fix — allow the sync bot in the repository Actions approval setting, push the sync with a token whose runs auto-execute, or drop auto-sync. **Success:** a bot-synced PR reaches green required checks with no manual push. **Stop:** never widen approval settings for fork/untrusted actors or clear the block by bypassing required checks. | GitHub #1235; PR #1220/#1217 2026-07-25 (bot heads action_required); session 2026-07-25 | 2026-07-25 | ## Resolved / archive @@ -134,7 +136,6 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | ID | Type | Summary | Outcome | Resolved | | ---- | ----- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| #064 | task | Reconcile the preserved browser and contrast patch | Landed via PR #1250 squash `b91b4600171be08198e92bcf19b7d67e8207cb2f`. Opacity-free disabled Previous/Continue styling plus native-disabled/focus/axe Playwright coverage is on `main`. Historical `agent/formulation-disabled-contrast` remained unrecovered; conflicted PRs #1219/#1223/#1226/#1231/#1249 were closed without merge. | 2026-07-26 | | #024 | issue | WebKit e2e `_rsc` prefetch access-control errors | Fixed in PR #1205 after current-main reproducers showed catch-all interception plus duplicate viewport navigation racing same-origin WebKit RSC prefetches. The harness now intercepts external HTTP(S) only, directly proves blocking/recording, navigates each route once, and leaves `pageerror` unfiltered. On `64e46036`, the local Chromium/WebKit matrix passed 24/24 and `verify:pr-local` passed 3,431 tests, build/bundle checks, and 36 offline RAG fixtures. | 2026-07-25 | | #081 | issue | Open PR #1196 would undo the #030 alias tightening | Closed as no longer live: PR #1196 was closed 2026-07-25 as superseded by #913/current `main` (~680 commits behind, conflicting), and its successor #1198 does not touch `src/lib/eval-document-matching.ts`. The generalized alias-disjointness and single-document contracts landed in PR #1215 fail closed if any later branch re-adds the dual-listed admission aliases, so the regression route is guarded rather than watched. | 2026-07-25 | | #077 | issue | Concurrent tasks can re-dirty the canonical primary checkout | Added cooperative primary-checkout write lease with dirty/operation fail-closed checks, stale-owner recovery, and lifecycle start/cleanup wiring; focused concurrency tests refuse a second primary writer while read-only/feature worktrees stay unblocked. | 2026-07-25 | diff --git a/docs/search-chrome-behaviour.md b/docs/search-chrome-behaviour.md index 241c3d175b..fd53d8d663 100644 --- a/docs/search-chrome-behaviour.md +++ b/docs/search-chrome-behaviour.md @@ -4,14 +4,13 @@ This repo uses one shared search experience across the global shell, dashboard r ## Page ownership model -| Page state | Composer placement | Reserve owner | -| ----------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| Answer home / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve | -| Submitted/search-result views | Compact bottom dock on phones; pinned below the header on tablets; in normal page flow on desktop | Shell/dashboard `--mobile-composer-reserve` on phones; page content on desktop | -| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves | -| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding | -| Calculators (`/calculators`) | Page-owned composer (desktop top + phone bottom dock) | Calculators page pad; shell reserve stays `0` | -| Info/detail pages with no composer | No fixed composer | Idle shell padding only | +| Page state | Composer placement | Reserve owner | +| ----------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- | +| Answer home / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve | +| Submitted/search-result views | Compact bottom dock on phones; header/inline placement on larger screens | Shell/dashboard `--mobile-composer-reserve` | +| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves | +| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding | +| Info/detail pages with no composer | No fixed composer | Idle shell padding only | ## Invariants @@ -20,7 +19,7 @@ This repo uses one shared search experience across the global shell, dashboard r 3. A visible fixed phone dock may include `var(--safe-area-bottom)` so the pill clears the home indicator. 4. A hidden phone dock must release the content-facing reserve to `0rem`; do not use `env(safe-area-inset-bottom)` or `var(--safe-area-bottom)` for hidden content padding. 5. Edge-to-edge phone dock mode is `left: 0; right: 0; bottom: 0; width: 100%`; inset the pill with padding, not with a non-zero bottom offset. -6. Header and footer chrome that share the same scroll signal should hide/reveal symmetrically for the surfaces that actually hide: when the top bar is hidden, underlying content must be visible to the viewport edge. The OS top safe-area band (`chrome-safe-area-top` / `var(--safe-area-top)`) is never released — scrolled text must not paint under the status-bar icons. Top-bar hide/reveal is cross-breakpoint; the search field stays pinned on tablets, while desktop search belongs to page flow and scrolls away naturally; the bottom search dock is phone-only. Hidden bottom dock reserve stays `0rem` (invariant 4). Read "Scroll hide/reveal" below before changing either. +6. Header and footer chrome that share the same scroll signal should hide/reveal symmetrically for the surfaces that actually hide: when the top bar is hidden, underlying content must be visible to the viewport edge. The OS top safe-area band (`chrome-safe-area-top` / `var(--safe-area-top)`) is never released — scrolled text must not paint under the status-bar icons. Top-bar hide/reveal is cross-breakpoint; the search field stays on tablet/desktop; the bottom search dock is phone-only. Hidden bottom dock reserve stays `0rem` (invariant 4). Read "Scroll hide/reveal" below before changing either. 7. Do not add page-local dock-sized `pb-[calc(...safe-area...)]` under a shell-owned dock. Put clearance in the shared reserve or the page-owned composer, never both. 8. `GlobalSearchShell` uses an inner `mobile-composer-reserve-pad` so phone padding contributes to scroll height; do not move phone shell clearance back to scrollport padding without a browser proof. 9. Keep collapse-budget policy geometry-aware: an in-flow collapsing header needs enough remaining runway to absorb header + dock clearance, while a fixed overlay that only releases bottom reserve may hide when its post-collapse range retains the top reveal band plus deliberate hide intent _and_ the current offset already fits that post-collapse range (no material near-bottom clamp). Do not use synthetic page padding to make the stricter gate pass. @@ -28,31 +27,31 @@ This repo uses one shared search experience across the global shell, dashboard r ## Scroll hide/reveal -The universal **top bar** (mode, new chat, menu) is the only sticky desktop chrome: it hides on a deliberate scroll down and returns on a deliberate scroll up at **every** breakpoint. Tablet search stays pinned below it. Desktop search is mounted at the top of normal page content, so it scrolls away with that content and is independent of the header's hide state. Only the phone bottom search dock scroll-hides, and that stays phone-only. The top bar and phone dock read one `useScrollHideReporter` per host, so they can never disagree about direction. +The universal **top bar** (mode, new chat, menu) hides on a deliberate scroll down and returns on a deliberate scroll up at **every** breakpoint. The search field does **not** hide with it on tablet or desktop — only the phone bottom search dock still scroll-hides, and that stays phone-only. Both the top bar and the phone dock read one `useScrollHideReporter` per host, so they can never disagree about direction. Choose the hide mechanism from where the host's scrollport lives, because that decides what hiding costs the reader: -| Host | Scrollport | `hideOnScroll` | Mechanism | -| --------------------------------- | --------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `ClinicalDashboard` (answer view) | `
` at every width | `strategy: "overlay", allBreakpoints` | Absolute glass top bar translates off; `
` keeps its top reserve; search stays | -| `ClinicalDashboard` (other modes) | `
` at every width | `strategy: "collapse", wide: "collapse"` | Top-bar row collapses; tablet search stays sticky; desktop search portals into `
` page flow | -| `GlobalSearchShell` | `#main-content` on phones, the document above | `strategy: "collapse", wide: "sticky"` | Tablet pins [top bar \| search]; desktop portals search into `#main-content`, leaving a sticky auto-hiding top bar | +| Host | Scrollport | `hideOnScroll` | Mechanism | +| --------------------------------- | --------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `ClinicalDashboard` (answer view) | `
` at every width | `strategy: "overlay", allBreakpoints` | Absolute glass top bar translates off; `
` keeps its top reserve; search stays | +| `ClinicalDashboard` (other modes) | `
` at every width | `strategy: "collapse", wide: "collapse"` | Top-bar 1fr -> 0fr grid row; released strip goes to content; search stays as a sibling | +| `GlobalSearchShell` | `#main-content` on phones, the document above | `strategy: "collapse", wide: "sticky"` | Sticky stack of [top bar \| search]; only the top-bar row collapses so search rises to the top | Rules that keep this working: -- **Hide the top bar, not the search field.** The collapse wrapper (`data-testid="universal-header-collapse"`) wraps `header#search` plus optional page chrome that must match top-bar hide/reveal (via `headerCollapseAddonSlotId`). Keep composers outside the collapse row: tablet search stays pinned independently, and desktop search scrolls with page content rather than being translated by the header. +- **Hide the top bar, not the search field, above phones.** The collapse wrapper (`data-testid="universal-header-collapse"`) wraps `header#search` plus optional page chrome that must match top-bar hide/reveal (via `headerCollapseAddonSlotId`). Putting the inline search composer inside that wrapper is what made tablet/desktop search disappear with the mode bar — keep composers outside the collapse row. - **Page chrome that must match the top bar portals into the collapse host.** Do not pin tool secondary nav with `position: sticky` inside `#main-content` on phones — after the header collapses that sticky row becomes a second stuck header. Therapy's section strip portals into `#therapy-header-collapse-addon-slot` below `max-sm` and stays in-flow sticky above that. - **Feed the reporter from the element that actually scrolls.** `GlobalSearchShell`'s `#main-content` is the scrollport only on phones, so above that it also runs `useDocumentScrollHideReporter`. That hook self-gates: the phone shell is `fixed inset-0`, so the document cannot scroll and never fires. -- **Tablet stickiness belongs on the outer [top bar \| search] stack, not on `header#search`.** The top bar sits inside header-height boxes, which leaves a sticky rule on it zero travel. For the same reason the stack's ancestor in `GlobalSearchShell` is `display: contents` above the phone breakpoint rather than a block, and collapse returns a fragment (safe-area spacer + stack) rather than a single root box. At desktop widths the search portal leaves that same outer stack holding only the top bar. -- **Collapse only the top-bar row inside a sticky stack.** On tablets, translating the whole stack would take the search field off-screen; collapsing just the top bar lets search stay pinned at the viewport top below the always-on safe-area spacer. On desktop, the page-flow search is outside the stack entirely. +- **Sticky belongs on the outer [top bar \| search] stack, not on `header#search`.** The top bar sits inside header-height boxes, which leaves a sticky rule on it zero travel. For the same reason the stack's ancestor in `GlobalSearchShell` is `display: contents` above the phone breakpoint rather than a block, and collapse returns a fragment (safe-area spacer + stack) rather than a single root box. +- **Collapse only the top-bar row inside a sticky stack.** Translating the whole stack would take the search field off-screen; collapsing just the top bar lets search stay pinned at the viewport top below the always-on safe-area spacer. - **Keep `chrome-safe-area-top` outside the hide mechanism.** Collapse hosts render an always-on `h-[var(--safe-area-top)]` spacer sibling; the 0fr grid only moves the top-bar controls. Sticky chrome pins at `top: var(--safe-area-top)` so the spacer stays put while the bar collapses. -- **Do not double-sticky tablet search inside an outer sticky stack.** When `wide: "sticky"` owns the tablet stack, the composer stays `relative` in that stack. A second sticky search with its own `top` overlays page controls (and blocks clicks) once the top bar collapses. -- **Desktop search is page-owned.** `desktop-page-search-composer-slot` is rendered at the top of normal shell/dashboard content and accepts the shared composer only at `min-width: 1024px`. The mode-home hero slot takes precedence. Never give the desktop page composer, its slot, or an ancestor `fixed`/`sticky` positioning. +- **Do not double-sticky the search inside an outer sticky stack.** When `wide: "sticky"` owns an outer stack, the composer stays `relative` in that stack. A second sticky search with its own `top` overlays page controls (and blocks clicks) once the top bar collapses. +- **Pinned search can cover wide side rails.** The sticky stack stays centred and can geometrically overlap `xl` decision rails (Services). Prefer `scroll-margin-top` on rail controls and centre scroll-into-view in tests; do not put the composer back to `fixed`/`sticky` inside the stack to “win” that fight. - **Collapse-everywhere hosts still drop their own sticky search offset while the top bar is hidden.** Dashboard result composers that clear a visible top bar with `top: 4.75rem + safe-area` must switch to `top: 0` when collapse hide is active — otherwise a dead band the height of the mode bar remains above the search field. - **Rebase the reporter on geometry switches.** Pass `resetKey` when the host changes the scrollport under it (`ClinicalDashboard` passes `searchMode`, which swaps `
`'s header reserve); otherwise the carried-over offset spends the first post-switch scroll on a spurious hide or reveal. - **Do not carry composer focus into submitted result views.** Focus pins both chrome edges for keyboard safety. `GlobalSearchShell` must not pass `focus: true` with `run: true`, must gate `queryInputAutoFocus` on `!hasSubmittedModeSearch`, and must blur the dock input when the result canvas scrolls so hide-on-scroll can reclaim the header and the bottom dock (including its white safe-area rail). -Coverage: `tests/header-scroll-hide-contract.test.ts` (wiring), `tests/use-hide-on-scroll.test.ts` (decision logic), `tests/ui-chrome-scroll.spec.ts` (tablet pinned-search behaviour and desktop page-flow search plus top-bar hide/reveal), `tests/ui-phone-scroll.spec.ts` (phone scroll geometry), `tests/ui-therapy-nav-scroll.spec.ts` (Therapy section nav hide/reveal with the top bar). +Coverage: `tests/header-scroll-hide-contract.test.ts` (wiring), `tests/use-hide-on-scroll.test.ts` (decision logic), `tests/ui-chrome-scroll.spec.ts` (tablet/desktop top-bar hide/reveal with search still visible), `tests/ui-phone-scroll.spec.ts` (phone scroll geometry), `tests/ui-therapy-nav-scroll.spec.ts` (Therapy section nav hide/reveal with the top bar). ## Change checklist diff --git a/docs/site-map.md b/docs/site-map.md index f71206fa0a..50fc97a62a 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -5,7 +5,6 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check` ## Main product routes - `/` - Main Clinical KB shell. Source: `src/app/(search-app)/page.tsx`. -- `/calculators` - Route discovered from app directory Source: `src/app/(search-app)/calculators/page.tsx`. - `/differentials` - Differentials home and search surface. Source: `src/app/(search-app)/differentials/page.tsx`. - `/differentials/diagnoses` - Diagnosis stream. Source: `src/app/(search-app)/differentials/diagnoses/page.tsx`. - `/differentials/presentations/[slug]` - Route discovered from app directory Source: `src/app/(search-app)/differentials/presentations/[slug]/page.tsx`. diff --git a/scripts/check-design-system-contract.mjs b/scripts/check-design-system-contract.mjs index 3910ddae52..9eb2832ddd 100644 --- a/scripts/check-design-system-contract.mjs +++ b/scripts/check-design-system-contract.mjs @@ -28,7 +28,8 @@ function isPrototype(relativePath) { return ( relativePath.includes("/mockups/") || relativePath.includes("-mockup") || - relativePath.includes("/favourites-page-mockups/") + relativePath.includes("/favourites-page-mockups/") || + relativePath.includes("/calculator-mockups/") ); } diff --git a/scripts/repro-coalesce-poison-race.mjs b/scripts/repro-coalesce-poison-race.mjs new file mode 100644 index 0000000000..c87d7eca17 --- /dev/null +++ b/scripts/repro-coalesce-poison-race.mjs @@ -0,0 +1,133 @@ +/** + * Proves the coalesce poison race: + * last waiter aborts the shared controller but leaves the map entry until + * the producer's .finally deletes it; a new healthy caller joins the dying + * promise and fails even though its own signal is not aborted. + * + * Mirrors src/lib/openai.ts awaitInflightEmbedding / embedTextWithTelemetry + * and src/app/api/search/route.ts coalesceScopedSearch. + */ +import { setTimeout as delay } from "node:timers/promises"; + +function awaitWithCallerSignal(promise, signal) { + if (!signal) return promise; + if (signal.aborted) { + return Promise.reject( + signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError"), + ); + } + return new Promise((resolve, reject) => { + const onAbort = () => { + cleanup(); + reject( + signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError"), + ); + }; + const cleanup = () => signal.removeEventListener("abort", onAbort); + signal.addEventListener("abort", onAbort, { once: true }); + promise.then( + (value) => { + cleanup(); + resolve(value); + }, + (error) => { + cleanup(); + reject(error); + }, + ); + }); +} + +async function runBuggyPattern({ deleteOnLastWaiterAbort }) { + const inflight = new Map(); + let producerStarts = 0; + let sharedAbortCount = 0; + + async function awaitInflight(entry, signal) { + entry.waiters += 1; + try { + return await awaitWithCallerSignal(entry.promise, signal); + } finally { + entry.waiters -= 1; + if (entry.waiters === 0 && !entry.settled) { + entry.controller.abort(new DOMException("No active waiters.", "AbortError")); + sharedAbortCount += 1; + if (deleteOnLastWaiterAbort && inflight.get("k") === entry) { + inflight.delete("k"); + } + } + } + } + + async function coalesce(signal) { + signal.throwIfAborted(); + let entry = inflight.get("k"); + if (!entry) { + const controller = new AbortController(); + producerStarts += 1; + entry = { + promise: Promise.resolve(null), + controller, + waiters: 0, + settled: false, + }; + entry.promise = delay(50, "ok", { signal: controller.signal }).finally(() => { + entry.settled = true; + if (inflight.get("k") === entry) inflight.delete("k"); + }); + inflight.set("k", entry); + } + return awaitInflight(entry, signal); + } + + const firstController = new AbortController(); + const first = coalesce(firstController.signal); + await delay(0); + firstController.abort(new DOMException("first left", "AbortError")); + await first.catch(() => {}); + + // Map still holds the dying entry in the buggy pattern. + const mapHasDyingEntry = inflight.has("k"); + const dyingAborted = inflight.get("k")?.controller.signal.aborted ?? false; + + const secondController = new AbortController(); + let secondError = null; + let secondValue = null; + try { + secondValue = await coalesce(secondController.signal); + } catch (error) { + secondError = error; + } + + return { + deleteOnLastWaiterAbort, + producerStarts, + sharedAbortCount, + mapHasDyingEntryAfterFirstAbort: mapHasDyingEntry, + dyingAbortedAfterFirstAbort: dyingAborted, + secondCallerSignalAborted: secondController.signal.aborted, + secondValue, + secondErrorName: secondError?.name ?? null, + secondErrorMessage: secondError?.message ?? null, + poisoned: Boolean(secondError) && !secondController.signal.aborted, + }; +} + +const buggy = await runBuggyPattern({ deleteOnLastWaiterAbort: false }); +const fixed = await runBuggyPattern({ deleteOnLastWaiterAbort: true }); + +console.log("=== coalesce poison race repro ==="); +console.log("BUGGY (leave map entry after last-waiter abort):"); +console.log(JSON.stringify(buggy, null, 2)); +console.log("FIXED (delete map entry immediately on last-waiter abort):"); +console.log(JSON.stringify(fixed, null, 2)); + +if (!buggy.poisoned) { + console.error("EXPECTED buggy pattern to poison the healthy second caller."); + process.exit(1); +} +if (fixed.poisoned || fixed.secondValue !== "ok" || fixed.producerStarts !== 2) { + console.error("EXPECTED fixed pattern to start a fresh producer and succeed."); + process.exit(1); +} +console.log("PROOF OK: buggy poisons healthy caller; immediate delete recovers."); diff --git a/src/app/(search-app)/calculators/page.tsx b/src/app/(search-app)/calculators/page.tsx deleted file mode 100644 index cb6ca98959..0000000000 --- a/src/app/(search-app)/calculators/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Metadata } from "next"; - -import { CalculatorsSearchPage } from "@/components/calculators"; - -export const metadata: Metadata = { - title: "Calculators - Clinical KB", - description: "Psychiatry clinical decision calculators and rating scales with source-cited scoring guidance.", -}; - -export default function CalculatorsRoute() { - return ; -} diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts index bfc8de55e1..c109cd7e5e 100644 --- a/src/app/api/search/route.ts +++ b/src/app/api/search/route.ts @@ -128,6 +128,11 @@ async function coalesceScopedSearch>( ) { signal.throwIfAborted(); let entry = scopedSearchInflight.get(key); + // Skip a flight already aborted by its last waiter (owner-catalogue pattern). + if (entry?.controller.signal.aborted) { + if (scopedSearchInflight.get(key) === entry) scopedSearchInflight.delete(key); + entry = undefined; + } const coalesced = Boolean(entry); if (!entry) { const controller = new AbortController(); @@ -149,7 +154,12 @@ async function coalesceScopedSearch>( return { payload: (await awaitWithCallerSignal(entry.promise, signal)) as T, coalesced }; } finally { entry.waiters -= 1; - if (entry.waiters === 0 && !entry.settled) entry.controller.abort(); + // Drop the map entry immediately on last-waiter abort so a new healthy + // caller cannot join the dying promise before the producer's .finally runs. + if (entry.waiters === 0 && !entry.settled) { + entry.controller.abort(); + if (scopedSearchInflight.get(key) === entry) scopedSearchInflight.delete(key); + } } } diff --git a/src/app/globals.css b/src/app/globals.css index ad09367b11..c74775f03d 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -99,9 +99,7 @@ /* Font families: bind Tailwind's font-sans / font-mono to the loaded Geist faces (variables set on by next/font). font-mono is used for clinical codes and IDs (guideline refs, chunk/page numbers, versions). */ - --font-sans: - var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, - Arial, sans-serif; + --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Arial, sans-serif; --font-mono: var(--font-geist-mono), ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, monospace; --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1); @@ -586,9 +584,11 @@ summary::-webkit-details-marker { beat @layer components, so a literal there would reintroduce drift. */ padding-left: max(var(--header-edge-pad), var(--safe-area-left)); padding-right: max(var(--header-edge-pad), var(--safe-area-right)); - /* Wide layouts retain the glass treatment. Phones override this with a - solid surface below: a translucent scrim extending past the bar veils - mode-home titles instead of behaving like bounded chrome. */ + /* Translucent glass bar: the header's backdrop-blur utilities frost the + content scrolling beneath it (they were inert over the old opaque + var(--surface)). The .edge-glass-header-backdrop scrim supersedes the + old hard border-bottom and below-header ::after gradient. Keep this + value in lock-step with .universal-header below (chrome parity). */ background: color-mix(in srgb, var(--surface) 72%, transparent); box-shadow: none; } @@ -1380,24 +1380,6 @@ summary::-webkit-details-marker { because its override also changes other properties. A single mobile-scoped rule leaves nothing for the compiler to merge away. */ @media (max-width: 639px) { - /* Phone baseline: a full-width opaque header. Controls still respect the - side safe areas, but no blur or tint extends below the actual bar. */ - .edge-glass-header, - .universal-header { - background: var(--surface); - backdrop-filter: none; - } - - .edge-glass-header-backdrop, - .edge-glass-header-backdrop::before, - .edge-glass-header-backdrop::after { - display: none; - background: none; - backdrop-filter: none; - mask-image: none; - -webkit-mask-image: none; - } - .answer-suggestion-row-scroll { mask-image: linear-gradient(90deg, black calc(100% - 1rem), transparent); -webkit-mask-image: linear-gradient(90deg, black calc(100% - 1rem), transparent); @@ -1831,9 +1813,10 @@ summary::-webkit-details-marker { 0 8px 22px rgb(16 24 40 / 8%); } - /* Edge-to-edge phone dock: one full-width footer surface flush to the - viewport. The pill is the only translucent layer; the dock itself paints - the safe-area/home-indicator region. Never add a non-zero bottom offset. */ + /* Edge-to-edge phone dock: full-width footer flush to the glass. The pill is + inset via padding-bottom only — never by a non-zero `bottom` offset (that + reintroduces a white strip under the bar). Form background paints the + safe-area pad so it reads as dock chrome, not empty page margin. */ .answer-footer-search-dock.answer-footer-search-edge, .answer-footer-search-dock.dashboard-composer-edge.answer-footer-search-edge, .answer-footer-search-dock.document-mobile-search-edge.answer-footer-search-edge { @@ -1846,7 +1829,13 @@ summary::-webkit-details-marker { padding-inline: max(0.75rem, var(--safe-area-left)) max(0.75rem, var(--safe-area-right)); padding-top: 0.5rem; padding-bottom: max(0.5rem, var(--safe-area-bottom)); - background: var(--surface); + background: linear-gradient( + 180deg, + transparent 0%, + color-mix(in srgb, var(--background) 72%, transparent) 42%, + color-mix(in srgb, var(--background) 94%, transparent) 72%, + var(--background) 100% + ); } .answer-footer-search-dock.document-mobile-search-edge.answer-footer-search-edge.document-mobile-search-compact, @@ -1889,13 +1878,13 @@ summary::-webkit-details-marker { pointer-events: none; } - .answer-footer-search-dock .answer-footer-search-backdrop { - display: none; - } - .answer-footer-search-dock .answer-footer-search-pill { border-color: var(--border-strong); - background: color-mix(in srgb, var(--surface) 92%, transparent); + background: var(--surface); + /* The dock repaints the pill opaque, so the inherited blur has nothing + translucent to sample — drop it to save a compositing layer per scroll + frame (matches the chip override just below). */ + backdrop-filter: none; box-shadow: 0 -1px 0 color-mix(in srgb, var(--border) 60%, transparent), 0 1px 3px rgb(16 24 40 / 5%); diff --git a/src/app/mockups/calculators-bedside-sheet/page.tsx b/src/app/mockups/calculators-bedside-sheet/page.tsx index faa07f0cfd..fdfd598da9 100644 --- a/src/app/mockups/calculators-bedside-sheet/page.tsx +++ b/src/app/mockups/calculators-bedside-sheet/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsBedsideSheetMockup } from "@/components/calculators"; +import { CalculatorsBedsideSheetMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Bedside Sheet Mockup - Clinical KB", diff --git a/src/app/mockups/calculators-clinical-console/page.tsx b/src/app/mockups/calculators-clinical-console/page.tsx index 0a04c66158..baf66eeb26 100644 --- a/src/app/mockups/calculators-clinical-console/page.tsx +++ b/src/app/mockups/calculators-clinical-console/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsClinicalConsoleMockup } from "@/components/calculators"; +import { CalculatorsClinicalConsoleMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Clinical Console Mockup - Clinical KB", diff --git a/src/app/mockups/calculators-directory-grid/page.tsx b/src/app/mockups/calculators-directory-grid/page.tsx index bebb44ef52..811927db56 100644 --- a/src/app/mockups/calculators-directory-grid/page.tsx +++ b/src/app/mockups/calculators-directory-grid/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsDirectoryGridMockup } from "@/components/calculators"; +import { CalculatorsDirectoryGridMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Directory Grid Mockup - Clinical KB", diff --git a/src/app/mockups/calculators-guided-flow/page.tsx b/src/app/mockups/calculators-guided-flow/page.tsx index c957ac289a..eeab3f746e 100644 --- a/src/app/mockups/calculators-guided-flow/page.tsx +++ b/src/app/mockups/calculators-guided-flow/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsGuidedFlowMockup } from "@/components/calculators"; +import { CalculatorsGuidedFlowMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Guided Flow Mockup - Clinical KB", diff --git a/src/app/mockups/calculators-popup-sheet/page.tsx b/src/app/mockups/calculators-popup-sheet/page.tsx index 8f37ebe823..ef91a5d445 100644 --- a/src/app/mockups/calculators-popup-sheet/page.tsx +++ b/src/app/mockups/calculators-popup-sheet/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsPopupSheetMockup } from "@/components/calculators"; +import { CalculatorsPopupSheetMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Popup Sheet Mockup - Clinical KB", diff --git a/src/app/mockups/calculators-search-page/page.tsx b/src/app/mockups/calculators-search-page/page.tsx index 329f975f57..2e39887c4d 100644 --- a/src/app/mockups/calculators-search-page/page.tsx +++ b/src/app/mockups/calculators-search-page/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsSearchPage } from "@/components/calculators"; +import { CalculatorsSearchPageMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Search Page Mockup - Clinical KB", @@ -9,5 +9,5 @@ export const metadata: Metadata = { }; export default function CalculatorsSearchPageMockupRoute() { - return ; + return ; } diff --git a/src/app/mockups/calculators-search/page.tsx b/src/app/mockups/calculators-search/page.tsx index 1d424b7707..804b0a21d6 100644 --- a/src/app/mockups/calculators-search/page.tsx +++ b/src/app/mockups/calculators-search/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; -import { CalculatorsSearchDetailMockup } from "@/components/calculators"; +import { CalculatorsSearchDetailMockup } from "@/components/calculator-mockups"; export const metadata: Metadata = { title: "Calculators Search Mockup - Clinical KB", diff --git a/src/app/mockups/recent-searches-bottom/page.tsx b/src/app/mockups/recent-searches-bottom/page.tsx index 79c275596f..5d38f69840 100644 --- a/src/app/mockups/recent-searches-bottom/page.tsx +++ b/src/app/mockups/recent-searches-bottom/page.tsx @@ -29,7 +29,7 @@ function RecentSearchRail() { className="inline-flex min-h-9 max-w-[15rem] shrink-0 items-center gap-1.5 rounded-full border border-[color:var(--border)] bg-[color:var(--surface-raised)]/78 px-3 text-xs font-semibold text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] backdrop-blur transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface)] hover:text-[color:var(--text)]" > - {search} + {search} ))} diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index fbffa31dba..1c7dfb653e 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -201,11 +201,7 @@ import type { DocumentLabel, } from "@/lib/types"; import type { SearchScopeFilters } from "@/lib/search-scope"; -import { - desktopPageComposerSlotId, - differentialsMobileCompareAddonSlotId, - modeHomeDesktopComposerSlotId, -} from "@/lib/mode-home-composer"; +import { differentialsMobileCompareAddonSlotId, modeHomeDesktopComposerSlotId } from "@/lib/mode-home-composer"; import { toolCatalogRecords } from "@/lib/tools-catalog"; import { createQuoteFollowUp, type AnswerViewMode, shouldPollForUpdates } from "@/lib/ward-output"; import { @@ -3061,13 +3057,7 @@ export function ClinicalDashboard({ !modeSearchSubmitted && !(query.trim() && documentMatches.length > 0))))); const desktopHomeComposerSlotId = showDesktopHomeComposer ? modeHomeDesktopComposerSlotId : undefined; - const desktopResultComposerSlotId = - !desktopHomeComposerSlotId && searchMode !== "answer" ? desktopPageComposerSlotId : undefined; - // Any mounted mode home (answer, documents, prescribing, differentials, tools, - // favourites) keeps the in-flow hero pill on phones ("all") per the - // page-ownership contract. Only result/submitted views fall back to "sm-up" - // so phones get the compact bottom dock. - const heroComposerBreakpoint = showDesktopHomeComposer ? "all" : "sm-up"; + const heroComposerBreakpoint = showDesktopHomeComposer || showAnswerHome ? "all" : "sm-up"; const heroOwnsPhoneComposer = Boolean(desktopHomeComposerSlotId) && heroComposerBreakpoint === "all"; const hasMobileBottomSearch = searchMode !== "answer" && !heroOwnsPhoneComposer; // Favourites and Tools are content-rich hubs: they share the centred hero but @@ -3365,7 +3355,6 @@ export function ClinicalDashboard({ differentialsCompareAddonActive ? differentialsMobileCompareAddonSlotId : undefined } desktopHomeComposerSlotId={desktopHomeComposerSlotId} - desktopPageComposerSlotId={desktopResultComposerSlotId} // Mode homes keep the composer in the centred hero slot at every // breakpoint; documents, therapy, and other homes share the phone/tablet structure. heroComposerBreakpoint={heroComposerBreakpoint} @@ -3479,13 +3468,6 @@ export function ClinicalDashboard({ : "pb-8 sm:pb-10 lg:pb-12", )} > - {desktopResultComposerSlotId ? ( -
- ) : null} {actionNotice && ( setActionNotice(null)} animated> {actionNotice.message} diff --git a/src/components/applications-launcher-page.tsx b/src/components/applications-launcher-page.tsx index c707dda512..009abe5c53 100644 --- a/src/components/applications-launcher-page.tsx +++ b/src/components/applications-launcher-page.tsx @@ -3,7 +3,6 @@ import Link from "next/link"; import { Brain, - Calculator, ChevronRight, ClipboardCheck, ClipboardList, @@ -96,7 +95,6 @@ const launcherIconById: Record = { forms: FileCheck2, "care-plans": ClipboardCheck, "safety-plan": ClipboardList, - calculators: Calculator, monitoring: Waves, favourites: Star, }; diff --git a/src/components/calculators/bedside-sheet.tsx b/src/components/calculator-mockups/bedside-sheet-mockup.tsx similarity index 98% rename from src/components/calculators/bedside-sheet.tsx rename to src/components/calculator-mockups/bedside-sheet-mockup.tsx index ffda9d8243..3914be772e 100644 --- a/src/components/calculators/bedside-sheet.tsx +++ b/src/components/calculator-mockups/bedside-sheet-mockup.tsx @@ -160,7 +160,9 @@ export function CalculatorsBedsideSheetMockup() {