Skip to content

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

Merged
Programcode merged 7 commits into
mainfrom
fix/t6-usage-pricing
Oct 23, 2026
Merged

Programcode merged 7 commits into
mainfrom
fix/t6-usage-pricing

Conversation

@Programcode

@Programcode Programcode commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

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_counters through ai_usage_today()), so a Sparring round or a Crossfire session counts once and Opus counts as 5. The fallback that counted ai_requests rows 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 (refreshAIUsage in src/lib/query-client.ts), so the count moves without a reload. Production already has the migration that adds ai_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 passes units. 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 (403 upgrade_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, billingAction in src/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 on DialogContent; 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/billing redirects 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 verify on the current head (32e10d11, based on main at a7610fa5), Node 22, exit 0. main has 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.

  • docs:check, lint (eslint, knip, repo rules) and typecheck: pass
  • unit: 81 files, 605 tests pass
  • collaboration: 5 files, 30 tests pass
  • edge (Deno): 457 pass, 0 fail
  • build: pass
  • service worker offline check: pass
  • e2e smoke (desktop Chromium): 47 pass, 0 flaky. One earlier run had two cold-start flakes in 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: SubscriptionErrorModals passes 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-debatelab skill (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/billing lands 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").
  • Final runs, all passing on desktop-chromium and mobile-chromium. On 32e10d11: billing-states 7/7 on each (.verify/artifacts/20261023T080857Z-billing-states, 20261023T081128Z-billing-states). On eb279102: practice 5/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-limits 6/6 on each (20261023T070000Z-usage-limits, 20261023T070045Z-usage-limits) and e2e/full-stack/billing.spec.ts 2/2 on each. The billing recipe also adds bill-states-es for 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 through gh.
  • Agentic tests (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-portal with 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

  • Real Stripe Checkout and the billing portal (not driven, per the batch rules).
  • The trial banner and usage line on a real phone, in Safari.
  • Pre-existing and not changed here (review round 1, finding 2). When the portal says "no subscription" for an account the database still has as Pro, Settings opens the pricing dialog, which offers Manage again. That only happens when Stripe and the subscriptions table disagree.

Review rounds

Round 1 (fresh-context Claude, claude-opus-5-5, on 1e60a867) found nothing at Medium or above. Its Lows and Nits:

  1. Low. billingAction returns 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.
  2. Low. A portal "no subscription" answer for a database-Pro account sends Settings to a dialog that offers Manage again. Not changed, because it's pre-existing. Listed under manual checks.
  3. Low. Sparring's shortfall note named the model as the reason while the round cost was unknown. Fixed: it now says "This round needs N AI uses and you have M left today".
  4. Low. The trial banner said Upgrade while the dialog it opens could offer Manage (the QB-17 fallback). Fixed: the banner label comes from the same billingAction.
  5. Low (uncertain). More refetches after AI calls means more chances to hit 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.
  6. Nit. The ES/HU trial-ended sentence used other verbs and hardcoded "3-day". Fixed, with {{days}} from TRIAL_DAYS.
  7. Nit. The usage line joined two sentences in code. Fixed: one plural key.
  8. Nit. The page header rendered an empty meta div. Fixed with empty:hidden.
  9. Nit. "Used 7 of 5" after a lowered limit. Fixed: current is capped at the limit.
  10. Nit. The full email is only in a tooltip. Not changed, since it meets ST-03.

Round 2 (fresh-context Claude, claude-opus-5-5, on eb279102) found nothing at Medium or above, and confirmed the six round-1 fixes. Its findings:

  1. Low. A promo redeemed from the trial banner or a tool's dialog didn't refresh the plan, because only Settings passed onPromoRedeemed. Fixed: the dialog re-reads the plan and count itself after a successful redeem.
  2. Low. The dialog's Manage path showed the portal's English "No active subscription" in a toast. Fixed: it shows the same translated message as Settings.
  3. Low (pre-existing). A legacy free account at the daily cap got only "OK", as if it paid, while Settings and the dialog offer it Upgrade. Fixed: the "already pays" check now uses the billing state (Pro, cancelling, past due), and a test covers legacy free.
  4. Low. When the quota check gives no answer, Sparring's note states the browser's per-call estimate as the round's cost. Not changed, since the estimate predates this PR. Worth a softer wording in a follow-up.
  5. Nit. The skeleton's aria-busy wrapped its own status message. Fixed by dropping aria-busy.
  6. Nit. After Stop, the count can refresh before the server's refund lands, so it shows the use until the next refresh. Not changed. Before this PR the count was never refreshed after a run at all.

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

    • Daily AI usage and remaining limits are now shown across tool pages, with usage refreshed after AI requests.
    • Trial subscribers see a dismissible reminder with the time remaining in their trial.
    • Pricing and subscription screens show plan-specific details and actions, localized prices, and clearer limit and model-restriction messages.
    • The billing link now takes users to the subscription section in Settings.
  • Bug Fixes

    • Subscription refusals are handled consistently across AI tools, including during streaming and generation.
    • Usage is no longer estimated when daily usage data is unavailable.

Programcode and others added 4 commits October 23, 2026 07:19
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>
Copilot AI balanced review requested due to automatic review settings October 23, 2026 07:23

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between a7610fa and 32e10d1.

📒 Files selected for processing: 46
  • .claude/skills/verify-debatelab/features/README.md
  • .claude/skills/verify-debatelab/features/practice.md
  • .claude/skills/verify-debatelab/recipes/billing-states.spec.ts
  • .claude/skills/verify-debatelab/recipes/practice.spec.ts
  • .claude/skills/verify-debatelab/recipes/usage-limits.spec.ts
  • docs/architecture.md
  • src/App.tsx
  • src/components/features/sparring/SparringSetup.tsx
  • src/components/features/subscription/DailyUsage.tsx
  • src/components/features/subscription/LimitReachedModal.tsx
  • src/components/features/subscription/ModelRestrictedModal.tsx
  • src/components/features/subscription/PricingModal.tsx
  • src/components/features/subscription/SubscriptionErrorModals.tsx
  • src/components/features/subscription/SubscriptionStatus.tsx
  • src/components/features/subscription/TrialBanner.tsx
  • src/components/features/subscription/dialog-layout.ts
  • src/components/features/subscription/index.ts
  • src/components/layout/AppLayout.tsx
  • src/components/layout/PageHeader.tsx
  • src/components/ui/dialog.tsx
  • src/hooks/use-subscription.test.tsx
  • src/hooks/use-subscription.ts
  • src/lib/ai/error-display.ts
  • src/lib/ai/errors.ts
  • src/lib/ai/features/session-quota.ts
  • src/lib/ai/limit-message.test.ts
  • src/lib/ai/limit-message.ts
  • src/lib/ai/request.ts
  • src/lib/ai/streaming.ts
  • src/lib/billing/plan.ts
  • src/lib/query-client.ts
  • src/locales/en.json
  • src/locales/es.json
  • src/locales/hu.json
  • src/pages/AskPage.tsx
  • src/pages/AttackPage.tsx
  • src/pages/BrainstormPage.tsx
  • src/pages/CrossfirePage.tsx
  • src/pages/FactoidsPage.tsx
  • src/pages/GeneratePage.tsx
  • src/pages/SettingsPage.tsx
  • src/test/billing/pricing-modal-redirect.test.tsx
  • src/test/components/LimitReachedModal.test.tsx
  • src/test/pages/AskAccountSwitch.test.tsx
  • src/test/pages/AskStreamFallback.test.tsx
  • src/test/pages/CrossfireVoice.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The change updates subscription actions and dialogs, adds daily-usage displays and trial reminders, and routes /app/billing to the Settings subscription card. AI request paths refresh usage data, and billing and usage verification now covers additional states, languages, and viewports.

Changes: Subscription and usage behavior

Layer / File(s) Summary
Billing actions and plan dialogs
src/lib/billing/plan.ts, src/components/features/subscription/*, src/components/ui/dialog.tsx, src/locales/*.json, src/test/billing/*, src/test/components/LimitReachedModal.test.tsx, .claude/skills/verify-debatelab/features/README.md, .claude/skills/verify-debatelab/recipes/billing-states.spec.ts
Shared plan helpers select the current plan and billing action. The pricing dialog routes management to the billing portal and other actions to Checkout. Subscription modals use shared billing labels and dialog styling. Tests cover billing states, redirects, and localized presentation.
Usage counters and refusal messages
src/components/features/subscription/DailyUsage.tsx, src/hooks/use-subscription.ts, src/lib/query-client.ts, src/lib/ai/*, src/pages/*, src/components/features/sparring/SparringSetup.tsx, src/locales/*.json, src/test/*, .claude/skills/verify-debatelab/recipes/*, .claude/skills/verify-debatelab/features/practice.md, docs/architecture.md
The usage display reads the server counter and appears on Settings and tool pages. AI request and streaming paths invalidate usage queries after completion. Subscription refusals use shared limit messages, including weighted-usage and shortfall text.
Trial banner and subscription navigation
src/components/features/subscription/TrialBanner.tsx, src/components/layout/*, src/App.tsx, src/pages/SettingsPage.tsx, .claude/skills/verify-debatelab/recipes/billing-states.spec.ts
The trial banner displays remaining days, opens the pricing dialog, and supports dismissal. /app/billing redirects to the Settings subscription section. Settings scrolls to that section when the URL hash is #subscription.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant PricingModal
  participant billingAction
  participant Stripe
  User->>PricingModal: Select plan action
  PricingModal->>billingAction: Derive action from subscription state
  billingAction-->>PricingModal: Return selected billing action
  alt Action is manage
    PricingModal->>Stripe: Redirect to billing portal
  else Other action
    PricingModal->>Stripe: Redirect to Checkout
  end
Loading
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

Programcode and others added 3 commits October 23, 2026 07:41
… 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>
@Programcode
Programcode merged commit 91e1e85 into main Oct 23, 2026
3 of 13 checks passed
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.

2 participants