Skip to content

feat(ui): give SegmentedControl an option hint slot - #1848

Merged
BigSimmo merged 11 commits into
mainfrom
claude/segmented-control-count
Aug 12, 2026
Merged

feat(ui): give SegmentedControl an option hint slot#1848
BigSimmo merged 11 commits into
mainfrom
claude/segmented-control-count

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

Prerequisite for the filter contract rollout (docs/filter-contract.md, PR #1847). The four one-of-N mode rails about to converge onto this primitive — differentials, medication, applications, specifiers — all carry a per-option count, and SegmentedControlOption had nowhere to put one. Baking it into label would fold the number into the truncating span and lose the tabular alignment.

Landing it separately because it depends on none of the contract work, and it turns each mode adoption into a pure call-site change.

  • Adds optional hint?: string to SegmentedControlOption, rendered as a nums tabular-nums sibling span so the rail does not reflow as a live count crosses a digit boundary.
  • Mirrors ResultFilterOption.hint, so a mode can build one option array and hand it to both the desktop rail and the phone sheet — which is the duplication the rollout is removing (differentials currently declares its three counts twice, once as count and once as hint).

Two findings worth recording

The hint needs an explicit aria-label. Adjacent label and hint spans concatenate to "All62" in the computed accessible name, and a text-node separator does not survive — the name computation normalises inter-element whitespace away. I tried the whitespace fix first and measured that it did not work. The button is now named "All (62)", which is the shape the differentials rail already used before this. Both behaviours are pinned, including that a hintless option's name does not drift, since every existing call site passes no hint.

No .design-sync regeneration is needed — contrary to what I expected going in. dtsPropsFor.SegmentedControl references SegmentedControlOption<string>by name rather than expanding it, so adding a field leaves the pinned props string unchanged. Verified rather than assumed: all 56 tests in tests/design-sync-visual-exports.test.ts pass untouched.

Carried forward, not smuggled in

ResultFilterSheet renders option.hint the same adjacent-span way, so its options announce "Crisis12" today. Same defect, same fix — tracked with the rollout rather than folded into an unrelated primitive PR.

Verification

  • npm run verify:pr-locallint, typecheck and test green. build failed once on a corrupt generated .next/dev/types/validator.ts; that was the dev server writing into .next while it was being deleted, not this diff. Re-run with the server stopped and a clean .next:
Client bundle secret surface check passed. # npm run build, exit 0
Offline RAG fixture and manifest validation passed (36 golden cases, 23 suites).
  • tests/ui-v2-components.dom.test.tsx80 passed, including two new tests: the hint renders and reaches the accessible name as "All (62)", and a hintless option's name is unchanged.
  • tests/design-sync-visual-exports.test.ts — 56 passed, no config regeneration.
  • npm run check:bundle-budget after rm -rf .next && npm run build — production 1302.5 KiB and mockups 286.5 KiB, both within tolerance.

No UI verification: no call site passes hint yet, so there is no rendered change. git diff --stat origin/main is the primitive, its tests, and the next dev AGENTS.md block. Browser proof belongs on the first adoption PR.

Risk and rollout

  • Risk: Low. One optional field and one conditional span; every existing call site omits hint and renders identically. The aria-label is only set when a hint is present, so no existing accessible name changes.
  • Rollback: revert the commit; nothing consumes the new field.
  • Provider or production effects: None.

Next in the rollout, once #1847 lands: one PR adopting kind: "lens" across differentials, medication, applications and specifiers, replacing each mode's aria-pressed desktop rail with this control so the two breakpoints stop disagreeing about whether the dimension is one-of-N.


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Segmented controls can now display optional right-aligned hints, such as counts, alongside each option.
    • Hints use consistent spacing and remain visually stable as values change.
    • Screen readers can access each option’s label and hint together.
  • Bug Fixes

    • Improved hint contrast and layout consistency.
    • Hints are omitted cleanly when no value is provided.
  • Tests

    • Added coverage for hint visibility, accessibility labels, styling, dynamic counts, and absent hints.
  • Documentation

    • Updated development guidance and review records.

Prerequisite for the filter contract rollout (docs/filter-contract.md,
PR #1847). The four one-of-N mode rails that are about to converge onto
this primitive — differentials, medication, applications, specifiers —
all carry a per-option count, and the primitive had nowhere to put one.
Baking it into `label` would fold the number into the truncating span
and lose the tabular alignment.
Landing it separately because it depends on none of the contract work,
and it makes each adoption a pure call-site change.
Two things worth knowing:
- The hint needs an explicit aria-label. Adjacent label and hint spans
concatenate to "All62" in the computed accessible name, and a text
node separator does not survive — the name computation normalises
inter-element whitespace away. The button is therefore named
"All (62)", the shape the differentials rail already used. Both
behaviours are pinned by tests, including that a hintless option's
name does not drift.
- No .design-sync regeneration is needed, contrary to expectation:
dtsPropsFor.SegmentedControl references SegmentedControlOption<string>
by name rather than expanding it, so adding a field leaves the pinned
props string unchanged. Verified — all 56 design-sync tests pass.
Note for the adoption PRs: ResultFilterSheet renders option.hint the
same adjacent-span way, so its options announce "Crisis12" today. Same
fix, tracked with the rollout rather than smuggled in here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011btGFwWKYFWDs5McQkqz9J
@supabase

supabaseBot commented Aug 12, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in:39 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af710012-7ea5-46eb-aa6c-93a730cec6be

📥 Commits

Reviewing files that changed from the base of the PR and between c528a94 and fa24dde.

📒 Files selected for processing (2)
  • docs/branch-review-ledger.md
  • src/components/ui/segmented-control.tsx
📝 Walkthrough

Walkthrough

The PR adds optional hints to SegmentedControlOption, renders hints with stable numeric alignment, includes hints in accessible radio names, and adds DOM coverage. It also updates Next.js agent guidance and appends review ledger records.

Changes

SegmentedControl hint support

Layer / File(s)Summary
Hint contract and rendering
src/components/ui/segmented-control.tsx
SegmentedControlOption<T> now accepts an optional hint. Hinted options expose label (hint) as the accessible name and render a fixed-width, right-aligned tabular-numeral hint.
Hint validation and review records
tests/ui-v2-components.dom.test.tsx, docs/branch-review-ledger.md
DOM tests cover hint output, accessible names, styling, changing counts, and omitted hints. The ledger records the implementation and follow-up fixes.

Next.js agent guidance

Layer / File(s)Summary
Generated Next.js guidance
AGENTS.md
The guidance resolves local documentation from the file directory, notes monorepo package visibility, and identifies regeneration through next dev and generate-agent-files.js.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
participant SegmentedControl
participant RadioInput
participant HintSpan
SegmentedControl->>RadioInput: set accessible name to label (hint)
SegmentedControl->>HintSpan: render hint with reserved numeric column
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely describes the addition of an option hint slot to SegmentedControl.
Description check✅ PassedThe description follows the template and documents the change, verification results, risk, rollback, and production effects.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/segmented-control-count

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011btGFwWKYFWDs5McQkqz9J
Regenerated by the production build run for this PR's bundle-budget
check. Written by next dev via
node_modules/next/dist/server/lib/generate-agent-files.js; left
uncommitted it re-appears on every build and leaves a dirty tree.
Its own commit rather than folded into the SegmentedControl change, so
it can be dropped independently. Note this block is not yet on main —
PR #1846 also carries it, so whichever lands first makes the other a
no-op.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011btGFwWKYFWDs5McQkqz9J

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:d5ff316024

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment threadsrc/components/ui/segmented-control.tsx Outdated
Comment threadsrc/components/ui/segmented-control.tsx Outdated
@github-actions

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 2 failed job(s):

  • Static PR checksneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #9872 (success).

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

@BigSimmo
BigSimmo enabled auto-merge August 12, 2026 12:49
claudeand others added 3 commits August 12, 2026 12:52
…ft alias
CI Static PR checks failure: --text-soft is a decoration-only
compatibility alias that production must not consume, and
check:design-system-contract ratchets its consumer count at zero
(textSoftConsumers increased from 0 to 1).
--text-muted is what ResultFilterSheet already uses for option.hint, so
this also makes the desktop rail and the phone sheet render the same
count the same way, which was the point of adding the slot.
Same root cause as the radius fix on #1847: lifted from the mockup
study, where the token gates do not apply. Also merges origin/main to
clear the 26-commit staleness that failed PR mergeability.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011btGFwWKYFWDs5McQkqz9J

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/ui-v2-components.dom.test.tsx (1)

396-410: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that hintless options render no hint span.

Line 409 verifies only the radio accessible name. A regression could still render an empty .tabular-nums span and pass this test while adding width or spacing. Add an assertion for the absence of the hint span.

Proposed test assertion
- expect(screen.getByRole("radio", { name: "All" })).toBeInTheDocument();+ const radio = screen.getByRole("radio", { name: "All" });+ expect(radio).toBeInTheDocument();+ expect(radio.querySelector(".tabular-nums")).not.toBeInTheDocument();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/ui-v2-components.dom.test.tsx` around lines 396 - 410, The hintless
option test should also verify that no hint span is rendered. In the test
“leaves an option without a hint unchanged,” assert that the hint-specific
`.tabular-nums` element is absent while preserving the existing accessible-name
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/ui-v2-components.dom.test.tsx`:
- Around line 396-410: The hintless option test should also verify that no hint
span is rendered. In the test “leaves an option without a hint unchanged,”
assert that the hint-specific `.tabular-nums` element is absent while preserving
the existing accessible-name assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f069826d-c1a3-4350-afb3-1efb4d7f8bdd

📥 Commits

Reviewing files that changed from the base of the PR and between 34a2f9f and c528a94.

📒 Files selected for processing (4)
  • AGENTS.md
  • docs/branch-review-ledger.md
  • src/components/ui/segmented-control.tsx
  • tests/ui-v2-components.dom.test.tsx

BigSimmoand others added 5 commits August 12, 2026 21:08
Both heads fixed the same CI failure — production must not consume the
decoration-only text alias — and theirs is the better fix, so this
resolves entirely in its favour and drops mine.
Theirs removes the hint's own colour so it inherits the button's opaque
foreground, which sidesteps the alias without introducing a second token
that would have to stay legible against both the checked and unchecked
backgrounds. It also reserves a three-digit column with min-w-6 and
text-right: tabular figures keep digit widths equal but do not stop the
span growing at 9 -> 10, so without it a live count still moves the
segment bounds. That was a real gap in my version.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011btGFwWKYFWDs5McQkqz9J
@BigSimmo
BigSimmo merged commit 1728e6f into mainAug 12, 2026
26 checks passed
@BigSimmo
BigSimmo deleted the claude/segmented-control-count branch August 12, 2026 13:35
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@BigSimmo@claude