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.
WS-H — My Account SPA (
site/) + account APIThe last product slice of the wardnet-cloud initial release. Builds on WS-F
(human web login +
audJWTs), 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.mdWS-H)A React SPA at
site/— not a Cargo workspace member — built to staticassets and pushed to a CDN. It calls the Tenants API (its backend) with an
account-session bearer (the
POST /v1/auth/token→ 5-minUSERJWT exchangefrom WS-F / ADR-0009). Surface:
OIDC + GitHub OAuth2 already exist server-side).
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?
site/in this repo (recommended)wardnet/wardnet-web)wardnet/wardnetwardnet-jsSDK.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, andcommon::contractdiscipline avoids the cross-repo drift aseparate 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 untilsite/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:site/**bucket to.github/actions/detect-changes.build-sitereusable leaf (Vite/React: install → lint → test → build staticassets) wired into
ci.yml/pr.yml+ theAll checks passedaggregator.release-account-site.ymlon theaccount-site-v*tag → static build →CDN deploy (its own release cadence, independent of the three services).
codeql.yml(WS-I dropped it as the repo waspure-Rust) + an
npmentry to.github/dependabot.ymlforsite/.the same Codecov upload).
Open design gates (decide at WS-H start)
has password + OIDC + OAuth2).
Dependencies
POST /v1/auth/token,GET /v1/me) — ✅ merged.detect-changes, aggregator) — in review onfeature/ws-i-ci.The other remaining slice, WS-J (docs/ADRs/CONTEXT pass), is independent and
can land before or after this.