V1 Web PWA · Surface walkthrough

Billing

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
9sections4 minread

On this page

Context. surface customer · domain account · route /billing · auth signed-in · source apps/oshun/web/src/app/billing/page.tsx

Last walked. 2026-06-29 billing-index direct-route addendum — Playwright real-dev-infra coverage for signed-in /billing render, anonymous proxy redirect, rail hrefs, 44 px tap targets, no horizontal overflow at 390 px, standalone display-mode launch, and real service-worker offline replay of the cached static billing document. Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §202; spec: apps/oshun/web/e2e/billing-index.spec.ts. 2026-06-29 billing-rail telemetry addendum — the same real-dev-infra spec now asserts the static billing card, rail list, crypto/profile/AAA rail links, and billing steward link expose no telemetry hook attributes and dispatch no oshun-analytics:event when activated. Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §216; spec: apps/oshun/web/e2e/billing-index.spec.ts.

Purpose#

The top-level account-billing landing surface — a calm directory that routes the user to the rail that fits where they came from. Today only the Aje crypto rail is wired (/billing/crypto); card and bank rails are explicitly "follow in subsequent releases" per the in-page copy. Users arrive from the Manage billing affordance in /profile.

Entry points#

  • Cross-domain link from /profile billing row ("Manage billing" affordance)
  • Direct URL / bookmark — yes; auth required (proxy.ts redirects no-session to /welcome; /billing is not in PUBLIC_PATHS/PUBLIC_PREFIXES)
  • Alias hops: /settings and /account rewrite to /profile, which links here
  • Upgrade flow that lands a user on a payment decision (/aaa-upgrade)
  • Email link from invoice issuance (which more typically deep-links straight to /billing/crypto?invoice=…)

Layout regions#

page.tsx is a static server component (no data fetch, no LWebShell — it uses raw Lilith CSS variables --l-bg, --l-paper, --l-ink, --l-accent, --l-rule, --l-muted directly). A single <main data-billing-index> centers one bordered card.

  • Header: none (no shell header; bare <main> on --l-bg)
  • Hero / above-the-fold: the card — eyebrow Account · billing, serif H1 "Your subscription, kept calm.", and a muted intro paragraph
  • Main: a <ul> of three rail/destination links
  • Footer / sticky bottom: a muted help line with a mailto: steward link

States#

  • Loading — N/A because the page is static (no await, no data fetch); renders instantly
  • Empty — N/A because content is hardcoded copy, not data-driven
  • Populated (short) — N/A; the three links are fixed
  • Populated (long) — N/A; fixed link list
  • Error (recoverable) — N/A; no query to fail
  • Error (unrecoverable) — no segment error.tsx exists under apps/oshun/web/src/app/billing; a static render crash would fall through to the nearest parent/global boundary
  • Offline — static HTML; SW-cached shell renders the links offline; the mailto: and outbound links still resolve when connectivity returns
  • Gated / anonymousproxy.ts redirects no-session users to /welcome before this renders; verified by billing-index
  • Standalone PWA — bare <main> uses 100svh and safe-area padding; the 390 px standalone launch is verified for display-mode, containment, 44 px tap targets, and no horizontal overflow

Interactions#

  • Pay with crypto (USDC, USDT, ETH, BTC) → (link)
    • Function: navigates to /billing/crypto (the only wired rail)
    • Keyboard: in document order, first focusable; Enter activates
    • Screen reader: announces "Pay with crypto (USDC, USDT, ETH, BTC), link"
    • Touch target: verified ≥ 44 px tap height at a 390 px phone viewport
    • Mobile (≤ 640 px): full-width card, links stack
    • Offline behavior: the billing index itself replays from the real service worker cache after an authenticated online visit; the dynamic crypto target waits for connectivity
    • Telemetry: explicitly absent; no hook attributes and no oshun-analytics:event dispatch on activation
  • Manage your account profile → (link)
    • Function: navigates to /profile
    • Screen reader: announces "Manage your account profile, link"
    • Telemetry: explicitly absent; no hook attributes and no oshun-analytics:event dispatch on activation
  • Upgrade to the AAA Studio plan → (link)
    • Function: navigates to /aaa-upgrade
    • Screen reader: announces "Upgrade to the AAA Studio plan, link"
    • Telemetry: explicitly absent; no hook attributes and no oshun-analytics:event dispatch on activation
  • billing@oshun.app (mailto link)
    • Function: opens the user's mail client to the billing steward
    • Screen reader: announces the address as a link
    • Offline behavior: hands off to OS mail client; composition works offline
    • Telemetry: explicitly absent; no hook attributes and no oshun-analytics:event dispatch on activation

E2E coverage#

  • apps/oshun/web/e2e/billing-index.spec.ts — signed-in direct-route render, static directory copy, crypto/profile/AAA rail hrefs, billing steward mailto:, 44 px tap-target floor at 390 px, no horizontal overflow, standalone display-mode launch, real service-worker cached-document offline replay with no generic offline-fallback card, explicit absence of rail/card/steward telemetry hooks and click-dispatched analytics events, and anonymous redirect-before-render.

Data & contracts#

  • Reads: none — fully static
  • Writes: none
  • Realtime: none
  • Caching: static (no dynamic, no fetch); /billing is an exact shell navigation in public/sw.js, so an authenticated online navigation writes the document into the static SW cache and offline navigation replays the cached billing page rather than the generic offline fallback
  • Auth/role check: proxy.ts — protected by default (not public); no-session → /welcome
  • Metadata: title: 'Billing', description: 'Plan, payment method, and invoice history for your OSHUN account.', alternates.canonical: '/billing'

Note the description promises "Plan, payment method, and invoice history," but the page itself renders none of those — it is a router to /billing/crypto. See gaps.

Cross-references#

Open questions / known gaps#

  • The page metadata promises plan / payment-method / invoice-history views, but none exist here — it is purely a link directory. Confirm whether those are intended for this route or always delegated to /billing/crypto.
  • No telemetry on the rail links — current V1 behavior is explicitly no billing_rail_selected-style event or hook on the static rail directory.
  • Card-rail and bank-rail surfaces are unbuilt ("follow in subsequent releases") — confirm they are out of V1 scope.
  • Add a segment error.tsx for /billing/* if product wants a local unrecoverable render-crash boundary; none exists today.