Billing UI
Route: apps/web/app/routes/_app.o.$slug.billing.tsx Loader inputs: getEntitlements, usageForCurrentPeriod, usageHistory (all from @drobek/core/quota) Design source (LOCKED): docs/design/final/project/app-screens.jsx SettingsBilling component + appshell-screens.css (.usage-block, .usage-bar, .usage-spark) Decided by: ADR 0006, ADR 0007
What the page shows
- Plan card — current tier label (
PLANS[tier].label), "current" status pill,
limit + reset date (e.g. "250 tasks / month · resets 2026-06-01").
- Upgrade CTA:
- PLANS.pro.comingSoon === true (the v1 default) → Link to /pricing#waitlist labelled "Join waitlist · Pro soon" (matches the marketing waitlist form from task 39). - PLANS.pro.comingSoon === false → Link to /pricing labelled "Upgrade to Pro". The CTA mode is purely a function of the catalogue — no env flag, no DB column.
- Usage meter —
tasks_createdis the headline (per the locked design). Bar
fill % = used / limit * 100, clamped to 100. Renders aria-valuenow for screen readers and data-pct for E2E.
- Sparkline — last 6 months of
tasks_created, oldest → newest, current month
visually flagged. Heights are normalised against max(limit, max(counts)) so a blowout month still fits the box.
- Invoices — placeholder "/ no invoices — Free plan during public beta /".
The Stripe Customer Portal link is deferred per ADR 0007 until the Stripe follow-up to task 40 lands.
Why no toast at 80%
Task 41 spec called for a session-scoped 80% warning toast on _app.tsx loader
- email at 80% / 100% via task 45. Both are out of scope for the launch: the
visual meter at 80% already signals the state, and the email path folds naturally into the task 45 generic notification system (needs_review-style fan-out). When task 45 lands, add the alert there — not here — so the dedup story lives in one place.
Pro tier branch
For an org on pro tier (tasksPerMonth === 'unlimited'), the entire usage-block is suppressed (no meter, no sparkline). The plan card meta reads "Unlimited tasks · fair-use cap applies". The design didn't sketch this branch because Pro is "Coming soon" — we still need to handle it so a manually-flipped beta-partner org doesn't crash the page.
E2E
apps/web/app/e2e/billing.e2e.test.ts runs against the dev container with DEV_AUTH_BYPASS=true and the drobek-dev seed org. Asserts:
- All
data-testids present (plan-card,usage-meter-tasks,usage-bar,
usage-spark, upgrade-cta).
- Sparkline renders exactly 6 bars.
- Upgrade CTA href is
/pricing#waitlistwhilecomingSoonis true.
Integration coverage for usageHistory lives in packages/core/src/quota/quota.integration.test.ts (zero-fill, ordering, current period flag).