feat(status-bar): toggle compact usage summary

This commit is contained in:
Brennan Benson
2026-07-20 14:21:08 -07:00
parent ec72d43538
commit ecbbf78680
18 changed files with 288 additions and 23 deletions
+5
View File
@@ -103,6 +103,7 @@ import {
} from '../shared/constants'
import { parseWorkspaceSession } from '../shared/workspace-session-schema'
import { normalizeUsagePercentageDisplay } from '../shared/usage-percentage-display'
import { normalizeStatusBarUsageMode } from '../shared/status-bar-usage-mode'
import { isExistingPersistedProfile } from '../shared/project-order-manual-default-notice'
import { resolveUsagePercentageDisplayChangeNoticeDismissed } from '../shared/usage-percentage-display-change-notice'
import { normalizePRBotAuthorOverrides } from '../shared/pr-bot-author-overrides'
@@ -5181,6 +5182,7 @@ export class Store {
usagePercentageDisplay: normalizeUsagePercentageDisplay(
this.state.ui?.usagePercentageDisplay
),
statusBarUsageMode: normalizeStatusBarUsageMode(this.state.ui?.statusBarUsageMode),
// Why: strict boolean coercion so a missing/legacy value reads as false (first-run notice still fires).
trayMinimizeNoticeShown: this.state.ui?.trayMinimizeNoticeShown === true,
markdownTocPanelWidth: clampMarkdownTocPanelWidth(this.state.ui?.markdownTocPanelWidth),
@@ -5278,6 +5280,9 @@ export class Store {
usagePercentageDisplay: normalizeUsagePercentageDisplay(
sanitizedUpdates.usagePercentageDisplay ?? this.state.ui?.usagePercentageDisplay
),
statusBarUsageMode: normalizeStatusBarUsageMode(
sanitizedUpdates.statusBarUsageMode ?? this.state.ui?.statusBarUsageMode
),
markdownTocPanelWidth: clampMarkdownTocPanelWidth(
sanitizedUpdates.markdownTocPanelWidth ?? this.state.ui?.markdownTocPanelWidth
),
@@ -219,6 +219,7 @@ export const UiUpdate = z
_grokStatusBarDefaultAdded: z.boolean().optional(),
statusBarVisible: z.boolean().optional(),
usagePercentageDisplay: z.enum(['used', 'remaining']).optional(),
statusBarUsageMode: z.enum(['verbose', 'compact']).optional(),
dismissedUpdateVersion: NullableString.optional(),
lastUpdateCheckAt: z.number().finite().nullable().optional(),
pendingUpdateNudgeId: NullableString.optional(),
@@ -94,6 +94,10 @@ import {
type UsagePercentageDisplay
} from '../../../../shared/usage-percentage-display'
import { formatUsagePercentageLabel } from './usage-percentage-label'
import {
normalizeStatusBarUsageMode,
type StatusBarUsageMode
} from '../../../../shared/status-bar-usage-mode'
type StatusBarProps = {
floatingTerminalOpen: boolean
@@ -1155,14 +1159,100 @@ function getProviderLetter(provider: ProviderRateLimits['provider']): string {
// Provider segment
// ---------------------------------------------------------------------------
export function ProviderSegment({
// Why: Gemini exposes extra experimental buckets that made the pre-existing verbose footer noisy.
const STATUS_BAR_BUCKET_NAMES = new Set(['Flash', 'Pro', '1.5 Pro'])
function VerboseProviderUsage({
p,
compact,
display
}: {
p: ProviderRateLimits
compact: boolean
display: UsagePercentageDisplay
}): React.JSX.Element {
if (p.buckets && p.buckets.length > 0) {
const visibleBuckets = p.buckets.filter((bucket) => STATUS_BAR_BUCKET_NAMES.has(bucket.name))
return (
<>
{visibleBuckets.map((bucket, index) => (
<React.Fragment key={bucket.name}>
{index > 0 ? <span className="text-muted-foreground">·</span> : null}
<span className="tabular-nums">
{bucket.name} {formatUsagePercentageLabel(bucket.usedPercent, display)}
</span>
</React.Fragment>
))}
{visibleBuckets.length === 0 && p.session ? (
<WindowLabel
w={p.session}
label={formatRateLimitWindowChipLabel(p.session)}
display={display}
/>
) : null}
</>
)
}
const visibleWindows = [
p.session
? {
key: 'session',
window: p.session,
label: formatRateLimitWindowChipLabel(p.session)
}
: null,
p.weekly
? {
key: 'weekly',
window: p.weekly,
label: formatRateLimitWindowChipLabel(p.weekly)
}
: null,
p.fableWeekly
? {
key: 'fableWeekly',
window: p.fableWeekly,
label: translate('auto.components.status.bar.StatusBar.a79c64f87e', 'Fable')
}
: null,
// Why: monthly stays inline for monthly-only providers; otherwise the detail panel carries it.
p.monthly && !p.session && !p.weekly
? {
key: 'monthly',
window: p.monthly,
label: formatRateLimitWindowChipLabel(p.monthly)
}
: null
].filter((window): window is { key: string; window: RateLimitWindow; label: string } => {
return window !== null
})
return (
<>
{p.session && !compact ? (
<MiniBar usedPct={clampUsedPercent(p.session.usedPercent)} display={display} />
) : null}
{visibleWindows.map((window, index) => (
<React.Fragment key={window.key}>
{index > 0 ? <span className="text-muted-foreground">·</span> : null}
<WindowLabel w={window.window} label={window.label} display={display} />
</React.Fragment>
))}
</>
)
}
export function ProviderSegment({
p,
compact,
display,
mode = 'verbose'
}: {
p: ProviderRateLimits | null
compact: boolean
display: UsagePercentageDisplay
mode?: StatusBarUsageMode
}): React.JSX.Element {
const provider = p?.provider ?? 'claude'
const statusLabel = p ? getProviderUsageStatusLabel(p) : ''
@@ -1215,17 +1305,23 @@ export function ProviderSegment({
return (
<span className="inline-flex items-center gap-1.5">
<ProviderIcon provider={provider} />
{tightest && !compact ? (
<MiniBar usedPct={clampUsedPercent(tightest.window.usedPercent)} display={display} />
) : null}
{tightest ? (
<WindowLabel
w={tightest.window}
label={tightest.label}
display={display}
showLabel={!compact}
/>
) : null}
{mode === 'verbose' ? (
<VerboseProviderUsage p={p} compact={compact} display={display} />
) : (
<>
{tightest && !compact ? (
<MiniBar usedPct={clampUsedPercent(tightest.window.usedPercent)} display={display} />
) : null}
{tightest ? (
<WindowLabel
w={tightest.window}
label={tightest.label}
display={display}
showLabel={!compact}
/>
) : null}
</>
)}
{isStale && <AlertTriangle size={11} className="text-muted-foreground/80" />}
</span>
)
@@ -1870,6 +1966,8 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
const usagePercentageDisplay = normalizeUsagePercentageDisplay(
useAppStore((s) => s.usagePercentageDisplay)
)
const statusBarUsageMode = normalizeStatusBarUsageMode(useAppStore((s) => s.statusBarUsageMode))
const setStatusBarUsageMode = useAppStore((s) => s.setStatusBarUsageMode)
const [usageMenuOpen, setUsageMenuOpen] = useState(false)
const usageMenuFocusHandoff = useStatusBarMenuFocusHandoff()
const statusBarVisible = useAppStore((s) => s.statusBarVisible)
@@ -2133,6 +2231,7 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
p={p}
compact={compact}
display={usagePercentageDisplay}
mode={statusBarUsageMode}
/>
)
)}
@@ -2153,6 +2252,8 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
<UsageRosterPanel
providers={rosterProviders}
display={usagePercentageDisplay}
statusBarUsageMode={statusBarUsageMode}
onStatusBarUsageModeChange={setStatusBarUsageMode}
isRefreshing={isRefreshing || anyFetching}
onRefresh={handleRefresh}
onOpenProvider={handleOpenProviderAccounts}
@@ -17,6 +17,21 @@ vi.mock('@/hooks/useResetCountdownClock', () => ({
useResetCountdownClock: mocks.useResetCountdownClock
}))
vi.mock('@/components/ui/dropdown-menu', () => ({
DropdownMenuCheckboxItem: ({
children,
checked,
onCheckedChange: _onCheckedChange,
onSelect: _onSelect,
...props
}: React.PropsWithChildren<{
checked?: boolean
onCheckedChange?: (checked: boolean) => void
onSelect?: () => void
}>) => (
<div data-checked={checked} {...props}>
{children}
</div>
),
DropdownMenuItem: ({
children,
onSelect: _onSelect,
@@ -107,6 +122,8 @@ describe('UsageRow', () => {
}
]}
display="used"
statusBarUsageMode="verbose"
onStatusBarUsageModeChange={() => {}}
isRefreshing={false}
onRefresh={() => {}}
onOpenProvider={() => {}}
@@ -120,5 +137,7 @@ describe('UsageRow', () => {
expect(mocks.useResetCountdownClock).toHaveBeenCalledOnce()
expect(mocks.useResetCountdownClock).toHaveBeenCalledWith([sessionReset, weeklyReset])
expect(markup).toContain('Resets in 2m')
expect(markup).toContain('Verbose footer')
expect(markup).toContain('data-checked="true"')
})
})
@@ -1,6 +1,6 @@
import React from 'react'
import { ChevronRight, RefreshCw } from 'lucide-react'
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
import { DropdownMenuCheckboxItem, DropdownMenuItem } from '@/components/ui/dropdown-menu'
import { useResetCountdownClock } from '@/hooks/useResetCountdownClock'
import { translate } from '@/i18n/i18n'
import { formatRateLimitWindowChipLabel, formatWindowLabel } from '@/lib/window-label-formatter'
@@ -14,6 +14,7 @@ import { barColor, formatResetCountdown, getWindowSections, ProviderIcon } from
import { getProviderDisplayName } from './usage-error-copy'
import { formatPlanLabel, usageTextColorClass } from './usage-roster-formatting'
import { getUsageRosterRowState, type UsageRosterRowState } from './usage-roster-row-state'
import type { StatusBarUsageMode } from '../../../../shared/status-bar-usage-mode'
type ProviderId = ProviderRateLimits['provider']
export type UsageSection = { label: string; window: RateLimitWindow }
@@ -156,6 +157,8 @@ export function UsageRow({
export function UsageRosterPanel({
providers,
display,
statusBarUsageMode,
onStatusBarUsageModeChange,
isRefreshing,
onRefresh,
onOpenProvider,
@@ -167,6 +170,8 @@ export function UsageRosterPanel({
}: {
providers: ProviderRateLimits[]
display: UsagePercentageDisplay
statusBarUsageMode: StatusBarUsageMode
onStatusBarUsageModeChange: (mode: StatusBarUsageMode) => void
isRefreshing: boolean
onRefresh: () => void
onOpenProvider: (provider: ProviderId) => void
@@ -254,6 +259,30 @@ export function UsageRosterPanel({
)
})}
<div className="border-t border-border/70" />
<DropdownMenuCheckboxItem
checked={statusBarUsageMode === 'verbose'}
onCheckedChange={(checked) => {
onStatusBarUsageModeChange(checked ? 'verbose' : 'compact')
}}
onSelect={(event) => event.preventDefault()}
className="w-full cursor-pointer rounded-none py-2 pl-8 pr-3.5"
>
<span className="flex flex-col">
<span className="text-[13px] text-foreground">
{translate(
'auto.components.status.bar.UsageRosterPanel.verboseFooter',
'Verbose footer'
)}
</span>
<span className="text-[11px] font-normal text-muted-foreground">
{translate(
'auto.components.status.bar.UsageRosterPanel.verboseFooterDescription',
'Show every usage window'
)}
</span>
</span>
</DropdownMenuCheckboxItem>
<div className="border-t border-border/70" />
<DropdownMenuItem
onSelect={onUsageDetails}
className="w-full cursor-pointer justify-between rounded-none px-3.5 py-2.5 text-[13px] text-foreground"
@@ -48,7 +48,7 @@ describe('ProviderSegment monthly window', () => {
const { ProviderSegment } = await import('./StatusBar')
const markup = renderToStaticMarkup(
<ProviderSegment p={grokMonthlyLimits('ok')} compact={false} display="used" />
<ProviderSegment p={grokMonthlyLimits('ok')} compact={false} display="used" mode="compact" />
)
expect(markup).toContain('25% used 30d')
@@ -58,7 +58,12 @@ describe('ProviderSegment monthly window', () => {
const { ProviderSegment } = await import('./StatusBar')
const markup = renderToStaticMarkup(
<ProviderSegment p={grokMonthlyLimits('fetching')} compact={false} display="used" />
<ProviderSegment
p={grokMonthlyLimits('fetching')}
compact={false}
display="used"
mode="compact"
/>
)
expect(markup).toContain('25% used 30d')
@@ -78,7 +83,7 @@ describe('ProviderSegment monthly window', () => {
status: 'ok'
}
const markup = renderToStaticMarkup(
<ProviderSegment p={limits} compact={false} display="used" />
<ProviderSegment p={limits} compact={false} display="used" mode="compact" />
)
expect(markup).toContain('30% used 30d')
@@ -102,7 +107,7 @@ describe('ProviderSegment monthly window', () => {
}
const markup = renderToStaticMarkup(
<ProviderSegment p={limits} compact={false} display="used" />
<ProviderSegment p={limits} compact={false} display="used" mode="compact" />
)
expect(markup).toContain('80% used Pro')
@@ -127,7 +132,7 @@ describe('ProviderSegment monthly window', () => {
status: 'ok'
}
const markup = renderToStaticMarkup(
<ProviderSegment p={limits} compact={false} display="used" />
<ProviderSegment p={limits} compact={false} display="used" mode="compact" />
)
expect(markup).toContain('42% used 2h 33m')
@@ -139,4 +144,27 @@ describe('ProviderSegment monthly window', () => {
dateNow.mockRestore()
}
})
it('restores every inline window in verbose mode', async () => {
const { ProviderSegment } = await import('./StatusBar')
const limits: ProviderRateLimits = {
provider: 'claude',
session: windowOf(10, 300),
weekly: windowOf(20, 10_080),
fableWeekly: windowOf(30, 10_080),
monthly: windowOf(40, 43_200),
updatedAt: Date.now(),
error: null,
status: 'ok'
}
const markup = renderToStaticMarkup(
<ProviderSegment p={limits} compact={false} display="used" mode="verbose" />
)
expect(markup).toContain('10% used 5h')
expect(markup).toContain('20% used wk')
expect(markup).toContain('30% used Fable')
expect(markup).not.toContain('40% used')
})
})
+3 -1
View File
@@ -3308,7 +3308,9 @@
"usageDetails": "Usage details & history",
"loadingUsage": "Loading usage…",
"usageUnavailable": "Usage unavailable",
"noUsageData": "No usage data"
"noUsageData": "No usage data",
"verboseFooter": "Verbose footer",
"verboseFooterDescription": "Show every usage window"
}
}
},
+3 -1
View File
@@ -3285,7 +3285,9 @@
"usageDetails": "Usage details & history",
"loadingUsage": "Loading usage…",
"usageUnavailable": "Usage unavailable",
"noUsageData": "No usage data"
"noUsageData": "No usage data",
"verboseFooter": "Verbose footer",
"verboseFooterDescription": "Show every usage window"
}
}
},
+3 -1
View File
@@ -3285,7 +3285,9 @@
"usageDetails": "Usage details & history",
"loadingUsage": "Loading usage…",
"usageUnavailable": "Usage unavailable",
"noUsageData": "No usage data"
"noUsageData": "No usage data",
"verboseFooter": "Verbose footer",
"verboseFooterDescription": "Show every usage window"
}
}
},
+3 -1
View File
@@ -3285,7 +3285,9 @@
"usageDetails": "Usage details & history",
"loadingUsage": "Loading usage…",
"usageUnavailable": "Usage unavailable",
"noUsageData": "No usage data"
"noUsageData": "No usage data",
"verboseFooter": "Verbose footer",
"verboseFooterDescription": "Show every usage window"
}
}
},
+3 -1
View File
@@ -3285,7 +3285,9 @@
"usageDetails": "Usage details & history",
"loadingUsage": "Loading usage…",
"usageUnavailable": "Usage unavailable",
"noUsageData": "No usage data"
"noUsageData": "No usage data",
"verboseFooter": "Verbose footer",
"verboseFooterDescription": "Show every usage window"
}
}
},
+28
View File
@@ -1452,6 +1452,34 @@ describe('createUISlice hydratePersistedUI', () => {
expect(store.getState().usagePercentageDisplay).toBe('used')
})
it('persists and hydrates the status bar usage mode', () => {
const setUI = vi.fn().mockResolvedValue(undefined)
vi.stubGlobal('window', { api: { ui: { set: setUI } } })
const store = createUIStore()
expect(store.getState().statusBarUsageMode).toBe('verbose')
store.getState().setStatusBarUsageMode('compact')
expect(store.getState().statusBarUsageMode).toBe('compact')
expect(setUI).toHaveBeenCalledWith({ statusBarUsageMode: 'compact' })
store.getState().hydratePersistedUI(makePersistedUI({ statusBarUsageMode: 'verbose' }))
expect(store.getState().statusBarUsageMode).toBe('verbose')
})
it('defaults invalid status bar usage modes to verbose', () => {
const store = createUIStore()
store.getState().hydratePersistedUI(
makePersistedUI({
statusBarUsageMode: 'expanded' as PersistedUIState['statusBarUsageMode']
})
)
expect(store.getState().statusBarUsageMode).toBe('verbose')
})
it('clamps persisted workspace board column width', () => {
const store = createUIStore()
+14
View File
@@ -41,6 +41,11 @@ import {
DEFAULT_USAGE_PERCENTAGE_DISPLAY,
normalizeUsagePercentageDisplay
} from '../../../../shared/usage-percentage-display'
import {
DEFAULT_STATUS_BAR_USAGE_MODE,
normalizeStatusBarUsageMode,
type StatusBarUsageMode
} from '../../../../shared/status-bar-usage-mode'
import type { GitLabWorkItem } from '../../../../shared/gitlab-types'
import type { LaunchSource } from '../../../../shared/telemetry-events'
import type { TaskSourceContext } from '../../../../shared/task-source-context'
@@ -878,6 +883,8 @@ export type UISlice = {
setStatusBarVisible: (v: boolean) => void
usagePercentageDisplay: UsagePercentageDisplay
setUsagePercentageDisplay: (display: UsagePercentageDisplay) => void
statusBarUsageMode: StatusBarUsageMode
setStatusBarUsageMode: (mode: StatusBarUsageMode) => void
workspacePortScan: { key: string; result: WorkspacePortScanResult } | null
workspacePortScansByKey: Record<string, WorkspacePortScanResult>
workspacePortScanRefreshing: boolean
@@ -2109,6 +2116,12 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
usagePercentageDisplayChangeNoticeDismissed: true
})
},
statusBarUsageMode: DEFAULT_STATUS_BAR_USAGE_MODE,
setStatusBarUsageMode: (mode) => {
const normalized = normalizeStatusBarUsageMode(mode)
window.api.ui.set({ statusBarUsageMode: normalized }).catch(console.error)
set({ statusBarUsageMode: normalized })
},
workspacePortScan: null,
workspacePortScansByKey: {},
workspacePortScanRefreshing: false,
@@ -2377,6 +2390,7 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
statusBarItems: statusBarItemsWithGrok,
statusBarVisible: ui.statusBarVisible ?? true,
usagePercentageDisplay: normalizeUsagePercentageDisplay(ui.usagePercentageDisplay),
statusBarUsageMode: normalizeStatusBarUsageMode(ui.statusBarUsageMode),
// Why: default true so existing users see the pet on first enabling the flag; only an explicit Hide persists false.
petVisible: ui.petVisible ?? ui.sidekickVisible ?? true,
petSize: clampPetSize(ui.petSize ?? ui.sidekickSize ?? PET_SIZE_DEFAULT),
+4
View File
@@ -75,6 +75,7 @@ import { normalizeTerminalCursorStyleDefault } from '../../../shared/terminal-cu
import { normalizeTerminalCustomThemes } from '../../../shared/terminal-custom-themes'
import { normalizeUiLanguage } from '../../../shared/ui-language'
import { normalizeUsagePercentageDisplay } from '../../../shared/usage-percentage-display'
import { normalizeStatusBarUsageMode } from '../../../shared/status-bar-usage-mode'
import type { RateLimitState } from '../../../shared/rate-limit-types'
import type { RuntimeStatus, RuntimeSyncWindowGraph } from '../../../shared/runtime-types'
import {
@@ -3449,6 +3450,9 @@ function mergeWebUIState(
),
usagePercentageDisplay: normalizeUsagePercentageDisplay(
safeUpdates.usagePercentageDisplay ?? base.usagePercentageDisplay
),
statusBarUsageMode: normalizeStatusBarUsageMode(
safeUpdates.statusBarUsageMode ?? base.statusBarUsageMode
)
}
}
+2
View File
@@ -32,6 +32,7 @@ import { DEFAULT_SOURCE_CONTROL_GROUP_ORDER } from './source-control-group-order
import { DEFAULT_SETUP_AGENT_STARTUP_POLICY } from './setup-agent-startup-policy'
import { DESKTOP_TERMINAL_SCROLLBACK_ROWS_DEFAULT } from './terminal-scrollback-policy'
import { DEFAULT_USAGE_PERCENTAGE_DISPLAY } from './usage-percentage-display'
import { DEFAULT_STATUS_BAR_USAGE_MODE } from './status-bar-usage-mode'
export { DEFAULT_STATUS_BAR_ITEMS } from './status-bar-defaults'
export {
@@ -464,6 +465,7 @@ export function getDefaultUIState(): PersistedUIState {
statusBarItems: [...DEFAULT_STATUS_BAR_ITEMS],
statusBarVisible: true,
usagePercentageDisplay: DEFAULT_USAGE_PERCENTAGE_DISPLAY,
statusBarUsageMode: DEFAULT_STATUS_BAR_USAGE_MODE,
dismissedUpdateVersion: null,
lastUpdateCheckAt: null,
trustedOrcaHooks: {},
+14
View File
@@ -0,0 +1,14 @@
import { describe, expect, it } from 'vitest'
import { normalizeStatusBarUsageMode } from './status-bar-usage-mode'
describe('normalizeStatusBarUsageMode', () => {
it('defaults missing and invalid values to verbose', () => {
expect(normalizeStatusBarUsageMode(undefined)).toBe('verbose')
expect(normalizeStatusBarUsageMode('expanded')).toBe('verbose')
})
it('preserves supported modes', () => {
expect(normalizeStatusBarUsageMode('verbose')).toBe('verbose')
expect(normalizeStatusBarUsageMode('compact')).toBe('compact')
})
})
+7
View File
@@ -0,0 +1,7 @@
export type StatusBarUsageMode = 'verbose' | 'compact'
export const DEFAULT_STATUS_BAR_USAGE_MODE: StatusBarUsageMode = 'verbose'
export function normalizeStatusBarUsageMode(value: unknown): StatusBarUsageMode {
return value === 'verbose' || value === 'compact' ? value : DEFAULT_STATUS_BAR_USAGE_MODE
}
+3
View File
@@ -41,6 +41,7 @@ import type {
LocalWindowsRuntimePreference
} from './project-execution-runtime'
import type { UsagePercentageDisplay } from './usage-percentage-display'
import type { StatusBarUsageMode } from './status-bar-usage-mode'
import type { PersistedNativeChatSessionOptions } from './native-chat-session-options'
// Re-exported for backward compat with renderer call sites that import
@@ -3213,6 +3214,8 @@ export type PersistedUIState = {
statusBarVisible: boolean
/** Why: this is client-side presentation, not a provider/account or execution-host setting. */
usagePercentageDisplay?: UsagePercentageDisplay
/** Client-side footer presentation; verbose preserves the pre-roster all-window default. */
statusBarUsageMode?: StatusBarUsageMode
dismissedUpdateVersion: string | null
lastUpdateCheckAt: number | null
pendingUpdateNudgeId?: string | null