feat(web): redesign usage insights#7147

merged8
main
ui-refresh/usage
openedAug 16, 2026byMmaria
View on GitHubTwo-way sync enabledPR last imported 3d ago

What changed

  • Reworks Usage around provider summaries, API cost estimates, hourly and daily curves, clamped hover details, and a simpler totals breakdown.
  • Adds per-provider session counts while retaining source-fingerprint ownership so the same transcript directory is counted once across environments.
  • Uses the shared workspace header from #7153, introduces a content frame shared by Settings and Usage, and adopts segmented controls.

Screenshots

Direct parent on the left; this PR on the right. Same viewport and copied application state.

BeforeAfter
Usage page before Usage page after
## Why

Usage was mixed with unrelated workspace, Pull Requests, terminal, and tool-call work. This layer keeps the presentation and its small shared aggregation change together for one review.

Validation

  • UsageProviderChart tests
  • usageMerge tests, including duplicate-source ownership and empty-provider handling
  • Affected-package typechecks
  • Changed-file lint and formatting checks

Stack order

  1. #7153 workspace and navigation
  2. #7147 Usage, this PR
  3. #7148 Pull Requests
  4. #7149 terminal
  5. #7150 composer
  6. #7151 tool lifecycle projection
  7. #7152 tool activity UI

Built with GPT-5.6-sol in the Codex harness.


Note

Medium Risk Changes usage aggregation (ProviderTotals.sessions, provider list shape) and a large Usage UI refactor; merge logic is tested but displayed totals/session labels depend on correct per-provider session attribution across environments.

Overview Usage moves metric, time window, and breakdown controls into the shared workspace headerβ€”segmented ToggleGroups on large screens and compact Selects on small onesβ€”with the active date range shown in the breadcrumb.

The page layout uses WorkspacePageHeader and WorkspacePageContainer (wide), simplifies the headline and provider sidebar (stable PROVIDER_ORDER, session counts, chart color dots; no metric-based reordering or share bars), trims the totals strip, and swaps custom bordered buttons for segmented toggles in the breakdown section.

Shared UI: new WorkspacePageContainer with readable / wide / expanded max-widths; Settings adopts it instead of local width classes. Toggle / ToggleGroup gain a segmented variant (default size when variant is segmented). WorkspaceBreadcrumbSeparator accepts an optional className.

Data / chart: usageMerge adds sessions per provider (from owned source distinctSessions, still deduped by fingerprint) and tests for empty-provider omission. UsageProviderChart repositions the hover tooltip from cursor-local coordinates (clamped, ResizeObserver); UsageChartLegend is removed.

Reviewed by Cursor Bugbot for commit 3fa0aedff1dad360959cfb31658c3243a01d5220. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Redesign the Usage Insights page with responsive controls, provider session counts, and unified header components

  • Replaces the Usage page layout with a breadcrumb header containing responsive metric/period controls (segmented toggles on desktop, selects on mobile), removing the in-chart legend and metric toggle
  • Adds per-provider session counts to mergeUsage output via a new sessions field on ProviderTotals; providers with no sessions and no usage are omitted from results
  • Simplifies the Totals section to show only label/value pairs, removing period averages, cache share details, and provider bar visualizations; providers now display in fixed PROVIDER_ORDER
  • Introduces shared layout primitives: WorkspacePageHeader (standardizes electron drag-region and native-control insets) and WorkspacePageContainer (standardized width presets), replacing bespoke header elements across chat, settings, and onboarding routes
  • Moves the settings sidebar Back button into a new SidebarUtilityMenu component shared with the main sidebar footer; the back action now prefers window.history.back() before falling back to navigating to /
  • Adds segmented size and variant to Toggle/ToggleGroup for the new control styling
  • Behavioral Change: Metric component no longer renders a detail line; UsageSkeleton no longer accepts a resolution prop

Macroscope summarized 8156179.

Synced from GitHub

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e5ee8af-399e-4f05-89fe-808aef67f105

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

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

Synced from GitHub

Thread transfer impact

βœ… Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiBβˆ’15 B (βˆ’0.1%)15.1 KiBβœ…
CodexThread snapshot wire6.3 KiB6.3 KiBβˆ’4 B (βˆ’0.1%)7.3 KiBβœ…
CodexLive turn WebSocket wire6.3 KiB6.3 KiBβˆ’11 B (βˆ’0.2%)7.8 KiBβœ…
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiBβœ…
CodexLive turn messages16160 (0.0%)21βœ…
ClaudeTotal thread wire12.6 KiB12.6 KiB+12 B (+0.1%)15.1 KiBβœ…
ClaudeThread snapshot wire6.3 KiB6.3 KiB+13 B (+0.2%)7.3 KiBβœ…
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiBβˆ’1 B (βˆ’0.0%)7.8 KiBβœ…
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiBβœ…
ClaudeLive turn messages16160 (0.0%)21βœ…

Baseline: 07f8027 Β· PR result: 3fa0aed Β· Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

MmacroscopeappBotreviewed Β· 2 inline threadsAug 16, 2026
Synced from GitHub

Two consistency findings in the Usage refresh: a hand-rolled glass surface that bypasses the shared glass contract, and a call-site override of the shared workspace topbar height.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsageProviderChart.tsxlines 411–411Outdated Β· e093685Open thread
……@@ -373,10 +407,11 @@ export function UsageProviderChart({
375409 <div
376- className="pointer-events-none absolute top-0 z-10 min-w-36 border border-border bg-background/95 px-2 py-1.5 text-xs"
410+ ref={tooltipRef}
411+ className="pointer-events-none absolute z-10 min-w-36 max-w-full rounded-xl border border-border/50 bg-background/65 px-2.5 py-2 text-xs shadow-lg backdrop-blur-xl backdrop-saturate-150"
MmacroscopeappBoton lines 411–411Aug 16, 2026

usage/UsageProviderChart.tsx:411

This hand-rolls a glass surface instead of using the shared glass composition. Every other translucent floating surface in apps/web goes through the surface-glass / dropdown-glass utilities (index.css), which are driven by --glass-opacity, --glass-blur and --glass-saturation and are re-tuned under @variant dark. bg-background/65 backdrop-blur-xl backdrop-saturate-150 pins those values at the call site, so the tooltip is more transparent than the token contract (80%) over the coloured chart fills and does not pick up the dark-theme glass adjustments. Consider keeping only the border/radius/shadow locally and letting the utility own the glass:

suggestion
1 className="surface-glass pointer-events-none absolute z-10 min-w-36 max-w-full rounded-xl border border-border/50 px-2.5 py-2 text-xs shadow-lg"

No longer relevant as of acf68768ff7a71042c0b70d826ce9edc8d8e04e4

apps/web/src/components/usage/UsagePage.tsxlines 147–159Outdated Β· e093685Open thread
MmacroscopeappBoton lines 147–159Aug 16, 2026

usage/UsagePage.tsx:147

h-auto py-2 overrides the core geometry WorkspacePageHeader owns. That height is not just visual: --workspace-topbar-height resolves to env(titlebar-area-height) under .wco, and the fixed sidebar-toggle / native-control strips are sized and positioned from the same variable (AppSidebarLayout.tsx, ChatView.tsx). Because the header content is flex-wrap with basis-full below sm, the header (and the Electron drag region) grows past the titlebar area on narrow widths, and the vertically centred breadcrumb no longer lines up with those overlay controls β€” a divergence from every other workspace surface (settings, project settings, pull requests), and the reason the onboarding header is regression-tested against py- in routes/-chatIndexTitlebar.test.ts.

Suggest dropping the override and keeping the shared topbar height, condensing the controls when space is tight instead of growing the bar (e.g. SegmentedTab density="compact", or a compact menu like the Pull Requests header uses).

No longer relevant as of acf68768ff7a71042c0b70d826ce9edc8d8e04e4

packages/shared/src/usageMerge.tsline 279Outdated Β· e093685Open thread
……@@ -255,12 +263,20 @@ export function mergeUsage(
276+ };
277+ provider.sessions += providerSessions;
278+ providerAccumulator.set(providerKind, provider);
279+ }
CcursorBoton line 279Aug 16, 2026

Zero-activity providers enter merge

Low Severity

The new session loop writes every owned source into providerAccumulator, including scanned directories with distinctSessions of 0 and no buckets. merged.providers can therefore include all-zero rows. Callers that hide the provider list when it is empty, such as mobile, now show unused providers as $0 / 0 sessions.

Additional Locations (1)

Reviewed by Cursor Bugbot for commit e093685b241e8da2a56784b57fe113095f559688. Configure here.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable β€” This PR redesigns the usage insights page with new UI components, a segmented toggle variant, and changes to how session counts are aggregated per provider. As a feature introducing new components and data structure changes, it warrants human review.

No code changes detected at 3fa0aed. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

MmacroscopeappBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

UI Consistency

One finding: the new compact header menu duplicates an existing header control instead of routing through a shared primitive. The two items flagged on the previous run (header height override on WorkspacePageHeader, hand-rolled glass on the chart tooltip) are resolved on this head β€” the header now uses the primitive's geometry unchanged and the tooltip composes surface-glass.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 61–63Outdated Β· acf6876Open thread
……@@ -34,6 +44,40 @@ const WINDOW_OPTIONS = [
60+ <Menu>
61+ <MenuTrigger
62+ aria-label={label}
63+ className="inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
MmacroscopeappBoton lines 61–63Aug 16, 2026

usage/UsagePage.tsx:63

UsageHeaderMenu re-creates CompactFilterMenu (apps/web/src/routes/_chat.pull-requests.tsx:1619) β€” same component shape, same MenuTrigger + MenuRadioGroup wiring, and a byte-identical trigger class string (git grep "inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-1.5" now matches exactly these two sites).

That trigger geometry and muted-ghost treatment is durable, shared header-bar styling, so it belongs in one named owner rather than a copied call-site string: the height, radius, padding, hover colors and chevron tone can only drift apart from here, and neither copy picks up Button's cursor/focus/hit-target contract. Consider extracting the compact menu (or at least the trigger) into a shared component β€” e.g. MenuTrigger render={<Button size="xs" variant="ghost-muted" />} inside one HeaderFilterMenu β€” and leaving only popup align/min-w-* and the option list at each call site.

No longer relevant as of 5e96339b38370155e7f0227d7d5a8fed3f0aaad5

apps/web/src/components/HeaderFilterMenu.tsxlines 41–41Outdated Β· 2ec34dcOpen thread
……@@ -0,0 +1,59 @@
38+ <ChevronDownIcon aria-hidden className="size-3 text-muted-foreground/70" />
39+ </MenuTrigger>
40+ <MenuPopup align={align} side="bottom" className={popupClassName ?? "min-w-40"}>
41+ <MenuRadioGroup value={value} onValueChange={(next) => onChange(next as Value)}>
MmacroscopeappBoton lines 41–41Aug 16, 2026

🟑 Medium components/HeaderFilterMenu.tsx:41

When value is absent from options, the trigger displays options[0] but MenuRadioGroup receives the unmatched value, so no radio item is selected and the filter UI shows conflicting state. Pass the normalized current.value to MenuRadioGroup so the displayed label and selection stay aligned.

βœ… Resolved in 4e4d9b33a0846e071081993ad5fc0b0654b97853

MmacroscopeappBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

One finding: the usage chart is now the only place provider colors appear, and this PR removes every static key for them.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 221–247Outdated Β· 2ec34dcOpen thread
MmacroscopeappBoton lines 221–247Aug 16, 2026

usage/UsagePage.tsx:221

The chart still encodes each provider purely by color (PROVIDER_PRESENTATION[provider].color drives the fills and strokes), but this PR deletes both UsageChartLegend and the colored share bars that used to live in these summary rows, so color is no longer rendered anywhere outside the chart itself. The only remaining color-to-provider mapping is the hover tooltip, which is mouse-only (UsageProviderChart binds onMouseMove/onMouseLeave and nothing pointer/touch/keyboard), so on touch and keyboard the two series become unidentifiable.

Smallest fix is to keep a static swatch next to the provider label here (or restore a legend next to the chart heading) so the mapping survives without hover:

diff
1 <span className="flex min-w-0 items-center gap-2 text-sm text-foreground">
2+ <span
3+ aria-hidden
4+ className="size-2 shrink-0 rounded-full"
5+ style={{ backgroundColor: PROVIDER_PRESENTATION[provider].color }}
6+ />
7 <ProviderMark provider={provider} className="size-4" />

No longer relevant as of 4e4d9b33a0846e071081993ad5fc0b0654b97853

MmacroscopeappBotreviewed Β· 2 inline threadsAug 16, 2026
Synced from GitHub

UI consistency review: the earlier glass-surface and topbar-geometry issues are resolved (the tooltip now uses surface-glass, and the header goes through WorkspacePageHeader without overriding its height). Two remaining findings, both about new shared API surface introduced here.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/segmented-tabs.tsxlines 26–26Outdated Β· 2ec34dcOpen thread
……@@ -0,0 +1,40 @@
23+ ...props
24+}: {
25+ selected: boolean;
26+ density?: "default" | "compact";
MmacroscopeappBoton lines 26–26Aug 16, 2026

density="compact" (and the segmented-compact size it selects in toggle.tsx) has no call site β€” all three usages here use the default density. Adding a size to the shared toggleVariants contract that nothing renders means its geometry is never validated against a real surface. Suggest dropping density and the segmented-compact size until a second surface needs the smaller pill, and adding them back with that consumer.

Posted via Macroscope β€” UI Consistency

Fixed by removing the unused compact density and segmented-tabs wrapper.

apps/web/src/components/HeaderFilterMenu.tsxlines 32–34Outdated Β· 2ec34dcOpen thread
……@@ -0,0 +1,59 @@
31+ <Menu>
32+ <MenuTrigger
33+ render={
34+ <Button size="compact" variant="ghost-muted" aria-label={label} className="text-sm" />
MmacroscopeappBoton lines 32–34Aug 16, 2026

HeaderFilterMenu is the same control as CompactFilterMenu (apps/web/src/routes/_chat.pull-requests.tsx:1619): same Menu + MenuTrigger + MenuRadioGroup shape, same chevron glyph, and the optional icon / disabledReason props map one-to-one onto that component's option.Icon / option.unavailable while having no consumer on this page.

Leaving both means the same header filter control now exists twice with divergent trigger behavior: this one inherits the Button compact / ghost-muted contract (focus-visible ring, coarse-pointer hit target, muted icon color), while the pull-requests copy keeps a hand-rolled trigger class string with no focus ring. Consider migrating those three call sites to this component (Icon -> icon, unavailable -> disabledReason) and deleting the local copy, so the two prop generalizations have the consumer they were shaped for. If that migration is out of scope for this PR, dropping icon and disabledReason until something uses them keeps the new contract to what it actually serves.

Posted via Macroscope β€” UI Consistency

Fixed by removing the duplicate HeaderFilterMenu and using the shared control.

apps/web/src/components/HeaderFilterMenu.tsxlines 21–21Outdated Β· 8f2efdaOpen thread
……@@ -0,0 +1,48 @@
18+}: {
19+ label: string;
20+ value: Value;
21+ options: ReadonlyArray<HeaderFilterMenuOption<Value>>;
MmacroscopeappBoton lines 21–21Aug 16, 2026

🟑 Medium components/HeaderFilterMenu.tsx:21

Passing options={[]} makes HeaderFilterMenu throw while rendering because current becomes undefined and current.label is read. The public ReadonlyArray prop type permits this value even though the component requires at least one option; require a non-empty tuple (or handle the empty state).

βœ… Resolved in e8d00a2e96aafa9eaf8d8922a94c62e2d2ccede4

MmacroscopeappBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

One consistency finding on the new SegmentedTabList primitive. Earlier findings from previous runs (glass tooltip, header geometry, provider color encoding, unused density size) look addressed; the HeaderFilterMenu / CompactFilterMenu duplication flagged previously is still open but not re-reported here.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/segmented-tabs.tsxlines 6–17Outdated Β· 8f2efdaOpen thread
……@@ -0,0 +1,38 @@
14+ {...props}
15+ />
16+ );
17+}
MmacroscopeappBoton lines 6–17Aug 16, 2026

SegmentedTabList reconstructs ToggleGroup (apps/web/src/components/ui/toggle-group.tsx), the primitive the other single-select segmented controls already compose (DiffPanel.tsx:752, PullRequestCodeTab.tsx:1098). A raw role="group" div with per-item pressed/onClick drops what the Base UI group owns: value/onValueChange single-selection semantics and the composite keyboard model (one tab stop, arrow keys to move between segments β€” here each segment is its own tab stop and arrows do nothing), plus the group's coarse-pointer hit-target normalisation (*:pointer-coarse:after:min-w-auto), so on touch each 24px-tall pill keeps a 44px ::after overlay that spills onto its neighbour and the content below.

Since the segmented size/variant you added live on the shared toggleVariants, ToggleGroupItem can already wear them; suggest keeping the track styling (rounded-lg bg-muted/45 p-0.5) as a ToggleGroup className and letting variant === "segmented" take the gap-0.5 branch rather than the joined-border branch, instead of shipping a second group primitive next to the existing one.

Posted via Macroscope β€” UI Consistency

Fixed. Usage now composes the existing Base UI ToggleGroup instead of a second group implementation.

MmacroscopeappBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

One consistency finding on the new SegmentedTabList primitive. Earlier findings from previous runs (glass tooltip, header geometry, provider color encoding, unused density size) look addressed; the HeaderFilterMenu / CompactFilterMenu duplication flagged previously is still open but not re-reported here.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/segmented-tabs.tsxlines 6–17Outdated Β· 8f2efdaOpen thread
……@@ -0,0 +1,38 @@
14+ {...props}
15+ />
16+ );
17+}
MmacroscopeappBoton lines 6–17Aug 16, 2026

SegmentedTabList reconstructs ToggleGroup (apps/web/src/components/ui/toggle-group.tsx), the primitive the other single-select segmented controls already compose (DiffPanel.tsx:752, PullRequestCodeTab.tsx:1098). A raw role="group" div with per-item pressed/onClick drops what the Base UI group owns: value/onValueChange single-selection semantics and the composite keyboard model (one tab stop, arrow keys to move between segments β€” here each segment is its own tab stop and arrows do nothing), plus the group's coarse-pointer hit-target normalisation (*:pointer-coarse:after:min-w-auto), so on touch each 24px-tall pill keeps a 44px ::after overlay that spills onto its neighbour and the content below.

Since the segmented size/variant you added live on the shared toggleVariants, ToggleGroupItem can already wear them; suggest keeping the track styling (rounded-lg bg-muted/45 p-0.5) as a ToggleGroup className and letting variant === "segmented" take the gap-0.5 branch rather than the joined-border branch, instead of shipping a second group primitive next to the existing one.

Posted via Macroscope β€” UI Consistency

Fixed with the same migration to the shared ToggleGroup primitive.

MmacroscopeappBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

One finding: the new segmented variant splits ownership of the segmented control's appearance between the primitive (items) and three identical call-site strings (track). Details inline.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/toggle-group.tsxline 33Outdated Β· be5b41cOpen thread
……@@ -30,7 +30,7 @@ function ToggleGroup({
3131 ? "*:pointer-coarse:after:min-w-auto"
3232 : "*:pointer-coarse:after:min-h-auto",
33- variant === "default"
33+ variant === "default" || variant === "segmented"
MmacroscopeappBoton line 33Aug 16, 2026

ui/toggle-group.tsx:33

The new segmented variant only owns the item styling here β€” the track (rounded-lg bg-muted/45 p-0.5) is repeated verbatim at all three call sites this PR adds (UsagePage.tsx:116, :133, :306). That splits one treatment across two owners: variant="segmented" alone renders a transparent, unpadded group, so the next surface that reaches for the variant gets pressed pills with no track unless it copies the same string, and re-tuning the track means editing every consumer.

Suggest folding the track into the segmented branch and keeping only contextual layout at the call sites:

diff
1- variant === "default" || variant === "segmented"
2- ? "gap-0.5"
3+ variant === "segmented"
4+ ? "gap-0.5 rounded-lg bg-muted/45 p-0.5"
5+ : variant === "default"
6+ ? "gap-0.5"

(the existing orientation branches then nest one level deeper), and drop className="rounded-lg bg-muted/45 p-0.5" from the three ToggleGroup usages.

No longer relevant as of 9ac145fda6f2ac9c93d06e06357b4627b1c38515

MmacroscopeappBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

One finding: the new segmented variant splits ownership of the segmented control's appearance between the primitive (items) and three identical call-site strings (track). Details inline.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/toggle-group.tsxline 33Outdated Β· be5b41cOpen thread
……@@ -30,7 +30,7 @@ function ToggleGroup({
3131 ? "*:pointer-coarse:after:min-w-auto"
3232 : "*:pointer-coarse:after:min-h-auto",
33- variant === "default"
33+ variant === "default" || variant === "segmented"
MmacroscopeappBoton line 33Aug 16, 2026

ui/toggle-group.tsx:33

The new segmented variant only owns the item styling here β€” the track (rounded-lg bg-muted/45 p-0.5) is repeated verbatim at all three call sites this PR adds (UsagePage.tsx:116, :133, :306). That splits one treatment across two owners: variant="segmented" alone renders a transparent, unpadded group, so the next surface that reaches for the variant gets pressed pills with no track unless it copies the same string, and re-tuning the track means editing every consumer.

Suggest folding the track into the segmented branch and keeping only contextual layout at the call sites:

diff
1- variant === "default" || variant === "segmented"
2- ? "gap-0.5"
3+ variant === "segmented"
4+ ? "gap-0.5 rounded-lg bg-muted/45 p-0.5"
5+ : variant === "default"
6+ ? "gap-0.5"

(the existing orientation branches then nest one level deeper), and drop className="rounded-lg bg-muted/45 p-0.5" from the three ToggleGroup usages.

No longer relevant as of 9ac145fda6f2ac9c93d06e06357b4627b1c38515

CcursorBotreviewed Β· 1 inline threadAug 16, 2026
Synced from GitHub

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f399a1b81e49fc256f12d16b309d6958be95921a. Configure here.

apps/web/src/components/usage/UsagePage.tsxline 180Outdated Β· f399a1bOpen thread
……@@ -100,78 +95,103 @@ export function UsagePage() {
177+ <RefreshCwIcon className="size-3.5" />
178+ </Button>
179+ </WorkspacePageHeaderEdgeControl>
180+ </div>
CcursorBoton line 180Aug 16, 2026

Electron header controls still drag

Medium Severity

Metric, period, and refresh controls were moved into WorkspacePageHeader with electron={isElectron}, which applies drag-region. Shared CSS only marks button/a/inputs as no-drag, so padding and gaps around the segmented groups still start a window drag. Elsewhere (DiffPanel) interactive header clusters wrap the whole control row in [-webkit-app-region:no-drag].

Additional Locations (1)

Reviewed by Cursor Bugbot for commit f399a1b81e49fc256f12d16b309d6958be95921a. Configure here.

MmacroscopeappBotreviewed Β· 1 inline threadAug 17, 2026
Synced from GitHub

One finding on the new shared segmented toggle treatment: the pressed pill and its track resolve to the same token in the default dark theme, so the selected segment loses its background signal. Details inline.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/toggle.tsxlines 32–33Outdated Β· 5e9c854Open thread
……@@ -27,6 +29,8 @@ const toggleVariants = cva(
2830 outline:
2931 "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:data-pressed:bg-input dark:hover:bg-input/64 dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:not-disabled:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] [:disabled,:active,[data-pressed]]:shadow-none",
32+ segmented:
33+ "border-transparent text-muted-foreground shadow-none transition-colors before:shadow-none hover:bg-accent/45 hover:text-foreground data-pressed:bg-accent data-pressed:text-foreground data-pressed:shadow-xs/5",
MmacroscopeappBoton lines 32–33Aug 17, 2026

ui/toggle.tsx:33

The pressed pill and the group track resolve to the same token in the default dark theme: index.css sets --muted and --accent both to --alpha(var(--color-white) / 4%) under @variant dark (:root), so the track bg-muted/45 (added in toggle-group.tsx) computes to ~1.8% white while data-pressed:bg-accent computes to 4% white β€” roughly #0e0e0e vs #141414 over neutral-950, and data-pressed:shadow-xs/5 adds nothing on a dark surface. hover:bg-accent/45 computes to exactly the track color, so a hovered unselected segment ends up looking like the selected one (both get text-foreground). All three new controls (UsagePage.tsx metric, period, breakdown) then communicate selection by text color alone in dark mode.

Suggest lifting the pressed/hover states with the token the primitive already uses for pressed state (bg-input/64 in the base class and dark:data-pressed:bg-input in outline), which stays above muted in both themes; data-pressed:bg-background plus a dark-only lift would work too if a white pill is wanted in light mode.

suggestion
1 segmented:
2 "border-transparent text-muted-foreground shadow-none transition-colors before:shadow-none hover:bg-input/32 hover:text-foreground data-pressed:bg-input/64 data-pressed:text-foreground data-pressed:shadow-xs/5",

No longer relevant as of 8156179d91f5f5bd9bb77ef8766f729f6d7dfe00

MmacroscopeappBotreviewed Β· 2 inline threadsAug 17, 2026
Synced from GitHub

Two consistency findings on the new Usage header and loading state. Details inline.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 106–110Outdated Β· d586e6dOpen thread
……@@ -102,78 +93,121 @@ export function UsagePage() {
107+ <span aria-hidden="true" className="text-icon-muted">
108+ /
109+ </span>
110+ <span className="truncate">{windowLabel}</span>
MmacroscopeappBoton lines 106–110Aug 17, 2026

usage/UsagePage.tsx:110

The / between crumbs is hand-rolled here, but the breadcrumb module already exports WorkspaceBreadcrumbSeparator, which every other workspace header uses (_chat.pull-requests.tsx:1861, SettingsBreadcrumb.tsx:26, ProjectSettingsPanel.tsx:215) and which renders the glyph as its own aria-hidden <li> with the text-icon-muted treatment. Nesting a <span aria-hidden> inside the crumb <li> re-declares that treatment at the call site and gives this header a different <ol> structure from the rest of the app.

The reason for the local copy looks like the responsive hide, since the shared separator takes no className. Suggest adding an optional className to WorkspaceBreadcrumbSeparator and rendering <WorkspaceBreadcrumbSeparator className="hidden md:flex" /> before the window crumb, so the separator stays in one owner.

No longer relevant as of ad49e563b67e030ef8846428afe158f1247b4583

apps/web/src/components/usage/UsagePage.tsxlines 587–588Outdated Β· d586e6dOpen thread
……@@ -571,70 +556,51 @@ function UsageDeviceStrip({
623- ))}
624- </div>
587+ <div className="h-4 w-24 rounded-sm bg-muted" />
588+ <div className="h-56 rounded-sm bg-muted/35" />
MmacroscopeappBoton lines 587–588Aug 17, 2026

usage/UsagePage.tsx:588

This placeholder no longer matches the geometry of the chart it stands in for: UsageProviderChart renders a w-14 axis gutter plus gap-2 beside the h-56 plot and an x-axis label row under it (UsageProviderChart.tsx:327-457), and the loaded heading is a text-sm h2 (20px), so the skeleton is ~20px shorter and its block starts 64px further left than the real chart. The section β€” and everything below it once the columns stack under lg β€” shifts when the last device answers, which is what the mirrored skeleton geometry existed to prevent.

Suggest reserving the gutter and the label row:

suggestion
1 <div className="h-5 w-24 rounded-sm bg-muted" />
2 <div className="flex flex-col gap-1">
3 <div className="ml-16 h-56 rounded-sm bg-muted/35" />
4 <div className="ml-16 h-3 rounded-sm bg-muted/25" />
5 </div>

No longer relevant as of ad49e563b67e030ef8846428afe158f1247b4583

MmacroscopeappBotreviewed Β· 2 inline threadsAug 17, 2026
Synced from GitHub

Reviewed the usage refresh against the shared component system. Two ownership/consistency findings on the new shared pieces; the earlier tooltip-glass, breadcrumb-separator and segmented-track points are addressed in this revision.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/WorkspacePageContainer.tsxlines 56–61Outdated Β· 858c85cOpen thread
……@@ -52,3 +52,11 @@ export function WorkspacePageHeader({
58+ className,
59+ ...props
60+}: ComponentPropsWithoutRef<"div">) {
61+ return <div className={cn("-me-[7px] flex shrink-0", className)} {...props} />;
MmacroscopeappBoton lines 56–61Aug 17, 2026

components/WorkspacePageContainer.tsx:61

This adds a shared export whose only consumer is UsagePage, and it makes the same header action align differently per page: the Pull Requests header renders the identical ghost refresh button flush inside the header padding with a size-4 glyph (_chat.pull-requests.tsx:1905-1912), and the Settings header places its trailing action flush too (settings.tsx:78-80). The offset is also tied to one call site's geometry rather than a durable contract β€” -me-[7px] optically centers a 28px button around a 14px glyph, which is size="icon-sm" only at sm and up (size-8 sm:size-7), so below sm the button is 32px and the glyph lands 2px inside the edge this helper exists to touch.

Suggest either dropping the wrapper and rendering the button flush like the other workspace headers, or moving the optical inset into WorkspacePageHeader itself so every workspace header's trailing icon action inherits it instead of one page opting in.

No longer relevant as of 4421251ac3996468d6a3cf3a015c9ee06df69557

apps/web/src/components/ui/toggle-group.tsxlines 33–34Outdated Β· 858c85cOpen thread
……@@ -30,11 +30,13 @@ function ToggleGroup({
36- ? "*:not-first:not-data-[slot=separator]:before:-start-[0.5px] *:not-last:not-data-[slot=separator]:before:-end-[0.5px] *:not-first:rounded-s-none *:not-last:rounded-e-none *:not-first:border-s-0 *:not-last:border-e-0 *:not-first:before:rounded-s-none *:not-last:before:rounded-e-none"
37- : "*:not-first:not-data-[slot=separator]:before:-top-[0.5px] *:not-last:not-data-[slot=separator]:before:-bottom-[0.5px] flex-col *:not-first:rounded-t-none *:not-last:rounded-b-none *:not-first:border-t-0 *:not-last:border-b-0 *:not-first:before:rounded-t-none *:not-last:before:rounded-b-none *:data-[slot=toggle]:not-last:before:hidden dark:*:last:before:hidden dark:*:first:before:block",
33+ variant === "segmented"
34+ ? "gap-0.5 rounded-lg bg-input/40 p-0.5"
MmacroscopeappBoton lines 33–34Aug 17, 2026

ui/toggle-group.tsx:34

The segmented treatment is still split across two knobs: the track lands on variant="segmented" here, but the pill geometry it is padded for lives in size="segmented" (toggle.tsx:21-22). Nothing couples them, so every call site has to pass both (UsagePage.tsx:118, :134, :322) and variant="segmented" on its own renders default 36px rounded-lg items inside a p-0.5 track.

Suggest letting the variant own its geometry by defaulting the size from it, so consumers pass one prop:

diff
1 variant = "default",
2- size = "default",
3+ size = variant === "segmented" ? "segmented" : "default",

No longer relevant as of af1fc0e676da1acbee1833e22bbfc2bf30b751a6

MmacroscopeappBotreviewed Β· 1 inline threadAug 17, 2026
Synced from GitHub

One finding: the chart keeps several non-obvious rendering invariants whose rationale was dropped in this pass. Everything else in scope β€” the segmented variant/size coupling, the shared header and breadcrumb separator, the glass tooltip surface, the skeleton geometry, and the table row hover treatment β€” now lines up with the shared primitives and the prevailing repo patterns.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsageProviderChart.tsxline 251Outdated Β· af1fc0eOpen thread
……@@ -260,30 +247,75 @@ export function UsageProviderChart({
267-
268- return { paths: ordered, ticks: tickValues, stepX: step, toY, series: columns };
250+ return {
251+ paths: built.toSorted((a, b) => b.total - a.total),
MmacroscopeappBoton line 251Aug 17, 2026

usage/UsageProviderChart.tsx:251

The layered zero-baseline drawing still depends on three non-obvious invariants whose explanations this PR removes: the scale tops out at the largest single provider-period rather than the summed peak (line 224-228), PLOT_TOP reserves headroom so the constant-width stroke is not shaved off at a peak (line 231), and this sort plus the separate fill and stroke passes (lines 375-392) keep the heavier series from burying the lighter one. With the rationale gone, the sort and the two-pass paint read as arbitrary and are easy to "simplify" away later β€” the same class of regression the niceScale and buildDayColumns tests already guard with explicit comments.

Suggest keeping a one-line reason next to each, e.g. here:

suggestion
1 // Heavier series first so the lighter one is never buried; strokes are
2 // drawn in a second pass regardless, so neither can be hidden.
3 paths: built.toSorted((a, b) => b.total - a.total),

No longer relevant as of 4b69cf3c6748436adfce3203477b6f409aa38897

MmacroscopeappBotreviewed Β· 1 inline threadAug 17, 2026
Synced from GitHub

One finding: the loading skeleton no longer matches the new provider-row geometry, so the rows shift horizontally when data lands. Everything else in the diff (segmented ToggleGroup/Toggle variant now owning both track and pill geometry, WorkspaceBreadcrumbSeparator reuse, WorkspacePageHeader/WorkspacePageContainer adoption, surface-glass tooltip, chart-skeleton gutter alignment) reads consistent with the shared component system.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 573–575Outdated Β· 4b69cf3Open thread
……@@ -571,70 +554,54 @@ function UsageDeviceStrip({
573+ <span className="flex items-center gap-2">
599574 <ProviderMark provider={provider} className="size-4" />
600- {PROVIDER_PRESENTATION[provider].label}
575+ <div className="h-3.5 w-20 rounded-sm bg-muted" />
MmacroscopeappBoton lines 573–575Aug 17, 2026

usage/UsagePage.tsx:575

The loaded provider row now starts with an 8px color dot plus an 8px gap (UsagePage.tsx:239-245), but the skeleton row still begins at the brand mark, so every provider row jumps 16px to the right when the last device answers β€” the shift this skeleton exists to prevent (and the same mismatch that was just fixed for the chart gutter below).

Suggest mirroring the dot here; the skeleton already renders the real ProviderMark, so the real swatch keeps the two states identical:

suggestion
1 <span className="flex items-center gap-2">
2 <span
3 aria-hidden
4 className="size-2 shrink-0 rounded-full"
5 style={{ backgroundColor: PROVIDER_PRESENTATION[provider].color }}
6 />
7 <ProviderMark provider={provider} className="size-4" />
8 <div className="h-3.5 w-20 rounded-sm bg-muted" />

No longer relevant as of 095cd9edb12a39a89dc174831cc8b84592d59df2

MmacroscopeappBotreviewed Β· 1 inline threadAug 19, 2026
Synced from GitHub

One finding: the usage skeleton's left column no longer matches the geometry of the loaded content it stands in for, so the page still shifts when the last device answers (visible below lg, where the summary column stacks above the chart and everything under it moves). Everything else in this revision looks consistent with the shared system β€” the segmented size now defaults from the variant, the chart tooltip uses the shared surface-glass composition, and the provider swatches restore the chart's color mapping outside the hover tooltip.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 568–585Outdated Β· 144e585Open thread
……@@ -571,70 +555,59 @@ function UsageDeviceStrip({
602583 <div className="h-3.5 w-14 rounded-sm bg-muted" />
603584 </div>
604- <div className="h-1 w-full rounded-full bg-muted" />
605585 <div className="h-3 w-36 rounded-sm bg-muted" />
MmacroscopeappBoton lines 568–585Aug 19, 2026

usage/UsagePage.tsx:585

These placeholders are shorter than the content they stand in for, so the summary column still reflows when the data lands β€” the shift this skeleton exists to prevent, and below lg (where the columns stack) it moves the Totals and Breakdown sections too.

Loaded geometry: the headline is text-4xl (40px line box) over a text-xs caption (16px) (UsagePage.tsx:216-225), and each provider row is a text-sm line (~20px) over a text-xs detail (16px) (UsagePage.tsx:238-267). Here they are 32/12 and 16/12 β€” roughly 30px short across the column.

Suggest sizing the blocks to the text they replace:

suggestion
1 <div className="h-10 w-36 rounded-sm bg-muted" />
2 <div className="h-4 w-32 rounded-sm bg-muted" />
3 </div>
4 {PROVIDER_ORDER.map((provider) => (
5 <div key={provider} className="flex flex-col gap-1">
6 <div className="flex min-h-5 items-center justify-between gap-4">
7 <span className="flex items-center gap-2">
8 <span
9 aria-hidden
10 className="size-2 shrink-0 rounded-full"
11 style={{ backgroundColor: PROVIDER_PRESENTATION[provider].color }}
12 />
13 <ProviderMark provider={provider} className="size-4" />
14 <div className="h-3.5 w-20 rounded-sm bg-muted" />
15 </span>
16 <div className="h-3.5 w-14 rounded-sm bg-muted" />
17 </div>
18 <div className="h-4 w-36 rounded-sm bg-muted" />

No longer relevant as of 91c8fa29e612b494a22717243483848f7a0f59ee

MmacroscopeappBotreviewed Β· 1 inline threadAug 19, 2026
Synced from GitHub

One finding: the new segmented defaulting logic in the shared ToggleGroup primitive has no focused test. Everything else in this revision (shared page frame, breadcrumb separator, segmented track/size ownership, glass tooltip, skeleton geometry) now lines up with the existing shared-component and Tailwind ownership conventions.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/toggle-group.tsxline 20Outdated Β· 91c8fa2Open thread
……@@ -17,7 +17,7 @@ const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariant
1818 className,
1919 variant = "default",
20- size = "default",
20+ size = variant === "segmented" ? "segmented" : "default",
MmacroscopeappBoton line 20Aug 19, 2026

This makes variant silently own the item geometry, and all three new call sites depend on it (UsagePage.tsx:113, :128, :311 pass variant="segmented" and no size). Nothing pins the contract: reordering the ternary or adding a variant would render default 36px rounded-lg items inside the 2px p-0.5 track with no test failing. Since this is defaulting logic on a shared primitive, a focused test would be worth adding β€” button.test.tsx already covers geometry contracts this way:

tsx
1// apps/web/src/components/ui/toggle-group.test.tsx
2import { renderToStaticMarkup } from "react-dom/server";
3import { describe, expect, it } from "vite-plus/test";
4
5import { Toggle, ToggleGroup } from "./toggle-group";
6
7describe("toggle group segmented defaults", () => {
8 it("derives the segmented item size from the variant", () => {
9 const html = renderToStaticMarkup(
10 <ToggleGroup variant="segmented" value={["a"]}>
11 <Toggle value="a">A</Toggle>
12 </ToggleGroup>,
13 );
14
15 expect(html).toContain('data-size="segmented"');
16 expect(html).toContain("h-6");
17 });
18});

Posted via Macroscope β€” UI Consistency

Fixed on the latest head. toggle-group.test.tsx now pins the segmented group and item size contract, including data-size="segmented" and h-6.

MmacroscopeappBotreviewed Β· 1 inline threadAug 19, 2026
Synced from GitHub

One finding on the breakdown tables: the new row treatment drops the separators and replaces them with a hover tint that resolves to an imperceptible color in both themes. Everything else in this revision looks consistent with the shared system β€” the segmented variant now owns its item geometry (with a focused test), the tooltip composes surface-glass, the skeleton mirrors the loaded geometry, and WorkspacePageContainer / WorkspacePageHeader are shared by Settings and Usage alike.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 354–354Outdated Β· 3bd11ecOpen thread
……@@ -358,7 +351,7 @@ export function UsagePage() {
359352 <tr
360353 key={`${model.provider}:${model.model}`}
361- className="border-b border-border/50"
354+ className="hover:bg-muted/20"
MmacroscopeappBoton lines 354–354Aug 19, 2026

usage/UsagePage.tsx:354

The per-row border-b border-border/50 is dropped here and replaced with hover:bg-muted/20, which is effectively invisible in both themes: light --muted is zinc-50 on a zinc-25 background and dark --muted is --alpha(var(--color-white) / 4%) (index.css:1036, :1098), so /20 resolves to well under 1% delta against the page. The breakdown tables can run 90 rows, so they end up with neither delineation nor hover feedback.

Every other data table in the app pairs that same tint with separators β€” <tbody className="divide-y divide-border/50"> (ResourceTelemetryDiagnostics.tsx:622, DiagnosticsSettings.tsx:468) β€” and the shared TableRow contract is border-b transition-colors hover:bg-muted/50 (ui/table.tsx:46). Suggest matching that contract here (and on the time-breakdown row at line 401):

suggestion
1 className="border-b border-border/50 transition-colors hover:bg-muted/50"

No longer relevant as of 5147b582cf9c1bed036d8b567c986cf5e50389ec

MmacroscopeappBotreviewed Β· 1 inline threadAug 19, 2026
Synced from GitHub

One finding: the two new topbar Select popups keep Base UI's item-aligned positioning, unlike every other align="end" popup in apps/web. Details inline.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 162–162Outdated Β· 8d10d46Open thread
……@@ -102,78 +94,113 @@ export function UsagePage() {
159+ >
160+ <SelectValue>{metric === "cost" ? "Cost" : "Tokens"}</SelectValue>
161+ </SelectTrigger>
162+ <SelectPopup align="end">
MmacroscopeappBoton lines 162–162Aug 19, 2026

usage/UsagePage.tsx:162

These two new popups (:162 metric, :178 period) are the only align="end" SelectPopups in apps/web that do not pass alignItemWithTrigger={false} β€” SettingsPanels.tsx:409, ProjectSettingsPanel.tsx:883, IntegrationsSettings.tsx:303, ThemeSearchSection.tsx:259, ConnectionsSettings.tsx:2864 and the rest all disable it. SelectPopup defaults it to true (select.tsx:116), so these open in Base UI's item-aligned mode: the popup is anchored so the selected item covers the trigger and the positioner reports side=none, which also switches items to the in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] width path (select.tsx:190) off a w-auto min-w-0 trigger. In a topbar pinned to the top edge there is no room above, so the menu lands over the header with the scroll arrows engaged instead of dropping below the trigger like every other header/compact select.

Suggest matching the convention on both popups:

suggestion
1 <SelectPopup align="end" alignItemWithTrigger={false}>

No longer relevant as of 77e09a2e9de223184c85d85616887e86d326a6c5

MmacroscopeappBotreviewed Β· 1 inline threadAug 19, 2026
Synced from GitHub

One finding: the two new topbar Select popups keep Base UI's item-aligned positioning, unlike every other align="end" popup in apps/web. Details inline.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/usage/UsagePage.tsxlines 162–162Outdated Β· 8d10d46Open thread
……@@ -102,78 +94,113 @@ export function UsagePage() {
159+ >
160+ <SelectValue>{metric === "cost" ? "Cost" : "Tokens"}</SelectValue>
161+ </SelectTrigger>
162+ <SelectPopup align="end">
MmacroscopeappBoton lines 162–162Aug 19, 2026

usage/UsagePage.tsx:162

These two new popups (:162 metric, :178 period) are the only align="end" SelectPopups in apps/web that do not pass alignItemWithTrigger={false} β€” SettingsPanels.tsx:409, ProjectSettingsPanel.tsx:883, IntegrationsSettings.tsx:303, ThemeSearchSection.tsx:259, ConnectionsSettings.tsx:2864 and the rest all disable it. SelectPopup defaults it to true (select.tsx:116), so these open in Base UI's item-aligned mode: the popup is anchored so the selected item covers the trigger and the positioner reports side=none, which also switches items to the in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] width path (select.tsx:190) off a w-auto min-w-0 trigger. In a topbar pinned to the top edge there is no room above, so the menu lands over the header with the scroll arrows engaged instead of dropping below the trigger like every other header/compact select.

Suggest matching the convention on both popups:

suggestion
1 <SelectPopup align="end" alignItemWithTrigger={false}>

No longer relevant as of 77e09a2e9de223184c85d85616887e86d326a6c5

MmacroscopeappBotreviewed Β· 1 inline threadAug 19, 2026
Synced from GitHub

One finding on the new segmented toggle treatment: in the default dark theme the hover fill lands on top of the pressed fill, so the selected segment is not identifiable while the pointer is inside the group. Everything else in this revision (segmented size defaulting plus its test, alignItemWithTrigger={false} on the new header popups, shared WorkspacePageContainer/WorkspacePageHeader composition, surface-glass chart tooltip, skeleton geometry) lines up with the existing primitives.

Posted via Macroscope β€” UI Consistency

apps/web/src/components/ui/toggle.tsxlines 32–33Outdated Β· 146c56fOpen thread
……@@ -27,6 +29,8 @@ const toggleVariants = cva(
2830 outline:
2931 "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:data-pressed:bg-input dark:hover:bg-input/64 dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:not-disabled:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] [:disabled,:active,[data-pressed]]:shadow-none",
32+ segmented:
33+ "border-transparent text-muted-foreground shadow-none transition-colors before:shadow-none hover:bg-background/55 hover:text-foreground data-pressed:bg-background data-pressed:text-foreground data-pressed:shadow-xs/10",
MmacroscopeappBoton lines 32–33Aug 19, 2026

ui/toggle.tsx:33

In the default dark theme this makes a hovered unselected segment read the same as the selected one. The track added in toggle-group.tsx:34 is bg-input/40, and dark --input is --alpha(var(--color-white)/8%) (index.css:1106), so the track is 3.2% white over --background #0a0a0a (#121212). hover:bg-background/55 composites 55% of #0a0a0a back over that (#0e0e0e) and data-pressed:bg-background is #0a0a0a β€” a ~1.4% delta β€” while hover:text-foreground already matches the pressed text and data-pressed:shadow-xs/10 is a black shadow on a near-black surface, so it adds no lift. Net effect: with the pointer in the group, selection appears to follow the cursor. Light mode is fine (white pill over a ~#eeeeef track).

Suggest keeping the light treatment and letting the pill lift rather than sink in dark, the way the other toggle variants already do (data-pressed:bg-input/64 on the base, dark:data-pressed:bg-input on outline); exact ratios are worth tuning against the real header:

suggestion
1 segmented:
2 "border-transparent text-muted-foreground shadow-none transition-colors before:shadow-none hover:bg-background/55 hover:text-foreground data-pressed:bg-background data-pressed:text-foreground data-pressed:shadow-xs/10 dark:hover:bg-input/32 dark:data-pressed:bg-input/72",

No longer relevant as of 8156179d91f5f5bd9bb77ef8766f729f6d7dfe00

Added 8 commits

  • test(web): cover segmented toggle defaults0111bbd
  • fix(web): share workspace frame with usage1861c3a
  • fix(web): match usage skeleton line heights33ec3a5
  • fix(web): distinguish segmented dark states3fa0aed
  • fix(shared): omit empty usage providers7f66d41
  • fix(web): align usage filter menusd0802d0
  • feat(web): redesign usage insightsdeff578
  • fix(web): restore usage table rowsee497f9