Skip to content

feat(widgets): value colors in dollars, tokens and counts on the cost, token and compaction widgets - #47

Draft
eric-engberg wants to merge 74 commits into
feat/value-colors-morefrom
feat/amount-value-colors
Draft

eric-engberg wants to merge 74 commits into
feat/value-colors-morefrom
feat/amount-value-colors

Conversation

@eric-engberg

@eric-engberg eric-engberg commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Based on #46 (value colors on the percent widgets), and merges #16 (the shared token count base), #35 (Session Cost Rate) and #37 (Daily Cost Rate), which add or reshape widgets this PR colors. Review the two commits after those merges. It goes upstream after #41, #46 and those three.

What

Value colors come to the widgets that count an amount rather than a percent, with break points in that amount's own unit:

Widget Key Green below Red from ←→ step
Session Cost v $5 $20 $1
Session Cost Rate, Daily Cost Rate v $5/hr $15/hr $1
Tokens Input v 10k 50k 5k
Tokens Output v 100k 1M 50k
Tokens Cached, Tokens Total v 10M 100M 5M
Compaction Counter l 1 3 1

Each one opens the same editor as the percent widgets, which now shows break points, samples and typed values in the widget's unit: $7.50, 250k or 1.5M, 3.

Why

#46 covered the widgets measured against a whole. The rest of the numbers on a status line, what a session costs, how fast it spends and how many tokens it has used, are just as worth reading at a glance. They have no 100%, though, so their break points need to be amounts.

The token break points come from measuring the 23 sessions on my machine, with each API response counted once:

  • Tokens Input had a median of 90 and never passed 14.6k.
  • Tokens Output had a median of 17.4k and reached 4.3M.
  • Tokens Cached and Total had a median of 3.5M and reached 2.3 billion, because cache reads add up every turn.

A single pair of break points would leave Input always green and Cached always red, so each widget has its own. With these, 15 to 22 of the 23 sessions stay green on each widget, and only the largest reach yellow or red.

Counting each response once is what upstream's open fix for over-counted tokens does (sirmalloc#595, for sirmalloc#549). Today the token widgets count a response once per content block, which on these sessions comes to 2.6 to 3.7 times the real count (medians of 164 for Input and 6.1M for Cached). Until that fix lands, the colors arrive earlier in a session than these numbers suggest; the break points are set for the corrected counts, so they won't need changing when it does.

How

  • A scale can name its unit (ValueUnit in value-units.ts): percent (the default, so the existing scales don't change), dollars, dollars per hour, tokens, or a count. The unit sets:

    • how the editor shows a value;
    • how a typed value reads: dollars to the cent with or without $, and tokens in full or with k or M;
    • its step, range and decimal places.

    Break points stay one decimal place apart: 1, or a cent for dollars.

  • A scale can set where its gradient ends by default. An amount has no 100%, so every widget here ends its gradient at its high break point.

  • Compaction Counter uses l (va(l)ue colors), since v already cycles its value. It colors the count, or the auto, manual or unknown count. Its icon or label, its trigger split and the tokens reclaimed keep the widget color. For that, formatColoredValue takes an optional suffix, drawn like the label. The reclaimed value is tokens, not a count, so it has no value colors.

  • Where the code lives:

  • The editor's lowest sample is now half the mid break point rounded down rather than to the nearest. That keeps a sample of 0 below a count's mid of 1. On a percent scale it can move that sample by one, e.g. 43% to 42%.

  • Nothing changes until value colors are turned on. Off, every widget draws exactly the text it did before.

Demo

Piped through the real pipeline at three levels, with made-up amounts: a payload with the session cost and times, and a transcript with the token usage and compactions. Daily Cost Rate sees only this session, so it's shown on clock time, apart from Session Cost Rate on Claude's active time.

Powerline

Value colors on the amount widgets, Powerline

Plain

Value colors on the amount widgets, plain

The editor walkthrough:

  1. Session Cost shows its break points in dollars. Value colors go on, and mid is typed as 7.50.
  2. Tokens Cached shows 10M and 100M.
  3. Compaction Counter's editor opens on l, in compactions.

At the end the preview shows all three in their colors.

Powerline

Value colors editor in dollars, tokens and compactions, Powerline

Plain

Value colors editor in dollars, tokens and compactions, plain

Testing

  • Tests, each written first and seen failing:
    • value-units.test.ts (6): each unit's display and typed values, including $2.50, 1.5M and a float product like 1.005k, and the rounding helpers.
    • value-coloring.test.ts (6 new):
      • a dollar, token and count scale's bands, steps and typed values;
      • errors in the unit (Mid has to start below high ($20).);
      • its own gradient end;
      • a suffix kept in the widget color.
    • value-colors-editor.test.ts (3 new): a dollar scale's rows, samples, hint and typed 7.50; its gradient ending at $20; and a count's sample keeping 0 below mid.
    • amount-value-colors.test.ts (42):
      • for all eight widgets: the key, the editor row, the editor and preservesRenderedColors, and unchanged output with value colors off;
      • each widget's bands at its break points;
      • each token widget's own defaults;
      • Compaction Counter's label, suffix, metrics and glyph editor.
    • The existing key tests for Compaction Counter, Session Cost Rate and Daily Cost Rate now include the new key.
  • Bun: bun test gives 3155 pass, 0 fail (Bun 1.4.2). bun run lint is clean. A 12-run loop of the editor Ink tests had no failures. The first commit alone also passes lint and tests (3113).
  • Node 26 vitest: the 9 touched test files pass (204 tests).
  • Built CLI on both runtimes: runtime-check.sh -c amount-value-colors, with the demo's mid level seeded, showed Bun 1.4.2 and Node 26.10.0 agreeing byte for byte in plain and Powerline modes. The TUI opens and exits under both.

Pin down the parts of Tokens Input, Output, Cached and Total the existing
tests left open: Input and Output render nothing when neither the transcript
metrics nor the context_window totals have a count, a zero transcript count
wins over the context_window totals, a zero context_window total is hidden by
the zero hide state, non-zero counts and the preview sample stay visible with
that state on, a global token number format wins over the widget's own, and
each widget's name, description, color, category, editor row, hide state and
supported options.
Tokens Input, Output, Cached and Total were the same class apart from where
the count comes from, and their label, name, color, description and preview
sample. Each carried its own copy of the render (preview sample, missing
count, zero hide state, number format, raw or labeled value), the zero hide
state, the editor row, the category and the supported options.
TokenCountWidget now holds all of that; each widget names its label and
preview sample and reads its count.

Behavior is unchanged.
Session Cost shows what a session has cost so far, but not how fast it's
spending. Session Cost Rate divides the session cost by time, the
agent's hourly rate: `Rate: $5.00/hr`.

By default it divides by the time Claude spent working (the API
duration Claude Code reports), which reads as the rate for the hours it
actually worked. A `t` keybind switches to clock time, the whole session
including time spent waiting for the user, which reads as the burn rate.
It shows nothing until a full minute of the chosen time has passed,
since a rate over the first seconds swings wildly.
Claude Code reports each session's cost and time as running totals.
Keep each session's totals per Claude Code profile in the daily state
file, with where they stood when the UTC day began (zero for a session
that started today, otherwise the last totals seen yesterday, or the
first seen today), and work out today's totals across the profile's
sessions: cost, active time (parallel sessions add up) and clock time
(the time any session was running, counted once). Unchanged totals
aren't rewritten more than once a minute.
When a line shows Daily Cost Rate, each render records its session's
totals under the Claude Code profile (CLAUDE_CONFIG_DIR, or default)
and passes today's totals to widgets as dailyCost. With the widget's
billed spend option on, the usage prefetch also fetches the extra usage
state and works out today's billed spend; without it, the widget
triggers no usage fetch at all.
Session Cost Rate shows one session's cost per hour; Daily Cost Rate
shows it across all of today's Claude Code sessions ("Rate Today:
$6.20/hr"). It divides Claude Code's own session costs by default; the
b keybind switches to today's billed spend (as Extra Usage Today shows
it, including claude.ai and other machines). The t keybind switches from
active time (Claude working, parallel sessions adding up) to clock time
(the time any session was running). It shows nothing until a full minute
of the chosen time has passed.
…t/amount-value-colors

# Conflicts:
#	docs/USAGE.md
…amount-value-colors

# Conflicts:
#	docs/USAGE.md
#	src/utils/usage-prefetch.ts
#	src/utils/widget-manifest.ts
#	src/widgets/index.ts
Value colors only knew percents: break points and the gradient's end
were whole percents, stepped by 5 and shown with a % sign. A scale can
now name a unit (dollars, dollars per hour, tokens or a count), and
everything the editor shows or takes follows it: "$5" and "$2.50", "100k"
and "1.5M", "3". Each unit sets its step, its range, how many decimals
a value can have (dollars go to the cent) and how a typed value reads
(tokens take "250k" or "1.5M"). A scale can also set where its gradient
ends by default, since an amount has no 100%.

Percent scales are unchanged, except that the editor's lowest sample
is now half the mid break point rounded down rather than to the
nearest, so a count's mid of 1 keeps a sample of 0 below it.

formatColoredValue takes an optional suffix, drawn in the widget color
like the label, for text after the value.
… and Compaction Counter

Value colors (v) come to the widgets that count an amount, with break
points in its unit:
- Session Cost: green below $5, yellow below $20, red from $20.
- Session Cost Rate and Daily Cost Rate: $5/hr and $15/hr.
- Tokens Input: 10k and 50k; Tokens Output: 100k and 1M; Tokens Cached
  and Tokens Total: 10M and 100M. Uncached input stays in the
  thousands over a session while cache reads add up every turn, so one
  pair of break points can't fit all four.
- Compaction Counter (l, since v cycles its value): 1 and 3, on the
  count or the auto, manual or unknown count. Its label, trigger split
  and tokens reclaimed keep the widget color, and the reclaimed value
  has no value colors.

Each gradient ends at the widget's high break point by default. Off,
every widget draws exactly the text it did before.
# Conflicts:
#	src/widgets/CompactionCounter.ts
#	src/widgets/SessionCost.ts
#	src/widgets/TokensCached.ts
#	src/widgets/TokensInput.ts
#	src/widgets/TokensOutput.ts
#	src/widgets/TokensTotal.ts
#	src/widgets/shared/token-count-widget.ts
#	src/widgets/shared/value-coloring.ts
@eric-engberg
eric-engberg added this pull request to stack #12 October 7, 2026 02:52
@eric-engberg eric-engberg reopened this Oct 7, 2026
@eric-engberg eric-engberg reopened this Oct 7, 2026
@sonarqubecloud

Copy link
Copy Markdown

Brings in upstream main through #36. Daily Cost Rate's billed-spend test
reads the fetch time from getUsageFetchedAtMs, which #36 now uses.
Brings in upstream main through #46. Daily Cost Rate's billed-spend test
stubs getUsageFetchedAtMs, which #36 now reads in place of its own
getUsageFetchedAt.

# Conflicts:
#	src/ccstatusline.ts
#	src/types/RenderContext.ts
# Conflicts:
#	src/utils/usage-prefetch.ts
It names its label like the other labeled widgets, so the label editor
offers it.
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.

1 participant