Skip to content

fix(progress): keep the spinner status on a single row - #1500

Merged
ryoppippi merged 3 commits into
mainfrom
spinner
Jul 27, 2026
Merged

fix(progress): keep the spinner status on a single row#1500
ryoppippi merged 3 commits into
mainfrom
spinner

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the flickering progress spinner on narrow terminals (#1411). Each frame clears its line with \r\x1b[K, which only erases the row the cursor sits on, so a status wider than the terminal wraps and every redraw leaves the overflow row behind.

What changed

  • ccusage-core::progress truncates the status to the terminal width minus the spinner prefix and one spare column before writing it, so the line always occupies a single row and the single-line clear stays correct.
  • table.rs already had ANSI- and CJK-aware truncation for table cells, so that helper moves to ccusage-terminal::width as the public truncate_to_width rather than being duplicated. Its tests and insta snapshot move with it.

Why

On an 80-column terminal the combined status is ~85 columns:

⠏ Refreshing model pricing from LiteLLM... :: Loading usage logs (0/2) :: Claude, Codex

so it wrapped on every online run. -O skips the pricing status, which is why it avoided the flicker.

Testing

  • just rust::test, clippy (--all-targets), just hawk, and just fmt are clean.
  • New unit tests cover the narrow, wide, and no-room-at-all cases, plus width/CJK/ANSI truncation behaviour.
  • Verified end to end under a pty: at COLUMNS=40 all 284 spinner frames stay within 39 columns (83 of them truncated, none wrapping); at COLUMNS=80 nothing is truncated.

Closes #1411


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.


Summary by cubic

Fixes spinner flicker on narrow terminals by keeping the spinner line on a single row. Also makes truncation ANSI/CJK-aware in ccusage-terminal and treats zero-width truncation as empty to avoid overflow.

  • Bug Fixes

    • Truncate spinner status to terminal width minus the spinner prefix and one spare column using truncate_to_width and terminal_width to prevent wrapping and flicker.
    • Make truncate_to_width(..., 0) return "" (not "…") to avoid overrunning the width on ultra-narrow terminals.
  • Refactors

    • Move ANSI/CJK-aware truncation from table.rs to ccusage-terminal::width as public truncate_to_width, share ANSI escape scanning via skip_ansi_escape, and re-export in ccusage-terminal; update table code and snapshots.

Written for commit 7f130ed. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a public truncate_to_width helper for ANSI-aware, Unicode column–based truncation.
    • Re-exported the truncation helper from the terminal crate for easier access.
  • Bug Fixes

    • Reduced spinner/status flicker when terminal line wrapping occurs.
    • Improved status rendering in narrow terminals by truncating text to available width.
    • Enhanced table truncation to preserve ANSI styling correctly and handle wide characters.
  • Documentation

    • Updated terminal crate documentation to reflect its width/truncation responsibilities.

Each spinner frame clears its line with `\r\x1b[K`, which only erases the
row the cursor sits on. When the status is wider than the terminal it
wraps, so every redraw leaves the overflow row on screen and the line
reads as flicker. The combined status
`Refreshing model pricing from LiteLLM... :: Loading usage logs (0/2) ::
Claude, Codex` is ~85 columns, so any terminal narrower than that hits it
on every online run; `-O` skips the pricing status and avoids it.

Truncate the status to the terminal width minus the spinner prefix and one
spare column before writing, so the single-line clear stays correct and
the cursor never ends a frame in the wrap-pending state.

`table.rs` already had ANSI- and CJK-aware truncation for cells, so that
helper moves to `ccusage-terminal::width` as the public
`truncate_to_width` instead of being duplicated. Its tests and insta
snapshot move with it.

Verified under a pty at `COLUMNS=40`: all 284 spinner frames stay within
39 columns, and at `COLUMNS=80` nothing is truncated.

Closes #1411
Copilot AI review requested due to automatic review settings July 27, 2026 13:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The terminal crate adds ANSI-aware display-width truncation, table rendering adopts the shared helper, and progress statuses are truncated to fit the terminal while reserving spinner space.

Changes

Terminal width rendering

Layer / File(s) Summary
ANSI-aware truncation API
rust/crates/ccusage-terminal/src/width.rs, rust/crates/ccusage-terminal/src/lib.rs, rust/crates/ccusage-terminal/README.md
Adds and documents truncate_to_width, including Unicode display widths, ANSI handling, reset preservation, and tests.
Table truncation integration
rust/crates/ccusage-terminal/src/table.rs
Replaces the local truncate_visible implementation with the shared width helper in table cell wrapping.
Progress status width fitting
rust/crates/ccusage-core/src/progress.rs
Reserves spinner prefix space and truncates rendered status text for narrow terminals, with coverage for narrow, wide, and minimal widths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes truncate the progress status to fit the terminal width, which matches issue #1411's requirement to prevent wrapping flicker.
Out of Scope Changes check ✅ Passed The README, width helper, and table cleanup all support the spinner truncation fix and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: preventing the progress spinner status from wrapping onto multiple rows.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spinner

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 27, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide 7f130ed Commit Preview URL

Branch Preview URL
Jul 27 2026, 01:54 PM

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed changes — moves ANSI/CJK-aware truncation from table.rs to width.rs as a public truncate_to_width, then uses it in the spinner render path so the status always fits in a single row on narrow terminals, fixing flicker (#1411).

  • Make truncate_to_width public in ccusage-terminal::width — moves the private truncate_visible from table.rs, renames it, adds a docstring and doctests. contains_ansi and char_display_width become private helpers.
  • Truncate spinner status to terminal width in progress.rsfit_status_to_width calls truncate_to_width with terminal_width() - SPINNER_PREFIX_WIDTH - 1, reserving space for the spinner frame and one spare column to avoid wrap-pending cursor state.
  • Update call sites and snapshotstable.rs uses truncate_to_width instead of the old private function. Snapshots and README docs updated.

✅ No new issues found.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
rust/crates/ccusage-terminal/src/width.rs (1)

3-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate ANSI-escape-skipping logic between visible_width and truncate_to_width.

Both functions re-implement the same CSI-sequence-skipping loop (detect 0x1b, optional [, scan to the first ASCII alpha terminator). Extracting a small fn skip_ansi_escape(bytes: &[u8], index: usize) -> usize helper would remove the duplication and reduce the risk of the two copies drifting apart.

♻️ Proposed extraction
+fn skip_ansi_escape(bytes: &[u8], mut index: usize) -> usize {
+    index += 1;
+    if index < bytes.len() && bytes[index] == b'[' {
+        index += 1;
+        while index < bytes.len() && !(bytes[index] as char).is_ascii_alphabetic() {
+            index += 1;
+        }
+        index += usize::from(index < bytes.len());
+    }
+    index
+}

Then both visible_width and truncate_to_width can call index = skip_ansi_escape(bytes, index); (adjusting truncate_to_width to also capture start..index for copying).

Also applies to: 62-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/crates/ccusage-terminal/src/width.rs` around lines 3 - 26, Extract the
shared ANSI CSI-sequence scanning logic from visible_width and truncate_to_width
into a skip_ansi_escape(bytes: &[u8], index: usize) helper. Replace both inline
escape-skipping loops with this helper, preserving truncate_to_width’s
escape-slice copying and the existing behavior for non-CSI escapes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/crates/ccusage-terminal/src/width.rs`:
- Around line 55-99: Update truncate_to_width to return an empty string when
width == 0, while preserving the ellipsis behavior for width == 1 and larger
truncated values. Update the
fits_the_status_when_the_terminal_has_no_room_at_all test in fit_status_to_width
to expect an empty status for the zero-width budget.

---

Nitpick comments:
In `@rust/crates/ccusage-terminal/src/width.rs`:
- Around line 3-26: Extract the shared ANSI CSI-sequence scanning logic from
visible_width and truncate_to_width into a skip_ansi_escape(bytes: &[u8], index:
usize) helper. Replace both inline escape-skipping loops with this helper,
preserving truncate_to_width’s escape-slice copying and the existing behavior
for non-CSI escapes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 99e710b7-6ee2-4e9c-af94-b6ec895b6a5a

📥 Commits

Reviewing files that changed from the base of the PR and between d7fec09 and 0f2d584.

⛔ Files ignored due to path filters (2)
  • rust/crates/ccusage-terminal/src/snapshots/ccusage_terminal__table__tests__snapshots_ansi_truncation_boundary.snap is excluded by !**/*.snap
  • rust/crates/ccusage-terminal/src/snapshots/ccusage_terminal__width__tests__snapshots_ansi_truncation_boundary.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • rust/crates/ccusage-core/src/progress.rs
  • rust/crates/ccusage-terminal/README.md
  • rust/crates/ccusage-terminal/src/lib.rs
  • rust/crates/ccusage-terminal/src/table.rs
  • rust/crates/ccusage-terminal/src/width.rs

Comment thread rust/crates/ccusage-terminal/src/width.rs
@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ccusage@1500

@ccusage/ccusage-darwin-arm64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-arm64@1500

@ccusage/ccusage-darwin-x64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-x64@1500

@ccusage/ccusage-linux-arm64

npx https://pkg.pr.new/@ccusage/ccusage-linux-arm64@1500

@ccusage/ccusage-linux-x64

npx https://pkg.pr.new/@ccusage/ccusage-linux-x64@1500

@ccusage/ccusage-win32-x64

npx https://pkg.pr.new/@ccusage/ccusage-win32-x64@1500

commit: 7f130ed

`truncate_to_width` treated a width of 0 like a width of 1 and returned the
ellipsis, which occupies one display column and so overran the budget it was
given. The spinner reaches this on terminals of three columns or fewer,
where `fit_status_to_width` has no room left after the spinner prefix, so
the status could still spill into the column that is deliberately kept
spare.

Return an empty string instead, and fold the CSI-scanning loop that
`visible_width` and `truncate_to_width` each carried into a shared
`skip_ansi_escape` helper so the two cannot drift apart.
@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 0f2d584158ac
Base SHA: d7fec09f558c

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: d7fec09f558c

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 0f2d584158ac
Base SHA: d7fec09f558c

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: d7fec09f558c

Copilot AI review requested due to automatic review settings July 27, 2026 13:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pullfrog

pullfrog Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The security & lint preflight check failed due to a race condition in the schema-gen treefmt formatter: cp -f inside the Nix sandbox failed with "File exists" when treefmt ran the formatter with 32 threads. Fixed by replacing cp -f with rm -f + cp in both nix/treefmt.nix and apps/ccusage/justfile. Verified with nix build .#checks.x86_64-linux.treefmt — passes cleanly. The commit (381b5f0d) is on the spinner branch but could not be pushed due to org-level GITHUB_TOKEN push restrictions. Patch posted as a PR comment.

Task list (6/7 completed)

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed changes — the incremental commit extracts a shared skip_ansi_escape helper and makes truncate_to_width(_, 0) return an empty string instead of an ellipsis, then updates the fit_status_to_width test to match.

  • Extract skip_ansi_escape helper — the duplicated ANSI CSI-sequence scanning logic in visible_width and truncate_to_width is replaced with a shared private function.
  • Fix zero-width truncationtruncate_to_width now returns "" when width == 0 rather than "…", since the ellipsis has no column budget at that width.

✅ No new issues found.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@pullfrog

pullfrog Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The security & lint preflight check failed because the schema-gen formatter in treefmt errored with:

cp: cannot create regular file 'docs/public/config-schema.json': File exists

This is a race in the Nix sandbox when treefmt runs the schema-gen formatter with 32 threads — one invocation creates docs/public/config-schema.json while another tries the same cp -f and hits a coreutils edge case.

Fix: replaced the single cp -f with rm -f followed by cp in both nix/treefmt.nix and apps/ccusage/justfile. Verified with nix build .#checks.x86_64-linux.treefmt — passes cleanly.

Patch (also committed on spinner branch):

diff --git apps/ccusage/justfile apps/ccusage/justfile
index f02f5d7b..78b72347 100644
--- apps/ccusage/justfile
+++ apps/ccusage/justfile
@@ -24,6 +24,7 @@ ensure-native-binary:
 generate-schema:
     cargo run --quiet --manifest-path ../../rust/Cargo.toml -p ccusage-config --bin generate-config-schema -- config-schema.json
     oxfmt --write config-schema.json
+    rm -f ../../docs/public/config-schema.json
     cp config-schema.json ../../docs/public/config-schema.json
 
 start *args:
diff --git nix/treefmt.nix nix/treefmt.nix
index 3c026979..e7608d59 100644
--- nix/treefmt.nix
+++ nix/treefmt.nix
@@ -58,7 +58,8 @@ in
             cp -f "$tmp" apps/ccusage/config-schema.json
           fi
           if [ -d docs/public ] && ! cmp -s apps/ccusage/config-schema.json docs/public/config-schema.json; then
-            cp -f apps/ccusage/config-schema.json docs/public/config-schema.json
+            rm -f docs/public/config-schema.json
+            cp apps/ccusage/config-schema.json docs/public/config-schema.json
           fi
         '';
       };

Push is blocked by org-level GITHUB_TOKEN restrictions — the patch needs to be applied manually or pushed with a PAT.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 84cd6deb4fd4
Base SHA: d7fec09f558c

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: d7fec09f558c

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 84cd6deb4fd4
Base SHA: d7fec09f558c

Performance comparison skipped.

Base package URL was not ready before 300.000s. Fixture performance comparison requires a base package when --base-dir is not provided.

Base package: d7fec09f558c

Copilot AI review requested due to automatic review settings July 27, 2026 13:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 7f130ed1f6ac
Base SHA: 24a3ed0e0346

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runtime diagnostics

Compares 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 /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 348.9ms 2.89 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 311.7ms 3.23 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 143.3ms 7.03 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 104.2ms 9.66 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.4ms 4.9ms 6.41x 55.00 MiB 12.45 MiB 0.23x 0.05 MiB/s 0.32 MiB/s
claude session --offline --json 0.00 MiB 25.6ms 2.6ms 9.94x 55.00 MiB 12.45 MiB 0.23x 0.06 MiB/s 0.60 MiB/s
codex daily --offline --json 0.00 MiB 24.1ms 2.5ms 9.77x 55.00 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.35 MiB/s
codex session --offline --json 0.00 MiB 23.2ms 2.4ms 9.70x 55.00 MiB 10.45 MiB 0.19x 0.04 MiB/s 0.36 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 369.4ms 323.6ms 1.14x 952.58 MiB 954.58 MiB 1.00x 2.73 GiB/s 3.11 GiB/s
codex --offline --json 1.01 GiB 128.2ms 95.1ms 1.35x 412.65 MiB 410.65 MiB 1.00x 7.85 GiB/s 10.59 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.78 KiB 18.78 KiB +0.00 KiB 1.00x
installed native package binary 4156.91 KiB 4156.97 KiB +0.06 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 7f130ed1f6ac
Base SHA: 24a3ed0e0346

This compares the PR package against the configured base package on the same CI runner.

Package runtime diagnostics

Compares 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 /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 359.0ms 2.80 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 309.2ms 3.26 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 122.9ms 8.19 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 92.5ms 10.88 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 31.2ms 28.8ms 1.08x 55.00 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
claude session --offline --json 0.00 MiB 29.0ms 25.9ms 1.12x 55.00 MiB 55.00 MiB 1.00x 0.05 MiB/s 0.06 MiB/s
codex daily --offline --json 0.00 MiB 26.0ms 23.7ms 1.10x 55.00 MiB 55.00 MiB 1.00x 0.03 MiB/s 0.04 MiB/s
codex session --offline --json 0.00 MiB 23.6ms 23.7ms 1.00x 55.00 MiB 55.00 MiB 1.00x 0.04 MiB/s 0.04 MiB/s

Large real-world-shaped fixture performance

Generated 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 /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 374.6ms 351.6ms 1.07x 946.58 MiB 948.58 MiB 1.00x 2.69 GiB/s 2.86 GiB/s
codex --offline --json 1.01 GiB 118.0ms 117.8ms 1.00x 410.66 MiB 424.65 MiB 1.03x 8.53 GiB/s 8.55 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.78 KiB 18.78 KiB +0.00 KiB 1.00x
installed native package binary 4156.91 KiB 4156.97 KiB +0.06 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@ryoppippi
ryoppippi merged commit 292e720 into main Jul 27, 2026
35 checks passed
@ryoppippi
ryoppippi deleted the spinner branch July 27, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Progress spinner flickers when the "Refreshing model pricing…" status line wraps on narrow terminals

3 participants