Merge pull request #325 from devy1540/feat/label

feat: add styled Weekly label for 7-day usage window
This commit is contained in:
Jarrod Watts
2026-04-04 14:38:48 +11:00
committed by GitHub
6 changed files with 61 additions and 33 deletions

View File

@@ -4,6 +4,7 @@ export const en: Messages = {
// Labels
"label.context": "Context",
"label.usage": "Usage",
"label.weekly": "Weekly",
"label.approxRam": "Approx RAM",
"label.rules": "rules",
"label.hooks": "hooks",

View File

@@ -2,6 +2,7 @@ export type MessageKey =
// Labels
| "label.context"
| "label.usage"
| "label.weekly"
| "label.approxRam"
| "label.rules"
| "label.hooks"

View File

@@ -4,6 +4,7 @@ export const zh: Messages = {
// Labels
"label.context": "上下文",
"label.usage": "用量",
"label.weekly": "本周",
"label.approxRam": "内存",
"label.rules": "规则",
"label.hooks": "钩子",

View File

@@ -46,7 +46,7 @@ export function renderUsageLine(ctx: RenderContext): string | null {
if (fiveHour === null && sevenDay !== null) {
const weeklyOnlyPart = formatUsageWindowPart({
label: "7d",
label: t("label.weekly"),
percent: sevenDay,
resetAt: ctx.usageData.sevenDayResetAt,
colors,
@@ -68,12 +68,13 @@ export function renderUsageLine(ctx: RenderContext): string | null {
if (sevenDay !== null && sevenDay >= sevenDayThreshold) {
const sevenDayPart = formatUsageWindowPart({
label: "7d",
label: t("label.weekly"),
percent: sevenDay,
resetAt: ctx.usageData.sevenDayResetAt,
colors,
usageBarEnabled,
barWidth,
forceLabel: true,
});
return `${usageLabel} ${fiveHourPart} | ${sevenDayPart}`;
}
@@ -93,7 +94,7 @@ function formatUsagePercent(
}
function formatUsageWindowPart({
label,
label: windowLabel,
percent,
resetAt,
colors,
@@ -101,7 +102,7 @@ function formatUsageWindowPart({
barWidth,
forceLabel = false,
}: {
label: "5h" | "7d";
label: string;
percent: number | null;
resetAt: Date | null;
colors?: RenderContext["config"]["colors"];
@@ -111,17 +112,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} (${t("format.resetsIn")} ${reset})`
: `${quotaBar(percent ?? 0, barWidth, colors)} ${usageDisplay}`;
return forceLabel ? `${label}: ${body}` : body;
return forceLabel ? `${styledLabel} ${body}` : body;
}
return reset
? `${label}: ${usageDisplay} (${t("format.resetsIn")} ${reset})`
: `${label}: ${usageDisplay}`;
? `${styledLabel} ${usageDisplay} (${t("format.resetsIn")} ${reset})`
: `${styledLabel} ${usageDisplay}`;
}
function formatResetTime(resetAt: Date | null): string {

View File

@@ -157,7 +157,7 @@ export function renderSessionLine(ctx: RenderContext): string {
const usageBarEnabled = display?.usageBarEnabled ?? true;
if (fiveHour === null && sevenDay !== null) {
const weeklyOnlyPart = formatUsageWindowPart({
label: '7d',
label: t('label.weekly'),
percent: sevenDay,
resetAt: ctx.usageData.sevenDayResetAt,
colors,
@@ -179,12 +179,13 @@ export function renderSessionLine(ctx: RenderContext): string {
const sevenDayThreshold = display?.sevenDayThreshold ?? 80;
if (sevenDay !== null && sevenDay >= sevenDayThreshold) {
const sevenDayPart = formatUsageWindowPart({
label: '7d',
label: t('label.weekly'),
percent: sevenDay,
resetAt: ctx.usageData.sevenDayResetAt,
colors,
usageBarEnabled,
barWidth,
forceLabel: true,
});
parts.push(`${fiveHourPart} | ${sevenDayPart}`);
} else {
@@ -276,7 +277,7 @@ function formatUsagePercent(percent: number | null, colors?: RenderContext['conf
}
function formatUsageWindowPart({
label,
label: windowLabel,
percent,
resetAt,
colors,
@@ -284,7 +285,7 @@ function formatUsageWindowPart({
barWidth,
forceLabel = false,
}: {
label: '5h' | '7d';
label: string;
percent: number | null;
resetAt: Date | null;
colors?: RenderContext['config']['colors'];
@@ -294,17 +295,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} (${t('format.resetsIn')} ${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} (${t('format.resetsIn')} ${reset})`
: `${label}: ${usageDisplay}`;
? `${styledLabel} ${usageDisplay} (${t('format.resetsIn')} ${reset})`
: `${styledLabel} ${usageDisplay}`;
}
function formatResetTime(resetAt: Date | null): string {

View File

@@ -986,7 +986,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)', () => {
@@ -1000,8 +1000,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');
});
@@ -1016,8 +1016,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');
});
@@ -1035,7 +1035,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('(resets in 1d 4h)'), `should include 7d reset countdown in text-only mode: ${line}`);
});
@@ -1051,7 +1051,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', () => {
@@ -1066,8 +1066,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}`);
});
@@ -1082,7 +1082,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');
});
@@ -1118,11 +1118,32 @@ 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}`);
});
test('renderUsageLine translates weekly label when Chinese is enabled', () => {
const ctx = baseContext();
ctx.config.display.sevenDayThreshold = 80;
ctx.usageData = {
planName: 'Pro',
fiveHour: 45,
sevenDay: 85,
fiveHourResetAt: null,
sevenDayResetAt: new Date(Date.now() + (28 * 60 * 60 * 1000)),
};
setLanguage('zh');
try {
const line = stripAnsi(renderUsageLine(ctx) ?? '');
assert.ok(line.includes('本周'));
assert.ok(line.includes('重置剩余'));
} finally {
setLanguage('en');
}
});
test('renderUsageLine shows 7d reset countdown in bar mode when above threshold', () => {
const ctx = baseContext();
const resetTime = new Date(Date.now() + (28 * 60 * 60 * 1000)); // 1d 4h from now
@@ -1156,8 +1177,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}`);
});
@@ -1205,7 +1226,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');
});
@@ -1213,8 +1234,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', () => {
@@ -1276,7 +1297,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');
});