Files
Ben Stull 999c4b65ef §22 M3 frontend: /p/<project>/ routing, runtime branding, directory, 308s (v0.35.0)
Implements the M3-frontend slice of the §22 multi-project track, per
docs/superpowers/specs/2026-06-03-m3-frontend-design.md (design merged in
#10). Completes the runtime-config cut 0.33.0 (M3-backend Plan A) began.

Frontend:
- DeploymentProvider boots GET /api/deployment → {name, tagline,
  defaultProjectId, projects}; brandTitle() neutral 'RFC' pre-fetch fallback.
- /p/:projectId/* routing with generic /e/<slug> segment. ProjectLayout
  fetches /api/projects/:id, applies per-project theme (reset on switch),
  provides ProjectContext, guards the corpus (served only for the default;
  others get NotServedPlaceholder — decouples this slice from Plan B).
- Directory at / (2+ projects) with N=1 redirect into the single project;
  ProjectSwitcher in deployment chrome; entry-noun by project type.
- VITE_APP_NAME hard cut: removed from vite.config + index.html; the 6 brand
  reads now use deployment.name via context; static <title>RFC</title> + JS
  document.title. Internal /rfc·/proposals links → /p/<project>/e|proposals
  via lib/entryPaths.

Backend:
- GET /api/deployment returns default_project_id (the guard contract).
- Server-side 308s: /rfc/<slug>, /rfc/<slug>/pr/<n>, /proposals/<n> →
  /p/<default>/… . nginx (testing + prod) routes /rfc/ and /proposals/ to
  the backend.

Tests: 3 new backend redirect/deployment tests (438 pass); Vitest unit for
DeploymentProvider, ProjectLayout (theme/guard/404), Directory (11 pass);
clean build with no VITE_APP_NAME. Playwright e2e deferred until Tier-1 seeds
a registry (see CHANGELOG 0.35.0 step 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 05:58:59 -07:00
..

Test harness (handbook §10.3 two-tier testing)

One environment-agnostic suite, two targets.

Tier 1 — local Docker (every PR)

make e2e-install     # one-time: install Playwright + chromium (cold checkout)
make tier1-up        # build + start: gitea(seeded) + backend + web(nginx) + mailpit
make e2e             # run Playwright against http://localhost:8080
make fe-unit         # run Vitest frontend unit tests
make tier1-down      # stop + wipe volumes

The stack is hermetic and disposable — fresh SQLite + fresh seeded Gitea each tier1-up. e2e signs in via the email OTC flow, reading the code back from Mailpit, so no real OAuth provider is needed.

CI note: the backend enforces a per-IP OTC request limiter (5 requests / 300s, backend/app/ratelimit.py). A single make e2e run uses exactly one OTC request, so the normal "fresh tier1-up then one e2e" flow is well clear of it. Do not retry make e2e more than ~4 times in a 5-minute window against the same running stack, or the 6th OTC request will 429. Restarting the backend container (or tier1-down/tier1-up) resets the in-process limiter. Note: this is distinct from the per-email 60s OTC cooldown (OTC_REQUEST_COOLDOWN_SECONDS, backend/app/otc.py); the smoke spec handles that separately by using a unique email address per run.

Tier 2 — PPE (deploy gate)

The SAME suite, pointed at the PPE instance (once rfc-app-ppe.<base> is stood up via flotilla — see the engineering handbook §10.1/§10.3):

cd e2e && BASE_URL=https://rfc-app-ppe.<base> MAILSINK_URL=<ppe-mailpit-api> npm run e2e

PPE provides the real nginx/systemd/SQLite topology + its own isolated Gitea + always-pass Turnstile keys. Standing up the PPE VM is an operator task, not part of this repo.