From edbf8cb7ea4117940ec32eae5ccb752fb3fd3f35 Mon Sep 17 00:00:00 2001 From: implementer Date: Mon, 31 Aug 2026 00:56:54 +0000 Subject: [PATCH] docs(adr): record React/Vite admin decision as ADR-008 Commits ADR-008 (React/Vite admin) for [E01-S01-T07], with the six mandated sections (Context, Decision, Alternatives, Consequences, Operational impact, Revisit trigger) per ADR index 46, and adds docs/adr/README.md as the repository copy of the ADR index (section 70) so the decision-text match is verifiable inside the repo. Documentation-only: no source, manifest, lockfile, workflow or test changes. --- docs/adr/ADR-008-react-vite-admin.md | 136 +++++++++++++++++++++++++++ docs/adr/README.md | 65 +++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 docs/adr/ADR-008-react-vite-admin.md create mode 100644 docs/adr/README.md diff --git a/docs/adr/ADR-008-react-vite-admin.md b/docs/adr/ADR-008-react-vite-admin.md new file mode 100644 index 0000000..332d81f --- /dev/null +++ b/docs/adr/ADR-008-react-vite-admin.md @@ -0,0 +1,136 @@ +# ADR-008: React/Vite admin + +- Status: Accepted +- Date: 2026-08-31 +- Deciders: platform stream +- References: ADR index (section 70, `docs/adr/README.md`), ADR-001, ADR-007, + Architecture wiki (sections 1, 4, 9, 11, 20, 42), Technology-Stack wiki + (sections 5.2, 5.3, 6, 7, 8) + +## Context + +EPPP is a modular monolith (ADR-001) whose single application image contains the +public server, admin API, rendering pipeline, extension runtime, core services, +built admin assets and first-party extensions (Architecture §4). The v0.1 scope +explicitly includes an "administration interface" (§1), and Architecture §20 +already fixes its shape: a **React client app built with Vite**, served by the +same image, with `/admin/*` talking to the admin API under `/api/admin/v1/*` and +an initial IA of Dashboard, Content→Posts, Home, Navigation, Appearance (active +theme, theme settings), Extensions and Settings→Site. ADR-001 fixes the runtime +as "Node.js 24 LTS with Fastify 5, PostgreSQL 18, React 19 for server-rendered +public components and a Vite admin". + +React and Vite are already the pinned toolchain in the golden compatibility +tuple. React / React DOM 19.2.8 (class C, exact-pinned) is in the runtime stack +and golden tuple (Technology-Stack §5.2, §7), and the build/admin/test toolchain +is pinned to Vite 8.2.2, `@vitejs/plugin-react` 6.1.0, pnpm 11.23.0, Vitest +4.1.10 and Playwright 1.62.1 (§5.3). The LTS strategy keeps React exact-pinned +at 19.2.8, keeps React Server Components out of v1 and keeps React out of +persisted content formats (§6); direct platform dependencies are exact-pinned +with a committed lockfile (§8). + +The workspace does not implement the admin app yet — `apps/server` is the bare +Fastify health shell and there is no `apps/admin` (Architecture §9; the CI +`build-apps` stage already globs `apps/**` so the admin is picked up when it +lands). This ADR therefore records the decision ahead of the code that +implements it, formalising what the Architecture and Technology-Stack wikis +already mandate: the admin frontend is a React client app built with Vite and +served by the same application image. + +## Decision + +EPPP builds the admin frontend with **React/Vite admin**: a React client +application built with Vite — React 19.2.8 and Vite 8.2.2, exact-pinned class C +dependencies from the golden compatibility tuple (Technology-Stack §5.2, §5.3, +§7) — served by the same application image (ADR-001, Architecture §4). The admin +lives under `/admin/*` and calls the admin API at `/api/admin/v1/*` (§20, §42). +It is deliberately a separate concern from the public rendering pipeline: +public pages stay server-rendered with no client hydration (ADR-007, §19.1), +while the admin is a client-rendered SPA — the interactive management surface +where JavaScript is expected and required. React Server Components stay out of +v1 and React stays out of persisted content formats (Technology-Stack §6). +The decision text — **React/Vite admin** — matches the ADR index entry +(ADR-008, section 70). + +## Alternatives + +- **Server-rendered admin through the public React SSR pipeline** — rejected: + the admin is an interactive management surface where JavaScript is expected; + routing it through the zero-JavaScript, non-hydrated SSR pipeline (ADR-007, + §19.1) would buy nothing — every admin page needs client interactivity — and + would couple admin rendering to the public pipeline's per-request server + rendering. +- **Separate admin framework (e.g. Next.js, Angular, Vue SPA)** — rejected: the + golden tuple pins React 19.2.8 (Technology-Stack §5.2, §7) and ADR-001 already + records "a Vite admin"; a second frontend ecosystem would split the admin UI + from the public components with no v1 requirement justifying it. +- **Framework-less Vite / hand-rolled DOM** — rejected: the admin IA (§20) needs + a component model for the content, theme, extension and settings screens; + re-implementing state management and composition by hand duplicates what React + already provides, and the golden tuple already pins React. +- **Build-time static-site-generated admin** — rejected: the admin is an + authenticated, connection-backed interface over the canonical database + (ADR-005, ADR-019); build-time HTML would go stale against the canonical DB + and add a rebuild/redeploy cycle per content change. +- **Admin as a separate service or image** — rejected: ADR-001 fixes one + application image containing built admin assets; a separate admin deployment + would break the single-deployable-unit model and add service boundaries with + no v1 benefit. + +## Consequences + +- Positive: one frontend technology — React 19.2.8 — spans the public SSR + pipeline (ADR-007) and the admin SPA, so patterns, components and the golden + tuple pin are shared; Vite gives fast HMR during admin development and a + static build that ships inside the same image (ADR-001); the admin is + explicitly outside the public zero-JavaScript baseline (§19.1), so its + interactivity does not conflict with the public surface; the exact-pinned + toolchain (Vite 8.2.2, React 19.2.8, committed lockfile) keeps admin builds + reproducible under the ADR-026 upgrade lanes. +- Negative: the admin ships client JavaScript and a client runtime to + authenticated admin users; a client-side build step joins the CI pipeline + (the `build-apps` stage globs `apps/**`) and brings its own test tooling + (Vitest, Playwright — Technology-Stack §5.3, §7); two rendering paths — public + SSR and admin SPA — must be maintained, and the client/server contract at + `/api/admin/v1/*` must stay versioned; the admin is an authenticated security + surface (opaque DB-backed sessions, ADR-019) that the security baseline must + keep covering (CSRF, session handling, authz). +- Neutral: Vite is a build-time detail — what ships is static assets served + behind `/admin/*` from the same image; the decision constrains the later + client-side decisions (zero-JS public baseline and the client-island model, + ADR-009 and ADR-010) to the public surface, not the admin. + +## Operational impact + +- `apps/admin` builds with Vite into static assets that are served by the same + application image behind `/admin/*` (Architecture §20); there is no separate + admin service, container or deployment (ADR-001). +- `/admin/*` serves the SPA shell; the SPA reads and writes through the admin + API at `/api/admin/v1/*` (§42), secured by the Fastify lifecycle and opaque + DB-backed sessions (ADR-019). +- The admin build runs in the CI `build-apps` stage — the pnpm `apps/**` glob + picks `apps/admin` up automatically when the app lands — and admin behaviour + is covered by Vitest unit suites and Playwright e2e (Technology-Stack §5.3, + §7). +- Admin traffic is authenticated and internal; the admin is not part of the + public zero-JavaScript surface (§19.1) — client JavaScript is expected and + required there — so no byte-budget applies to admin assets. +- The admin is not implemented yet — today rollback is reverting this + documentation commit; once the admin lands, rollback is redeploying the + previous image (built admin assets live inside the image, so there is no + separate asset deploy to coordinate). + +## Revisit trigger + +- Revisit when the admin grows a requirement the React/Vite SPA model cannot + meet — for example a genuinely different client architecture or offline + capability — with evidence per the architecture review gates (ADR index + section 68). +- Revisit when the golden tuple moves React or Vite to a new major — an + ADR-recorded upgrade program item (Technology-Stack §7/§8, ADR-026 lane) — or + changes the React pin's support class. +- Revisit when the public client-side decisions (ADR-009, ADR-010) change the + boundary between the public surface and the admin, or the admin API contract + at `/api/admin/v1/*` needs a different shape. +- Revisit when the single-image model (ADR-001) changes such that built admin + assets must be served or deployed separately. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..82259bb --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,65 @@ +# ADR index (§70) + +Repository copy of the ADR index from the wiki ADR-Index page (§70): the +canonical list of architectural decisions, committed or planned. Every ADR +committed to `docs/adr/` must record a decision that matches its row in +section 70; the wiki page remains the canonical index. + +| ADR | Decision | +|---|---| +| ADR-001 | Modular monolith | +| ADR-002 | Node.js 24 LTS runtime | +| ADR-003 | TypeScript 6.0.3 pending TS7.1 ecosystem review | +| ADR-004 | Fastify 5 HTTP runtime | +| ADR-005 | PostgreSQL 18 sole canonical DB | +| ADR-006 | Kysely contained inside DB adapter | +| ADR-007 | React SSR for public rendering | +| ADR-008 | React/Vite admin | +| ADR-009 | Zero-JS public baseline | +| ADR-010 | Client-island model for optional public interactivity | +| ADR-011 | JSON Schema + TypeBox + Ajv validation | +| ADR-012 | EPPP Extension API hides framework internals | +| ADR-013 | Node/Amber is a theme extension | +| ADR-014 | Blog is a first-party content extension | +| ADR-015 | Versioned block documents | +| ADR-016 | Versioned page composition | +| ADR-017 | Extension-owned migrations/tables | +| ADR-018 | Docker Compose primary installation | +| ADR-019 | Opaque DB-backed admin sessions | +| ADR-020 | Separate anonymous preference identity | +| ADR-021 | Local media storage through storage port | +| ADR-022 | PostgreSQL jobs before external broker | +| ADR-023 | No Redis initially | +| ADR-024 | No microservices initially | +| ADR-025 | Trusted build-time executable extensions in v1 | +| ADR-026 | Exact dependency pinning + controlled upgrade lanes | +| ADR-027 (v1.1) | `core.markdown` block restores Markdown authoring inside the block model | +| ADR-028 (v1.1) | Hardened outbound fetch as a core service; extensions never fetch directly | +| ADR-029 (v1.1) | Embed provider allowlist enforced in renderer and generated CSP | +| ADR-030 (v1.1) | Native server-side SVG charts and diagrams with an accessibility contract | +| ADR-031 (v1.1) | Theme renaming replaces trademark references | +| ADR-032 (v1.1) | Day-one byte budgets; latency targets from measurement | + +Every ADR contains: Context, Decision, Alternatives, Consequences, Operational +impact, Revisit trigger (§46 E01-S01). + +## Architectural fitness tests (§71) + +- **Add Ledger/Paper:** create `theme-paper` extension → register + manifest/tokens/assets → optionally override renderer slots → tests → + include in build. Failure = editing Home/Post domain, core DB, auth, or + `if (theme === "paper")` in core. +- **Add Reading:** create `org.eppp.reading` → migrations → public route → + admin contribution → Home section → settings → job(s) → content/block + contributions. Failure = core learning seam/half-life/bookmark/link-health + semantics. + +## Architecture review gates (§68) + +Gate A (end Sprint 1): publish a real post without core becoming +blog/Amber-specific. Gate B (end Sprint 3): add a Home feature as an extension +with no core edits. Gate C (end Sprint 4): a radically different theme runs +without changing content/business logic. Gate D (end Sprint 5): visitor +preference persists without coupling to auth or theme storage. Gate E (before +public SDK): Extension API v1 proven enough to maintain. Gate F (Reading): +Reading owns its whole domain without `if (readingEnabled)` in core. -- 2.54.0