fix(status-bar): sync usage meters with display mode (#8582)

* fix(status-bar): sync usage meters with display mode

* docs(status-bar): correct barColor comment now that fill follows display mode

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
This commit is contained in:
gatsby74
2026-07-15 14:12:12 -07:00
committed by GitHub
co-authored by Brennan Benson
parent e7f785f0ac
commit 0690be5656
4 changed files with 42 additions and 32 deletions
@@ -81,6 +81,7 @@ import {
} from '@/runtime/runtime-provider-accounts-client'
import { translate } from '@/i18n/i18n'
import {
getDisplayedUsagePercentage,
normalizeUsagePercentageDisplay,
type UsagePercentageDisplay
} from '../../../../shared/usage-percentage-display'
@@ -944,15 +945,21 @@ function ClaudeSwitcherMenu({
}
// ---------------------------------------------------------------------------
// Mini progress bar (shows consumption / % used, grey)
// Mini progress bar (follows the selected usage percentage meaning, grey)
// ---------------------------------------------------------------------------
function MiniBar({ usedPct }: { usedPct: number }): React.JSX.Element {
function MiniBar({
usedPct,
display
}: {
usedPct: number
display: UsagePercentageDisplay
}): React.JSX.Element {
return (
<div className="w-[48px] h-[6px] rounded-full bg-muted overflow-hidden flex-shrink-0">
<div
className="h-full rounded-full transition-all duration-300 bg-muted-foreground/40"
style={{ width: `${clampUsedPercent(usedPct)}%` }}
style={{ width: `${getDisplayedUsagePercentage(usedPct, display)}%` }}
/>
</div>
)
@@ -972,8 +979,6 @@ export function InlineUsageBars({
const display = normalizeUsagePercentageDisplay(
useAppStore((state) => state.usagePercentageDisplay)
)
// Why: the preference changes copy, while bar fill stays consumption-based
// so empty/green and full/red keep the meter semantics introduced in #8167.
const usageWindows = [
limits.session
? {
@@ -1008,9 +1013,10 @@ export function InlineUsageBars({
{usageWindows.map((window) => (
<div key={window.key} className="flex min-w-0 items-center gap-1">
<div className="h-[4px] min-w-0 flex-1 overflow-hidden rounded-full bg-muted">
{/* Why: fill follows the selected percentage; color still signals consumption urgency. */}
<div
className={`h-full rounded-full ${barColor(window.used)}`}
style={{ width: `${window.used}%` }}
style={{ width: `${getDisplayedUsagePercentage(window.used, display)}%` }}
/>
</div>
<span className="shrink-0 text-[10px] tabular-nums text-muted-foreground">
@@ -1231,7 +1237,9 @@ export function ProviderSegment({
return (
<span className="inline-flex items-center gap-1.5">
<ProviderIcon provider={provider} />
{p.session && !compact && <MiniBar usedPct={clampUsedPercent(p.session.usedPercent)} />}
{p.session && !compact && (
<MiniBar usedPct={clampUsedPercent(p.session.usedPercent)} display={display} />
)}
{visibleWindows.map((window, index) => (
<React.Fragment key={window.key}>
{index > 0 && <span className="text-muted-foreground">·</span>}
@@ -71,7 +71,7 @@ describe('InlineUsageBars', () => {
expect(markup).toContain('42% used Fable')
})
it('shows remaining copy without reversing consumption meter fill', async () => {
it('shows remaining copy and remaining meter fill', async () => {
mocks.usagePercentageDisplay = 'remaining'
const { InlineUsageBars } = await import('./StatusBar')
@@ -82,6 +82,9 @@ describe('InlineUsageBars', () => {
expect(markup).toContain('68% left 5h')
expect(markup).toContain('84% left wk')
expect(markup).toContain('58% left Fable')
expect(markup).toContain('width:32%')
expect(markup).toContain('width:68%')
expect(markup).toContain('width:84%')
expect(markup).toContain('width:58%')
expect(markup).not.toContain('width:32%')
})
})
@@ -514,26 +514,24 @@ describe('ProviderPanel reset rendering', () => {
expect(markup).not.toContain('140%')
})
it.each(PROVIDER_IDS)(
'applies remaining copy to %s while retaining consumption bar direction',
(providerId) => {
const p = provider({
provider: providerId,
status: 'ok',
session: {
usedPercent: 25,
windowMinutes: 300,
resetsAt: null,
resetDescription: null
}
})
it.each(PROVIDER_IDS)('applies remaining copy and meter fill to %s', (providerId) => {
const p = provider({
provider: providerId,
status: 'ok',
session: {
usedPercent: 25,
windowMinutes: 300,
resetsAt: null,
resetDescription: null
}
})
const markup = renderToStaticMarkup(ProviderPanel({ p, usagePercentageDisplay: 'remaining' }))
const markup = renderToStaticMarkup(ProviderPanel({ p, usagePercentageDisplay: 'remaining' }))
expect(markup).toContain('75% left')
expect(markup).toContain('width:25%')
}
)
expect(markup).toContain('75% left')
expect(markup).toContain('width:75%')
expect(markup).not.toContain('width:25%')
})
})
describe('clampUsedPercent', () => {
@@ -13,6 +13,7 @@ import {
} from './usage-error-copy'
import {
clampUsedPercent,
getDisplayedUsagePercentage,
type UsagePercentageDisplay
} from '../../../../shared/usage-percentage-display'
import { formatUsagePercentageLabel } from './usage-percentage-label'
@@ -184,8 +185,8 @@ export function getWindowSections(
// `text-background` for primary text and `text-background/50` for secondary
// to stay readable inside the inverted tooltip container.
// Why: color-coded by consumption so users can quickly gauge urgency.
// Matches common harness usage meters (Claude/Codex): bars fill with % used.
// Why: color always tracks % used so urgency reads correctly even when the meter
// fills with % remaining (#8560) — low remaining still turns red, not green.
// Green = comfortable (<60% used), yellow = caution (60-80%), red = critical (≥80%).
export function barColor(usedPct: number): string {
if (usedPct < 60) {
@@ -278,18 +279,18 @@ export function ProviderPanel({
if (!w) {
return null
}
// Why: preference changes the copy only; consumption-based bar direction
// preserves the empty/green to full/red meter convention from #8167.
const usedPct = clampUsedPercent(w.usedPercent)
const displayedPct = getDisplayedUsagePercentage(usedPct, usagePercentageDisplay)
const resetLabel = w.resetsAt ? formatResetCountdown(w.resetsAt - Date.now()) : null
return (
<div className="space-y-1">
<div className={`font-medium ${textClass}`}>{label}</div>
<div className={`h-[6px] w-full overflow-hidden rounded-full ${emptyBarClass}`}>
{/* Why: fill follows the selected percentage; color still signals consumption urgency. */}
<div
className={`h-full rounded-full ${barColor(usedPct)} transition-all duration-300`}
style={{ width: `${usedPct}%` }}
style={{ width: `${displayedPct}%` }}
/>
</div>
<div className={`flex justify-between ${mutedClass}`}>