You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Free-plan ad-hoc credit purchases at a 10% premium (desktop UI) [single-commit mirror of #14453] (#14584)
> **Single-commit mirror of #14453.** This PR carries the entire net
content of #14453 (head
`jlloyd/free-adhoc-credit-purchases-desktop` @ `7462b207`) as exactly
**one commit** squashed onto current `master`. No new authorship: the
source PR owns the change and its behavioural verification. The only
content delta vs. the source branch is master's concurrent edit to
`app/src/ai/request_usage_model_tests.rs` (adds
`MockTelemetryContextProvider` registration), which the squash-merge
picked up automatically — see *Mirror fidelity* below.
---
## Description
Desktop UI for free-plan ad-hoc credit purchases at a 10% premium
(Pricing Transparency M5, pulled forward). Server side:
warpdotdev/warp-server#13446. Plan:
https://staging.warp.dev/drive/notebook/uxcmHeaDP7Dd2RwDKFL6kd
**What**: Free-plan (premium-policy) users — including fresh users with
**no team yet**, the primary persona — get the full add-on credits
purchase experience: billing pages (v1 + v2), out-of-credits banner,
prompt alert, at premium-adjusted prices, including auto-reload. Per
copy review, premium pricing is presented as one final price with a
single savings-framed upsell ("Save 10% on add-on credits by upgrading
to a Build plan."); no surcharge language or +X% badges anywhere. Branch
is rebased onto master `02b53fcd8` (post-#14393 window-scoped team
APIs).
**How**:
- GraphQL crate: `PurchaseAddOnCreditsPolicy` gains
`premiumEnabled`/`pricePremiumBps`; the `PurchaseAddonCreditsResult`
union gains a dedicated `PurchaseAddonCreditsCheckoutOutput` member
(warpdotdev/warp-server#13618); vendored schema updated to match the
server SDL.
- Gating is centralized on the policy fields (`enabled ||
premiumEnabled` to show purchase surfaces; premium treatment iff
`!enabled && premiumEnabled`). There is no client-side flag: while the
server rollout flag is off it masks the policy, so this UI stays dormant
and free plans keep today's upgrade-only nudge.
- Display prices use `price_usd_cents_with_premium` — byte-identical
integer ceiling math to the server — everywhere prices surface: pack
rows, banner dropdown, auto-reload labels/tooltips, post-purchase modal,
and all monthly-spend-limit checks
(`Workspace::get_auto_reload_price_cents` is premium-adjusted
centrally).
- Purchases without a saved payment method return the checkout union
member: the initiating surface opens the browser (single-opener via each
surface's loading flag), shows a pending state, and credits land via
existing polling. The banner's auto-reload opt-in persists only after
purchased credits are observed landing, so an abandoned checkout never
silently enables auto-reload.
- Teamless support: `UserWorkspaces::purchase_policy_for_team` resolves
the governing policy with a strict three-leg chain — team's → current
workspace's → **user-level** `billingMetadata` (a slim, dedicated
selection carrying only the purchase policy; the teamless fallback
exists because a fresh free user's only workspace is the REV-717
placeholder, which the client rightly filters out — no placeholder
mining). The stored user-level policy refreshes on all three metadata
apply paths so it can't go stale. `teamUid` is `Option` end-to-end
(omitted → the server auto-creates the personal team), teamless users
are permitted for their own purchase, and team-settings affordances
(spend-limit row, auto-reload switch) stay hidden until the
server-created team lands.
- Paid-tier rendering is unchanged (0 bps degenerates every new code
path to existing values).
**Why**: Free users' only option today is subscribing; the 10% premium
is the subscription incentive, framed in-product as savings for
upgrading. See the PRODUCT/TECH specs in the server PR.
## Linked Issue
No GitHub issue; part of the Pricing Transparency effort tracked in the
plan/spec linked above.
- [ ] The linked issue is labeled `ready-to-spec` or
`ready-to-implement`.
- [ ] Where appropriate, screenshots or a short video of the
implementation are included below (especially for user-visible or UI
changes).
## Testing
Automated:
- `crates/graphql` unit tests for the premium price math (exact cents at
1000 bps, round-up edges, negative-bps guard).
- `workspace_tests` for policy gating (premium enables purchasing;
standard plans 0 bps; both-set falls back to standard).
- `request_usage_model_tests` for banner display-state gating (with
explicit teamless assertions), premium auto-reload counting as AI
remaining, and a reload blocked at the premium price but not list price.
- `user_workspaces_tests` + `update_manager_tests`: regression coverage
through the REAL conversion/apply paths — a `GqlUser` fixture whose only
workspace is the placeholder resolves the user-level premium policy
while `workspaces` stays empty; workspace and team legs each win over
the user leg; the poll-path apply stores and clears the fallback; plus
`Option` teamUid forwarding to a mocked `WorkspaceClient`.
- `cargo check` / `cargo clippy --all-targets --tests -- -D warnings` /
`./script/format` clean on touched crates; targeted `cargo nextest`
48/48.
Manual (against a local warp-server on the server feature branch,
rollout flag on):
1. Free user → Settings > Billing & Usage (both page variants): packs at
$11/$22/$55/$110, the "Save 10% on add-on credits by upgrading to a
Build plan." note with a working upgrade link, and the auto-reload
switch present with its standard copy (no premium annotations).
1a. Fresh free user with NO team: same panel appears (minus spend-limit
row and auto-reload switch); buying with no card opens browser checkout,
and after the webhook grant the personal team appears and the
team-settings rows materialize.
2. Buy with no saved card → browser opens Stripe Checkout, pending state
shown, credits land via polling; second purchase charges synchronously.
3. Out-of-credits banner: premium prices in dropdown, checkout handoff
state, auto-reload checkbox opt-in applied after credits land.
4. Regression: Build-plan account renders identically to master; flag
off → free plan shows the old upgrade nudge.
- [ ] I have manually tested my changes locally with `./script/run`
(Manual QA pass on staging pending — tracked before marking ready for
review.)
## Agent Mode
- [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode
Conversation:
https://staging.warp.dev/conversation/a516b043-08e8-46c3-9ae5-9b903e94458b
CHANGELOG-NONE
Co-Authored-By: Warp <agent@warp.dev>
Co-Authored-By: Oz <oz-agent@warp.dev>
> **Deploy order**: depends on warpdotdev/warp-server#13618 being
deployed before this ships, since the client selects the new
`PurchaseAddonCreditsCheckoutOutput` union member.
---
## Mirror fidelity (factory verification)
- Exactly one commit on top of `origin/master`: `git rev-list --count
origin/master..HEAD` → `1`.
- Net change is identical in shape to the source PR: **22 files changed,
+1599 / -211** (same as #14453).
- Content proof: `git diff
jlloyd/free-adhoc-credit-purchases-desktop..HEAD -- <the 22 PR files>`
is a **20-line diff confined to
`app/src/ai/request_usage_model_tests.rs`**, consisting solely of
master's newer `MockTelemetryContextProvider` test-harness registration
that landed after the source branch's merge-base. Every other one of the
22 files is byte-identical to the source branch. The remainder of a raw
two-dot diff against the source branch is master advancement (21
commits, 127 files) inherent to re-basing the content onto current
`master`.
- No tests were added and no code was edited beyond that automatic merge
resolution (mirror of already-authored work).
- UI/computer-use proof is intentionally **not** attached: this PR
authors no new user-facing behaviour — it reproduces #14453's content
verbatim, and #14453 owns the behavioural/visual verification for the
feature.
- Source PR #14453, its branch, and its review conversation were left
untouched. Its comment threads were deliberately **not** carried over
(only the code and the description are mirrored).
Originating thread:
https://warpdotdev.slack.com/archives/D0BGF2WSJBB/p1785503011978259
Linear ticket:
https://linear.app/warpdotdev/issue/REV-1879/single-commit-mirror-pr-of-warp14453-free-plan-ad-hoc-credit-purchases
<!-- factory-agent:
{"source":"factory-agent","task_id":"REV-1879","task_source":"linear","task_url":"https://linear.app/warpdotdev/issue/REV-1879/single-commit-mirror-pr-of-warp14453-free-plan-ad-hoc-credit-purchases","oz_run_id":"019fb84a-d9be-7344-8367-1ba3d235578c","repo":"warpdotdev/warp"}
-->
Co-authored-by: Oz <oz-agent@warp.dev>
Co-authored-by: Warp <agent@warp.dev>
0 commit comments