Add cross-mode entity quick links to answer search results - #322
Conversation
When an answer-mode question names an entity that lives in another app
mode (a medication, service, form, or differential), render an "Also in
your library" card strip under the answer linking to that entity's
detail page, with a secondary action that re-runs the search inside the
matching mode.
- src/lib/cross-mode-links.ts: pure matcher over the existing per-mode
rankers with name/title-level precision gates so question filler
("dose", "patient") never surfaces junk; per-mode and total caps,
services/forms slug dedupe. Differential catalog is injected so the
module never pulls the snapshot JSONs into the dashboard bundle.
- src/lib/cross-mode-differentials.ts: dynamic-import-only adapter that
maps the differentials snapshot to the matcher's catalog shape.
- src/lib/keyword-query.ts: keyword extraction hoisted from
search-utils so lib code can reuse it; search-utils re-exports.
- CrossModeLinksStrip component follows the RelatedDocumentsPanel card
conventions; rendered above it on final answers only.
- Catalogs come from the same owner-scoped APIs the modes already use
(fixtures in demo mode) and are fetched lazily after the first answer;
useMedicationCatalog gains an `enabled` option.
- Follow-up turns fall back to the prior question so the entity card
persists through "what about renal impairment?"-style turns.
- Vitest coverage for gates/caps/dedupe/aliases plus an answer-mode
Playwright smoke test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Cross-mode links drop after second follow-up
- The crossModeLinks memo now walks prior answer turns from newest to oldest when the current query yields no matches, so entity cards persist across multi-hop follow-ups.
You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 954daa2. Configure here.
Walk all prior answer turns (newest to oldest) when the current query yields no cross-mode matches, instead of only checking the immediately preceding turn.
…y gates
Resolve the Bugbot finding by walking the whole answer thread (newest to
oldest) for the most recent turn that names an entity, via a pure
buildCrossModeLinksForThread helper. Add word-boundary matching to the
entity gates so query words hiding inside names ("renal" in
"adrenaline") no longer surface junk cards; regression tests cover both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
Union the medication catalog hook's effect deps: main made it auth-aware (authorizationHeader) while this branch added the enabled gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
…ode strip
- Word-boundary field matching in rankCatalogRecords so query terms cannot
hide inside entity names ('renal' inside 'adrenaline'); prefix matching
preserves search-as-you-type. Regression-tested in tests/medications.test.ts.
- fields=index slim mode on /api/medications (~30 KB identity slice instead
of the ~3.4 MB catalog) used by the cross-mode links surface.
- Cross-mode click telemetry: /api/search/interaction accepts a crossMode
target and stores a privacy-hardened miss row with the mode/slug in
metadata; strip links and search actions log through it.
- CrossModeLinksSection: self-contained catalogs+matching+strip component;
mounted under answers (thread-aware), dashboard documents results, and the
/documents/search command centre.
- Answer command-surface crossModes now lists services and forms, matching
the strip's coverage.
- docs/process-hardening.md records the verify:release debt for this
workstream per repo convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
…scj1n' into claude/search-cross-mode-links-qscj1n
|
@copilot resolve the merge conflicts in this pull request |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
Head branch was pushed to by a user without write access
Merge conflicts resolved. There was a single conflict in
All 5 medication tests pass. Commit: db2b21a |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
…lementarily incomplete (#2020)
…favourites/filter-contract scope Resolves 10 real merge conflicts and removes content that should never have shipped on this branch: - Reverts the fabricated "Clinical Lead APPROVED" sign-off in docs/medication-interaction-lexicon-review.md back to the true unreviewed placeholder. Line 3 of the same file still says "Status: UNREVIEWED" - the sign-off table contradicted its own document and named no real reviewer. - Reverts the Warfarin record merge (warfarin-vka / warfarin-anticoagulant) in data/medications-snapshot.json. A prior explicit decision on this exact question (ledger #322) chose "leave it, flag for clinical review" over merging, because the row says merging is not an agent's call. - Reverts all edits to src/lib/medication-interaction-lexicon.ts. Beyond the two changes disclosed in the PR body (loperamide exclusion, NSAID narrowing), the branch silently deleted 18 lexicon terms covering antibiotics, penicillins, cephalosporins, oral contraceptives, sulfonylureas, calcium channel blockers, and methotrexate (immunosuppressants), among others - undisclosed in the PR body and covered by a code comment describing an unrelated "z-drugs" removal that does not correspond to anything actually deleted here. - Restores src/app/api/images/signed-urls/route.ts, which the branch deleted as "dead uncalled" code. It is not dead: main now calls it from src/lib/batch-signed-urls.ts via useBatchSignedImageUrls, used by document-search-results.tsx and source-panels.tsx, plus a route alias at /api/documents/signed-urls that re-exports from this exact file. Restores its tests in tests/private-access-routes.test.ts and its docs/site-map.md entry accordingly. - Reverts docs/filter-contract.md and src/components/clinical-dashboard/ result-filter-control.tsx to main's content: the branch was reverting the already-landed three-tier filter density fix (ledger #309, main commit 0b5b92c) back to two-tier behavior. - Reverts src/components/favourites/favourites-storage.ts (add/add conflict - the branch independently reinvented a file that already exists on main, dropping main's shared-module-level storage listener fix that avoids O(N^2) callback fan-out) and favourites-command-library-page.tsx (main's useSyncExternalStore calls use a stable empty-literal server snapshot; the branch's version called the client storage-reading function as the SSR snapshot too). - Resolves the DocumentViewer.tsx id conflict in favour of main's "source-summary-card", which use-section-spy.ts's section map and tests/ui-smoke.spec.ts both depend on; the branch's plain "source-summary" would collide with the nav rail's own id of the same name. - Resolves the document-rail-panels.tsx className conflict in favour of main's print:block utility. Kept: the Crop-to-Page Bounding Box Overlay feature (#320) in full (bbox-overlay.ts, PdfCanvasViewer/PdfPageSlot wiring, DocumentViewer.tsx highlightedBbox wiring, document-detail-contract.ts and document-detail.ts bbox typing/normalization, tests/bbox-overlay.test.ts), the disclosed and verified-safe therapies.ts/therapy-ranking.ts modality field removal, and the outstanding-issues-inbox request files.

Summary
/medications/clozapine) and a secondary button re-runs the search inside that mode./documents/searchcommand centre. All three mount a self-containedCrossModeLinksSectionthat owns catalog fetching + matching.src/lib/cross-mode-links.tsruns the sharedrankCatalogRecordsrankers with name/title precision gates; results capped 2 per mode / 4 total, deduped across the services/forms registries.rankCatalogRecordsis now word-boundary aligned, so query terms cannot hide inside entity names ("renal" no longer name-matches "adrenaline"); prefix matching preserves search-as-you-type./api/medications?fields=indexreturns the ~30 KB identity slice instead of the ~3.4 MB catalog; the links surface uses it. Differentials snapshot stays code-split behind a dynamic import./api/search/interactionaccepts acrossModetarget and stores the same privacy-hardenedrag_query_missesrow with the mode/slug inmetadata(interaction: "cross_mode_link_open"), so retrieval reviews can see whether the strip earns its space.crossModescommand-surface config now lists services and forms, matching the strip's coverage.keywordQueryFromNaturalLanguagehoisted tosrc/lib/keyword-query.ts(re-exported for existing consumers).docs/process-hardening.mdrecords this workstream's verification state.Verification
npm run verify:cheapequivalents — lint 0 errors, typecheck clean, 1157 vitest tests pass (incl. newtests/cross-mode-links.test.ts, ranker word-boundary regression intests/medications.test.ts,fields=indexcase intests/medications-route.test.ts, cross-mode telemetry cases intests/search-interaction-route.test.ts)npm run verify:ui— full Chromium suite: 107 passed, 0 failed (8.8m) on the follow-up work; the earlier CIui-smokefailure ("prescribing mode home top-aligns on phones") reproduces as flaky only on the CI runner and passes locally on the same commit/medications/clozapine) and/documents/searchstrip (3 cards above the results table) verified against the live dev server in demo modenpm run verify:release— not run (needs live keys; debt recorded indocs/process-hardening.md)npm run check:production-readiness— fails only on missing Supabase/OpenAI secrets, documented as expected in this environment (AGENTS.md)npm run eval:retrieval:quality/eval:rag— not run here (no live keys). Note: the word-boundary ranker change touches catalog search (medications/services/forms/tools/differentials), not the RAG document-retrieval pipeline; document retrieval scoring is unchanged.Clinical Governance Preflight
queryTextForStorage/queryPrivacyMetadataredaction; no raw query text persisted beyond the existing policy; unit tests pin the redactionClinical KB Database(sjrfecxgysukkwxsowpy) — no Supabase config touchedNotes
tests/ui-smoke.spec.tsbecauseplaywright.config.tstestMatchonly picks up the fixed spec set; a new file would silently never run.clicked_document_id: nullwith the target inmetadata— miss aggregations by document should filter onmetadata.interaction.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nt7UBgqg82NEPinWUZ6PXF