From ecbbf786801ea2523ae6e8d33ee93ffff1b00fb2 Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Mon, 20 Jul 2026 14:21:08 -0700
Subject: [PATCH] feat(status-bar): toggle compact usage summary
---
src/main/persistence.ts | 5 +
.../runtime/rpc/methods/client-ui-schemas.ts | 1 +
.../src/components/status-bar/StatusBar.tsx | 125 ++++++++++++++++--
.../status-bar/UsageRosterPanel.test.tsx | 19 +++
.../status-bar/UsageRosterPanel.tsx | 31 ++++-
.../provider-segment-monthly-window.test.tsx | 38 +++++-
src/renderer/src/i18n/locales/en.json | 4 +-
src/renderer/src/i18n/locales/es.json | 4 +-
src/renderer/src/i18n/locales/ja.json | 4 +-
src/renderer/src/i18n/locales/ko.json | 4 +-
src/renderer/src/i18n/locales/zh.json | 4 +-
src/renderer/src/store/slices/ui.test.ts | 28 ++++
src/renderer/src/store/slices/ui.ts | 14 ++
src/renderer/src/web/web-preload-api.ts | 4 +
src/shared/constants.ts | 2 +
src/shared/status-bar-usage-mode.test.ts | 14 ++
src/shared/status-bar-usage-mode.ts | 7 +
src/shared/types.ts | 3 +
18 files changed, 288 insertions(+), 23 deletions(-)
create mode 100644 src/shared/status-bar-usage-mode.test.ts
create mode 100644 src/shared/status-bar-usage-mode.ts
diff --git a/src/main/persistence.ts b/src/main/persistence.ts
index 2e5b8255367..0fb057c8c7b 100644
--- a/src/main/persistence.ts
+++ b/src/main/persistence.ts
@@ -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
),
diff --git a/src/main/runtime/rpc/methods/client-ui-schemas.ts b/src/main/runtime/rpc/methods/client-ui-schemas.ts
index b5adcf617b4..48d014b0de9 100644
--- a/src/main/runtime/rpc/methods/client-ui-schemas.ts
+++ b/src/main/runtime/rpc/methods/client-ui-schemas.ts
@@ -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(),
diff --git a/src/renderer/src/components/status-bar/StatusBar.tsx b/src/renderer/src/components/status-bar/StatusBar.tsx
index fa2b7d5805a..3fd5a3d72c0 100644
--- a/src/renderer/src/components/status-bar/StatusBar.tsx
+++ b/src/renderer/src/components/status-bar/StatusBar.tsx
@@ -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) => (
+
+ {index > 0 ? · : null}
+
+ {bucket.name} {formatUsagePercentageLabel(bucket.usedPercent, display)}
+
+
+ ))}
+ {visibleBuckets.length === 0 && p.session ? (
+
+ ) : 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 ? (
+
+ ) : null}
+ {visibleWindows.map((window, index) => (
+
+ {index > 0 ? · : null}
+
+
+ ))}
+ >
+ )
+}
+
+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 (
- {tightest && !compact ? (
-
- ) : null}
- {tightest ? (
-
- ) : null}
+ {mode === 'verbose' ? (
+
+ ) : (
+ <>
+ {tightest && !compact ? (
+
+ ) : null}
+ {tightest ? (
+
+ ) : null}
+ >
+ )}
{isStale && }
)
@@ -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
({
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
+ }>) => (
+
+ {children}
+
+ ),
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"')
})
})
diff --git a/src/renderer/src/components/status-bar/UsageRosterPanel.tsx b/src/renderer/src/components/status-bar/UsageRosterPanel.tsx
index 2d441896108..cbfdbdd4869 100644
--- a/src/renderer/src/components/status-bar/UsageRosterPanel.tsx
+++ b/src/renderer/src/components/status-bar/UsageRosterPanel.tsx
@@ -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({
)
})}
+ {
+ onStatusBarUsageModeChange(checked ? 'verbose' : 'compact')
+ }}
+ onSelect={(event) => event.preventDefault()}
+ className="w-full cursor-pointer rounded-none py-2 pl-8 pr-3.5"
+ >
+
+
+ {translate(
+ 'auto.components.status.bar.UsageRosterPanel.verboseFooter',
+ 'Verbose footer'
+ )}
+
+
+ {translate(
+ 'auto.components.status.bar.UsageRosterPanel.verboseFooterDescription',
+ 'Show every usage window'
+ )}
+
+
+
+
{
const { ProviderSegment } = await import('./StatusBar')
const markup = renderToStaticMarkup(
-
+
)
expect(markup).toContain('25% used 30d')
@@ -58,7 +58,12 @@ describe('ProviderSegment monthly window', () => {
const { ProviderSegment } = await import('./StatusBar')
const markup = renderToStaticMarkup(
-
+
)
expect(markup).toContain('25% used 30d')
@@ -78,7 +83,7 @@ describe('ProviderSegment monthly window', () => {
status: 'ok'
}
const markup = renderToStaticMarkup(
-
+
)
expect(markup).toContain('30% used 30d')
@@ -102,7 +107,7 @@ describe('ProviderSegment monthly window', () => {
}
const markup = renderToStaticMarkup(
-
+
)
expect(markup).toContain('80% used Pro')
@@ -127,7 +132,7 @@ describe('ProviderSegment monthly window', () => {
status: 'ok'
}
const markup = renderToStaticMarkup(
-
+
)
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(
+
+ )
+
+ expect(markup).toContain('10% used 5h')
+ expect(markup).toContain('20% used wk')
+ expect(markup).toContain('30% used Fable')
+ expect(markup).not.toContain('40% used')
+ })
})
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index f141804da5d..9a611c3314e 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -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"
}
}
},
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index 0857b8da95b..9ecf549208c 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -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"
}
}
},
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index ad5fa07517e..bf1993ba500 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -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"
}
}
},
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index 05f344e78c1..717ba022d1c 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -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"
}
}
},
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index 0beabe342dd..80956df816b 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -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"
}
}
},
diff --git a/src/renderer/src/store/slices/ui.test.ts b/src/renderer/src/store/slices/ui.test.ts
index f7fa68279f1..b9d9454de08 100644
--- a/src/renderer/src/store/slices/ui.test.ts
+++ b/src/renderer/src/store/slices/ui.test.ts
@@ -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()
diff --git a/src/renderer/src/store/slices/ui.ts b/src/renderer/src/store/slices/ui.ts
index fc0d5eba962..1a46226d356 100644
--- a/src/renderer/src/store/slices/ui.ts
+++ b/src/renderer/src/store/slices/ui.ts
@@ -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
workspacePortScanRefreshing: boolean
@@ -2109,6 +2116,12 @@ export const createUISlice: StateCreator = (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 = (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),
diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts
index 153839e27bf..2f33d2bacd4 100644
--- a/src/renderer/src/web/web-preload-api.ts
+++ b/src/renderer/src/web/web-preload-api.ts
@@ -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
)
}
}
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 19074727e06..61bc2a088fa 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -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: {},
diff --git a/src/shared/status-bar-usage-mode.test.ts b/src/shared/status-bar-usage-mode.test.ts
new file mode 100644
index 00000000000..f5d2e3e384c
--- /dev/null
+++ b/src/shared/status-bar-usage-mode.test.ts
@@ -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')
+ })
+})
diff --git a/src/shared/status-bar-usage-mode.ts b/src/shared/status-bar-usage-mode.ts
new file mode 100644
index 00000000000..aa780e92909
--- /dev/null
+++ b/src/shared/status-bar-usage-mode.ts
@@ -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
+}
diff --git a/src/shared/types.ts b/src/shared/types.ts
index fbb1288de3d..eed668cf04f 100644
--- a/src/shared/types.ts
+++ b/src/shared/types.ts
@@ -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