Skip to content

feat(chrome): :ratelimit becomes a real-time gauge + 60s sparkline#5

Open
posquit0 wants to merge 2 commits into
mainfrom
feat/mw11-ratelimit-gauge
Open

feat(chrome): :ratelimit becomes a real-time gauge + 60s sparkline#5
posquit0 wants to merge 2 commits into
mainfrom
feat/mw11-ratelimit-gauge

Conversation

@posquit0

@posquit0 posquit0 commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

  • `:ratelimit` used to dump static text. Upgrade to a real-time gauge per Okta rate-limit category with a 60-second sparkline
  • Chrome's `[RL: N/M]` badge stays as the always-on peek; this is the drill-in view
  • Tick + subscription are Bubble Tea msgs — no global goroutines

Files

  • `internal/tui/ratelimitgauge/gauge.go` — Model (per-category ring buffer, proportional gauge bar, 60-step sparkline, empty state, height-collapse for 80x24, worst-used-first ordering)
  • `internal/app/app.go` — `OverlayRateLimit`, `rateLimitGauge` slot, palette route (`ratelimit` / `rate-limit` / `rl`), tick command with generation counter, `RateLimitObservedMsg` fan-in, modal render + Esc/q handler
  • `gauge_test.go` — empty state, ingest+render, sparkline rise, ignore Limit=0, ring decay, tight-height collapse, worst-used sort
  • `ratelimit_gauge_test.go` — palette opens overlay, populated from RateLimitPort, Esc closes

Test plan

  • `go build ./...` + `go test ./... -race -count=1` green
  • manual: press `:ratelimit`, watch the gauge tick as list refreshes

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a real-time rate-limit gauge overlay (:ratelimit) with a 60-second sparkline history per category, updating every second via a tick mechanism. Feedback focuses on improving the overlay's usability and robustness: updating the footer to accurately reflect close gestures (Esc / q), truncating the category list when the terminal height budget is too tight to prevent modal overflow, removing an unused shared.Tokens parameter from renderGauge, and clamping the calculated percentage in usedPct to ensure consistent sorting.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread internal/app/app.go Outdated
return shared.MountModal(shared.ModalIn{
Title: "Rate limit",
Body: sized.View(),
Footer: "auto-refreshes 1s · Esc / :ratelimit to close",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The footer message says Esc / :ratelimit to close, but typing : is blocked while OverlayRateLimit is active because handleRateLimitGaugeKey drops all keys other than Esc and q. Furthermore, even if the palette could be opened, the :ratelimit command only triggers opening/re-opening the overlay rather than toggling it closed.\n\nSince q is already implemented to close the overlay, the footer should be updated to accurately reflect the available gestures.

Suggested change
Footer: "auto-refreshes 1s · Esc / :ratelimit to close",
\t\tFooter: "auto-refreshes 1s · Esc / q to close",

Comment on lines +129 to +141
names := sortedCategories(m.histories)
// Height budget: each category renders as 3 lines (header,
// gauge, sparkline) + 1 blank separator. When the budget is
// tight (80x24 collapses to ~15 body rows) drop the sparkline
// row so the top-N categories still fit.
includeSpark := true
if m.height > 0 {
perRow := 3 // header + gauge + spark
budget := m.height
if len(names)*perRow+len(names)-1 > budget {
includeSpark = false
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the height budget is tight, dropping the sparkline (includeSpark = false) reduces the rows per category to 2, but if there are still too many categories, the rendered output will still exceed the height budget and overflow the modal.\n\nWe can solve this by truncating the names slice to the maximum number of categories that can actually fit within the budget (either with or without the sparkline).

\tnames := sortedCategories(m.histories)\n\t// Height budget: each category renders as 3 lines (header,\n\t// gauge, sparkline) + 1 blank separator. When the budget is\n\ttight (80x24 collapses to ~15 body rows) drop the sparkline\n\t// row so the top-N categories still fit.\n\tincludeSpark := true\n\tif m.height > 0 {\n\t\tperRow := 3 // header + gauge + spark\n\t\tbudget := m.height\n\t\tif len(names)*perRow+len(names)-1 > budget {\n\t\t	includeSpark = false\n\t\t	perRow = 2\n\t\t}\n\t\tmaxCategories := (budget + 1) / (perRow + 1)\n\t\tif maxCategories < 1 {\n\t\t	maxCategories = 1\n\t\t}\n\t\tif len(names) > maxCategories {\n\t\t	names = names[:maxCategories]\n\t\t}\n\t}

Comment thread internal/tui/ratelimitgauge/gauge.go Outdated
b.WriteString(header)
b.WriteByte('\n')
b.WriteString(" ")
b.WriteString(renderGauge(pct, gaugeWidth, tk))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The tk argument passed to renderGauge is unused because the function signature has been simplified to remove the unused shared.Tokens parameter.

Suggested change
b.WriteString(renderGauge(pct, gaugeWidth, tk))
\t\tb.WriteString(renderGauge(pct, gaugeWidth))

Comment thread internal/tui/ratelimitgauge/gauge.go Outdated
// renderGauge draws a proportional bar `[███████·······] ` for the
// supplied used% ratio. Kept token-neutral so callers wrap the box in
// their theme's Muted/Warning/Danger as appropriate.
func renderGauge(pct float64, width int, _ shared.Tokens) string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _ shared.Tokens parameter in renderGauge is unused, and the function is a private helper. We should remove this unused parameter to simplify the signature and clean up the call site.

Suggested change
func renderGauge(pct float64, width int, _ shared.Tokens) string {
func renderGauge(pct float64, width int) string {

Comment on lines +312 to +317
func usedPct(s domain.RateLimitSnapshot) float64 {
if s.Limit <= 0 {
return 0
}
return float64(s.Limit-s.Remaining) / float64(s.Limit)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In usedPct, the calculated percentage is not clamped. If s.Remaining > s.Limit (or is negative), it can return a value outside the [0, 1] range, which is inconsistent with View() where used is clamped to >= 0 and pct is clamped to [0, 1] during ingestion.\n\nWe should clamp the used value and the resulting percentage to ensure consistent sorting and rendering.

func usedPct(s domain.RateLimitSnapshot) float64 {\n\tif s.Limit <= 0 {\n\t\treturn 0\n\t}\n\tused := s.Limit - s.Remaining\n\tif used < 0 {\n\t\tused = 0\n\t}\n\tpct := float64(used) / float64(s.Limit)\n\tif pct > 1 {\n\t\tpct = 1\n\t}\n\treturn pct\n}

posquit0 added 2 commits July 5, 2026 23:53
Upgrade the :ratelimit modal from a text dump to a real-time gauge
per Okta rate-limit category with a rolling 60-second sparkline.
Watch consumption dynamically as the app makes calls.

- internal/tui/ratelimitgauge/gauge.go: Model with per-category ring
  buffer, proportional gauge bar, 60-step sparkline, empty state,
  height-budget collapse for 80x24, worst-used-first ordering
- internal/app/app.go: OverlayRateLimit + rateLimitGauge slot, palette
  route (ratelimit / rate-limit / rl), tick command with generation
  counter so ticks from closed overlay drop instead of self-reopening,
  RateLimitObservedMsg fan-in, renderRateLimitGaugeModal, Esc/q handler
- Tests: gauge unit tests + app-level palette route + Esc close
…+ signature

- Footer: `:ratelimit` is blocked while the overlay is active, so
  advertise the actually-working close gestures (`Esc` / `q`).
- gauge View: after the sparkline-collapse decision, cap the visible
  category count to `(budget+1)/(perRow+1)` so N categories × 2 rows
  can't overflow the modal body.
- renderGauge: drop the unused `shared.Tokens` parameter (and the
  unused import); the parent supplies styling via MountModal.
- usedPct: clamp `used = max(0, Limit-Remaining)` and `pct <= 1` to
  match View()'s per-sample clamp — otherwise a stale snapshot with
  Remaining > Limit sorts categories incorrectly.
- Tests: raise the collapse test's height to 8 (still exercises the
  spark drop without triggering truncation) and add a truncation
  test that renders 20 categories on a 15-row budget.
@posquit0 posquit0 force-pushed the feat/mw11-ratelimit-gauge branch from e699f86 to 03bf862 Compare July 5, 2026 14:53
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