Skip to content

Search results band: truthful failure state, approved design, and adoption gate - #1316

Merged
BigSimmo merged 39 commits into
mainfrom
claude/top-search-design-mockups-w53znc
Jul 28, 2026
Merged

Search results band: truthful failure state, approved design, and adoption gate#1316
BigSimmo merged 39 commits into
mainfrom
claude/top-search-design-mockups-w53znc

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fixes a clinical-safety defect that is live on main. SearchResultsHeaderBand had two states — loading or done — and no failure path. When a registry call errored or returned unauthorized, the page handed it matchCount={0} and it rendered a confident "0 matches". On services-navigator-page.tsx that states there are no crisis services when the search never ran. The same held on medications, differentials, documents, forms and favourites.
  • Lands the approved mockup (Option A · Edit 2 — final), which already encoded the fix: a warning rail, "Couldn't search", and no number asserted, held distinct from a genuine zero result which does assert 0. The redesign and the safety fix are one change, not two.
  • Makes adoption structural, so a future search page cannot ship without the band.

Six commits, each independently revertible and reviewable in order:

Commit What
c9c494e status union + fault panel on the band. No call site changes, no visual change.
54f75e8 Services pilot — the commit that removes the false "0 matches" from the crisis page.
3750784 The remaining six pages that can actually fail.
a868e8c Adoption gate (compile-time + contract test).
26e258a Visual pass to the mockup + refetch state.
+2 Formatter, and one prettier-stability fix.

Design decisions worth a reviewer's attention

  • status union, not a second boolean. loading && error is otherwise representable and undefined; the invariant "a faulted search never asserts a number" becomes one guard instead of a per-call-site puzzle. It mirrors RegistryRequestStatus, so most callers pass a near-identity map. The deprecated loading prop stays as a shim so the five pages with no async source need no edit and the nine existing DOM assertions are undisturbed.
  • Accessibility. The spine keeps one unconditional role="status"ui-tools.spec.ts:953 asserts it is visible on every search route — and the fault panel carries role="alert". While faulted the spine goes aria-live="off" so the alert makes the single announcement rather than both speaking.
  • The rail is now a real border-top. As an absolutely-positioned bar inside an overflow-hidden 12px-radius card, the corner arc sliced its ends (at y=0 the arc is 12px in, at y=2px it is 5.4px in) so it started short and tapered while the 1px border curved past it. The desktop left-hand vertical bar goes with it.
  • Breakpoint moved selectively. The row/stack switch goes lgsm so portrait tablets stop getting the phone layout, but the rail's overflow, fade mask and trailing spacer stay on lg — at 640–1023px a page with chips, sort, a mobile filter and utility controls can exceed the width, and containing that in a scrollable rail is what keeps the no-horizontal-overflow assertions green.
  • Weights are numeric font-weight on named classes in globals.css, not Tailwind arbitrary values: check:type-scale --strict is a zero gate on arbitrary text-[Npx], and this is already the idiom in that file. Geist is a variable face, so 470/540/560/580 interpolate rather than snapping to 700.
  • Forced colors. The rail survives as thickness (3px, and 6px double for a fault) because --clinical-accent resolves to LinkText and would otherwise be indistinguishable from the other borders — that is what keeps a failed search distinct from a successful one when colour is gone. The block is appended last in globals.css, since at equal specificity a later rule wins and an earlier block is silently overridden while still reading correctly. --warning-border is not remapped by the token block, so it is pinned explicitly.
  • forms was structural — the band was mounted only when the registry was ready, so a failure removed the header entirely. It is now mounted in every state and the redundant RegistryStatusNotice is deleted.
  • favourites needed a hook changeuseSavedRegistryFavourites discarded both underlying registry statuses, so the page could not tell "no favourites" from "could not load favourites". It now returns { items, status }, folding only the registries it actually requested: a disabled hook sits in its initial state forever and must not read as a failure.

Verification

  • npm run verify:cheap — exit 0. Includes check:type-scale --strict, check:icon-scale, check:design-system-contract, check:knip, sitemap:check, docs:check-*, lint, typecheck, and the full unit suite (408 files, 4133 passed, 4 skipped).

  • npm run lint, npm run typecheck — exit 0.

  • npm run verify:pr-local — not run; verify:cheap plus the targeted Playwright runs below were used instead.

  • npm run verify:uipartially run. UI verification not run in full: the pinned Playwright browser build (chromium_headless_shell-1228) is absent from this environment's /opt/pw-browsers, which only has 1194. The suite was run against the available build via the config's existing PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH hook: ui-tools 86/87, ui-smoke 91/92, ui-formulation + ui-specifiers + ui-accessibility + ui-route-coverage 37/37.

    The two failures are outside this diff and neither file is touched by it:

    • ui-tools.spec.ts:2362 safety-plan — strict-mode violation on duplicate copy in patient-safety-plan.tsx.
    • ui-smoke.spec.ts:3526 document viewer — PDF.js canvas not rendering in pdf-canvas-viewer.tsx; plausibly the browser-build mismatch.

    A reviewer should re-run verify:ui on the pinned build before merge.

  • npm run eval:retrieval:qualitynot applicable. No retrieval, ranking, selection, chunking, or scoring behaviour is touched.

  • 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 is presentation chrome above the result list. No file under src/lib/rag/**, clinical-search, retrieval-selection, released-search-order, ranking-config, the eval harness, the golden fixture, or the retrieval RPCs is modified.

Risk and rollout

  • Risk: the band is shared chrome, so this lands on every mode at once. Highest-risk items are the forms gate inversion (the band's mount condition changed) and the breakpoint move (horizontal overflow at 640–1023px). Both are covered by existing Playwright assertions that pass. The favourites hook signature changed, which touched three call sites and three test mocks.
  • Rollback: every phase is a separate commit. Reverting the visual commit restores the old rail and weights without touching the status API, so the clinical-safety fix survives a visual rollback — that is why it was sequenced first.
  • Provider or production effects: None. No provider-backed gate was run and no live service was contacted.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use — unchanged; this touches only result-count presentation.
  • No patient-identifiable document workflow was introduced or expanded.
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy) — untouched.
  • Service-role keys and private document access remain server-only — untouched.
  • Demo/synthetic content remains clearly separated from real clinical sources — untouched.
  • Source metadata, review status, and outdated/unknown-source behaviour remain conservative — improved. The change makes the band fail conservatively: it now refuses to assert a count it cannot substantiate, where previously it asserted zero.
  • Deployment classification/TGA SaMD impact checked — no decision-support behaviour changed; a false negative ("no crisis services") is removed, not introduced.

Notes

One piece of the approved plan is deliberately incomplete, and it is the item to discuss.

The refetch pulse (stale count held visible with a pulsing dot instead of a skeleton) is built in the band and adopted on formulation, where the "loading" is useDeferredValue lag over static data — nothing identity-scoped is held across the gap, so it is safe.

It is not extended to the auth-backed registries, because every async hook here deliberately clears its data on entering loading:

  • src/lib/use-registry-records.ts:85refetch() resets to records: []
  • use-medication-catalog.ts:76setState({ data: null, … })
  • use-differential-catalog.ts:133 — resets to emptyDifferentialMatches

use-differential-catalog.ts:122 states why: "Auth must clear prior identity's matches immediately (parity with useUniversalSearch)", and :164 adds that "a later retype of any prior query cannot resurrect authorized matches." That is a privacy invariant, not an oversight. Holding a stale count across a reload on those surfaces means changing three shared hooks under an identity-and-query guard, with a test pinning that an identity change still clears. That deserves its own commit and its own review rather than riding along here.

Also worth flagging: no automated review is currently available on this repository — CodeRabbit is rate-limited and Codex is out of credits, which is how PR #1280 merged with no bot review. Given this diff touches shared chrome rendered by 12 production surfaces, it wants a human reader.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added unified “search results band” behavior with standardized states (ready/loading/refetching/error/unauthorized), a dedicated fault panel, and recovery actions.
    • Enforced consistent band adoption via a required results surface configuration.
  • Bug Fixes
    • Prevented match counts and related page controls from showing during faulted/loading states.
    • Improved fault vs empty rendering across multiple search surfaces.
  • Tests
    • Expanded DOM, accessibility, forced-colors, responsive visibility, scroll focus, and adoption-gate coverage.
  • Documentation / Style
    • Updated band documentation contract and introduced dedicated band styling (including forced-colors adjustments).

claude added 7 commits July 28, 2026 05:11
The shared results band had two states, loading or done, and no failure
path. When a registry call errored or returned unauthorized, the page
handed it matchCount={0} and it rendered a confident "0 matches". On the
services navigator that reads as "there are no crisis services" when the
truth is "we could not check" — the band asserted a clinical fact it had
no basis for. The same held on medications, differentials, documents,
forms and favourites.

Add a `status` union (ready | loading | refetching | error | unauthorized)
so the invariant "a faulted search never asserts a number" lives in one
guard rather than being re-derived per call site. A union rather than a
second boolean because `loading && error` is otherwise representable and
undefined; it also mirrors RegistryRequestStatus, so most callers can pass
a near-identity map of the status they already hold.

The deprecated `loading` prop stays as a shim so the five pages with no
async source keep working untouched and the existing DOM assertions are
undisturbed. No call site changes yet — this commit only adds the
capability.

Accessibility: the spine keeps one unconditional role="status" (Playwright
asserts it is visible on every search route), and the fault panel below
carries role="alert". While faulted the spine's live region is silenced so
the alert makes the single announcement rather than both speaking. Retry
goes through the shared AsyncButton busy contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
The services navigator passed `loading={registryLoading}` and left
`registryBlocked` unused by the band, so an errored or unauthorized
registry reached the header as matchCount={0} and rendered "0 matches".
On the crisis-services surface that states there are no crisis services
when the search never ran.

Pass the real status instead, with the existing notice copy moved into the
band's fault panel and `registry.refetch` — previously computed and never
used — wired to Retry. The unauthorized case keeps its distinct "Session
expired" copy and the account-setup link rather than being flattened into
a generic error.

Remove the now-duplicate ModeHomeStatusNotice blocks below the band so the
failure is announced once. The mode-home page keeps its own notice; it has
no results band.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
Every page that can actually fail now tells the band so, instead of
letting an errored fetch arrive as matchCount={0}:

- differentials: catalogFailed was computed and used only for a panel
  below the band; the band itself still printed "0 matches".
- prescribing: catalog.error was rendered in a separate danger strip.
- therapy-compass: b.error was never referenced on this screen at all,
  and b.retryData now backs Retry.
- documents: the standalone unavailable alert is kept only for routes
  that render no ribbon, so the message is never lost.
- forms: structural. The band was mounted only when the registry was
  ready, so a failure removed the header entirely. It is now mounted in
  every state and RegistryStatusNotice, made redundant, is deleted.
- favourites: useSavedRegistryFavourites discarded both underlying
  registry statuses and returned bare items, so the page could not tell
  "no favourites" from "could not load favourites". It now returns
  { items, status }, folding only the registries it actually requested —
  a disabled hook sits in its initial state forever and must not read as
  a failure.

The five pages with no async source are untouched; they are already
correct on the default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
Wiring every current page is worth little if the next search page can
skip the band and go back to reporting "0 matches" for a failed search.
Two layers close that:

- `AppModeSearchConfig.resultsSurface` is required and non-optional, so a
  fourteenth mode fails typecheck until its author declares whether it
  presents a result list or a synthesised answer.
- tests/search-results-band-adoption.test.ts holds them to it: every mode
  declaring "results-band" must have a production component mounting the
  band for that modeId, and every production search route must reach the
  band within one import hop.

The modeId scan collects literals recursively because document-search
computes its mode via a ternary; a naive matcher would read that page as
having no mode. The route assertion carries one documented allowlist
entry (the documents search stub, which has no result list of its own)
and asserts it found routes at all, so it cannot pass vacuously.

Verified by negative control: flipping answer to "results-band" fails the
gate with a named mode, and restoring it returns to green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
Visual pass to the approved mockup (Option A, Edit 2).

Rail: the accent was an absolutely-positioned bar inside an overflow-hidden
12px-radius card, so the corner arc sliced its ends — at y=0 the arc is
12px in, at y=2px it is 5.4px in — and it started short and tapered while
the 1px border curved past it. Two lines, two geometries. It is now the
card's real border-top, which mitres into the side borders and follows the
radius by construction. The desktop left-hand bar goes with it.

Type: nothing on this surface is bold any more. The heaviest step is 580
and the query (560) and figure (580) share it, separated by tabular
numerals and a hairline rather than by shouting; "matches" drops to 400
and a zero count steps down to 470 and muted. Weights are numeric
font-weight on named classes in globals.css rather than Tailwind arbitrary
values, because check:type-scale --strict is a zero gate on arbitrary
text-[Npx] and that is already the idiom in this file.

Breakpoint: the row/stack switch moves from lg to sm so portrait tablets
stop getting the phone layout. The rail's overflow, fade mask and trailing
spacer stay on lg deliberately — at 640-1023px a page with chips, sort, a
mobile filter and utility controls can exceed the width, and containing
that in a scrollable rail is what keeps the no-horizontal-overflow
assertions green.

Forced colors: the rail survives as thickness (3px, 6px double for a
fault) because --clinical-accent resolves to LinkText and would otherwise
be indistinguishable from the other borders. That is what keeps a failed
search distinct from a successful one when colour is gone. The block is
appended last in globals.css, since at equal specificity a later rule wins
and an earlier block is silently overridden while still reading correctly.
--warning-border is not remapped by the token block, so it is pinned here.

Formulation adopts the refetch state: its "loading" was useDeferredValue
lag over static data, not a network request, so the previous count is
still correct and stays visible with a pulse instead of collapsing to a
skeleton. Safe there precisely because nothing identity-scoped is held
across the gap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
A comment inside the null branch of the results ternary was reordered by
Prettier on every run, so format:check could never converge. Move it above
the expression.

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

supabase Bot commented Jul 28, 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 Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The shared SearchResultsHeaderBand now uses explicit status and fault props, suppresses untrusted counts, centralizes recovery messaging, and supports forced-colors styling. Search pages and favourites consumers were migrated, with app-mode adoption rules and expanded DOM, accessibility, retry, and browser test coverage.

Changes

Search results band

Layer / File(s) Summary
Status-aware band contract and presentation
src/components/clinical-dashboard/search-results-header-band.tsx, src/app/globals.css, docs/search-chrome-behaviour.md, tests/search-results-header-band.dom.test.tsx, tests/ui-accessibility.spec.ts
The band supports ready, loading, refetching, error, and unauthorized states, suppresses untrusted counts and controls, renders fault recovery, and applies shared typography and forced-colors styling.
Mode, document, account, and favourites status contracts
src/lib/app-modes.ts, src/components/clinical-dashboard/document-search-unavailable-status.ts, src/components/account-data-provider.tsx, src/components/clinical-dashboard/*favourites*, src/components/clinical-dashboard/universal-search-command-surface.tsx
Search modes declare their results surface; document availability, account loading, and saved favourites expose structured statuses, reload behavior, and trusted-count handling.
Search page fault and loading integration
src/components/clinical-dashboard/{differentials-home,document-search-results,medication-prescribing-workspace}.tsx, src/components/forms/forms-search-results-page.tsx, src/components/services/services-navigator-page.tsx, src/components/formulation/formulation-home-page.tsx, src/components/therapy-compass/screens/search-screen.tsx
Search surfaces pass explicit status, fault copy, retry handlers, and unauthorized actions to the shared band while removing duplicate notices and fault content.
Catalogue, favourites, and Therapy Compass recovery
src/components/clinical-dashboard/use-differential-catalog.ts, src/components/clinical-dashboard/use-saved-registry-favourites.ts, src/components/therapy-compass/{bindings.tsx,data/use-therapy-data.ts}, tests/*retry*, tests/use-differential-search.dom.test.tsx
Catalogue, favourites, account, and Therapy Compass retries rerun failed requests and expose settled recovery state.
Adoption and browser validation
tests/search-results-band-adoption.test.ts, tests/search-results-header-band.dom.test.tsx, tests/ui-*.spec.ts, tests/*favourites*.tsx, tests/document-search-unavailable-status.test.ts, tests/saved-registry-favourites-status.test.ts
Tests enforce band reachability, status invariants, forced-colors ordering, retry behavior, structured hook mocks, and visible search-control targeting.
Review and issue documentation
docs/branch-review-ledger.md, docs/outstanding-issues.md
Review closeout rows and outstanding issue ledger entries are updated.

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

Possibly related PRs

Suggested labels: codex

Suggested reviewers: claude

Sequence Diagram(s)

sequenceDiagram
  participant SearchRoute
  participant RegistryOrCatalogue
  participant SearchResultsHeaderBand
  participant User
  SearchRoute->>RegistryOrCatalogue: read loading, error, or unauthorized state
  SearchRoute->>SearchResultsHeaderBand: pass resolved status and fault content
  SearchResultsHeaderBand->>SearchResultsHeaderBand: suppress count and controls when faulted
  SearchResultsHeaderBand->>User: render status region and fault alert
  User->>SearchResultsHeaderBand: activate retry
  SearchResultsHeaderBand->>RegistryOrCatalogue: invoke retry handler
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% 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 main change: the search results band failure-state update and adoption gate.
Description check ✅ Passed The description follows the required template and includes summary, verification, risk, governance, and notes with only minor gaps.
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.

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

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

CI triage

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

  • Production UIneeds 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 #6289 (success).

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

@BigSimmo
BigSimmo marked this pull request as ready for review July 28, 2026 07:15
BigSimmo and others added 3 commits July 28, 2026 15:15
Production UI failed twice on this PR with the same signature but a
different test each time: a strict-mode violation where a document-wide
getByTestId resolved to two identical elements, one of them hidden. First
`differentials-search-results`, then `global-search-input`.

That is the Next streaming `S:` clone of the page root, which CI load makes
observable and which #1294 already documented and fixed the same way on the
differentials detail page. It is not caused by this branch: the test that
failed first passed untouched on the next run.

Scope the affected assertions to the visible element, which is the idiom
already used for this exact testid in ui-overlap.spec.ts. Applied to the
class rather than the single instance, so the flake does not simply move to
whichever spec loses the race next.

Deliberately not changed: the `toHaveCount` assertions on this testid. Those
count elements on purpose as single-owner guards, and visible-scoping them
would change what they assert.

Verified locally: ui-chrome-scroll + ui-phone-scroll 65 passed; ui-smoke 91
passed with only the pre-existing PDF-canvas failure, which is a local
browser-build artifact and passes in CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
…-w53znc' into claude/top-search-design-mockups-w53znc

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: 389250d3f6

ℹ️ 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 thread src/app/globals.css Outdated
Comment thread src/components/clinical-dashboard/differentials-home.tsx Outdated
@BigSimmo

Copy link
Copy Markdown
Owner Author

@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. This is the pull request's single automatic repair pass: do not perform a fresh review, create new standalone findings, or request another review. Work only the existing unresolved Codex threads on the current head. Always fix P0 and P1 findings. For P2 and lower findings, fix only clear, scoped, low-risk issues; otherwise disposition them with a concise reason. After fixing or dispositioning a thread, reply in that thread with as the first line, followed by a concise summary; that marker authorizes the workflow to close that exact thread. If human input or new authorization is required, do not use the marker and leave the thread open with the blocker. Finish only after every actionable thread is fixed or dispositioned and closed, or explicitly left open for a human decision. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: ade17ac880

ℹ️ 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 thread src/components/forms/forms-search-results-page.tsx
Comment thread src/components/clinical-dashboard/document-search-results.tsx Outdated
Four findings, all real, all mine.

P1 — forms/medication/differentials: suppressing the count in the spine is
worthless while a page-supplied filter chip still renders "Forms 0" or
"All (0)" beside it. The reader still saw a zero asserted about a search
that never ran, which is the exact defect this branch exists to remove.
Fixed at the band rather than per page: a faulted band drops filter and
mobile controls entirely, since a filter over a result set that failed to
load is meaningless anyway. Test now asserts no digit anywhere in the
ribbon, not just in the status node.

P1 — documents: on the services/forms path the registry has its own
status and can be healthy while the unrelated document API is down.
Deriving the ribbon's status from the document API let that invalidate
good registry results — announcing "Couldn't search" and hiding a valid
recordMatchCount while SearchRecordResults rendered those very matches
below. Derive from whichever source the ribbon is actually counting.

P2 — the accent rail was not rendering at all. `@layer components` loses
to Tailwind's utilities layer, and the band root carries `border` /
`border-[color:var(--border)]`, so the 2px accent silently degraded to a
neutral 1px. AGENTS.md already says component classes here are
deliberately unlayered; this block was not. Verified by computed style:
borderTopWidth 1px/rgb(229,231,235) before, 2px/rgb(11,111,134) after.
My own test missed it because it asserted class presence, so
ui-accessibility now asserts computed border width and colour, plus the
forced-colors thickening to 3px.

P2 — differentials: the fault copy told the reader to retry or browse the
catalogue directly while the removed error section had taken both actions
with it. Restore rerunSearch as onRetry and the catalogue links as
faultAction.

Verified: 409 files / 4148 unit tests; ui-accessibility 14 passed;
ui-tools + ui-formulation + ui-specifiers 99 passed.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: bbad2a9a9f

ℹ️ 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 thread src/components/clinical-dashboard/differentials-home.tsx
The Retry I added in bbad2a9 called rerunSearch, which only re-runs the
parent document-evidence search. useDifferentialSearch keys on query plus
auth identity, neither of which changes when the reader asks to try again,
so the catalogue request was never reissued and the band stayed faulted.

A Retry that cannot retry is worse than no Retry: it promises recovery and
silently does nothing.

Give the hook a refetch, mirroring useRegistryRecords. A failed request is
never written to the cache, so bumping a retry counter in the effect deps
is enough to re-request — no eviction needed, and the identity/query
clearing guards are untouched.

Test asserts the real behaviour Codex asked for: first catalogue request
fails, refetch is called, and both kind=diagnosis and kind=presentation are
requested again with the band returning to ready. One existing assertion
moved from toEqual to toMatchObject, since the hook's return now carries
the refetch callback.

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

@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: 10

🧹 Nitpick comments (2)
src/components/clinical-dashboard/search-results-header-band.tsx (1)

410-435: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the retry against a rejected onRetry.

await onRetry() has no catch, so a rejecting recovery callback (several call sites pass registry.refetch / b.retryData directly) escapes as an unhandled rejection while the panel silently returns to the idle "Retry" label with no feedback. finally only resets the spinner.

🛡️ Proposed fix
                   onClick={async () => {
                     setRetrying(true);
                     try {
                       await onRetry();
+                    } catch {
+                      // The fault panel is already the failure surface; a failed
+                      // retry simply leaves it in place rather than crashing.
                     } finally {
                       setRetrying(false);
                     }
                   }}
🤖 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/components/clinical-dashboard/search-results-header-band.tsx` around
lines 410 - 435, Update the retry handler on the AsyncButton in the retry action
block to catch rejected onRetry calls, preserving the existing
setRetrying(false) cleanup while surfacing the failure through the component’s
established error or feedback mechanism instead of allowing an unhandled
rejection.
src/components/clinical-dashboard/medication-prescribing-workspace.tsx (1)

471-472: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a retry action to the medication catalogue fault panel. useMedicationCatalog only returns data/loading/error, so this panel has no recovery path. Expose a refetch/retry from the hook and pass it into SearchResultsHeaderBand as onRetry (or faultAction).

🤖 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/components/clinical-dashboard/medication-prescribing-workspace.tsx`
around lines 471 - 472, Expose a refetch or retry callback from
useMedicationCatalog, then pass it as onRetry or faultAction to the
SearchResultsHeaderBand rendering the medication catalogue fault panel. Preserve
the existing catalog error, loading, and ready status behavior while enabling
users to retry failed requests.
🤖 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/search-chrome-behaviour.md`:
- Around line 53-60: Update the typography guidance in the section describing
heading weights to consistently state whether the query and figure both use
weight 580, or only the query is emphasized; align the opening claim with the
explicit weight values and clarify the intended hierarchy for implementers.

In `@src/components/clinical-dashboard/differentials-home.tsx`:
- Around line 919-929: Update the faultBody expression in the catalog fault
panel to branch on catalog.status like faultTitle, omitting retry guidance for
unauthorized responses while retaining the retry and browse guidance for other
failures; keep onRetry behavior unchanged.
- Around line 992-1007: Update the main catalogue-content branching around the
`!best && !catalogFailed` empty-state condition so `catalogFailed` suppresses
the entire results body, preventing the results grid from rendering when `best`
is null. Preserve the existing fault panel as the sole failure UI and leave the
guarded `DifferentialsMobileCompareBar` behavior unchanged.

In `@src/components/clinical-dashboard/document-search-results.tsx`:
- Around line 894-909: Update the record-mode handling in the status/faultBody
logic and the related standalone alert near the corresponding duplicate block so
an unavailableMessage remains surfaced when the ribbon does not carry it.
Preserve recordStatus-based status and valid ribbon counts, but avoid setting
faultBody and alert visibility in a way that drops auth, API, or setup notices
on services and forms record paths.

In `@src/components/clinical-dashboard/favourites-command-library-page.tsx`:
- Around line 1198-1201: Update the empty-results rendering in the favourites
command library page so SearchResultsEmptyState is shown only when
favouritesRegistryStatus is ready. For loading, error, or unauthorized statuses,
suppress the normal “No matches” branch and use the existing recovery action for
faulted states; retain the current empty-state behavior for successfully loaded
results.

In `@src/components/clinical-dashboard/favourites-hub.tsx`:
- Line 50: Update the favourites data handling around useSavedRegistryFavourites
so it retains the returned status alongside items. Render the shared
fault/recovery state for registry errors or unauthorized responses, and only
show “No favourites match” when the registry status is ready and the item list
is empty.

In `@src/components/clinical-dashboard/use-saved-registry-favourites.ts`:
- Around line 27-34: Update useSavedRegistryFavourites to expose and return the
refetch callback from useRegistryRecords, then thread it through the consuming
page into its fault UI so the error state provides the existing retry action.

In `@src/components/therapy-compass/screens/search-screen.tsx`:
- Around line 31-33: Update the search screen body rendering to suppress the
results and “No therapies match those filters” empty state whenever b.error is
set, while preserving the existing loading and successful-results behavior. Use
the existing b.error state and the body rendering logic near the fault panel,
matching the services page’s faulted-state handling.

In `@tests/search-results-band-adoption.test.ts`:
- Around line 151-157: Update the forced-colors validation in the search-results
adoption test to fail when the final `.search-band` selector is absent instead
of returning early. Determine the final forced-colors block’s closing boundary
and assert that the selector’s position falls between its opener and closer,
rather than relying only on opener offset ordering.

In `@tests/ui-accessibility.spec.ts`:
- Around line 620-626: Update the rail style extraction in the band evaluation
to return borderLeftColor instead of borderLeftWidth, then compare rail.color
against that left-border color in the accent-rail assertion. Keep the existing
width and neutral-color checks unchanged.

---

Nitpick comments:
In `@src/components/clinical-dashboard/medication-prescribing-workspace.tsx`:
- Around line 471-472: Expose a refetch or retry callback from
useMedicationCatalog, then pass it as onRetry or faultAction to the
SearchResultsHeaderBand rendering the medication catalogue fault panel. Preserve
the existing catalog error, loading, and ready status behavior while enabling
users to retry failed requests.

In `@src/components/clinical-dashboard/search-results-header-band.tsx`:
- Around line 410-435: Update the retry handler on the AsyncButton in the retry
action block to catch rejected onRetry calls, preserving the existing
setRetrying(false) cleanup while surfacing the failure through the component’s
established error or feedback mechanism instead of allowing an unhandled
rejection.
🪄 Autofix (Beta)

❌ 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: c2e69a80-fdf3-4abf-ae6f-bbef25acf1da

📥 Commits

Reviewing files that changed from the base of the PR and between 7f91d99 and bbad2a9.

📒 Files selected for processing (24)
  • docs/search-chrome-behaviour.md
  • src/app/globals.css
  • src/components/clinical-dashboard/differentials-home.tsx
  • src/components/clinical-dashboard/document-search-results.tsx
  • src/components/clinical-dashboard/favourites-command-library-page.tsx
  • src/components/clinical-dashboard/favourites-hub.tsx
  • src/components/clinical-dashboard/medication-prescribing-workspace.tsx
  • src/components/clinical-dashboard/search-results-header-band.tsx
  • src/components/clinical-dashboard/universal-search-command-surface.tsx
  • src/components/clinical-dashboard/use-saved-registry-favourites.ts
  • src/components/forms/forms-search-results-page.tsx
  • src/components/formulation/formulation-home-page.tsx
  • src/components/services/services-navigator-page.tsx
  • src/components/therapy-compass/screens/search-screen.tsx
  • src/lib/app-modes.ts
  • tests/favourites-auth-gate.dom.test.tsx
  • tests/favourites-hub-unavailable-controls.dom.test.tsx
  • tests/mode-menu-prefetch.dom.test.tsx
  • tests/search-results-band-adoption.test.ts
  • tests/search-results-header-band.dom.test.tsx
  • tests/ui-accessibility.spec.ts
  • tests/ui-chrome-scroll.spec.ts
  • tests/ui-phone-scroll.spec.ts
  • tests/ui-smoke.spec.ts

Comment thread docs/search-chrome-behaviour.md Outdated
Comment thread src/components/clinical-dashboard/differentials-home.tsx Outdated
Comment thread src/components/clinical-dashboard/differentials-home.tsx
Comment thread src/components/clinical-dashboard/document-search-results.tsx
Comment thread src/components/clinical-dashboard/favourites-hub.tsx Outdated
Comment thread src/components/clinical-dashboard/use-saved-registry-favourites.ts
Comment thread src/components/therapy-compass/screens/search-screen.tsx
Comment thread tests/search-results-band-adoption.test.ts Outdated
Comment thread tests/ui-accessibility.spec.ts
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⚠️ Branch updated during autofix.

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

@BigSimmo
BigSimmo enabled auto-merge (squash) July 28, 2026 07:46
CodeRabbit found a crash I introduced. `best` is `results[0] ?? null`, and
the gate `!best && !catalogFailed` means a faulted catalogue with no
results takes neither the loading nor the empty branch — control reaches
the results grid, which dereferences `best.id` and `best.kind`
unconditionally and throws. That fires on every failed or unauthorized
differentials search: exactly the state this branch exists to report
truthfully.

I had already seen this and misread it. An earlier probe of the failed
catalogue path returned zero rendered elements and I recorded that as "the
component does not render when the catalogue fails". It was an error
boundary swallowing a crashed subtree.

`catalogFailed` now short-circuits before the `!best` test.

Four more findings in the same family, all confirmed against the code
rather than taken on the reviewer's word:

- therapy-compass rendered "No therapies match those filters" underneath
  the fault panel, so the page claimed both failure and emptiness.
- favourites (library and hub) treated an empty list as "no favourites"
  during loading and failure, which reads as data loss rather than an
  unavailable registry. The hub had also discarded the status entirely.
- documents dropped the unavailable-API notice in record mode: the ribbon
  takes its fault from the registry there, so nothing carried the message.
- a rejecting `onRetry` escaped as an unhandled rejection.

Also fixed the assertion I added last round to catch the inert rail: it
compared `borderTopColor` against `borderLeftWidth` — a colour against a
length — so it could never fail. It now compares colour to colour.

Verified: 409 files / 4149 unit tests, lint, typecheck.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: 65ba242837

ℹ️ 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 thread src/components/forms/forms-search-results-page.tsx
Comment thread tests/search-results-band-adoption.test.ts Outdated
…ion gate

Two more from Codex, both mine.

The count leak I fixed at the band only covered faults. Forms forces
`displayedMatches` to [] until the registry is ready, so ResultTabs still
asserted "Forms 0" underneath a "Searching…" spine — the same untrue zero,
one state over. Loading now suppresses count-bearing controls too.
`refetching` deliberately does not: there the prior count is still correct,
which is the whole point of that state.

The adoption gate matched the bare identifier, so an import, a comment, or
a dead reference counted as adoption — a production search route could drop
the band while the gate I built to prevent that stayed green. It now
requires a rendered `<SearchResultsHeaderBand` element, with a negative
fixture asserting that an import, a mention and an alias are all rejected
while a mount is accepted.

Verified: 409 files / 4150 unit tests, lint, typecheck.

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

Copy link
Copy Markdown
Owner Author

@cursoragent Inspect the PR and repository state, identify the root cause of every failing CI check, unresolved review comment, merge conflict, and related defect. Apply the smallest robust fix using existing project patterns and best practices, without unrelated changes. Run Bugbot and all relevant lint, type-check, test, build, and CI-equivalent commands, fix any issues found, then resolve only the review comments that are fully addressed. Summarise the root causes, changes made, validation results, and any remaining blockers.

@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

CI/review closeout for #1316 is done on tip 8ae374ac.

Root causes

Issue Cause
Prior Production UI red (6a4810c) Suspense duplicate global-search-input strict-mode (already hardened earlier; not on current tip)
Codex/CodeRabbit threads Favourites status incomplete; unauthorized copy promised Retry; forced-colors gate soft-passed; docs wording conflict
New Codex P2 Account /api/account/favourites pending/failure ignored → false “ready / 0 matches”
Merge conflicts None (merge-tree clean)

Changes made

  • Differentials unauthorized faultBody no longer promises Retry
  • Favourites: account readiness folded with registry status; partial/demo items keep a truthful nonzero band count; refetch wired to Retry
  • Adoption gate: real JSX mount + forced-colors membership in the final block
  • Band DOM test for loading suppressing count controls
  • Docs typography clarified

Validation

  • Focused vitest 43/43, tsc + eslint on touched files PASS
  • All 19 review threads resolved
  • Bugbot via pr-bugbot (no Bugbot MCP here): residual note that a binary band status can’t express true multi-source “partial” without a dedicated shape

Remaining blockers

  1. Hosted CI still settling on the latest push — wait for Production UI / PR required
  2. Branch is 1 commit behind main (clean merge-tree) — sync before merge
  3. Full verify:ui / verify:pr-local not re-run in this pass (focused gates only)
Open in Web Open in Cursor 

claude and others added 5 commits July 28, 2026 09:06
Prettier formatting only, no behaviour change. The unformatted file was
failing both `Static PR checks` and the new `ci/circleci: verify` job,
which each run `npm run format:check`; `prettier --check .` now reports
all matched files clean.

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

Track GET /api/account/favourites failures as loadError so a failed first
save cannot mark an already-loaded empty library unavailable. Discover
results-band mode homes in the adoption gate (not only /search/ paths),
with two-hop import reachability and landing-only allowlist entries.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
…hub.com/BigSimmo/Database into claude/top-search-design-mockups-w53znc

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Prettier formatting only, no behaviour change. Second recurrence of the
same break: `format:check` is not part of `npm run test`, `typecheck`,
or eslint, so a change can report a green suite and still fail both
`Static PR checks` and `ci/circleci: verify`, which each run it.

Verified on this tip: `prettier --check .` clean, and the full unit
suite is 416 files / 4232 passed / 4 skipped — the post-merge run that
had not yet been done.

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

Copy link
Copy Markdown
Owner Author

Heads-up for anyone else pushing to this branch: run npm run format:check before you push.

The same failure has now broken CI twice — 77f6b0a9 fixed tests/favourites-hub-unavailable-controls.dom.test.tsx, and 9662d9b2 fixed tests/search-results-band-adoption.test.ts. Both were Prettier-only, no behaviour change.

It keeps recurring because format:check is not part of npm run test, npm run typecheck, or npm run lint. Both preceding closeouts reported a green unit suite, a clean typecheck and clean eslint — all accurate — and CI was still red, because Static PR checks and the new ci/circleci: verify job each run prettier --check . as a separate step. A green suite is not evidence of a green static gate.

npm run verify:cheap does cover it, as does npm run format:changed if you want something quicker.

Worth knowing about the CircleCI check specifically: it is new. 5bcdc1dd on main replaced the CircleCI stub with a real verify job, so this PR is among the first to actually run it. It isn't misconfigured — it caught a genuine defect both times.

Verified on the current tip 9662d9b2:

  • prettier --check .All matched files use Prettier code style!
  • npm run typecheck, npm run lint — exit 0
  • npm run test416 files, 4232 passed, 4 skipped

That last one also closes the gap the previous closeout flagged, which noted the suite was green before the tip merge rather than after it. It is now green after.

One red still outstanding and unaddressed: Gitleaks. It is not reporting a secret — the log says no leaks found in partial scan — but it scanned ~0 bytes because its commit range was invalid on the runner, so the secret scan did not actually execute on this PR. That is a genuine gap rather than noise, and it is not fixed by any of the above.


Generated by Claude Code

@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: 1

🤖 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/components/therapy-compass/data/use-therapy-data.ts`:
- Around line 68-90: Scope retry waiters to the request key and retry attempt in
retry and settleRetryWaiters, so a pending retry promise is resolved only by the
matching request effect. Prevent a route change from allowing the new request to
settle the old retry, while ensuring the original retry is settled when its
matching request completes; add a test covering navigation during an in-flight
retry.
🪄 Autofix (Beta)

❌ 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: 7995cdbc-521f-4be3-b2b9-de2e58ecbf9e

📥 Commits

Reviewing files that changed from the base of the PR and between 14e16a7 and 1b7c023.

📒 Files selected for processing (13)
  • docs/branch-review-ledger.md
  • docs/outstanding-issues.md
  • src/components/account-data-provider.tsx
  • src/components/clinical-dashboard/favourites-hub.tsx
  • src/components/clinical-dashboard/saved-registry-favourites-status.ts
  • src/components/clinical-dashboard/use-saved-registry-favourites.ts
  • src/components/therapy-compass/bindings.tsx
  • src/components/therapy-compass/data/use-therapy-data.ts
  • tests/favourites-account-retry.dom.test.tsx
  • tests/favourites-hub-unavailable-controls.dom.test.tsx
  • tests/saved-registry-favourites-status.test.ts
  • tests/search-results-band-adoption.test.ts
  • tests/therapy-compass-data-recovery.dom.test.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/saved-registry-favourites-status.test.ts
  • tests/favourites-account-retry.dom.test.tsx
  • tests/search-results-band-adoption.test.ts
  • docs/outstanding-issues.md
  • src/components/clinical-dashboard/use-saved-registry-favourites.ts

Comment thread src/components/therapy-compass/data/use-therapy-data.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: 9662d9b2cc

ℹ️ 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 thread tests/search-results-band-adoption.test.ts
…uction UI run

ui-overlap.spec.ts:199 passed toHaveCount(1) and then resolved the same
header#search locator to 2 elements one statement later, one hidden.
A duplicate appearing after a passing count assertion is a stream or
hydration artifact by construction, which rules out a static double
mount and rules out this branch's CSS and component changes as a cause.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: 54b302f38d

ℹ️ 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 thread src/components/clinical-dashboard/document-search-results.tsx
Comment thread src/components/clinical-dashboard/document-search-results.tsx
The band's fault panel now owns error/unauthorized for the services and
forms registry, but RecordRegistryNotice still rendered underneath it,
so one failed request produced two adjacent panels saying much the same
thing. This is the double-reporting already removed from the standalone
services and forms pages, missed on the dashboard path.

Loading is deliberately left to the notice: the band only says
"Searching…" there and never names the registry, so suppressing it would
lose information rather than duplicate it.

Checked the test catches the defect rather than merely passing — with
the guard removed both fault cases fail on the duplicate copy while the
loading case still passes, confirming the suppression is not too broad.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot 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: 7abc91f19d

ℹ️ 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 thread src/components/account-data-provider.tsx
The save and clear paths already call markSessionExpired() on a 401
(lines 177, 200); the load path only stored a generic message. That gap
was invisible until this branch added reload and wired Retry to it —
Retry re-sends the same rejected authorization header, so an expired
token produced a button that could never recover. A control that
advertises an action must perform one.

markSessionExpired is depended on by identity rather than reached
through auth, which would re-run the load effect on every auth-object
render; it is a useCallback upstream, so it stays stable.

Two tests, both checked against the unfixed code: the 401 case fails
without the change, and a 503 case pins that a transient server fault
still leaves a signed-in reader signed in rather than bouncing them to
a sign-in screen.

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

Copy link
Copy Markdown
Owner Author

Re: account-data-provider.tsx — "Treat favourites 401 responses as session expiry" — fixed in 861a3518.

Confirmed the asymmetry before changing anything: setFavourite (:177) and clearFavourites (:200) both call markSessionExpired() on a 401; the load path at :122 threw a generic message with no status inspection at all.

The finding is right that this branch created the harm. Before reload existed there was no Retry on that path, so the missing check was inert — wiring Retry to it turned it into a button that re-sends the same rejected token forever. Same rule as the earlier finding here: a control that advertises an action must perform one.

Implementation note: I depend on markSessionExpired by identity rather than reaching through auth inside the effect. react-hooks/exhaustive-deps demands the whole auth object once you call a method on it, and depending on that would re-run the favourites load on every auth-object render. It's a useCallback in client.tsx:366, so the destructured reference is stable.

Two tests, both checked against the unfixed code rather than assumed:

  • 401 → markSessionExpired() called. Removing the guard fails this case specifically.
  • 503 → not called. A transient server fault is exactly what Retry is for; flipping the session there would bounce a signed-in reader to a sign-in screen over a blip. That's the over-fire I wanted pinned, not just the under-fire reported.

I also had to clear the hoisted vi.fn() call history in beforeEach — it survives restoreAllMocks, so the "not called" assertion was inheriting the previous test's call and passing for the wrong reason.

Verification on 861a3518: prettier --check . clean; npm run typecheck and npm run lint exit 0 with no exhaustive-deps warning; npm run test417 files, 4237 passed, 4 skipped.

For context on the run before this one: 54b302f3 was fully green in CI, including Production UI at 325 passed (12.1m) — the first complete browser run this PR has had, and the ui-overlap duplicate-header failure did not recur, consistent with the intermittent clone tracked as #093.


Generated by Claude Code

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found.

Copy link
Copy Markdown
Owner Author

Correction: the therapy retry finding is marked addressed but is not fixed

use-therapy-data.ts"Scope retry settlement to its request key and attempt" now carries "✅ Addressed in commits b92c272 to 5911c2e". I checked the code rather than the claim, and it is unchanged:

  • retryWaitersRef is still useRef<Array<() => void>>([]) — no request-key or attempt token
  • settleRetryWaiters still drains the whole array unconditionally
  • nothing in that range touches the file

5911c2e is a merge of main, and the two commits it brought in (18bed6da, 3a8edb93) are the CSS/motion audit PRs #1296 and #1297. The range is what CodeRabbit re-scanned, not a fix.

The finding itself stands and I verified it independently earlier: the old effect's !active guard stops a superseded effect settling a newer reload, but nothing stops a newer effect settling an older waiter, because the ref is hook-scoped. The in-code comment asserts the stronger property — "Only the active attempt may settle Retry" — which the code does not provide.

Practical consequence: it is now invisible to the @Codex fix sweep, so unlike the four findings just routed, nobody will pick it up. Severity is genuinely low — Retry's busy state ends early on a route change, and the hook already returns loading: true through the state.requestKey !== requestKey guard at that moment — but "low severity" and "silently closed" are different things.

Separately: #093 is now confirmed pre-existing on main

Earlier I said I had not shown the duplicate-root condition predates this branch, only that it was order-dependent. That gap is now closed, and not by me — main fixed it independently in 3a8edb93, and its reasoning matches the CI evidence exactly:

checking count then immediately calling waitFor races that flicker into a strict-mode violation

Decisive proof it predates this PR: at 631d90d2, the commit before this branch's first commit, tests/ui-overlap.spec.ts already carried the comments "two header#search nodes, which trips Playwright strict mode" and "a second transient header#search can exist briefly."

So the sole Production UI failure this PR ever produced was a known main condition, and the hardened helper is now merged in.

Verification on the merged tip: prettier --check . clean; npm run typecheck and npm run lint exit 0; npm run test417 files, 4237 passed, 4 skipped.


Generated by Claude Code

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • Scoped Therapy Compass retry waiters and in-flight retry coalescing to both the originating request key and retry-attempt token. A newer route request can no longer resolve a retry promise belonging to an older request. src/components/therapy-compass/data/use-therapy-data.tsL67-L103
  • Updated request settlement so superseded effects may settle only their matching retry while remaining unable to update the active route’s state. Unmount cleanup still safely releases remaining waiters. src/components/therapy-compass/data/use-therapy-data.tsL105-L137
  • Added a route-change regression test proving that completion of a newer full-catalogue request does not settle the older index-catalogue retry; the retry settles only after its original replacement request completes. tests/therapy-compass-data-recovery.dom.test.tsxL145-L189
  • Committed the fix as e544d0d (fix(therapy): scope retry settlement to request) and created the required follow-up PR metadata titled “Follow-up: scope Therapy Compass retry settlement.”

Testing

  • git diff --check
  • npx prettier --check src/components/therapy-compass/data/use-therapy-data.ts tests/therapy-compass-data-recovery.dom.test.tsx — all files passed Prettier validation.
  • git status --short --branch — worktree clean after commit.
  • ⚠️ npm run test -- tests/therapy-compass-data-recovery.dom.test.tsx — not run because repository dependencies are absent and the environment provides Node v20.20.2, while the project requires Node 24.

View task →

@BigSimmo
BigSimmo merged commit 4bcfeb9 into main Jul 28, 2026
83 checks passed
@BigSimmo
BigSimmo deleted the claude/top-search-design-mockups-w53znc branch July 28, 2026 12:37
BigSimmo pushed a commit that referenced this pull request Jul 28, 2026
The row cited seven abbreviated SHAs as the thing to consolidate. All
seven are unreachable: git fetch origin <sha> fails for every one, no
open PR or branch carries them, and none was in the squash merge. They
were authored in a sandbox on a branch named work and never pushed, so
the follow-up PRs each commit reported do not exist. A ledger entry
whose only pointer is a dead hash is worse than no entry, because it
reads as tracked.

Rewritten around what is actually true and checkable: the PR #1316
review threads persist and describe each fix with file and line detail,
and two of the findings are still live on main — the unscoped Therapy
Compass retry waiters at use-therapy-data.ts:68, and the adoption gate
returning null for root-path modes at
tests/search-results-band-adoption.test.ts:101, which leaves
/?mode=prescribing and Documents outside the inventory. Both verified
against origin/main rather than inferred.

The rest of the batch had already landed independently and is recorded
as needing no action.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
BigSimmo pushed a commit that referenced this pull request Jul 28, 2026
Codex pushed back on my own row and was right. retryWaitersRef at
use-therapy-data.ts:68 is genuinely unscoped, but nothing observes the
early settle: useTherapyData lives in the long-lived TcProvider and
requestKey derives only from screen, so it cannot change without the
screen changing. The sole awaiting caller is the band's AsyncButton
inside search-screen.tsx, which unmounts on exactly that transition, and
workspace.tsx uses onClick={b.retryData}, discarding the promise.

I had already rated this below the original Major severity but still
listed it as live with a visible "Retry stops being busy" symptom. That
claim was wrong, and a ledger row asserting a user-visible defect that
cannot occur would have sent follow-up work at unnecessary waiter
keying. The correction is retained rather than deleted, with the
condition that would make it real.

Also links the durable PR #1316 review source, using the repo's pinned
Prettier 3.9.5 rather than a transient npx version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
BigSimmo added a commit that referenced this pull request Jul 30, 2026
* issues: archive five completed rows, delete none

Triage pass over all 58 open items found five rows whose work is finished and
whose next action is empty. Each moves from Open items to Resolved / archive
with its fix evidence and the 2026-07-30 date:

- #95 the pr-required aggregate now routes a cancelled result through a shared
  cancelled_error helper; guarded by seven cases that execute the extracted
  script, three mutation-proven. The red is deliberately retained, since GitHub
  counts a skipped required check as passing.
- #96 every PR #1316 sub-item is dispositioned: the adoption-gate root-path gap
  closed on PR #1394, four findings were fixed independently, and the Therapy
  Compass retry-waiter finding was corrected to not-a-live-defect.
- #104 a correction row with no next action - the worker's triple image read is
  an accepted peak-memory trade-off documented at worker/main.ts:866-869, not
  debt. Archived so a fourth audit does not re-file it.
- #109 the branch sweep refuses on a shallow clone, an indeterminate result is
  its own failure, and the guard moved into the exported collector so the
  evidence-pack path fails closed too.
- #115 the band adoption gate was redesigned to walk a real reachability graph
  rather than asking whether a file mentions the band.

Nothing is deleted. The ledger contract is archive-only (SKILL.md:44 "Archive,
never delete"; this file's own conventions: "rows are archived, not deleted, so
the history stays auditable"), so no row qualifies for deletion. Row total is
unchanged at 120: 58 -> 53 open, 62 -> 67 archived.

Prettier widened the archive Outcome column to fit the new evidence, which
repads the other archive rows; git diff --ignore-all-space is 7 insertions /
7 deletions, i.e. the five moved rows plus both separators.

Verified: node scripts/check-outstanding-issues.mjs --self-test && node
scripts/check-outstanding-issues.mjs -> "Outstanding-issues guard passed: 120
rows (53 open, 67 archived), unique ids, next-id=126 above the highest".
npx prettier --check . -> "All matched files use Prettier code style!"

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

* docs: record PR 1428 review

* docs: align issue 109 resolution date

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

3 participants