Skip to content

Clinical results feel instant: row placeholders, quiet Also matches, catalogue warm-up - #3496

Merged
gitar-bot[bot] merged 13 commits into
mainfrom
claude/site-instant-results-xk2j9i
Oct 13, 2026
Merged

gitar-bot[bot] merged 13 commits into
mainfrom
claude/site-instant-results-xk2j9i

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

User description

Requested by Josh · project thread

Summary

Site recommendations 12 Oct 2026, item 6. [CLINICAL search display only; Josh asked on 12 Oct for these drafts to be sent live once the full browser suite was checked]

Before: after pressing search on Medication, the page showed "Searching..." and "Loading medication catalogue..." over an empty page, and the "Also matches" strip kept saying "Searching..." after the main results had arrived. The first search also waited a 250 ms typing pause.

After: Medication and Documents show grey placeholder rows shaped like real results while loading (one named loading status for screen readers); Also matches stays out of view until every mode's matches are in, then appears below the results; the first request after Enter goes out at once; opening the Medication or Documents home preloads the results code (no data request, no query text, nothing paid or AI-backed).

How: new ResultRowsPlaceholder and useModeResultsWarmup; use-medication-catalog, medication-prescribing-workspace, universal-search-also-matches, document-search-results, answer-status updated.

Areas touched: App experience, Design system (plus 1 file not yet placed in any area)

RAG impact: no retrieval behaviour change — only when results render; result content and order untouched. None of the changed files match ragRankingPatterns.

Screenshots: /mnt/project-files/site-recommendations-2026-10-12/built/instant-results/ in the project files (loading moment and settled, 390 light and dark, 1440, 320).

Verification

  • eslint on changed files: clean; tsc: no errors; prettier clean
  • npm run test:focused on the changed files (after the review fixes): medication workspace and instant-results DOM tests pass
  • npm run check:design-system-contract: exit 0
  • Browser: full suite on this draft (run 38260466446). Chromium, Firefox, desktop Safari and iPad passed; iPhone Safari failures are only main's known backlog (see comment)

Dev-server timings (machine shared and loaded, indicative only): Medication Enter to first row about 2.0 s before, about 1.7 s after; other modes within noise.

Risk and rollout

  • Risk: low to medium; clinical search pages' loading states change. One test that required the Also matches "Searching..." text was updated because that text is removed on purpose.
  • Rollback: revert this PR.
  • Provider or production effects: None. The warm-up makes no network data request.

Notes

  • Follow-ups not done (files owned by other open PRs): the results header band's "Searching..." pill, preloading on composer focus, Differentials placeholders. Biggest remaining Medication wait: each search returns the whole 3 MB medication list, and the page loads twice after Enter (ClinicalDashboard / global-search-shell).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL


CodeAnt-AI Description

Show clinical search results sooner with clearer loading states

What Changed

  • Medication and document searches show result-shaped placeholders while results load.
  • Related matches appear beneath the main results only after matches are available; the strip no longer shows a lingering “Searching…” message.
  • The first medication request after submitting a search skips the typing delay, while later query changes remain debounced.
  • Opening Medication or Documents preloads results code without sending a background search.

Impact

✅ Clearer medication and document loading
✅ No typing-delay wait on the first medication search
✅ No background search when opening a mode

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

…hes, catalogue warm-up

- Medication and Documents results show rows shaped like results (aria-busy on the
  results region) instead of a loading sentence or spinner over an empty page.
- The Also matches strip is no longer drawn while it is still searching; it fills in
  under the main results once it has matches, and never shows a Searching row.
- The first medication request of a submitted search goes without the 250 ms typing
  debounce; later query changes are still debounced.
- Opening the Medication home warms its results chunk and the local catalogue route
  (no query text, nothing model-backed); Documents warms its results chunk only.

Display timing only: which results appear and their order are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL
…laceholder bars

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL
@BigSimmo BigSimmo self-assigned this Oct 12, 2026
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

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

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 232eb1fc-6387-4640-9297-19442482d20f

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

@supabase

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

@codeant-ai

codeant-ai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: dcb387eb
Scan Time: 2026-10-13 01:20:44 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found
Duplicate Code ✅ PASSED 0.0% duplicated
SAST ✅ PASSED No security issues
Bugs ✅ PASSED Rating S: No bugs
IAC ✅ PASSED Rating S: No issues

View Full Results

@codspeed

codspeed Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by ×2.5

⚠️ 1 benchmark spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

ℹ️ 1 benchmark has no flame graph

The flamegraph could not be generated because too much time was spent in runtime internals, making the visualization not useful for performance analysis.

⚡ 1 improved benchmark
✅ 7 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ ⚠️ medication-entities: medicationEntityMatchesInText 11.5 ms 4.6 ms ×2.5

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/site-instant-results-xk2j9i (dcb387e) with main (a754fb8)

Open in CodSpeed

The zero-delay first request no longer fires as an anonymous call while
the session is still loading; the resolved request still goes at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL
gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 12, 2026

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

The Also matches strip now mounts only once its lookup answers, so its
header could land after the main rows settled and differ between runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL
gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 12, 2026

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

Copy link
Copy Markdown
Owner Author

Full browser suite on this draft (run 38260466446): Chromium, Firefox, desktop Safari and iPad all passed.

The only failures are iPhone Safari jobs (mobile-webkit 5/5, mobile-pwa-standalone 3/5, 4/5, 5/5), and each failing test is one that also fails on unrelated PRs today and on main's iPhone backlog (ledger #9Z197J): ui-on-call-boards.spec.ts:558 (Admin Help anchor), ui-phone-scroll-assessments.spec.ts:19, ui-dictionary.spec.ts:131, ui-accessibility.spec.ts:295. None of them touches the search results surface this PR changes, so they are not this PR's. Marking ready.


Generated by Claude Code

@BigSimmo
BigSimmo marked this pull request as ready for review October 12, 2026 15:34
@codeant-ai

codeant-ai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed dcb387e Oct 12, 2026 · 21:04 21:05
✅ Incremental review completed fdd40d5 Oct 12, 2026 · 19:14 19:15
✅ Incremental review completed e466399 Oct 12, 2026 · 16:49 16:50
✅ Reviewed your PR d6027c8 Oct 12, 2026 · 15:34 15:41

@codeant-ai

codeant-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@greptile-apps greptile-apps 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.

BigSimmo has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T15:39:38.036889Z d6027c8 Draft marked ready
ℹ️ About Codex in GitHub

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

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

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

@gitar-bot
gitar-bot Bot enabled auto-merge (squash) October 12, 2026 15:35
@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Oct 12, 2026
@claude

claude Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Make clinical search loading feel faster

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Show accessible result-shaped placeholders while Medication and Documents load.
• Keep Also matches hidden until related results are available.
• Send the first medication request immediately while preserving later typing and sign-in delays.
• Warm results code and the medication catalogue from mode home screens.
Diagram

graph TD
  Home["Mode Home"] --> Warmup["Results Warm-up"] --> Catalogue[("Medication API")]
  Warmup --> Med["Medication Results"] --> Placeholder["Placeholder Rows"]
  Warmup --> Docs["Document Results"] --> Placeholder
  Med --> CatalogHook["Catalogue Hook"] --> Catalogue
  Med --> Also["Also Matches"]
Loading
High-Level Assessment

Keep the presentation-focused approach: shared placeholders, idle-time preloading, and a targeted first-request timing change improve perceived speed without changing retrieval or result ordering. A broader search-pipeline rewrite is unnecessary for this goal.

Files changed (13) +405 / -107

Enhancement (6) +196 / -44
answer-status.tsxStart results warm-up from mode homes +4/-0

Start results warm-up from mode homes

• Calls the warm-up hook from SharedHomeEmptyState so supported modes can prepare results resources before submission.

src/components/clinical-dashboard/answer-status.tsx

document-search-results.tsxShow document result placeholders +5/-11

Show document result placeholders

• Replaces the loading panel with four placeholder rows and marks the document workspace busy while results load.

src/components/clinical-dashboard/document-search-results.tsx

medication-prescribing-workspace.tsxShow medication placeholders before first results +13/-5

Show medication placeholders before first results

• Displays placeholder rows and sets aria-busy during initial catalogue loading. Keeps Also matches mounted for its lookup but hidden until the main loading state ends.

src/components/clinical-dashboard/medication-prescribing-workspace.tsx

result-rows-placeholder.tsxAdd shared accessible result-row placeholder +54/-0

Add shared accessible result-row placeholder

• Introduces configurable skeleton rows shaped like result cards, with one named status region and decorative rows hidden from assistive technology.

src/components/clinical-dashboard/result-rows-placeholder.tsx

use-medication-catalog.tsSkip debounce for the first medication request +42/-28

Skip debounce for the first medication request

• Sends a newly mounted catalogue hook's first request without the typing delay. Subsequent requests remain debounced, and the delay remains in place while sign-in resolves.

src/components/clinical-dashboard/use-medication-catalog.ts

use-mode-results-warmup.tsAdd idle-time results warm-up +78/-0

Add idle-time results warm-up

• Preloads Medication and Documents results chunks and makes a query-free Medication catalogue request. Runs each supported mode at most once per page load and ignores warm-up failures.

src/components/clinical-dashboard/use-mode-results-warmup.ts

Bug fix (1) +11 / -42
universal-search-also-matches.tsxRemove pending state from Also matches +11/-42

Remove pending state from Also matches

• Removes its loading skeletons and Searching header; the strip renders only when it has current-query groups to show.

src/components/clinical-dashboard/universal-search-also-matches.tsx

Tests (4) +194 / -17
audit-navigation-auth-regressions.test.tsUpdate Also matches regression assertions +11/-5

Update Also matches regression assertions

• Adjusts source assertions to require the new match-only rendering gate and absence of the Searching header.

tests/audit-navigation-auth-regressions.test.ts

instant-results.dom.test.tsxTest request timing, warm-up, and placeholders +142/-0

Test request timing, warm-up, and placeholders

• Covers first and subsequent medication requests, sign-in timing, per-mode warm-up behavior, and placeholder accessibility.

tests/instant-results.dom.test.tsx

medication-prescribing-workspace.dom.test.tsxTest initial medication loading state +34/-12

Test initial medication loading state

• Adds no-data loading coverage and verifies placeholder and aria-busy states before and after initial results.

tests/medication-prescribing-workspace.dom.test.tsx

ui-style-contract.spec.tsStabilize style audit after cross-mode lookup +7/-0

Stabilize style audit after cross-mode lookup

• Waits for the universal-search response before enumerating rendered controls, accounting for the later appearance of Also matches.

tests/ui-style-contract.spec.ts

Documentation (1) +2 / -2
COMPONENTS.mdRefresh component usage counts +2/-2

Refresh component usage counts

• Updates generated LoadingPanel and Skeleton usage counts to reflect the new loading presentation.

docs/design-system/COMPONENTS.md

Other (1) +2 / -2
adoption-manifest.jsonRefresh design-system adoption inventory +2/-2

Refresh design-system adoption inventory

• Moves Document Search Results out of the LoadingPanel inventory and adds the new placeholder component to the Skeleton inventory.

docs/design-system/adoption-manifest.json

@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo Fixer

No findings are available for this PR yet. Findings appear here once Qodo has reviewed the PR.

@qodo-code-review

qodo-code-review Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

No findings for this group


Remediation recommended

1. Record searches appear idle to readers 🟠 Medium 🐞 Bug ≡ Correctness
Description
The new aria-busy value in DocumentSearchResultsPanelImpl follows only the document request's
loading flag, not the separate registry recordStatus. Services and forms searches set loading
to false while their registry request can still be loading, so the shared results workspace reports
that it is not busy as record results arrive.
Code

src/components/clinical-dashboard/document-search-results.tsx[1456]

+    <div data-testid="document-search-workspace" aria-busy={loading} className="w-full space-y-2.5 sm:space-y-3">
Relevance

●●● Strong

Busy state omits an independently loading registry request, a concrete accessibility correctness
gap.

PR-#2290
PR-#2313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The caller passes document loading and registry status separately. The services/forms search path
clears document loading, while this component already recognizes registry loading for its header and
empty-state logic but omits it from the newly added busy attribute.

src/components/ClinicalDashboard.tsx[1715-1724]
src/components/ClinicalDashboard.tsx[3453-3463]
src/components/clinical-dashboard/document-search-results.tsx[1000-1005]
src/components/clinical-dashboard/document-search-results.tsx[1455-1479]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shared results workspace reports a non-busy accessibility state during services and forms registry searches.
## Fix Focus Areas
- src/components/clinical-dashboard/document-search-results.tsx[1453-1479]
- src/components/clinical-dashboard/document-search-results.tsx[1000-1005]
## Recommended Fix
Set the workspace busy while either document loading or an initial services/forms registry search is running; clear it when the corresponding results settle.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Informational

No findings for this group

Grey Divider


Resolved findings

1. Screen readers miss loading updates ✓ Resolved
Description
DocumentSearchResultsPanelImpl and MedicationResults set aria-busy on containers that also
hold their new role="status" loading placeholders. While a request is pending, assistive
technology may defer the status announcement until the busy flag clears, by which point the
placeholder has been removed; the document container can also contain already-rendered record
matches during that wait.
Code

src/components/clinical-dashboard/document-search-results.tsx[1456]

+    <div data-testid="document-search-workspace" aria-busy={loading} className="w-full space-y-2.5 sm:space-y-3">
Relevance

●● Moderate

ARIA busy/status interaction is plausible, but historical evidence supports accessibility fixes
without this exact nesting pattern.

PR-#2362
PR-#2479

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The accessibility-review skill requires screen-reader descriptions for interface updates. Both newly
busy parent regions contain the placeholder's only loading status, and the document parent also
contains record matches rendered independently of its loading branch.

src/components/clinical-dashboard/document-search-results.tsx[1455-1457]
src/components/clinical-dashboard/document-search-results.tsx[1531-1543]
src/components/clinical-dashboard/medication-prescribing-workspace.tsx[788-793]
src/components/clinical-dashboard/result-rows-placeholder.tsx[32-33]
Skill: accessibility-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new loading announcements sit inside regions marked `aria-busy`, so screen readers may not announce them before they disappear. The document region can also contain record matches that have already rendered.

## Fix Focus Areas
- src/components/clinical-dashboard/document-search-results.tsx[1453-1457]
- src/components/clinical-dashboard/medication-prescribing-workspace.tsx[788-793]
- src/components/clinical-dashboard/result-rows-placeholder.tsx[32-33]

## Recommended Fix
Keep the loading status outside the busy subtree in both workspaces, and scope `aria-busy` to the results still awaiting replacement rather than the entire document workspace. Add a focused accessibility test for the parent-and-status arrangement.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Screen readers miss cached match counts ✓ Resolved
Description
The live status region is rendered only after the viewport gate has passed, so a cached lookup can
insert its completed count into a newly mounted role="status" node instead of changing an
already-mounted empty node. When a clinician revisits a query in the shared cache, the count can
therefore be visually available but not announced, and the same issue affects any surface reusing
that cached result.
Code

src/components/clinical-dashboard/universal-search-also-matches.tsx[R189-192]

+  const liveStatus = (
+    <span className="sr-only" role="status">
+      {stripShown ? panelStatus : ""}
+    </span>
Relevance

●●● Strong

Recent accepted accessibility precedents fix live-reader lifecycle; cached synchronous results
create exactly this missed announcement.

PR-#3466
PR-#2362

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The component returns before rendering liveStatus while viewportReady is false, then renders the
status text only when stripShown is true. The universal-search hook returns cached entries
synchronously with loading: false, so revisiting a cached query can mount the status node with its
final count rather than updating an existing empty live region.

src/components/clinical-dashboard/universal-search-also-matches.tsx[171-192]
src/components/clinical-dashboard/use-universal-search.ts[290-319]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The live status region is not mounted while `viewportReady` is false, but cached universal-search results are returned synchronously as complete. The first render after the viewport becomes ready can therefore mount `role="status"` with the final count already present, which may not produce a screen-reader announcement.

## Fix Focus Areas
- src/components/clinical-dashboard/universal-search-also-matches.tsx[171-192]
- src/components/clinical-dashboard/use-universal-search.ts[290-319]

## Recommended Fix
Render the empty live-status node before the viewport-ready early return, or otherwise ensure it is mounted before cached results can populate `panelStatus`. Preserve the existing behavior of keeping the visible strip hidden until completed matches are available, and add a regression test covering a cached lookup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Related-mode counts appear before search ends ✓ Resolved
Description
UniversalSearchAlsoMatches now renders as soon as currentGroups contains one streamed group and
labels its current matchCount without a pending indicator. When other groups are still arriving,
readers see a final-looking count that increases as more mode cards arrive.
Code

src/components/clinical-dashboard/universal-search-also-matches.tsx[R157-160]

+  // No pending arm. The strip is not drawn at all until this query's matches are in (see the
+  // gate below), so a "Searching…" here could only ever show during a background refetch,
+  // where the count already on screen is the honest thing to keep saying.
+  const headerMeta = matchCount > 0 ? matchCountLabel(matchCount) : "No other matches";
Relevance

●●● Strong

Partial streamed groups produce unstable final-looking counts, contradicting the PR’s stated
quiet-loading intent.

PR-#2320
PR-#2313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Each stream callback publishes groups with complete: false before the final response. The
component accepts those groups for the current query and its changed header reports their current
count instead of the former pending label.

src/components/clinical-dashboard/use-universal-search.ts[212-267]
src/components/clinical-dashboard/universal-search-also-matches.tsx[129-160]
src/components/clinical-dashboard/universal-search-also-matches.tsx[163-173]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The related-modes disclosure presents a partial streamed group count as a settled count while the lookup is still running.
## Fix Focus Areas
- src/components/clinical-dashboard/universal-search-also-matches.tsx[143-173]
- src/components/clinical-dashboard/use-universal-search.ts[212-267]
## Recommended Fix
Keep the disclosure hidden during an incomplete initial stream; retain settled groups and their count during a background refetch if that behavior is desired.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View resolved (1)
4. Medication warm-ups can block searches ✓ Resolved
Description
warmModeResults fetches the medication catalogue whenever the prescribing home goes idle,
consuming the same registry rate-limit quota as an actual search. On a shared anonymous IP, enough
home-page loads or tabs within a minute can exhaust the 60-request allowance before a clinician
submits a medication search.
Code

src/components/clinical-dashboard/use-mode-results-warmup.ts[R35-36]

+    if (typeof fetch === "function") {
+      void fetch(medicationCatalogueWarmupUrl, { priority: "low" } as RequestInit).catch(() => undefined);
Relevance

●● Moderate

Warm-up quota consumption is plausible, but once-per-page gating and low traffic make production
impact uncertain.

PR-#3373
PR-#3464

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new idle fetch calls the medication endpoint without a query. That endpoint consumes the
registry limit before serving a response; anonymous callers share an IP-derived subject with a limit
of 60 requests per minute, while the warm-up guard lasts only for one page load.

src/components/clinical-dashboard/use-mode-results-warmup.ts[32-37]
src/components/clinical-dashboard/use-mode-results-warmup.ts[46-54]
src/app/api/medications/route.ts[203-216]
src/lib/api-rate-limit.ts[132-143]
src/lib/public-api-access.ts[49-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The idle medication fetch consumes the shared registry request allowance, so background home-page visits can cause later searches to be rate-limited.
## Fix Focus Areas
- src/components/clinical-dashboard/use-mode-results-warmup.ts[32-37]
- src/app/api/medications/route.ts[203-216]
## Recommended Fix
Warm the JavaScript chunk without making an automatic request against the rate-limited catalogue endpoint, or provide a separately bounded warm-up path that cannot consume the search allowance.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 9 rules
✅ Skills: 5 invoked
  accessibility-review
  testing-review
  ui
  review
  frontend-architecture-review
✅ Cross-repo context — repo relationships
✅ REVIEW.md
Review mode: Auto: 🚀 Fast: Localized UI visibility and accessibility gating change; low blast radius.

Grey Divider

Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit dcb387e

Results up to commit 734b394 ⚖️ Balanced


🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required

No findings for this group


Remediation recommended
1. Record searches appear idle to readers 🟠 Medium 🐞 Bug ≡ Correctness
Description
The new aria-busy value in DocumentSearchResultsPanelImpl follows only the document request's
loading flag, not the separate registry recordStatus. Services and forms searches set loading
to false while their registry request can still be loading, so the shared results workspace reports
that it is not busy as record results arrive.
Code

src/components/clinical-dashboard/document-search-results.tsx[1456]

+    <div data-testid="document-search-workspace" aria-busy={loading} className="w-full space-y-2.5 sm:space-y-3">
Relevance

●●● Strong

Busy state omits an independently loading registry request, a concrete accessibility correctness
gap.

PR-#2290
PR-#2313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The caller passes document loading and registry status separately. The services/forms search path
clears document loading, while this component already recognizes registry loading for its header and
empty-state logic but omits it from the newly added busy attribute.

src/components/ClinicalDashboard.tsx[1715-1724]
src/components/ClinicalDashboard.tsx[3453-3463]
src/components/clinical-dashboard/document-search-results.tsx[1000-1005]
src/components/clinical-dashboard/document-search-results.tsx[1455-1479]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shared results workspace reports a non-busy accessibility state during services and forms registry searches.
## Fix Focus Areas
- src/components/clinical-dashboard/document-search-results.tsx[1453-1479]
- src/components/clinical-dashboard/document-search-results.tsx[1000-1005]
## Recommended Fix
Set the workspace busy while either document loading or an initial services/forms registry search is running; clear it when the corresponding results settle.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Informational

No findings for this group


Resolved findings

1. Screen readers miss loading updates ✓ Resolved
Description
DocumentSearchResultsPanelImpl and MedicationResults set aria-busy on containers that also
hold their new role="status" loading placeholders. While a request is pending, assistive
technology may defer the status announcement until the busy flag clears, by which point the
placeholder has been removed; the document container can also contain already-rendered record
matches during that wait.
Code

src/components/clinical-dashboard/document-search-results.tsx[1456]

+    <div data-testid="document-search-workspace" aria-busy={loading} className="w-full space-y-2.5 sm:space-y-3">
Relevance

●● Moderate

ARIA busy/status interaction is plausible, but historical evidence supports accessibility fixes
without this exact nesting pattern.

PR-#2362
PR-#2479

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The accessibility-review skill requires screen-reader descriptions for interface updates. Both newly
busy parent regions contain the placeholder's only loading status, and the document parent also
contains record matches rendered independently of its loading branch.

src/components/clinical-dashboard/document-search-results.tsx[1455-1457]
src/components/clinical-dashboard/document-search-results.tsx[1531-1543]
src/components/clinical-dashboard/medication-prescribing-workspace.tsx[788-793]
src/components/clinical-dashboard/result-rows-placeholder.tsx[32-33]
Skill: accessibility-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new loading announcements sit inside regions marked `aria-busy`, so screen readers may not announce them before they disappear. The document region can also contain record matches that have already rendered.

## Fix Focus Areas
- src/components/clinical-dashboard/document-search-results.tsx[1453-1457]
- src/components/clinical-dashboard/medication-prescribing-workspace.tsx[788-793]
- src/components/clinical-dashboard/result-rows-placeholder.tsx[32-33]

## Recommended Fix
Keep the loading status outside the busy subtree in both workspaces, and scope `aria-busy` to the results still awaiting replacement rather than the entire document workspace. Add a focused accessibility test for the parent-and-status arrangement.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Related-mode counts appear before search ends ✓ Resolved
Description
UniversalSearchAlsoMatches now renders as soon as currentGroups contains one streamed group and
labels its current matchCount without a pending indicator. When other groups are still arriving,
readers see a final-looking count that increases as more mode cards arrive.
Code

src/components/clinical-dashboard/universal-search-also-matches.tsx[R157-160]

+  // No pending arm. The strip is not drawn at all until this query's matches are in (see the
+  // gate below), so a "Searching…" here could only ever show during a background refetch,
+  // where the count already on screen is the honest thing to keep saying.
+  const headerMeta = matchCount > 0 ? matchCountLabel(matchCount) : "No other matches";
Relevance

●●● Strong

Partial streamed groups produce unstable final-looking counts, contradicting the PR’s stated
quiet-loading intent.

PR-#2320
PR-#2313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Each stream callback publishes groups with complete: false before the final response. The
component accepts those groups for the current query and its changed header reports their current
count instead of the former pending label.

src/components/clinical-dashboard/use-universal-search.ts[212-267]
src/components/clinical-dashboard/universal-search-also-matches.tsx[129-160]
src/components/clinical-dashboard/universal-search-also-matches.tsx[163-173]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The related-modes disclosure presents a partial streamed group count as a settled count while the lookup is still running.
## Fix Focus Areas
- src/components/clinical-dashboard/universal-search-also-matches.tsx[143-173]
- src/components/clinical-dashboard/use-universal-search.ts[212-267]
## Recommended Fix
Keep the disclosure hidden during an incomplete initial stream; retain settled groups and their count during a background refetch if that behavior is desired.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Medication warm-ups can block searches ✓ Resolved
Description
warmModeResults fetches the medication catalogue whenever the prescribing home goes idle,
consuming the same registry rate-limit quota as an actual search. On a shared anonymous IP, enough
home-page loads or tabs within a minute can exhaust the 60-request allowance before a clinician
submits a medication search.
Code

src/components/clinical-dashboard/use-mode-results-warmup.ts[R35-36]

+    if (typeof fetch === "function") {
+      void fetch(medicationCatalogueWarmupUrl, { priority: "low" } as RequestInit).catch(() => undefined);
Relevance

●● Moderate

Warm-up quota consumption is plausible, but once-per-page gating and low traffic make production
impact uncertain.

PR-#3373
PR-#3464

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new idle fetch calls the medication endpoint without a query. That endpoint consumes the
registry limit before serving a response; anonymous callers share an IP-derived subject with a limit
of 60 requests per minute, while the warm-up guard lasts only for one page load.

src/components/clinical-dashboard/use-mode-results-warmup.ts[32-37]
src/components/clinical-dashboard/use-mode-results-warmup.ts[46-54]
src/app/api/medications/route.ts[203-216]
src/lib/api-rate-limit.ts[132-143]
src/lib/public-api-access.ts[49-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The idle medication fetch consumes the shared registry request allowance, so background home-page visits can cause later searches to be rate-limited.
## Fix Focus Areas
- src/components/clinical-dashboard/use-mode-results-warmup.ts[32-37]
- src/app/api/medications/route.ts[203-216]
## Recommended Fix
Warm the JavaScript chunk without making an automatic request against the rate-limited catalogue endpoint, or provide a separately bounded warm-up path that cannot consume the search allowance.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 67bf944 🚀 Fast


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required

No findings for this group


Remediation recommended

No findings for this group


Informational

No findings for this group


Resolved findings

1. Screen readers miss cached match counts ✓ Resolved
Description
The live status region is rendered only after the viewport gate has passed, so a cached lookup can
insert its completed count into a newly mounted role="status" node instead of changing an
already-mounted empty node. When a clinician revisits a query in the shared cache, the count can
therefore be visually available but not announced, and the same issue affects any surface reusing
that cached result.
Code

src/components/clinical-dashboard/universal-search-also-matches.tsx[R189-192]

+  const liveStatus = (
+    <span className="sr-only" role="status">
+      {stripShown ? panelStatus : ""}
+    </span>
Relevance

●●● Strong

Recent accepted accessibility precedents fix live-reader lifecycle; cached synchronous results
create exactly this missed announcement.

PR-#3466
PR-#2362

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The component returns before rendering liveStatus while viewportReady is false, then renders the
status text only when stripShown is true. The universal-search hook returns cached entries
synchronously with loading: false, so revisiting a cached query can mount the status node with its
final count rather than updating an existing empty live region.

src/components/clinical-dashboard/universal-search-also-matches.tsx[171-192]
src/components/clinical-dashboard/use-universal-search.ts[290-319]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The live status region is not mounted while `viewportReady` is false, but cached universal-search results are returned synchronously as complete. The first render after the viewport becomes ready can therefore mount `role="status"` with the final count already present, which may not produce a screen-reader announcement.

## Fix Focus Areas
- src/components/clinical-dashboard/universal-search-also-matches.tsx[171-192]
- src/components/clinical-dashboard/use-universal-search.ts[290-319]

## Recommended Fix
Render the empty live-status node before the viewport-ready early return, or otherwise ensure it is mounted before cached results can populate `panelStatus`. Preserve the existing behavior of keeping the visible strip hidden until completed matches are available, and add a regression test covering a cached lookup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Plan monthly usage limit reached ↗
Resets Oct 16

Get more usage

…t the pending arm

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit e466399

…p so the count is announced

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL
@gitar-bot
gitar-bot Bot enabled auto-merge (squash) October 12, 2026 17:08
gitar-bot[bot]
gitar-bot Bot previously approved these changes Oct 12, 2026

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 67bf944

…o a cached count is still announced

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mws6schjKQbpCQqQzPMKSL

@gitar-bot gitar-bot 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.

Gitar has auto-approved this PR (configure)

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit f5ca834

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Oct 12, 2026
@qodo-code-review

qodo-code-review Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

No code changes since the last review — review skipped

Qodo Logo

@gitar-bot

gitar-bot Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor
Code Review ✅ Approved 2 closed / 2 findings

🟡 Medium risk · Changing clinical search request timing and gating related matches could leave results delayed or incomplete-looking if asynchronous states diverge.

Adds result-shaped loading placeholders for Medication and Documents, keeps "Also matches" hidden until cross-mode lookups finish, and preloads search code on home-page open without any data request. Resolved the first-request auth race and restored the screen-reader announcement for the Also matches count. No open issues found.

✅ 2 closed
✅ Bug: Zero-delay first request fires before auth resolves, then re-debounces

📄 src/components/clinical-dashboard/use-medication-catalog.ts:236-241 📄 src/components/clinical-dashboard/use-medication-catalog.ts:278-281 📄 src/components/clinical-dashboard/use-medication-catalog.ts:306
The effect is not gated on authStatus, and useAuthSession has a "loading" state. With the 250 ms debounce, a sign-in that resolved quickly cancelled the anonymous timer before it fired. With the new 0 ms first timer, opening a medication search URL directly while auth is still loading sends /api/medications?q=… with no Authorization header straight away. That forces a full server catalogue read (the ~3 MB list) whose result is either aborted or, if it lands first, briefly shows the public fixture catalogue to a signed-in owner (the hook's own comment at line 227 describes this). Because requestSentRef is now true, the real authenticated request that follows the identity change then waits the full 250 ms, so signed-in deep-link loads can end up slower than before. Fix: don't schedule (or don't treat as sent) a request while authStatus === "loading", or only flip requestSentRef after a request made with resolved auth.

✅ Bug: Also-matches live region now mounts pre-filled, so the count may not be announced

📄 src/components/clinical-dashboard/universal-search-also-matches.tsx:152-156 📄 src/components/clinical-dashboard/universal-search-also-matches.tsx:183
The new gate if (currentGroups.length === 0 || searchPending) return null; stops the whole <section> from rendering while the lookup is running. That section holds the role="status" sr-only node. Before this change, the node was mounted showing "Searching other modes" and then its text changed to the count, which is the update that live regions announce. Now the node is added to the page already holding the final count. Several screen reader and browser combinations do not announce a live region's starting content, so the "N related modes also match" message the comment block exists to protect can go unheard, and the searchPending arm of panelStatus can never render. Fix: keep the status node mounted (empty or holding the pending text) whenever submissionActive and the query is at least 2 characters, outside the visual gate, and gate only the visible header and tray.

Verification

Rules No rules evaluated

Functional validation Not enabled

Set up

Auto-approval Approved

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔴 Large blast radius size:XL This PR changes 500-999 lines, ignoring generated files 🕐 40+ Minutes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants