Skip to content

fix(filter): finish the counted-lens migration and stop the API misreporting its catalogue - #2729

Open
BigSimmo wants to merge 1 commit into
mainfrom
claude/blissful-lamport-hy7804
Open

fix(filter): finish the counted-lens migration and stop the API misreporting its catalogue#2729
BigSimmo wants to merge 1 commit into
mainfrom
claude/blissful-lamport-hy7804

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Summary

Four follow-ups left open by #2666, each the smallest change that actually closes the item.

  • Derive the segmented renderer instead of opting into it. A one-of-N filter whose options all carry counts is a segmented bar because of what it is, not because a call site asked. renderAs is gone and the option list decides. That was always the stated end state — the flag existed only so modes could migrate one at a time — and leaving it in kept twelve surfaces on the wrapping counted chips §5 rejects. Bounded at five options, where the chip tier ends: past that a segmented bar wraps into rows and stops reading as one control. This also restores §1 ("No call site picks chips, rows or a segmented control") to an unqualified rule.
  • Split the count on SegmentedControl. It used one field for both the visible figure and the accessible name, so a count carrying a unit had to choose between an unreadable segment and a name that dropped the unit — which is why documents' Source locality stayed on chips. It now takes the same hint/hintLabel pair a filter option already has: "1 loaded source" announced, 1 displayed.
  • Report the catalogue size from /api/differentials. total measured the records being returned, which under a query are the ranked matches — so a caller asking how many differentials exist got its own result count back, while the route's other three branches all reported the catalogue. That is what left the differentials filter unable to state the figure.
  • Drop the redundant "together" from the differentials filter subtitle.

Two defects found while doing the above

Both would have shipped, and both are worth reading:

  1. A state-dependent renderer made the control change shape mid-interaction. Documents' Source locality marks an option dead the moment its count reaches zero, and my first derivation vetoed the segmented bar on any dead end — so the filter would have morphed from a bar into a chip row while the reader was using it. The renderer now depends on the option list alone; nothing about the current selection can change it.
  2. Fixing that by redefining disabled was wrong. SegmentedControl is shared well beyond filters, and skipping genuinely unavailable options with the arrow keys is a deliberate contract with a test pinning it (ui-v2-components.dom.test.tsx, "disabled skipping"). Dead ends are a different state and now have their own field: disabled means "not on offer" and leaves the arrow path; deadEnd means "your own narrowing emptied this" and stays on it with aria-disabled and a stated reason, as docs/filter-contract.md §3 requires. No consumer passes disabled to a segment today, so existing output is unchanged.

Verification

  • npm run verify:ui737 passed (21.2m), exit 0. Full Chromium suite, not a narrowed run: plan:browser fails closed here because the change touches a shared component no spec attributes, and the derivation restyles up to thirteen filter surfaces, so the full sweep was the right gate.
  • Full offline unit suite — 18,038 passing, no failures. (The three failures that were red on main earlier in this work — clinical-hazard-controls, privacy-readiness-contract, rag-plan-package-parity — are gone, fixed upstream by clinical(governance): harden query guard, alert tiers, status derivation, and lexicon rationales #2698.)
  • npm run lint, npm run typecheck, whole-tree prettier --check .
  • npm run check:design-system-contractDesign-system contract passed (1268 production files; raw colors 0; literal shadows 0; legacy tap classes 0; ... 1px shadow spreads 0), adoption + design-sync green
  • npm run check:type-scale, npm run check:icon-scale — both

New coverage: a dead end stays on the arrow path with selection withheld and a stated reason; hint is announced with its unit while hintLabel is displayed; and total does not move when a query is applied — the last fails on the old code with expected 10 to be 201.

Risk and rollout

  • Risk: Moderate and presentational. The derivation changes rendered output on every filter surface whose lens options carry counts, which is the point of the change but is a wider visual diff than feat(filter): fit the shared filter panel to its content at every width #2666. Mitigated by the full Chromium suite passing and by the ≤5-option bound keeping longer lenses on chips. The SegmentedControl change is additive — deadEnd is a new field no existing consumer sets.
  • Rollback: Revert the single commit. No data, schema or config changes.
  • Provider or production effects: None. /api/differentials changes which number it reports in an existing field; it makes no new calls and touches no Supabase or OpenAI surface.
  • RAG impact: none — no retrieval, ranking, selection or scoring surface is touched.

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 separated 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

This PR edits src/app/api/differentials/route.ts, so it classifies as clinical-risk and carries the full preflight. Each box above was checked on its own merits — the basis for each, in the same order:

Item Why it holds
Source verification Untouched. No answer, citation or evidence path is in this diff.
Patient-identifiable workflow None introduced or expanded; no patient-data surface is touched.
Supabase target No Supabase config, migration or client change. The Supabase bot independently reported no supabase/ changes on this PR.
Service-role keys The edited branch is publicDifferentialPayload, the public/demo path. No key handling, auth resolution, rate limiting or access check is altered, and the owner-scoped branches are untouched.
Demo/synthetic separation Unchanged. The demo branch still sets demoMode: true exactly as before.
Source metadata and review status The payload's governance field is untouched.
Deployment classification / TGA SaMD Checked, and nil. The filter work is presentational, and the corrected total has no consumer (verified: nothing in src/ reads it), so no clinical decision-support behaviour changes.

Two points worth stating beyond the checklist:

  • The corrected total makes the API more truthful, not less — it previously reported a figure that was wrong whenever a query was present. Filter counts themselves are untouched and still come from the same predicate as the filter, per §3.
  • Dead-end options remain visible, focusable and explained rather than hidden, so a reader who narrows to nothing can still see which choice did it — the conservative behaviour §3 requires.

Notes

The deadEnd/disabled split is the part most worth a second opinion. Conflating them is the intuitive move and it is wrong in both directions: skipping a dead end hides the explanation from the keyboard user who caused it, and keeping a genuinely unavailable option on the arrow path invites selection of something that is not on offer. The repo's own require-button-wiring rule caught the halfway state — a button carrying both disabled and aria-disabled — which is why the two are now rendered exclusively rather than both being spelled onto the element.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GMaWYk6iF8bsMwEx38egVX

…porting its catalogue

Four follow-ups from the filter panel work, each the smallest change that
actually closes the item.

Derive the segmented renderer instead of opting into it. A one-of-N filter
whose options all carry counts is a segmented bar because of what it is, not
because a call site asked; `renderAs` is gone and the option list decides.
That was always the stated end state — the flag existed only so modes could
move one at a time — and leaving it in kept twelve surfaces on the wrapping
counted chips section 5 rejects. Bounded at five options, where the chip tier
ends: past that a segmented bar wraps into rows and stops reading as one
control.

Split the count on SegmentedControl. It used one field for both the visible
figure and the accessible name, so a count carrying a unit had to choose
between an unreadable segment and a name that dropped the unit — which is why
documents' Source locality stayed on chips. It now takes the same
`hint`/`hintLabel` pair an option already has: "1 loaded source" announced,
"1" displayed.

Report the catalogue size from /api/differentials. `total` measured the
records being returned, which under a query are the ranked matches, so a
caller asking how many differentials exist got its own result count back —
while the route's other three branches all reported the catalogue. That is
what left the differentials filter unable to state the figure. Pinned by a
test that fails on the old code with "expected 10 to be 201".

Drop the redundant "together" from the differentials filter subtitle.

Two defects found and fixed while doing the above, both of which would
otherwise have shipped:

A state-dependent renderer made the control change shape mid-interaction.
Documents' Source locality marks an option dead the moment its count reaches
zero, and the first derivation vetoed the segmented bar on any dead end — so
the filter morphed from a bar into a chip row while the reader was using it.
The renderer now depends on the option list alone.

Fixing that by redefining `disabled` was wrong. `SegmentedControl` is shared
well beyond filters, and skipping genuinely unavailable options with the
arrow keys is a deliberate contract with a test pinning it. Dead ends are a
different state and now have their own field: `disabled` leaves the arrow
path, `deadEnd` stays on it with `aria-disabled` and a stated reason, as
section 3 requires. No consumer passed `disabled` to a segment, so existing
output is unchanged.

Verified: typecheck, lint, whole-tree prettier, check:design-system-contract,
and the full offline unit suite — 18,038 passing, no failures. The three
failures that were red on main earlier in this work are gone, fixed upstream.
Full Chromium suite still running at commit time; result reported separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GMaWYk6iF8bsMwEx38egVX
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: ac532504-45ed-428e-bbbd-0a9feb96df5a


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@supabase

supabase Bot commented Sep 7, 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 ↗︎.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T11:05:04.459442Z 2ab2c2d PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_311d51b0-69d8-42b5-829b-8e224efb8fef)

Sign up for free to 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