Repository navigation
fix(terminal): preserve numeric columns in narrow tables - #1671
Conversation
Keep wide model lists from consuming responsive table space while preserving numeric widths derived from every rendered row, including totals. Co-authored-by: cristos <[email protected]>
Prioritize text-column reduction and redistribute fitted capacity to measured numeric columns so large totals remain visible whenever the table can accommodate them. Co-authored-by: cristos <[email protected]>
Reserve measured numeric cell widths before distributing optional table padding, and reset ANSI-styled cell fragments before padding and borders. Co-authored-by: cristos <[email protected]>
When measured numeric content plus the Date and Models fallback minima needs one more column than the terminal provides, the fallback scaler can truncate numeric cells even though the table can fit. Allow wrap-capable text columns to give back space down to the established text floor before falling back, so numeric cells remain intact at the boundary.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ccusage-guide | 1d0d237 | Commit Preview URL Branch Preview URL |
Aug 31 2026, 04:27 AM |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughThe terminal table now caps model content, preserves numeric widths during fitting, updates compact-date behavior, distributes spare width, and maintains ANSI-safe wrapping and padding. Tests cover constrained layouts, model truncation, list markers, ANSI continuation, and reset handling. ChangesTerminal layout behavior
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to This PR improves narrow terminal table layout by preserving numeric values while allowing text columns to yield space, without changing external interfaces or deployment behavior. No actionable merge-blocking risk remains beyond normal checks and review. Sequence Diagram(s)sequenceDiagram
participant TableRenderer
participant column_widths
participant fit_widths_to_terminal
participant wrap_cell_line
participant pad_cell
participant Terminal
TableRenderer->>column_widths: calculate model, numeric, and text widths
column_widths->>fit_widths_to_terminal: pass content minimums and layout metadata
fit_widths_to_terminal->>fit_widths_to_terminal: fit columns to terminal width
fit_widths_to_terminal-->>TableRenderer: return fitted column widths
TableRenderer->>wrap_cell_line: wrap over-wide cell content
wrap_cell_line->>wrap_cell_line: preserve list markers and ANSI continuation
TableRenderer->>pad_cell: format each cell
pad_cell->>Terminal: render reset-safe padded cells
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Important
This PR has two narrow-layout/rendering regressions that should be addressed before merging.
Reviewed changes
- Responsive width fitting — Reviewed the new model-column cap, numeric-width reservation, fallback minima, compact-date handling, and spare-width redistribution in
ccusage-terminal. - ANSI cell padding — Reviewed the new reset handling in
pad_celland its interaction with multiline expansion and existing terminal styling. - Regression coverage — Reviewed the added terminal tests for large totals, 80/120-column layouts, compact dates, wide characters, and ANSI cells.
@v0 or keep the SHA fresh with Dependabot | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using GPT Luna (free via Pullfrog for OSS) | 𝕏
|
|
||
| fn pad_cell(cell: &str, width: usize, align: Align) -> String { | ||
| let visible = visible_width(cell); | ||
| let cell = ensure_ansi_reset(cell); |
There was a problem hiding this comment.
ensure_ansi_reset runs after multiline expansion, so it resets the first physical fragment even when the original ANSI style intentionally continues across the newline. For example, color(style, "red continued", Color::Red) becomes a red first line and an uncolored second line, changing multiline ANSI rendering.
ccusage
@ccusage/ccusage-darwin-arm64
@ccusage/ccusage-darwin-x64
@ccusage/ccusage-linux-arm64
@ccusage/ccusage-linux-x64
@ccusage/ccusage-win32-x64
commit: |
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="rust/crates/ccusage-terminal/src/width.rs">
<violation number="1" location="rust/crates/ccusage-terminal/src/width.rs:51">
P3: For the common case where a cell has no ANSI codes, this always allocates a fresh String copy via `value.to_string()` even though the value is returned unchanged. This runs once per cell per rendered table row, so color-less renders allocate a needless copy per cell. Return `Cow<'_, str>` (Borrowed when no reset is needed) to avoid the allocation; the caller would need `.into_owned()` in the paths that own the result.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| pub(crate) fn ensure_ansi_reset(value: &str) -> String { | ||
| if !contains_ansi(value) || value.ends_with("\x1b[0m") { | ||
| return value.to_string(); |
There was a problem hiding this comment.
P3: For the common case where a cell has no ANSI codes, this always allocates a fresh String copy via value.to_string() even though the value is returned unchanged. This runs once per cell per rendered table row, so color-less renders allocate a needless copy per cell. Return Cow<'_, str> (Borrowed when no reset is needed) to avoid the allocation; the caller would need .into_owned() in the paths that own the result.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage-terminal/src/width.rs, line 51:
<comment>For the common case where a cell has no ANSI codes, this always allocates a fresh String copy via `value.to_string()` even though the value is returned unchanged. This runs once per cell per rendered table row, so color-less renders allocate a needless copy per cell. Return `Cow<'_, str>` (Borrowed when no reset is needed) to avoid the allocation; the caller would need `.into_owned()` in the paths that own the result.</comment>
<file context>
@@ -46,6 +46,13 @@ pub(crate) fn visible_width_max_line(value: &str) -> usize {
+pub(crate) fn ensure_ansi_reset(value: &str) -> String {
+ if !contains_ansi(value) || value.ends_with("\x1b[0m") {
+ return value.to_string();
+ }
+ format!("{value}\x1b[0m")
</file context>
Derive fallback widths for non-numeric columns from their rendered row content so status markers remain intact when Models is not the second column. Reclaim width from the Models column before shrinking other wrap-capable text while retaining measured numeric minima.\n\nCarry active SGR sequences onto later physical fragments of multiline cells, while keeping the per-fragment reset at cell borders to prevent style leaks into adjacent cells.
Keep dash list markers attached to provider-prefixed model names when a wrapped cell must truncate them. Replay active SGR sequences across automatic word-wrapped fragments so styled model output remains consistent. Retain explicit-newline continuation and per-cell border resets, and update the affected rendered snapshot and regressions. Co-authored-by: Cristos L-C <[email protected]>
Derive automatic-wrap continuation from the source fragment before truncation adds a display reset, preserving active SGR state in later model fragments. Treat authored newlines as logical boundaries while retaining state across automatic wrapping, and cover the complete 56-column five-column render. Co-authored-by: Cristos L-C <[email protected]>
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="rust/crates/ccusage-terminal/src/width.rs">
<violation number="1" location="rust/crates/ccusage-terminal/src/width.rs:64">
P2: When a wrapped cell uses an SGR reset other than exact `ESC[0m`, `ansi_continuation` retains every prior style command and prepends it to later fragments. Track the current SGR state, or canonicalize attribute resets, so long ANSI cells do not produce unbounded repeated escape sequences.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| let end = skip_ansi_escape(bytes, index); | ||
| let escape = &value[index..end]; | ||
| if escape.ends_with('m') { | ||
| if escape == "\x1b[0m" { |
There was a problem hiding this comment.
P2: When a wrapped cell uses an SGR reset other than exact ESC[0m, ansi_continuation retains every prior style command and prepends it to later fragments. Track the current SGR state, or canonicalize attribute resets, so long ANSI cells do not produce unbounded repeated escape sequences.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rust/crates/ccusage-terminal/src/width.rs, line 64:
<comment>When a wrapped cell uses an SGR reset other than exact `ESC[0m`, `ansi_continuation` retains every prior style command and prepends it to later fragments. Track the current SGR state, or canonicalize attribute resets, so long ANSI cells do not produce unbounded repeated escape sequences.</comment>
<file context>
@@ -46,6 +46,32 @@ pub(crate) fn visible_width_max_line(value: &str) -> usize {
+ let end = skip_ansi_escape(bytes, index);
+ let escape = &value[index..end];
+ if escape.ends_with('m') {
+ if escape == "\x1b[0m" {
+ continuation.clear();
+ } else {
</file context>
There was a problem hiding this comment.
ℹ️ No new correctness issues found in the incremental changes. The remaining explicit-newline ANSI behavior is unchanged and is not re-raised here.
Reviewed changes
- Preserved non-model text markers — Derived fallback minima from text content and prioritized the actual
Modelscolumn while shrinking narrow tables. - Maintained wrapped formatting — Grouped list markers with their following model token and replayed active ANSI state across word-wrapped fragments.
- Added regression coverage — Added tests and snapshots for status markers, ANSI continuation, truncation, compact dates, and narrow numeric layouts.
@v0 or keep the SHA fresh with Dependabot | Fix it ➔ | View workflow run | Using GPT Luna (free via Pullfrog for OSS) | 𝕏
ccusage performance comparisonPR SHA: This compares the Rust PR release binary against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
ccusage performance comparisonPR SHA: This compares the PR package against the configured base package on the same CI runner. Package runtime diagnosticsCompares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself. Fixtures: Claude
Committed fixture performanceCommitted small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage. Fixtures: Claude
Large real-world-shaped fixture performanceGenerated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures. Fixtures: Claude
Artifact size
Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees. |
Upstream through 8841f92: official Antigravity SQLite adapter (ccusage#1677), ZCode (ccusage#1675) and Grok Build CLI sources, Copilot session-state usage (ccusage#1676), Codex originator breakdowns and cache-write token accounting (ccusage#1663, ccusage#1674), date-window file skipping (ccusage#1665), session totals scoped to the date window (ccusage#1664), OpenCode v2 session usage (ccusage#1668), timestamp-aware DeepSeek V4 pricing (ccusage#1679), ETag-validated pricing cache refreshes (ccusage#1672), and numeric-column preservation in narrow tables (ccusage#1671). Conflict resolutions per the personal divergence ledger: - Antigravity: adopt the upstream native adapter wholesale; remove the personal heuristic adapter and the antigravity-analysis/ provenance directory. - Codex: keep counting copied parent history (replay.rs stays deleted), keep tier changes applying at the following turn_context, keep the pre-v0.144.0 fast windows and the 2x fast-multiplier fallback, and keep the append-aware grouped cache with serde'd parser state. Integrate upstream cache-write tokens, originator sources, session_meta line detection, and filter_codex_usage_files date-window skipping. Bump the group, per-file event, and all-agent row cache discriminators. - Claude: rewire the cached daily/session summary wrappers onto upstream's date-scoped loaders (ccusage#1664). - OpenCode: keep WAL-signature cache signatures, the summary cache wrapper, and --no-cost -> Display mapping; take upstream's v2 session usage loading and split directory loader. - Pricing: keep the explicit GLM-5.2 rates and 1,000,000-token context limit (now via put_builtin_entry for both GLM-5.1 and GLM-5.2); take upstream's DeepSeek V4 scheduled rates and catalog rules. - Terminal: keep full dates whenever the minimum full-date layout fits and attached breakdown rows; take upstream's content-aware fallback minimums and numeric-column floors, adapting the 80-column regression test to the personal full-date policy. - Presentation: keep the hidden-by-default Models column and the all-agent --with-models opt-in; regenerate zcode/copilot session snapshots under the wider first-column floor. - Ledger: audit baseline updated to 8841f92; heuristic Antigravity and replay suppression recorded as retired divergences.

Summary\n\n- Preserve numeric content when responsive terminal tables fit at the boundary.\n- Let wrap-capable text columns yield optional width before numeric cells are truncated.\n- Keep ANSI and wide-character handling intact.\n\n## Validation\n\n- Focused terminal and Codex report tests pass.\n- Verified the nine-column Codex layout at COLUMNS=120 in cmux.\n\nFixes #946
Summary by cubic
Fixes #946 so terminal tables preserve numeric column content and ANSI formatting when they shrink to fit. Previously width fitting could truncate large numeric values even though wrap-capable text columns had space to give; now text columns yield first and measured numeric widths are reserved before any truncation.
Written for commit 1d0d237. Summary will update on new commits.
Summary by CodeRabbit