Skip to content

refactor(usage): extract the usage-percent widgets onto a shared module - #545

Draft
zachthedev wants to merge 3 commits into
sirmalloc:mainfrom
zachthedev:refactor-usage-percent-widgets
Draft

refactor(usage): extract the usage-percent widgets onto a shared module#545
zachthedev wants to merge 3 commits into
sirmalloc:mainfrom
zachthedev:refactor-usage-percent-widgets

Conversation

@zachthedev

Copy link
Copy Markdown
Contributor

Stacked on #456, so this targets main and the diff below includes that PR
until it merges. The commit to review here is just the last one. I will retarget
once #456 lands.

Stacked deliberately rather than sequenced after: #456 threads
formatPercent(value, format) through exactly these five render bodies, so
extracting on top of it absorbs that threading instead of colliding with it.

Five widgets carried five copies of one render body: SessionUsage.ts,
WeeklyUsage.ts, WeeklySonnetUsage.ts, WeeklyOpusUsage.ts and
FableWeeklyUsage.ts. Normalized for the per-widget names, SessionUsage and
WeeklyUsage are identical, so are WeeklySonnetUsage and WeeklyOpusUsage,
and the worst pair differs by three lines. Net -225 lines.

Each file is now delegation, following the existing shared/speed-widget.tsx and
InputSpeed.ts pair.

Six axes vary and are parameterized: label, preview percent, usage data field,
window resolver, display name, description. Default color, category, editor
display, handleEditorAction, keybinds and the three supports* flags were
already uniform across all five, so they live in the shared module outright.
None of the five declares getHideableStates or renderEditor.

One rendered string changes. FableWeeklyUsage's label was the only one of
the five that disagreed with its own display name:

Widget Display name Label
Session Usage Session Usage Session:
Weekly Usage Weekly Usage Weekly:
Weekly Sonnet Usage Weekly Sonnet Usage Weekly Sonnet:
Weekly Opus Usage Weekly Opus Usage Weekly Opus:
Weekly Fable Usage Weekly Fable Usage Fable Weekly:

It becomes Weekly Fable: , matching the sibling rule of display name minus
Usage. Raw mode prints no label, so raw output is unchanged, as are the widget
type fable-weekly-usage, the class name and the display name. Six assertions in
FableWeeklyUsage.test.ts move with it; no other assertion in the repo changed.

Two things worth a look:

  • The resolvers dispatch at call time, not through a captured reference. All
    five test files spy their resolver on the utils/usage namespace. A
    module-level config table holding resolveWeeklyOpusUsageWindow would bind at
    module evaluation, before beforeEach installs the spy, and silently bypass
    it. SessionUsage also needs a second argument (context.blockMetrics) that
    the four weekly resolvers ignore, so the parameter takes the whole context.
  • ExtraUsageUtilization stays out. It gates on a tri-state
    extraUsageEnabled with its own n/a branch, has no window resolver, no time
    cursor, an extra keybind and a different default color. Folding it in would mean
    three optional flags that are uniformly absent from the five. It is also the one
    usage widget that does not use runUsagePercentWidgetSuite.

The shared suite gains a whole-percent case, which is the one test addition
beyond the label. formatPercent's format argument is optional and its default
reproduces the baseline output, so a render path that stopped passing the
resolved format rendered identically under default settings and nothing could
catch it. Dropping the argument at all three call sites now fails all five
widgets.

Tested: bun run lint clean; bun test 1900 pass, plus the
global-command-resolution failure main already has on this host and one hit of
the flaky fetchUsageData case.

zachthedev and others added 3 commits August 5, 2026 13:59
Add a per-widget `numberFormat` ({ style: precise|compact|whole, decimals })
and an optional per-kind global (Settings.numberFormat), resolved by
resolveNumberFormat(kind, item, settings) with a set global winning over the
per-widget value (matching overrideForegroundColor / globalBold). Every numeric
formatter routes through renderMagnitude with a type-appropriate baseline, so
default output is unchanged: tokens/speed/percent stay 1 decimal, cost 2,
memory G1/M0/K0.

Styles: precise keeps trailing zeros (1.0M, today's default); compact trims
them (1M, 1.1M); whole drops decimals (1M).

Threaded through every numeric widget (tokens, context length/window/bar,
compaction reclaimed, cache read/write/hit, free memory, session cost, speeds,
context %, usage sliders, block/weekly timers). TUI: ColorMenu (n) cycles a
widget's style, Global Overrides has a per-type submenu, and decimals is set in
settings.json. Optional schema fields, so no settings migration.

Co-Authored-By: Claude <noreply@anthropic.com>
Widgets declare supportsNumberFormat(), matching the existing supportsRawValue / supportsColors convention, and the items editor injects a shared precision keybind for any widget that returns true. The cycle itself moves to utils/number-format.ts so all number-format logic lives in one module.

This keeps every non-color override on the line editor screen, where the rest of them already live, and keeps precision reachable while a powerline theme is active. The color menu goes back to color, bold and dim only.

The keybind is '.', reading as the decimal point, because every letter is already taken by a widget bind or an editor-level one. The items-editor dispatcher no longer bails early for widgets that expose no keybinds of their own, so plain numeric widgets receive the injected bind. Fable Weekly Usage, which landed since this branch opened, is threaded through formatPercent too.
SessionUsage, WeeklyUsage, WeeklySonnetUsage, WeeklyOpusUsage and
FableWeeklyUsage carried five copies of one render body. Normalized for
the per-widget names, the worst pair differed by three lines and two
pairs were identical. Each file is now delegation, following the
speed-widget.tsx and InputSpeed.ts pair.

Six axes vary and are parameterized: label, preview percent, usage field,
window resolver, display name and description. Default color, category,
editor display, editor actions, keybinds and the three supports* flags
were already uniform.

The resolvers dispatch at call time rather than through a captured
reference, because the widget tests spy them on the utils/usage namespace
and a module-level table would bind before the spy is installed.

FableWeeklyUsage's label was the one of the five that disagreed with its
own display name, so 'Fable Weekly: ' becomes 'Weekly Fable: ', matching
the sibling rule of display name minus ' Usage'. Raw mode prints no label
and is unaffected. The widget type, class name and display name are
untouched.

ExtraUsageUtilization stays out: it gates on a tri-state enabled flag,
has no window resolver, no time cursor and a different default color.

The shared suite gains a whole-percent case. formatPercent's format
argument is optional and its default reproduces the baseline, so a render
path that stopped passing the resolved format rendered identically under
default settings and no test could see 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