diff --git a/.github/workflows/a11y-check.yml b/.github/workflows/a11y-check.yml index d135ec6..9805e08 100644 --- a/.github/workflows/a11y-check.yml +++ b/.github/workflows/a11y-check.yml @@ -32,7 +32,7 @@ jobs: - name: Diagram pipeline unit tests working-directory: site - run: node --test test/diagram.test.mjs test/replace-plantuml.test.mjs test/schema-versions.test.mjs test/build-sports.test.mjs + run: node --test test/diagram.test.mjs test/replace-plantuml.test.mjs test/schema-versions.test.mjs test/build-sports.test.mjs test/mermaid-marked.test.mjs - name: Build site working-directory: site @@ -70,6 +70,7 @@ jobs: http://localhost:4321/learn/ \ http://localhost:4321/use/ \ http://localhost:4321/build/ \ + http://localhost:4321/build/architecture/ \ http://localhost:4321/docs/ \ http://localhost:4321/docs/spec/ \ http://localhost:4321/docs/schema/ \ diff --git a/docs/superpowers/plans/2026-08-28-siteD-arc42.md b/docs/superpowers/plans/2026-08-28-siteD-arc42.md new file mode 100644 index 0000000..5cbed4c --- /dev/null +++ b/docs/superpowers/plans/2026-08-28-siteD-arc42.md @@ -0,0 +1,1173 @@ +# Plan D — arc42 Architecture Integration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to execute this plan. Work one task at a time, dispatch a subagent per task, and stop at each review checkpoint. Do not batch tasks. + +## Goal + +Ship Plan D of 4 (the final plan) of the site redesign. Pull the ocf-validator's +**arc42 architecture docs** into the Astro site at build time and publish them +under **Build → Architecture**, with their Mermaid diagrams rendered. + +The arc42 docs live in `opencoachingformat/ocf-validator` at `docs/arc42/*.md` +— 13 Markdown files: a `README.md` and twelve numbered sections +(`01-introduction-and-goals` … `12-glossary`). Exactly three of them +(`03-system-scope-context`, `06-runtime-view`, `10-quality-requirements`) +contain fenced ` ```mermaid ` code blocks; there is **no PlantUML** anywhere in +arc42. + +A build step (`site/scripts/build-arc42.mjs`) fetches those 13 files from a +**pinned validator commit** via jsDelivr's GitHub-file CDN (the same +read-remote-at-build → write-`src/generated/` pattern as `build-adoc.mjs` +already uses for `error-codes.json`), renders each Markdown file to HTML with +`marked`, and — crucially — converts each fenced ` ```mermaid ` block into a +`
…` element (instead of the default +`
`), so that a small client-side module
+script (`mermaid@11` ESM from jsDelivr) renders them **in the browser**. This
+is the design's 2026-08-28 revision: **Mermaid renders client-side**;
+`@mermaid-js/mermaid-cli`/`mmdc` is explicitly **not** used (it downloads a
+headless Chromium and failed to install in this environment). PlantUML stays
+build-time (Plan A), but arc42 has no PlantUML so this plan touches only Mermaid.
+
+Two new Astro routes serve the docs: `build/architecture/index.astro` (an
+overview + table of contents) and `build/architecture/[section].astro` (a
+`getStaticPaths` template that renders each section's generated HTML via
+`set:html`). The section pages include the client-side mermaid module script;
+the Build hub gains an **Architecture** card linking `/build/architecture/`.
+
+Plan D changes **no schema/validator code** — it consumes the arc42 Markdown at
+a pinned commit. It adds one runtime-free dependency (`marked`) to the site.
+
+## Architecture
+
+**Verified facts (2026-08-28):**
+
+- arc42 lives in `opencoachingformat/ocf-validator` at `docs/arc42/`, 13
+ Markdown files: `README.md`, `01-introduction-and-goals.md`,
+ `02-architecture-constraints.md`, `03-system-scope-context.md`,
+ `04-solution-strategy.md`, `05-building-block-view.md`, `06-runtime-view.md`,
+ `07-deployment-view.md`, `08-crosscutting-concepts.md`,
+ `09-architecture-decisions.md`, `10-quality-requirements.md`,
+ `11-risks-and-technical-debt.md`, `12-glossary.md`. Only
+ `03-system-scope-context`, `06-runtime-view`, and `10-quality-requirements`
+ contain ` ```mermaid ` fenced blocks. No PlantUML anywhere in arc42.
+- The validator repo's `main` commit that carries the current arc42 is
+ `07361d99f2433f2c715486e0894da5594160a218`. This plan pins arc42 to that
+ commit via a **new, separate** `OCF_ARC42_COMMIT` constant in
+ `site/src/lib/validator-version.mjs` (kept distinct from
+ `OCF_VALIDATOR_COMMIT`, which coincidentally holds the same value today, so
+ that arc42 docs and the validator bundle can be re-pinned independently later).
+- jsDelivr serves individual repo files at
+ `https://cdn.jsdelivr.net/gh/opencoachingformat/ocf-validator@/docs/arc42/.md`.
+ This mirrors how `OCF_ERROR_CODES_URL` is built from `JSDELIVR_GH_BASE` in
+ `validator-version.mjs`. arc42 is **not** part of the published npm package,
+ so — like `error-codes.json` — it must come from a pinned git commit, not the
+ npm CDN.
+- `mermaid@11` ESM loads at
+ `https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs` (HTTP 200,
+ `Access-Control-Allow-Origin: *`). Client-side rendering: emit
+ `SOURCE
` and a module `
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+3. Build and assert every section page resolves, at least one carries a
+ `');process.exit(1)}
+ if(scriptPages!==mermaidPages){console.error('mermaid pages without script:',mermaidPages-scriptPages);process.exit(1)}
+ console.log('arc42 sections OK:',idx.length,'pages,',mermaidPages,'with mermaid + module script');
+ "
+ ```
+ Expect success (≥1 section with `
+
+
+
+
+
+ ```
+ Insert an **Architecture** card between the JSON Schema card and the Error
+ codes card so the grid reads:
+ ```astro
+
+
+
+
+
+
+
+ ```
+ Leave the frontmatter, hero section, and `
diff --git a/site/src/pages/build/architecture/index.astro b/site/src/pages/build/architecture/index.astro
new file mode 100644
index 0000000..299915a
--- /dev/null
+++ b/site/src/pages/build/architecture/index.astro
@@ -0,0 +1,51 @@
+---
+import Base from '../../../layouts/Base.astro';
+import Card from '../../../components/Card.astro';
+import arc42 from '../../../generated/arc42/index.json';
+---
+
+
+ Architecture (arc42)
+
+ How the reference validator is built, documented with the
+ arc42 template. Pulled from the
+ ocf-validator
+ repository at a pinned commit and rendered here — diagrams included.
+
+
+
+
+
+ {arc42.map((entry) => (
+
+ ))}
+
+
+
+
+
+
+
diff --git a/site/src/pages/build/index.astro b/site/src/pages/build/index.astro
index b9de147..962d3ca 100644
--- a/site/src/pages/build/index.astro
+++ b/site/src/pages/build/index.astro
@@ -26,6 +26,11 @@ import Card from '../../components/Card.astro';
description="The machine-readable JSON Schema you validate documents against."
href="/docs/schema/"
/>
+
", () => {
+ const md = "# Title\n\n```mermaid\ngraph TD\n A --> B\n```\n";
+ const html = mermaidAwareMarked(md);
+ assert.match(html, //);
+ // The diagram source is preserved verbatim inside the pre (NOT wrapped in
+ // , NOT given a language-* class).
+ assert.match(html, /graph TD/);
+ assert.match(html, /A --> B/);
+ assert.doesNotMatch(html, /language-mermaid/);
+ // The block holds no nested element.
+ const block = html.slice(html.indexOf(''));
+ assert.doesNotMatch(block.slice(0, block.indexOf('')), / {
+ // mermaid parses raw source; --> must stay literal, not >/&.
+ const md = "```mermaid\nA --> B\nC -->|x & y| D\n```\n";
+ const html = mermaidAwareMarked(md);
+ assert.match(html, /A --> B/);
+ assert.match(html, /-->\|x & y\| D/);
+ assert.doesNotMatch(html, />/);
+ assert.doesNotMatch(html, /&/);
+});
+
+test("mermaidAwareMarked: renders ordinary markdown normally", () => {
+ const html = mermaidAwareMarked("# Heading\n\nSome **bold** text.\n");
+ assert.match(html, /]/);
+ assert.match(html, /bold<\/strong>/);
+});
+
+test("mermaidAwareMarked: a non-mermaid code fence stays a normal block, escaped", () => {
+ const md = "```json\n{ \"a\": 1 < 2 }\n```\n";
+ const html = mermaidAwareMarked(md);
+ assert.match(html, /]*>/);
+ assert.doesNotMatch(html, //);
+ // Ordinary code fences keep marked's default HTML escaping.
+ assert.match(html, /1 < 2/);
+});
+
+test("mermaidAwareMarked: handles multiple mermaid blocks in one document", () => {
+ const md = "```mermaid\ngraph LR\nA-->B\n```\n\ntext\n\n```mermaid\nsequenceDiagram\nA->>B: hi\n```\n";
+ const html = mermaidAwareMarked(md);
+ const count = (html.match(//g) || []).length;
+ assert.equal(count, 2);
+ assert.match(html, /sequenceDiagram/);
+});
+
+test("buildArc42Index: derives slug + title, README first, then numeric order", () => {
+ const files = [
+ "12-glossary.md",
+ "README.md",
+ "01-introduction-and-goals.md",
+ "06-runtime-view.md",
+ ];
+ const idx = buildArc42Index(files);
+ assert.deepEqual(idx.map((e) => e.slug), [
+ "readme",
+ "01-introduction-and-goals",
+ "06-runtime-view",
+ "12-glossary",
+ ]);
+ const readme = idx.find((e) => e.slug === "readme");
+ assert.equal(readme.title, "Overview");
+ const intro = idx.find((e) => e.slug === "01-introduction-and-goals");
+ assert.equal(intro.title, "1. Introduction and Goals");
+ const runtime = idx.find((e) => e.slug === "06-runtime-view");
+ assert.equal(runtime.title, "6. Runtime View");
+ assert.equal(runtime.file, "06-runtime-view.md");
+});
+
+test("buildArc42Index: ignores non-markdown files", () => {
+ const idx = buildArc42Index(["README.md", "images", "notes.txt"]);
+ assert.deepEqual(idx.map((e) => e.slug), ["readme"]);
+});