Skip to content

feat: Add usage-status widget extension#13

Merged
rblaine95 merged 10 commits into
masterfrom
feat/usage-status
Jul 20, 2026
Merged

feat: Add usage-status widget extension#13
rblaine95 merged 10 commits into
masterfrom
feat/usage-status

Conversation

@rblaine95

@rblaine95 rblaine95 commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a new usage-status extension that renders remaining subscription usage with reset countdowns in a color-coded row above the editor, next to the status line, so the numbers from /usage are always visible.

Data comes from ctx.modelRegistry.authStorage.fetchUsageReports(...) — the same normalized, cached path /usage and the built-in footer use. Unlike the built-in usage segment (active provider only), this is fully data-driven: every provider and account the reports return is rendered, so users without Claude or Codex (Gemini, Grok, OpenCode, Cursor, …) still see their quota.

Claude:alice 5h 50% (1h30m) │ Claude:bob 5h 12% (10m) │ Grok 5h 88% (42m) │ OpenCode 5h 60% (2h) · 7d 15% (4d) · 30d 95% (20d)

Design

  • aboveEditor component widget (ui.setWidget), not ui.setStatussetStatus feeds the hook-status block, which renders apart from the main line with its own spacing. A component row sits flush above the editor and carries theme colors.
  • Dynamic windows: tokens derive from durationMs when present, falling back to windowId/label/word-forms (rolling-5h5h, weekly7d, monthly30d).
  • Multi-account safe: iterates per report, so two accounts on one provider are never merged; an account label (Claude:alice) is added only when a provider has >1 account.
  • Color by remaining availability: success (>50%), warning (21–50%), error (≤20%); labels accent, countdowns dim.
  • Refresh: network fetch cached 5 min; a per-minute timer redraws the countdown locally from cached reset timestamps. Idle in headless (ctx.hasUI false); widget cleared on shutdown.

Also bumps @oh-my-pi/pi-coding-agent to ^17.0.5 (and Biome ^2.5.4) and registers the plugin in the root manifest, README/AGENTS tables, and the marketplace catalog.

Verification

  • bun typecheck clean, bun check clean, bun test → 81 pass / 0 fail (formatter, provider labels, window tokens, dynamic providers, multi-account, widget wiring, headless).
  • End-to-end smoke: session_start installs the widget, live redraw fires after fetch, colored row renders, width fallback drops countdowns then hides, shutdown clears.

Known limitation

When the row cannot fit the terminal width even without countdowns, it hides entirely rather than degrading per-segment (dropping the least-urgent provider first). Does not trigger for typical 1–3 subscriptions on a normal-width terminal.

Summary by CodeRabbit

  • New Features

    • Added the usage-status extension, showing color-coded provider/window quota usage above the editor.
    • Includes dynamic reset countdowns, automatic refresh, multi-account/provider labeling, and width-aware rendering with deduped window rows.
  • Tests

    • Added a Bun test suite validating formatting, ordering, unknown provider/window handling, widget lifecycle in UI vs headless mode, refresh/switch/shutdown behavior, and resilience to malformed payloads.
  • Documentation

    • Updated extension documentation and marketplace metadata to list usage-status.
  • Chores

    • Bumped dev tooling, updated Biome schema, and registered the new extension in the workspace.

Add a data-driven `usage-status` extension that renders remaining subscription usage with reset countdowns in a color-coded row above the editor, sourced from `authStorage.fetchUsageReports` (the same data as `/usage`). It covers every provider and account the reports return, not a fixed list, so users without Claude or Codex still see their quota.

- Render as an `aboveEditor` component widget so the row sits flush with the status line, unlike the `setStatus` hook block
- Derive window tokens from `durationMs` with `windowId`/label fallbacks (`5h`, `7d`, `30d`)
- Color remaining availability green/yellow/red and keep each account separate
- Cache network fetches for five minutes and redraw the countdown each minute locally

Register the extension in the root manifest and marketplace catalog, and bump `@oh-my-pi/pi-coding-agent` to `^17.0.5`.

Glory to the Omnissiah
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rblaine95, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9db2e481-4da2-4cd0-8e6b-7ed90f81a907

📥 Commits

Reviewing files that changed from the base of the PR and between 1016504 and 31f9e55.

📒 Files selected for processing (4)
  • .omp-plugin/marketplace.json
  • extensions/usage-status/index.test.ts
  • extensions/usage-status/index.ts
  • extensions/usage-status/package.json
📝 Walkthrough

Walkthrough

Adds the usage-status extension, which fetches usage reports, formats color-coded quota rows, and renders them above the editor. It also adds lifecycle tests, registration, packaging, marketplace metadata, documentation, and tooling updates.

Changes

Usage status extension

Layer / File(s) Summary
Usage report formatting
extensions/usage-status/index.ts, extensions/usage-status/index.test.ts
Defines report shapes and formatting helpers for providers, windows, remaining percentages, reset countdowns, colors, sorting, deduplication, and account labels, with unit coverage.
Widget rendering and lifecycle
extensions/usage-status/index.ts, extensions/usage-status/index.test.ts
Adds width-aware widget rendering, cached report fetching, periodic updates, session lifecycle handling, cleanup, malformed-payload handling, stale-fetch protection, and UI/headless integration tests.
Extension registration and publication
extensions/usage-status/package.json, package.json, .omp-plugin/marketplace.json, AGENTS.md, README.md, biome.json
Registers and packages usage-status, adds marketplace metadata and documentation, and updates tooling schema and development dependencies.

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

Sequence Diagram(s)

sequenceDiagram
  participant SessionLifecycle
  participant usageStatus
  participant authStorage.fetchUsageReports
  participant UsageRow
  SessionLifecycle->>usageStatus: session_start
  usageStatus->>authStorage.fetchUsageReports: fetch usage reports
  authStorage.fetchUsageReports-->>usageStatus: return reports
  usageStatus->>UsageRow: render usage row
  SessionLifecycle->>usageStatus: session_shutdown
  usageStatus->>UsageRow: clear widget
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding the new usage-status widget extension.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

coderabbitai[bot]

This comment was marked as resolved.

CI enforces `coverageThreshold = 1.0`, but the suite only exercised the pure formatter, leaving the widget lifecycle uncovered.

Bun compounds this: an unexecuted function blanket-marks its whole source range uncovered, so the four unhit runtime functions were clobbering line hits across the file (`oven-sh/bun#33959`), reporting ~8% lines despite passing tests.

Add lifecycle tests that exercise every function: the widget factory and `render` width tiers, `baseUrlResolver`, the interval `tick` (via fake timers), and the `session_switch` handler. Coverage is now 100% functions and 100% lines.

Glory to the Omnissiah
Network data from `fetchUsageReports` was cast straight to `UsageReportLike[]` without validation, so a malformed report or limit (missing `amount`/`scope`, or a non-object entry) could reach `UsageRow.render()` and throw in the TUI hot path.

Sanitize at the fetch boundary: `sanitizeUsageReports` keeps only report objects with a string provider and array limits, and `isUsageLimit` drops malformed limits, so only well-formed data reaches the renderer. `remainingPercent` also guards a missing `amount` as defense in depth.

Add tests for the malformed and null fetch payloads and a focused `session_switch` check that stale reports are cleared and fresh usage is rendered after a switch. Coverage stays at 100%.

Glory to the Omnissiah
The `session_switch` handler manually reset part of the state and re-ran `start`, but never routed through `stop()`, so the previous widget was replaced without an explicit teardown and the stale `tui` reference lingered until the host re-invoked the factory.

Route the switch through `stop(state)` first, so the old widget is cleared and the component/`tui` references are released before `start` reinstalls. The test now asserts the switch emits a `setWidget(undefined)` teardown before the fresh install and renders the newly fetched usage.

By the will of the Machine God
The limit guard only checked that `amount` and `scope` were objects, so a limit with a wrong-typed nested field — e.g. a numeric `scope.windowId` — passed sanitization and then crashed `windowToken` at `.toLowerCase()`, or produced `NaN%` from a non-numeric fraction.

Validate the optional nested fields before accepting a limit: `scope.windowId`/`tier` and `window.id`/`label` must be strings when present, and `amount.remainingFraction`/`usedFraction`, `window.durationMs`/`resetsAt` must be finite numbers. Extend the fetch-path resilience test with these nested-malformation cases.

By the will of the Machine God
* `rblaine96` -> `rblaine95`
A `fetchUsageReports` request still in flight when a session switch happened could resolve afterward and write into the new session, and `stop()` left `inFlight` set so the new session skipped its own fetch. A `ctx`-identity check would not help — omp reuses one `ExtensionContext` across sessions.

Add a generation token: capture it before the await and commit `reports`/`fetchedAt`, clear `inFlight`, and redraw only when it still matches. `stop()` advances the generation and resets `inFlight`, so a switch invalidates the pending request and the next session fetches immediately. Add a regression test with a deferred fetch resolved after the switch.

Ave Deus Mechanicus
@rblaine95
rblaine95 force-pushed the feat/usage-status branch from db69697 to 1016504 Compare July 20, 2026 13:59

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.omp-plugin/marketplace.json (1)

24-24: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix awkward wording in the marketplace description.

"for every provider /usage reports" reads as a typo; the stray /usage breaks the sentence for a user-facing marketplace listing.

✏️ Suggested wording
-      "description": "Show remaining subscription usage with reset countdowns for every provider /usage reports, in a color-coded row above the omp editor.",
+      "description": "Show remaining subscription usage with reset countdowns for every provider's usage reports, in a color-coded row above the omp editor.",
🤖 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 @.omp-plugin/marketplace.json at line 24, Update the marketplace description
value in marketplace.json to remove the stray “/usage” fragment and use
grammatically clear wording while preserving the intended meaning about
remaining subscription usage, reset countdowns, and every provider.
🤖 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.

Outside diff comments:
In @.omp-plugin/marketplace.json:
- Line 24: Update the marketplace description value in marketplace.json to
remove the stray “/usage” fragment and use grammatically clear wording while
preserving the intended meaning about remaining subscription usage, reset
countdowns, and every provider.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7b66bb83-9720-45e6-8fd9-be30aa9a09e6

📥 Commits

Reviewing files that changed from the base of the PR and between 7a0b118 and 1016504.

📒 Files selected for processing (3)
  • .omp-plugin/marketplace.json
  • extensions/usage-status/index.test.ts
  • extensions/usage-status/index.ts

The `usage-status` description read "for every provider /usage reports", a stray fragment that scanned as broken grammar. Reword to "remaining subscription usage and reset countdowns for every provider" in both the marketplace catalog and the member `package.json` so they stay identical.

Ave Deus Mechanicus
Account counts were tallied from every valid report, but a report whose windows all resolve to no remaining %% is dropped from the rendered row. So a provider with two accounts where only one is renderable still counted as two, mislabeling the lone survivor `Claude:bob` instead of `Claude`.

Build the rendered list first, then count providers from it, so the account label is added only when more than one account actually appears. Add a regression test for the single-renderable-account case.

Glory to the Omnissiah
@rblaine95
rblaine95 merged commit 0aaf691 into master Jul 20, 2026
2 checks passed
@rblaine95
rblaine95 deleted the feat/usage-status branch July 20, 2026 14:49
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