Skip to content

ADR-0066 — UI Stack: Figma + MkDocs + Structurizr + Mermaid + Storybook + Next.js

Status: active Date: 2026-04-24 Introduced by: CVN-N008 / issue #678 Supersedes: none (first UI-stack ADR)


Context

The platform is about to build its first production-grade UI — the Configuration Console of CVN-N008 (modern replacement for the legacy Streamlit admin tool). Before any EA story opens, the stack and the handoff conventions between design, documentation, system architecture, coded components, and implementation must be fixed. Otherwise each story re-invents the pipeline and the handoff drifts — the exact pattern that produced the accumulated pain in the Streamlit tool (#606 / #607).

Today the project already owns three of the tools in isolation — MkDocs for documentation, Structurizr for system architecture (ADR-57), Mermaid for inline diagrams — but there is no canonical answer to:

  • Where does the visual design (mockups, interactions, mobile breakpoints) live?
  • Where do live coded components (with their state matrix, accessibility notes, props spec) live?
  • How do design frames, documentation pages, Storybook stories, and Next.js implementations cross-link?
  • How does a reader trace a UI feature from business need → wireframe → documentation page → component story → production route?

The Streamlit tool avoided these questions by conflating documentation, design, and implementation into a single Python file. That conflation is exactly what we are migrating away from. Re-importing the same ambiguity into the new Console would waste the migration.

The no-discipline rule (memory feedback_no_discipline_workflows.md) forbids workflows that require the operator to remember to update multiple places. The answer cannot be "we will keep Figma and MkDocs in sync by convention" — it has to be a rule backed by IDs and verifiable links.

Decision

Adopt a six-tool UI stack with a strict single-responsibility split, backed by a shared ID scheme that makes every artefact traceable from any starting point.

Tool Single responsibility
Figma Source of truth for screen compositions, flows, and interactions — wireframes, high-fidelity screens, state transitions, mobile breakpoints. Figma is a projection surface for individual components and tokens (see §Direction of truth below)
MkDocs (in documentation/ui/) Source of truth for project UI documentation — per-screen pages, user flows, design-system catalogue, acceptance criteria
Structurizr (existing workspace.dsl) Source of truth for system architecture — where the UI container fits, its dependencies, its data flows. Not pixels
Mermaid (inside MkDocs pages) Flows — user journeys, sequences, state diagrams, approval flows
Storybook (new console-next/ folder, bootstrapped under epic EA) Source of truth for coded components and their state matrix — props spec, accessibility notes, visual variants, Figma link per story
Next.js (same folder) Implementation — App Router pages, routes, API integration

All six tools are interconnected by a single ID scheme (see Invariants). A UI artefact in any one tool carries IDs that resolve in the other five.

Direction of truth per layer

The common mistake is to treat Figma as the single source of truth for everything visual. That collapses under automation: the moment a designer picks #3B82F6 in Figma and a developer types #3B82F7 in CSS, the platform has drift with no fix except "discipline" — exactly what the no-discipline rule forbids. The fix is to split the "visual" concept into three layers, each with a clear owner:

Layer Source of truth Projection
Design tokens (colours, typography scale, spacing, radii, shadows, motion) Codeconsole-next/tokens/*.json in W3C DTCG format Projected to Tailwind config + Figma Variables by a sync script in CI
Components (Button, Select, Dialog, and their state matrix) Code — Storybook stories under console-next/ Mirrored in Figma via the official shadcn Figma kit, kept in sync by the designer
Screens, flows, mobile compositions Figma — frames authored by the designer using tokens + component mirrors Referenced from MkDocs pages; Dev Mode annotations point developers at the right component + props

Key consequence: code → design is automated (sync script), design → code is annotated reference (Dev Mode comments point at the coded component). The reverse — generating tokens from Figma, or generating components from Figma frames — is explicitly rejected because it guarantees drift.

Invariants

The following rules MUST hold for any UI work that lands on the project.

  • ID scheme is shared across all six tools. Four prefixes, each canonical in its bare form (no CVN- supra-prefix — those exist only for Need / Epic / Story / Release IDs in the blueprint):
  • UI-<area>-<nnn> — screen (e.g. UI-CONFIG-001)
  • FLOW-<area>-<nnn> — user flow (e.g. FLOW-CONFIG-002)
  • CMP-<area>-<nnn> — coded component (e.g. CMP-CONFIG-005)
  • ADR-UI-<nnn> — UI decision (e.g. ADR-UI-001) <area> matches the top-level surface (CONFIG, TRADING, ADMIN, future RUNS, DASHBOARD).
  • Figma hosts the screen compositions and flows, not the tokens or the component library. Wireframes, mockups, interactive prototypes. Frames whose content is load-bearing for a story carry a functional ID, a link to their MkDocs page, a link to the story / issue, and a status tag (Draft, Ready for Dev, Implemented, Deprecated). Designers edit screens using tokens and components that were created in code and projected into Figma — never the reverse.
  • Design tokens flow code → Figma, never the reverse. The DTCG-format JSON in console-next/tokens/ is the source. A CI script pushes updated tokens to Figma Variables via the Variables REST API on every merge to main. A designer who wants a new token opens a PR on the JSON, not a Figma edit.
  • A coded component does not exist until it has a Storybook story. No import { MyNewButton } from './MyNewButton' in any page.tsx or higher-level component unless MyNewButton.stories.tsx is merged first. Storybook is the acceptance gate, not an afterthought.
  • Props are the component API. A <Button variant="destructive"> is allowed; a <Button style={{background: 'red'}}> is not. Styling deviations go through the cva variant system or a new variant proposal in Storybook — never via inline CSS or ad-hoc Tailwind overrides.
  • Required Storybook states per component: default, loading, error, disabled, mobile viewport. Missing any of the five is a review-blocking gap. Accessibility addon (@storybook/addon-a11y) must be green at WCAG AA on every story.
  • MkDocs is the only place UI project documentation lives. One page per screen under documentation/ui/, following the canonical template (Purpose / Figma source / User flow / Components / Implementation / Acceptance criteria). The design-system catalogue lives at documentation/ui/design-system.md; user flows live at documentation/ui/user-flows.md.
  • Structurizr contains no pixels. The workspace.dsl model covers where a UI container sits in the system and its data flows. A Structurizr container that represents a UI surface may carry a url pointing to its MkDocs page — cross-link, not duplicate.
  • Mermaid diagrams live inside MkDocs pages only. No standalone .mmd files. Mermaid is the tool for flows and sequences; flowcharts for state machines larger than one screen use Mermaid state diagrams rather than prose.
  • Next.js contains no design decisions. Routes implement what Storybook has already specified. Implementation-only PRs cannot introduce a new visual pattern.
  • Every PR touching runtime UI artefacts references at least one Figma frame, one MkDocs page, and one Storybook story. "Runtime UI artefacts" means changes that introduce or modify UI-* screens, CMP-* components, or FLOW-* flow behaviour shipped to operators. Pure governance edits (this ADR itself, the PR template, documentation/ui/* meta-docs that do not add a new screen) are explicitly exempt — they describe the platform, they do not ship UI and therefore cannot generate Figma frames or Storybook stories. Enforced by the PR template (addition in this ADR). Implementation-only PRs on existing UI artefacts may reference a previously-merged doc page + story as their minimum.
  • A screen's MkDocs page uses the canonical template. Template lives in documentation/ui/index.md. Missing sections fail the docs-strict build once CI gate is in place.
  • Variable rename is not supported for IDs. An ID (UI-CONFIG-001) may be retired via Deprecated status but is never renumbered or reassigned. The history is append-only.

Implementation primitives

The stack above names the tools. The concrete technology picks inside each tool are fixed here so every story works off the same baseline.

Concern Pick Rationale
Framework Next.js 14+ with App Router Same framework as modern shadcn examples, Server Components mature, SSR-ready for SEO if needed
Language TypeScript (strict mode) End-to-end types from DB schema to form to component props
Styling Tailwind CSS + CSS variables driven by the tokens JSON Tokens become Tailwind config entries + Figma Variables in one direction
Primitives Radix UI Headless + accessibility-tested. shadcn is built on it
Component owner model shadcn/ui — copied into the repo, not npm-installed You own the code, no opaque upstream, Radix underneath
Variant system cva (class-variance-authority) Typed variants with autocomplete, no string concatenation of classes
Form + validation React Hook Form + Zod with resolver Zod schemas shared between form, API route handler, and component props — one source of validation truth
Data layer TanStack Query Client cache + mutations with optimistic updates; well-tested against realistic API patterns
Tables + grids TanStack Table Headless, composable, integrates with shadcn table primitives
Design tokens format W3C DTCG JSON Emerging standard, portable across Tailwind / Figma / native / other tools
Token → Figma sync Custom script scripts/sync_design_tokens.ts calling the Figma Variables REST API Run in CI on merge to main. Deterministic, reviewable
Component library spec Storybook 8+ with autodocs + addon-a11y + addon-viewport Living docs with the component code; a11y green at WCAG AA is a gate
Unit tests Vitest Fast, Vite-native, good Storybook integration
End-to-end tests Playwright Covers the operator flows that must not regress across releases

Explicitly out of scope for CVN-N008 (revisit post-EA if needed):

  • Visual regression (Chromatic / Percy / Argos) — overkill for the current single-reviewer setup; adopt when ≥ 2 reviewers consistently look at UI PRs.
  • Monorepo tooling (Turborepo / Nx / pnpm workspaces) — YAGNI until there is a second frontend sharing components. The legacy Trading Frontend is separate; if the two converge, revisit.
  • Custom theming engine (dark/light toggle, multi-brand) — shadcn handles this out of the box via CSS variables; no custom layer.
  • AI-generated screens in CI (Galileo, Uizard, generating .fig files from prompts) — quality insufficient for a regulated trading surface and fights the direction-of-truth principle above.

Anti-patterns (explicit rejection)

These practices are common enough to be worth naming as prohibited.

  • Figma as the source of truth for colours / tokens. The colour #3B82F6 that lands as #3B82F7 in CSS is the canonical example of drift by intent. Tokens are code; Figma Variables are a mirror.
  • "The designer will hand off screens, the dev will translate to components." Translation = drift. The designer works with already-translated components (via the shadcn Figma kit) to compose screens.
  • Custom CSS escapes from the token system. A PR that introduces a colour not in tokens/colors.json is a token PR first, a component PR second. No exceptions.
  • Component shipped without a Storybook story. It does not exist in Next.js until it exists in Storybook with all required states + a11y green.
  • "Writing design docs in Figma comments." Comments belong in the MkDocs screen page (one source of truth for the operator contract); Figma carries visual intent, not textual rationale.
  • Custom design system from scratch. shadcn/ui covers 90% of the scope; the remaining 10% builds on top. A fresh design system is a six-month project with negligible value over the above.
  • CSS-in-JS at runtime (styled-components, emotion for new code). Tailwind + cva won this war for our use case; runtime CSS is a performance tax we do not need.

Alternatives rejected

  • Keep Streamlit, harden session-state handling. The framework is not a production configuration-management surface; further investment returns diminishing value. Source of truth for decommission: CVN-N008 Need §2.2.
  • Single mega-tool (only Figma, or only Storybook + MDX). Figma cannot execute code; Storybook cannot host mobile breakpoints or interactive prototypes cleanly; either choice collapses the single-responsibility rule and re-creates the Streamlit conflation. Specialised tools + tight linkage beats one big hammer.
  • Embed design artefacts directly in MkDocs (images + prose, no Figma). Loses interactivity, version control of design iterations, Dev Mode handoff, and the ability for the design stakeholder to work in their native tool. MkDocs is a documentation surface, not a design surface.
  • Use only inline Markdown for flows (no Mermaid). ASCII art does not render in docs-as-code uniformly; complex flows become unreadable. Mermaid is already deployed via the mermaid2 plugin in mkdocs.yml and well-supported in MkDocs Material.
  • ADR-less adoption (informal team agreement). The no-discipline rule forbids conventions that rely on memory. Without an ADR, the next newcomer adds an inconsistent tool; a year later the stack is fragmented. Binding the stack in ADR form is the enforcement surface.
  • Plain PlantUML / drawio for architecture. Structurizr DSL is already the source of truth per ADR-57; not reopening that decision here.

Consequences

Forcing functions:

  • Every new UI story must produce a Figma frame, a MkDocs page, a Storybook story, and an implementation PR — in that order, traceable by ID. The Streamlit shortcut ("just edit the Python file") is structurally gone.
  • Adding a new UI area introduces new UI-<area>-* / CMP-<area>-* counters. The <area> alphabet is extended in this ADR as needed (today: CONFIG, TRADING, ADMIN; future: RUNS, DASHBOARD).
  • The PR template (.github/PULL_REQUEST_TEMPLATE.md) gains a UI section requesting three mandatory artefacts (Figma frame, MkDocs page, Storybook story) plus one conditional Flow reference, applicable only when the PR introduces or modifies a user flow (flows are cross-cutting — one flow spans multiple screens, and not every runtime UI PR touches one). The generic Linked work ID requirement is exemptable via docs: / trivial: / chore: labels, but the three mandatory artefacts are unconditional when a PR touches a runtime UI artefact (UI-* / CMP-* / FLOW-*) — a chore: PR that adjusts a component still ships the Figma + MkDocs + Storybook links. Governance-only PRs (ADR edits, PR template edits, meta-docs under documentation/ui/ that do not add a new screen) delete the entire UI artefacts section from the PR body; the rule targets what ships to operators, not what describes the platform.
  • Adding a new design token requires a PR on console-next/tokens/*.json; Figma receives it automatically on merge. A designer who wants a token change opens the PR themselves, not a Figma edit.
  • Adding a new coded component requires a Storybook story merged first; Next.js usage comes in a second PR that references the merged story.

Costs:

  • Setup tax on the first UI PRs: Figma project bootstrap, Storybook scaffolding, MkDocs skeleton, token-sync script. Scoped as deliverable items of CVN-N008-EA-US1 (Modern Console foundation).
  • Cross-tool drift detection is a follow-up task (a linter that verifies "every UI-CONFIG-* in Storybook has a matching MkDocs page and vice versa"). Out of scope for this ADR — tracked separately.
  • New contributors have a wider tool surface to learn. Mitigated by documentation/ui/index.md serving as a one-page entry point.
  • Designers must learn that Figma Variables are read-only in the new workflow — changes go via JSON PRs. Documented in documentation/ui/index.md §Workflow and in the Figma project's 00 - Principles page.

What becomes easier:

  • Full traceability from a business need to its rendered pixels and back. Given any one ID (UI-CONFIG-001, CMP-CONFIG-005, FLOW-CONFIG-003), the other four artefacts are one link away.
  • Per-component accessibility auditing — Storybook story states make a11y checks mechanical.
  • Mobile responsiveness tested in Figma (breakpoints page) and Storybook (viewport addon) before implementation.
  • Post-mortem analysis of a UI-related bug is faster: the component story captures the intended state matrix, the MkDocs page captures the operator contract, the Next.js file is the only place to look for deviations.

What becomes harder:

  • Quick-and-dirty UI experiments. A 30-minute spike in the Streamlit tool now lives in Figma + Storybook before it can ship. Acceptable trade-off for a production platform.
  • Handling a screen that needs to live in both CONFIG and ADMIN areas. Convention: the primary area owns the ID; secondary areas reference via See also: UI-CONFIG-....

Ownership

  • DRI for the UI stack (this ADR + the conventions): to be named at kickoff of CVN-N008-EA. Responsible for the cross-tool drift linter (future), the PR template UI section, and the documentation/ui/index.md page.
  • Review cadence: the ADR is reviewed at the end of the first EA story (Modern Console MVP) for real-world friction; amended via a new ADR if the invariants prove too rigid or too loose.

References

  • Parent Need: CVN-N008 — Unified Configuration Platform
  • Source discussion: issue #678 — UI design stack proposal
  • Neighbour ADRs: ADR-26 (Grafana single entry point — same single-SoT principle applied to observability), ADR-57 (Structurizr for architecture), ADR-59 (ftf_config as sole config SoT)
  • External docs: Figma Dev Mode guide, Structurizr DSL, shadcn/ui