Repository navigation
Show usage, limits and plans consistently (T6: QB-03, QB-06, QB-16, VS-06, QB-09, VS-08, IN-07, QB-11, QB-12, ST-03) - #217
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…for legacy free; Spanish card shots Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ℹ️ Recent review info
📝 Walkthrough
Note 🎁 Summarized by CodeRabbit Free
Comment |
… dialog, and tidy limit copy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l, offer legacy free Pro at the cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Batch T6 from the 22 Oct 2026 QA report covers how usage, limits and pricing are shown. It builds on T4 (#207, billing states and refusal codes) and T5 (#201, per-session quota). It changes the front end only, with no migrations and no Edge Function changes.
What changed, per issue
QB-03 (usage on tool pages, wrong count in Settings). Settings and the header of every AI tool (Generate, Attack, Brainstorm, Factoids, Ask, Crossfire, Sparring) now show "N of L AI uses left today" from one component,
DailyUsage. The number is the server's weighted counter (daily_usage_countersthroughai_usage_today()), so a Sparring round or a Crossfire session counts once and Opus counts as 5. The fallback that countedai_requestsrows is gone: if the counter cannot be read, the line is hidden rather than guessed. After every AI call the transport re-reads usage and the session-quota check (refreshAIUsageinsrc/lib/query-client.ts), so the count moves without a reload. Production already has the migration that addsai_usage_today(npm run backend:status, read-only: 59 of 59 applied).QB-06 (limit message differs by mode; raw server text). All seven tools now mount the same
SubscriptionErrorModals. Crossfire and Sparring went through it before but it dropped the request's weight, so they said "50 of 50 used" where the other tools said "this model counts as 5 uses". It now passesunits. A refusal never shows server text: a 402 without a code, which used to show "Edge Function returned a non-2xx status code", gets a localized generic line, and a zero limit gets its own sentence instead of "0 of 0". Sparring's separate "no AI requests left" and "this round costs N" warnings now use the same catalog sentences as the dialog. The Pro reasoning or model refusal (403upgrade_required) is localized too. Generate's section regenerate and the Crossfire debrief now open the limit dialog instead of a toast or a bare error. General server error localization stays with T12 (IN-11).QB-16 (pricing dialog contradicts account state). One function,
billingActioninsrc/lib/billing/plan.ts, picks the next step for Settings and the pricing dialog alike. Active Pro, Pro set to cancel and a failed payment get "Manage Subscription" (the billing portal, never a second Checkout), with a subtitle for cancelling and past-due accounts. Promo Pro without Stripe gets "Set Up Billing", a former subscriber gets "Resubscribe", and everyone else gets "Upgrade to Pro". The Current badge marks the plan the account is actually served on: Trial for a running trial; Pro for Pro, a payment grace and legacy free; neither for extended access or an ended plan. The old "Free Trial" badge on the Pro card and the always-on "Current" on the free card are gone. The title reads "Your plan" for payers, and the promo-code field is hidden while the portal is the next step.VS-06 (HU subscription row squeezed at 360 px). The badge, date and button row in the Settings card wraps, so the Hungarian button drops below the badge instead of squeezing it.
QB-09 (no trial banner). A slim line under the header, "Free trial: 2 days left.", shows during a trial, with "Upgrade to Pro" and a 44 px dismiss button. Dismissal is remembered per user and trial end date in local storage.
VS-08 (upgrade dialog full-screen on phones, tiny close). The pricing, limit and Pro reasoning dialogs sit 1 rem in from the screen edges with rounded corners and scroll inside when taller than the screen. Their close button is a visible 44×44 px target (
closeClassName, a new optional prop onDialogContent; other dialogs are unchanged).IN-07 (HU limit sentence, two button names, hardcoded price). The Hungarian daily-limit sentence is rewritten. The upgrade button reads "Upgrade to Pro" / "Mejorar a Pro" / "Váltás Pro-ra" everywhere: Settings, pricing dialog, limit dialog, Pro reasoning dialog, trial banner. The old "Upgrade Now" and "Upgrade to Pro - $5/mo" are gone. The price comes from
src/config/site.json(pricing.proMonthly,currency,trialDays), formatted per locale: "$5" in English, "5 $" in Spanish and Hungarian. No tax wording was added; the existing "/mo + tax" suffix is unchanged.QB-11 ("10 models" vs 12 in the picker). The number is dropped from the pricing dialog: "every model, including Claude Opus and Sonnet, GPT-6.1 Sol and Gemini".
QB-12 (/app/billing is a 404).
/app/billingredirects to/app/settings#subscription, and Settings scrolls the subscription card into view.ST-03 (email wraps mid-word; long "Loading…"). The signed-in email is truncated with the full address in its tooltip, and the danger-zone sentence breaks long emails only where needed. The subscription card shows a skeleton while it loads, with a screen-reader status.
Also fixed in passing, two Hungarian sentences printed a doubled period after a date ("2026. 11. 04.."). They were the payment grace line and the new cancelling subtitle.
Left alone on purpose, the landing and SEO copy (
hero.proBadge,pricing.*,faq.a1,termsPage.p4,public/debatelab-pricing) still hardcode "$5" and "10 models". That copy belongs to T14 (#209, now merged) and the landing owners.Verification
Full
npm run verifyon the current head (32e10d11, based onmainata7610fa5), Node 22, exit 0.mainhas since gained #218, which only touches migrations and shares no files with this PR, so I pushed the review fixes on top instead of rebasing and force-pushing. This is a local run. GitHub Actions minutes are used up, so there is no CI run.e2e/free-tools.spec.ts, which passed 13/13 when run alone.Unit tests added or changed:
src/test/billing/pricing-modal-redirect.test.tsx: the pricing dialog's single button and Current badge for nine states, and that Manage goes to the portal and never to Checkout.src/lib/ai/limit-message.test.ts: the same refusal produces the same text through the one-shot path (mapAIError), the pre-start quota check (sessionQuotaError) and the dialog, for six refusal codes. No server text appears without a code, and a zero limit gets its own sentence. This replaces the old "keeps the server text when the refusal carries no code" test, which pinned the QB-06 bug.src/test/components/LimitReachedModal.test.tsx:SubscriptionErrorModalspasses the weight through (I confirmed this test fails with the fix reverted), and a legacy free account at the daily cap is offered Pro rather than told it already pays.src/hooks/use-subscription.test.tsx: an unreadable counter leaves the count unknown instead of counting rows.Browser proof with the
verify-debatelabskill (fake provider, local stack):recipes/billing-states.spec.ts, rewritten, runs six states: trial, trial ended with an override (extended access), active Pro, Pro set to cancel, past due in the grace, subscription ended. For each state, in EN and HU, it checks Settings (badge, sentence, button, usage line, trial banner), the pricing dialog (one button, Current badge, price in the reader's format, 44 px close) and a refused AI request (title and button). It screenshots each surface at 360 and 375 px (mobile-chromium) and 768 and 1280 px (desktop-chromium), light and dark: 288 images, plus 24 Spanish card shots. New tests in the same file: the usage line matches on Settings and all seven tools and drops from "2 of 5" to "1 of 5" after one Brainstorm run without a reload; the trial banner counts down, opens the pricing dialog and stays dismissed;/app/billinglands on the subscription card.recipes/practice.spec.ts: the Sparring shortfall note now asserts the same "You've used 2 of 2 included AI uses today." as the Crossfire dialog.recipes/usage-limits.spec.ts: one exact-text assertion updated for "1 AI use" (was "1 AI request").32e10d11:billing-states7/7 on each (.verify/artifacts/20261023T080857Z-billing-states,20261023T081128Z-billing-states). Oneb279102:practice5/5 on each (20261023T074432Z-practice,20261023T074813Z-practice). Earlier in the branch, before the round-1 copy fixes, which don't touch what they assert:usage-limits6/6 on each (20261023T070000Z-usage-limits,20261023T070045Z-usage-limits) ande2e/full-stack/billing.spec.ts2/2 on each. The billing recipe also addsbill-states-esfor VS-06: Spanish cards for trial, cancelling and past due at 360/375/768/1280 in light and dark. In total 312 region screenshots are kept locally; they can't be attached throughgh.verify.sh e2e run) were not run: they spend OpenRouter credit. The flows they cover that this PR changes are the Settings card, the pricing dialog, the limit dialog and the new usage line on every tool header.The pricing dialog's Manage path is reached in the recipe by stubbing
create-portalwith the answer the function gives an account Stripe does not know. Settings then falls back to the dialog. The portal itself needs Stripe and is not exercised.Needs a manual check
Review rounds
Round 1 (fresh-context Claude,
claude-opus-5-5, on1e60a867) found nothing at Medium or above. Its Lows and Nits:billingActionreturns Manage for promo Pro with an old Stripe customer and no open subscription. Not changed. That branch predates this PR in the Settings card (T4), and changing it would change Settings and entitlement-adjacent behavior. Left open for a follow-up.billingAction.useSubscription's signed-in fallback when a refetch errors. Not changed, because the fallback-on-error predates this PR. Worth a follow-up that keeps the last good data.{{days}}fromTRIAL_DAYS.empty:hidden.Round 2 (fresh-context Claude,
claude-opus-5-5, oneb279102) found nothing at Medium or above, and confirmed the six round-1 fixes. Its findings:onPromoRedeemed. Fixed: the dialog re-reads the plan and count itself after a successful redeem.aria-busywrapped its own status message. Fixed by droppingaria-busy.I did not run a third round: neither round found anything at Medium or above, and the round-2 fixes are small and covered by the unit tests, the full verify and the billing recipe rerun on
32e10d11.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes