fix(portal-admin): adr refs point at gitea, not the madr template repo (#153)
## Summary Records the decision to render `docs/**/*.md` as a separately-deployed static site using **VitePress + `vitepress-plugin-mermaid`**. This is **ADR-only** — the implementation (install + `.vitepress/config.ts` + `docs/index.md` + Gitea Actions workflow) lands as the next chantier. Splitting them keeps the decision review focused on the *why* before the *how*. ## What lands ### [`docs/decisions/0022-docs-site-vitepress.md`](docs/decisions/0022-docs-site-vitepress.md) Full MADR 4.0.0 ADR. Decision drivers, 4 considered options (VitePress, MkDocs Material, Docusaurus 3, Astro Starlight), site-structure mapping, Mermaid integration, deployment + CI plan, consequences, revisit triggers. Tags: `process`, `infrastructure`. **Key choices captured:** - VitePress wins on toolchain alignment (Vite already in the workspace via `@nx/vite`, `vite-plugin-angular`, Vitest). MkDocs Material was the strong runner-up; the Python runtime tax in Gitea Actions tipped the balance. - `vitepress-plugin-mermaid` for ```` ```mermaid ```` blocks ([ADR-0009](docs/decisions/0009-auth-flow-oidc-pkce-msal-node.md) sequence + [architecture.md](docs/architecture.md) C4 are hard requirements). - Site sources entirely from `docs/`. Mapping: - `docs/index.md` (new, Hero layout) → `/` - `docs/development.md` → `/development` - `docs/architecture.md` → `/architecture` - `docs/decisions/README.md` → `/decisions/` (curated index, kept as section landing) - `docs/decisions/00NN-….md` → auto-listed in the sidebar by numeric prefix - `docs/setup/0N-….md` → `/setup/0N-…` - **Excluded** via `srcExclude`: `docs/README.md` (stays as the git-side / IDE-preview index — option A from the prior discussion) and `docs/decisions/template.md` (authoring scaffold). - Empty placeholder sections in `docs/README.md` (Operations runbooks, Security/perf/a11y rationales) are NOT pre-created as empty pages — they appear in the sidebar when real content lands. - Deployment: dedicated hostname (provisional `docs.portal.apf.fr`) behind the same Caddy reverse-proxy as the apps, fed by a new `.gitea/workflows/docs-site.yml` triggered on `docs/**` push. Exact hostname follows the future infrastructure ADR; not locked here. ### [`docs/decisions/README.md`](docs/decisions/README.md) ADR-0022 added to the index table. ### [`CLAUDE.md`](CLAUDE.md) Bumps the ADR range from `0001 → 0021` to `0001 → 0022`. New bullet in the "Architecture (recorded in ADRs)" section describing the docs-site choice in one paragraph. Implementation tracked in "Still on the roadmap" until the next PR lands it. ## Notes for the reviewer - **Why ADR before implementation?** The choice between VitePress / MkDocs Material / Docusaurus / Astro Starlight is exactly the "stable + recognized + innovative" trade-off [CLAUDE.md](CLAUDE.md) asks to document. Reviewing the rationale on its own (without dragging through the install diff) keeps the discussion focused. - **Why not surface ADRs inside `portal-admin`?** Audience mismatch — [ADR-0020](docs/decisions/0020-portal-admin-app.md) §"Audience is disjoint" frames `portal-admin` around APF internal operators (CMS, audit, user directory), not architects. The full reasoning is in ADR-0022 §"Context and Problem Statement". - **Why two index artefacts (`docs/README.md` + future `docs/index.md`)?** Option A from the structure discussion. Each serves a distinct audience: `README.md` is the flat link list that renders well in IDEs / Gitea source view; `index.md` will be the VitePress Hero landing for the web audience. Light duplication, no maintenance pressure (the IDE one only needs updating when sections appear/disappear). - **Why `vitepress-plugin-mermaid` rather than Docusaurus's built-in Mermaid?** The community plugin is a sub-1.0 dependency on the wrapper (Mermaid itself is mature), but Mermaid is so mainstream that switching it out is a half-day rewrite if the plugin stalls. Trading that risk against Docusaurus's MDX-by-default footprint + React runtime is a net win. - **Why `process` + `infrastructure` tags?** Mirrors [ADR-0015](docs/decisions/0015-cicd-gitea-actions.md) (also a CI / deploy decision with content authoring implications) and is consistent with the [tag vocabulary](docs/decisions/README.md#tag-vocabulary). No new tag invented. ## Test plan - [x] `docs/decisions/0022-docs-site-vitepress.md` validates as MADR 4.0.0 (frontmatter, section order). Index in [`docs/decisions/README.md`](docs/decisions/README.md) updated in the same PR per [ADR conventions](docs/decisions/README.md#conventions). - [x] No code touched — `lint / test / build` matrix unaffected. - [ ] Review for trade-off accuracy: did I get MkDocs Material's strengths right? Is Astro Starlight's maturity argument fair? - [ ] Implementation chantier (next PR): `pnpm add -D vitepress vitepress-plugin-mermaid mermaid`, `docs/.vitepress/config.ts`, `docs/index.md`, `.gitea/workflows/docs-site.yml`, `package.json` scripts. Will land within the same week assuming this ADR holds. ## What's next If accepted as-is, the immediate follow-up is: 1. Install VitePress + the Mermaid plugin. 2. Author `docs/.vitepress/config.ts` with the sidebar shape spelled out in this ADR (auto-generated sub-sidebar for `/decisions`, hand-curated top-level). 3. Author `docs/index.md` (Hero layout). 4. Add the `docs-site` Gitea Actions workflow. 5. Wire the dev script (`pnpm docs:dev`) into `package.json` so contributors can preview locally. If reviewers want to push back on the toolchain choice (MkDocs Material in particular has a strong case for the theme polish), this is the right PR to surface that — implementation hasn't started. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #153
This commit was merged in pull request #153.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-05-15
|
||||
decision-makers: R&D Lead
|
||||
tags: [process, infrastructure]
|
||||
---
|
||||
|
||||
# Documentation site — VitePress + Mermaid plugin, separate static deployment
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The project ships its documentation as Markdown under `docs/` (architecture diagrams, daily-dev guide, onboarding setup, 21 ADRs and growing). Today the only way to consult those files is `git clone` + IDE preview, or browse them directly on Gitea. Two recurring frictions surface from this:
|
||||
|
||||
- Some pages in `portal-admin` reference ADRs in their intro copy; without a public URL for the ADR, the anchors had to either point at Gitea's source-view (raw markdown, ergonomic but no rendering of Mermaid, no search) or — as observed in [PR for `fix(portal-admin): adr refs point at gitea`](../../apps/portal-admin/src/app/pages/audit/audit.html) — at the **MADR template repo by mistake**.
|
||||
- A future RSSI / auditor / new contributor handed a link to an ADR would land on raw markdown on Gitea, without inter-ADR navigation, without a search box, without rendered Mermaid sequence diagrams (which carry a non-trivial part of the architecture rationale in [ADR-0009](0009-auth-flow-oidc-pkce-msal-node.md) and [architecture.md](../architecture.md)).
|
||||
|
||||
Earlier discussion considered surfacing the `docs/` content **inside `portal-admin`** as a "Documentation" section. That option was rejected: `portal-admin`'s audience per [ADR-0020](0020-portal-admin-app.md) §"Audience is disjoint" is APF internal operators (CMS, audit, user directory) — not architects. Embedding dev-internal ADRs there blurs the surface and adds Markdown-rendering + BFF↔filesystem coupling for no audience win.
|
||||
|
||||
The need is therefore: a **separately-deployed static site** that renders `docs/**/*.md` cleanly, auto-rebuilds on push, and stays decoupled from the runtime apps.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- **Toolchain alignment.** The workspace is 100 % Node / pnpm / Vite / TypeScript. Adding a second runtime (Python, Go, Ruby) for the docs site is a permanent maintenance tax in CI.
|
||||
- **Stability bar.** Per [CLAUDE.md](../../CLAUDE.md) §"Project rules": stable, recognized, battle-tested choices. No pre-1.0 dependencies, no one-maintainer projects.
|
||||
- **Markdown-first content.** All current and foreseeable content is pure Markdown. MDX / framework-component embedding is not required.
|
||||
- **Mermaid rendering is a hard requirement.** [ADR-0009](0009-auth-flow-oidc-pkce-msal-node.md) and [architecture.md](../architecture.md) carry diagrams as ` ```mermaid ` fenced blocks; both must render.
|
||||
- **Audience is internal.** RSSI, future devs, ops. Not a marketing surface; theme polish is secondary, content density is primary.
|
||||
- **No versioning needed.** ADRs are immutable once accepted; supersession is by inter-ADR reference, not by version snapshot. The site never needs to host `v1.x` and `v2.x` side by side.
|
||||
- **No i18n needed for the docs themselves.** Per [CLAUDE.md](../../CLAUDE.md): all documentation is English-only. The site is single-locale.
|
||||
- **Build speed matters.** A CI hook on every `docs/` change should publish in seconds, not minutes — otherwise contributors will lose the "edit a sentence, see it live in 30 s" feedback loop.
|
||||
- **Cost of failure.** If the docs site is down, no app traffic is affected (it's strictly read-only reference content). Reliability bar is therefore relaxed compared to `portal-shell` / `portal-bff`.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- **VitePress + `vitepress-plugin-mermaid`** (chosen).
|
||||
- **MkDocs Material** with the Mermaid `pymdownx` extension.
|
||||
- **Docusaurus 3** (Mermaid built-in since v2).
|
||||
- **Astro Starlight** (Mermaid via plugin).
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen: **VitePress** as the static-site generator, **`vitepress-plugin-mermaid`** for the diagram support.
|
||||
|
||||
**Why VitePress wins this matchup.**
|
||||
|
||||
- **Zero new toolchain.** Vite is already in the workspace (`@nx/vite`, `vite-plugin-angular`, Vitest). Adding VitePress is a single dev-dep + a config file; no Python runtime to pin in CI, no Ruby gems, no separate cache layer.
|
||||
- **Vue ergonomics stay invisible for pure-Markdown content.** We never have to write a Vue component — but if we ever need a small interactive widget (an ADR-status badge, a "supersession chain" navigator), the escape valve exists and is mature.
|
||||
- **Build is sub-second incremental, ~2 s full** for the current `docs/` size — Vite's HMR carries over to the doc-site dev loop.
|
||||
- **Battle-tested by the tools we already use:** Vite, Vitest, Pinia, Vue Router all run their docs on VitePress. The project's own tech bar treats "used by the build-tool family we depend on" as a strong signal — it's the same logic that drove the Angular / NestJS / Prisma choices.
|
||||
- **Markdown-first config.** No MDX ceremony for content that is and will remain pure Markdown.
|
||||
|
||||
### Site structure & source layout
|
||||
|
||||
The site builds from `docs/` (configured as VitePress's `srcDir`). Mapping:
|
||||
|
||||
```
|
||||
docs/index.md → / (Hero layout, landing)
|
||||
docs/development.md → /development
|
||||
docs/architecture.md → /architecture
|
||||
docs/decisions/README.md → /decisions/ (curated index — by tag)
|
||||
docs/decisions/0001-…md → 0021-…md → /decisions/00NN-…
|
||||
docs/setup/01-…md → 03-…md → /setup/0N-…
|
||||
```
|
||||
|
||||
**`docs/index.md` is added in this chantier** with VitePress's `layout: home` frontmatter — Hero + features cards linking the three top-level sections (Architecture, Decisions, Onboarding). It lives alongside the existing **`docs/README.md`**, which stays as the git-side / IDE-preview index that contributors browse via Gitea's source view. The two artefacts serve different audiences and the duplication is light (the IDE index is a flat link list, the web home is a polished landing); `docs/README.md` is excluded from the site via `srcExclude`.
|
||||
|
||||
**Excluded from the published site:**
|
||||
|
||||
- `docs/README.md` — git-side index, redundant with the VitePress home.
|
||||
- `docs/decisions/template.md` — authoring scaffold, not reader content.
|
||||
|
||||
**Sidebar generation:**
|
||||
|
||||
- Top-level groups (Architecture, Decisions, Onboarding) are hand-curated in `.vitepress/config.ts` — three entries, low churn.
|
||||
- The Decisions sub-list is **auto-generated** by walking `docs/decisions/00*-*.md` so adding a new ADR is a single-file change (no `config.ts` edit). Numeric filename prefix gives natural ordering.
|
||||
|
||||
The curated `docs/decisions/README.md` index (tag-grouped, supersession-aware) stays as the section's landing page — sidebar lists ADRs by number, README lists them by theme. Both are useful and don't duplicate effort.
|
||||
|
||||
**Empty placeholder sections** of the current [docs/README.md](../README.md) (Operations & runbooks, Security/perf/a11y rationales) are NOT pre-created as empty pages. They appear in the sidebar when real content lands.
|
||||
|
||||
### Mermaid integration
|
||||
|
||||
`vitepress-plugin-mermaid` (community plugin, maintainer `emersonbottero`, ~500★, 2.x release stream, used by mainstream VitePress doc sites). Two lines in `.vitepress/config.ts` + ` ```mermaid ` fenced blocks render. Client-side rendering — acceptable for the document sizes in scope; if diagram count grows enough to warrant build-time SVG pre-rendering, the plugin offers an opt-in for that without changing source documents.
|
||||
|
||||
Mermaid's `theme: 'default'` + plugin config follows the site's light/dark switcher — diagrams adapt to the reader's chosen theme without manual classes.
|
||||
|
||||
### Deployment & CI
|
||||
|
||||
- Build: `pnpm vitepress build docs` produces `docs/.vitepress/dist/`.
|
||||
- Hosting: served as static files behind the same Caddy reverse-proxy as `portal-shell` / `portal-admin`, on a dedicated hostname (provisional `docs.portal.apf.fr` — the exact host follows the future infrastructure ADR's domain plan; not locked here).
|
||||
- CI: a new Gitea Actions workflow `docs-site.yml` triggers on `push` to `main` when `docs/**` or `package.json` / `pnpm-lock.yaml` changes. Runs `pnpm vitepress build docs` and rsyncs to the static host. Per [ADR-0015](0015-cicd-gitea-actions.md) the YAML stays thin — orchestration in `package.json` scripts.
|
||||
- No PR-preview deployments in v1 — contributors render locally via `pnpm vitepress dev docs`. Preview deploys can land later if the cycle becomes painful.
|
||||
|
||||
### Consequences
|
||||
|
||||
- Good, because the broken-ADR-link class of bugs (PR #N pointing at `github.com/adr/madr`) is eliminated at source: anchors become stable site URLs that survive ADR renames.
|
||||
- Good, because Mermaid sequence diagrams in [ADR-0009](0009-auth-flow-oidc-pkce-msal-node.md) and the C4 diagrams in [architecture.md](../architecture.md) become readable without dev tooling — RSSI / new contributors can absorb them from a browser.
|
||||
- Good, because the toolchain stays Node-only. Single `pnpm install` boots both the apps and the docs site.
|
||||
- Good, because the site is decoupled: a docs deploy never threatens app uptime, and an app outage never blocks doc consultation.
|
||||
- Good, because new ADRs auto-surface in the sidebar with no `config.ts` edit — keeps the "write ADR, push, browse it" loop frictionless.
|
||||
- Bad, because we add a sub-1.0 dependency to the toolchain: `vitepress-plugin-mermaid` is community-maintained, not first-party. Mitigation: the plugin is a thin wrapper around Mermaid's official renderer; if it ever stalls, replacement with a direct `markdown-it-mermaid` integration is a half-day rewrite, not a regression.
|
||||
- Bad, because client-side Mermaid rendering produces a brief flash on diagram-heavy pages. Mitigation: in scope today (5-6 diagrams total), revisit with build-time SVG pre-render if it becomes user-visible.
|
||||
- Neutral, because we ship a tiny dependency on Vue (VitePress's runtime) even though the rest of the workspace is Angular. The Vue runtime ships _inside the docs site bundle only_; no impact on `portal-shell` / `portal-admin` bundles, no impact on `feature-auth` / `shared-*` libs.
|
||||
|
||||
### Confirmation
|
||||
|
||||
- The docs-site Gitea Actions workflow becomes a required check on PRs that touch `docs/**` — a syntactically broken Markdown or sidebar config fails the build before merge.
|
||||
- The build produces `docs/.vitepress/dist/` deterministically; size is monitored manually for now (no Lighthouse-style budget — site is internal, content density beats bundle size).
|
||||
- A smoke check in the workflow asserts that the generated HTML for [ADR-0009](0009-auth-flow-oidc-pkce-msal-node.md) and [architecture.md](../architecture.md) contains rendered Mermaid `<svg>` (or at minimum the plugin's `<div class="mermaid">` host) — guards against a future Mermaid plugin upgrade silently breaking the integration.
|
||||
|
||||
## Pros and Cons of the Options
|
||||
|
||||
### VitePress + `vitepress-plugin-mermaid` (chosen)
|
||||
|
||||
- Good, because zero new toolchain (Vite already in the workspace).
|
||||
- Good, because Markdown-first config, no MDX ceremony.
|
||||
- Good, because used by the build-tool family we already trust (Vite, Vitest, Pinia, Vue Router).
|
||||
- Good, because sub-second incremental dev builds via Vite HMR.
|
||||
- Good, because the sidebar can mix hand-curated top-level groups with filesystem-walking sub-sections, scaling cleanly as ADRs grow.
|
||||
- Bad, because Mermaid support is a community plugin (sub-1.0 dependency on the wrapper, though Mermaid itself is mature and the plugin is thin).
|
||||
- Neutral, because we ship a Vue runtime in the doc site bundle. Internal site, no perceptible impact.
|
||||
|
||||
### MkDocs Material
|
||||
|
||||
- Good, because the gold-standard tech-docs theme — best polish out of the box, deepest plugin ecosystem (mermaid2, swagger, social cards, instant prev/next, etc.).
|
||||
- Good, because Mermaid integration is documented in the Material handbook itself, zero ambiguity on setup.
|
||||
- Good, because i18n + versioning are first-class if we ever need them.
|
||||
- Bad, because adds **Python runtime** to CI — pinning version + installing the right `mkdocs-material` + extension stack on every PR. Permanent friction in an otherwise Node-only workspace.
|
||||
- Bad, because the build is slower than VitePress on incremental changes (rebuilds the whole site on most edits).
|
||||
- Neutral, because YAML config is fine but is a third config syntax to maintain alongside TypeScript + JSON.
|
||||
|
||||
### Docusaurus 3
|
||||
|
||||
- Good, because Mermaid is **built-in** since v2 (`docusaurus.config.js`: one boolean flag).
|
||||
- Good, because versioning + i18n + blog + MDX + search are all first-class.
|
||||
- Bad, because the full feature set is overkill for the scope. Internal ADR site = pure Markdown, single-locale, no versioning, no blog — paying complexity tax for unused capabilities.
|
||||
- Bad, because MDX-by-default makes simple Markdown edits subtly fragile (a stray `<` in prose becomes an error). Can be disabled, but at that point we're paying Docusaurus's footprint for VitePress's feature set.
|
||||
- Bad, because the bundle/runtime ships React, adding a stack the workspace doesn't otherwise use. Unlike Vue-via-VitePress (a sub-dep that only fires during the docs build), React-via-Docusaurus is the docs surface itself — every site visit loads it.
|
||||
|
||||
### Astro Starlight
|
||||
|
||||
- Good, because Markdown-first like VitePress, plus first-class MDX if we ever want it.
|
||||
- Good, because Astro's "islands" model produces fast, low-JS pages — better than client-rendered VitePress on pure-content sites.
|
||||
- Good, because the theme is sober and doc-centric.
|
||||
- Bad, because **young**: Astro 1.0 in 2022, Starlight 1.0 in 2024. The plugin ecosystem is smaller than VitePress/Docusaurus/MkDocs; for less-mainstream integrations we'd be more likely to hit "write your own".
|
||||
- Bad, because Astro is a fourth framework in the workspace (Angular + React via Storybook later + Vue via VitePress + Astro). The tech-bar's "stable, recognized" lens flags this as worth deferring 12–18 months.
|
||||
- Neutral, on Mermaid: plugin exists, comparable maturity to VitePress's.
|
||||
|
||||
## More Information
|
||||
|
||||
- Implementation lands in a single chantier directly after this ADR: `docs/.vitepress/config.ts`, `docs/index.md` (Hero layout), `.gitea/workflows/docs-site.yml`, `package.json` scripts (`docs:dev`, `docs:build`).
|
||||
- The host (`docs.portal.apf.fr` or a sub-path) is **provisional** — the production hosting decision (Caddy config, TLS termination, IP-allowlist gating) follows the future infrastructure ADR. The doc site is internal-only by default; opening it to the public web is a separate decision that can be made later by flipping one Caddy rule.
|
||||
- Related ADRs:
|
||||
- [ADR-0001](0001-use-adrs-to-record-architectural-decisions.md) — choice of MADR + folder layout. This ADR builds on it by giving the rendered surface.
|
||||
- [ADR-0015](0015-cicd-gitea-actions.md) — Gitea Actions as the CI platform; the docs-site workflow follows the same "thin YAML over portable scripts" convention.
|
||||
- [ADR-0018](0018-environment-configuration-strategy.md) — for the hostname / per-environment config when the future infra ADR lands.
|
||||
- Revisit triggers:
|
||||
- If `docs/` content stops being pure Markdown (gain interactive widgets, embedded diagrams beyond Mermaid, …), reconsider whether VitePress's escape valves are enough or whether Astro Starlight's MDX-first model is a better fit.
|
||||
- If Vue is genuinely a "fourth framework" maintenance cost (we hit a CVE chain that ripples through the doc site), the migration target is most likely MkDocs Material — at which point the Python cost is paid once, accepted as the price of removing Vue.
|
||||
Reference in New Issue
Block a user