ADR-0022 — Diagrams show their Archify view on the page and their Mermaid in Markdown
Every diagram is one component holding a Mermaid fence: pages show its Archify view, and every Markdown surface carries the Mermaid.
- Status: Approved
- Date: 2026-11-01
- Deciders: @puncoz
- Approved by: @puncoz
- Approved on: 2026-11-01
- Supersedes: ADR-0011
decisions 1 to 3 (where the files live, the byte-for-byte copy into
public/,<HtmlEmbed>with the Mermaid shown above it) - Superseded by: —
- Design documents:
../app-flow/diagrams.mdx,../prd/diagrams.mdx,../architecture/system.mdx,../api/content-routes.mdx,../security/threat-model.mdx,AGENTS.md§8,PROJECT_AGENTS.md§4,DESIGN.md
Context
ADR-0011 put each Archify diagram on its page in a sandboxed frame, under its Mermaid version. The owner asked (2026-11-01):
- Inside the frame, only the diagram, its labels and its controls. Today the frame also shows Archify's title and subtitle, its notes and index rail and its theme and style buttons, and because all of that is taller than the frame, the frame scrolls.
- On the page, only the Archify view; in Markdown, only the Mermaid. The copy-Markdown
action, the Markdown routes and
llms-full.txtgive Mermaid, never the Archify tag. Enforced for every package and project. - The diagram's background matches the site's theme, while boxes and arrows stay vibrant and meaningful, with live animation.
The owner then chose, from options: wireframes get Archify versions too, drawn from their Mermaid, one per screen; enforcement is strict, so every Mermaid diagram outside the templates has an Archify view, and the four that lack one today get it in this change; and the implementation is checked in the owner's Chrome through the Claude in Chrome extension, on localhost only.
Facts this decision rests on, read from Archify 3.2.0's output and the installed packages:
- What an Archify page holds. A toolbar (theme, style, Live/Still, Present, Export); a header (the title and subtitle); the diagram, with its own controls (Route, Map, Lens, find, guide, zoom); and a notes rail (cards, a node index, "Notes & index").
?embed=1hides the toolbar, header, cards and diagram controls, and turns motion and exploration off. Too bare for what the owner asked.?theme=lightor?theme=darksets the theme before first paint.- Colours are CSS variables. The page's:
--bg,--grid,--canvas-dot,--panel,--panel-border,--textand its muted, dim and faint variants. The nodes' come from their kind:--frontend-strokecyan,--backend-strokegreen,--database-strokeviolet,--cloud-strokeamber,--security-strokerose,--messagebus-strokeorange,--external-strokeslate, each with a light-theme value. - Motion starts Still when no preference is saved. Inside the sandbox, storage throws, so every embed starts Still today.
- Size. The diagram's
svgcarries aviewBoxanddata-reader-fit:width-first(it fills the width) orintrinsic-height(a wide diagram, such as the 1277 × 236 version lifecycle). Archify has no wheel handler; the scrolling comes from its page being taller than the 420 or 560px frame. - Markdown. fumadocs-mdx's
includeProcessedMarkdowntakes afilterElementthat can keep an element's children and drop the element ('children-only',fumadocs-coreremark-llms). The copy-Markdown action,/<page>.md, Markdown negotiation andllms-full.txtall read that processed Markdown (docsLlmsinsrc/lib/source.ts). Search records exclude code blocks, so a Mermaid fence never reaches them. - Wireframes. Archify has no wireframe type. Its
architecturetype places components byposandsizeand wraps them in labelledregionboundaries, enough to draw a screen as boxes inside a phone frame. - Counts. 37 wireframes in 8 app flows, 4 Mermaid diagrams without an Archify view
(
maintain/index.mdx,architecture/system.mdx,dfd/system.mdx,sequence/build-and-search-sync.mdx), and 9 diagrams with one. An Archify file is about 820 KB, most of it the viewer built into every file.
Decision
-
One shape for every diagram. A diagram in
content/is written as a<Diagram>holding exactly one Mermaid fence and nothing else:<Diagram src="/diagrams/example-cli/request-flow.html" title="Request flow"> ```mermaid flowchart LR Parser --> Router --> Handler ``` </Diagram>srcfollows ADR-0011's rule (/diagrams/<project-slug>/<slug>.html);titleis required.<Diagram>is registered insrc/components/mdx.tsx;<HtmlEmbed>is no longer available to pages and lives on inside<Diagram>. -
The page shows only the Archify view.
<Diagram>renders the frame; the Mermaid is not rendered on the page. Under the frame stay the title as its caption and Open full screen. -
Markdown shows only the Mermaid. The processed Markdown keeps
<Diagram>'s children and drops the tag (filterElement,'children-only'), so every Markdown surface (the copy-Markdown action,/<page>.md, Markdown negotiation,llms-full.txt) carries the Mermaid fence and never an Archify URL. -
Every Mermaid diagram is a
<Diagram>, wireframes included. The build fails on a Mermaid fence outside a<Diagram>, anywhere incontent/exceptcontent/docs/maintain/_templates/(templates show the shape; a copy must be wrapped before it builds). A wireframe's Archify version is anarchitecturediagram drawn from its Mermaid: one per screen, its boxes placed as the Mermaid rows, inside a labelled phone-screen region, at phone width. The four diagrams without one get theirs in this change. -
Where the files live. Both files sit in
diagrams/<project-slug>/: the candidate,<slug>.json, and Archify's HTML,<slug>.html, byte for byte asdeliverwrote it, socheck --require-provenancecan run on the committed file.public/diagrams/goes. A route,src/app/diagrams/[project]/[file]/route.ts, prerenders each at/diagrams/<project-slug>/<slug>.html, with the site shell of decision 6, served with ADR-0011's CSPsandboxheader. The build fails on: an HTML without its JSON; asrcwith no file; a diagram file no page uses; an Archify receipt (*.delivery*.json) indiagrams/orpublic/. -
The site shell,
src/lib/diagram-shell.ts, is added to every diagram as it is served, so a new package's diagrams need nothing of their own:- In a frame (
?frame=1, set by<Diagram>): Archify's title and subtitle, notes rail and its reveal button, theme and style buttons and the guide's credit line are hidden. The diagram, its labels, Live/Still, Present, Export and the diagram controls stay. A direct visit, through Open full screen, keeps Archify's title and notes. - Colours. The page variables take the site's tokens for each theme (
--home-bg,--home-panel,--home-line,--home-ink,--home-muted,--home-faint), so the diagram sits on the page's own ground. Nodes and arrows keep Archify's kind palette, vibrant and meaningful. Diagrams are delivered with Archify's classic style (novisual_preset), and the existing ones are delivered again that way. - Theme.
<Diagram>passes?theme=from the site's theme, and changes it when the reader switches theme (the frame reloads from the browser cache). - Motion starts Live: the shell turns Archify's motion on once after load, unless the reader prefers reduced motion. The reader can switch to Still.
- No inner scrolling. The frame's height is set on the server from the diagram's
viewBox, plus room for the controls, as a CSS aspect ratio, so the page doesn't jump. The shell then reports the exact content height to the page withpostMessage. The page takes it only from that frame (event.source), and only as a number within bounds. Page scrolling passes over the frame. A wide diagram (intrinsic-height) scrolls sideways inside the frame on phones, Archify's own containment.
- In a frame (
-
The same for every repository.
AGENTS.md§8,PROJECT_AGENTS.md§4 and the templates state the shape. The build checks run on every folder incontent/.
Options considered
| Concern | Option | Pros | Cons | Verdict |
|---|---|---|---|---|
| Trimming Archify | A — a shell added as the file is served | One place for every diagram; the committed file stays Archify's own | Depends on Archify's class names; re-checked on each Archify upgrade | Chosen |
| Trimming Archify | B — Archify's ?embed=1 | No site code | No controls and no motion: not what the owner asked | Rejected |
| Trimming Archify | C — edit each delivered file in the repository | No route | Breaks check --require-provenance on the committed file; repeated per file | Rejected |
| Mermaid on the page | A — not shown | What the owner asked | The text version is one action away (Markdown), not on the page | Chosen (owner) |
| Mermaid on the page | B — a closed "Text version" disclosure | Text on the page for those who want it | Not what the owner asked | Rejected |
| Markdown | A — filterElement, 'children-only' | Every Markdown surface at once, at build | Ties to fumadocs-mdx's option | Chosen |
| Markdown | B — rewrite the text in renderPage | No build option | A regular expression over MDX | Rejected |
| Frame height | A — from the viewBox on the server, refined by the shell's report | No jump; no inner scroll | A postMessage channel to validate | Chosen |
| Frame height | B — fixed, as today | Simple | Scrolls or leaves empty space | Rejected |
| Wireframes | A — one Archify file per screen | Each screen's picture where its Mermaid is | 37 files, about 30 MB; a page with 7 screens loads about 5.6 MB of frames as it scrolls | Chosen (owner) |
| Wireframes | B — one per app flow | 8 files, about 6.5 MB | Screens apart from their states | Rejected (owner) |
| Wireframes | C — stay as code | No new files | Code on the page | Rejected (owner) |
| Enforcement | A — strict: every Mermaid diagram outside the templates | No page can skip it | The 4 diagrams without a view are drawn now | Chosen (owner) |
| Enforcement | B — only the <Diagram> shape | Less work now | Bare Mermaid keeps showing as code | Rejected (owner) |
| Colours | A — site ground, Archify's kind palette, classic style | Matches the site; colour carries meaning | Every existing diagram is delivered again | Chosen |
| Colours | B — keep Archify's backgrounds | No shell colours | Doesn't match the site | Rejected |
Consequences
Positive
- A reader sees the diagram and its controls, at its own height, in the site's colours, moving.
- Tools and LLMs get Mermaid text, never an iframe URL.
- A consuming repository writes one
<Diagram>; the rest is the site's.
Negative / accepted trade-offs
- About 41 new Archify files, roughly 34 MB more in the repository and the deployed output.
- A page with many screens loads several frames as the reader scrolls.
- Each diagram exists twice, Mermaid and Archify; both change in the same change.
- The shell depends on Archify's markup. Its tests run against a committed Archify file, so an upgrade that renames what the shell hides fails the tests.
- The Mermaid text is not on the page. Archify's diagram is an accessible
svg(role="img", title and description), and the frame has a title.
Content invariants
- 1 (Private stays private). Unchanged: a diagram of a private project never goes into
diagrams/. - 5 (Readable as Markdown). Strengthened: every diagram's Markdown is its Mermaid.
Security considerations
- Isolation unchanged. The shell's script runs inside the same sandboxed, opaque-origin
frame (no
allow-same-origin), and direct visits keep the CSPsandboxheader. postMessage. The page accepts a message only whenevent.sourceis that frame's window and the data is{ type: 'archify-height', height }with a finite height between 120 and 4000; anything else is ignored. The frame sends nothing but its height, and a Present request ({ type: 'archify-present' }, no data) when it cannot go fullscreen; the page answers it by opening that diagram's own URL, which it builds itself (implementation notes).- The route serves only files matching
<project-slug>/<slug>.htmlfromdiagrams/, listed at build (generateStaticParams, no other paths). No request data is read. - Nothing new leaves the system. The shell adds no network call; the checks of ADR-0011 (no external resources, no network APIs, no local paths) run on each committed file.
Implementation notes
-
src/components/diagram.tsx(<Diagram>, server: checks thesrcand title, reads the size) with its client partdiagram-frame.tsxfor the theme and the height.<HtmlEmbed>was folded into the frame rather than kept as a separate component; it is removed fromsrc/components/mdx.tsxand from the repository, and a page that still uses it fails the build with the way forward. -
src/lib/source.ts:includeProcessedMarkdownwithfilterElementkeeping<Diagram>'s children (markdownFilterinsrc/lib/remark-diagrams.ts). - The build check of decision 4 (
remarkDiagrams, first among the remark plugins throughapplyMdxPreset) and the file checks of decision 5 (checkDiagrams, run by the loader plugin; a JSON without its HTML fails too). -
src/app/diagrams/[project]/[file]/route.ts(withsrc/lib/diagram-files.ts) andsrc/lib/diagram-shell.ts. A diagram is wide, for the frame's minimum width, at Archify's own ratio (1.55); the frame's first height is theviewBoxratio plus 10rem for the controls, then the reported height. - Every diagram moved to
diagrams/docs/and delivered again in the classic style; Archify views made for the 4 diagrams and the 37 wireframes; every page converted to<Diagram>; the templates updated. Wireframe text is shortened with "…" where Archify would draw it below about 11px (it draws a label on one line); the full text is in the Mermaid. 49 diagrams in all, about 40 MB of HTML. Every file passescheck --require-provenance, holds no local path and calls no network API. - Tests: the shell (what it hides, against a committed Archify file; the colours against
global.css; Live start and reduced motion; the height report, an open menu, a height that answers itself, the toolbar's stacking, Present to fullscreen and its fallback), the height bounds and the Present request, the<Diagram>shape check (no fence, two fences, other content, a bare fence outside one, a bare fence nested in another element, templates exempt,<HtmlEmbed>), the Markdown output (the fence kept, the tag gone),<Diagram>'s frame, and the route's paths. - Checked in Chrome on localhost (the built site, desktop and phone width, both themes).
Three faults found there and fixed in the shell and the frame:
- Flicker. Archify lifts its bottom dock and reserves room for it from the viewport height, which in a frame is the frame: the frame answered its own height, about 16 times a second. In a frame the dock holds still and has fixed room, and past 8 changes in 2 seconds the shell reports only growth until the frame's width changes.
- Export menu under the diagram. The frame's toolbar kept its stacking (
z-index), spans the frame so the menu opens inside it on phones, and the menu takes its own height; the shell reports the open menu's bottom, so the frame grows to show it. - Present. Archify's Present stage is the viewport's height, the frame's own. The
frame now has
allow="fullscreen", and the shell takes the stage to fullscreen; leaving fullscreen leaves the stage. Where fullscreen is refused, the frame asks the page (archify-present), which opens the diagram's own page with?present=1. - Theme. A lazy frame did not always follow its changed
srcafter hydration, and stayed dark on a light page; the frame is now a new element per theme. Real fullscreen could not be seen: the automated Chrome refuses fullscreen to any page ("not granted", even at the top level). The fallback was seen working; the fullscreen path is covered by tests.
-
bun run test,bun run types:checkandbunx next buildpass.
Docs to update on implementation
-
../app-flow/diagrams.mdx: Current;../app-flow/html-embeds.mdxremoved, its index row with it -
../prd/diagrams.mdx: Current -
../architecture/system.mdx,../api/content-routes.mdx,../security/threat-model.mdx,../security/checklist.mdx -
AGENTS.md§5, §8 and §10;PROJECT_AGENTS.md§3 and §4;README.md;.claude/agent-skills/INDEX.md(the Archify override) -
DESIGN.mdand.impeccable/design.json: the diagram frame -
./index.mdx: status on approval; ADR-0011's "Superseded by"
ADR-0021 — Tests run with Bun's built-in test runner
Tests run on Bun's built-in test runner, with no test-runner dependency and only Bun's types added, checked like the rest of the code.
ADR-0023 — Sidebar stations open one at a time
At most one sidebar station is open at each level, so opening one closes the one beside it and the sidebar stays short.