M11.1 — /[slug]/catalog page renders the live catalog from tenant-registry,
gates already-owned products with an 'Active' chip, and exposes two
server actions per remaining card:
- Request → POST /v1/catalog/request (emits an audit event; sales
follow-up flow will pick this up when M8.x lands ERPNext + the
Lead webhook)
- Start 14-day trial → POST /v1/catalog/trial-request (provisions
the entitlement immediately; 14-day expiry per M4.2)
Flash banner on success/error (?ok= / ?err= query params).
M12.1 — Public /start route. Server action calls
createTenant({slug, name, plan, admin_email}) → tenant-registry's
KC-aware POST /v1/tenants → user lands at /<slug>/dashboard. The
dashboard now renders a trial-days-left banner when status=trial
and trial_ends_at is set (urgent styling when ≤3 days remain).
Library:
src/lib/tenant-registry.ts widened from one-call client to the
full read+mutate surface (fetchCatalog, fetchEntitlements,
requestProduct, startTrial, createTenant). Returns typed
{ok: true, ...} | {ok: false, error: '...'} so server actions
branch cleanly. 22 vitest cases, 100% line+branch+function
coverage of src/lib/.
Catalog tests rely on the mock-fetch pattern; the user-visible
flow is exercised by Playwright when the dev stack is up.
Refs: M11.1 + M12.1
10 route shells under /[slug]/, role-filtered Nav, backstage stub at /__backstage__, dashboard reads session.products to render tiles. src/lib/session.ts is the canonical role × surface matrix; canSee() is the only RBAC primitive in the portal (real enforcement remains at the API layer). 24 vitest tests; 100% src/lib coverage.
Refs: M5.2
Next.js 16 + Auth.js v5 skeleton: host→slug middleware, tenant-context layout, OIDC sign-in flow against breakpilot-dev realm. 100% coverage on src/lib. Bumps next to 16.2.6 to clear trivy CVEs in 15.0.3.