From e9bd7e5fa937b3700ceed80752cc3d9285e02ef5 Mon Sep 17 00:00:00 2001 From: Oliver-Marcus Eder Date: Fri, 28 Aug 2026 19:43:57 +0200 Subject: [PATCH 1/7] =?UTF-8?q?docs(design):=20site=20redesign=20=E2=80=94?= =?UTF-8?q?=20audience-first=20IA,=20merged=20Try-it,=20Kroki=20diagrams,?= =?UTF-8?q?=20arc42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../specs/2026-08-28-site-redesign-design.md | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-28-site-redesign-design.md diff --git a/docs/superpowers/specs/2026-08-28-site-redesign-design.md b/docs/superpowers/specs/2026-08-28-site-redesign-design.md new file mode 100644 index 0000000..17b69f1 --- /dev/null +++ b/docs/superpowers/specs/2026-08-28-site-redesign-design.md @@ -0,0 +1,168 @@ +# Design: Site Redesign — Audience-First IA, Merged "Try it", Diagram Rendering, arc42 + +| Field | Value | +|----------|---------------------------------------------------------| +| Date | 2026-08-28 | +| Status | Draft (design) | +| Repo | `opencoachingformat/spec` (the `site/` Astro app) | +| Pulls from | `opencoachingformat/ocf-validator` (arc42, at build time) | +| Supersedes | the parked "Strang C" multi-sport redesign (folded in here) | + +## Problem + +The current Astro site is technical-first and undersells OCF: + +- The **homepage** reads like reference docs, not an introduction. A newcomer + ("what is this, what does it do, how is it built?") gets no narrative. +- **No page mentions multi-sport** or the `sport` field, though RFC 0003 shipped + in v1.2.0 — the format looks basketball-only. +- **Playground and validator/renderer are two separate pages** + (`playground.astro`, `playground/renderer.astro`) when they operate on the same + document. +- **Diagrams don't render.** The spec `.adoc` has 4 `[plantuml]` blocks that + produce nothing; the validator's **arc42** (Markdown, using Mermaid + PlantUML) + isn't on the site at all. +- **Rendered diagrams are too small** even where they exist. + +## Goals + +- A **non-technical, informative homepage** for anyone learning about OCF: + what it is, what it does, roughly how it's built — details one level deeper. +- **Audience-first navigation**: Home · Learn · Use · Build. +- **Multi-sport story** front and center, with a dedicated page per sport + (basketball full; others a reserved template + a call to contribute). +- **One "Try it" page** merging validation and rendering, with **large, + full-width diagrams**. +- **Mermaid + PlantUML render** at build time (spec `.adoc` blocks + arc42). +- **arc42 architecture docs** browsable on the site under "Build". + +## Non-Goals + +- Making the renderer itself presentation-quality (tracked separately). The + homepage therefore ships **without** a diagram; a live preview can be added + later once the renderer is good enough — no page rebuild required. +- Self-hosting a Kroki instance now (public `kroki.io` first; the endpoint is a + config constant so it can move later). +- Any schema/validator code change — this is a site-only redesign that *consumes* + the version-aware validator (0.2.0) already shipped. + +--- + +## Architecture + +### Information architecture (audience-first) + +Top-level nav: **Home · Learn · Use · Build** (+ GitHub). + +- **Home** — story scroll, non-technical, **no diagram**. Order: + 1. The problem (diagrams locked in proprietary apps, image-only export) + 2. What OCF is (a readable JSON format for diagrams & animations) + 3. **Multi-sport** (sport cards → sport pages) — *deliberately before* structure + 4. Rough structure (court coordinates · entities & actions · frames/animation) + 5. Where next → Learn / Use / Build +- **Learn** — concepts + **sport pages** (overview + one page per sport). +- **Use** — **"Try it"** (merged playground+validator+renderer) · rulesets · + how to embed. +- **Build** — Spec · Schema · **Architecture (arc42)** · Error Codes · + Ecosystem/repos. + +Existing pages are re-grouped, not discarded: `docs/spec`, `docs/schema`, +`docs/rulesets`, `docs/features`, `examples`, `errors`, `ecosystem` move under +Learn/Use/Build; `playground.astro` + `playground/renderer.astro` are replaced by +one "Try it" page. + +### "Try it" (merged) + +One document, one editor, switchable outputs: + +- Left: JSON editor with an examples dropdown. +- Right: tabs **[Validation] [Diagram]**. +- On the **Diagram** tab the editor collapses to a thin strip so the diagram + takes the **full width**; plus a fullscreen button. Diagrams scale responsively + (replacing today's fixed small size). +- **Validation** integrates the version-aware validator (0.2.0+): a schema-version + badge ("validated against 1.4.0"), a "newer version available" hint driven by + `/schema/versions.json`, the validator loaded at `@latest`, and the + error/warning list with error codes. + +### Diagram rendering pipeline (Kroki) + +A build-time module renders `[plantuml]` and `[mermaid]` sources to **SVG**, +embedded statically into the page (no client JS). Same path serves both the spec +`.adoc`'s 4 PlantUML blocks and the arc42 (Mermaid + PlantUML). The Kroki +endpoint is a **config constant** (default `https://kroki.io`, self-hostable +later). If Kroki is unreachable at build time the build **fails loudly** rather +than silently dropping diagrams. + +### arc42 integration + +A build step pulls the arc42 Markdown from `opencoachingformat/ocf-validator` at +a **pinned commit** (same pattern as `error-codes.json`, pin lives beside the +others in `validator-version.mjs`), renders Markdown→HTML with diagrams via +Kroki, and publishes it under **Build → Architecture**. + +### Sport pages (under Learn) + +- **Overview**: a card per sport with a status badge (full / reserved). +- **Basketball (full)**: vocabulary (actions), rulesets, named positions, + examples — data-driven from `sports/basketball-v1.json`, `positions/*`, + `examples/*`. +- **Reserved sports (soccer/handball/hockey/futsal)**: one shared **template** + component, data-fed from `sports/-v0.0.1.json` — status "reserved", + provisional vocabulary, and a prominent **"Contribute"** block (how to help: + the RFC path, repo link, "sport experts wanted"). No copy-paste; the four share + the template. + +--- + +## Components (site build) + +- `site/src/lib/kroki.mjs` — pure-ish helper: given diagram source + type, + return SVG (fetches Kroki). Endpoint is a constant; errors throw. +- `site/scripts/build-adoc.mjs` — extended to route `[plantuml]`/`[mermaid]` + blocks through `kroki.mjs`. +- `site/scripts/build-arc42.mjs` — new: fetch arc42 markdown at the pinned + commit, render to HTML, run diagrams through Kroki. +- `site/src/lib/validator-version.mjs` — gains a dedicated `OCF_ARC42_COMMIT` + pin (separate from `OCF_VALIDATOR_COMMIT`, since arc42 docs and the validator + bundle can be updated independently). +- `site/src/pages/` — restructured to Home/Learn/Use/Build; new `use/try-it`, + `learn/sports/*`, `build/architecture/*`; nav component updated. +- Sport page template: one component, fed per-sport JSON. + +## Testing + +- **Kroki helper**: unit test with an injected fetch — success returns SVG; + a non-OK response throws (build-fails-loud contract). +- **build-adoc / build-arc42**: run in CI; assert the generated HTML contains + ` Date: Fri, 28 Aug 2026 19:49:42 +0200 Subject: [PATCH 2/7] =?UTF-8?q?docs(design):=20correct=20diagram=20pipelin?= =?UTF-8?q?e=20=E2=80=94=20Kroki(PlantUML)+mmdc(Mermaid)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified public kroki.io renders PlantUML but 500s on Mermaid; spec uses only PlantUML, arc42 uses only Mermaid. Split into two renderers behind one façade. --- .../specs/2026-08-28-site-redesign-design.md | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/docs/superpowers/specs/2026-08-28-site-redesign-design.md b/docs/superpowers/specs/2026-08-28-site-redesign-design.md index 17b69f1..6c63d3b 100644 --- a/docs/superpowers/specs/2026-08-28-site-redesign-design.md +++ b/docs/superpowers/specs/2026-08-28-site-redesign-design.md @@ -85,14 +85,23 @@ One document, one editor, switchable outputs: `/schema/versions.json`, the validator loaded at `@latest`, and the error/warning list with error codes. -### Diagram rendering pipeline (Kroki) +### Diagram rendering pipeline (build-time SVG, two renderers) -A build-time module renders `[plantuml]` and `[mermaid]` sources to **SVG**, -embedded statically into the page (no client JS). Same path serves both the spec -`.adoc`'s 4 PlantUML blocks and the arc42 (Mermaid + PlantUML). The Kroki -endpoint is a **config constant** (default `https://kroki.io`, self-hostable -later). If Kroki is unreachable at build time the build **fails loudly** rather -than silently dropping diagrams. +A build-time module renders diagram sources to **SVG**, embedded statically into +the page (no client JS). **Verified fact (2026-08-28):** the two doc sets use +disjoint diagram languages, and the public `kroki.io` renders PlantUML but +returns HTTP 500 for Mermaid (its Mermaid companion is unavailable). So the +pipeline uses two renderers, one per language: + +- **PlantUML → Kroki** (`https://kroki.io`, POST). Serves the spec `.adoc`'s + 4 PlantUML blocks. Endpoint is a **config constant** (self-hostable later). +- **Mermaid → local `@mermaid-js/mermaid-cli` (`mmdc`)**. Serves the arc42's + 5 Mermaid blocks. No network dependency; runs headless in CI. + +Both go through one façade module (`renderDiagram(type, source) -> svg`) that +dispatches by type. If a renderer fails, the build **fails loudly** rather than +silently dropping diagrams. (A single self-hosted Kroki instance could later +serve both and collapse this back to one renderer — noted in Open Points.) ### arc42 integration @@ -117,8 +126,9 @@ Kroki, and publishes it under **Build → Architecture**. ## Components (site build) -- `site/src/lib/kroki.mjs` — pure-ish helper: given diagram source + type, - return SVG (fetches Kroki). Endpoint is a constant; errors throw. +- `site/src/lib/diagram.mjs` — façade `renderDiagram(type, source) -> svg`: + dispatches `plantuml` → Kroki (fetch), `mermaid` → local `mmdc`. Endpoint/CLI + are constants; any renderer error throws (build-fails-loud). - `site/scripts/build-adoc.mjs` — extended to route `[plantuml]`/`[mermaid]` blocks through `kroki.mjs`. - `site/scripts/build-arc42.mjs` — new: fetch arc42 markdown at the pinned @@ -160,9 +170,11 @@ here is shared across all four. ## Open Points -- **Kroki availability at build time.** Public `kroki.io` is the default and the - build needs network to reach it; a CI without egress would fail the diagram - step. Mitigation path (not built now): self-host Kroki and point the config - constant at it. -- **arc42 diagram dialect coverage.** Confirm Kroki handles every Mermaid/PlantUML - variant the arc42 uses during Plan D; fix any unsupported block then. +- **PlantUML needs network (Kroki) at build time.** Public `kroki.io` is the + default; a CI without egress would fail the PlantUML step. Mitigation (not built + now): self-host Kroki. Mermaid (via local `mmdc`) has no such dependency. +- **`mmdc` runs headless Chromium.** The Mermaid CLI downloads/uses a headless + browser; Plan D must ensure the CI runner has it (puppeteer's bundled Chromium + or a system install) — verified during Plan D. +- **Future consolidation.** A self-hosted Kroki with a working Mermaid companion + could render both languages, collapsing the two renderers back into one. From 319151579f448f92c83beaa1932fdb6e5be78ead Mon Sep 17 00:00:00 2001 From: Oliver-Marcus Eder Date: Fri, 28 Aug 2026 19:51:08 +0200 Subject: [PATCH 3/7] =?UTF-8?q?docs(plan):=20Site=20Plan=20A=20=E2=80=94?= =?UTF-8?q?=20diagram=20pipeline=20(Kroki/PlantUML)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-08-28-siteA-diagram-pipeline.md | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-28-siteA-diagram-pipeline.md diff --git a/docs/superpowers/plans/2026-08-28-siteA-diagram-pipeline.md b/docs/superpowers/plans/2026-08-28-siteA-diagram-pipeline.md new file mode 100644 index 0000000..82f885d --- /dev/null +++ b/docs/superpowers/plans/2026-08-28-siteA-diagram-pipeline.md @@ -0,0 +1,305 @@ +# Site Plan A — Diagram Pipeline (Kroki/PlantUML) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Render the spec page's PlantUML blocks as inline SVG at build time (they currently show as raw `@startuml` text), via a small, tested diagram façade backed by Kroki. + +**Architecture:** A façade `renderDiagram(type, source)` dispatches by type; Plan A implements only the `plantuml` branch (POST to Kroki, returns SVG). `build-adoc.mjs` post-processes the asciidoctor HTML: every `literalblock` whose `
` starts with `@startuml` is replaced by the rendered SVG. Mermaid (arc42) comes in Plan D.
+
+**Tech Stack:** Node (ESM), Astro build, `@asciidoctor/core` (existing), Kroki public API, `node --test`.
+
+**Design ref:** `docs/superpowers/specs/2026-08-28-site-redesign-design.md` (Diagram rendering pipeline).
+
+**Verified facts:** public `kroki.io` returns 200+SVG for PlantUML via POST `text/plain`; the spec `.adoc` produces exactly 2 `literalblock` `
@startuml…
` blocks in `site/src/generated/spec.html`; the spec page imports `spec.html?raw` and renders it via ``. + +--- + +### Task 1: Diagram façade — the `plantuml` branch + +**Files:** +- Create: `site/src/lib/diagram.mjs` +- Create: `site/test/diagram.test.mjs` +- Modify: `site/package.json` (add a `test` script) + +- [ ] **Step 1: Write the failing test (fetch injected — no real network)** + +`site/test/diagram.test.mjs`: + +```js +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { renderDiagram } from "../src/lib/diagram.mjs"; + +test("plantuml: posts source to Kroki and returns the SVG body", async () => { + const calls = []; + const fakeFetch = async (url, opts) => { + calls.push({ url, body: opts.body, method: opts.method }); + return { ok: true, status: 200, text: async () => "ok" }; + }; + const svg = await renderDiagram("plantuml", "@startuml\nA -> B\n@enduml", { fetchImpl: fakeFetch }); + assert.equal(svg, "ok"); + assert.match(calls[0].url, /\/plantuml\/svg$/); + assert.equal(calls[0].method, "POST"); + assert.equal(calls[0].body, "@startuml\nA -> B\n@enduml"); +}); + +test("plantuml: a non-OK Kroki response throws (build-fails-loud)", async () => { + const fakeFetch = async () => ({ ok: false, status: 500, text: async () => "boom" }); + await assert.rejects( + () => renderDiagram("plantuml", "@startuml\n@enduml", { fetchImpl: fakeFetch }), + /kroki.*500/i, + ); +}); + +test("unknown diagram type throws", async () => { + await assert.rejects(() => renderDiagram("nope", "x"), /unsupported diagram type/i); +}); +``` + +- [ ] **Step 2: Run it — expect FAIL (module missing)** + +Run: `cd site && node --test test/diagram.test.mjs` +Expected: FAIL — cannot resolve `../src/lib/diagram.mjs`. + +- [ ] **Step 3: Implement the façade** + +`site/src/lib/diagram.mjs`: + +```js +// Build-time diagram rendering. One façade, dispatch by type. +// PlantUML -> Kroki (public kroki.io renders PlantUML fine). +// Mermaid -> added in Plan D (local mmdc; kroki.io 500s on Mermaid). +export const KROKI_BASE = "https://kroki.io"; + +async function renderPlantumlViaKroki(source, fetchImpl) { + const doFetch = fetchImpl ?? fetch; + const res = await doFetch(`${KROKI_BASE}/plantuml/svg`, { + method: "POST", + headers: { "Content-Type": "text/plain" }, + body: source, + }); + if (!res.ok) { + throw new Error(`Kroki PlantUML render failed: ${res.status}`); + } + return await res.text(); +} + +export async function renderDiagram(type, source, opts = {}) { + if (type === "plantuml") return renderPlantumlViaKroki(source, opts.fetchImpl); + throw new Error(`Unsupported diagram type: ${type}`); +} +``` + +- [ ] **Step 4: Run it — expect PASS** + +Run: `cd site && node --test test/diagram.test.mjs` +Expected: PASS (3 tests). + +- [ ] **Step 5: Add a `test` script to the site package** + +In `site/package.json` scripts, add: +```json + "test:diagram": "node --test test/diagram.test.mjs", +``` +(Keep existing scripts; this gives CI a hook. The repo's existing +`test:renderer-*` scripts stay.) + +- [ ] **Step 6: Commit** + +```bash +git add site/src/lib/diagram.mjs site/test/diagram.test.mjs site/package.json +git commit -m "feat(site): diagram façade with Kroki PlantUML renderer" +``` + +--- + +### Task 2: Post-process spec HTML — replace `@startuml` literalblocks with SVG + +**Files:** +- Modify: `site/scripts/build-adoc.mjs` +- Create: `site/test/replace-plantuml.test.mjs` + +- [ ] **Step 1: Write the failing test for the pure replacer** + +`site/test/replace-plantuml.test.mjs`: + +```js +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { replacePlantumlBlocks } from "../scripts/build-adoc.mjs"; + +test("replaces a literalblock whose pre starts with @startuml", async () => { + const html = + `

x

\n
\n
\n` + + `
@startuml\nA -> B\n@enduml
\n
\n
\n

after

`; + const fakeRender = async (type, src) => { + assert.equal(type, "plantuml"); + assert.match(src, /@startuml/); + return "DIAGRAM"; + }; + const out = await replacePlantumlBlocks(html, fakeRender); + assert.match(out, /DIAGRAM<\/svg>/); + assert.doesNotMatch(out, /@startuml/); + assert.match(out, /

after<\/p>/); // surrounding content preserved + assert.match(out, /diagram-svg/); // wrapped for styling +}); + +test("leaves non-diagram literalblocks untouched", async () => { + const html = `

just text
`; + const out = await replacePlantumlBlocks(html, async () => "NO"); + assert.equal(out, html); +}); +``` + +- [ ] **Step 2: Run it — expect FAIL (export missing)** + +Run: `cd site && node --test test/replace-plantuml.test.mjs` +Expected: FAIL — `replacePlantumlBlocks` is not exported. + +- [ ] **Step 3: Add the pure replacer + export it from build-adoc.mjs** + +In `site/scripts/build-adoc.mjs`, add this exported function (near the top, +after imports) and import the façade: + +```js +import { renderDiagram } from '../src/lib/diagram.mjs'; + +// Replace asciidoctor literalblocks that hold a PlantUML source +// (
@startuml…
) with rendered inline SVG. Pure w.r.t. I/O: the +// renderer is injected so it is unit-testable without network. +export async function replacePlantumlBlocks(html, render = renderDiagram) { + const blockRe = + /
\s*
\s*
(@startuml[\s\S]*?)<\/pre>\s*<\/div>\s*<\/div>/g;
+  const matches = [...html.matchAll(blockRe)];
+  let out = html;
+  for (const m of matches) {
+    const source = decodeHtmlEntities(m[1]);
+    const svg = await render('plantuml', source);
+    out = out.replace(m[0], `
${svg}
`); + } + return out; +} + +function decodeHtmlEntities(s) { + return s + .replace(/</g, '<').replace(/>/g, '>') + .replace(/"/g, '"').replace(/’/g, '’') + .replace(/&/g, '&'); +} +``` + +- [ ] **Step 4: Run it — expect PASS** + +Run: `cd site && node --test test/replace-plantuml.test.mjs` +Expected: PASS (2 tests). + +- [ ] **Step 5: Wire the replacer into the build (after `doc.convert()`)** + +In `build-adoc.mjs`, change the spec-HTML generation so the converted HTML is +post-processed before it's written: + +```js +const specHtmlRaw = await doc.convert(); +const specHtml = await replacePlantumlBlocks(specHtmlRaw); +``` +(Leave the rest — `writeFileSync(... 'spec.html', specHtml ...)` — unchanged; it +now writes the SVG-embedded HTML.) + +- [ ] **Step 6: Commit** + +```bash +git add site/scripts/build-adoc.mjs site/test/replace-plantuml.test.mjs +git commit -m "feat(site): replace PlantUML literalblocks with inline SVG at build" +``` + +--- + +### Task 3: Style the embedded diagrams (large, responsive) + +**Files:** +- Modify: `site/src/pages/docs/spec.astro` (or the shared style used by the spec page) + +- [ ] **Step 1: Add CSS for `.diagram-svg`** + +Find the ` From 7b780f2d379878805f905aa647d9787759ed6d9c Mon Sep 17 00:00:00 2001 From: Oliver-Marcus Eder Date: Fri, 28 Aug 2026 19:55:37 +0200 Subject: [PATCH 7/7] ci(site): run diagram pipeline unit tests before the build --- .github/workflows/a11y-check.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/a11y-check.yml b/.github/workflows/a11y-check.yml index e9dab4a..87b9aec 100644 --- a/.github/workflows/a11y-check.yml +++ b/.github/workflows/a11y-check.yml @@ -12,6 +12,7 @@ on: - 'site/package-lock.json' - 'site/scripts/**' - 'site/src/lib/**' + - 'site/test/**' jobs: axe: @@ -29,6 +30,10 @@ jobs: working-directory: site run: npm ci + - name: Diagram pipeline unit tests + working-directory: site + run: node --test test/diagram.test.mjs test/replace-plantuml.test.mjs + - name: Build site working-directory: site run: npm run build