From 0ae9b59069be5cc8e2f2e38de5a085890b9e30ab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 16:30:08 +0000 Subject: [PATCH 1/2] Compact the shared filter sheet and quieten document result cards The documents filter sheet spent most of a phone viewport before the first filter was reachable, and its counted options wrapped one per line down a ragged column. The result cards carried 800-weight titles, a filled accent rank chip and saturated blue metadata pills. Filter sheet (shared by every mode, per docs/filter-contract.md): - Drive header density from ResultFilterSheet rather than from Sheet, so the filter surface tightens everywhere while other dialogs keep their header. Smaller title, smaller description, borderless close (still 48px), and the close control top-aligned with the title instead of floating mid-block. - New density tier: a facet group of two to five options that carry counts uses the row renderer in two columns instead of wrapping chips. A chip carrying a count is wide enough that four of them wrap one per line and leave most of each row empty. Below 380px it stays single-column, where a second column would truncate a label like "Locally reviewed". - Split hint (announced, keeps its unit) from hintLabel (displayed, a bare numeral), so "0 loaded sources" no longer repeats down the panel while the accessible name is unchanged. - Raise the row renderer's pointer floor from sm:min-h-9 to sm:min-h-10. It sat under the filter system's documented 40px desktop floor; documents only reached this renderer via the new tier, which is what surfaced it. - Lighten the eyebrows, meter and footer actions off bold. Documents call site: one-line sheet description, and the governance rule stated once above the first of the three adjacent groups rather than repeated under each heading. Result cards: hierarchy from size and space, at most one accent per card. Title and action rail drop to 600, the rank becomes a quiet ordinal, and the page/table/image badges go neutral. Only "Best match" keeps a filled accent; a plain "Relevant" verdict no longer renders in blue. Contract tests updated to the new tier rather than relaxed: the uncounted chip path keeps its own case, and the counted two-column path gains one that also pins the announced-vs-displayed split. Full unit suite green (684 files, 7451 tests); lint and typecheck clean. verify:ui could not run in this container -- Playwright pins chromium revision 1234 and the image ships chromium-1194 (known drift), so browser proof is delegated to CI Production UI rather than forced onto a mismatched binary. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01DEYgxhV7KJDg7CVpcsKaLY --- docs/filter-contract.md | 24 ++++- .../document-search-results.tsx | 75 +++++++++------ .../result-filter-control.tsx | 92 +++++++++++++++---- tests/decoration-on-text.contract.test.ts | 4 +- tests/filter-contract.dom.test.tsx | 56 ++++++++++- tests/ui-smoke.spec.ts | 6 +- 6 files changed, 198 insertions(+), 59 deletions(-) diff --git a/docs/filter-contract.md b/docs/filter-contract.md index ec26f06e2b..cfcf20d012 100644 --- a/docs/filter-contract.md +++ b/docs/filter-contract.md @@ -118,16 +118,30 @@ corpus of that size, and it stays. Facet groups only. Density scales with option and group volume across three tiers: -| Options / Groups | Renderer | -| --------------------------- | -------------------------------------------------------------------------------------- | -| ≤ 5 options | chips, single row / wrapping chips | -| 6–20 options | dense full-width vertical list with right-aligned count column and group headings | -| > 3 groups, or > 20 options | list/chips plus find-a-filter and collapse-by-default, every group behind a disclosure | +| Options / Groups | Renderer | +| ------------------------------- | -------------------------------------------------------------------------------------- | +| ≤ 5 options, no counts | chips, single row / wrapping chips | +| 2–5 options **carrying counts** | the row renderer below, in a two-column grid | +| 6–20 options | dense full-width vertical list with right-aligned count column and group headings | +| > 3 groups, or > 20 options | list/chips plus find-a-filter and collapse-by-default, every group behind a disclosure | `ResultFilterSheet` computes the threshold across facet groups. Facet groups containing 6–20 options render as compact full-width rows with a right-aligned count column for fast scanning. When a sheet exceeds 3 groups or 20 total options, it additionally adds find-a-filter and collapse-by-default chrome. +**A counted chip is not a chip.** A two-to-five-option group whose options carry counts uses the same +row renderer as the 6–20 tier, laid out in two columns. A chip carrying a count is wide enough that +four of them wrap one per line and leave most of each row empty — documents' Source status (4) and +Clinical validation (3) were exactly that, a ragged single column down a phone sheet. Two columns +halve the height and align the counts. A group with no counts keeps the wrapping chip row, which is +still the right renderer for short bare labels. + +**`hint` is announced, `hintLabel` is displayed.** `hint` carries the unit (`"1 loaded source"`) and +is what the option's accessible name is built from; `hintLabel` is the short visible form (`"1"`). +Set both when a count has a unit — spelling the unit into every visible option is what made the +counted rows too wide to sit two-up in the first place. `hintLabel` alone is never enough: the +announced name must keep the unit. + Collapse rules, when they apply: groups start collapsed; a group holding a selection opens itself; an explicit user collapse beats that; an active needle forces every matched group open and owns openness. A selected option always survives the needle, so an active constraint can diff --git a/src/components/clinical-dashboard/document-search-results.tsx b/src/components/clinical-dashboard/document-search-results.tsx index 04804bf9fe..1a8ba9e447 100644 --- a/src/components/clinical-dashboard/document-search-results.tsx +++ b/src/components/clinical-dashboard/document-search-results.tsx @@ -220,6 +220,14 @@ function loadedSourceCountHint(count: number) { return `${count.toLocaleString()} loaded ${count === 1 ? "source" : "sources"}`; } +// What the option DISPLAYS. The announced name keeps the full phrase above, so +// a reader still hears the unit; the visible column is a bare numeral. Spelling +// "0 loaded sources" seven times down a phone sheet made every option wide +// enough to wrap onto its own line and repeated the same two words in each one. +function loadedSourceCountLabel(count: number) { + return count.toLocaleString(); +} + function relevanceTone(document: DocumentMatch) { const verdict = document.relevance?.verdict as string | undefined; const percent = documentRelevancePercent(document); @@ -462,10 +470,10 @@ function DocumentResultMoreMenu({ }} className={cn( documentActionClass, - "min-h-12 w-full min-w-0 rounded-br-xl px-2 !text-sm font-bold text-[color:var(--text-heading)]", + "min-h-12 w-full min-w-0 rounded-br-xl px-2 !text-sm !font-semibold text-[color:var(--text)]", )} > -