fix(tui): preserved organization suffixes in usage panel
Removed the 24-column cap from account cells so wide terminals can show full disambiguating labels. Kept usage bars independently capped and added regression coverage for same-email organization accounts. Fixes #5701
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user