Frontend Architecture
Spectral’s frontend topology is four Cloudflare Pages projects: the customer dashboard, the staff Operations app, public user docs, and the staff Codex. The customer dashboard is an inspection and management surface, not the product’s primary interface — Spectral’s product surface is the decision API that customers’ agents call programmatically; the dashboard is where a customer reviews what that API decided. All data flows through FastAPI; the auth helper is the only Supabase JS surface used. Decision lineage in ADR-050 (frontend pattern), ADR-046 (hosting), and ADR-047 (Operations app as its own deployable).
Five proxied subdomains
Section titled “Five proxied subdomains”| Hostname | Origin | Auth | Audience |
|---|---|---|---|
app.runspectral.com | Cloudflare Pages spectral-dashboard + /api/* proxy Function | Supabase Auth (JWT in cookie) | Customer |
ops.runspectral.com | Cloudflare Pages spectral-operations + JWKS middleware + /operator/* proxy Function | JWKS-local middleware + OPERATIONS_SCOPES | Staff |
api.runspectral.com | Cloudflare app container — api (FastAPI) | Authorization: Bearer JWT | Programmatic |
docs.runspectral.com | Cloudflare Pages docs-user | None (public) | Anyone |
codex.runspectral.com | Cloudflare Pages docs-codex + Pages Function | Pages Function JWKS-local + OPERATIONS_SCOPES | Staff |
Cookie scope: Domain=runspectral.com eTLD+1, set by the Supabase auth helper at initialization. The same login session is naturally readable on app., ops., and (via the Pages Function) codex..
Rendering mode and data path
Section titled “Rendering mode and data path”The two SPA frontends share the same primitives.
- Default rendering mode: SPA. Server-side rendering is opt-in per route, used only where a specific route’s UX demands it.
- Auth helper only. The Supabase JS client handles auth (PKCE flow + JWT claim extraction). It is not used for data access.
- Data path: Frontend → Pages Function proxy → FastAPI → Postgres. Frontends never call PostgREST or the Supabase data API directly. A lint rule rejects data-access imports from the frontend packages.
Specific framework choices, build toolchain, and per-route routing primitives live in ADR-050.
Operator auth (Pattern A)
Section titled “Operator auth (Pattern A)”Pages Function middleware runs on every non-public operator-facing route (per ADR-046 D9):
- Read JWT from session cookie.
- Validate signature + expiry against Supabase JWKS (cached locally, with
kid-miss bypass). - Check
app_metadata.organization_role === "operations"(per ADR-039). - Reject with the staff-only UX on failure.
A contract test enforces parity between the frontend’s JWT validation and the API’s Python JWKS-local validation on the same inputs (signature, expiry, audience, issuer, scope set).
The Codex Pages Function uses the same pattern with the same scope — the auth gate is a direct port. JWKS cache lives in Cloudflare KV (10-minute TTL with kid-miss bypass) since Pages Free isolate lifetime is non-deterministic.
Realtime (server → client)
Section titled “Realtime (server → client)”Customer-facing realtime is unidirectional, server → client — principally World Agent chat token streaming (the post-release chat affordance per ADR-081 D5) and agent-activity indicators. Decisions themselves are synchronous — /decide returns the binding contract inline — so there is no decision-delivery stream. Implementation: sse-starlette from FastAPI.
Internally, the workers → API streaming hop uses Supabase Realtime as a channel keyed by conversation_id; apps/api proxies that channel as SSE to the client. That internal hop is invisible to customers and is the only Supabase Realtime use in production.
Docs sites
Section titled “Docs sites”docs-user(public) — Astro static; deploys todocs.runspectral.comasspectral-docsviawrangler pages deploy.docs-codex(staff-internal) — Astro static plus a Pages Function for JWKS-local auth +OPERATIONS_SCOPES. Deploys tocodex.runspectral.com.