diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md
index d806eff1c..4808a2600 100644
--- a/docs/branch-review-ledger.md
+++ b/docs/branch-review-ledger.md
@@ -22,6 +22,7 @@ This file is append-only. Never rewrite or delete an existing review record; app
| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks |
| ---------- | -------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 2026-07-24 | `codex/query-ribbon-search-headings` | `16ce57d9615708528e7924b41837210a24414722` + reviewed ledger diff | Universal Query Ribbon implementation and responsive search-heading review | APPROVE after two scoped polish fixes. The shared result header now presents query, progress/result state, scopes, and real sort/view/save controls consistently across Services, Forms, Favourites, Differentials, and Medication/Prescribing search surfaces; the mockup remains available at `/mockups/search-heading`. The review restored the compact `Sort` label at ordinary phone widths and prevents stale/unknown scope IDs from producing an empty utility strip. No P0-P2 finding remains. Retrieval, ranking, source selection, and clinical output behavior are unchanged. Highest residual risk is unexercised browser combinations outside the focused cross-route responsive matrix. | Focused header DOM 4/4; focused ESLint; `git diff --check`; live 1280px route QA with zero overflow and visible keyboard focus; production Playwright build compiled, TypeScript passed, 1,678 pages generated, and focused Chromium responsive/search/prescribing matrix passed 16/16. `verify:pr-local` passed runtime, Prettier, full ESLint, and TypeScript; its complete unit run stopped only when the unrelated load-sensitive `reconciliation-preflight` case exceeded 30 seconds, then that file passed 5/5 in isolation. No OpenAI, Supabase, Railway, deployment, production-data, or clinical provider workflow ran. |
| 2026-07-24 | `main` | `037b4808100946f96aee28b8ff51939ae2986851` | Comprehensive repository review (architecture, security/privacy, API/DB, RAG/clinical, UX/a11y, performance/CI/ops) | Changes requested. No P0. Two confirmed P1 clinical-grounding defects: (1) numeric verification skips truncated claims after the 24-claim cap when earlier claims contain numbers; (2) unsupported prescriptive treatment recommendations remain grounded as routine/partial. Multiple P2 privacy/ops findings: signed-URL cache survives sign-out; upload/enrichment/search telemetry can retain raw filenames; negated urgency still labelled Red flag; mixed `partial_invalid_model_citation_ids` evade the invalid-ID gate; readiness probe lacks an explicit dependency timeout; Railway watch patterns miss transitive build controllers. Known tracked residuals reconfirmed: #052 enrichment/reindex overlap, #059 credential containment (operator), #007 Tools route dual entry. Highest residual risk remains live answer-quality/clinical grounding plus operator credential/#053 legal gates. No source fixes applied. | Offline: runtime, migration-role, function-grants (30), owner-scope (40 routes), GitHub Actions pins, PR-policy self-test, gate/CI-scope/Codex autofix guards, lint, typecheck, sitemap, knip, knip architecture budgets, env-parity names-only, docs index/scripts, production-readiness:ci, offline RAG 36 fixtures + 21 suites/308 tests, focused clinical/RAG contract suite 122/122, Chromium accessibility 12/12 and `verify:ui` 267/267 after identity-confirmed `npm run ensure`. Targeted offline red proofs for both P1s and the citation/safety/signed-URL P2s. Provider-gated skipped: live drift, supabase-project (non-dry), migration-history, live RAG/OpenAI evals, hosted CI, Railway mutations. |
| 2026-07-24 | `remediate-audit-system-issues` | `ae54de2b10f1c586d90c62fc3e50654dd8e917a1` + fixes | Audit remediation verification and merge readiness review | READY. Fixed the P1 (Unsafe automation) by restoring the WMI process name filter while expanding it to include common node wrappers (`node|npm|npx|tsx|vitest|playwright|bun`). Fixed the P3 (maintainability friction) by adding `rimraf` to `devDependencies`, ensuring offline availability in CI. No high-confidence P0-P2 defects remain. | Static review of diff against origin/main. Fixed issues locally and re-verified. No OpenAI, Supabase, or live provider command ran. |
| 2026-07-24 | `cursor/docs-reliability-review-c38b` | `af5d44abf031581d256006b51a1be98563d441d5` | Documentation reliability review vs repo state (setup, env, ops runbooks, testing safety) | Fixed P1/P2 doc drift: worker region Sydney→Railway Singapore; DR golden gate 23/23→36/36; Railway health `/api/health/ready`; auth checklist aligned to magic-link+OAuth UI; staging identity vars in `.env.example`; provider-approval boundary on testing/readiness docs; mode count 11→13. No P0. Residual: historical `23/23` mentions in point-in-time/archive docs left alone. | `npm run docs:check-links`; `npm run docs:check-index`; `git diff --check`. No provider/OpenAI/Supabase/Railway mutation. |
@@ -738,5 +739,11 @@ This file is append-only. Never rewrite or delete an existing review record; app
| 2026-07-24 | remediate-audit-system-issues (PR #1160) | 992ebefa296d6894d5448c1381f1b0b95580e529 | Run PR sweep: CI fix + threads + drift | supersedes prior #1160 row: final HEAD after prettier site-map; merge origin/main clean; stale sitemap fixed; no threads | vitest site-map pass; sitemap:check pass; no provider-backed checks run |
| 2026-07-24 | remediate-audit-system-issues (PR #1160) | 04201a87cc7ad7dd1477d17cd2b96544b7379789 | Run PR sweep: CI fix + threads + drift | re-merge origin/main after CONFLICTING relapse; resolved scripts/run-eval-safe.mjs (kept main taskkill /T /F + validPids); sitemap Prettier format() retained from fe0588b86; no unresolved threads; no conflict markers | merge + conflict resolve only; no provider-backed checks run |
| 2026-07-24 | codex/fix-next.js-startup-failure-and-verify-pages (PR #1149) | 8ddddbab2a29a94b3f993cbd114889f72c95f4f1 | Run PR sweep: CI fix + threads + drift | Before: behind main. After: merged origin/main cleanly (no conflicts). Unresolved review threads left as non-P0/P1. CI not waited. | merge origin/main only; thread scan read-only; no provider-backed checks run |
+| 2026-07-24 | codex/query-ribbon-search-headings (PR #1166) | cbb493c2b51dd7120df39cfbcd5f19b73f882d43 | Run PR babysit: CI/threads/drift | Before: not behind at start, then main advanced (8 behind); required checks green except Production UI still pending on prior head; 0 unresolved review threads; no Bugbot findings. After: merged origin/main cleanly (docs/branch-review-ledger.md + src/app/layout.tsx auto-merge). No code fix needed. | merge origin/main only; no provider-backed checks run. |
+| 2026-07-24 | codex/query-ribbon-search-headings (PR #1166) | ad3d38c62a19f5fa2a7e8356021795937c5b0f66 | Run PR babysit: CI/threads/drift | Supersedes prior #1166 row in this push: post-merge+ledger HEAD after syncing origin/main (clean auto-merge). 0 threads; required CI re-running. | merge origin/main; ledger append; no provider-backed checks run. |
+| 2026-07-24 | `codex/query-ribbon-search-headings` (PR #1166) | `37cfa5553ccb784ee5e9f47ded1ad69914c053ed` + reviewed correction diff | Correction: universal Query Ribbon implementation and responsive search-heading review | SUPERSEDES the earlier row that named non-existent pre-amend SHA `16ce57d9615708528e7924b41837210a24414722`. This resolvable reviewed tip contains functional commit `0b67944b0d2973d612833422fb4074aeacdb6c8c`, current-main syncs, and the append-only ledger correction. The prior APPROVE outcome and residual-risk statement are unchanged; no P0-P2 finding remains. | Query Ribbon DOM 4/4 after each main sync; exact-head hosted policy, static checks, unit coverage, build, advisory UI, Production UI, safety/config, Semgrep, Gitleaks, GitGuardian, and `PR required` passed before the final docs-only correction. No OpenAI, Supabase, Railway, deployment, production-data, or clinical provider workflow ran. |
+| 2026-07-24 | codex/query-ribbon-search-headings (PR #1166) | 37cfa5553ccb784ee5e9f47ded1ad69914c053ed | Run PR babysit: CI/threads/drift | Final HEAD after merge origin/main + ledger bookkeeping. 0 unresolved threads; no Bugbot actionable findings; required CI re-running on this SHA. | merge origin/main; no provider-backed checks run. |
+| 2026-07-24 | codex/query-ribbon-search-headings (PR #1166) | 9eac2e252bcc5c548aa919b69faeb79b9ff7d2cf | Run PR babysit: CI/threads/drift | Merged origin/main; Codex ledger-SHA P2 dispositioned+resolved (append-only supersede already in 9eac2e252). 0 unresolved threads. | merge origin/main; thread resolve only; no provider-backed checks run. |
| 2026-07-24 | remediate-audit-system-issues (PR #1160) | bdf530fc8c6faaa4491c510396b47872fc39bf25 | Run PR sweep: CI fix + threads + drift | second re-merge after main moved to 2e68888f3 during first push; clean ort merge (ledger + layout.tsx); taskkill /T retained; sitemap prettier retained | merge only; no provider-backed checks run |
+| 2026-07-24 | codex/query-ribbon-search-headings (PR #1166) | c94e89f392f578c4b2c749195dd485b74959074c | Run PR re-sync sweep | Before: CONFLICTING. After: merged origin/main clean. CI re-running. | merge origin/main and/or conflict re-check only; no provider-backed checks run |
| 2026-07-24 | cursor/comprehensive-repo-review-ledger-d9a1 (PR #1150) | 345c02cdbaefb13aeb951a14674aedfe4648a50e | Run PR re-sync sweep | Before: CONFLICTING. After: merged origin/main clean. | merge origin/main and/or conflict re-check only; no provider-backed checks run |
diff --git a/docs/site-map.md b/docs/site-map.md
index 21ca3e0ce..a9926e307 100644
--- a/docs/site-map.md
+++ b/docs/site-map.md
@@ -1004,6 +1004,7 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check`
- `/mockups/medication-prescribing` - Route discovered from app directory Source: `src/app/mockups/medication-prescribing/page.tsx`.
- `/mockups/mode-dropdown` - Route discovered from app directory Source: `src/app/mockups/mode-dropdown/page.tsx`.
- `/mockups/recent-searches-bottom` - Route discovered from app directory Source: `src/app/mockups/recent-searches-bottom/page.tsx`.
+- `/mockups/search-heading` - Route discovered from app directory Source: `src/app/mockups/search-heading/page.tsx`.
- `/mockups/settings-search-clinical` - Route discovered from app directory Source: `src/app/mockups/settings-search-clinical/page.tsx`.
- `/mockups/settings-search-general` - Route discovered from app directory Source: `src/app/mockups/settings-search-general/page.tsx`.
- `/mockups/settings-search-privacy` - Route discovered from app directory Source: `src/app/mockups/settings-search-privacy/page.tsx`.
diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx
index ed508daad..784bd0ad5 100644
--- a/src/app/mockups/mockups-layout-client.tsx
+++ b/src/app/mockups/mockups-layout-client.tsx
@@ -13,6 +13,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
const isSourceOverlayRedesignMockup = pathname === "/mockups/document-search/source-overlays";
const isStandaloneDocumentFlow = pathname === "/mockups/document-search";
const isUniversalSearchRedesignMockup = pathname === "/mockups/universal-search-redesign";
+ const isSearchHeadingMockup = pathname === "/mockups/search-heading";
// The calculators search page owns its own search input (top on desktop, docked
// at the bottom on phones), so the shared universal composer is suppressed here
// to avoid a second, floating search bar.
@@ -36,7 +37,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) {
!isUniversalSearchRedesignMockup &&
!isCalculatorsSearchPageMockup
}
- chromeVisible={!isSourceOverlayRedesignMockup}
+ chromeVisible={!isSourceOverlayRedesignMockup && !isSearchHeadingMockup}
>
{children}
diff --git a/src/app/mockups/search-heading/page.tsx b/src/app/mockups/search-heading/page.tsx
new file mode 100644
index 000000000..dfaf04b60
--- /dev/null
+++ b/src/app/mockups/search-heading/page.tsx
@@ -0,0 +1,5 @@
+import { SearchHeadingMockupsPage } from "@/components/search-heading-mockups";
+
+export default function SearchHeadingMockupRoute() {
+ return ;
+}
diff --git a/src/components/clinical-dashboard/favourites-command-library-page.tsx b/src/components/clinical-dashboard/favourites-command-library-page.tsx
index c9b8618a2..4e2baf8d0 100644
--- a/src/components/clinical-dashboard/favourites-command-library-page.tsx
+++ b/src/components/clinical-dashboard/favourites-command-library-page.tsx
@@ -1186,9 +1186,7 @@ export function FavouritesCommandLibraryPage({ query = "", demoMode }: { query?:
className="mode-home-composer-slot hidden w-full max-w-3xl [&:not(:empty)]:block"
/>
-
-
-
+
-
- {query}
-
- );
-}
-
function MedicationHome({
loading,
realDataReady,
@@ -466,20 +457,7 @@ function MedicationResults({
return (
-
-
-
-
-
-
Medication search
-
- {resultCount} prescribing matches
-
-
-
-
-
-
+
diff --git a/src/components/clinical-dashboard/search-results-header-band.tsx b/src/components/clinical-dashboard/search-results-header-band.tsx
index 97e9bbe20..83fe52082 100644
--- a/src/components/clinical-dashboard/search-results-header-band.tsx
+++ b/src/components/clinical-dashboard/search-results-header-band.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Bookmark, ChevronsUpDown, LayoutList, Search, Table2, X } from "lucide-react";
+import { Bookmark, CheckCircle2, ChevronsUpDown, LayoutList, LoaderCircle, Search, Table2, X } from "lucide-react";
import { searchCommandSurfaceConfig } from "@/lib/search-command-surface";
import { cn } from "@/components/ui-primitives";
@@ -37,95 +37,151 @@ export function SearchResultsHeaderBand({
const command = useSearchCommand();
const config = searchCommandSurfaceConfig(modeId);
const activeScopes = command?.commandScopes ?? [];
+ const visibleScopes = activeScopes.flatMap((scopeId) => {
+ const scope = config?.scopes.find((entry) => entry.id === scopeId);
+ return scope ? [scope] : [];
+ });
const displayQuery = query.trim() || "All";
+ const statusLabel = loading ? "Searching…" : `${matchCount} ${matchCount === 1 ? "match" : "matches"}`;
+ const hasUtilities = visibleScopes.length > 0 || Boolean(onSortChange || onViewChange || onSaveSearch);
return (
-
-
-
- {displayQuery}
-
-
- {loading ? "Searching…" : `${matchCount} ${matchCount === 1 ? "match" : "matches"}`}
-
- {activeScopes.map((scopeId) => {
- const scope = config?.scopes.find((entry) => entry.id === scopeId);
- if (!scope) return null;
- return (
-
-
{supportsPathwayClaims ? (
= [
+ {
+ id: "signal",
+ number: "01",
+ name: "Signal band",
+ summary: "The clearest all-round hierarchy: query first, live status second, utilities kept quiet.",
+ strengths: ["Fast to scan", "Strong loading state", "Adapts cleanly to phones"],
+ },
+ {
+ id: "ribbon",
+ number: "02",
+ name: "Query ribbon",
+ summary: "The selected direction, refined into a clear query, live status, and honest scope-and-sort toolbar.",
+ strengths: ["Clear hierarchy", "State-aware", "Long-query safe"],
+ recommended: true,
+ },
+ {
+ id: "editorial",
+ number: "03",
+ name: "Results title",
+ summary: "A more confident page heading that makes the search term the visual anchor.",
+ strengths: ["Strongest page identity", "Room for future metadata", "Calm and premium"],
+ },
+];
+
+function SortControl({ compact = false }: { compact?: boolean }) {
+ return (
+
+ );
+}
+
+function Status({
+ state,
+ quiet = false,
+ compactAtNarrow = false,
+}: {
+ state: SearchHeadingState;
+ quiet?: boolean;
+ compactAtNarrow?: boolean;
+}) {
+ const searching = state === "searching";
+
+ return (
+
+ {searching ? (
+
+ ) : (
+
+ )}
+ {searching ? "Searching…" : "12 matches"}
+
+ );
+}
+
+function SignalBand({ state, compact }: { state: SearchHeadingState; compact?: boolean }) {
+ if (compact) {
+ return (
+
+
+
+
+
+
+
+
+
+ Search results
+
+
+ chest pain
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ Search results
+
+
+ chest pain
+
+
+
+
+
+
+
+
+ );
+}
+
+function QueryRibbon({ state, compact }: { state: SearchHeadingState; compact?: boolean }) {
+ const searching = state === "searching";
+
+ if (compact) {
+ return (
+
+
+
+
+
+
+
+
+
+ Query
+
+
+ chest pain
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {searching ? "Searching for" : "Results for"}
+
+
+ chest pain
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function ScopeControl({ compact = false }: { compact?: boolean }) {
+ if (compact) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+function EditorialTitle({ state, compact }: { state: SearchHeadingState; compact?: boolean }) {
+ if (compact) {
+ return (
+
+
+
+ Search results
+
+
+
+
+ “chest pain”
+
+
+
+
+ All sources
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ Search results
+
+
+
+
+
+ “chest pain”
+
+
+
+
+
+ All sources
+
+
+
+
+ );
+}
+
+function SearchHeading({
+ concept,
+ state,
+ compact = false,
+}: {
+ concept: SearchHeadingConcept;
+ state: SearchHeadingState;
+ compact?: boolean;
+}) {
+ if (concept === "signal") return ;
+ if (concept === "ribbon") return ;
+ return ;
+}
+
+function PreviewFrame({
+ label,
+ children,
+ phone = false,
+}: {
+ label: string;
+ children: React.ReactNode;
+ phone?: boolean;
+}) {
+ return (
+
+
+
+ {label}
+
+ {phone ? "390 px" : "1440 px"}
+
+
+
+ {children}
+
+
+ {!phone ? (
+
+ ) : null}
+
+
+
+
+ );
+}
+
+export function SearchHeadingMockupsPage() {
+ const [state, setState] = useState("searching");
+
+ return (
+
+
+
+
+
+
+ Universal search heading
+
+
+ Three polished directions for every search page
+
+
+ Each concept keeps the query, live search status, and result sorting together while preserving a clear
+ reading order on desktop and phone.
+
+
+
+ {(["searching", "results"] as const).map((value) => (
+ setState(value)}
+ className={cn(
+ "inline-flex min-h-9 items-center gap-1.5 rounded-lg px-3 text-xs font-extrabold transition-colors",
+ focusRing,
+ state === value
+ ? "bg-[color:var(--surface)] text-[color:var(--text-heading)] shadow-[var(--shadow-inset)]"
+ : "text-[color:var(--text-muted)] hover:text-[color:var(--text)]",
+ )}
+ >
+ {value === "searching" ? (
+
+ ) : (
+
+ )}
+ {value === "searching" ? "Searching" : "Results ready"}
+
+ ))}
+
+
+
+
+
+
+ {concepts.map((concept) => (
+
+
+
+
+ {concept.number}
+
+
+
+
+ {concept.name}
+
+ {concept.recommended ? (
+
+ Recommended
+
+ ) : null}
+
+
{concept.summary}
+
+
+
+ {concept.strengths.map((strength) => (
+
+ {strength}
+
+ ))}
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/tests/search-results-header-band.dom.test.tsx b/tests/search-results-header-band.dom.test.tsx
new file mode 100644
index 000000000..489569dd0
--- /dev/null
+++ b/tests/search-results-header-band.dom.test.tsx
@@ -0,0 +1,90 @@
+/** @vitest-environment jsdom */
+
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+
+import { SearchCommandProvider } from "@/components/clinical-dashboard/search-command-context";
+import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band";
+
+describe("SearchResultsHeaderBand", () => {
+ it("presents the query and completed count as one labelled results ribbon", () => {
+ const query = "a deliberately long clinical search query";
+
+ render();
+
+ expect(screen.getByRole("region", { name: `Search results for ${query}` })).toHaveAttribute("aria-busy", "false");
+ expect(screen.getByRole("heading", { name: query })).toHaveAttribute("title", query);
+ expect(screen.getByRole("status")).toHaveTextContent("12 matches");
+ });
+
+ it("announces the loading state without exposing a stale result count", () => {
+ render();
+
+ expect(screen.getByRole("region", { name: "Search results for acute confusion" })).toHaveAttribute(
+ "aria-busy",
+ "true",
+ );
+ expect(screen.getByRole("status")).toHaveTextContent("Searching…");
+ expect(screen.queryByText("8 matches")).toBeNull();
+ });
+
+ it("does not render an empty utility strip for a stale scope from another mode", () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.queryByTestId("search-query-ribbon-utilities")).toBeNull();
+ });
+
+ it("keeps scope, sort, view, and save controls wired inside the utility row", async () => {
+ const user = userEvent.setup();
+ const onRemoveScope = vi.fn();
+ const onSortChange = vi.fn();
+ const onViewChange = vi.fn();
+ const onSaveSearch = vi.fn();
+
+ render(
+
+
+ ,
+ );
+
+ await user.click(screen.getByRole("button", { name: "Remove Official only filter" }));
+ await user.selectOptions(screen.getByLabelText("Sort results"), "alpha");
+ await user.click(screen.getByRole("button", { name: "List view" }));
+ await user.click(screen.getByRole("button", { name: "Save search" }));
+
+ expect(onRemoveScope).toHaveBeenCalledWith("official");
+ expect(onSortChange).toHaveBeenCalledWith("alpha");
+ expect(onViewChange).toHaveBeenCalledWith("list");
+ expect(onSaveSearch).toHaveBeenCalledOnce();
+ });
+});
diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts
index b80984204..2ed0fc9bc 100644
--- a/tests/ui-tools.spec.ts
+++ b/tests/ui-tools.spec.ts
@@ -856,14 +856,35 @@ test.describe("Clinical KB tools launcher", () => {
{ name: "desktop", width: 1280, height: 900 },
] as const) {
for (const route of [
- { path: "/services?q=13YARN&focus=1&run=1", modeButton: "Mode Services", compactBottomSearch: true },
- { path: "/services/13yarn", modeButton: "Mode Services", compactBottomSearch: true },
- { path: "/forms?q=transport&focus=1&run=1", modeButton: "Mode Forms", compactBottomSearch: true },
- { path: "/favourites?q=lithium&focus=1&run=1", modeButton: "Mode Favourites", compactBottomSearch: true },
+ {
+ path: "/services?q=13YARN&focus=1&run=1",
+ modeButton: "Mode Services",
+ compactBottomSearch: true,
+ ribbonQuery: "13YARN",
+ },
+ {
+ path: "/services/13yarn",
+ modeButton: "Mode Services",
+ compactBottomSearch: true,
+ ribbonQuery: undefined,
+ },
+ {
+ path: "/forms?q=transport&focus=1&run=1",
+ modeButton: "Mode Forms",
+ compactBottomSearch: true,
+ ribbonQuery: "transport",
+ },
+ {
+ path: "/favourites?q=lithium&focus=1&run=1",
+ modeButton: "Mode Favourites",
+ compactBottomSearch: true,
+ ribbonQuery: "lithium",
+ },
{
path: "/differentials?q=acute+confusion&focus=1&run=1",
modeButton: "Mode Differentials",
compactBottomSearch: true,
+ ribbonQuery: "acute confusion",
},
] as const) {
test(`search route keeps the correct composer at ${viewport.name} width on ${route.path}`, async ({ page }) => {
@@ -873,6 +894,12 @@ test.describe("Clinical KB tools launcher", () => {
await expect(visibleGlobalSearchInput(page), `${route.path} at ${viewport.name}`).toHaveCount(1, {
timeout: 20_000,
});
+ if (route.ribbonQuery) {
+ const ribbon = page.getByTestId("search-query-ribbon");
+ await expect(ribbon, `${route.path} at ${viewport.name}`).toBeVisible({ timeout: 20_000 });
+ await expect(ribbon.getByRole("heading", { name: route.ribbonQuery })).toBeVisible();
+ await expect(ribbon.getByRole("status")).toBeVisible();
+ }
const metrics = await globalSearchComposerMetrics(page);
expect(metrics, `${route.path} at ${viewport.name}`).not.toBeNull();
@@ -2178,6 +2205,10 @@ test.describe("Responsive layout guards", () => {
const resultCard = page.getByTestId("medication-result-acamprosate-phone");
const bottomDock = page.locator("form.answer-footer-search-dock");
await expect(resultCard).toBeVisible();
+ const queryRibbon = page.getByTestId("search-query-ribbon");
+ await expect(queryRibbon).toBeVisible();
+ await expect(queryRibbon.getByRole("heading", { name: "acamprosate renal dose" })).toBeVisible();
+ await expect(queryRibbon.getByRole("status")).toBeVisible();
await expect(bottomDock).toBeVisible();
await page.locator("main#main-content").evaluate((main) => main.scrollTo({ top: main.scrollHeight }));
const resultBox = await resultCard.boundingBox();