Skip to content

WS-H: My Account SPA (site/) + account API #1

Description

@pedromvgomes

WS-H — My Account SPA (site/) + account API

The last product slice of the wardnet-cloud initial release. Builds on WS-F
(human web login + aud JWTs), WS-G (Stripe billing/entitlement), and WS-I
(per-service CI/release). Tracked separately so it lands on its own cadence.

Scope (from PLAN-INITIATIVE.md WS-H)

A React SPA at site/not a Cargo workspace member — built to static
assets and pushed to a CDN. It calls the Tenants API (its backend) with an
account-session bearer (the POST /v1/auth/token → 5-min USER JWT exchange
from WS-F / ADR-0009). Surface:

  • Signup + email login (verified-email two-gate; password argon2id + Google
    OIDC + GitHub OAuth2 already exist server-side).
  • Stripe checkout / customer-portal redirects; subscription + entitlement view.
  • Instance-code (daemon enrollment code) generation.
  • Network / vanity-name management.

The auth layer stays pure-JWT; the cookie↔JWT exchange already lives in
IdentitiesService / api/auth.rs. "Am I logged in?" = attempt the mint.

Decision needed first: where does the SPA live?

Option Summary Trade-off
A. site/ in this repo (recommended) SPA co-located with its only backend (Tenants). Atomic API↔UI contract changes; matches the existing locked decision + the path-gated CI already shipped in WS-I. Mixes Rust+JS toolchains (CI already gates by path).
B. New frontend monorepo (wardnet/wardnet-web) Dedicated FE repo for this + future frontends. Clean FE separation + shared UI/SDK, but premature for one small SPA and reintroduces cross-repo API-contract drift.
C. Into wardnet/wardnet Reuse the daemon repo's React tooling + wardnet-js SDK. Recouples cloud and daemon — against the split that justified wardnet-cloud existing.

Recommendation: A. The My Account SPA is the Tenants API's dedicated
frontend; co-location keeps the contract atomic, the WS-I CI already supports a
site/** leaf, and common::contract discipline avoids the cross-repo drift a
separate FE repo would bring. Revisit a frontend monorepo only if/when multiple
cloud frontends emerge. (This reopens a previously-locked decision — confirm
before WS-H starts.)

CI / release follow-ons (the site/ half of WS-I, deferred until site/ exists)

WS-I shipped the Rust-side per-service CI/release in full but deferred the
SPA pipeline because site/ doesn't exist yet. WS-H must add:

  • A site/** bucket to .github/actions/detect-changes.
  • A build-site reusable leaf (Vite/React: install → lint → test → build static
    assets) wired into ci.yml/pr.yml + the All checks passed aggregator.
  • A release-account-site.yml on the account-site-v* tag → static build →
    CDN deploy (its own release cadence, independent of the three services).
  • Re-add the JS/TS language to codeql.yml (WS-I dropped it as the repo was
    pure-Rust) + an npm entry to .github/dependabot.yml for site/.
  • A coverage path for the SPA if desired (the daemon repo merges site LCOV into
    the same Codecov upload).

Open design gates (decide at WS-H start)

  • Account-session login mechanism: magic-link vs emailed code (server already
    has password + OIDC + OAuth2).
  • CDN target (Cloudflare Pages / R2 + Workers / other) and its deploy auth.
  • SPA tooling: Vite + React version, package manager, lint/test stack.

Dependencies

  • WS-F (login plane, POST /v1/auth/token, GET /v1/me) — ✅ merged.
  • WS-G (Stripe checkout/portal, entitlement) — ✅ merged.
  • WS-I (per-service CI/release, detect-changes, aggregator) — in review on
    feature/ws-i-ci.

The other remaining slice, WS-J (docs/ADRs/CONTEXT pass), is independent and
can land before or after this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions