Skip to content

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

Closed
eric-engberg wants to merge 1 commit into
developfrom
feat/weekly-limit-timer
Closed

eric-engberg wants to merge 1 commit into
developfrom
feat/weekly-limit-timer

Conversation

@eric-engberg

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

Copy link
Copy Markdown
Owner

Standalone, cut from main (upstream-ready; #72 and #89 are upstream as sirmalloc#699 and sirmalloc#747).

Relates to sirmalloc#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.

@eric-engberg eric-engberg changed the title feat(usage): add a Weekly Limit Timer widget feat(usage): add Weekly Limit Timer and Weekly Fable Limit Timer widgets Oct 9, 2026
@eric-engberg
eric-engberg changed the base branch from feat/session-forecast to develop October 30, 2026 07:14
eric-engberg added a commit that referenced this pull request Oct 30, 2026
…mer's prefetch calls

#115 passes the statusline payload's 5-hour and weekly reset ends to
fetchUsageData, so #80's two prefetch tests for Weekly Fable Limit Timer,
which check the exact fetchUsageData calls, expect that option too.
eric-engberg added a commit that referenced this pull request Oct 30, 2026
…build

The branch was rebuilt on today's updated #115 and #80, which hold upstream
main; #71 and #89, which it also merged, are upstream (sirmalloc#697, sirmalloc#747) and in
#115 through main. This keeps the old history so the push is a
fast-forward; the tree is the rebuild's.
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.
@eric-engberg
eric-engberg force-pushed the feat/weekly-limit-timer branch from dce4d10 to 44323f3 Compare October 30, 2026 21:11
@eric-engberg
eric-engberg marked this pull request as ready for review October 30, 2026 21:11
@sonarqubecloud

Copy link
Copy Markdown

@eric-engberg

Copy link
Copy Markdown
Owner Author

Merged upstream as sirmalloc#768.

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