Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
42 changes: 13 additions & 29 deletions src/components/applications-launcher-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
import { type FormEvent, useMemo, useState } from "react";

import { ModeHomeHero, ModeHomeVerificationFooter } from "@/components/mode-home-template";
import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band";
import {
MobileResultFilterControl,
SearchResultsHeaderBand,
} from "@/components/clinical-dashboard/search-results-header-band";
import { useSearchCommand } from "@/components/clinical-dashboard/search-command-context";
import { useFavouritesAccess } from "@/components/clinical-dashboard/use-favourites-access";
import { cn, toneInfo, toneSuccess, toneWarning } from "@/components/ui-primitives";
Expand Down Expand Up @@ -392,34 +395,15 @@ function FilterTabs({
);
})}
</div>
<div
className="flex min-w-0 gap-1 overflow-x-auto pb-1 sm:hidden"
role="group"
aria-label="Filter by tool category"
>
{mobileFilters.map((filter) => {
const active = filter.id === activeFilter || (filter.id === "all" && activeFilter === "saved");
return (
<button
key={filter.id}
type="button"
id={`launcher-filter-mobile-${filter.id}`}
aria-pressed={active}
aria-controls="launcher-results-panel"
onClick={() => onFilterChange(filter.id)}
className={cn(
"inline-flex min-h-tap shrink-0 items-center justify-center gap-0.5 whitespace-nowrap rounded-lg border px-2 text-2xs font-bold transition",
active
? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]"
: "border-[color:var(--border)] bg-[color:var(--surface-lux)] text-[color:var(--text-muted)]",
focusRing,
)}
>
{filter.label}
</button>
);
})}
</div>
<MobileResultFilterControl
label="Category"
ariaLabel="Filter by tool category"
testId="tool-category-select"
className="sm:hidden"
value={activeFilter === "more" ? "all" : activeFilter}
options={desktopFilters.map((filter) => ({ value: filter.id, label: filter.label }))}
onChange={onFilterChange}
/>
</>
);
}
Expand Down
19 changes: 18 additions & 1 deletion src/components/clinical-dashboard/differentials-home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
} from "lucide-react";

import { ModeHomeTemplate, ModeHomeVerificationFooter } from "@/components/mode-home-template";
import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band";
import {
MobileResultFilterControl,
SearchResultsHeaderBand,
} from "@/components/clinical-dashboard/search-results-header-band";
import { UniversalSearchAlsoMatches } from "@/components/clinical-dashboard/universal-search-also-matches";
import { useDifferentialSearch } from "@/components/clinical-dashboard/use-differential-catalog";
import { useResultSort } from "@/components/use-result-sort";
Expand Down Expand Up @@ -908,6 +911,20 @@ function SearchResultsView({
sortValue={sortValue}
onSortChange={setSortValue}
filterLabel="Filter differential result type"
mobileControls={
<MobileResultFilterControl
label="Show"
ariaLabel="Filter by result type"
testId="differential-result-type-select"
value={kindFilter}
options={[
{ value: "all", label: `All (${results.length})` },
{ value: "presentation", label: `Presentations (${presentationCount})` },
{ value: "diagnosis", label: `Diagnoses (${diagnosisCount})` },
]}
onChange={setKindFilter}
/>
}
filterControls={
<ResultTypeTabs
activeFilter={kindFilter}
Expand Down
22 changes: 20 additions & 2 deletions src/components/clinical-dashboard/document-search-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import { documentDisplayTitle } from "@/components/DocumentOrganizationBadges";
import { isDeployedClinicalKb } from "@/lib/deployed-app";
import { ModeHomeTemplate, ModeHomeVerificationFooter } from "@/components/mode-home-template";
import { ScopeAndGovernanceNotice } from "@/components/clinical-dashboard/answer-content";
import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band";
import {
MobileResultFilterControl,
SearchResultsHeaderBand,
} from "@/components/clinical-dashboard/search-results-header-band";
import { UniversalSearchAlsoMatches } from "@/components/clinical-dashboard/universal-search-also-matches";
import { useResultSort } from "@/components/use-result-sort";
import { SafeBoldText } from "@/components/SafeBoldText";
Expand Down Expand Up @@ -408,7 +411,7 @@ function DocumentSourceTypeFilters({
aria-pressed={active}
onClick={() => onResultTypeChange(tab.key)}
className={cn(
"inline-flex min-h-tap shrink-0 items-center gap-1.5 rounded-lg border px-2.5 text-2xs font-bold transition motion-reduce:transition-none sm:min-h-9 sm:text-xs",
"inline-flex min-h-tap shrink-0 items-center gap-1.5 rounded-lg border px-2.5 text-2xs font-bold transition motion-reduce:transition-none sm:text-xs",
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]",
active
? "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)] shadow-[var(--shadow-inset)]"
Expand Down Expand Up @@ -903,6 +906,21 @@ function DocumentSearchResultsPanelImpl({
) : null
}
filterLabel="Filter documents by source type"
mobileControls={
showResultsControls && resultTabs.length > 1 ? (
<MobileResultFilterControl
label="Show"
ariaLabel="Filter by source type"
testId="document-source-type-select"
value={effectiveResultType}
options={resultTabs.map((tab) => ({
value: tab.key,
label: `${tab.label} (${tab.count})`,
}))}
onChange={setActiveResultType}
/>
) : null
}
filterControls={
showResultsControls && resultTabs.length > 1 ? (
<DocumentSourceTypeFilters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import Link from "next/link";
import { useMemo, useState } from "react";

import { ModeHomeTemplate, ModeHomeVerificationFooter } from "@/components/mode-home-template";
import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band";
import {
MobileResultFilterControl,
SearchResultsHeaderBand,
} from "@/components/clinical-dashboard/search-results-header-band";
import { UniversalSearchAlsoMatches } from "@/components/clinical-dashboard/universal-search-also-matches";
import { considerationSummaryBadge } from "@/components/clinical-dashboard/medication-considerations";
import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context";
Expand Down Expand Up @@ -467,6 +470,19 @@ function MedicationResults({
matchCount={resultCount}
loading={catalog.loading}
filterLabel="Filter medication results"
mobileControls={
<MobileResultFilterControl
label="Show"
ariaLabel="Filter medication results"
testId="medication-result-filter-select"
value={activeFilter}
options={medicationResultFilters.map((filter) => ({
value: filter.id,
label: `${filter.label} (${counts[filter.id]})`,
}))}
onChange={setActiveFilter}
/>
}
filterControls={<FilterStrip activeFilter={activeFilter} counts={counts} onFilterChange={setActiveFilter} />}
/>

Expand Down
107 changes: 95 additions & 12 deletions src/components/clinical-dashboard/search-results-header-band.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function SearchResultsHeaderBand({
onSortChange,
onSaveSearch,
utilityControls,
mobileControls,
filterControls,
filterLabel = "Filter search results",
headingLevel = 2,
Expand All @@ -39,6 +40,8 @@ export function SearchResultsHeaderBand({
onSaveSearch?: () => void;
/** Page-specific actions that belong beside sort/view controls. */
utilityControls?: ReactNode;
/** Compact page-specific controls shown in the utility row below `sm`. */
mobileControls?: ReactNode;
/** Page-specific filters rendered as a full-width row within the shared ribbon. */
filterControls?: ReactNode;
filterLabel?: string;
Expand All @@ -56,7 +59,8 @@ export function SearchResultsHeaderBand({
const displayQuery = query.trim() || "All";
const statusLabel = loading ? "Searching…" : `${matchCount} ${matchCount === 1 ? "match" : "matches"}`;
const hasUtilities =
visibleScopes.length > 0 || Boolean(onSortChange || onViewChange || onSaveSearch || utilityControls);
visibleScopes.length > 0 ||
Boolean(onSortChange || onViewChange || onSaveSearch || utilityControls || mobileControls);
const QueryHeading = headingLevel === 1 ? "h1" : "h2";

return (
Expand Down Expand Up @@ -133,14 +137,43 @@ export function SearchResultsHeaderBand({
<X className="h-3 w-3 shrink-0" aria-hidden />
</button>
))}
{onSortChange ? (
<ResultSortControl
value={sortValue}
onChange={onSortChange}
compact
className="min-w-[8.5rem] flex-1 sm:flex-none"
/>
) : null}
{onSortChange && mobileControls ? (
<div
data-testid="search-query-ribbon-mobile-control-pair"
className="grid min-w-[15rem] flex-1 grid-cols-2 gap-1.5 sm:flex sm:min-w-0 sm:flex-none sm:items-center"
>
<ResultSortControl
value={sortValue}
onChange={onSortChange}
compact
className="w-full min-w-0 sm:w-auto sm:min-w-[8.5rem]"
/>
<div role="group" aria-label={filterLabel} className="min-w-0 sm:hidden">
{mobileControls}
</div>
</div>
) : (
<>
{onSortChange ? (
<ResultSortControl
value={sortValue}
onChange={onSortChange}
compact
className="min-w-[8.5rem] flex-1 sm:flex-none"
/>
) : null}
{mobileControls ? (
<div
data-testid="search-query-ribbon-mobile-controls"
role="group"
aria-label={filterLabel}
className="min-w-0 flex-1 sm:hidden"
>
{mobileControls}
</div>
) : null}
</>
)}
{utilityControls}
{onViewChange ? (
<div
Expand Down Expand Up @@ -201,7 +234,10 @@ export function SearchResultsHeaderBand({
data-testid="search-query-ribbon-filters"
role="group"
aria-label={filterLabel}
className="min-w-0 border-t border-[color:var(--border)] bg-[color:var(--surface-subtle)] px-2.5 py-2 sm:px-3"
className={cn(
"min-w-0 border-t border-[color:var(--border)] bg-[color:var(--surface-subtle)] px-2.5 py-2 sm:px-3",
Boolean(mobileControls) && "hidden sm:block",
)}
>
{filterControls}
</div>
Expand All @@ -225,7 +261,7 @@ export function ResultSortControl({
return (
<label
className={cn(
"relative inline-flex min-h-tap items-center gap-1.5 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] py-1 pl-2.5 pr-7 text-xs font-bold shadow-[var(--shadow-inset)] sm:min-h-10",
"relative inline-flex min-h-tap min-w-0 items-center gap-1.5 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] pl-2.5 pr-7 text-xs font-bold shadow-[var(--shadow-inset)]",
"focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[color:var(--focus)]",
className,
)}
Expand All @@ -236,7 +272,7 @@ export function ResultSortControl({
<select
value={value}
onChange={(event) => onChange(readResultSort(event.target.value))}
className="cursor-pointer appearance-none bg-transparent text-xs font-bold text-[color:var(--text)] outline-none [-webkit-appearance:none]"
className="h-tap min-w-0 flex-1 cursor-pointer appearance-none bg-transparent text-xs font-bold text-[color:var(--text)] outline-none [-webkit-appearance:none]"
aria-label="Sort results"
>
<option value="relevance">Relevance</option>
Expand All @@ -250,6 +286,53 @@ export function ResultSortControl({
);
}

export function MobileResultFilterControl<Value extends string>({
label,
ariaLabel,
value,
options,
onChange,
testId,
className,
}: {
label: string;
ariaLabel: string;
value: Value;
options: ReadonlyArray<{ value: Value; label: string; disabled?: boolean }>;
onChange: (value: Value) => void;
testId?: string;
className?: string;
}) {
return (
<label
className={cn(
"relative inline-flex min-h-tap w-full min-w-0 items-center gap-1.5 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] pl-2.5 pr-7 text-xs font-bold shadow-[var(--shadow-inset)]",
"focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[color:var(--focus)]",
className,
)}
>
<span className="shrink-0 text-[color:var(--text-soft)] max-[359px]:sr-only">{label}</span>
<select
data-testid={testId}
value={value}
onChange={(event) => onChange(event.target.value as Value)}
aria-label={ariaLabel}
className="h-tap min-w-0 flex-1 cursor-pointer appearance-none bg-transparent text-xs font-bold text-[color:var(--text)] outline-none [-webkit-appearance:none]"
>
{options.map((option) => (
<option key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</option>
))}
</select>
<ChevronsUpDown
className="pointer-events-none absolute right-2 size-icon-sm text-[color:var(--text-soft)]"
aria-hidden
/>
</label>
);
}

export function SearchResultsEmptyState({
modeId,
query,
Expand Down
Loading
Loading