Feature: Per-Template Architecture Diagram (Mermaid)
IMPLEMENTATION RULES: Before implementing this plan, read and follow:
- WORKFLOW.md - The implementation process
- PLANS.md - Plan structure and best practices
Status: Completed
Goal: Render an auto-generated ## Architecture section on every non-overlay template documentation page, containing a Mermaid flowchart (steady-state) and a Mermaid sequence diagram (configure-time flow), derived from existing template registry data.
Last Updated: 2026-04-11
Completed: 2026-04-12 — v1 shipped in commits c20c402 and 2b77ed2. Superseded the same day by the v2 redesign — see PLAN-architecture-diagram-v2.md.
Investigation: INVESTIGATE-template-architecture-diagram.md — read for the full decision record, rationale, and archetype diagram drafts.
Dependencies
Prerequisites (must complete before this plan moves to active/):
PLAN-environment-card.mdPhase 4 complete and plan moved tocompleted/.template-info.yamlschema refactor shipped — must deliver a cleanquickstart.run: stringfield (or equivalent) that the sequence builder reads verbatim. That refactor is scoped by a separate future investigation the user will create later. This plan does not block on the investigation being written — it blocks on its shipped outcome. See INVESTIGATE § Dependencies for details.
Priority: Medium.
Environment conventions
All shell commands in this plan run inside the devcontainer. Never on the host. This is a project-wide rule from the repo-root CLAUDE.md. Every ### Validation block below assumes the command is executed in the devcontainer — the plan does not repeat this per task.
Tool availability in the devcontainer (verified in existing scripts):
tsxis available globally (see comment inscripts/generate-registry.sh)node≥ 20 (CI uses Node 20, devcontainer ships a compatible version)jqis used by all existing generatorsnpxis available via thewebsite/package
Invocation patterns this plan uses:
- Run registry generator:
npx tsx scripts/generate-registry.ts(matches CI — see.github/workflows/deploy-docs.yml) - Run doc generator:
bash scripts/generate-docs-markdown.sh --force(matches CI) - Run build:
cd website && npm run build - Run unit tests (new):
npx tsx --test scripts/test/build-architecture-mermaid.test.ts— iftsx --testpassthrough is unavailable in the installed tsx version, fall back tonpx tsx scripts/test/build-architecture-mermaid.test.ts(node:test runs automatically when itstest()function is called)
Overview
Each template documentation page under website/docs/templates/** currently renders (confirmed by reading scripts/generate-docs-markdown.sh lines 144–207):
---front matter---
<TemplateHeader … />
<TemplateEnvironment … /> (conditional — skipped for overlays)
README content (embedded from template source)
## Related Templates (conditional — skipped when none)
This plan inserts a new auto-generated ## Architecture section between <TemplateEnvironment /> and the README embed, i.e. at script line ≈196 (after the MDXEOF heredoc sentinel that closes the TemplateEnvironment block, before the _get_readme_content … >> "$page_file" call at line 198–207).
TypeScript-first composition shape
The builder emits one field on the registry entry — the complete MDX section, ready to concatenate into the page:
interface ArchitectureResult {
mdx: string | null; // Full composed section or null (overlay)
}
mdx is either the full block (including the ## Architecture heading, conditional ### Steady-state and ### Configure flow sub-headings, and both fenced ```mermaid code blocks) or null for overlay templates. All conditional logic (single-diagram templates, overlay skip, archetype variation) lives in TypeScript. The bash script shrinks to a jq -r read and an echo.
Why one field, not two: the investigation's D1 design detail originally proposed architectureFlowchart and architectureSequence as separate fields with bash composing them. We consolidated to a single pre-composed field to honour the project's TypeScript-first preference and keep generate-docs-markdown.sh a dumb pipe. Individual builder functions (buildFlowchart, buildSequence) remain exported from the module for unit testability.
Phase 1: Builder module + tests (TypeScript)
Create a standalone TS module that can be unit-tested in isolation, mirroring the scripts/lib/dct-doc-paths.ts pattern.
Tasks
-
1.1 Create
scripts/lib/build-architecture-mermaid.tswith a header comment block in the style ofscripts/lib/dct-doc-paths.ts(purpose, inputs, expected registry entry fields, examples). Export:buildArchitectureMdx(entry: TemplateEntry): { mdx: string | null }— main entry point; composes the full MDX sectionbuildFlowchart(entry: TemplateEntry): string | null— returns the rawflowchart LRstring, ornullfor overlaysbuildSequence(entry: TemplateEntry): string | null— returns the rawsequenceDiagramstring, ornullfor overlays AND templates withresolvedServices.length === 0
-
1.2 Implement
buildFlowchart(entry). Archetype rules follow INVESTIGATE § E:- Overlay (
install_type === 'overlay') → returnnull - DCT subgraph (when
resolvedTools.length > 0ORmanifestpresent): emitsubgraph dct["DCT devcontainer"]with one node per tool (usetool.idas node id,tool.nameas label) plus anappnode (label derived fromparams.app_namewhen present, otherwiseentry.nameorentry.id— no hardcoding) plus anenvnode labelled.envwhenmanifestis present - K8s subgraph (when
resolvedServices.length > 0ORmanifestpresent): emitsubgraph k8s["Local Kubernetes Cluster (Test environment)"]— exact label matcheswebsite/docs/architecture.mdcanonical diagram; include service nodes as cylinders[("<service.name><br/><service.database>")]plus asecK8s Secret node whenmanifest.secretName, plusargo+podnodes whenmanifestpresent (even for templates with no services — E2 still deploys via ArgoCD) - UIS node (outside subgraphs, when
resolvedServices.length > 0): emituis["UIS provision-host"] - GitHub subgraph (for app templates only): emit
subgraph gh["GitHub"]withrepo,actions,ghcrnodes - Edges:
- Per service:
uis -->|creates| <serviceNodeId>, anduis -->|creates| secwhen manifest uis -->|writes| envwhen manifestapp -->|host.docker.internal:<service.exposePort>| <serviceNodeId>per service (app-to-service runtime connection)dct -->|git push| repo(subgraph-to-node edge — see Implementation Note on fallback)repo -->|trigger| actions -->|push image| ghcrargo -->|monitors| repo(ArgoCD pull-based GitOps — matches canonicalarchitecture.md)ghcr -->|image pull| argo -->|deploys| podwhen manifestpod -->|<service.namespace>.svc.cluster.local:5432| <serviceNodeId>per service when manifest (in-cluster connection; hardcode 5432 for postgresql, or derive from service metadata if available)
- Per service:
- Stack template variant (
install_type === 'stack'): emit a minimal K8s subgraph with only service nodes, auisnode,uis -->|deploys + seeds| <serviceNodeId>per service, and a dashed consumer edge:consumers["Consumer templates"] -.->|use this| <serviceNodeId>- The
consumersnode is a hardcoded generic label — not derived from cross-template data. Documented as a known simplification (see § Open design decisions).
- Overlay (
-
1.3 Implement
buildSequence(entry)— returnsnullwheninstall_type === 'overlay'ORresolvedServices.length === 0. Otherwise:- Participants (in this order):
Dev as Developer,DCT as DCT devcontainer,UIS as UIS provision-host,K8s as Local Kubernetes cluster - Stack template flow:
Dev->>DCT: uis template install <entry.id>DCT->>UIS: install stack- For each service:
UIS->>K8s: create <service.name> (<service.database>) - If any service has
initFilePath:UIS->>K8s: run init-*.sql seed files UIS->>UIS: kubectl port-forward <service.exposePort>(first service)UIS-->>DCT: return connection JSON
- App-with-services flow:
Dev->>DCT: dev-template configureDCT->>UIS: request provisioningUIS->>K8s: create namespace- For each service:
UIS->>K8s: create <service.name> (<service.database>) - If manifest:
UIS->>K8s: create K8s Secret (<manifest.secretName>) UIS->>UIS: kubectl port-forward <service.exposePort>(first service)UIS-->>DCT: write .env (host.docker.internal:<service.exposePort>)Dev->>DCT: <quickstart.run>— reads the post-refactorquickstart.runfield directly; if that field is absent (prerequisite 2 not yet shipped), throw a clear error at build time pointing at the missing field, do not fall backDCT->>K8s: connect via host.docker.internal:<service.exposePort>
- Participants (in this order):
-
1.4 Implement
buildArchitectureMdx(entry)— composes the final MDX section from the two raw strings:- If
buildFlowchart(entry)returnsnull→ return{ mdx: null } - Otherwise, build the section string:
## Architecture### Steady-state```mermaid<flowchart>
- If
buildSequence(entry)is non-null, append:### Configure flow```mermaid<sequence> - Terminate with a trailing newline. Return
{ mdx: <composed> }.
- If
-
1.5 Create
scripts/test/build-architecture-mermaid.test.ts. First test file in the repo, so this task also establishes the pattern for futurescripts/tests. Usenode:test+node:assert/strict:import { test } from 'node:test';import assert from 'node:assert/strict';import { buildArchitectureMdx, buildFlowchart, buildSequence } from '../lib/build-architecture-mermaid.ts';Fixtures: define inline minimal
TemplateEntryshaped objects for each archetype. Do not load real registry JSON — keep tests hermetic. -
1.6 Test coverage — one
test()per archetype, asserting on substrings (not exact string equality, to avoid whitespace brittleness):- E1 fixture (
python-basic-webserver-databaseshape: install_type=app, 1 tool, 1 service with exposePort+database, manifest present, params.app_name='my-app'): assertbuildArchitectureMdxreturns non-nullmdx; assert it contains## Architecture,### Steady-state,### Configure flow,flowchart LR,sequenceDiagram,subgraph dct,subgraph k8s,argo,dct -->|git push| repo,argo -->|monitors| repo,host.docker.internal:35432,dev-template configure, and the exactquickstart.runvalue from the fixture. Assert it does NOT containuis template install. - E2 fixture (
python-basic-webservershape: install_type=app, 1 tool, 0 services, manifest present, params=null): assertmdxis non-null; flowchart present;### Configure flowandsequenceDiagramabsent (null sequence branch); nosubgraph k8sservices but ArgoCD + pod still rendered because manifest is present; app node label falls back toentry.nameorentry.id. - E3 fixture (
postgresql-demoshape: install_type=stack, 0 tools, 1 service, manifest absent, params present): assertmdxnon-null; containsflowchart LR,sequenceDiagram,uis template install postgresql-demo(verbatim, usesentry.id),consumers["Consumer templates"], dashed edge-.->, nosubgraph dct, noargo. - E4 fixture (
plan-based-workflowshape: install_type=overlay): assertbuildArchitectureMdxreturns{ mdx: null }; assertbuildFlowchartreturnsnull; assertbuildSequencereturnsnull.
- E1 fixture (
-
1.7 Determinism test — one additional
test()that callsbuildArchitectureMdx(e1Fixture)twice and asserts the two outputs are===equal. Catches accidentalObject.keys()ordering non-determinism that would pollute git diffs. -
1.8 MDX smoke test — write a tiny fixture MDX file to
/tmp(in devcontainer) that contains one representative E1 output between a<TemplateHeader>stub and some body text, then runcd website && npx docusaurus buildagainst a throwaway project. If you can't afford a full Docusaurus build: instead, manually copy the E1 fixture output into one existing template page underwebsite/docs/templates/**as a one-off smoke, runcd website && npm run build, then revert the change. The goal is catching MDX-parser collisions with Mermaid syntax (<br/>,{,") before any of Phases 2–3 build on top.
Validation
Run inside the devcontainer:
npx tsx --test scripts/test/build-architecture-mermaid.test.ts
(Fallback if --test passthrough is missing: npx tsx scripts/test/build-architecture-mermaid.test.ts.) All tests pass. Task 1.8 MDX smoke test also passes. User confirms before moving to Phase 2.
Phase 2: Integrate into registry generator
Tasks
-
2.1 Open
scripts/generate-registry.ts. Add the import at the top alongside the existingdct-doc-pathsimport:import { buildArchitectureMdx } from './lib/build-architecture-mermaid.ts'; -
2.2 Extend the
TemplateEntry-like type / theentry: Record<string, unknown>shape (see line ≈600) to include the new field. Sinceentryis typed loosely asRecord<string, unknown>, just assigning the field is legal — but add a type-level note so future readers understand it:// architectureMdx: added by build-architecture-mermaid.ts below — the full// `## Architecture` section as a ready-to-embed MDX string, or null for// overlay templates (where no diagram is rendered). -
2.3 Insert the builder call between existing lines 663 and 665 of
scripts/generate-registry.ts(verified by reading the file on 2026-04-11) — i.e. afterentry.resolvedInitFiles = readInitFiles(...)and beforeallTemplates.push(entry):const { mdx } = buildArchitectureMdx(entry as TemplateEntry);entry.architectureMdx = mdx; -
2.4 Regenerate the registry:
npx tsx scripts/generate-registry.ts -
2.5 Inspect
website/src/data/template-registry.json. For the four canonical archetypes, assert the new field is populated as expected:python-basic-webserver-database→architectureMdxis a non-null string containing## Architecture,### Steady-state,### Configure flow,sequenceDiagram,argo,dct -->|git push| repo,host.docker.internal:35432python-basic-webserver→architectureMdxis non-null, contains## Architectureand### Steady-statebut does NOT contain### Configure floworsequenceDiagrampostgresql-demo→architectureMdxnon-null, containsuis template install postgresql-demoplan-based-workflow→architectureMdxisnull
Use
jqinside the devcontainer to pull the field:jq -r '.templates[] | select(.id=="python-basic-webserver-database") | .architectureMdx' website/src/data/template-registry.json
Validation
Command above succeeds and the four assertions hold. User inspects the python-basic-webserver-database sample output and confirms the diagrams look right as raw MDX text (rendering happens in Phase 3).
Phase 3: MDX emit step (minimal bash)
Tasks
-
3.1 Open
scripts/generate-docs-markdown.sh. Verified insertion point: after theMDXEOFheredoc sentinel that closes the<TemplateEnvironment />block (currently at line ≈195, inside theif [[ -n "$local_has_tools" || … ]]; then … figuard at lines 173–196) and before the "Embed README content" block (currently at line 198). -
3.2 Add this block at the insertion point (outside the TemplateEnvironment conditional — the Architecture section has its own overlay suppression via the
nullcheck):# Emit auto-generated ## Architecture section (PLAN-template-architecture-diagram.md).# The full MDX is pre-composed by buildArchitectureMdx() in# scripts/lib/build-architecture-mermaid.ts and stored as a single string field# on each registry entry. Overlay templates get null, which jq returns as the# empty string under `// empty`, and the conditional skips the section.local_arch_mdx=$(jq -r ".templates[$i].architectureMdx // empty" "$REGISTRY")if [[ -n "$local_arch_mdx" ]]; thenprintf '\n%s\n\n' "$local_arch_mdx" >> "$page_file"fiWhy
printf '\n%s\n\n': adds one leading blank line to separate the heading from whatever came before, and one trailing blank line so the README embed doesn't run on.jq -ralready unescapes newlines inside the string, so multi-line Mermaid is preserved. -
3.3 Regenerate all MDX files:
bash scripts/generate-docs-markdown.sh --force -
3.4 Spot-check the regenerated
.mdxoutput. Forpython-basic-webserver-database.mdx: confirm the## Architecturesection appears between<TemplateEnvironment />and the README content, and that the fenced```mermaidblocks are unmangled (no escaped newlines, no double-escaped quotes). -
3.5 Run the Docusaurus build to catch any MDX parser collisions with Mermaid syntax — this is the definitive end-to-end syntax check:
cd website && npm run buildIf the build fails on a specific template, the error message identifies the file and line. Fix by adjusting the builder module (Phase 1 task 1.2 or 1.3), regenerate registry + docs, rebuild. Iterate.
-
3.6 Run the existing validators to confirm no regressions:
bash scripts/validate-metadata.shbash scripts/validate-docs.shNote:
validate-docs.shruns rules against.mdfiles only, not.mdx, so the new## Architectureheading is not subject to itsrequired_heading/forbidden_patternrules. Confirmed by readingscripts/validate-rules.confon 2026-04-11.
Validation
npm run build succeeds. User opens the regenerated MDX for the four canonical archetypes and confirms:
python-basic-webserver-database.mdx—## Architecturepresent with both diagramspython-basic-webserver.mdx—## Architecturepresent, flowchart only, no### Configure flowsub-headingpostgresql-demo.mdx—## Architecturepresent with stack-shaped flowchart anduis template installsequenceplan-based-workflow.mdx— no## Architecturesection at all
Phase 4: Visual review in running dev server
Tasks
-
4.1 Start the Docusaurus dev server:
cd website && npm run start -
4.2 Visit every template page under
http://localhost:3000/docs/templates/(all templates inwebsite/docs/templates/**). For each, capture:- Node overflow / label truncation
- Subgraph-to-node edges (
dct -->|git push| repo) rendering correctly — this is the primary blocker risk; if any template renders thedct --> repoedge as two disconnected subgraphs, fall back to adding an explicitgit_localnode inside the DCT subgraph (see Implementation Notes) and rerun Phases 2–3 - Wrong-direction arrows or labels that don't match the real commands
- Dark-mode vs light-mode readability (toggle in the Docusaurus navbar)
- Mobile viewport (Chrome DevTools device emulation) — horizontal scroll is acceptable per investigation § H
-
4.3 For each issue: prefer fixes in the builder (
scripts/lib/build-architecture-mermaid.ts) over source-data changes. Regenerate registry + docs, reload the dev server, retest. -
4.4 After all 10 templates pass visual review, re-run
npm run buildone final time to confirm no regressions from iterative fixes.
Validation
User walks through all 10 template pages in the running dev server and confirms the diagrams are accurate and readable. All labels match the real commands and addresses used in each template's configure/run story.
Phase 5: Contributor docs
Tasks
-
5.1
website/docs/ai-developer/readme-structure.mddoes not exist (verified 2026-04-11). Instead, updatewebsite/docs/ai-developer/project-dev-templates.md(pointed at byCLAUDE.md) with a short section on the auto-generated## Architectureblock. Explain:- The section is built at registry-generation time by
scripts/lib/build-architecture-mermaid.ts - Contributors do NOT author it per-template — editing the generated
.mdxfile is pointless, changes will be overwritten - To change the diagram shape, edit the builder module
- Overlay templates skip the section entirely by design
- The section is built at registry-generation time by
-
5.2 Add a one-line back-reference in the investigation doc's § Next Steps that this plan is the downstream implementation and link to the builder module path.
-
5.3 Update
plans/active/index.mdandplans/completed/index.mdas this plan moves through states (handled automatically bybash scripts/generate-plan-indexes.shif the script sees the new file — verify by running it).
Validation
User reviews the updated project-dev-templates.md section and confirms it accurately describes the contributor-facing model.
Acceptance Criteria
- All 10 non-overlay template pages render a
## Architecturesection between<TemplateEnvironment />and the README - App templates with services (E1) render both flowchart and sequence diagram
- App templates with manifest but no services (E2) render flowchart only (no
### Configure flowsub-heading); ArgoCD + pod chain is present because a manifest exists - Stack templates (E3) render both diagrams; sequence uses
uis template install <entry.id>(notdev-template configure) - Overlay templates (E4) render no
## Architecturesection - The sequence diagram's "run the app" step uses the post-refactor
quickstart.runfield verbatim (no hardcoded per-language commands) - All diagrams match the visual style of
website/docs/architecture.md— plain text labels, no emojis, default theming,subgraphgrouping, subgraph label"Local Kubernetes Cluster (Test environment)" -
npx tsx scripts/generate-registry.tswritesarchitectureMdxon every entry (string ornull) -
bash scripts/generate-docs-markdown.sh --forceemits the section into every non-overlay page -
cd website && npm run buildsucceeds — Docusaurus Mermaid parser validates every fenced block -
bash scripts/validate-metadata.sh && bash scripts/validate-docs.shpass with no regressions - Unit tests at
scripts/test/build-architecture-mermaid.test.tscover all four archetypes + determinism and pass undernpx tsx --test - Determinism: running
npx tsx scripts/generate-registry.tstwice in a row produces a byte-identicaltemplate-registry.json - Contributor docs in
project-dev-templates.mdmention the new section and point to the builder module
Implementation Notes
-
Subgraph-to-node edges are a blocker risk. Mermaid 10.x in Docusaurus 3.9 should support edges where the source is a subgraph id (
dct --> repo), but rendering can be finicky in some flowchart layouts. Phase 1 task 1.8 catches this early via the MDX smoke test; Phase 4 visual review catches any residual issues. Fallback pattern: add a dedicatedgit_local["Local git working copy"]node inside the DCT subgraph and change the edge source fromdcttogit_local. This mirrors thearchitecture.mdcanonical pattern which uses explicitgit[Git Repository]nodes inside the "Developer's Local Machine" subgraph. -
ArgoCD is drawn as a solid edge, not dashed/planned. Decision locked in investigation § F. When the real ArgoCD integration ships, the labels may need adjusting but the chain shape (
argo -->|monitors| repo,ghcr -->|image pull| argo -->|deploys| pod) should be stable. -
Node id collisions for multi-service templates are deferred. The current 10 templates all have 0 or 1 service. If a future template has two services, the builder must disambiguate (
svc_postgresql,svc_redis, plus matchingsec_postgresql,sec_redis). For v1, assume single-service and fail loudly (throw with a clear error) ifresolvedServices.length > 1— better to block than silently emit a broken diagram. -
Node label derivation precedence: prefer
params.app_name→entry.name→entry.id. Use the string verbatim (no dash↔underscore normalization). E2 fixture (python-basic-webserver) hasparams === null, so it exercises theentry.namefallback path. -
quickstart.rundependency is hard. If prerequisite 2 (schema refactor) has not shipped when Phase 1 runs, the sequence builder cannot do its job. The plan cannot start until the field is present in every app template'stemplate-info.yaml. -
Stack template consumer node is a hardcoded generic label —
consumers["Consumer templates"]— not derived from cross-template data. Simplest implementation; acceptable v1 tradeoff. See § Open design decisions. -
Typecheck gap:
scripts/**is not typechecked by the website'snpm run typecheck(thewebsite/tsconfig.jsonbaseUrl is.= the website folder; scripts live at repo root). The new builder module is only validated at runtime by tsx. Extending typecheck coverage is noted in § Open design decisions as a future improvement. -
Determinism: the builder must iterate in a stable order. Prefer
for (const svc of entry.resolvedServices)(array iteration is order-stable) overfor (const key in obj)(object key order is implementation-defined). Test 1.7 guards this. -
Test runner establishment: this plan creates the first TypeScript test file in the repo. Task 1.5 sets the precedent — future tests for other
scripts/lib/modules should follow the same pattern (node:test+node:assert/strict, run vianpx tsx --test).
Open design decisions (defaults applied)
These were surfaced during the 2026-04-11 gap analysis. Defaults applied — user can override before or during implementation.
| # | Question | Default chosen | Why |
|---|---|---|---|
| O1 | Where to document the new section (no readme-structure.md exists)? | Update project-dev-templates.md | It's the file CLAUDE.md already points at for project-specific docs |
| O2 | Typecheck strategy for scripts/** | Runtime-only via tsx for v1; extending tsconfig to include scripts noted as future improvement | Matches the current state of the repo; no new infrastructure in this plan |
| O3 | Stack template consumer edge | Hardcoded consumers["Consumer templates"] label | Cross-template lookup would require a second pass over all templates — too much complexity for v1 |
| O4 | How to handle multi-service templates (>1 service) | Throw a clear error and block registry generation | Better than silently emitting a broken diagram; real multi-service support is future work |
| O5 | Where to run Phase 1 tests in CI | Not wired to CI in this plan | No existing test runner in .github/workflows/deploy-docs.yml; adding CI integration is optional follow-up work |
Files to Modify
scripts/lib/build-architecture-mermaid.ts(new, TypeScript)scripts/test/build-architecture-mermaid.test.ts(new, TypeScript — first test file in repo)scripts/generate-registry.ts(import at top, +2 lines in the entry-assembly loop at line 663–665)scripts/generate-docs-markdown.sh(+5 lines at line ≈196, outside the TemplateEnvironment conditional)website/docs/ai-developer/project-dev-templates.md(Phase 5 contributor-docs note)website/docs/ai-developer/plans/backlog/INVESTIGATE-template-architecture-diagram.md(Phase 5 back-reference)
Automatically regenerated (do not edit by hand):
website/src/data/template-registry.json— newarchitectureMdxfield on every entrywebsite/docs/templates/**/*.mdx— 9 non-overlay pages get the new section