Skip to content

feat(search-band): give every mode the documents phone filter - #1706

Merged
BigSimmo merged 8 commits into
mainfrom
claude/search-bar-mobile-layout-buu0io
Aug 7, 2026
Merged

feat(search-band): give every mode the documents phone filter#1706
BigSimmo merged 8 commits into
mainfrom
claude/search-bar-mobile-layout-buu0io

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Every search mode now uses the documents phone filter. Six modes plus the tools launcher shipped a w-full native <select> as their phone filter — formulation and specifiers passed two, in a two-column grid. All seven now pass the same compact badged trigger documents already used, with mobileControlsPlacement="inline", so the one-line phone results band is universal rather than a documents/therapy-compass exception. Converted: differentials, services, factsheets, prescribing, the tools launcher, formulation, specifiers.
  • The idiom is shared, not copied. New src/components/clinical-dashboard/result-filter-control.tsx holds ResultFilterTrigger — lifted verbatim from DocumentFilterTrigger, so the control is literally the same component everywhere — and ResultFilterSheet, a single-choice sheet rendering one role="radiogroup" per dimension. Real radio semantics, because these dimensions are genuinely one-of-N and a bank of aria-pressed toggles asserts they are not. Documents keeps its own panel: multi-select facet groups with counts, a find-a-filter field and collapse-by-default are not expressible as radios.
  • Desktop is untouched. The ribbon renders filterControls from sm up and mobileControls below it, never both, so every mode keeps the chip row or tab strip it already had on a wide screen. No desktop control changed.
  • MobileResultFilterControl is deleted — no caller remains. Its cost was concrete: a whole second band row, no way to report how many filters were active without spending label width on it, and (because the iOS anti-zoom rule in globals.css pins every native select to 16px below sm) a value rendered at the same size as the query heading above it.
  • mobileControlsPlacement still defaults to row. Nothing relies on that fallback now that every caller passes inline; it stays so a new mode that forgets the prop degrades to a second row rather than to an unreadable 58px line at 320px. The #247 "Stop: do not flip the default" is honoured.
  • Docs updated: a new rule 4 in docs/search-chrome-behaviour.md (list renumbered 5–9), the phone-control section of docs/search-results-bar-decisions.md, and the design-system adoption manifest regenerated for the new component. Closes outstanding-issues #247; #253 updated because the component open PR Ship soft Category filter (replace phone select blue highlight) #1606 rewrites no longer exists.

Verification

  • npm run verify:pr-local

Ran to completion except for the last two steps, which were run directly afterwards because the gate aborts on a pre-existing failure (below). Decisive lines:

Test Files  1 failed | 520 passed (521)
     Tests  1 failed | 5538 passed | 4 skipped (5543)

The single failure is tests/pr-handoff-stop.test.ts > emits handoff context only when the marker file exists. It is pre-existing and not caused by this diff — baselined by stashing the whole working tree and re-running against the unmodified checkout, where it fails identically (1 failed | 10 passed). It is a session-hook marker artefact in this container, and the diff touches nothing under .claude/.

The two steps after npm run test, run directly:

Offline RAG fixture and manifest validation passed (36 golden cases, 23 suites).
Client bundle secret surface check passed.

npm run check:bundle-budget: client chunks: 298 files, 1494.5 KiB gzip against baseline 1406.4 KiB gzip; within tolerance (+6.3%, tolerance 10%).

  • npm run verify:ui when UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed

UI verification not run as the full verify:ui sweep: the container's Playwright browser build (chromium-1194) does not match the lockfile pin (chromium_headless_shell-1234), which is the known gap tracked as #255. Every affected owner and journey was proved instead, against a real production build, with PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH pointed at the installed Chromium:

✓ tests/ui-accessibility.spec.ts — 16 passed (25.0s)      (includes both axe WCAG A/AA scans)
✓ tests/ui-specifiers.spec.ts + ui-formulation.spec.ts — 12 passed (22.9s)
✓ tests/ui-tools.spec.ts (launcher, services, differentials ×2, prescribing) — 5 passed
✓ tests/ui-smoke.spec.ts (factsheets ribbon, documents @critical) — 2 passed (11.0s)
✓ tests/ui-stress.spec.ts — 3 passed (10.6s)

Browser measurement of the change itself, at the phone widths that matter — the differentials band is now geometrically identical to the documents band:

390 documents height: 89    390 differentials height: 89
414 documents height: 60    414 differentials height: 60
430 documents height: 60    430 differentials height: 60
  • npm run verify:release before release or handoff confidence claims — not run; no release claim is made here.
  • npm run eval:retrieval:quality, eval:rag, eval:quality: not applicable and not run. This diff touches no retrieval, ranking, selection, chunking, scoring, or answer-generation surface — it is presentation-layer filter chrome. See the RAG impact line below.
  • npm run check:production-readiness: not run — no clinical workflow, privacy, environment, Supabase, source-governance or deployment behaviour changed.

RAG impact: no retrieval behaviour change — this diff only replaces the phone filter control in the results ribbon (a native <select> swapped for a trigger plus sheet) and touches no file under src/lib/rag/**, no clinical-search, retrieval-selection, ranking-config, result-sort or answer-ranking surface, no retrieval RPC, and neither the eval harness nor the golden fixture. The filter values each mode reads and writes are unchanged; only the widget that sets them differs.

Risk and rollout

  • Risk: Low, and confined to phone breakpoints. The blast radius is seven page components plus one new shared component; desktop rendering is byte-identical because the ribbon never renders mobileControls from sm up. The realistic failure mode is a filter dimension whose values are wired wrongly at one call site, which is what the per-mode journeys above exercise. Two behaviours deliberately preserved from the selects they replace: services' and formulation's selected-but-disabled placeholder options (they name the state the reader is already in and remain unselectable), and factsheets'/services'/formulation's navigation-not-filtering semantics, where choosing an option runs a new search and closes the sheet rather than narrowing the current list.
  • Rollback: git revert the feature commit. It is self-contained — the new component file is added, not extracted from a shared dependency, so reverting restores MobileResultFilterControl and all seven call sites together with no partial state.
  • Provider or production effects: None. No provider-backed command was run; no Supabase, OpenAI, or hosted-CI call was made.

Notes

Two things worth a reviewer's attention, neither blocking:

  1. The max-[413px]:flex-wrap threshold is now provably stale. The band's first line wraps below 414px, justified in its own comment as "one line provably cannot hold count + query + sort + filter even with the query fully truncated". Both halves of that premise are gone: Sort became sm-and-up in fix(search-band): drop the sort segments from the phone results bar #1689, and the phone select is gone here. Measured in a real browser with the wrap class stripped at runtime, scrollWidth - clientWidth is 0 at 320, 360, 375, 390 and 402px. Deliberately left unchanged in this PR, because deleting it also changes documents — the reference layout this PR was asked to match — and it is shared geometry with a 320–540px clip sweep asserting against it. Captured as its own outstanding-issues row.

  2. Open PR Ship soft Category filter (replace phone select blue highlight) #1606 is superseded, not conflicted. It replaces MobileResultFilterControl's native <select> with a portal menu to fix the iOS system-blue selected-value highlight. That function no longer exists after this PR, so the defect is gone by removal and Ship soft Category filter (replace phone select blue highlight) #1606 cannot be rebased onto it. #253 has been updated to say so; closing Ship soft Category filter (replace phone select blue highlight) #1606 is a human call and that row stays open until someone makes it.

Summary by CodeRabbit

  • New Features

    • Introduced compact mobile filter triggers with accessible sheet-based controls across search, tools, clinical, factsheet, formulation, service, and specifier experiences.
    • Added active-filter badges, result counts, radio-based single-choice filters, and clear-all actions.
    • Preserved existing desktop filtering behavior.
  • Documentation

    • Updated filter behavior, component adoption, maturity, and issue-tracking documentation.
  • Tests

    • Expanded accessibility and responsive UI coverage for the new mobile filtering experience.

Six search modes plus the tools launcher shipped a `w-full` native
`<select>` as their phone filter — formulation and specifiers passed two,
in a two-column grid. That control cost the results band a whole second
row, could not report how many filters were active, and, because the iOS
anti-zoom rule in globals.css pins every native select to 16px below
`sm`, rendered its value at the same size as the query heading above it.

All seven now pass the badged trigger documents already used, so the
one-line phone band is universal rather than a documents/therapy-compass
exception. Verified in a real browser: the differentials band is
geometrically identical to the documents band at 390px (89px), 414px and
430px (60px).

The idiom is now shared rather than copied. `result-filter-control.tsx`
holds `ResultFilterTrigger`, lifted verbatim from `DocumentFilterTrigger`
so the control is the same component everywhere, and `ResultFilterSheet`,
which renders one `role="radiogroup"` per dimension — real radio
semantics, because these dimensions are one-of-N and a bank of
`aria-pressed` toggles asserts they are not. Documents keeps its own
panel: multi-select facet groups with counts, a find-a-filter field and
collapse-by-default are not expressible as radios.

Desktop is untouched. The ribbon renders `filterControls` from `sm` up
and `mobileControls` below it, never both, so every mode keeps the chip
row or tab strip it already had on a wide screen.

`MobileResultFilterControl` is deleted; no caller remains.
`mobileControlsPlacement` still defaults to `row` — nothing relies on
that fallback now, and it stays so a mode that forgets the prop degrades
to a second row rather than to an unreadable line.

Closes outstanding-issues #247.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013mcqh46SDdiR5KxScihkFQ
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@supabase

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

@coderabbitai

coderabbitai Bot commented Aug 7, 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: 15 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 @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: f38531b6-86dc-4d1b-9494-4a03d107fdff

📥 Commits

Reviewing files that changed from the base of the PR and between 17d7ba6 and f8959c0.

📒 Files selected for processing (6)
  • docs/outstanding-issues.md
  • docs/search-chrome-behaviour.md
  • src/components/clinical-dashboard/result-filter-control.tsx
  • src/components/clinical-dashboard/search-results-header-band.tsx
  • tests/search-results-header-band.dom.test.tsx
  • tests/ui-accessibility.spec.ts
📝 Walkthrough

Walkthrough

The PR replaces native phone result-filter controls across seven modes with shared ResultFilterTrigger and ResultFilterSheet components. It adds typed radio-group behavior, accessibility coverage, mode integrations, updated design-system records, and migration documentation.

Changes

Mobile result-filter migration

Layer / File(s) Summary
Shared trigger and sheet contract
src/components/clinical-dashboard/result-filter-control.tsx, src/components/clinical-dashboard/search-results-header-band.tsx, docs/search-chrome-behaviour.md, docs/search-results-bar-decisions.md, docs/design-system/*
Adds typed filter groups, accessible trigger and sheet components, radio-group keyboard behavior, active-filter counts, and updated mobile placement rules. Removes MobileResultFilterControl.
Seven mode integrations
src/components/applications-launcher-page.tsx, src/components/clinical-dashboard/*, src/components/factsheets/*, src/components/formulation/*, src/components/services/*, src/components/specifiers/*
Connects each phone filter surface to shared trigger and sheet controls with local panel state, filter options, counts, clear behavior, and result updates.
Accessibility and mode validation
tests/search-results-header-band.dom.test.tsx, tests/ui-accessibility.spec.ts, tests/ui-formulation.spec.ts, tests/ui-smoke.spec.ts, tests/ui-specifiers.spec.ts, tests/ui-stress.spec.ts, tests/ui-tools.spec.ts
Updates DOM, accessibility, smoke, responsive, stress, and mode-specific tests for trigger and radio-panel interactions.
Migration records and issue ledgers
docs/branch-review-ledger.md, docs/outstanding-issues.md
Records the shipped migration, updates design-system adoption data, archives issue #247, revises issue #253, and adds issue #273.

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

Sequence Diagram(s)

sequenceDiagram
  participant ModePage
  participant ResultFilterTrigger
  participant ResultFilterSheet
  participant FilterState
  ModePage->>ResultFilterTrigger: Render active-filter count and panel state
  ResultFilterTrigger->>ResultFilterSheet: Open or close filter panel
  ResultFilterSheet->>FilterState: Apply, clear, or reset filter
  FilterState->>ModePage: Update results and visible count
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: applying the documents-style phone filter across every search mode.
Description check ✅ Passed The description covers the required summary, verification, risks, rollback, effects, and relevant verification exceptions with clear evidence.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 claude/search-bar-mobile-layout-buu0io

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

Copilot AI 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.

Pull request overview

This PR standardizes the mobile (“phone”) filter UI across multiple search modes by replacing full-width native <select> controls with a shared badged trigger + sheet pattern, aligning those modes with the existing Documents-style one-line results band.

Changes:

  • Introduces shared ResultFilterTrigger and ResultFilterSheet components for mobile single-choice filtering.
  • Updates multiple mode pages (differentials, prescribing, services, factsheets, formulation, specifiers, tools launcher) to use the shared trigger + sheet and opt into mobileControlsPlacement="inline".
  • Updates Playwright and DOM/unit tests plus docs to reflect the new mobile filter idiom and remove MobileResultFilterControl.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/ui-tools.spec.ts Updates launcher/services/differentials mobile filter journeys to use trigger + radio-sheet interactions.
tests/ui-stress.spec.ts Updates mobile medication filter selector test id to the new trigger.
tests/ui-specifiers.spec.ts Updates specifiers phone filter assertions to use trigger + multi-group radio sheet and badge counts.
tests/ui-smoke.spec.ts Updates factsheets phone filter smoke path to use trigger + sheet and asserts hidden state above sm.
tests/ui-formulation.spec.ts Updates formulation phone filter path to open the sheet and validate default radio selection.
tests/ui-accessibility.spec.ts Updates accessibility test to validate phone filter trigger focus/Enter and radio semantics in the sheet.
tests/search-results-header-band.dom.test.tsx Replaces MobileResultFilterControl usage with ResultFilterTrigger and adds focused unit coverage for trigger + sheet behavior.
src/components/specifiers/specifiers-home-page.tsx Replaces two phone selects with one trigger opening a two-group ResultFilterSheet (family + diagnosis).
src/components/services/services-navigator-page.tsx Replaces phone quick-filter select with trigger + sheet; closes sheet on query-rewriting selections.
src/components/formulation/formulation-home-page.tsx Replaces two phone selects with trigger + sheet (pattern preset + domain), keeping domain as the counted “narrowing” filter.
src/components/factsheets/factsheets-search-page.tsx Replaces phone category select with trigger + sheet; closes sheet on navigation.
src/components/clinical-dashboard/search-results-header-band.tsx Removes MobileResultFilterControl and updates docs/comments around mobileControlsPlacement.
src/components/clinical-dashboard/result-filter-control.tsx Adds new shared mobile filter trigger and single-choice filter sheet (radio-group per dimension).
src/components/clinical-dashboard/medication-prescribing-workspace.tsx Replaces phone medication filter select with trigger + sheet and adds count hints.
src/components/clinical-dashboard/differentials-home.tsx Replaces phone differentials filter select with trigger + sheet, preserving desktop chip controls.
src/components/applications-launcher-page.tsx Replaces phone tool category select with trigger + sheet for the tools launcher.
docs/search-results-bar-decisions.md Updates the design decision record to document the new universal trigger + sheet approach.
docs/search-chrome-behaviour.md Updates the search chrome contract to codify “trigger + sheet, never a select” for phone filters.
docs/outstanding-issues.md Marks #247 as resolved and updates related ledger notes/next-id.
docs/design-system/COMPONENTS.md Updates generated component inventory counts (Sheet usage).
docs/design-system/adoption-manifest.json Adds the new result-filter-control.tsx to the design-system adoption manifest.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/components/clinical-dashboard/result-filter-control.tsx Outdated
Comment thread src/components/clinical-dashboard/result-filter-control.tsx Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… tabIndex keyboard navigation

- Fix contradictory JSDoc: Sheet unmounts when open=false; remove "stays
  mounted while closed" which contradicted the true behaviour.
- Extract FilterRadioGroup sub-component with useRef + roving tabIndex
  (single tab stop on selected option) and onKeyDown handler for
  ArrowLeft/Right/Up/Down + Home/End keyboard navigation, matching the
  SegmentedControl pattern. Dead-end options retain tabIndex=-1 so
  arrow keys skip them while Tab still reaches them for explanation.

Co-authored-by: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
auto-merge was automatically disabled August 7, 2026 16:41

Head branch was pushed to by a user without write access

@BigSimmo
BigSimmo enabled auto-merge August 7, 2026 16:41

@coderabbitai coderabbitai Bot 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: 8

🤖 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 `@docs/branch-review-ledger.md`:
- Line 714: Update the 2026-08-07 entry in the branch-review ledger to include
the decisive success-summary output for the typecheck, lint, and build gates,
rather than listing only their command names. Preserve the existing verification
results and format the added summaries consistently with the other recorded gate
evidence.

In `@docs/outstanding-issues.md`:
- Line 161: Update the execution-queue entry for item `#253` to remove the
obsolete hand-merge `#1606` instruction. Replace it with the remaining human
supersession/closure decision for the deleted MobileResultFilterControl and
require keyboard-parity verification.

In `@docs/search-chrome-behaviour.md`:
- Around line 132-150: Update the shared migration count from six to seven in
docs/search-chrome-behaviour.md lines 132-150 and in
src/components/clinical-dashboard/search-results-header-band.tsx lines 175-188,
including the converted-select count in the header-band implementation. No other
changes are needed.

In `@src/components/clinical-dashboard/result-filter-control.tsx`:
- Line 134: Replace the arbitrary padding, viewport breakpoint, height, and
minimum-width values in the result filter control with named Tailwind 4 design
tokens. Define the corresponding tokens in globals.css using the project’s
established `@theme` conventions, then update the classes around the result filter
control and its responsive/icon elements to reference those tokens instead of
bracketed values or hardcoded breakpoints.
- Around line 253-255: Update the tabIndex expression in the option rendering to
assign tabIndex={0} to every dead-end option, while preserving the existing
roving tab stop for selectable options based on option.value === tabStopValue
and keeping other selectable options at -1.

In `@src/components/services/services-navigator-page.tsx`:
- Around line 740-745: Update the activeQuickFilter onClearAll handler in the
services navigator page to clear both localQuery and the URL’s q parameter, so
reloads do not restore the filter. Add or reuse a shared clear-handler path that
updates the route, and use that same handler for the desktop Clear action.

In `@tests/search-results-header-band.dom.test.tsx`:
- Around line 593-627: Update
tests/search-results-header-band.dom.test.tsx:593-627 to assert the selected
disabled placeholder remains checked and focusable, then verify focus moves to
Crisis and checked states update after keyboard interaction. Add Chromium
coverage in tests/ui-accessibility.spec.ts:483-499 for Arrow keys, Home, End,
focus movement, and checked-state changes in ResultFilterSheet. Replace the
stale native-radio statement in docs/outstanding-issues.md:295 with the current
bespoke roving-tabIndex keyboard contract, then run the focused Chromium gate
via npm run ensure and its printed URL.

In `@tests/ui-tools.spec.ts`:
- Around line 417-425: Update the filter interaction flow around categoryTrigger
so it does not click the underlying trigger while the sheet is open: select “All
tools” from the currently open Category radiogroup, then assert the trigger’s
accessible name is “No filters active.” Remove the premature
categoryTrigger.click() used to reopen or close the sheet.
🪄 Autofix

❌ Autofix failed (check again to retry)

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: 886a3f33-c1e1-448a-926e-4982b513385a

📥 Commits

Reviewing files that changed from the base of the PR and between 1797ba3 and 17d7ba6.

📒 Files selected for processing (22)
  • docs/branch-review-ledger.md
  • docs/design-system/COMPONENTS.md
  • docs/design-system/adoption-manifest.json
  • docs/outstanding-issues.md
  • docs/search-chrome-behaviour.md
  • docs/search-results-bar-decisions.md
  • src/components/applications-launcher-page.tsx
  • src/components/clinical-dashboard/differentials-home.tsx
  • src/components/clinical-dashboard/medication-prescribing-workspace.tsx
  • src/components/clinical-dashboard/result-filter-control.tsx
  • src/components/clinical-dashboard/search-results-header-band.tsx
  • src/components/factsheets/factsheets-search-page.tsx
  • src/components/formulation/formulation-home-page.tsx
  • src/components/services/services-navigator-page.tsx
  • src/components/specifiers/specifiers-home-page.tsx
  • tests/search-results-header-band.dom.test.tsx
  • tests/ui-accessibility.spec.ts
  • tests/ui-formulation.spec.ts
  • tests/ui-smoke.spec.ts
  • tests/ui-specifiers.spec.ts
  • tests/ui-stress.spec.ts
  • tests/ui-tools.spec.ts

Comment thread docs/branch-review-ledger.md
Comment thread docs/outstanding-issues.md Outdated
Comment thread docs/search-chrome-behaviour.md
Comment thread src/components/clinical-dashboard/result-filter-control.tsx
Comment thread src/components/clinical-dashboard/result-filter-control.tsx Outdated
Comment thread src/components/services/services-navigator-page.tsx
Comment thread tests/search-results-header-band.dom.test.tsx
Comment thread tests/ui-tools.spec.ts
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Branch updated during autofix.

The branch was updated while autofix was in progress. Please try again.

claude added 2 commits August 7, 2026 17:00
…d-end reachability

The roving-tabIndex implementation landed in 17d7ba6 with no test coverage, so
the contract it added could regress silently. Adds DOM coverage that fails
without it: one tab stop per group, Arrow/Home/End moving focus and selection,
wrap staying inside a dimension, the checked placeholder as the tab stop, and
the fallback when `value` matches no option. Verified by mutation — removing the
roving tabIndex and the key handler fails exactly these three. Adds the same
assertions as Chromium coverage in ui-accessibility, because jsdom cannot vouch
for focus behaviour under the sheet's real focus trap.

Dead ends now sit on the arrow path but are never selected by it. Previously
they were excluded from both the arrow cycle and the tab order, so a
keyboard-only reader could not reach the option or hear its "Not selectable from
here" note — while the comment claimed they stayed Tab-reachable, which
`tabIndex={-1}` denies. Moving focus without committing selection is the ARIA
guidance for a disabled radio and is the only arrangement that keeps the single
tab stop the role requires; giving dead ends `tabIndex={0}` would restore
reachability by adding a tab stop per option to a control whose point is having
one. No call site produces a dead end today, so this path is defensive and is
asserted rather than assumed.

Also corrects two records against the code: `#253` claimed the sheet inherits
native radio semantics, when it is a bespoke roving handler over buttons, and
its execution-queue row still asked for a `#1606` hand-merge that the deletion
of MobileResultFilterControl made impossible. Disambiguates the six-versus-seven
count — six band modes shipped a select, plus the tools launcher, which carries
no band.

Removes `activeResultFilterCount`, which no caller ever used.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013mcqh46SDdiR5KxScihkFQ
…buu0io' into claude/search-bar-mobile-layout-buu0io

BigSimmo commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

Dispositions for the 8 CodeRabbit findings, all against f8959c0. Four fixed, four declined with reasons. Flagging this before the autofix run lands, because two of the four I declined would make the code worse.

Fixed

  1. #253 execution-queue row still said "hand-merge Ship soft Category filter (replace phone select blue highlight) #1606" — correct, and my own inconsistency: I updated the item's detail but not its queue row. Rewritten to the actual remaining decision.
  2. #253 claimed the sheet uses native radio semantics — also my error, and the reviewer is right to catch it. ResultFilterSheet is role="radiogroup" over buttons with a bespoke roving handler, not native radios. Corrected, and the correction says so explicitly.
  3. Six versus seven — the number was right but I made it confusable. Six band modes shipped a select; the tools launcher was a seventh surface with no results band. Both the doc rule and the prop docstring now say which is which rather than a bare count.
  4. Keyboard coverage — added, and this was the real gap. The roving implementation in 17d7ba6 shipped with no tests at all. There are now DOM assertions for the single tab stop, Arrow/Home/End moving focus and selection, wrap staying inside a dimension, the checked placeholder as tab stop, and the no-matching-value fallback; plus the same as Chromium coverage in ui-accessibility, since jsdom cannot vouch for focus under the sheet's real focus trap. Verified by mutation — deleting the roving tabIndex and the key handler fails exactly three of them.

Declined

  1. Give dead-end options tabIndex={0} — the underlying observation is right (a keyboard reader could not reach a dead end or hear its explanation), but the proposed fix contradicts the finding this same file just fixed: it adds a tab stop per option to a radiogroup whose entire point is having one. Fixed the right way instead — dead ends now sit on the arrow path, so focus reaches them and their note is announced, while selection is withheld and the single tab stop survives. That is the ARIA guidance for a disabled radio. Pinned by a new test.

  2. pr-[0.6875rem] / h-[1.0625rem] / min-[414px]:max-[429px] should be @theme tokens — fair rule, wrong scope. Every one of those values is copied verbatim from DocumentFilterTrigger, already on main; this component was lifted from it precisely so the two cannot drift. Tokenising only the copy reintroduces the drift the extraction removed. Captured as its own item covering both call sites, with the constraint that the measurements themselves must not change (each carries a measured justification).

  3. Services' Clear should also drop q from the URL — the behaviour is real, but it is pre-existing on the desktop Clear button, which the phone sheet was deliberately wired to share. Fixing it here would change a shipped desktop control's semantics inside a phone-scoped change. Captured, with the note that both Clear paths must move together.

  4. ui-tools clicks the trigger while the sheet is open — the premise does not hold. The launcher's onChange calls setFilterOpen(false) before onFilterChange, so selecting "Assess" closes the sheet and the following click reopens it. The test passes: tests/ui-tools.spec.ts:407 › tools launcher is usable at mobile is green in a 104-test Chromium run on this head.

Not actioned: the ledger row asking for decisive gate output. docs/branch-review-ledger.md is append-only by repo policy and rows are never hand-edited; the decisive lines live in the PR body, which is the reviewer-facing record.

Also worth stating plainly for anyone reading CI: the two PR required failures on this PR were both CANCELLED with no failing job, from pushes superseding in-flight runs — not product failures. The Lighthouse budget (advisory) failures are the known runner-Chrome nondeterminism, and that check is advisory.


Generated by Claude Code

@BigSimmo
BigSimmo merged commit cd2c67f into main Aug 7, 2026
24 of 25 checks passed
@BigSimmo
BigSimmo deleted the claude/search-bar-mobile-layout-buu0io branch August 7, 2026 17:15
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.

4 participants