Documents: one reachable filter panel, and a library control named after what it opens - #1536
Conversation
…174) Every selected facet key ANDed, regardless of which group it came from. So picking two medications asked for a document about *both* lithium and clozapine, which returns nothing in almost every corpus — multi-select within a group was a dead affordance rather than a feature, and it was the main source of the zero-count dead ends the previous commit had to guard against. Two values from the same group are alternatives: "lithium or clozapine", "renal or thyroid risk". Values from different groups are constraints that stack: "lithium documents, about renal risk". That is OR within a group, AND across groups — the conventional faceted-search model, and the one that matches what the labels mean. Group membership is read from the tags each index entry already carries, not from `index.groups`: that list is sliced to `limitPerGroup`, so a selected key outside the top N of its group would have no resolvable group. A key whose group cannot be resolved is bucketed under its own identity so it still constrains rather than being silently dropped from the filter. `projectSmartTagFacetGroups` now shares the filter's predicate rather than narrowing an already-filtered subset. It has to: adding a key to a group that is already selected *widens*, so the old subset-narrowing shortcut would have under-reported every same-group sibling — the exact numbers this fix exists to correct. Migration risk is close to nil. The behaviour being replaced almost never produced results, so nothing can depend on it: under the old rules a same-group sibling counted 0 and led to an empty list. Tests: seven cases covering alternatives within a group, narrowing across groups, both rules at once, independent widening per group, a single selection unchanged, count/filter agreement under the new rules, and a same-group sibling reported as widening rather than as a dead end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
Two small things, both about not hiding information. A zero-count facet used `disabled`, which drops it out of the tab order. A keyboard or screen-reader user then loses the row entirely and never learns why it went quiet — and a `title` on a disabled control is not reliably announced. It now uses `aria-disabled` with the click guarded, so the row stays focusable and carries an sr-only explanation via `aria-describedby`. That is the disabled-affordance pattern docs/wiring-conventions.md already describes. `#175` corrects `#171`, which is already on main. `#171` claims the documents source-type control duplicates the `Document type` facet group. It does not, and the claim was made from a shared word rather than from the code: `resultTypeTabs`/`filterMatchesByResultType` filter on artefact properties — tableCount, imageCount, a .pdf extension — while the `Document type` facet comes from `document_type` labels meaning policy, guideline, form. A guideline containing a table is both. They are complementary axes. The real duplication is the scope chip `tables` against the source-type `Tables` tab. The merge `#171` recommends is still worth doing, but source-type becomes its own group rather than being absorbed, and the scope chips are the part that folds away. `#171`'s separate claim that `Sources` is navigation stands. The correction is appended rather than edited in: the ledger is append-only, and `#158` is precedent for keeping a withdrawn finding on record so it is not re-filed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
Traced while re-checking Batch B's premise. universal-search-command-surface voids both commandScopes and its setter, and every other write passes an empty array, so the scope chips configured for six modes, the three matcher helpers, and the scope shelf in the results band are all unreachable in production. Not a wiring-conventions defect — nothing is clickable — but it reads as a feature and needs a decision before more filtering UI is built on top of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
The smart-tag facets were unreachable in production. `DocumentTagFacetRail` was mounted only when `activeFacetKeys.length > 0`, and the only writers of that state — `onToggle` and `onClear` — lived inside the gated subtree, so no sequence of clicks could ever satisfy the gate. The three earlier commits on this branch (recount against selection, OR-within/AND-across, dead-end facets) fixed logic no user could reach. Source type (All/Tables/Images/PDFs) was the second filtering surface: a chip row inside the ribbon on desktop, a native `<select>` on phones. Two surfaces, one of them dead. Both now live in `DocumentFilterPanel`, opened by a Filter trigger in the ribbon. Source type keeps radio semantics because it is mutually exclusive; the tag facets keep `aria-pressed` because they are not. The panel footer carries the live result count, which is the thing that tells a reader whether the combination they have built still returns anything. The trigger renders into both of the ribbon's page-control slots, since it shows `mobileControls` below `sm` and `filterControls` from `sm` up. Both copies are in the DOM, so they carry distinct test ids — a shared one makes every Playwright lookup ambiguous under strict mode. Verified: `npm run verify:cheap` exit 0, 450 files / 4710 tests passed. Mutation-tested — restoring the old gate fails all six new DOM tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
"Sources", labelled "Open source filters" with the title "Filter and browse
sources", sat next to the new Filter trigger and read as a second filter. It is
not one — it opens the source library drawer. It is now "Library" /
"Open source library" / "Browse all indexed sources", and the documents action
menu names the same destination the same way ("Collections" / "Open document
folders" -> "Browse library" / "All indexed sources"), as does the mode-home
tile, whose description was literally "Filter all indexed sources."
The control stays in the ribbon rather than moving into the action menu.
Removing it looked right and was wrong: the menu's handler routes through
`onSearchModeChange`, which does `setQuery("")` and
`setModeSearchSubmitted(false)` (ClinicalDashboard.tsx:2670-2677), so reaching
the library that way discards the search being read. The ribbon button is the
only in-context route to it. Caught by running the browser, not by reading.
Verified: `npm run verify:cheap` exit 0, 450 files / 4710 tests passed;
`ui-smoke.spec.ts` 93 passed / 1 failed, the failure being the document-viewer
PDF canvas test, which fails identically with these changes stashed
(Chromium 1194 here against the pinned 1228).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduYWarning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:49 minutes 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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
✨ Finishing Touches📝 Generate docstrings
Comment |
This pull request has been ignored for the connected project Preview Branches by Supabase. |
🔒 Upgrade RequiredPrivate repository analysis requires Pro or Enterprise. Upgrade: https://ecc.tools/pricing?plan=pro ECC Tools keeps the core app open, and puts private repos, team features, and enterprise controls behind paid tiers. |
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
🔒 Upgrade RequiredPrivate repository analysis requires Pro or Enterprise. Upgrade: https://ecc.tools/pricing?plan=pro ECC Tools keeps the core app open, and puts private repos, team features, and enterprise controls behind paid tiers. |
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
BigSimmo
commented
Jul 31, 2026
@cursoragent Unblock the current open PR. Confirm the PR number and GitHub head first from context. If more than one open PR could apply, stop and say which one you would use and why. Fetch and start from the remote tip that matches that GitHub head. If the named branch ref is missing or stale, use the PR head ref. Preserve unrelated local WIP; do not discard dirty work; do not treat a local-ahead commit as the reviewed tip. Do not merge the PR, force-push, rebase, or arm auto-merge. No provider-backed gates without approval. If you cannot push or resolve threads, diagnose and comment only; if inline replies fail, resolve when possible and put blocker dispositions in the summary comment. If auto-merge is already armed, push only for a real blocker and avoid cancelling in-flight required CI unless the push clears that blocker. If the PR is already merged or closed: confirm outcome, post one summary, and stop. Primary job: clear mergeability and required-CI blockers on this tip. Do a full product review only if needed to explain or fix a blocker. Stay light otherwise—no broad verification suites or Playwright unless needed to prove one concrete unblock fix. Snapshot the GitHub head: tip, behind/ahead, mergeable state, merge-tree versus origin/main (real conflict versus behind-but-clean), required checks on that tip including Production UI when selected, and unresolved threads that block merge or required CI. Missing checks while dirty are not green. Tip moved → re-snapshot. Ledger-lookup against that GitHub head under an unblock/fix scope for this PR. If already handled at this head for unblock work, merge-tree clean, required checks green, and no blocking threads: summarize, comment, stop. Unblock once: real conflict → merge origin/main with the smallest correct resolution (prefer main’s shared queues; keep this PR’s notes); behind-but-clean → one sync, late if required CI is in flight; clean → leave. skip-branch-sync and similar labels do not block a manual sync needed to clear a real blocker. No sync thrash. After any sync or push, re-snapshot tip, merge-tree, and required checks before declaring done. Dedupe the ledger if a merge touched it. Fix only what blocks required checks or mergeability on this tip. Ignore advisory red unless it reveals a real required failure. Prefer the smallest safe fix; if a bot/autofix commit caused the break, prefer revert. Prove with the smallest targeted check only. Format and commit before push. Push only blocker-fix commits. Append ledger for the final GitHub head under the unblock scope. Never push a ledger-only tip. One PR comment: tip, sync/merge-tree, what blocked and what you fixed or dispositioned, required CI status, residual risks; merge left to me. Stop when merge-tree is clean on the current GitHub tip, no blocking threads remain for merge/required CI, and required checks are green or clearly in progress. |
PR #1536 is unblocked at tip Cleared
Status now
Merge left to you. |
PR Summary by QodoDocuments: unify filter UI, fix unreachable facets, rename Library control
AI Description
Diagram
High-Level Assessment
Files changed (9) |
BigSimmo
commented
Jul 31, 2026
@cursoragent Unblock the current open PR. Confirm the PR number and GitHub head first from context. If more than one open PR could apply, stop and say which one you would use and why. Fetch and start from the remote tip that matches that GitHub head. If the named branch ref is missing or stale, use the PR head ref. Preserve unrelated local WIP; do not discard dirty work; do not treat a local-ahead commit as the reviewed tip. Do not merge the PR, force-push, rebase, or arm auto-merge. No provider-backed gates without approval. If you cannot push or resolve threads, diagnose and comment only; if inline replies fail, resolve when possible and put blocker dispositions in the summary comment. If auto-merge is already armed, push only for a real blocker and avoid cancelling in-flight required CI unless the push clears that blocker. If the PR is already merged or closed: confirm outcome, post one summary, and stop. Primary job: clear mergeability and required-CI blockers on this tip. Do a full product review only if needed to explain or fix a blocker. Stay light otherwise—no broad verification suites or Playwright unless needed to prove one concrete unblock fix. Snapshot the GitHub head: tip, behind/ahead, mergeable state, merge-tree versus origin/main (real conflict versus behind-but-clean), required checks on that tip including Production UI when selected, and unresolved threads that block merge or required CI. Missing checks while dirty are not green. Tip moved → re-snapshot. Ledger-lookup against that GitHub head under an unblock/fix scope for this PR. If already handled at this head for unblock work, merge-tree clean, required checks green, and no blocking threads: summarize, comment, stop. Unblock once: real conflict → merge origin/main with the smallest correct resolution (prefer main’s shared queues; keep this PR’s notes); behind-but-clean → one sync, late if required CI is in flight; clean → leave. skip-branch-sync and similar labels do not block a manual sync needed to clear a real blocker. No sync thrash. After any sync or push, re-snapshot tip, merge-tree, and required checks before declaring done. Dedupe the ledger if a merge touched it. Fix only what blocks required checks or mergeability on this tip. Ignore advisory red unless it reveals a real required failure. Prefer the smallest safe fix; if a bot/autofix commit caused the break, prefer revert. Prove with the smallest targeted check only. Format and commit before push. Push only blocker-fix commits. Append ledger for the final GitHub head under the unblock scope. Never push a ledger-only tip. One PR comment: tip, sync/merge-tree, what blocked and what you fixed or dispositioned, required CI status, residual risks; merge left to me. Stop when merge-tree is clean on the current GitHub tip, no blocking threads remain for merge/required CI, and required checks are green or clearly in progress. |
PR #1536 — tip Already clear on this tip: merge-tree clean vs Stale context failures at |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Keep main's #175–#180 therapy/scroll findings. Renumber this PR's #175/#176 captures to #181/#182 and advance issues:next-id to 183. Add PR_POLICY_BODY with the required Clinical Governance Preflight checkboxes (classifier hits clinicalRisk via document-tags.ts). Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
🔒 Upgrade RequiredPrivate repository analysis requires Pro or Enterprise. Upgrade: https://ecc.tools/pricing?plan=pro ECC Tools keeps the core app open, and puts private repos, team features, and enterprise controls behind paid tiers. |
Governance checklist is now on the PR description. Removing the template avoids leaving another leftover body file on main. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Code Review by Qodo
1. Source-type buttons too small |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
PR #1536 unblockTip: What blocked
Fixed
Dispositioned
Required CIRe-running on Residual
|
🔒 Upgrade RequiredPrivate repository analysis requires Pro or Enterprise. Upgrade: https://ecc.tools/pricing?plan=pro ECC Tools keeps the core app open, and puts private repos, team features, and enterprise controls behind paid tiers. |
…change Facet keys can contain spaces, which made aria-describedby targets invalid HTML ids. Reset filterPanelOpen when the search query changes so the panel does not stay open over a different result set. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
🔒 Upgrade RequiredPrivate repository analysis requires Pro or Enterprise. Upgrade: https://ecc.tools/pricing?plan=pro ECC Tools keeps the core app open, and puts private repos, team features, and enterprise controls behind paid tiers. |
Unblock summary — PR #1536Tip: What blocked / what cleared
All previously open review threads are resolved. Required CI (this tip)
Residual risks
Proof for the Sentry fixes: focused |
Static PR lint failed on react-hooks/set-state-in-effect for closing the panel via useEffect when query changed. Mirror the facet query-scoped state pattern instead so a new search still hides the panel without a cascading render. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
🔒 Upgrade RequiredPrivate repository analysis requires Pro or Enterprise. Upgrade: https://ecc.tools/pricing?plan=pro ECC Tools keeps the core app open, and puts private repos, team features, and enterprise controls behind paid tiers. |
Unblock update — tip |
Unblock status — PR #1536Confirmed: only open PR in this context is #1536 (
Required CI on this tip
What was cleared earlier on this PR (no new blocker this pass)Merge conflict on This passNo sync, no code push (auto-merge armed + no real blocker). Ledger append for this head kept local only (no ledger-only tip). Residual: wait for Production UI shards / |
Uh oh!
There was an error while loading. Please reload this page.


Summary
DocumentTagFacetRailwas mounted only whenactiveFacetKeys.length > 0, and the only writers of that state —onToggleandonClear— lived inside the gated subtree, so no sequence of clicks could ever satisfy the gate. It has been that way onmain. The three earlier commits on this branch (recount against selection, OR-within/AND-across, dead-end facets) fixed logic no user could reach.<select>on phones. It now lives in the sameDocumentFilterPanelas the tag facets, opened by a Filter trigger in the results ribbon. Source type keeps radio semantics because it is mutually exclusive; the tag facets keeparia-pressedbecause they are not. The panel footer carries the live result count, which is what tells a reader whether the combination they have built still returns anything.Open source filterswith the titleFilter and browse sources, sat next to the new Filter trigger and read as a second filter. It is now "Library" /Open source library/Browse all indexed sources. The documents action menu names the same destination the same way ("Collections" / "Open document folders" → "Browse library" / "All indexed sources"), as does the mode-home tile, whose description was literally "Filter all indexed sources."#182(renumbered from#176after mergingmain) records a related finding this work turned up but does not fix: the command-scope system is inert.universal-search-command-surface.tsx:392-393voids bothcommandScopesand its setter, and every other write passes an empty array, so the scope chips configured for six modes, the three matcher helpers, and the scope shelf in the results band are all unreachable. Not a wiring-conventions defect — nothing is clickable — but it needs a decision before more filtering UI is built on it.One thing deliberately not done: moving the library control out of the ribbon into the action menu. That looked correct and was wrong. The menu's handler routes through
onSearchModeChange, which doessetQuery("")andsetModeSearchSubmitted(false)(ClinicalDashboard.tsx:2670-2677), so reaching the library that way discards the search being read. The ribbon button is the only in-context route to it. The browser run caught this; reading the code did not.Verification
npm run verify:cheap— exit 0,Test Files 450 passed (450),Tests 4710 passed | 4 skipped (4714)npm run verify:uiscope covered by a directui-smoke.spec.tsChromium run: 93 passed, 1 failed in 2.5m. The one failure isdocument viewer puts the PDF preview first with pinned evidence after it on mobile, atpdfScroller.locator("canvas"). It fails identically with these changes stashed, so it is pre-existing and environmental — this box has Chromium 1194 against the project's pinned 1228, run via thePLAYWRIGHT_CHROMIUM_EXECUTABLE_PATHhook the config already supports.activeFacetKeys.length > 0gate fails all six tests in the newtests/document-filter-panel.dom.test.tsx, and removing it passes all six. The tests guard the fix rather than merely accompanying it.npm run verify:pr-local— not run;verify:cheapplus the direct Chromium spec run is the evidence above.npm run eval:retrieval:quality,eval:rag,eval:quality,check:production-readiness— not run and not applicable. No retrieval, ranking, selection, chunking, scoring, or answer-generation code is touched.RAG impact: no retrieval behaviour change — this changes only which client-side controls are mounted and how already-returned
DocumentMatch[]results are filtered and labelled in the documents results view. No file undersrc/lib/rag/**, clinical-search, retrieval-selection, ranking-config, answer-ranking, the eval harness, or the golden fixture is touched, and no retrieval RPC or comparator ordering is altered.Risk and rollout
f92ad5ea(naming) reverts without touching the panel;83f67187(panel) reverts without touching the ledger row;608c5912is docs only. Note that a squash merge folds them, so a post-merge revert of one item means reverting its hunks by hand.Clinical Governance Preflight
classifyPullRequestFilesreturnsclinicalRisk: truefor this file set becausesrc/lib/document-tags.tsmatches the libdocumentpath pattern (client-side tag facet helpers only — no retrieval, ranking, or document-access change). Governance checklist completed for that classifier hit:Clinical KB Database(sjrfecxgysukkwxsowpy)Document results were already rendered by this component; this PR changes only which controls filter them client-side.
Notes
mobileControlsbelowsmandfilterControlsfromsmup. Both copies are in the DOM, so they carry distinct test ids (document-filter-trigger-phone/-wide); a shared id makes every Playwright lookup ambiguous under strict mode, which is the failure mode that took out an earlier PR on this branch.smup the ribbon controls are deliberatelymin-h-10(40px) for fine pointers, and the 44px floor is a phone contract asserted on the phone trigger at 390px. Asserting the phone floor at 1440px was a genuine failure in the first browser run.main, this PR's#175/#176ledger captures were renumbered to#181/#182so they do not collide with main's therapy/scroll findings.