diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 53fad287e..fbdaac08e 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -18,29 +18,30 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD ## Review Records -| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks | -| ---------- | --------------------------------------------------- | ---------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 2026-07-10 | codex/design-ux-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | design-system + UX + design | Five issue groups confirmed; scoped fixes applied in the worktree. | `npm run check:type-scale`; focused Vitest (19/19); `npm run typecheck`; `npm run lint`; `npm run sitemap:check`; browser/API-backed checks awaiting approval | -| 2026-07-11 | codex/design-ux-review-integration | 98093ec7b | branch-integration-review | Replayed the reviewed design and UX fixes onto current `origin/main`, preserved the lightweight evidence-panel boundary, and retained the merged quality fixes. | `npm run check:type-scale`; combined focused Vitest (8 files, 42 tests); runtime/action/sitemap/type-scale/lint stages of `verify:cheap`; typecheck blocked by stale worktree dependencies pending hosted clean install; `git diff --check` | -| 2026-07-09 | example/branch | abc1234 | branch-cleanup | Example: already merged into `main`; no unique patch content. | `git log --right-only --cherry-pick main...example/branch`; `git diff --name-status main...example/branch` | -| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree diff: PR testing streamlining | Changes requested: 2 P1 clinical-gate defects and 7 P2/P3 scope, local parity, and UI-process defects. | `npm run check:ci-scope`; `npm run check:github-actions`; `npm run check:codex-autofix-workflow`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; targeted scope classifications; `git diff --check` | -| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree remediation review | All recorded P1-P3 findings fixed; no remaining high-confidence issue in the changed scope. | `npm run verify:cheap`; `npm run verify:pr-local`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; `npm run test:e2e:advisory`; CI YAML parse; scope/action/Codex guards; `git diff --check` | -| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow | Fixed exact connector authorization, trusted-marker deduplication, and strict self-trigger matching; added regression coverage. | `npm run check:codex-autofix-workflow`; focused Vitest (4 passed); `npm run verify:cheap` pre-test stages passed before tool timeout; `npm test` (1,415 passed, 1 skipped); focused Prettier check; `npm run check:github-actions` | -| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow-followup | Fixed untrusted workflow-level concurrency interference and migrated the bridge from the Node 20 action runtime to `actions/github-script@v9`; added direct embedded-script execution coverage. | Focused Vitest (13 passed); targeted ESLint; `tsc --noEmit`; `npm run check:codex-autofix-workflow`; `npm run check:github-actions`; focused Prettier check; `git diff --check` | -| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-residual-fixes | Replaced one-shot PR deduplication with a three-cycle head-SHA cap, made comment permission failures fail visibly, and pinned `github-script` v9.0.0 to its verified immutable commit. | TDD red run (7 expected failures); focused Vitest green run (15 passed); `npm run verify:cheap` (152 files passed, 1 skipped; 1,426 tests passed, 1 skipped); focused Prettier check; `git diff --check`; official `git ls-remote` tag verification | -| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | architecture-review | Seven findings fixed in the working tree: three runtime cycles, unbounded owner caches, a client/server env boundary breach, reversed runtime-to-scripts ownership, and architecture-doc drift. | `npm run test -- tests/architecture-boundaries.test.ts tests/bounded-ttl-cache.test.ts tests/rag-score.test.ts tests/rag-cache-utils.test.ts tests/rag-cache-invalidation.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; `npm run check:production-readiness:ci` | -| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | frontend-architecture-review | Shared cycle/env findings confirmed and fixed; three additional findings fixed for defeated lazy boundaries, duplicate shell/dashboard subscriptions, and unstable search-context values. | `npm run test -- tests/architecture-boundaries.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; UI gate deferred pending explicit local-API approval | -| 2026-07-11 | codex/architecture-review-integration | b45df727b29aad8ba4ec5d4e96d1f0599d7dad8a | branch-integration-review | Replayed the reviewed architecture fixes onto current `origin/main`; preserved current CI/autofix history and found no new high-confidence defect in the integrated diff. | `npm run check:runtime`; `npm run check:github-actions`; `npm run sitemap:check`; `npm run lint`; `npm run typecheck`; focused Vitest (24 passed); full Vitest with `--testTimeout=30000` (1,433 passed, 1 skipped); `git diff --check` | -| 2026-07-10 | codex/quality-testing-typescript-fixes | 648abfa3f | code-quality + testing + TypeScript | 17 confirmed P2/P3 issues fixed; no P0/P1 findings; residual large-module complexity noted. | Focused Vitest and Playwright; full Vitest 1427 passed/1 skipped; coverage; lint; typecheck; production-readiness CI | -| 2026-07-11 | codex/quality-review-integration | d3fcef8bbc9ab12b929771421b532c1ed8b7e1e7 | branch-integration-review | Replayed the quality, testing, and TypeScript fixes onto current `origin/main` and consolidated the stronger standalone auth callback coverage into this branch. | Changed-file Prettier; focused Vitest (5 files, 23 tests); `git diff --check`; original branch full Vitest/coverage/lint/typecheck and targeted Chromium evidence retained | -| 2026-07-11 | codex/architecture-review-integration | 665103250ccc33b5870862b8d8467607a1ae5d23 | coderabbit-followup | Fixed POSIX project-root identity collisions and closed dynamic-import and self-cycle gaps in the architecture regression guard. | Local-server Vitest passed; architecture-boundaries Vitest passed (6 tests); `npm run typecheck`; focused Prettier; `git diff --check` | -| 2026-07-11 | codex/architecture-review-followup | f5deaaee98864f1d32c1060ae14966a4f5975872 | coderabbit-test-followup | Removed probabilistic no-collision assertions from the local identity test and replaced them with deterministic normalization, repeatability, ID-shape, and port-range checks. | Local-server Vitest (2 passed); focused Prettier; `git diff --check`; hosted CI/SAST/Secret Scan passed on the reviewed head | -| 2026-07-11 | codex/pr-check-followup | 298e8f5bec2a4673dd225da3f446f008b8f25953 | residual-pr-check-hardening | Ported only the three PR-check improvements not already merged by PR #454: pinned Supabase CLI/cache ownership, advisory Semgrep coverage for Edge Functions, and regression guards for both contracts. | `npm run check:github-actions`; `npm run check:ci-scope`; focused Prettier; `git diff --check` | -| 2026-07-11 | claude/mobile-search-bar-fix (PR #456) | b73196c2e2e4a536804cdcdb50879c29e2c582c5 | PR required-testing review | All 4 Advisory UI regression failures confirmed PR-caused via A/B against pre-merge main (01f2cee0d): the 640px mode-home query moved the phone composer out of the hero, contradicting the design tests; residual ≥640px vanish remained when the slot never mounts. PR merged (b32c17b34) before the rework landed; follow-up fix shipped on `claude/mode-home-composer-hero-fix` (0px hero query restored, portal-outcome inline fallback, new `@critical` composer-presence test). Also found: main CI red on every push — missing `RAG_QUERY_HASH_SECRET` secret fails the deployment boot smoke and skips `release-browser-matrix`; owner adding the secret. | Local chromium A/B (PR head 4/5 fail vs baseline product-pass); rework targeted run 6/6 pass incl. new `@critical`; `npm run typecheck`; `npm run lint`; focused Prettier check | -| 2026-07-11 | PR #487 / claude/answer-page-design-polish-ffd5a6 | b2c772606126f8323424bc9c0b636bac77c08789 | open-PR review, unresolved comments, and CI | Two findings fixed: expanded weak/unsupported prior answers retain an explicit source-review warning, and cross-mode search actions no longer log an incorrect detail-open telemetry event. Added a persisted prior-turn browser assertion. No additional high-confidence defect was found in the changed scope. | Focused answer-render and cross-mode Vitest (20/20); TypeScript; focused Prettier; `git diff --check`. Browser assertion delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #469 / claude/response-formatting-cleanup-b57a9c | f1864308e0b287bb83b2a13daca4c3aa2ab95a3e | open-PR review, unresolved comments, and CI | P2 fixed: the OCR bullet sanitizer now preserves line-start O blood values when followed by blood or red-cell noun tails while still stripping non-blood bullets such as `o Negative screen`. No additional high-confidence defect was found in the nine-file diff. | Focused sanitizer/extractive Vitest (75/75); TypeScript; focused Prettier; `git diff --check`. Production-readiness script ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | -| 2026-07-11 | PR #466 / claude/search-timeout-failure-s6aiuj | 54d52292eeb9e1c7856b3dad89d1b72e0d49fd53 | open-PR review, unresolved comments, and CI | P2 fixed: SSE progress/token/error emission now tolerates a client cancellation racing an enqueue, so the catch path cannot throw while reporting the original stream error. No additional high-confidence defect was found in the six-file diff. | Focused SSE and search utility Vitest (13/13); TypeScript; focused Prettier. Hosted advisory browser failure was shared stale assertion drift and is rerun after this push. | -| 2026-07-11 | PR #483 / claude/differentials-page-review-a3daaf | 36cca1bf7c13718dcc60a61b75272c7c4fa5cd44 | open-PR review, unresolved comments, and CI | P2 fixed: authenticated diagnosis detail responses now derive related links, overlap links, and comparison presentation from the owner's current diagnosis and presentation rows rather than the bundled snapshot. Added an owner-only catalog regression test. No additional high-confidence defect was found in the changed scope. | Focused differentials route/catalog Vitest (26/26); TypeScript; focused Prettier; `git diff --check`. Production readiness ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | -| 2026-07-11 | PR #489 / claude/document-viewer-redesign-55b68b | 9130c8b15a22dbbc965464a247ae930c04f2da62 | open-PR review, unresolved comments, and CI | P2 fixed: document deep links now expand the mobile indexed-text details and scroll the branch-specific visible mobile or desktop chunk instead of the first duplicated DOM match. Added focused desktop/mobile assertions. No additional high-confidence defect was found in the three-file diff. | Focused Prettier; TypeScript; `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #488 / claude/code-review-42a2c3 | 7a8ea145013444f7cc29631499f48a8b0454937a | open-PR review, unresolved comments, and CI | Confirmed the remaining public error-code finding was already fixed at the reviewed head. Added the two focused advisory UI assertion stabilizations required by the hosted failure logs; no additional high-confidence defect was found in the changed scope. | `tests/http-error-response.test.ts` (3/3); Prettier check on affected files; `git diff --check`; hosted required CI passed before the test-only fix. Browser rerun deferred to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #473 / claude/mobile-search-bar-popup-bx163m | 7cef01852a9713ec51184578868212df5805adbf | open-PR review, unresolved comments, and CI | P1 merge-conflict markers removed from the shared search header while retaining the all-viewport hero portal and inline fallback. P2 fixed: phone-hidden command results can no longer open, report expanded state, receive keyboard navigation, or execute an invisible selection. The launcher and global-shell conflict findings were already resolved at the reviewed head. | No conflict markers; TypeScript; focused Prettier; app-mode/search/universal-search Vitest (37/37); `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks | +| ---------- | ------------------------------------------------------ | ---------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-07-10 | codex/design-ux-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | design-system + UX + design | Five issue groups confirmed; scoped fixes applied in the worktree. | `npm run check:type-scale`; focused Vitest (19/19); `npm run typecheck`; `npm run lint`; `npm run sitemap:check`; browser/API-backed checks awaiting approval | +| 2026-07-11 | codex/design-ux-review-integration | 98093ec7b | branch-integration-review | Replayed the reviewed design and UX fixes onto current `origin/main`, preserved the lightweight evidence-panel boundary, and retained the merged quality fixes. | `npm run check:type-scale`; combined focused Vitest (8 files, 42 tests); runtime/action/sitemap/type-scale/lint stages of `verify:cheap`; typecheck blocked by stale worktree dependencies pending hosted clean install; `git diff --check` | +| 2026-07-09 | example/branch | abc1234 | branch-cleanup | Example: already merged into `main`; no unique patch content. | `git log --right-only --cherry-pick main...example/branch`; `git diff --name-status main...example/branch` | +| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree diff: PR testing streamlining | Changes requested: 2 P1 clinical-gate defects and 7 P2/P3 scope, local parity, and UI-process defects. | `npm run check:ci-scope`; `npm run check:github-actions`; `npm run check:codex-autofix-workflow`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; targeted scope classifications; `git diff --check` | +| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree remediation review | All recorded P1-P3 findings fixed; no remaining high-confidence issue in the changed scope. | `npm run verify:cheap`; `npm run verify:pr-local`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; `npm run test:e2e:advisory`; CI YAML parse; scope/action/Codex guards; `git diff --check` | +| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow | Fixed exact connector authorization, trusted-marker deduplication, and strict self-trigger matching; added regression coverage. | `npm run check:codex-autofix-workflow`; focused Vitest (4 passed); `npm run verify:cheap` pre-test stages passed before tool timeout; `npm test` (1,415 passed, 1 skipped); focused Prettier check; `npm run check:github-actions` | +| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow-followup | Fixed untrusted workflow-level concurrency interference and migrated the bridge from the Node 20 action runtime to `actions/github-script@v9`; added direct embedded-script execution coverage. | Focused Vitest (13 passed); targeted ESLint; `tsc --noEmit`; `npm run check:codex-autofix-workflow`; `npm run check:github-actions`; focused Prettier check; `git diff --check` | +| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-residual-fixes | Replaced one-shot PR deduplication with a three-cycle head-SHA cap, made comment permission failures fail visibly, and pinned `github-script` v9.0.0 to its verified immutable commit. | TDD red run (7 expected failures); focused Vitest green run (15 passed); `npm run verify:cheap` (152 files passed, 1 skipped; 1,426 tests passed, 1 skipped); focused Prettier check; `git diff --check`; official `git ls-remote` tag verification | +| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | architecture-review | Seven findings fixed in the working tree: three runtime cycles, unbounded owner caches, a client/server env boundary breach, reversed runtime-to-scripts ownership, and architecture-doc drift. | `npm run test -- tests/architecture-boundaries.test.ts tests/bounded-ttl-cache.test.ts tests/rag-score.test.ts tests/rag-cache-utils.test.ts tests/rag-cache-invalidation.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; `npm run check:production-readiness:ci` | +| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | frontend-architecture-review | Shared cycle/env findings confirmed and fixed; three additional findings fixed for defeated lazy boundaries, duplicate shell/dashboard subscriptions, and unstable search-context values. | `npm run test -- tests/architecture-boundaries.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; UI gate deferred pending explicit local-API approval | +| 2026-07-11 | codex/architecture-review-integration | b45df727b29aad8ba4ec5d4e96d1f0599d7dad8a | branch-integration-review | Replayed the reviewed architecture fixes onto current `origin/main`; preserved current CI/autofix history and found no new high-confidence defect in the integrated diff. | `npm run check:runtime`; `npm run check:github-actions`; `npm run sitemap:check`; `npm run lint`; `npm run typecheck`; focused Vitest (24 passed); full Vitest with `--testTimeout=30000` (1,433 passed, 1 skipped); `git diff --check` | +| 2026-07-10 | codex/quality-testing-typescript-fixes | 648abfa3f | code-quality + testing + TypeScript | 17 confirmed P2/P3 issues fixed; no P0/P1 findings; residual large-module complexity noted. | Focused Vitest and Playwright; full Vitest 1427 passed/1 skipped; coverage; lint; typecheck; production-readiness CI | +| 2026-07-11 | codex/quality-review-integration | d3fcef8bbc9ab12b929771421b532c1ed8b7e1e7 | branch-integration-review | Replayed the quality, testing, and TypeScript fixes onto current `origin/main` and consolidated the stronger standalone auth callback coverage into this branch. | Changed-file Prettier; focused Vitest (5 files, 23 tests); `git diff --check`; original branch full Vitest/coverage/lint/typecheck and targeted Chromium evidence retained | +| 2026-07-11 | codex/architecture-review-integration | 665103250ccc33b5870862b8d8467607a1ae5d23 | coderabbit-followup | Fixed POSIX project-root identity collisions and closed dynamic-import and self-cycle gaps in the architecture regression guard. | Local-server Vitest passed; architecture-boundaries Vitest passed (6 tests); `npm run typecheck`; focused Prettier; `git diff --check` | +| 2026-07-11 | codex/architecture-review-followup | f5deaaee98864f1d32c1060ae14966a4f5975872 | coderabbit-test-followup | Removed probabilistic no-collision assertions from the local identity test and replaced them with deterministic normalization, repeatability, ID-shape, and port-range checks. | Local-server Vitest (2 passed); focused Prettier; `git diff --check`; hosted CI/SAST/Secret Scan passed on the reviewed head | +| 2026-07-11 | codex/pr-check-followup | 298e8f5bec2a4673dd225da3f446f008b8f25953 | residual-pr-check-hardening | Ported only the three PR-check improvements not already merged by PR #454: pinned Supabase CLI/cache ownership, advisory Semgrep coverage for Edge Functions, and regression guards for both contracts. | `npm run check:github-actions`; `npm run check:ci-scope`; focused Prettier; `git diff --check` | +| 2026-07-11 | claude/mobile-search-bar-fix (PR #456) | b73196c2e2e4a536804cdcdb50879c29e2c582c5 | PR required-testing review | All 4 Advisory UI regression failures confirmed PR-caused via A/B against pre-merge main (01f2cee0d): the 640px mode-home query moved the phone composer out of the hero, contradicting the design tests; residual ≥640px vanish remained when the slot never mounts. PR merged (b32c17b34) before the rework landed; follow-up fix shipped on `claude/mode-home-composer-hero-fix` (0px hero query restored, portal-outcome inline fallback, new `@critical` composer-presence test). Also found: main CI red on every push — missing `RAG_QUERY_HASH_SECRET` secret fails the deployment boot smoke and skips `release-browser-matrix`; owner adding the secret. | Local chromium A/B (PR head 4/5 fail vs baseline product-pass); rework targeted run 6/6 pass incl. new `@critical`; `npm run typecheck`; `npm run lint`; focused Prettier check | +| 2026-07-11 | PR #487 / claude/answer-page-design-polish-ffd5a6 | b2c772606126f8323424bc9c0b636bac77c08789 | open-PR review, unresolved comments, and CI | Two findings fixed: expanded weak/unsupported prior answers retain an explicit source-review warning, and cross-mode search actions no longer log an incorrect detail-open telemetry event. Added a persisted prior-turn browser assertion. No additional high-confidence defect was found in the changed scope. | Focused answer-render and cross-mode Vitest (20/20); TypeScript; focused Prettier; `git diff --check`. Browser assertion delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #469 / claude/response-formatting-cleanup-b57a9c | f1864308e0b287bb83b2a13daca4c3aa2ab95a3e | open-PR review, unresolved comments, and CI | P2 fixed: the OCR bullet sanitizer now preserves line-start O blood values when followed by blood or red-cell noun tails while still stripping non-blood bullets such as `o Negative screen`. No additional high-confidence defect was found in the nine-file diff. | Focused sanitizer/extractive Vitest (75/75); TypeScript; focused Prettier; `git diff --check`. Production-readiness script ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | +| 2026-07-11 | PR #466 / claude/search-timeout-failure-s6aiuj | 54d52292eeb9e1c7856b3dad89d1b72e0d49fd53 | open-PR review, unresolved comments, and CI | P2 fixed: SSE progress/token/error emission now tolerates a client cancellation racing an enqueue, so the catch path cannot throw while reporting the original stream error. No additional high-confidence defect was found in the six-file diff. | Focused SSE and search utility Vitest (13/13); TypeScript; focused Prettier. Hosted advisory browser failure was shared stale assertion drift and is rerun after this push. | +| 2026-07-11 | PR #483 / claude/differentials-page-review-a3daaf | 36cca1bf7c13718dcc60a61b75272c7c4fa5cd44 | open-PR review, unresolved comments, and CI | P2 fixed: authenticated diagnosis detail responses now derive related links, overlap links, and comparison presentation from the owner's current diagnosis and presentation rows rather than the bundled snapshot. Added an owner-only catalog regression test. No additional high-confidence defect was found in the changed scope. | Focused differentials route/catalog Vitest (26/26); TypeScript; focused Prettier; `git diff --check`. Production readiness ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | +| 2026-07-11 | PR #489 / claude/document-viewer-redesign-55b68b | 9130c8b15a22dbbc965464a247ae930c04f2da62 | open-PR review, unresolved comments, and CI | P2 fixed: document deep links now expand the mobile indexed-text details and scroll the branch-specific visible mobile or desktop chunk instead of the first duplicated DOM match. Added focused desktop/mobile assertions. No additional high-confidence defect was found in the three-file diff. | Focused Prettier; TypeScript; `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #488 / claude/code-review-42a2c3 | 7a8ea145013444f7cc29631499f48a8b0454937a | open-PR review, unresolved comments, and CI | Confirmed the remaining public error-code finding was already fixed at the reviewed head. Added the two focused advisory UI assertion stabilizations required by the hosted failure logs; no additional high-confidence defect was found in the changed scope. | `tests/http-error-response.test.ts` (3/3); Prettier check on affected files; `git diff --check`; hosted required CI passed before the test-only fix. Browser rerun deferred to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #473 / claude/mobile-search-bar-popup-bx163m | 7cef01852a9713ec51184578868212df5805adbf | open-PR review, unresolved comments, and CI | P1 merge-conflict markers removed from the shared search header while retaining the all-viewport hero portal and inline fallback. P2 fixed: phone-hidden command results can no longer open, report expanded state, receive keyboard navigation, or execute an invisible selection. The launcher and global-shell conflict findings were already resolved at the reviewed head. | No conflict markers; TypeScript; focused Prettier; app-mode/search/universal-search Vitest (37/37); `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #461 / claude/differentials-search-ux-polish-f2ff06 | 5e313872a | open-PR review, unresolved comments, and CI | Preserved diagnosis selections through comparison routing, selected the workflow that actually contains those diagnoses, and removed comparison controls from presentation rows. Restored required anonymous-search coverage and hardened the answer/search route mocks against invalid payloads and stale-response races. The prior best-answer and zero-source findings were already fixed. | TypeScript; focused differentials/app-mode Vitest (38/38); focused Prettier; `git diff --check`; hosted browser verification pending on the final head. | diff --git a/src/app/differentials/presentations/[slug]/page.tsx b/src/app/differentials/presentations/[slug]/page.tsx index 9828f7ac6..9a8421db5 100644 --- a/src/app/differentials/presentations/[slug]/page.tsx +++ b/src/app/differentials/presentations/[slug]/page.tsx @@ -6,7 +6,7 @@ import { getPresentationWorkflow, presentationStaticParams } from "@/lib/differe type DifferentialPresentationRouteProps = { params: Promise<{ slug: string }>; - searchParams?: Promise<{ query?: string | string[]; q?: string | string[] }>; + searchParams?: Promise<{ query?: string | string[]; q?: string | string[]; ids?: string | string[] }>; }; function firstSearchParam(value?: string | string[]) { @@ -36,6 +36,10 @@ export default async function DifferentialPresentationRoute({ const resolvedSearchParams = searchParams ? await searchParams : {}; const query = firstSearchParam(resolvedSearchParams.query ?? resolvedSearchParams.q)?.trim() ?? ""; + const selectedIds = (firstSearchParam(resolvedSearchParams.ids) ?? "") + .split(",") + .map((value) => value.trim()) + .filter(Boolean); - return ; + return ; } diff --git a/src/app/differentials/presentations/page.tsx b/src/app/differentials/presentations/page.tsx index 1c8d69598..f69101827 100644 --- a/src/app/differentials/presentations/page.tsx +++ b/src/app/differentials/presentations/page.tsx @@ -1,7 +1,9 @@ import { redirect } from "next/navigation"; +import { getPresentationWorkflowSelectionForDiagnosisIds } from "@/lib/differentials"; + type DifferentialPresentationsRouteProps = { - searchParams?: Promise<{ query?: string | string[]; q?: string | string[] }>; + searchParams?: Promise<{ query?: string | string[]; q?: string | string[]; ids?: string | string[] }>; }; function firstSearchParam(value?: string | string[]) { @@ -11,6 +13,15 @@ function firstSearchParam(value?: string | string[]) { export default async function DifferentialPresentationsRoute({ searchParams }: DifferentialPresentationsRouteProps) { const params = searchParams ? await searchParams : {}; const query = firstSearchParam(params.query ?? params.q)?.trim(); - const suffix = query ? `?q=${encodeURIComponent(query)}` : ""; - redirect(`/differentials/presentations/acute-confusion-encephalopathy${suffix}`); + const ids = firstSearchParam(params.ids)?.trim(); + const selectedIds = (ids ?? "") + .split(",") + .map((id) => id.trim()) + .filter(Boolean); + const selection = getPresentationWorkflowSelectionForDiagnosisIds(selectedIds); + const destinationParams = new URLSearchParams(); + if (query) destinationParams.set("q", query); + if (selection?.diagnosisIds.length) destinationParams.set("ids", selection.diagnosisIds.join(",")); + const suffix = destinationParams.size ? `?${destinationParams.toString()}` : ""; + redirect(`/differentials/presentations/${selection?.workflow.id ?? "acute-confusion-encephalopathy"}${suffix}`); } diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 30f3ebe1e..934f900e6 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -6,18 +6,17 @@ import { useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { Activity, + ArrowUpDown, BrainCircuit, Check, ChevronRight, CircleHelp, Clock3, ExternalLink, - Filter, FlaskConical, GitCompareArrows, HeartPulse, Info, - ListFilter, Search, ShieldAlert, ShieldCheck, @@ -112,15 +111,32 @@ const candidateIconBySlug: Array<[string, LucideIcon]> = [ ["delirium", BrainCircuit], ]; -function routeWithQuery(path: string, query: string) { +function routeWithQuery(path: string, query: string, selectedIds?: Set) { const params = new URLSearchParams(); const trimmedQuery = query.trim(); if (trimmedQuery) params.set("q", trimmedQuery); + if (selectedIds && selectedIds.size > 0) { + params.set("ids", Array.from(selectedIds).join(",")); + } const suffix = params.toString(); return suffix ? `${path}?${suffix}` : path; } -function DifferentialsMobileCompareAddon({ selectedCount, query }: { selectedCount: number; query: string }) { +/** + * Phone-only floating compare action. Portals into the bottom search dock's + * addon slot so it stays anchored just above the composer pill (and hides + * with it on scroll), but renders as a self-contained floating pill so it + * reads as a batch-selection action rather than composer chrome. + */ +function DifferentialsMobileCompareBar({ + selectedCount, + selectedIds, + query, +}: { + selectedCount: number; + selectedIds: Set; + query: string; +}) { const [host, setHost] = useState(null); useEffect(() => { @@ -140,16 +156,35 @@ function DifferentialsMobileCompareAddon({ selectedCount, query }: { selectedCou if (!host) return null; + const hasSelection = selectedCount > 0; + return createPortal( - - - Compare selected ({selectedCount}) - - , +
+ {hasSelection ? ( + + + Compare selected + + {selectedCount} + + + + + + ) : ( +

+ + Tick results to compare +

+ )} +
, host, ); } @@ -278,29 +313,71 @@ function ResultTypeTabs({ ); })} - ); } +type SortMode = "relevance" | "urgency" | "alpha"; + +const statusSortPriority: Record = { + emergent: 0, + urgent: 1, + routine: 2, +}; + +function sortResults(items: DifferentialResult[], mode: SortMode) { + if (mode === "relevance") return items; + // Array.prototype.sort is stable, so ties keep their relevance order. + return [...items].sort((a, b) => + mode === "alpha" ? a.title.localeCompare(b.title) : statusSortPriority[a.status] - statusSortPriority[b.status], + ); +} + +function SortSelect({ + value, + onChange, + className, +}: { + value: SortMode; + onChange: (value: SortMode) => void; + className?: string; +}) { + return ( + + ); +} + function MatchBadge({ label }: { label: string }) { + // Match quality is a relevance signal, not a safety one — keep it in the + // accent family so red stays reserved for the emergent status badges. const tone = - label === "Best match" - ? "text-[color:var(--danger)]" - : label === "High match" - ? "text-[color:var(--clinical-accent)]" - : "text-[color:var(--warning)]"; - return {label}; + label === "Best match" || label === "High match" + ? "text-[color:var(--clinical-accent)]" + : "text-[color:var(--text-muted)]"; + return ( + + {label === "Best match" ? : null} + {label} + + ); } function Chip({ children }: { children: string }) { @@ -319,7 +396,7 @@ function SelectionToggle({ selected, onClick, label }: { selected: boolean; onCl aria-label={`${selected ? "Remove" : "Add"} ${label} ${selected ? "from" : "to"} comparison`} onClick={onClick} className={cn( - "grid h-10 w-10 shrink-0 place-items-center rounded-md border text-sm transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]", + "grid h-11 w-11 shrink-0 place-items-center rounded-md border text-sm transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]", selected ? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)]" : "border-[color:var(--border-strong)] bg-[color:var(--surface)] text-transparent hover:text-[color:var(--text-soft)]", @@ -341,14 +418,14 @@ function DesktopResultRow({ index: number; isBest: boolean; selected: boolean; - onToggle: () => void; + onToggle?: () => void; }) { const Icon = result.icon; return (
Open page - + {onToggle ? ( + + ) : null} - + {onToggle ? : }
); } @@ -422,34 +501,20 @@ function DesktopResultRow({ function MobileResultCard({ result, index, - isBest, selected, onToggle, }: { result: DifferentialResult; index: number; - isBest: boolean; selected: boolean; - onToggle: () => void; + onToggle?: () => void; }) { const Icon = result.icon; return ( -
-
- +
+
+ {index + 1}
- + {onToggle ? : } - {isBest ? ( -

{result.subtitle}

- ) : null}
- {result.tags.slice(0, isBest ? 4 : 2).map((tag) => ( + {result.tags.slice(0, 2).map((tag) => ( {tag} ))} - {result.tags.length > (isBest ? 4 : 2) ? {`+${result.tags.length - (isBest ? 4 : 2)}`} : null} + {result.tags.length > 2 ? {`+${result.tags.length - 2}`} : null}
); @@ -492,35 +554,79 @@ function BestAnswerCard({ best, selected, onToggle, + compact = false, }: { best: DifferentialResult; selected?: boolean; onToggle?: () => void; + compact?: boolean; }) { const Icon = best.icon; + const tagLimit = compact ? 3 : best.tags.length; + const visibleTags = best.tags.slice(0, tagLimit); + const hiddenTagCount = best.tags.length - visibleTags.length; + + // Use danger styling for emergent, accent styling for routine results + const isEmergent = best.status === "emergent"; + const cardBorderColor = isEmergent ? "var(--danger-border)" : "var(--clinical-accent-border)"; + const cardBgColor = isEmergent ? "var(--danger-soft)" : "var(--clinical-accent-soft)"; + const iconBorderColor = isEmergent ? "var(--danger-border)" : "var(--clinical-accent-border)"; + const iconColor = isEmergent ? "var(--danger)" : "var(--clinical-accent)"; return ( -
+
- - + +

Best answer

-

{best.title}

-
+

+ + {best.title} + +

+
+ + Open page + +
{onToggle ? : null}
-

{best.subtitle}

-
- {best.tags.map((tag) => ( +

+ {best.subtitle} +

+
+ {visibleTags.map((tag) => ( {tag} ))} + {hiddenTagCount > 0 ? {`+${hiddenTagCount}`} : null}
); @@ -582,13 +688,13 @@ function UrgencyCard({ results }: { results: DifferentialResult[] }) { Highest urgency
- {urgentResults.map((result, index) => ( + {urgentResults.map((result) => ( - + {result.title} @@ -602,11 +708,13 @@ function SourceStatusCard({ sourceCount, evidenceState, loading, + sourcesChecked, onRunSourceSearch, }: { sourceCount: number; evidenceState: DifferentialEvidenceState; loading: boolean; + sourcesChecked: boolean; onRunSourceSearch: () => void; }) { const hasSourceEvidence = evidenceState === "source-backed"; @@ -623,27 +731,35 @@ function SourceStatusCard({ {hasSourceEvidence ? "Source-backed" : "Guided local differential"} - {hasSourceEvidence ? `${sourceCount.toLocaleString()} sources` : "Evidence pending"} + {hasSourceEvidence + ? `${sourceCount.toLocaleString()} sources` + : sourcesChecked + ? "0 matches" + : "Evidence pending"}

- {hasSourceEvidence ? "Imported catalogue" : "Run source search"} + {hasSourceEvidence ? "Imported catalogue" : sourcesChecked ? "Sources checked" : "Run source search"} {hasSourceEvidence ? `${sourceCount.toLocaleString()} source${sourceCount === 1 ? "" : "s"}` - : "Not yet checked"} + : sourcesChecked + ? "No matches" + : "Not yet checked"}

{hasSourceEvidence ? "Catalogue matches are ranked from the imported, locally reviewed differentials library." - : "Showing reviewed local differential records. Run source search to validate against indexed documents."} + : sourcesChecked + ? "No indexed documents matched this query. Showing catalogue-only results." + : "Showing reviewed local differential records. Run source search to validate against indexed documents."}

- {!hasSourceEvidence ? ( + {!hasSourceEvidence && !sourcesChecked ? ( - + {loading ? "Searching sources" : "Run source search"} + + ) : sourcesChecked && !hasSourceEvidence ? ( +

+ + No source matches found +

+ ) : null} +
toggleSelected(best.id)} + onToggle={best.kind === "diagnosis" ? () => toggleSelected(best.id) : undefined} /> + {safetyLead?.safety ? ( +

+ + + Safety first: + {safetyLead.safety} + +

+ ) : null}
- + {visibleResults.length} result{visibleResults.length === 1 ? "" : "s"} {" "} - · {hasSourceEvidence ? "Ranked by relevance" : "Catalogue ranking"} + · {hasSourceEvidence ? "Source-backed" : "Catalogue ranking"} - +
- {!hasSourceEvidence ? ( + {!hasSourceEvidence && !sourcesChecked ? (
-

- Showing ranked catalogue records. Source-library evidence has not been checked for this query yet. +

+ Sources not checked for this query yet.

+ ) : sourcesChecked && !hasSourceEvidence ? ( +
+ +

+ No source matches found for this query. +

+
) : null}
@@ -950,25 +1105,29 @@ function SearchResultsView({ const globalIndex = results.findIndex((entry) => entry.kind === result.kind && entry.id === result.id); const isBest = result.kind === best.kind && result.id === best.id; return ( -
+ // The best answer is already featured above the phone list, + // so its ranked duplicate only renders from the desktop + // breakpoint (hiding the wrapper keeps the grid gap clean). +
toggleSelected(result.id)} - /> -
-
- toggleSelected(result.id)} + onToggle={result.kind === "diagnosis" ? () => toggleSelected(result.id) : undefined} />
+ {!isBest ? ( +
+ toggleSelected(result.id) : undefined} + /> +
+ ) : null}
); })} @@ -976,20 +1135,30 @@ function SearchResultsView({ View all catalogue matches ({results.length}) - + - - - Compare selected ({selectedCount}) - - + {selectedCount > 0 ? ( + + + Compare selected + + {selectedCount} + + + + ) : ( +

+ + Tick results to compare them side by side +

+ )}
)} - {best ? : null} + {best ? ( + + ) : null}

Clinical decision support only. Review before use. diff --git a/src/components/differentials/differential-presentation-workflow-page.tsx b/src/components/differentials/differential-presentation-workflow-page.tsx index bf334662d..f84fb3dea 100644 --- a/src/components/differentials/differential-presentation-workflow-page.tsx +++ b/src/components/differentials/differential-presentation-workflow-page.tsx @@ -593,11 +593,25 @@ function MobileTabs({ workflow }: { workflow: DifferentialPresentationWorkflow } export function DifferentialPresentationWorkflowPage({ query = "", presentationSlug = "acute-confusion-encephalopathy", + selectedIds = [], }: { query?: string; presentationSlug?: string; + selectedIds?: string[]; }) { - const workflow = getPresentationWorkflow(presentationSlug) ?? acuteConfusionPresentationWorkflow; + const baseWorkflow = getPresentationWorkflow(presentationSlug) ?? acuteConfusionPresentationWorkflow; + const requestedIds = new Set(selectedIds); + const workflow = requestedIds.size + ? (() => { + let selectedCount = 0; + const candidates = baseWorkflow.candidates.map((candidate) => { + const selected = requestedIds.has(candidate.slug); + if (selected) selectedCount += 1; + return { ...candidate, selected }; + }); + return { ...baseWorkflow, candidates, selectedCount }; + })() + : baseWorkflow; const candidates = getCandidates(workflow); return ( diff --git a/src/lib/differentials.ts b/src/lib/differentials.ts index 4e6ed2290..7723e77b7 100644 --- a/src/lib/differentials.ts +++ b/src/lib/differentials.ts @@ -75,6 +75,36 @@ export function getPresentationWorkflow(slug: string | null | undefined) { return differentialPresentations().find((presentation) => presentation.id === normalizedSlug) ?? null; } +export function getPresentationWorkflowForDiagnosisIds(ids: Iterable) { + const requestedIds = new Set(Array.from(ids, (id) => id.trim().toLowerCase()).filter(Boolean)); + if (!requestedIds.size) return null; + + let bestMatch: DifferentialPresentationWorkflow | null = null; + let bestMatchCount = 0; + for (const presentation of differentialPresentations()) { + const matchCount = presentation.candidates.reduce( + (count, candidate) => count + (requestedIds.has(candidate.slug) ? 1 : 0), + 0, + ); + if (matchCount > bestMatchCount) { + bestMatch = presentation; + bestMatchCount = matchCount; + } + } + return bestMatch; +} + +export function getPresentationWorkflowSelectionForDiagnosisIds(ids: Iterable) { + const diagnosisIds = Array.from(new Set(Array.from(ids, (id) => id.trim().toLowerCase()).filter(Boolean))); + const workflow = getPresentationWorkflowForDiagnosisIds(diagnosisIds); + if (!workflow) return null; + const candidateIds = new Set(workflow.candidates.map((candidate) => candidate.slug)); + return { + workflow, + diagnosisIds: diagnosisIds.filter((id) => candidateIds.has(id)), + }; +} + export const acuteConfusionPresentationWorkflow: DifferentialPresentationWorkflow = getPresentationWorkflow("acute-confusion-encephalopathy") ?? differentialPresentations()[0]!; diff --git a/tests/differentials.test.ts b/tests/differentials.test.ts index be7322124..be87015fe 100644 --- a/tests/differentials.test.ts +++ b/tests/differentials.test.ts @@ -10,6 +10,8 @@ import { differentialStaticParams, getDifferentialRecord, getPresentationWorkflow, + getPresentationWorkflowForDiagnosisIds, + getPresentationWorkflowSelectionForDiagnosisIds, loadDifferentialSnapshot, rankDifferentialRecords, rankPresentationWorkflows, @@ -20,6 +22,24 @@ import { type DifferentialRecordMatch, } from "@/lib/differentials"; +describe("presentation workflow routing", () => { + it("routes selected diagnoses to a workflow that contains them", () => { + expect(getPresentationWorkflowForDiagnosisIds(["bipolar-depression-mixed-state"])?.id).toBe( + "suicidal-ideation-suicide-attempt-self-harm", + ); + expect(getPresentationWorkflowForDiagnosisIds([])).toBeNull(); + }); + + it("forwards only diagnoses supported by the selected workflow", () => { + const selection = getPresentationWorkflowSelectionForDiagnosisIds([ + "wernicke-encephalopathy", + "major-depressive-disorder", + ]); + expect(selection?.workflow.id).toBe("acute-confusion-encephalopathy"); + expect(selection?.diagnosisIds).toEqual(["wernicke-encephalopathy"]); + }); +}); + const deliriumEntry = `=== ENTRY 1 === Delirium / Acute Confusion / Encephalopathy diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 93fb4b121..5877ba930 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -31,11 +31,6 @@ async function gotoApp(page: Page, path: string) { await page.waitForLoadState("networkidle", { timeout: 15_000 }).catch(() => undefined); } -async function gotoCriticalApp(page: Page, path: string) { - await page.goto(path, { waitUntil: "domcontentloaded" }); - await expect(page.locator("#main-content, main").first()).toBeVisible({ timeout: 30_000 }); -} - async function expectSingleMedicationPage(page: Page) { // The medication route renders inside GlobalMockupSearchShell, whose Suspense // fallback and resolved client subtree both render `children`. During a @@ -299,7 +294,11 @@ async function mockDemoApi(page: Page, options: MockDemoApiOptions = {}) { documentId?: string; documentIds?: string[]; }; - const query = body.query ?? "What monitoring is required?"; + const query = typeof body.query === "string" ? body.query.trim() : ""; + if (!query || query.length > 2000) { + await route.fulfill({ status: 400, json: { error: "A query between 1 and 2000 characters is required." } }); + return; + } options.onAnswerRequest?.(query); if (options.answerDelayMs) { await new Promise((resolve) => setTimeout(resolve, options.answerDelayMs)); @@ -817,7 +816,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await page.route(/\/api\/search(?:\?.*)?$/, async (route) => { await route.fulfill({ json: { results: [], telemetry: { retrieval_strategy: "text_fast_path" } } }); }); - await gotoCriticalApp(page, "/"); + await gotoApp(page, "/"); await waitForDemoDashboardReady(page); await expect(page.getByText("Create your Clinical Guide account")).toHaveCount(0); @@ -887,7 +886,7 @@ test.describe("Clinical KB UI smoke coverage", () => { test("tablet shows icon rail without drawer trigger or expand control @critical", async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await mockDemoApi(page); - await gotoCriticalApp(page, "/?mode=answer"); + await gotoApp(page, "/?mode=answer"); await waitForDemoDashboardReady(page); await expect(page.getByRole("button", { name: "Open Clinical Guide menu" })).toHaveCount(0); @@ -973,7 +972,7 @@ test.describe("Clinical KB UI smoke coverage", () => { }) => { await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); - await gotoCriticalApp(page, "/"); + await gotoApp(page, "/"); await waitForDemoDashboardReady(page); const settings = accountSettingsDialog(page); @@ -1132,7 +1131,7 @@ test.describe("Clinical KB UI smoke coverage", () => { test("demo answer flow reaches a source-backed answer @critical", async ({ browserName, page }) => { await page.setViewportSize({ width: 390, height: 820 }); await mockDemoApi(page); - await gotoCriticalApp(page, "/"); + await gotoApp(page, "/"); await waitForDemoDashboardReady(page); const question = "What clozapine monitoring items are shown in the table image?"; @@ -1878,13 +1877,17 @@ test.describe("Clinical KB UI smoke coverage", () => { await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); let requestCount = 0; + let resolveCurrentResponse!: () => void; + const currentResponseDelivered = new Promise((resolve) => { + resolveCurrentResponse = resolve; + }); await page.route(/\/api\/search$/, async (route) => { requestCount += 1; const currentRequest = requestCount; if (currentRequest === 1) await new Promise((resolve) => setTimeout(resolve, 500)); const sourceCount = currentRequest === 1 ? 2 : 1; - await route - .fulfill({ + try { + await route.fulfill({ json: { documentMatches: Array.from({ length: sourceCount }, (_, index) => ({ document_id: `00000000-0000-4000-8000-00000000000${index}`, @@ -1893,8 +1896,11 @@ test.describe("Clinical KB UI smoke coverage", () => { score: 0.9 - index * 0.1, })), }, - }) - .catch(() => undefined); + }); + if (currentRequest > 1) resolveCurrentResponse(); + } catch (error) { + if (currentRequest > 1) throw error; + } }); await page.goto("/differentials?q=acute+confusion&run=1", { waitUntil: "domcontentloaded" }); @@ -1905,11 +1911,10 @@ test.describe("Clinical KB UI smoke coverage", () => { }); await expect.poll(() => requestCount).toBeGreaterThan(baselineRequestCount); + await currentResponseDelivered; const sourceStatus = page.getByRole("heading", { name: "Source status" }).locator(".."); const singularSourceCount = sourceStatus.getByText("1 source", { exact: true }); await expect(singularSourceCount).toBeVisible(); - await page.waitForTimeout(600); - await expect(singularSourceCount).toBeVisible(); await expect(sourceStatus).not.toContainText("2 sources"); }); @@ -2029,7 +2034,7 @@ test.describe("Clinical KB UI smoke coverage", () => { }); await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); - await gotoCriticalApp(page, "/?mode=prescribing&q=acamprosate%20renal%20dose&run=1"); + await gotoApp(page, "/?mode=prescribing&q=acamprosate%20renal%20dose&run=1"); const globalSearchInput = page.getByTestId("global-search-input"); await expect(page.getByRole("button", { name: "Mode Medication" })).toBeVisible({ timeout: 30_000 }); @@ -2043,7 +2048,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expectSingleMedicationPage(page); await expect(page.getByRole("link", { name: "Back to medication search" })).toBeVisible(); - await gotoCriticalApp(page, "/mockups/medication-prescribing"); + await gotoApp(page, "/mockups/medication-prescribing"); await expect(page).toHaveURL(/\/medications\/acamprosate$/); await expectSingleMedicationPage(page); expect(parentNodeErrors).toEqual([]); @@ -2086,7 +2091,7 @@ test.describe("Clinical KB UI smoke coverage", () => { test("document search mode lists matching documents and scope actions @critical", async ({ page }) => { await page.setViewportSize({ width: 390, height: 820 }); await mockDemoApi(page); - await gotoCriticalApp(page, "/"); + await gotoApp(page, "/"); await waitForDemoDashboardReady(page); await switchToDocumentSearchMode(page); @@ -2173,7 +2178,7 @@ test.describe("Clinical KB UI smoke coverage", () => { test("search regressions avoid fetch errors and open viewer hits @critical", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); - await gotoCriticalApp(page, "/"); + await gotoApp(page, "/"); await waitForDemoDashboardReady(page); await switchToDocumentSearchMode(page); @@ -2186,7 +2191,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.getByRole("heading", { name: /Search command centre|Find source evidence/ })).toBeVisible(); const demoDocId = "11111111-1111-4111-8111-111111111111"; - await gotoCriticalApp(page, `/documents/${demoDocId}?chunk=55555555-5555-4555-8555-555555555555`); + await gotoApp(page, `/documents/${demoDocId}?chunk=55555555-5555-4555-8555-555555555555`); await expect(page).toHaveURL(/chunk=55555555-5555-4555-8555-555555555555/); await expect(page.locator("#source-evidence").getByTestId("highlighted-source-passage")).toContainText( "Patient safety plan should include", diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 4740c1cf9..8b9c599fc 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -1294,6 +1294,12 @@ test.describe("Clinical KB tools launcher", () => { expect(desktopTableBox?.width ?? 0).toBeGreaterThan(900); await expectNoPageHorizontalOverflow(page); + await gotoLauncher(page, "/differentials/presentations?ids=wernicke-encephalopathy"); + await expect(page).toHaveURL(/ids=wernicke-encephalopathy/); + await expect( + page.getByRole("heading", { name: `Selected differentials (1 of ${workflow.totalCount})` }).first(), + ).toBeVisible(); + await page.setViewportSize({ width: 390, height: 844 }); await gotoLauncher(page, "/differentials/presentations");