diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0e753ced7..10cda5e5b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed the TUI usage panel truncating organization suffixes from same-email account labels even when the terminal has enough width ([#5701](https://github.com/can1357/oh-my-pi/issues/5701)). + ## [17.0.1] - 2026-07-16 ### Changed diff --git a/packages/coding-agent/src/modes/controllers/command-controller.ts b/packages/coding-agent/src/modes/controllers/command-controller.ts index 5a8b7a715..4b7c55375 100644 --- a/packages/coding-agent/src/modes/controllers/command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/command-controller.ts @@ -1303,7 +1303,7 @@ export class CommandController { } const BAR_WIDTH_MAX = 24; -const BAR_WIDTH_MIN = 4; +const COLUMN_WIDTH_MIN = 4; function renderJobLine(job: AsyncJobSnapshotItem, now: number): string { const duration = formatDuration(Math.max(0, now - job.startTime)); @@ -1571,7 +1571,7 @@ function renderUsageBar(limit: UsageLimit, uiTheme: typeof theme, barWidth: numb } /** - * Pick a per-column width so n bars + a trailing amount string fit in `available` columns. + * Pick a per-account column width so the columns and trailing amount fit in `available`. * Falls back to the minimum when the terminal is too narrow rather than wrapping. */ function resolveColumnWidth(count: number, available: number, trailing: number): number { @@ -1580,10 +1580,7 @@ function resolveColumnWidth(count: number, available: number, trailing: number): const gaps = count - 1; const spaceForBars = available - indent - gaps - (trailing > 0 ? trailing + 1 : 0); const ideal = Math.floor(spaceForBars / count); - const min = BAR_WIDTH_MIN; - const max = BAR_WIDTH_MAX; - if (ideal < min) return min; - if (ideal > max) return max; + if (ideal < COLUMN_WIDTH_MIN) return COLUMN_WIDTH_MIN; return ideal; } @@ -1719,6 +1716,7 @@ export function renderUsageReports( const sectionCount = renderableGroups.reduce((max, g) => Math.max(max, g.sortedLimits.length), 0); const sectionTrailing = renderableGroups.reduce((max, g) => Math.max(max, visibleWidth(g.amountText)), 0); const sectionColumnWidth = resolveColumnWidth(sectionCount, availableWidth, sectionTrailing); + const sectionBarWidth = Math.min(sectionColumnWidth, BAR_WIDTH_MAX); for (const { group, sortedLimits, sortedReports, amountText } of renderableGroups) { const status = resolveAggregateStatus(sortedLimits); @@ -1736,7 +1734,7 @@ export function renderUsageReports( ); lines.push(` ${accountLabels.join(" ")}`.trimEnd()); const bars = sortedLimits.map(limit => - padColumn(renderUsageBar(limit, uiTheme, sectionColumnWidth), sectionColumnWidth), + padColumn(renderUsageBar(limit, uiTheme, sectionBarWidth), sectionColumnWidth), ); lines.push(` ${bars.join(" ")} ${amountText}`.trimEnd()); const resetText = sortedLimits.length <= 1 ? resolveResetRange(sortedLimits, nowMs) : null; diff --git a/packages/coding-agent/test/usage-report-tui-notes.test.ts b/packages/coding-agent/test/usage-report-tui-notes.test.ts index c107f7603..9f7031349 100644 --- a/packages/coding-agent/test/usage-report-tui-notes.test.ts +++ b/packages/coding-agent/test/usage-report-tui-notes.test.ts @@ -1,14 +1,16 @@ /** - * Regression for #3268 (TUI aggregate path, command-controller.ts). + * Regression coverage for the TUI aggregate path in `command-controller.ts`. * - * Two contracts that the CLI `formatUsageBreakdown` test cannot cover, because - * the bug lives in the TUI cross-account grouping renderer `renderUsageReports`: + * Three contracts that the CLI `formatUsageBreakdown` test cannot cover, + * because the bug lives in the TUI cross-account grouping renderer + * `renderUsageReports`: * * 1. Provider-wide `UsageReport.notes` render ONCE above the per-account * sections, not once per account/window. * 2. Identical per-limit notes from multiple accounts that fall in the same - * `label|windowId` group are de-duplicated (the `[...new Set(...)]` at the - * per-group note line). Without the dedup the note is bullet-joined N times. + * `label|windowId` group are de-duplicated. + * 3. Wide terminals preserve organization suffixes that distinguish accounts + * sharing an email address. */ import { beforeAll, describe, expect, it } from "bun:test"; @@ -81,4 +83,28 @@ describe("renderUsageReports (#3268 TUI aggregate)", () => { // would bullet-join it twice (one per account in the group). expect(occurrences).toBe(1); }); + + it("preserves organization suffixes when wide account columns can fit them", () => { + const now = Date.now(); + const accountLimit = () => ({ + ...limit("5 Hour limit", "rolling-5h", 5 * HOUR, 0.3), + window: { + id: "rolling-5h", + label: "5 Hour limit", + durationMs: 5 * HOUR, + resetsAt: now + 2.5 * HOUR, + }, + }); + const reports: UsageReport[] = [ + { + ...report("anthropic", "rae@example.com", [accountLimit()]), + metadata: { email: "rae@example.com", orgId: "team-org", orgName: "Team Org" }, + }, + report("anthropic", "rae@example.com", [accountLimit()]), + ]; + + const text = stripVTControlCharacters(renderUsageReports(reports, theme, now, 160)); + + expect(text).toContain("rae@example.com (Team Org)"); + }); });