From 7752a901eead674ae0a91a0725134c0fca9a7d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=ED=98=81=EC=A4=80?= Date: Thu, 26 Mar 2026 14:08:59 +0900 Subject: [PATCH] feat: add styled Weekly label for 7-day usage window When both 5h and 7d usage windows are displayed, the 7d gauge had no label prefix. This adds a dim-styled "Weekly" label (matching the Context/Usage label style) and removes colons from usage window labels for consistency with other HUD labels. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/render/lines/usage.ts | 16 +++++++++------- src/render/session-line.ts | 18 ++++++++++-------- tests/render.test.js | 36 ++++++++++++++++++------------------ 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/render/lines/usage.ts b/src/render/lines/usage.ts index c435fd3..e969fdd 100644 --- a/src/render/lines/usage.ts +++ b/src/render/lines/usage.ts @@ -44,7 +44,7 @@ export function renderUsageLine(ctx: RenderContext): string | null { if (fiveHour === null && sevenDay !== null) { const weeklyOnlyPart = formatUsageWindowPart({ - label: '7d', + label: 'Weekly', percent: sevenDay, resetAt: ctx.usageData.sevenDayResetAt, colors, @@ -66,12 +66,13 @@ export function renderUsageLine(ctx: RenderContext): string | null { if (sevenDay !== null && sevenDay >= sevenDayThreshold) { const sevenDayPart = formatUsageWindowPart({ - label: '7d', + label: 'Weekly', percent: sevenDay, resetAt: ctx.usageData.sevenDayResetAt, colors, usageBarEnabled, barWidth, + forceLabel: true, }); return `${usageLabel} ${fiveHourPart} | ${sevenDayPart}`; } @@ -88,7 +89,7 @@ function formatUsagePercent(percent: number | null, colors?: RenderContext['conf } function formatUsageWindowPart({ - label, + label: windowLabel, percent, resetAt, colors, @@ -96,7 +97,7 @@ function formatUsageWindowPart({ barWidth, forceLabel = false, }: { - label: '5h' | '7d'; + label: '5h' | 'Weekly'; percent: number | null; resetAt: Date | null; colors?: RenderContext['config']['colors']; @@ -106,17 +107,18 @@ function formatUsageWindowPart({ }): string { const usageDisplay = formatUsagePercent(percent, colors); const reset = formatResetTime(resetAt); + const styledLabel = label(windowLabel, colors); if (usageBarEnabled) { const body = reset ? `${quotaBar(percent ?? 0, barWidth, colors)} ${usageDisplay} (resets in ${reset})` : `${quotaBar(percent ?? 0, barWidth, colors)} ${usageDisplay}`; - return forceLabel ? `${label}: ${body}` : body; + return forceLabel ? `${styledLabel} ${body}` : body; } return reset - ? `${label}: ${usageDisplay} (resets in ${reset})` - : `${label}: ${usageDisplay}`; + ? `${styledLabel} ${usageDisplay} (resets in ${reset})` + : `${styledLabel} ${usageDisplay}`; } function formatResetTime(resetAt: Date | null): string { diff --git a/src/render/session-line.ts b/src/render/session-line.ts index a31edb8..e82c9ee 100644 --- a/src/render/session-line.ts +++ b/src/render/session-line.ts @@ -158,7 +158,7 @@ export function renderSessionLine(ctx: RenderContext): string { const usageBarEnabled = display?.usageBarEnabled ?? true; if (fiveHour === null && sevenDay !== null) { const weeklyOnlyPart = formatUsageWindowPart({ - label: '7d', + label: 'Weekly', percent: sevenDay, resetAt: ctx.usageData.sevenDayResetAt, colors, @@ -180,12 +180,13 @@ export function renderSessionLine(ctx: RenderContext): string { const sevenDayThreshold = display?.sevenDayThreshold ?? 80; if (sevenDay !== null && sevenDay >= sevenDayThreshold) { const sevenDayPart = formatUsageWindowPart({ - label: '7d', + label: 'Weekly', percent: sevenDay, resetAt: ctx.usageData.sevenDayResetAt, colors, usageBarEnabled, barWidth, + forceLabel: true, }); parts.push(`${fiveHourPart} | ${sevenDayPart}`); } else { @@ -277,7 +278,7 @@ function formatUsagePercent(percent: number | null, colors?: RenderContext['conf } function formatUsageWindowPart({ - label, + label: windowLabel, percent, resetAt, colors, @@ -285,7 +286,7 @@ function formatUsageWindowPart({ barWidth, forceLabel = false, }: { - label: '5h' | '7d'; + label: '5h' | 'Weekly'; percent: number | null; resetAt: Date | null; colors?: RenderContext['config']['colors']; @@ -295,17 +296,18 @@ function formatUsageWindowPart({ }): string { const usageDisplay = formatUsagePercent(percent, colors); const reset = formatResetTime(resetAt); + const styledLabel = label(windowLabel, colors); if (usageBarEnabled) { const body = reset - ? `${quotaBar(percent ?? 0, barWidth, colors)} ${usageDisplay} (${reset} / ${label})` + ? `${quotaBar(percent ?? 0, barWidth, colors)} ${usageDisplay} (${reset} / ${windowLabel})` : `${quotaBar(percent ?? 0, barWidth, colors)} ${usageDisplay}`; - return forceLabel ? `${label}: ${body}` : body; + return forceLabel ? `${styledLabel} ${body}` : body; } return reset - ? `${label}: ${usageDisplay} (${reset})` - : `${label}: ${usageDisplay}`; + ? `${styledLabel} ${usageDisplay} (${reset})` + : `${styledLabel} ${usageDisplay}`; } function formatResetTime(resetAt: Date | null): string { diff --git a/tests/render.test.js b/tests/render.test.js index 3a2e33f..33b5a85 100644 --- a/tests/render.test.js +++ b/tests/render.test.js @@ -881,7 +881,7 @@ test('renderSessionLine shows Bedrock label and hides usage for bedrock model id const line = renderSessionLine(ctx); assert.ok(line.includes('Sonnet'), 'should include model name'); assert.ok(line.includes('Bedrock'), 'should include Bedrock label'); - assert.ok(!line.includes('5h:'), 'should hide usage display'); + assert.ok(!line.includes('5h'), 'should hide usage display'); }); test('renderSessionLine displays usage percentages (7d hidden when low)', () => { @@ -895,8 +895,8 @@ test('renderSessionLine displays usage percentages (7d hidden when low)', () => sevenDayResetAt: null, }; const line = renderSessionLine(ctx); - assert.ok(line.includes('5h:'), 'should include 5h label'); - assert.ok(!line.includes('7d:'), 'should NOT include 7d when below 80%'); + assert.ok(line.includes('5h'), 'should include 5h label'); + assert.ok(!line.includes('Weekly'), 'should NOT include 7d when below 80%'); assert.ok(line.includes('6%'), 'should include 5h percentage'); }); @@ -911,8 +911,8 @@ test('renderSessionLine shows 7d when approaching limit (>=80%)', () => { sevenDayResetAt: null, }; const line = renderSessionLine(ctx); - assert.ok(line.includes('5h:'), 'should include 5h label'); - assert.ok(line.includes('7d:'), 'should include 7d when >= 80%'); + assert.ok(line.includes('5h'), 'should include 5h label'); + assert.ok(line.includes('Weekly'), 'should include 7d when >= 80%'); assert.ok(line.includes('85%'), 'should include 7d percentage'); }); @@ -930,7 +930,7 @@ test('renderSessionLine shows 7d reset countdown in text-only mode', () => { }; const line = stripAnsi(renderSessionLine(ctx)); - assert.ok(line.includes('7d: 85%'), `should include 7d label and percentage: ${line}`); + assert.ok(line.includes('Weekly 85%'), `should include 7d label and percentage: ${line}`); assert.ok(line.includes('(1d 4h)'), `should include 7d reset countdown in text-only mode: ${line}`); }); @@ -946,7 +946,7 @@ test('renderSessionLine respects sevenDayThreshold override', () => { }; const line = renderSessionLine(ctx); - assert.ok(line.includes('7d:'), 'should include 7d when threshold is 0'); + assert.ok(line.includes('Weekly'), 'should include 7d when threshold is 0'); }); test('renderSessionLine shows weekly-only usage without a ghost 5h section', () => { @@ -961,8 +961,8 @@ test('renderSessionLine shows weekly-only usage without a ghost 5h section', () }; const line = stripAnsi(renderSessionLine(ctx)); - assert.ok(!line.includes('5h:'), `should not render a ghost 5h section: ${line}`); - assert.ok(line.includes('7d:'), `should render the weekly window when it is the only usage value: ${line}`); + assert.ok(!line.includes('5h'), `should not render a ghost 5h section: ${line}`); + assert.ok(line.includes('Weekly'), `should render the weekly window when it is the only usage value: ${line}`); assert.ok(line.includes('13%'), `should render the weekly percentage: ${line}`); }); @@ -977,7 +977,7 @@ test('renderSessionLine shows 5hr reset countdown', () => { sevenDayResetAt: null, }; const line = renderSessionLine(ctx); - assert.ok(line.includes('5h:'), 'should include 5h label'); + assert.ok(line.includes('5h'), 'should include 5h label'); assert.ok(line.includes('2h'), 'should include reset countdown'); }); @@ -1013,8 +1013,8 @@ test('renderUsageLine shows 7d reset countdown in text-only mode', () => { }; const line = stripAnsi(renderUsageLine(ctx)); - assert.ok(line.includes('5h: 45%'), `should include 5h text-only usage: ${line}`); - assert.ok(line.includes('7d: 85%'), `should include 7d text-only usage: ${line}`); + assert.ok(line.includes('5h 45%'), `should include 5h text-only usage: ${line}`); + assert.ok(line.includes('Weekly 85%'), `should include 7d text-only usage: ${line}`); assert.ok(line.includes('(resets in 1d 4h)'), `should include 7d reset countdown in text-only mode: ${line}`); }); @@ -1051,8 +1051,8 @@ test('renderUsageLine shows weekly-only usage without a ghost 5h section', () => const line = stripAnsi(renderUsageLine(ctx)); assert.ok(line.includes('Usage'), `should render usage line: ${line}`); - assert.ok(!line.includes('5h:'), `should not render a ghost 5h section: ${line}`); - assert.ok(line.includes('7d:'), `should render the weekly window when it is the only usage value: ${line}`); + assert.ok(!line.includes('5h'), `should not render a ghost 5h section: ${line}`); + assert.ok(line.includes('Weekly'), `should render the weekly window when it is the only usage value: ${line}`); assert.ok(line.includes('13%'), `should render the weekly percentage: ${line}`); assert.ok(!line.includes('|'), `should not render a separator for a missing 5h window: ${line}`); }); @@ -1100,7 +1100,7 @@ test('renderSessionLine displays -- for null usage values', () => { sevenDayResetAt: null, }; const line = renderSessionLine(ctx); - assert.ok(line.includes('5h:'), 'should include 5h label'); + assert.ok(line.includes('5h'), 'should include 5h label'); assert.ok(line.includes('--'), 'should show -- for null values'); }); @@ -1108,8 +1108,8 @@ test('renderSessionLine omits usage when usageData is null', () => { const ctx = baseContext(); ctx.usageData = null; const line = renderSessionLine(ctx); - assert.ok(!line.includes('5h:'), 'should not include 5h label'); - assert.ok(!line.includes('7d:'), 'should not include 7d label'); + assert.ok(!line.includes('5h'), 'should not include 5h label'); + assert.ok(!line.includes('Weekly'), 'should not include 7d label'); }); test('renderSessionLine uses custom critical colors for limit-reached usage state', () => { @@ -1171,7 +1171,7 @@ test('renderSessionLine hides usage when showUsage config is false (hybrid toggl // Even with usageData present, setting showUsage to false should hide it ctx.config.display.showUsage = false; const line = renderSessionLine(ctx); - assert.ok(!line.includes('5h:'), 'should not show usage when showUsage is false'); + assert.ok(!line.includes('5h'), 'should not show usage when showUsage is false'); assert.ok(!line.includes('Pro'), 'should not show plan name when showUsage is false'); });