TUI: implement /usage slash command from Figma designs - #14968
TUI: implement /usage slash command from Figma designs#14968warp-agent-staging[bot] wants to merge 2 commits into
Conversation
Adds a read-only /usage panel to the TUI, matching the Figma design's three states: base credits and add-on credits as percentage bars, and pay-as-you-go spend as wrapping circles (500 credits each). - Register /usage as GuiAndTui so the TUI's slash-command menu picks it up; the existing GUI behavior (open the Billing and Usage settings pane) is unchanged. - Compute a TuiUsageSnapshot from data the client already has locally: AIRequestUsageModel for base credits, the current workspace's non-ambient bonus grants for add-on credits, and the current workspace's BillingCycleUsageData for pay-as-you-go spend this cycle (no server changes needed). - Widen the app crate's tui_export so AIRequestUsageModel is available to the TUI unconditionally (it was previously gated behind the voice_input feature, even though the model itself is always registered). - Render the panel as a bespoke element tree (not the shared TuiReadOnlyMenu row model) so it can host real clickable links (Manage billing and usage; Buy more credits or upgrade plan) plus custom bar/circle visualizations. Sections the account doesn't have are omitted entirely, per the design's spec note. - Add render-to-lines tests locking in the bar-fill percentages (0%, 20%, 100%) and the pay-as-you-go wrapping behavior across rows. Co-Authored-By: Warp Agent <agent@warp.dev>
There was a problem hiding this comment.
Overview
Adds a TUI-native /usage panel built from the Figma designs and wired to real credit data, leaving GUI /usage unchanged. Two questions below need the designer's decision before merge; they are not defects, they are places where the design and the product's actual data model disagree.
Concerns
- Five important implementation findings from review (add-on grant scoping, pay-as-you-go team scoping and redacted-entry handling, a 25-row clamp that contradicts unbounded circle wrapping, a three-line header where the design specifies one row, and an inert
ctrl+obinding) are being corrected in follow-up commits and are not repeated here. This review is only the two items that need a human call.
Verdict
Checks: build pass, tests pass (1001/1002 warp_tui, the one failure pre-existing and caused by WARP_API_KEY in the sandbox), CI not run (draft), visual proof present (live authenticated TUI capture)
Found: 0 critical, 0 important, 0 suggestions, 0 nits, 2 questions
| used: (granted - remaining).max(0), | ||
| limit: granted, | ||
| note: match auto_reload_denomination { | ||
| Some(credits) => format!("Auto-reload {credits} credits when balance is low"), |
There was a problem hiding this comment.
question — The design copy reads Auto-reload 500 credits July 31 at 5:00pm, but auto-reload is threshold-triggered server-side rather than scheduled, so no such date exists in the data model. This implementation says Auto-reload {credits} credits when balance is low, which is accurate but is not the designed copy, and the only way to match the design literally would be to fabricate a timestamp. Please confirm this wording, or supply replacement copy.
| /// circle row. Fixed rather than reactive to the live terminal width, | ||
| /// matching the rest of this read-only-menu family (see | ||
| /// `status_menu`/`shortcuts`, which format rows to a fixed label width too). | ||
| const BAR_WIDTH: usize = 60; |
There was a problem hiding this comment.
question — This 60-cell row width is not just a responsiveness tradeoff: at 500 credits per circle, the design's own wrapping example of 60,000 credits yields 120 circles, which fills exactly two 60-cell rows, whereas the design frame shows a full first row and a roughly 40%-filled second row. The 80x24 frame and the stated circle arithmetic do not pin down a compatible row width, so this needs your call on the intended width (or on the credits-per-circle value). Whatever width is chosen, the separate row-clamping bug is being fixed regardless.
…ctrl+o - Scope add-on credit grants to the current workspace (or user-level grants) instead of summing across every workspace the user belongs to, which could leak another team's balance into this account's total and show the section for a team that has none. - Scope pay-as-you-go spend to the current team's attributed entries, and omit the section entirely below FullBreakdown/OwnOnly usage visibility, since TeamAggregate/PerUserTotals collapse real spend into synthetic Aggregate rows that would otherwise render as a silently wrong "Spend: 0". - Remove the /usage panel's row cap in render_input_area so it can grow past the shared read-only-menu limit to fit wrapped pay-as-you-go circles, matching the design's overflow requirement. - Render the panel header (title, plan/team metadata, manage-billing link) as a single row with a flexible spacer instead of three stacked lines, matching the 80x24 Figma layout. - Enable the existing ctrl-o out-of-credits binding while the /usage panel is open, so the "(ctrl+o)" hint next to "Buy more credits or upgrade plan" actually opens the upgrade page. - Register AIRequestUsageModel unconditionally in TUI test fixtures (it was only wired up under the voice_input feature, an export gap separate from this feature's own voice_input-agnostic design) and add a set_bonus_grants_for_test helper plus a set_tui_usage_test_data seeding helper for TUI-side tests. - Add data-layer tests for the workspace/team scoping and visibility fixes, render-layer tests for the single-row header and the no-artificial-cap wrapping behavior, and an integration test for ctrl-o dispatch while the panel is open. Co-Authored-By: Warp Agent <agent@warp.dev>
|
Revision pushed in
Also fixed a latent gap found while writing tests: the TUI test fixture registered New tests were added at the data layer rather than only the renderer, since renderer-only coverage is precisely why findings 1, 2 and 5 passed a green suite: four data-layer tests for grant scope and pay-as-you-go attribution/visibility, four render tests for the header and for wrapping past 24 rows, and one integration test that opens
The two review questions above — the auto-reload copy and the circle row width — are untouched and still need the designer's call. This stays in draft until then. |
Description
Implements a read-only
/usagepanel for the TUI, matching the three Figma states (APP-5316): base credits and add-on credits as percentage bars, and pay-as-you-go spend as circles that wrap onto additional rows as needed (500 credits per circle)./usagealready existed as a GUI-only command that opens the Billing and Usage settings pane — that behavior is unchanged. This PR adds a TUI-only surface (SlashCommandKind::Usageis nowGuiAndTui) that renders the panel inline above the composer, mirroring the/statusmenu's slash-command lifecycle but with its own bespoke element tree (not the sharedTuiReadOnlyMenurow model) so it can host real clickable links and custom bar/circle visuals.Per the spec note, a section is omitted entirely when it doesn't apply to the account (e.g. no add-on credits purchased → no Add-on credits section; no admin permissions on any team → no "Manage billing and usage" link). Confirmed live below.
Data wiring — all real, no server changes:
AIRequestUsageModel(limit/used/next-refresh), already used by the GUI.grantedvsremaining), the same figures the GUI's Balance cards use.BillingCycleUsageData(already synced passively as part of routine workspace refresh — no extra fetch), aggregated overPayg-cost-type entries for the current billing cycle. Bar/circle fill is a strictused ÷ limit— confirmed with the designer, no special-casing.admin_billing_link_for_default_teamhelper the existing/manage-billingTUI command uses.ctrl+o: reuses the existingupgrade_url()helper already used elsewhere in the TUI.One export gap found along the way and fixed:
AIRequestUsageModelwas only re-exported to the TUI crate behind thevoice_inputfeature flag (a historical accident from when only voice-request limits needed it), even though the model itself is always registered. Widened that export so base credits work regardless of thevoice_inputfeature.Known limitation (flagged, not silently worked around): the design's "Auto-reload 500 credits July 31 at 5:00pm" copy implies a scheduled reload date, but add-on credit auto-reload is threshold-triggered server-side ("when balance reaches 100 credits remaining"), not date-based — there's no such date in the client's data model. This PR shows real auto-reload state (enabled + denomination) without inventing a timestamp: "Auto-reload 500 credits when balance is low". Flagging for design to confirm whether that's acceptable copy.
Also flagging: the row width for the credit bars / pay-as-you-go circles is a fixed 60 cells (padded to a
label+valueformat at a fixed label column), not reactive to the live terminal width — this matches the existing read-only-menu family's convention (/status,?shortcuts) of fixed-width rows that truncate rather than reflow, since there's no width-reactive layout plumbed into that shared component today. In a narrow terminal, the longest text row (Spend: ... / Kicks in after ...) may clip.Linked Issue
APP-5316: https://linear.app/warpdotdev/issue/APP-5316/tui-implement-usage-slash-command-from-figma-designs-3-states
Testing
cargo build -p warp_tui --bin warp-tui-oss(default features) and with--features voice_input: both build clean, no warnings.cargo clippy -p warp_tui --bin warp-tui-oss --tests --no-depsandcargo clippy -p warp --bin warp-oss --no-deps: clean.cargo nextest run -p warp_tui: 1001/1002 pass. The one failure (session::tests::accepts_startup_without_resume) is pre-existing and environment-specific — this sandbox hasWARP_API_KEYset, which that unrelated CLI-arg-parsing test doesn't account for; confirmed it isn't caused by this change.cargo nextest run -p warp --lib -E 'test(request_usage_model) or test(commands_tests) or test(slash_command)' --features tui: 175/175 pass.crates/warp_tui/src/terminal_session_view/usage_menu_tests.rs: render-to-lines tests locking in the bar-fill math (0%, 20%, 100% — the 100/500 = 20% case) and the pay-as-you-go circle-wrapping behavior (0 credits → dashed row; a handful of circles → one row; enough spend to overflow a row → wraps onto exactly as many rows as needed), plus conditional section rendering and the "kicks in" copy swap.localchannel (cargo build -p warp_tui --bin warp-tui) and ran it authenticated in a real account (kevin@warp.dev, Free plan), typed/usage, and captured the resulting panel — screenshot and screen recording attached below. This account only has base credits (no add-on credits, no pay-as-you-go), so the live capture exercises the "only-base-credits" branch of the conditional-section logic for real: Add-on credits, Pay-as-you-go, and the "Manage billing and usage" link (no admin permissions) are all correctly omitted, base credits render as150 remaining/0/150used / an all-empty bar, matching the account's real, unused Free-tier quota. The other two designed states (add-on credits + pay-as-you-go active, and pay-as-you-go wrapping) are covered by the render-to-lines tests above rather than faked against this account's real data.Screenshots / Videos
Live authenticated TUI,
/usagepanel (real account data, Free plan / base-credits-only state):Agent Mode
Computer-use video recordings
View video recording - Screen recording: typing /usage in the live authenticated TUI and the panel appearing
Conversation: https://staging.warp.dev/conversation/5a8d1272-b55c-4963-9139-d0697f734304
Run: https://oz.staging.warp.dev/runs/019ff2b0-9f44-72be-8937-24ccfd1ffa0d
This PR was generated with Oz.