§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>
This commit is contained in:
@@ -23,6 +23,84 @@ skip versions are the composition of each intervening adjacent
|
||||
release's steps in order — no A-to-B path is pre-computed beyond
|
||||
that.
|
||||
|
||||
## 0.35.0 — 2026-06-04
|
||||
|
||||
**Minor (breaking) — §22 M3 frontend: `/p/<project>/` routing, runtime
|
||||
branding (the `VITE_APP_NAME` hard cut), the deployment directory + project
|
||||
switcher, and server-side 308 redirects off the old corpus-root URLs.
|
||||
Completes the runtime-config cut 0.33.0 began.**
|
||||
|
||||
Added:
|
||||
|
||||
- **`DeploymentProvider`** (`src/context/DeploymentProvider.jsx`) — boots
|
||||
`GET /api/deployment` once and provides `{ name, tagline,
|
||||
defaultProjectId, projects[], loading }` to the tree. The neutral
|
||||
`brandTitle()` fallback (`'RFC'`) paints during the pre-fetch frame.
|
||||
- **`/p/:projectId/*` routing** with the generic `/e/<slug>` entry segment
|
||||
(§22.10). **`ProjectLayout`** (`src/components/ProjectLayout.jsx`) fetches
|
||||
`GET /api/projects/:id`, applies the project's `theme` as `:root` CSS
|
||||
custom-property overrides (reset on switch/unmount so accents never bleed),
|
||||
provides `ProjectContext`, and sets the tab title to the project name.
|
||||
- **The §4 guard** — `ProjectLayout` renders the corpus only for the
|
||||
corpus-served (default) project; any other id renders a "content not yet
|
||||
served" placeholder (`NotServedPlaceholder`), so per-project serving (the
|
||||
next backend slice, Plan B) can land without a wrong-content footgun.
|
||||
- **Deployment directory** at `/` (`Directory.jsx`) — renders the
|
||||
caller-visible projects as cards when **2+** are visible; the **N=1** case
|
||||
redirects straight into the single project (`/p/<id>/`), preserving OHM's
|
||||
"land in the corpus" UX. A **project switcher** (`ProjectSwitcher.jsx`)
|
||||
rides deployment chrome when 2+ projects are visible.
|
||||
- **Entry-noun terminology** (RFC / Spec / Feature) driven by `project.type`
|
||||
(§22.4a); the route segment stays the generic `/e/`.
|
||||
- **`GET /api/deployment`** now returns **`default_project_id`** — the
|
||||
corpus-served project the frontend guard keys on.
|
||||
- **Server-side 308 redirects** (`app/api_deployment.py`): `GET /rfc/<slug>`,
|
||||
`/rfc/<slug>/pr/<n>`, and `/proposals/<n>` permanently redirect to
|
||||
`/p/<default>/e/<slug>[…]` / `/p/<default>/proposals/<n>` (§5/§22.10), so
|
||||
external "RFC-0001"-style links and bookmarks keep working.
|
||||
|
||||
Breaking:
|
||||
|
||||
- **`VITE_APP_NAME` is removed.** The build no longer reads it and no longer
|
||||
fails without it; the deployment name comes from the registry
|
||||
(`deployment.name` in `projects.yaml`) served at runtime via
|
||||
`GET /api/deployment`. The same build now serves any deployment. The
|
||||
build-time `%VITE_APP_NAME%` HTML token and the `inject-app-name` Vite
|
||||
plugin are gone; `index.html` ships a static `<title>RFC</title>` and JS
|
||||
sets the real title after config loads.
|
||||
- **Entry/proposal URLs moved under `/p/<project>/`.** The old SPA routes
|
||||
`/rfc/:slug`, `/rfc/:slug/pr/:n`, `/proposals/:n` are removed from the SPA
|
||||
and served as backend 308s instead — so nginx must route `/rfc/` and
|
||||
`/proposals/` to the backend rather than the SPA `index.html`.
|
||||
|
||||
Scope boundary (informational, not breaking): RFC *data* is still served
|
||||
unscoped for the default project this slice — per-project corpus serving is
|
||||
the next backend slice (Plan B). Non-default projects show the placeholder.
|
||||
|
||||
Upgrade steps:
|
||||
|
||||
1. **MUST** update nginx: add `location /rfc/` and `location /proposals/`
|
||||
blocks that `proxy_pass` to the backend, **before** the SPA `location /`
|
||||
fallback. The framework's `deploy/nginx/ohm.wiggleverse.org.conf` (and
|
||||
`testing/web.nginx.conf`) already carry them; a deployment running a custom
|
||||
vhost MUST add them, or old corpus-root URLs 404 against the SPA instead of
|
||||
redirecting.
|
||||
2. **MUST** ensure the registry `projects.yaml` `deployment.name` is set — it
|
||||
is now the header brand and tab title (already required since 0.33.0).
|
||||
`tagline` shows on the directory.
|
||||
3. **SHOULD** remove `VITE_APP_NAME` from `frontend/.env`; it is no longer
|
||||
read (no error if left — simply ignored).
|
||||
4. **MUST** rebuild the frontend and deploy. Verify: the header shows the
|
||||
deployment name from `/api/deployment`; `/` lands in your project (N=1) or
|
||||
shows the directory (2+); an old `/rfc/<slug>` URL **308**-redirects to
|
||||
`/p/<default>/e/<slug>`; `/api/health` is green.
|
||||
5. **MAY** note: the Tier-1 Playwright e2e for these flows lands once the
|
||||
Tier-1 Docker stack seeds a registry repo + `projects.yaml` (`REGISTRY_REPO`
|
||||
is unset in `testing/.env.tier1` today, so the dockerized backend can't boot
|
||||
there post-0.33.0). This slice is covered by Vitest unit tests
|
||||
(`DeploymentProvider`, `ProjectLayout` theme/guard, `Directory`) + the
|
||||
backend redirect tests (`backend/tests/test_api_deployment.py`).
|
||||
|
||||
## 0.34.0 — 2026-06-04
|
||||
|
||||
**Minor — containerize rfc-app for per-PR preview environments (flotilla
|
||||
|
||||
Reference in New Issue
Block a user