Skip to content

feat(usage): add Weekly Limit Timer and Weekly Fable Limit Timer widgets - #768

Merged
sirmalloc merged 1 commit into
sirmalloc:mainfrom
eric-engberg:feat/weekly-limit-timer
Oct 30, 2026
Merged

sirmalloc merged 1 commit into
sirmalloc:mainfrom
eric-engberg:feat/weekly-limit-timer

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

Relates to #55, where a user asked for a projection of the 5-hour and 7-day limits.

What

Two widgets that say how long until a 7-day limit at this week's average pace, shown only when that comes before the window's reset:

  • Weekly Limit Timer, for the all-models window: Weekly limit in: 1d 22hr 40m. Its label reads as a countdown, like Block Limit Timer's Limit in: .
  • Weekly Fable Limit Timer, for Fable's own window: Fable limit in: 1d 4hr 8m.
Weekly: 72.0%  |  Weekly limit in: 1d 22hr 40m  |  Weekly Reset: 1d 23hr 59m
Weekly: 40.0%  |  Weekly Fable: 81.0%  |  Fable limit in: 1d 4hr 8m  |  Weekly Reset: 1d 23hr 59m
  • Both have raw value mode and s for the short form (1d22h40m), as Block Limit Timer does. Neither ever shows 0m.
  • Hidden during the window's first day, when the pace reaches the limit only after the reset, and at 100%, where Weekly Reset Timer takes over.

Why

Block Limit Timer answers "will I hit the 5-hour limit before it resets?" The weekly limits raise the same question over a longer stretch, and running out of one costs days rather than hours. Fable has a weekly limit of its own, which Weekly Fable Usage already shows.

The pace has to differ from the 5-hour one. Block Limit Timer extrapolates the last 30 minutes, which suits a window you work through in one sitting. Nobody works a week straight, so the same rule would put a weekly limit a day or two away during any busy hour. These use the week's average instead, the window's percent over the time since it started, so nights and days off count toward the pace. The window is the account's own: it starts at its reset time minus 7 days, not at a calendar week.

They wait a day because early on, a single busy hour would read as the week's pace.

How

  • utils/weekly-limit.ts, getWeeklyLimitInMs({ percent, nowMs, resetAtMs }): a pure function.

    • The pace is percent / elapsed, where elapsed runs from resetAt − 7 days to now, and the limit comes in (100 − percent) × elapsed / percent.
    • It returns null unless at least a day has elapsed, the percent is between 0 and 100 (both excluded), and the limit comes strictly before the reset.
  • widgets/shared/limit-timer-widget.ts:

    • Block Limit Timer's rendering, s key, editor display, raw value and the 1-minute floor move into a LimitTimerWidget base.
    • Each timer supplies its label, its preview sample, and the milliseconds until its limit.
    • Block Limit Timer still reads them from the 5-hour forecast, and its tests pass unchanged.
  • WeeklyLimitTimer takes the window it follows, from a small table, as the usage percent widgets take their kind:

    • all models by default: weeklyUsage and weeklyResetAt, from the payload's rate_limits.seven_day or the usage API;
    • Fable: fableUsage and fableResetAt, from the usage API only, since the payload has no Fable window. Like Weekly Fable Usage's time cursor, Fable's window falls back to the all-models reset when the API sends none of its own.

    FableWeeklyLimitTimer is the Fable instance. Neither needs a history file.

  • Prefetch: each widget requires its window's percent and reset; for Fable, the all-models reset can stand in for Fable's. A failed fetch made only for these widgets isn't an error for the rest of the line, as with the forecast widgets.

  • Registered after Weekly Usage and Weekly Fable Usage. docs/USAGE.md describes both, and the key list now says s for all three limit timers.

Demo

Piped from seeded payloads (made-up data) in six situations. The first five show Weekly Usage, Weekly Limit Timer and Weekly Reset Timer. The last adds Weekly Fable Usage and Weekly Fable Limit Timer: Fable is on pace for its limit while all models aren't. Its Fable percent comes from a seeded usage cache, since the payload has no Fable window.

Powerline

The weekly limit timers in six situations, Powerline

Plain

The weekly limit timers in six situations, plain

Adding Weekly Limit Timer after Weekly Usage with the picker's search; the preview shows its sample.

Powerline

Adding Weekly Limit Timer from the picker, Powerline

Plain

Adding Weekly Limit Timer from the picker, plain

Testing

  • weekly-limit.test.ts:
    • 72% five days in gives exactly 46h 40m; 30% one day in gives 56h.
    • Hidden when the limit comes after the reset, or exactly at it.
    • The day boundary: hidden at one millisecond short of a day, shown at a day.
    • Hidden at 0%, 100% and above, after the reset, and for an unreadable reset time.
  • WeeklyLimitTimer.test.ts (Date.now stubbed): one suite runs for both widgets.
    • Rendering, the short form, raw value, the 1-minute floor, the preview samples (1d 13hr, 2d 4hr), the editor display and s.
    • Hidden after the reset, at 0% and 100%, in the first day, and without a percent, a reset or any data.
    • Each timer reads only its own window.
    • Fable falls back to the all-models reset.
  • usage-prefetch.test.ts:
    • Weekly Limit Timer reads rate_limits.seven_day without fetching, and fetches only a missing reset, quietly.
    • Weekly Fable Limit Timer fetches only fableUsage when the payload has the all-models reset, quietly when that fails.
  • widgets.test.ts: the catalog lists both under Usage.
  • On current main: bun test 3736 pass, 0 fail (Bun 1.4.2). bun run lint passes. Under Node (Vitest), the five changed test files pass 131 of 131.
  • Built CLI under Bun 1.4.2 and Node 26.10.0, from the demo's seeded payloads:
    • Both runtimes print the same lines: 1d 22hr 40m, hidden, 2d 6hr, hidden, hidden, and Fable limit in: 1d 4hr 8m beside a hidden all-models timer.
    • runtime-check.sh matches main with the baseline configs. With the Fable demo config and the seeded HOME, both runtimes agree and differ from the base only by the new widgets.
    • The TUI opens and exits under both.

Two widgets that say how long until a 7-day limit at this week's average
pace, shown only when that comes before the window's reset:
`Weekly limit in: 1d 22hr 40m` for the all-models window and
`Fable limit in: 1d 4hr 8m` for Fable's.

The pace is the window's percent over the time since it started (its reset
minus 7 days), so nights and days off count; a 30-minute pace like Block
Limit Timer's would put the limit a day away during any busy hour. Both stay
hidden during the window's first day, when the limit comes after the reset,
and at 100%.

Block Limit Timer's rendering, `s` key, raw value and 1-minute floor move
into a LimitTimerWidget base that all three timers share.
@sirmalloc
sirmalloc merged commit eff0707 into sirmalloc:main Oct 30, 2026
3 checks passed
pcvelz added a commit to pcvelz/ccstatusline-usage that referenced this pull request Nov 2, 2026
Upstream: weekly limit timers (sirmalloc#768), prompt cache miss widgets (sirmalloc#770), context window size env override (sirmalloc#352, sirmalloc#407), windowed speed fixes (sirmalloc#769, sirmalloc#771), separator/renderer fixes (sirmalloc#773), custom-command styling (sirmalloc#775), clickable cwd links (sirmalloc#744), account email from live credentials (sirmalloc#724), cache timer reported expiry + short toggle (sirmalloc#576, sirmalloc#592), claude-status lock (sirmalloc#635, sirmalloc#772), dedup token metrics (sirmalloc#622, sirmalloc#725, sirmalloc#735).
Not adopted: sirmalloc#765 terminal width from COLUMNS with fixed reserve - the fork keeps flexMode/compactThreshold and tmux pane priority.

Hand edits outside conflicts:
- README.md: kept fork Configure flex separator behavior bullet and older-updates details wrapper; dropped upstream Related Projects additions (claude-eta)
- AGENTS.md: kept fork terminal width wording (sirmalloc#765 not adopted)
- src/types/FlexMode.ts, src/tui/components/TerminalWidthMenu.tsx, src/tui/components/__tests__/TerminalWidthMenu.test.ts, src/tui/components/TerminalOptionsMenu.tsx, src/tui/components/__tests__/TerminalOptionsMenu.test.ts: restored, fork keeps terminal width settings
- src/tui/App.tsx, src/tui/components/index.ts, src/types/index.ts: re-wired terminal width menu and FlexMode export
- src/types/StatusJSON.ts: dropped fork duplicate prompt_cache key, upstream schema is a superset
- src/utils/terminal.ts: COLUMNS source gated on non-tmux so tmux pane width keeps priority
- src/widgets/WeeklyLimitTimer.ts, src/utils/usage-prefetch.ts, src/widgets/__tests__/WeeklyLimitTimer.test.ts: compat fix - fableUsage/fableResetAt -> weeklyFableUsage/weeklyFableResetAt
- src/widgets/ApiUsage.tsx: compat fix - Context Bar honors effective context window (override, clamp, 1.0M format); Weekly renders a valid value despite a session error
- src/widgets/__tests__/ApiUsage.localModelFallback.test.ts: output tokens no longer counted in context (upstream sirmalloc#730)
- src/utils/__tests__/renderer-flex-width.test.ts, src/utils/__tests__/renderer-ansi.test.ts, src/utils/__tests__/gradient.test.ts, src/utils/__tests__/renderer-separator-collapse.test.ts, src/utils/__tests__/config.test.ts, src/tui/__tests__/App.test.ts: fork deviation - fork width model (flexMode, 6-column full reserve, kept settings, Terminal Width menu entry)
- src/utils/__tests__/default-colors.test.ts: fork deviation - accept ansi256:/hex: defaults
- src/utils/__tests__/usage-prefetch.test.ts: fork deviation - weekly usage comes from the API, fork widget imports from ApiUsage
- src/widgets/__tests__/CurrentWorkingDir.test.ts, src/widgets/__tests__/CustomCommand.test.ts: Settings literal gains flexMode/compactThreshold
- src/utils/__tests__/context-window.test.ts, src/utils/__tests__/jsonl-metrics.test.ts: restored fork tests upstream dropped
- docs/test-retirements.md: ledger for the dev-reload snapshot-path and RemoteControlStatus glyph (sirmalloc#605) tests upstream replaced
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.

2 participants