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
6 changes: 4 additions & 2 deletions docs/branch-review-ledger.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -899,12 +899,14 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | 5b96281ee7da817d5ce7f1102004ebe6f861b920 | pr-1815 heavy review-and-fix | remote already merged main (shadow-tight Switch kept); cherry-picked privacy -mb-4 reclaim + calculators dock cancel; removed duplicate UniversalSearchAlsoMatches; rail-aware section-sheet focus restore; dispositioned CodeRabbit docs/ledger/gates nits and outdated Sentry skeleton gap | verify:cheap PASS prior tip; verify:pr-local PASS prior tip; vitest privacy+in-page-nav 28 passed on cherry-pick; merge-tree clean vs origin/main |
| 2026-08-12 | PR #1815 / claude/spacing-icon-design-review-rxwh28 | 9f266210f02081be54d407c70a85f52fed436128 | babysit | no remaining actionable findings; one pre-existing thread resolved as no-change (Dockerfile.worker follow-up needed) | required checks: Gitleaks PR policy PR required (all pass); targeted vitest passed: tests/document-frame-contract.test.ts + tests/in-page-nav-header.dom.test.tsx |
| 2026-08-12 | 1815 | 27ce96e1755055ceee2eeae02d6efdf11259fcde | babysit | fixed | Unit coverage: targeted vitest passed: tests/shared-home-empty-state.dom.test.tsx (17 passed). PR required still blocked on pre-existing check failure at old remote head before sync. |
| 2026-08-12 | PR #1595 / claude/ds-v2-adopt | 590eb6cfb229c5ae0f7a5025352fa871d8321521 | Supersedes 2026-08-03 PR-J clinical-governance review at f9f73c707d9b6b6226fc04d172fef8e426513055; accepted delta through merged PR head | SUPERSEDES the earlier PR-J clinical-governance row for merge evidence. The final delta added the answer-state projection, the two scoped review fixes, and the clinically approved #228 attribution wording. The user accepted that delta without a second clinical-governance review; this record preserves that explicit limitation rather than implying the earlier review covered the final tree. | Final PR head 590eb6cfb229c5ae0f7a5025352fa871d8321521; squashed to main as f4448f8c1 (historical mapping recorded in #232); no new provider or clinical review performed |
| 2026-08-12 | claude/rag-canary-test-review-seprbt | bcf357a96fde74d39fc4726ffabb5079a744ef28 | eval-canary review: workflow, compare tooling, snapshot builder, alias tiering, rag-behaviour docs | PR #1843 opened; no retrieval behaviour change; snapshot refresh handed off as /issues #304 | verify:pr-local (green except env-only #296), eval:rag:offline 574/574, focused suites 40/40 |
| 2026-08-12 | codex/pr-workflow-safety-230-296 | bc0a491fdf4146775629f9b2b03e2a2cc61bd7cb | pr-1830 unblock | unblocked: merged origin/main (outstanding-issues conflict), PR body RAG impact + governance, resolved Copilot thread; merge-tree clean; required CI in progress | check:outstanding-issues pass; evaluatePullRequestPolicy ok; merge-tree clean; PR policy/mergeability/Change scope in progress |
| 2026-08-12 | claude/segmented-control-count | d5ff3160242986d2fac80ed9a915390b5c69b6e1 | SegmentedControl option hint slot (filter rollout prerequisite) | PR #1848 opened; additive, no call site passes hint yet; fixed a concatenated accessible name (All62 -> All (62)) | lint/typecheck/test green, 80 in ui-v2-components, 56 design-sync with no regen needed, clean build, bundle-budget within tolerance |
| 2026-08-12 | claude/design-issues-triage-wnr7k9 | a6bfc6f2707975d9b9c649843e083965c80afb9c | Tier 1 design-issue re-verification: archive #171/#172/#174/#181/#273/#274/#302, correct #293 | docs-only; six rows verified delivered on main, min-h-tap finding refuted as deliberate sm: step-down | verify:pr-local (10/10 green); check:outstanding-issues 138 open/163 archived |
| 2026-08-12 | claude/design-issues-triage-wnr7k9 | 970d39bc7023823d3283c660df090659a2f07aec | Full outstanding-issues ledger sweep: 47 rows individually verified against merged main | 19 archived (delivered or duplicate), 10 re-scoped with re-measured evidence, 1 refuted (#293), 4 machine-local rows annotated do-not-close-from-cloud; 98 rows bucketed by blocker, not individually verified | verify:pr-local 10/10 green; check:outstanding-issues 126 open/175 archived, no ids deleted from base |
| 2026-08-12 | claude/filter-contract-global | a0add717c2521c7fdeba4da5b094377014383c3e | global filter contract: lens/facet kinds + docs/filter-contract.md (no rendered change) | PR #1847 opened; additive only, zero call sites touched; fixed an accessible-name leak caught by the new DOM tests | verify:pr-local fully green (no failures), 4 new DOM tests, git diff over all 7 mode files empty |
| 2026-08-12 | claude/design-issues-triage-wnr7k9 | 2553b64b2342b0ef2ebef0afde152e76c1252496 | Ledger sweep round 2: open-PR cross-check plus clinical/answer-surface verification | 24 rows flagged IN FLIGHT against 7 open PRs (none had said so); 3 answer-surface rows archived (#166 #208 #216); #250 wave plan re-scoped; main-merge conflict resolved preserving both sides, 23 branch changes re-applied via the writer | verify:pr-local 10/10 green; check:outstanding-issues 121 open/180 archived, no ids deleted from base |
| 2026-08-12 | claude/design-issues-triage-wnr7k9 | 586012639565e4d3306b44361ebc5a3bdb3024ad | Land PR #1838 ledger sweep; close #147 mobile CLS by measurement | Merge resolved as union (main renumbered #302/#303 to #306/#307 — not lost, correcting an earlier claim); #306/#307 archived as already-delivered. #147 archived on two identical offline Lighthouse runs: mobile CLS 0.035/0.000/0.013/0.081/0.000, all under 0.1, cause fixed by PR #1616 not this session. #118 updated (browser drift 141-vs-151, wider than recorded); new #308 for desktop /documents/search CLS 0.119 | verify:pr-local 10/10 green; check:outstanding-issues 121 open/185 archived; verify:lighthouse x2 (gate ungraded on browser drift, measurements valid) |
| 2026-08-12 | 1848 | c65b9d91b760862c2ff9d5001974670219c5da02 | full PR diff and unresolved review feedback | P2 hint contrast and live-count width fixes applied | focused Vitest passed (81); focused ESLint passed; design-system contract passed |
| 2026-08-12 | PR #1595 / claude/ds-v2-adopt | 590eb6cfb229c5ae0f7a5025352fa871d8321521 | Supersedes 2026-08-03 PR-J clinical-governance review at f9f73c707d9b6b6226fc04d172fef8e426513055; accepted delta through merged PR head | SUPERSEDES the earlier PR-J clinical-governance row for merge evidence. The final delta added the answer-state projection, the two scoped review fixes, and the clinically approved #228 attribution wording. The user accepted that delta without a second clinical-governance review; this record preserves that explicit limitation rather than implying the earlier review covered the final tree. | Final PR head 590eb6cfb229c5ae0f7a5025352fa871d8321521; squashed to main as f4448f8c1 (historical mapping recorded in #232); no new provider or clinical review performed |
| 2026-08-12 | PR-1835 | fe46e5ade8018d21cf15d149711579b1b43c7fb8 | full PR diff and unresolved review feedback | P1/P2 findings fixed during fresh open-PR sweep; rename-only historical review comments dispositioned no-change | focused drift/docs/ledger/migration checks pass; verify:pr-local static+lint+typecheck pass, unrelated Windows unit baseline failures |
| 2026-08-12 | claude/filter-contract-global | a0add717c2521c7fdeba4da5b094377014383c3e | global filter contract: lens/facet kinds + docs/filter-contract.md (no rendered change) | PR #1847 opened; additive only, zero call sites touched; fixed an accessible-name leak caught by the new DOM tests | verify:pr-local fully green (no failures), 4 new DOM tests, git diff over all 7 mode files empty |
27 changes: 27 additions & 0 deletions src/components/ui/segmented-control.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,18 @@ export type SegmentedControlOption<T extends string> = {
label: string;
icon?: LucideIcon;
disabled?: boolean;
/**
* Trailing detail, almost always a count — "Presentations 41".
*
* Exists because the one-of-N rails this control replaces across the modes all
* carry a count, and baking it into `label` would fold the number into the
* truncating span and lose the tabular alignment. Never the only thing
* distinguishing two options: it joins the accessible name, so an option whose
* label is not unique without its hint reads as a near-duplicate to a screen
* reader. Mirrors `ResultFilterOption.hint`, so a mode can build one option
* array and hand it to both the desktop rail and the phone sheet.
*/
hint?: string;
};

type AccessibleName = { label: string; ariaLabelledBy?: never } | { label?: never; ariaLabelledBy: string };
Expand DownExpand Up@@ -100,6 +112,11 @@ export function SegmentedControl<T extends string>({
type="button"
role="radio"
aria-checked={checked}
// Without this the label and hint spans concatenate to "All62" in
// the accessible name — inter-element whitespace is normalised away
// by the name computation, so a text-node separator cannot fix it.
// Matches the `${label} (${count})` shape the mode rails used.
aria-label={option.hint ? `${option.label} (${option.hint})` : undefined}
tabIndex={option.value === tabStopValue ? 0 : -1}
disabled={option.disabled}
data-segment-value={option.value}
Expand All@@ -116,6 +133,16 @@ export function SegmentedControl<T extends string>({
>
{Icon ? <Icon aria-hidden="true" className="size-icon-sm shrink-0" /> : null}
<span className="min-w-0 truncate">{option.label}</span>
{option.hint ? (
// Reserve a three-digit count column as well as using tabular figures so live
// result updates do not move the segment bounds — tabular figures keep digit
// widths equal but do not stop the span growing at 9 -> 10. The hint inherits
// the button's opaque semantic foreground rather than carrying its own colour:
// the decoration-only text alias is barred from production by
// check:design-system-contract, and any second token here would have to stay
// legible against both the checked and unchecked backgrounds.
<span className="nums min-w-6 shrink-0 text-right tabular-nums">{option.hint}</span>
) : null}
</button>
);
})}
Expand Down
64 changes: 64 additions & 0 deletions tests/ui-v2-components.dom.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -345,6 +345,70 @@ describe("SegmentedControl", () => {
expect(screen.getByRole("radio", { name: "Comprehensive" })).toHaveFocus();
});

// The one-of-N rails this control replaces across the modes all carry a count.
// Baking it into `label` would fold the number into the truncating span, so it
// gets its own slot — and it must reach the accessible name, or a screen
// reader user loses information a sighted user has.
it("renders an option hint and folds it into the accessible name", () => {
render(
<SegmentedControl
label="Result type"
value="all"
onChange={() => undefined}
options={[
{ value: "all", label: "All", hint: "62" },
{ value: "presentation", label: "Presentations", hint: "41" },
]}
layout="fit"
/>,
);

expect(screen.getByRole("radio", { name: "All (62)" })).toHaveAttribute("aria-checked", "true");
expect(screen.getByRole("radio", { name: "Presentations (41)" })).toBeInTheDocument();
});

it("keeps a live count opaque and reserves a stable three-digit column", () => {
const renderControl = (hint: string) => (
<SegmentedControl
label="Result type"
value="all"
onChange={() => undefined}
options={[{ value: "all", label: "All", hint }]}
layout="fit"
/>
);
const { rerender } = render(renderControl("9"));

const initialRadio = screen.getByRole("radio", { name: "All (9)" });
const initialHint = within(initialRadio).getByText("9");
expect(initialHint).toHaveClass("min-w-6", "text-right", "tabular-nums");
expect(initialHint).not.toHaveClass("opacity-80");
expect(initialHint.className).not.toContain("--text-soft");
const initialRadioClasses = initialRadio.className;

rerender(renderControl("100"));

const updatedRadio = screen.getByRole("radio", { name: "All (100)" });
expect(updatedRadio.className).toBe(initialRadioClasses);
expect(within(updatedRadio).getByText("100")).toHaveClass("min-w-6", "text-right", "tabular-nums");
});

// A hintless option must not gain stray whitespace or an empty span — the
// existing call sites pass no hint and their names must not drift.
it("leaves an option without a hint unchanged", () => {
render(
<SegmentedControl
label="Result type"
value="all"
onChange={() => undefined}
options={[{ value: "all", label: "All" }]}
layout="fit"
/>,
);

expect(screen.getByRole("radio", { name: "All" })).toBeInTheDocument();
});

it("keeps a controlled disabled value checked instead of remapping to the first enabled option", () => {
render(
<SegmentedControl
Expand Down
Loading