feat(chrome): :ratelimit becomes a real-time gauge + 60s sparkline#5
feat(chrome): :ratelimit becomes a real-time gauge + 60s sparkline#5posquit0 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
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.
| return shared.MountModal(shared.ModalIn{ | ||
| Title: "Rate limit", | ||
| Body: sized.View(), | ||
| Footer: "auto-refreshes 1s · Esc / :ratelimit to close", |
There was a problem hiding this comment.
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.
| Footer: "auto-refreshes 1s · Esc / :ratelimit to close", | |
| \t\tFooter: "auto-refreshes 1s · Esc / q to close", |
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
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}| b.WriteString(header) | ||
| b.WriteByte('\n') | ||
| b.WriteString(" ") | ||
| b.WriteString(renderGauge(pct, gaugeWidth, tk)) |
| // 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 { |
There was a problem hiding this comment.
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.
| func renderGauge(pct float64, width int, _ shared.Tokens) string { | |
| func renderGauge(pct float64, width int) string { |
| func usedPct(s domain.RateLimitSnapshot) float64 { | ||
| if s.Limit <= 0 { | ||
| return 0 | ||
| } | ||
| return float64(s.Limit-s.Remaining) / float64(s.Limit) | ||
| } |
There was a problem hiding this comment.
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}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.
e699f86 to
03bf862
Compare
Summary
Files
Test plan