Design: Documentation Site Generation
Context
Teams accumulate ADRs and specs as markdown files in the repository. These lack discoverability -- no search, no cross-referencing, no visual distinction for RFC 2119 keywords, and no way to share with stakeholders who don't use Git. This capability adds /design:docs to generate Docusaurus documentation from design artifacts, supporting both standalone scaffolding and integration into existing Docusaurus sites, with manifest-based upgrades and separate spec/design pages. See 📝 ADR-0004 for the original scaffolding decision and 📝 ADR-0006 for integration mode, upgrade lifecycle, and spec/design separation.
Goals / Non-Goals
Goals
- Scaffold a complete, working Docusaurus site from templates in one command
- Integrate design documentation into existing Docusaurus sites via a build-time plugin
- Transform ADRs and specs into rich MDX with custom React components
- Provide live editing with file watching and hot reload
- Produce deployable static output for GitHub Pages or any CDN
- Give users full ownership of the generated site (copy, don't link)
- Provide a safe upgrade path when the plugin ships improvements
- Render spec requirements and design decisions as separate, independently navigable pages
Non-Goals
- Hosting or deployment automation (users deploy via their own CI/CD)
- Custom theme creation (the default Docusaurus theme with custom CSS is sufficient)
- OpenAPI spec rendering (only if the project has one, and it's not included by default)
- Automatic conflict resolution during upgrades (users must review modified file diffs)
Decisions
Docusaurus over other static site generators
Choice: Docusaurus 3.9 with React, MDX, and the classic preset.
Rationale: Best combination of native MDX support, React component model for rich rendering, built-in Mermaid via @docusaurus/theme-mermaid, and mature ecosystem with dark mode, search, and sidebar generation out of the box.
Alternatives considered:
- MkDocs with Material: No native JSX; custom components require Jinja2 macros; Python dependency
- VitePress: Vue-based (different ecosystem); no native MDX; smaller plugin ecosystem
- Astro Starlight: Newer/less mature; Mermaid requires extra config; island architecture adds complexity
Copy-then-customize over scaffold-from-scratch
Choice: cp -r the entire templates/docusaurus/ directory, then customize only package.json and docusaurus.config.ts.
Rationale: Templates are production-ready, not skeletons. Copying gives users full ownership and avoids the plugin needing to generate code at runtime.
Alternatives considered:
- Runtime code generation: More flexible but harder to maintain and debug
- Symlinks to plugin templates: Users can't customize without modifying the plugin
Separate docs-generated directory
Choice: Transform output goes to docs-generated/ at the project root, not into docs-site/docs/.
Rationale: Keeps generated MDX clearly separated from source. Makes it obvious what is a build artifact versus source content.
Alternatives considered:
- Output into
docs-site/docs/: Mixes generated and source files; confusing for version control
Centralized MDX escaping
Choice: All MDX v3 escaping logic lives in mdx-escape.js rather than being scattered across transform scripts.
Rationale: Single source of truth for escape rules. Easier to test and maintain. Preserves JSX component tags while escaping literal curly braces and angle brackets.
Alternatives considered:
- Per-transform escaping: Duplicated logic; inconsistent behavior between ADR and spec transforms
Dual-mode: scaffold vs. integration (ADR-0006)
Choice: Detect existing Docusaurus sites and offer two modes -- scaffold (standalone docs-site/) or integration (build-time plugin into existing site).
Rationale: Scaffold mode's zero-configuration simplicity remains valuable for greenfield projects, while integration mode addresses the real gap for teams with existing Docusaurus sites. A universal plugin-only approach would sacrifice scaffold mode's simplicity.
Alternatives considered:
- Universal plugin-only: Removes the simple "copy and run" experience; breaking change for existing users
- Git submodule linking: Submodules are error-prone; customizations create merge conflicts on every update
Build-time Docusaurus plugin for integration mode (ADR-0006)
Choice: Integration mode installs a self-contained Docusaurus plugin (sync-design-docs) that runs transforms during the loadContent() lifecycle phase and watches source files via getPathsToWatch().
Rationale: loadContent() is the standard Docusaurus hook for generating content before the build. The plugin is self-contained with its own transform scripts (parameterized for the existing site's paths), requiring no modifications to the existing site's build pipeline beyond registering the plugin.
Alternatives considered:
- Pre-build script: Requires users to modify their build commands; not integrated with Docusaurus HMR
- Custom Docusaurus preset: Overly heavy; presets are meant for bundling multiple plugins and themes
Component namespacing under design-docs/ (ADR-0006)
Choice: In integration mode, React components are installed to {site}/src/components/design-docs/ rather than directly into {site}/src/components/.
Rationale: Prevents naming collisions with existing components in the site. A project may already have a StatusBadge.tsx with different behavior.
Alternatives considered:
- Direct placement in
src/components/: Risk of overwriting existing components with the same names - Prefixed filenames (e.g.,
DesignStatusBadge.tsx): Requires changing all component references in transforms; inconsistent with scaffold mode
Manifest-based upgrade with checksum tracking (ADR-0006)
Choice: Create a .design-docs.json manifest at the project root tracking plugin version, mode, site directory, and SHA-256 checksums of all managed files. On re-run, compare checksums to classify files as unchanged (safe to replace), modified (show diff and ask), or missing (re-create).
Rationale: Checksums precisely detect user customizations without requiring git operations. Works in any environment (CI, restricted shells). The manifest is a single inspectable JSON file.
Alternatives considered:
- Git-based diffing against version tags: Requires plugin as a git repo at upgrade time; can't distinguish user changes from plugin changes without a common ancestor
- No upgrade support: Discourages upgrades; users permanently drift from improvements
Managed file opt-out (ADR-0006)
Choice: Each file in the manifest has a managed: boolean field. Users can set managed: false to permanently exclude a file from upgrades.
Rationale: Some users will heavily customize specific files (e.g., CSS, a particular component). Rather than repeatedly prompting them on every upgrade, opt-out provides a permanent resolution.
Alternatives considered:
- No opt-out (always prompt for modified files): Annoying for files the user has intentionally diverged
Directory-per-spec with separate pages (ADR-0006)
Choice: Transform each spec into a directory with separate spec.mdx and design.mdx files, using _category_.json for Docusaurus sidebar configuration. Specs with only spec.md (no design.md) emit a single page without a category wrapper.
Rationale: Separate pages are independently searchable and linkable. _category_.json is the standard Docusaurus sidebar mechanism, requiring no custom sidebar logic. The conditional handling for single-document specs avoids the awkward appearance of a category with a single child.
Alternatives considered:
- Tabbed single page: Tab content is invisible to Docusaurus search; deep linking into tabs is complex
- Combined single page (status quo): Long pages are hard to navigate; no direct linking to design decisions
Spec overview index page (ADR-0006)
Choice: Generate an index.mdx in the specs output directory with a table listing all specs and linked columns for Specification and Design documents.
Rationale: Provides a single entry point for browsing all specs. The table format makes it easy to see at a glance which specs have design documents and which don't.
Alternatives considered:
- Sidebar-only navigation: No overview; users must expand each category to find documents
- Auto-generated landing page from Docusaurus: Less control over formatting; can't include the linked table
Architecture
Mode Selection
Scaffold Mode Pipeline
Integration Mode Pipeline
Upgrade Flow
Risks / Trade-offs
- MDX v3 escaping complexity: Curly braces and angle brackets in source markdown must be escaped without breaking JSX components. Mitigation: centralized
mdx-escape.jswith thorough edge-case handling. - Node.js dependency: Docusaurus requires Node.js >= 20. Mitigation: pre-flight check in the skill reports the dependency clearly.
- Heavy node_modules: Docusaurus sites have large dependency trees. Mitigation: acceptable trade-off for the rich component model.
- Transform pipeline maintenance: 5 scripts must be kept in sync as ADR/spec formats evolve. Mitigation: shared utilities in
transform-utils.jsreduce duplication. - Dual-mode surface area: Two modes (scaffold and integration) double the testing and documentation burden. Mitigation: both modes share the same transform pipeline; integration mode reuses scaffold mode's scripts with parameterized paths.
- Manifest deletion: If
.design-docs.jsonis deleted, upgrade tracking is lost. Mitigation: the skill detects this case and offers to re-create the manifest by checksumming existing files. - URL structure change: Separating spec/design into directory-per-spec changes URLs from
/specs/{name}to/specs/{name}/specand/specs/{name}/design. Mitigation: this is a one-time breaking change documented in 📝 ADR-0006; existing bookmarks will need updating. - Integration transform sync: Integration mode's parameterized transform scripts must stay in sync with scaffold mode's
__dirname-relative scripts. Mitigation: the integration templates are shipped alongside the scaffold templates and tested together.
Open Questions
- Should the docs site include a changelog page generated from git history?
- Should the transform pipeline support custom component injection via user configuration?
- Should the upgrade flow support a
--forceflag to replace all files without prompting?