Skip to content

fix(differentials): preserve compare selections across presentations - #1756

Merged
BigSimmo merged 13 commits into
mainfrom
cursor/fix-differentials-compare-5c66
Aug 8, 2026
Merged

fix(differentials): preserve compare selections across presentations#1756
BigSimmo merged 13 commits into
mainfrom
cursor/fix-differentials-compare-5c66

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Selecting differentials from different presentation workflows no longer silently drops IDs on Compare (reproduced with Medical + BPSD for Pain).
  • Cross-presentation selections open /differentials/compare with an ad-hoc workflow built from diagnosis sections; same-presentation selections still use the hosting catalogue workflow.
  • Search ticks sync into the URL ids param so ModeNav Compare carries the same selection, and MobileTabs Compare keeps q/ids.

Verification

  • npm run test:focused -- --files src/lib/differentials.ts,src/lib/differentials-navigation.ts,src/lib/mode-secondary-navigation.ts,src/app/(search-app)/differentials/presentations/route.ts,src/app/(search-app)/differentials/compare/page.tsx,src/components/clinical-dashboard/differentials-home.tsx,src/components/differentials/differential-presentation-workflow-page.tsx255 passed
  • npm run verify:pr-localTest Files 529 passed (529); Tests 5709 passed | 4 skipped (5713); build lists ƒ /differentials/compare; Offline RAG fixture and manifest validation passed (36 golden cases, 23 suites)
  • UI verification not run: change is compare handoff/state routing; unit coverage asserts the Pain cross-presentation pair, redirect, ModeNav href, and URL ids sync. Chromium journey can follow if needed.
  • npm run verify:release — not required for this handoff

Risk and rollout

  • Risk: Low–medium UI/workflow. Wrong compare host could show an incomplete selected set; ad-hoc cells use diagnosis section summaries rather than presentation-authored comparison prose when IDs span presentations.
  • Rollback: Revert this PR; legacy /differentials/presentations?ids= redirect remains available.
  • Provider or production effects: None

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly labeled from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed — compare handoff only; no new decision-support claims

Notes

  • ModeNav Compare destination is now /differentials/compare (presentation deep links still mark Compare current).
  • Route-reachability now counts modeSecondaryNavigationRegistry hrefs as inbound nav builders.
Open in WebOpen in Cursor

Summary by CodeRabbit

  • New Features
    • Added a dedicated Differential Compare page for comparing selected diagnoses.
    • Supports ad-hoc comparisons across different presentations while preserving selected diagnoses.
    • Comparison selections are synchronized in the URL for sharing, navigation, and mobile workflows.
    • Updated Differential navigation and sitemap entries to include the new Compare route.
  • Bug Fixes
    • Improved diagnosis selection handling, including unknown IDs and cross-presentation comparisons.
  • Tests
    • Expanded coverage for comparison routing, navigation, URL selections, and route reachability.

Stop silently dropping diagnoses that do not share a presentation host.
Cross-presentation ticks now open an ad-hoc compare view, search selection
syncs into the URL for ModeNav handoff, and MobileTabs keep ids/q.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
@supabase

supabaseBot commented Aug 8, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in:1 minute

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac5fe1bd-6bed-401a-aa42-0016704c5311

📥 Commits

Reviewing files that changed from the base of the PR and between cb3e16e and 8823251.

📒 Files selected for processing (19)
  • docs/branch-review-ledger.md
  • docs/codebase-index.md
  • docs/design-system/ADOPTION.md
  • docs/design-system/adoption-contract.json
  • docs/design-system/adoption-manifest.json
  • docs/site-map.md
  • scripts/generate-site-map.ts
  • src/app/(search-app)/differentials/compare/page.tsx
  • src/components/clinical-dashboard/differentials-home.tsx
  • src/lib/differentials-navigation.ts
  • src/lib/differentials.ts
  • src/lib/mode-secondary-navigation.ts
  • tests/audit-navigation-auth-regressions.test.ts
  • tests/design-system-adoption.test.ts
  • tests/differentials-compare-selection.dom.test.tsx
  • tests/differentials-navigation.test.ts
  • tests/differentials.test.ts
  • tests/mode-secondary-navigation.test.ts
  • tests/site-map.test.ts
📝 Walkthrough

Walkthrough

The PR adds /differentials/compare for URL-backed diagnosis comparisons. It supports catalog and ad-hoc workflows, preserves selected IDs across navigation, updates differential routing, and adds route documentation and reachability coverage.

Changes

Differential workflow selection

Layer / File(s)Summary
Workflow selection and ad-hoc construction
src/lib/differentials.ts, tests/differentials.test.ts
Diagnosis IDs are normalized and resolved to either a full catalog workflow or an ad-hoc workflow that preserves valid selections across presentations.

Compare route and rendering

Layer / File(s)Summary
Compare route and workflow rendering
src/app/(search-app)/differentials/compare/page.tsx, src/app/(search-app)/differentials/presentations/route.ts, src/components/differentials/differential-presentation-workflow-page.tsx
The compare route parses query parameters, selects a workflow, and renders it. Presentation redirects preserve cross-presentation selections and route ad-hoc workflows to /differentials/compare.
Selection state and compare navigation
src/lib/differentials-navigation.ts, src/components/clinical-dashboard/differentials-home.tsx, tests/differentials-navigation.test.ts
Selection IDs are parsed, encoded, synchronized to the URL, and preserved by compare navigation and redirects.

Navigation and route coverage

Layer / File(s)Summary
Navigation registry and route coverage
src/lib/mode-secondary-navigation.ts, docs/codebase-index.md, docs/site-map.md, scripts/generate-site-map.ts, tests/mode-secondary-navigation.test.ts, tests/route-reachability.test.ts
Secondary navigation, sitemap data, route documentation, and reachability checks include /differentials/compare and its presentation descendants.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
participant Browser
participant DifferentialCompareRoute
participant differentials
participant DifferentialPresentationWorkflowPage
Browser->>DifferentialCompareRoute: Request /differentials/compare with query and ids
DifferentialCompareRoute->>differentials: Resolve diagnosis IDs
differentials-->>DifferentialCompareRoute: Return catalog or ad-hoc workflow
DifferentialCompareRoute->>DifferentialPresentationWorkflowPage: Render selected workflow
DifferentialPresentationWorkflowPage-->>Browser: Display comparison workflow
Loading

Possibly related PRs

Suggested labels:codex

Suggested reviewers:claude, cursoragent

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.63% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description check✅ PassedThe description follows the template, summarizes the change, records verification results, documents UI verification limits, and completes risk and governance sections.
Title check✅ PassedThe title clearly and concisely describes the primary change: preserving differential selections across presentation workflows.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-differentials-compare-5c66

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo marked this pull request as ready for review August 8, 2026 18:09
Therapy-compass action entries have no href; guard with an in-check so
typecheck accepts the builder-target scrape.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
@BigSimmo
BigSimmo enabled auto-merge (squash) August 8, 2026 18:10
cursoragentand others added 2 commits August 8, 2026 18:12
Register the new /differentials/compare page in the adoption contract and
regenerate the manifest so design-system adoption checks stay green.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Bump the production route coverage pin to 48 and refresh ADOPTION.md
generated sections after adding differentials compare.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d206f66fe4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadsrc/components/clinical-dashboard/differentials-home.tsx
Comment threadsrc/lib/differentials.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
tests/differentials-navigation.test.ts (1)

66-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the ids removal branch.

The new tests cover writing ids and parsing them. They do not cover differentialSelectionIdsSearch with an empty selection, which takes the params.delete("ids") branch. That branch controls what happens when the user unticks the last result.

Add a case that asserts an empty selection removes ids and preserves the other parameters.

💚 Proposed test
 ).toBe("?q=Pain&run=1&ids=medical-gi-endocrine-painful-organic-cause%2Cbpsd-as-unmet-need-delirium-pain-mimic");
});
++ it("removes ids when the compare selection empties", () => {+ expect(differentialSelectionIdsSearch([], "?q=Pain&run=1&ids=anorexia-nervosa")).toBe("?q=Pain&run=1");+ expect(differentialSelectionIdsSearch([], "?ids=anorexia-nervosa")).toBe("");+ });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/differentials-navigation.test.ts` around lines 66 - 74, Add a test case
for differentialSelectionIdsSearch that passes an empty selection with existing
query parameters including ids, and assert the result removes ids while
preserving the other parameters. Place it alongside the existing
differentialIdsFromSearchParams and differentialSelectionIdsSearch coverage.
src/app/(search-app)/differentials/presentations/route.ts (1)

15-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fold the reserved-ID guard into the pathname selection.

The guard at Lines 24-27 duplicates the suffix computation at Lines 29-32. Merge the reserved-ID condition into the pathname ternary. The behavior stays the same and there is one return path.

♻️ Proposed simplification
- // Cross-presentation selections keep every valid ID on the ad-hoc compare- // route instead of silently dropping diagnoses that lose the presentation vote.- const pathname =- selection?.kind === "ad-hoc"- ? "/differentials/compare"- : `/differentials/presentations/${selection?.workflow.id ?? "acute-confusion-encephalopathy"}`;-- // Guard against ever advertising the reserved ad-hoc id as a presentation slug.- if (selection?.kind === "presentation" && selection.workflow.id === AD_HOC_DIFFERENTIAL_COMPARE_ID) {- const suffix = params.toString();- return suffix ? `/differentials/compare?${suffix}` : "/differentials/compare";- }-+ // Cross-presentation selections keep every valid ID on the ad-hoc compare+ // route instead of silently dropping diagnoses that lose the presentation vote.+ // The reserved ad-hoc id is never advertised as a presentation slug.+ const usesCompareRoute =+ selection?.kind === "ad-hoc" || selection?.workflow.id === AD_HOC_DIFFERENTIAL_COMPARE_ID;+ const pathname = usesCompareRoute+ ? "/differentials/compare"+ : `/differentials/presentations/${selection?.workflow.id ?? "acute-confusion-encephalopathy"}`;+
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`(search-app)/differentials/presentations/route.ts around lines 15 -
28, Fold the reserved-ID check for AD_HOC_DIFFERENTIAL_COMPARE_ID into the
existing pathname selection ternary, alongside the ad-hoc branch, and remove the
separate guard return. Preserve the current compare-route behavior and suffix
query-string handling while leaving a single return path.
src/lib/differentials.ts (1)

142-192: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider capping the ad-hoc candidate count.

buildAdHocPresentationWorkflow accepts any number of valid IDs. The ids query parameter is user-controlled, so a request can select every catalogue slug. The compare page then renders one column per candidate, and safetySnapshot.summary concatenates every record summary into a single paragraph. The result is a very wide table and an unreadable safety block.

Apply a maximum candidate count and truncate the summary the same way safetyTags is capped at 6.

♻️ Proposed cap
+const AD_HOC_MAX_CANDIDATES = 8;+
export function buildAdHocPresentationWorkflow(ids: Iterable<string>): DifferentialPresentationWorkflow | null {
- const diagnosisIds = normalizeRequestedDiagnosisIds(ids);+ const diagnosisIds = normalizeRequestedDiagnosisIds(ids).slice(0, AD_HOC_MAX_CANDIDATES);
if (!diagnosisIds.length) return null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/differentials.ts` around lines 142 - 192, Update
buildAdHocPresentationWorkflow to cap the normalized valid records/candidates at
a defined maximum before building the comparison and safety snapshot. Ensure
selectedCount, totalCount, comparison columns, and concatenated
safetySnapshot.summary all use the capped records, and truncate the summary
consistently with the six-item safetyTags limit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(search-app)/differentials/compare/page.tsx:
- Around line 30-40: Update the compare page flow around
getPresentationWorkflowSelectionForDiagnosisIds so it renders an explicit empty
state with a link to /differentials when selection is null, including when IDs
are missing or all unknown. Only render DifferentialPresentationWorkflowPage
with the resolved workflow and diagnosis IDs when a valid selection exists, and
remove the acuteConfusionPresentationWorkflow fallback for this case.
In `@src/components/clinical-dashboard/differentials-home.tsx`:
- Around line 871-877: Update the selection state flow around comparisonIdsKey
and the useEffect that calls syncDifferentialSelectionIdsToUrl so URL
synchronization occurs only after a user toggle. Track a user-modified flag when
the selection toggle handler changes the selection, and gate the effect on that
flag so the mount-time diagnosisIds.slice(0, 2) auto-seed is never written to
the URL.
- Around line 835-844: Move the URL-based selection initialization out of the
render path in the component containing this diagnosis selection logic. Preserve
the first-render selection consistently between server and client, then apply
valid differentialIdsFromSearchParams values in a client-side effect (or
equivalent server-provided initialization) without changing later query
re-seeding behavior.
In `@src/lib/differentials-navigation.ts`:
- Around line 26-38: Update differentialIdsFromSearchParams to lowercase each
trimmed ID before deduplication and insertion into the returned list, matching
normalizeRequestedDiagnosisIds and the exact-match diagnosisIdSet consumer while
preserving empty-value filtering and order.
---
Nitpick comments:
In `@src/app/`(search-app)/differentials/presentations/route.ts:
- Around line 15-28: Fold the reserved-ID check for
AD_HOC_DIFFERENTIAL_COMPARE_ID into the existing pathname selection ternary,
alongside the ad-hoc branch, and remove the separate guard return. Preserve the
current compare-route behavior and suffix query-string handling while leaving a
single return path.
In `@src/lib/differentials.ts`:
- Around line 142-192: Update buildAdHocPresentationWorkflow to cap the
normalized valid records/candidates at a defined maximum before building the
comparison and safety snapshot. Ensure selectedCount, totalCount, comparison
columns, and concatenated safetySnapshot.summary all use the capped records, and
truncate the summary consistently with the six-item safetyTags limit.
In `@tests/differentials-navigation.test.ts`:
- Around line 66-74: Add a test case for differentialSelectionIdsSearch that
passes an empty selection with existing query parameters including ids, and
assert the result removes ids while preserving the other parameters. Place it
alongside the existing differentialIdsFromSearchParams and
differentialSelectionIdsSearch coverage.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e6fdd03-69da-4080-9521-8de412180e2d

📥 Commits

Reviewing files that changed from the base of the PR and between a503c22 and cb3e16e.

📒 Files selected for processing (14)
  • docs/codebase-index.md
  • docs/site-map.md
  • scripts/generate-site-map.ts
  • src/app/(search-app)/differentials/compare/page.tsx
  • src/app/(search-app)/differentials/presentations/route.ts
  • src/components/clinical-dashboard/differentials-home.tsx
  • src/components/differentials/differential-presentation-workflow-page.tsx
  • src/lib/differentials-navigation.ts
  • src/lib/differentials.ts
  • src/lib/mode-secondary-navigation.ts
  • tests/differentials-navigation.test.ts
  • tests/differentials.test.ts
  • tests/mode-secondary-navigation.test.ts
  • tests/route-reachability.test.ts

Comment threadsrc/app/(search-app)/differentials/compare/page.tsx Outdated
Comment threadsrc/components/clinical-dashboard/differentials-home.tsx
Comment threadsrc/components/clinical-dashboard/differentials-home.tsx Outdated
Comment threadsrc/lib/differentials-navigation.ts
cursoragentand others added 4 commits August 8, 2026 18:17
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Resolve generated docs/adoption/sitemap conflicts by regenerating from
source after combining main mode-home routing with the compare route.
Defer URL sync while search is loading, hydrate from mount-captured ids,
lowercase id parsing to match the server, and drop the unsupported
what-argues-against ad-hoc criterion that was all placeholders.
@github-actions

github-actionsBot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 6 failed job(s):

  • Buildneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Static PR checksneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Unit coverageneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Production UI criticalneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Lighthouse budget (advisory)needs investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #9371 (cancelled).

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

cursoragentand others added 5 commits August 8, 2026 19:19
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
The compare page expands discovered route coverage to 50; keep the
adoption contract assertion aligned so Unit coverage can pass.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Resolve adoption-manifest conflict by regenerating with
design-system:adoption:update (50 discovered routes including compare).
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
App Router rejects page.tsx and route.ts on the same path, which broke
Build/static-pr/coverage/ui-critical after the main merge. Keep compare as
a page that redirects same-presentation selections and renders ad-hoc
cross-presentation comparisons; regenerate site-map and adoption docs.
@BigSimmo
BigSimmo merged commit b877421 into mainAug 8, 2026
26 checks passed
@BigSimmo
BigSimmo deleted the cursor/fix-differentials-compare-5c66 branch August 8, 2026 22:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@BigSimmo@cursoragent