diff --git a/src/components/therapy-compass/screens/search-screen.tsx b/src/components/therapy-compass/screens/search-screen.tsx
index 14c831219..da416d9e8 100644
--- a/src/components/therapy-compass/screens/search-screen.tsx
+++ b/src/components/therapy-compass/screens/search-screen.tsx
@@ -1,6 +1,9 @@
"use client";
-import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band";
+import {
+ MobileResultFilterControl,
+ SearchResultsHeaderBand,
+} from "@/components/clinical-dashboard/search-results-header-band";
import { useTcBindings } from "../bindings";
import { outlineControl, softControl } from "../controls";
@@ -17,6 +20,7 @@ export function SearchScreen() {
const q = b.search.query;
const results = b.searchResults;
const shown = results.slice(0, MAX_CARDS);
+ const availabilityFilterCount = Number(b.search.reviewedOnly) + Number(b.search.briefOnly);
return (
@@ -44,6 +48,55 @@ export function SearchScreen() {
matchCount={results.length}
loading={b.loading}
filterLabel="Filter therapy results"
+ mobileControls={
+
+ ({
+ value: tag,
+ label: `${b.search.tags.includes(tag) ? "✓ " : ""}${tag}`,
+ })),
+ ]}
+ onChange={(tag) => {
+ if (tag) b.toggleTag(tag);
+ }}
+ />
+ {
+ if (filter === "reviewed") b.toggleReviewedOnly();
+ if (filter === "brief") b.toggleBriefOnly();
+ if (filter === "clear") b.clearSearch();
+ }}
+ />
+
+ }
filterControls={
{QUICK_TAGS.map((tag) => {
diff --git a/tests/search-results-header-band.dom.test.tsx b/tests/search-results-header-band.dom.test.tsx
index 378ba8e1d..faee7738e 100644
--- a/tests/search-results-header-band.dom.test.tsx
+++ b/tests/search-results-header-band.dom.test.tsx
@@ -5,7 +5,10 @@ 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";
+import {
+ MobileResultFilterControl,
+ SearchResultsHeaderBand,
+} from "@/components/clinical-dashboard/search-results-header-band";
describe("SearchResultsHeaderBand", () => {
it("presents the query and completed count as one labelled results ribbon", () => {
@@ -128,4 +131,47 @@ describe("SearchResultsHeaderBand", () => {
expect(onOpenSources).toHaveBeenCalledOnce();
expect(onFilterTables).toHaveBeenCalledOnce();
});
+
+ it("pairs sort with a page-specific dropdown on mobile without changing either action", async () => {
+ const user = userEvent.setup();
+ const onSortChange = vi.fn();
+ const onFilterChange = vi.fn();
+
+ render(
+
+ }
+ filterControls={
+
+ }
+ />,
+ );
+
+ const pair = screen.getByTestId("search-query-ribbon-mobile-control-pair");
+ await user.selectOptions(within(pair).getByLabelText("Sort results"), "alpha");
+ await user.selectOptions(within(pair).getByLabelText("Filter by result type"), "diagnosis");
+
+ expect(onSortChange).toHaveBeenCalledWith("alpha");
+ expect(onFilterChange).toHaveBeenCalledWith("diagnosis");
+ expect(screen.getByTestId("search-query-ribbon-filters")).toHaveClass("hidden", "sm:block");
+ });
});
diff --git a/tests/ui-accessibility.spec.ts b/tests/ui-accessibility.spec.ts
index 7f46d0cd6..7d1f133f0 100644
--- a/tests/ui-accessibility.spec.ts
+++ b/tests/ui-accessibility.spec.ts
@@ -389,7 +389,7 @@ test.describe("Clinical KB accessibility coverage", () => {
await expectNoBlockingAxeViolations(page, testInfo, { disableRules: ["color-contrast"] });
});
- test("differential result types are pressed filters instead of tabs", async ({ page }) => {
+ test("differential result types use an accessible mobile filter instead of tabs", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await mockMinimalDashboardApi(page);
await mockDifferentialSearch(page);
@@ -399,24 +399,18 @@ test.describe("Clinical KB accessibility coverage", () => {
await page.locator('button[aria-label="Search differential presentations"]:visible').click();
await expect(page.getByTestId("differentials-search-results")).toBeVisible();
- const filterGroup = page.getByRole("group", { name: "Result type", exact: true });
- await expect(filterGroup).toBeVisible();
- await expect(filterGroup.getByRole("tab")).toHaveCount(0);
- const allFilter = filterGroup.getByRole("button", { name: /^All \(\d+\)$/ });
- const presentationsFilter = filterGroup.getByRole("button", { name: /^Presentations \(\d+\)$/ });
- const diagnosesFilter = filterGroup.getByRole("button", { name: /^Diagnoses \(\d+\)$/ });
- await expect(allFilter).toHaveAttribute("aria-pressed", "true");
- await expect(presentationsFilter).toHaveAttribute("aria-pressed", "false");
-
- await presentationsFilter.focus();
- await page.keyboard.press("Space");
- await expect(presentationsFilter).toHaveAttribute("aria-pressed", "true");
- await expect(allFilter).toHaveAttribute("aria-pressed", "false");
-
- await diagnosesFilter.focus();
- await page.keyboard.press("Enter");
- await expect(diagnosesFilter).toHaveAttribute("aria-pressed", "true");
- await expect(presentationsFilter).toHaveAttribute("aria-pressed", "false");
+ const filterSelect = page.getByTestId("differential-result-type-select");
+ await expect(filterSelect).toBeVisible();
+ await expect(filterSelect).toHaveAccessibleName("Filter by result type");
+ await expect(filterSelect).toHaveValue("all");
+ await expect(page.getByRole("tab")).toHaveCount(0);
+
+ await filterSelect.focus();
+ await expect(filterSelect).toBeFocused();
+ await filterSelect.selectOption("presentation");
+ await expect(filterSelect).toHaveValue("presentation");
+ await filterSelect.selectOption("diagnosis");
+ await expect(filterSelect).toHaveValue("diagnosis");
});
test("guest upload action exposes the admin boundary and opens Sources", async ({ page }) => {
@@ -474,6 +468,12 @@ test.describe("Clinical KB accessibility coverage", () => {
const therapyRibbon = page.getByTestId("search-query-ribbon");
await expect(therapyRibbon.getByRole("heading", { name: "All" })).toBeVisible();
await expect(therapyRibbon.getByRole("group", { name: "Filter therapy results" })).toBeVisible();
+ const therapyTopics = therapyRibbon.getByTestId("therapy-topic-filter-select");
+ const therapyAvailability = therapyRibbon.getByTestId("therapy-availability-filter-select");
+ await expect(therapyTopics).toBeVisible();
+ await expect(therapyTopics).toHaveAccessibleName("Add or remove a therapy topic filter");
+ await expect(therapyAvailability).toBeVisible();
+ await expect(therapyAvailability).toHaveAccessibleName("Change therapy availability filters");
const searchInput = page.getByRole("textbox", { name: "Search therapies" });
await searchInput.focus();
@@ -484,14 +484,23 @@ test.describe("Clinical KB accessibility coverage", () => {
expect(inputFocus.outlineStyle).not.toBe("none");
expect(inputFocus.outlineWidth).toBeGreaterThanOrEqual(2);
- const clearButtonSize = await page
- .locator('[data-screen-label="Search"]')
- .getByRole("button", { name: "Clear", exact: true })
- .evaluate((element) => {
+ await therapyTopics.selectOption("CBT");
+ await expect(therapyTopics.locator('option[value=""]')).toHaveText("1 topics selected");
+ await therapyAvailability.selectOption("reviewed");
+ await expect(therapyAvailability.locator('option[value=""]')).toHaveText("1 more selected");
+
+ for (const control of [therapyTopics, therapyAvailability]) {
+ const controlSize = await control.evaluate((element) => {
const bounds = element.getBoundingClientRect();
return { width: bounds.width, height: bounds.height };
});
- expect(clearButtonSize.height).toBeGreaterThanOrEqual(44);
+ expect(controlSize.width).toBeGreaterThanOrEqual(44);
+ expect(controlSize.height).toBeGreaterThanOrEqual(44);
+ }
+
+ await therapyAvailability.selectOption("clear");
+ await expect(therapyTopics.locator('option[value=""]')).toHaveText("All topics");
+ await expect(therapyAvailability.locator('option[value=""]')).toHaveText("Any availability");
await expectNoPageHorizontalOverflow(page);
diff --git a/tests/ui-formulation.spec.ts b/tests/ui-formulation.spec.ts
index 0a458ec71..9ae2f4c7f 100644
--- a/tests/ui-formulation.spec.ts
+++ b/tests/ui-formulation.spec.ts
@@ -100,7 +100,10 @@ test("keeps mobile search, domain filtering, record actions, and universal chrom
await expect(queryRibbon.getByRole("heading", { level: 1, name: "What if something goes wrong" })).toBeVisible();
await expect(queryRibbon.getByRole("group", { name: "Filter formulation mechanisms" })).toBeVisible();
await expect(page.getByRole("link", { name: "Worry", exact: true })).toBeVisible();
- await expect(page.getByRole("combobox", { name: "Filter by formulation domain" })).toBeVisible();
+ await expect(queryRibbon.getByTestId("formulation-pattern-select")).toBeVisible();
+ const domainSelect = queryRibbon.getByTestId("formulation-domain-select");
+ await expect(domainSelect).toBeVisible();
+ await expect(domainSelect).toHaveAccessibleName("Filter by formulation domain");
await expect(page.getByTestId("global-search-input").filter({ visible: true }).first()).toBeVisible();
await expect(page.getByText("Source status", { exact: true })).toHaveCount(0);
await expect(page.getByText("Source", { exact: true })).toHaveCount(0);
diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts
index ba081f49a..1ec892593 100644
--- a/tests/ui-smoke.spec.ts
+++ b/tests/ui-smoke.spec.ts
@@ -2603,6 +2603,13 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(queryRibbon.getByRole("heading", { name: "sertraline" })).toBeVisible();
await expect(queryRibbon.getByRole("group", { name: "Result view" })).toBeVisible();
await expect(queryRibbon.getByRole("group", { name: "Filter factsheets by category" })).toBeVisible();
+ const categorySelect = queryRibbon.getByTestId("factsheet-category-select");
+ if (viewport.width < 640) {
+ await expect(categorySelect).toBeVisible();
+ await expect(categorySelect).toHaveAccessibleName("Filter factsheets by category");
+ } else {
+ await expect(categorySelect).toBeHidden();
+ }
await expectNoPageHorizontalOverflow(page);
}
});
@@ -3042,9 +3049,12 @@ test.describe("Clinical KB UI smoke coverage", () => {
const documentWorkspace = page.getByTestId("document-search-workspace");
const queryRibbon = documentWorkspace.getByTestId("search-query-ribbon");
await expect(queryRibbon).toBeVisible();
- await expect(queryRibbon.getByTestId("document-results-controls")).toBeVisible();
const resultsControls = queryRibbon.getByTestId("document-results-controls");
+ await expect(resultsControls).toBeHidden();
await expect(queryRibbon.getByLabel("Sort results")).toBeVisible();
+ const mobileTypeFilter = queryRibbon.getByTestId("document-source-type-select");
+ await expect(mobileTypeFilter).toBeVisible();
+ await expect(mobileTypeFilter).toHaveAccessibleName("Filter by source type");
const ribbonSourcesButton = queryRibbon.getByRole("button", { name: "Open source filters" });
await expect(ribbonSourcesButton).toBeVisible();
await expectMinTouchTarget(ribbonSourcesButton);
@@ -3058,15 +3068,11 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(documentResults).toContainText("Best match");
await expect(documentResults).toContainText("1 table");
- const typeFilters = resultsControls.getByLabel("Filter by source type");
- if ((await typeFilters.count()) > 0) {
- const tablesFilter = typeFilters.getByRole("button", { name: /Tables/i });
- await expect(tablesFilter).toBeVisible();
- await expectMinTouchTarget(tablesFilter);
- await tablesFilter.click();
- await expect(tablesFilter).toHaveAttribute("aria-pressed", "true");
+ if ((await mobileTypeFilter.locator('option[value="tables"]').count()) > 0) {
+ await mobileTypeFilter.selectOption("tables");
+ await expect(mobileTypeFilter).toHaveValue("tables");
await expect(documentResults).toBeVisible();
- await typeFilters.getByRole("button", { name: /^All/i }).click();
+ await mobileTypeFilter.selectOption("all");
}
await queryRibbon.getByLabel("Sort results").selectOption("alpha");
@@ -3088,6 +3094,13 @@ test.describe("Clinical KB UI smoke coverage", () => {
await page.setViewportSize({ width: 1440, height: 900 });
await expectNoPageHorizontalOverflow(page);
+ await expect(resultsControls).toBeVisible();
+ const typeFilters = resultsControls.getByLabel("Filter by source type");
+ if ((await typeFilters.count()) > 0) {
+ const tablesFilter = typeFilters.getByRole("button", { name: /Tables/i });
+ await expect(tablesFilter).toBeVisible();
+ await expectMinTouchTarget(tablesFilter);
+ }
const dashboardMain = page.locator("main#main-content");
const scrollTopBeforeSources = await dashboardMain.evaluate((element) => element.scrollTop);
const openSourcesButton = queryRibbon.getByRole("button", { name: "Open source filters" });
diff --git a/tests/ui-specifiers.spec.ts b/tests/ui-specifiers.spec.ts
index 047b2d803..f13f33bb8 100644
--- a/tests/ui-specifiers.spec.ts
+++ b/tests/ui-specifiers.spec.ts
@@ -108,18 +108,21 @@ test("keeps mobile search, filters, results, and the fixed composer usable", asy
await expect(queryRibbon.getByRole("heading", { level: 1, name: "returns every winter" })).toBeVisible();
await expect(queryRibbon.getByRole("group", { name: "Filter specifier results" })).toBeVisible();
await expect(page.getByRole("link", { name: "Open With seasonal pattern" })).toBeVisible();
- await expect(page.getByRole("group", { name: "Filter by specifier family" })).toBeVisible();
- await expect(page.getByRole("combobox", { name: "Filter by diagnosis" })).toBeVisible();
+ const familySelect = queryRibbon.getByTestId("specifier-family-select");
+ const diagnosisSelect = queryRibbon.getByTestId("specifier-diagnosis-select");
+ await expect(familySelect).toBeVisible();
+ await expect(familySelect).toHaveAccessibleName("Filter by specifier family");
+ await expect(diagnosisSelect).toBeVisible();
+ await expect(diagnosisSelect).toHaveAccessibleName("Filter by diagnosis");
await expect(page.getByTestId("global-search-input").filter({ visible: true }).first()).toBeVisible();
await expect(page.getByText("Source status", { exact: true })).toHaveCount(0);
await expect(page.getByText("Source", { exact: true })).toHaveCount(0);
- const courseFilter = page.getByRole("button", { name: /^(Course|Course and onset)$/ });
- await courseFilter.click();
- await expect(courseFilter).toHaveAttribute("aria-pressed", "true");
+ await familySelect.selectOption("course-onset");
+ await expect(familySelect).toHaveValue("course-onset");
await expect(page.getByRole("link", { name: "Open With seasonal pattern" })).toBeVisible();
- await page.getByRole("combobox", { name: "Filter by diagnosis" }).selectOption("depressive");
+ await diagnosisSelect.selectOption("depressive");
await expect(page.getByRole("link", { name: "Open With seasonal pattern" })).toBeVisible();
await expectNoHorizontalOverflow(page);
diff --git a/tests/ui-stress.spec.ts b/tests/ui-stress.spec.ts
index 6674a2d63..0be0a37fc 100644
--- a/tests/ui-stress.spec.ts
+++ b/tests/ui-stress.spec.ts
@@ -442,12 +442,15 @@ test.describe("Medication responsive stress coverage", () => {
await expect(phoneResult).toBeVisible();
await expect(desktopResult).toBeHidden();
- const metrics = await page.evaluate(() => {
+ const metrics = await page.evaluate((viewportWidth) => {
const workspace = document.querySelector(".medication-results-workspace");
const patient = document.querySelector(".medication-patient-strip");
const filters = document.querySelector(".medication-filter-strip");
const card = document.querySelector('[data-testid="medication-result-acamprosate-phone"]');
- const firstFilter = filters?.querySelector("button");
+ const firstFilter =
+ viewportWidth < 640
+ ? document.querySelector('[data-testid="medication-result-filter-select"]')
+ : filters?.querySelector("button");
if (!workspace || !patient || !filters || !card || !firstFilter) return null;
const workspaceRect = workspace.getBoundingClientRect();
const patientRect = patient.getBoundingClientRect();
@@ -466,7 +469,7 @@ test.describe("Medication responsive stress coverage", () => {
filterLeft: filterRect.left,
filterHeight: filterRect.height,
};
- });
+ }, viewport.width);
expect(metrics).not.toBeNull();
expect(metrics?.filterHeight ?? 0).toBeGreaterThanOrEqual(42);
diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts
index 62c79f134..035a16634 100644
--- a/tests/ui-tools.spec.ts
+++ b/tests/ui-tools.spec.ts
@@ -370,6 +370,12 @@ test.describe("Clinical KB tools launcher", () => {
await expect(page.locator("#launcher-results-panel")).toHaveAttribute("role", "group");
await expect(page.locator("#launcher-results-panel")).toHaveAttribute("aria-label", "All tools");
if (viewport.name === "mobile") {
+ const categorySelect = page.getByTestId("tool-category-select");
+ await expect(categorySelect).toBeVisible();
+ await expect(categorySelect).toHaveAccessibleName("Filter by tool category");
+ await categorySelect.selectOption("assessment");
+ await expect(page.locator("#launcher-results-panel")).toHaveAttribute("aria-label", "Assess tools");
+ await categorySelect.selectOption("all");
await page.getByTestId("application-row-medication-prescribing").click();
const selectedSheet = page.getByRole("dialog", { name: "Medication Prescribing" });
await expect(selectedSheet).toBeVisible();
@@ -752,6 +758,11 @@ test.describe("Clinical KB tools launcher", () => {
await expect(page.getByRole("button", { name: "Mode Services" })).toBeVisible();
const input = visibleGlobalSearchInput(page).first();
await expect(input).toBeVisible();
+ const quickFilter = page.getByTestId("service-quick-filter-select");
+ await expect(quickFilter).toBeVisible();
+ await expect(quickFilter).toHaveAccessibleName("Apply a quick service filter");
+ await quickFilter.selectOption("crisis");
+ await expect(page).toHaveURL(/\/services\?.*q=crisis/);
// Phones keep the full search results in the page instead of opening a
// command sheet over the small viewport.
@@ -1466,23 +1477,25 @@ test.describe("Clinical KB tools launcher", () => {
await submitDifferentialSearch(page, "acute confusion");
await expect(page.getByTestId("differentials-search-results")).toBeVisible();
- const tabs = page.getByTestId("differential-result-type-tabs");
- await expect(tabs).toBeVisible();
- await expect(tabs).toHaveAttribute("role", "group");
- await expect(tabs).toHaveAttribute("aria-label", "Result type");
- await expect(tabs.getByRole("button", { name: /All \(\d+\)/ })).toBeVisible();
- await expect(tabs.getByRole("button", { name: /Presentations \(\d+\)/ })).toBeVisible();
- await expect(tabs.getByRole("button", { name: /Diagnoses \(\d+\)/ })).toBeVisible();
-
- const tabMetrics = await tabs.getByRole("button").evaluateAll((buttons) =>
- buttons.map((button) => {
- const rect = button.getBoundingClientRect();
- return { height: rect.height, scrollHeight: button.scrollHeight };
- }),
- );
- for (const tab of tabMetrics) {
- expect(tab.scrollHeight).toBeLessThanOrEqual(tab.height + 1);
- }
+ const typeSelect = page.getByTestId("differential-result-type-select");
+ await expect(typeSelect).toBeVisible();
+ await expect(typeSelect).toHaveAccessibleName("Filter by result type");
+ await expect(typeSelect).toHaveValue("all");
+ await typeSelect.selectOption("diagnosis");
+ await expect(typeSelect).toHaveValue("diagnosis");
+ await typeSelect.selectOption("all");
+
+ const mobilePair = page.getByTestId("search-query-ribbon-mobile-control-pair");
+ const pairMetrics = await mobilePair.evaluate((element) => ({
+ width: element.getBoundingClientRect().width,
+ scrollWidth: element.scrollWidth,
+ controlHeights: Array.from(element.querySelectorAll("label")).map(
+ (label) => label.getBoundingClientRect().height,
+ ),
+ }));
+ expect(pairMetrics.scrollWidth).toBeLessThanOrEqual(pairMetrics.width + 1);
+ expect(pairMetrics.controlHeights).toHaveLength(2);
+ expect(Math.abs(pairMetrics.controlHeights[0] - pairMetrics.controlHeights[1])).toBeLessThanOrEqual(1);
const emergentBadge = page.getByTestId("differential-status-badge").first();
await expect(emergentBadge).toBeVisible();
@@ -1564,23 +1577,25 @@ test.describe("Clinical KB tools launcher", () => {
await submitDifferentialSearch(page, "acute confusion");
await expect(page.getByTestId("differentials-search-results")).toBeVisible();
- const tabs = page.getByTestId("differential-result-type-tabs");
- await expect(tabs).toBeVisible();
- await expect(tabs).toHaveAttribute("role", "group");
- await expect(tabs).toHaveAttribute("aria-label", "Result type");
- await expect(tabs.getByRole("button", { name: "All (8)" })).toBeVisible();
- await expect(tabs.getByRole("button", { name: "Presentations (1)" })).toBeVisible();
- await expect(tabs.getByRole("button", { name: "Diagnoses (7)" })).toBeVisible();
-
- const tabMetrics = await tabs.getByRole("button").evaluateAll((tabElements) =>
- tabElements.map((tab) => {
- const rect = tab.getBoundingClientRect();
- return { height: rect.height, scrollHeight: tab.scrollHeight };
- }),
- );
- for (const tab of tabMetrics) {
- expect(tab.scrollHeight).toBeLessThanOrEqual(tab.height + 1);
- }
+ const typeSelect = page.getByTestId("differential-result-type-select");
+ await expect(typeSelect).toBeVisible();
+ await expect(typeSelect).toHaveAccessibleName("Filter by result type");
+ await expect(typeSelect).toHaveValue("all");
+ await typeSelect.selectOption("presentation");
+ await expect(typeSelect).toHaveValue("presentation");
+ await typeSelect.selectOption("all");
+
+ const mobilePair = page.getByTestId("search-query-ribbon-mobile-control-pair");
+ const pairMetrics = await mobilePair.evaluate((element) => ({
+ width: element.getBoundingClientRect().width,
+ scrollWidth: element.scrollWidth,
+ controlHeights: Array.from(element.querySelectorAll("label")).map(
+ (label) => label.getBoundingClientRect().height,
+ ),
+ }));
+ expect(pairMetrics.scrollWidth).toBeLessThanOrEqual(pairMetrics.width + 1);
+ expect(pairMetrics.controlHeights).toHaveLength(2);
+ expect(Math.abs(pairMetrics.controlHeights[0] - pairMetrics.controlHeights[1])).toBeLessThanOrEqual(1);
const emergentBadge = page.getByTestId("differential-status-badge").first();
await expect(emergentBadge).toBeVisible();
@@ -2210,6 +2225,9 @@ test.describe("Responsive layout guards", () => {
await expect(queryRibbon).toBeVisible();
await expect(queryRibbon.getByRole("heading", { name: "acamprosate renal dose" })).toBeVisible();
await expect(queryRibbon.getByRole("status")).toBeVisible();
+ const resultFilter = queryRibbon.getByTestId("medication-result-filter-select");
+ await expect(resultFilter).toBeVisible();
+ await expect(resultFilter).toHaveAccessibleName("Filter medication results");
await expect(bottomDock).toBeVisible();
await page.locator("main#main-content").evaluate((main) => main.scrollTo({ top: main.scrollHeight }));
const resultBox = await resultCard.boundingBox();
From 6522bc48f4b65498eebbd40c3e06430f37da9996 Mon Sep 17 00:00:00 2001
From: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
Date: Sat, 25 Jul 2026 10:01:07 +0800
Subject: [PATCH 2/2] docs(review): record mobile filter release review
---
docs/branch-review-ledger.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md
index d956b4dc1..53fe3ea4c 100644
--- a/docs/branch-review-ledger.md
+++ b/docs/branch-review-ledger.md
@@ -797,3 +797,4 @@ This file is append-only. Never rewrite or delete an existing review record; app
| 2026-07-25 | codex/search-results-filters-20260725 | 88131e7267efd33059766dec80355a9246fbb2bf | Search result filters and document Sources merge-readiness review | APPROVE. No P0-P2 finding after current-main sync. Documents open Sources as an on-screen filtering surface with source-type controls; the shared results ribbon is applied across search pages. Highest residual risk: unusual real-content combinations may alter perceived density, while responsive, forced-colors, focus, and overflow paths are browser-covered. RAG impact: no retrieval behaviour change - UI controls and source browsing only. | `npm run verify:ui` pass 268/268; `npm run verify:cheap` pass (377 files, 3340 passed, 1 skipped); post-sync `npm run verify:pr-local` pass (378 files, 3349 passed, 1 skipped, production build, bundle-secret scan, offline RAG fixtures); `npm run check:production-readiness` pass with OPENAI_SAFETY_IDENTIFIER_SECRET warning; no live/provider-backed app checks run. |
| 2026-07-25 | `codex/therapy-page-polish-ad78b4` | `157559aa0678f02de09c14f66d544b62a5138c4a` | Targeted release review: Therapy naming, centred navigation, and white canvas | APPROVE. No P0-P3 findings. The production Therapy route consistently uses the title Therapy, the shared page background token, and a centred overflow-safe section navigation. The latest `origin/main` merge was clean and retained both upstream responsive/home-composer assertions. Highest residual risk is visual drift at an untested browser engine; exact desktop and phone Chromium measurements were stable. | Focused Vitest 40/40; pre-sync `verify:cheap` 378 files / 3342 passed / 1 skipped; pre-sync `verify:ui` passed; integrated runtime, Prettier, lint, and typecheck passed; integrated Vitest was interrupted by the shared heavyweight-test queue after an independent 378-file / 3342-pass run. Required hosted checks must pass on the published exact head before merge. No clinical/provider workflow ran. |
| 2026-07-25 | codex/search-results-filters-20260725 (PR #1184) | 8f74d8bd40810ede34ad4b155973b598c1be0101 | Superseding merge-readiness review after Sources focus repair | APPROVE. Supersedes the 88131e72 row: the automated P2 showed a transient Daily Actions menu item could disconnect before Sources restored focus. Closing Sources now falls back after unmount to the currently rendered action trigger, and the regression requires the visible Documents trigger to own focus. No P0-P2 finding remains. RAG impact: no retrieval behaviour change - UI focus restoration only. | Post-fix isolated production Chromium 1/1; post-current-main local Chromium 1/1; `npm run verify:cheap` pass (378 files, 3350 passed, 1 skipped); targeted Prettier and ESLint pass; required hosted checks must rerun on the published exact head; no live clinical/provider workflow ran. |
+| 2026-07-25 | `codex/mobile-search-filter-dropdowns-dcbb32` | `d9f0051ef8335817e7fa29aeb02ee6324331df39` | Release review: responsive search-result filter dropdowns across production modes | APPROVE with verification note. No P0-P2 finding. Phone result-type rails are replaced by page-specific native selects while desktop controls remain intact; shared controls own a real 44px interactive target and retain forced-colors/focus behavior. Highest residual risk is browser-specific native-select rendering outside Chromium. RAG impact: no retrieval behaviour change - result filtering and sort presentation only. | `npm run verify:cheap` pass twice (378 files, 3351 passed, 1 skipped); focused production Chromium routes and accessibility/stress guards pass; production `npm run build` and client-bundle secret scan pass; offline RAG fixtures 36/36 pass. `verify:pr-local` reached unit tests but local infrastructure tests timed out under heavy worktree contention; two remained timeout-only on focused retry. Required hosted checks must pass on the exact published head before merge. No live clinical/provider workflow ran. |