Document Content

# CCM Unified Design System — Decisions

Final conclusions from the design-system unification work, 2026-07-04. Full working record (inventories, research prompt and evaluation, discussion log): see the "Design System" Proof doc.

## The decision

**Base the unified CCM design system on Nuxt UI v4, grafting on the two strongest ideas from Varro's homegrown system: Utopia fluid scales and Every Layout composition components. Social House's current visual language is the target skin.**

Chosen to minimize owned maintenance for a small, agent-first studio. The anti-Tailwind prior was formally **dropped by choice** — the research verdict was CONDITIONAL (technically defensible for Vue, but only if we fund CSS ownership with metadata, docs, and lint), and we are deliberately not funding it. Runner-up was Reka UI + 100%-owned CSS; it loses on maintenance burden, not on merit.

Accepted trade-offs, eyes open:

* **Tailwind v4 coupling** — adopted as plumbing, never as a writing style (see doctrine below).

* **Episodic migration cost** — Nuxt UI's record is two ground-up rebuilds in \~18 months. This is outsourced maintenance, not zero maintenance; agents absorb the migration grunt work.

* **JS overlays instead of native** — Reka UI (Nuxt UI's engine, verified in installed code) implements modals/popovers with teleported portals, JS focus traps, and floating-ui positioning. We cede native `<dialog>`/Popover-API overlay internals to the library.

## The architecture

1. **Nuxt UI v4** — widgets, behavior, accessibility, theme engine. Restyled only through `app.config.ts` theme slots.
2. **Tokens**: single source, **OKLCH**, DTCG format, Style Dictionary pipeline. **Utopia fluid scales live in Tailwind's** **`@theme`** (`--text-step-*`, `--spacing-3xs…3xl`) and **coexist with the numeric scale** — numeric spacing for intra-component use (what Nuxt UI internals and agent reflexes emit), Utopia for macro rhythm (sections, stacks, type steps). Semantic values seeded from Social House's rendered look (Satoshi, indigo/slate, its radius/shadow language).
3. **The** **`ccm`** **layer** — exactly two kinds of components, nothing else:

   * **Composition primitives** (Every Layout lineage, ported from Varro): `Stack`, `Cluster`, `Grid`, `Reel`, `Switcher`, `Cover`, `Frame` — as **thin Vue components with typed props** (`<CStack space="m">`), CSS in Tailwind v4's `components` cascade layer, upgraded with container queries where it helps.

   * **Domain components**: ContentCard family (unifying PostCard / ArticleCard / TopicRow), social-channel registry (brand colors + icons — currently encoded separately in both apps), status→color conventions, shared formatting/clipboard utils.
4. **The one hard rule (anti-drift):** the ccm layer **never duplicates a Nuxt UI widget**. No bespoke buttons, chips, inputs, selects, tables, modals. Lint-enforced, not convention. (Varro's decay began exactly when `varChip`/`varIconButton` reimplemented existing primitives.)

## The class-soup doctrine — Tailwind as plumbing, not a writing style

Where class strings may live:

1. **Pages compose components — full stop.** Page templates contain component tags and at most trivial layout utilities.
2. **Inside our components, class strings live in exactly one place**: a Tailwind Variants definition exposing typed props (`variant`, `size`, `space`). Agents write props, not classes.
3. **Nuxt UI restyling →** **`app.config.ts`** **theme slots.** Never scattered inline `:ui` overrides, never `@apply`.
4. **ccm-layer CSS is plain CSS on shared tokens.** Tailwind v4 tokens are plain CSS custom properties, so `var(--spacing-m)` reaches the same values utilities compile from — no `@apply` needed, zero Tailwind syntax in those files.
5. **`@apply`** **is reserved for prose/markdown styling only** (v4's per-SFC `@reference` requirement keeps it usefully annoying).

Enforcement (agents are the primary authors, so lint > convention): ESLint utility-count budget on `class` attributes outside `components/`; Stylelint bans raw hex/px outside token files; AGENTS.md states the five rules.

## Modern-platform boundaries

* **All of modern CSS is available** in authored layers: container queries, `:has()`, cascade layers, nesting, subgrid, `color-mix()`/OKLCH, `@starting-style`, scroll-driven animations, View Transitions, `text-wrap: balance`, `field-sizing: content` (replaces the textarea-autosize composable).

* **Native interactive HTML is mostly displaced by the widget rule.** Carve-outs where native primitives remain the right tool: (a) markdown/prose-rendered content, where components can't reach; (b) trivial layering in domain components — hover-cards, lightboxes; (c) caution: never open native top-layer popovers inside `UModal` — the JS focus trap and the browser top layer don't coordinate.

## Migration reality

* **Social House is essentially already home** (Nuxt UI + Docus stay). Cleanups: extract its three hand-rolled card styles, three ad-hoc definition lists, duplicated disclosures/grids into ccm-layer components.

* **Varro carries the migration**: 79 components re-platform onto Nuxt UI + ccm layer. Pre-existing correctness bugs to fix on the way: six referenced-but-nonexistent components (`var-section-heading`, `var-persona-card`, `var-config-step-*`), duplicate parallel onboarding implementations, stale `docs/*.md`.

* **The shared-domain package extracts first, zero UI risk**: channel registry, status conventions, formatting utils — pure and framework-agnostic (Social House's `.mjs` utils already are).

## Next step: validation spike (2–3 days, agent-driven)

One fresh Nuxt 4 app proving the pieces coexist. Gates:

1. Nuxt UI v4 + Utopia-in-`@theme` + ccm composition layer live together cleanly in the cascade.
2. **Skin fidelity**: the app convincingly wears the Social House look from the token source.
3. **Unprompted agent compliance**: an agent given only AGENTS.md (the five rules) builds a representative page — form, modal, content-card grid, two composition primitives — and passes the lint gates without hand-holding.

If gate 3 fails persistently even after metadata iteration, the fallback ladder from the research holds: shadcn-vue (if the lesson is "agents need vendored source"), then Reka UI + owned CSS (if the lesson is "the library fights us").

***

# Implementation plan (requirements-only)

Brainstormed via compound engineering 2026-07-05. Canonical ticket: [PRO-226](https://app.plane.so/ccm-design/browse/PRO-226/) in the "Design System Layer (ds-layer)" module. Repo: `git@github.com:ccmdesign/ds-layer.git`.

## Summary

Build `@ccm/ds`: a greenfield Nuxt layer distilling the Social House and Varro experiences into one design system — Nuxt UI v4 base + Utopia fluid tokens + Every Layout composition components + a five-part agent-steering stack. Validated by a three-gate spike app, then adopted by Social House, growing strictly by extraction from real app needs. Varro migrates as a follow-on project.

## Problem frame

Two production apps run architecturally opposite systems (Varro: 79 homegrown components, CUBE/HSL; Social House: 4 components on Nuxt UI/OKLCH). Both separately encode the same domain concepts (channel branding, status colors, content cards). Agents are the primary UI authors going forward and currently get no consistent steering, so drift compounds per app. Nothing runs on the target setup today — the layer is created from scratch.

## Requirements

* **R1 — Packaging.** A Nuxt layer consumable via `extends`, carrying: token CSS, ccm components, Nuxt UI theme config, lint configs, AGENTS.md, and the Claude Code skill. One version bump updates a consuming app.

* **R2 — Tokens.** Single DTCG source, OKLCH, Style Dictionary pipeline. Utopia fluid type + space scales in Tailwind `@theme`, coexisting with the numeric scale (numeric = intra-component; Utopia = macro rhythm). Semantic values seeded from Social House's current rendered look.

* **R3 — Composition components.** Every Layout primitives (Stack, Cluster, Grid, Reel, Switcher, Cover, Frame) as thin Vue components with typed props (`space`, etc.), plain CSS on shared tokens, registered in the `components` cascade layer. Container-query behavior where it beats viewport queries.

* **R4 — Domain foundations.** Social-channel registry (brand colors + icons), status→color conventions, shared formatting/clipboard utils — inside the layer, framework-agnostic where practical.

* **R5 — Skin + gallery.** Nuxt UI themed via `app.config` to the Social House visual language (Satoshi, indigo/slate, its radius/shadow language). A rendered gallery page (tokens + components) for human review.

* **R6 — Agent-steering stack.** (a) Claude Code skill: fires on UI work; procedure = check manifest → compose from Nuxt UI + ccm → class strings only in variant definitions → run lint gate → propose extraction when a pattern recurs. (b) AGENTS.md: the five doctrine rules. (c) Component metadata manifest: machine-readable props/variants/slots/when-to-use/anti-patterns per component. (d) Generated llms.txt. (e) ESLint + Stylelint gates: utility-count budget outside `components/`, token enforcement (no raw hex/px), no-widget-duplication rule.

* **R7 — Validation spike.** A spike app (form, modal, content-card grid, two composition primitives) passing three gates: (1) cascade coexistence — including alongside Docus; (2) skin fidelity to Social House; (3) unprompted agent compliance using only the skill + AGENTS.md.

* **R8 — i18n safety.** No hardcoded user-facing strings in DS components (Varro is EN/PT).

* **R9 — First adoption.** Social House extends the layer; initial extractions from its documented debt: card shell, key-value list, disclosure, auto-fill grid.

## Key decisions

* **Nuxt UI v4 base** — maintenance outsourcing for a small agent-first team; anti-Tailwind prior dropped by choice; accepted trade-offs: Tailwind coupling, episodic major migrations, JS overlays (full record in the Decisions doc).

* **Nuxt layer over npm packages / copy-in / monorepo** — idiomatic Nuxt, no vendored drift, minimal release ceremony.

* **Hybrid growth model** — spike-first validation, then extraction-only growth: a component enters the layer only when a real app needs it, never speculatively.

* **ccm layer never duplicates a Nuxt UI widget** — lint-enforced; this is the anti-drift rule Varro's decay proved necessary.

* **MCP server deferred** until the manifest exists to feed it.

## Scope boundaries

Out of scope for this effort: Varro's 79-component migration (follow-on project once Social House proves the layer); the MCP server; a new-product starter template; non-Nuxt consumers.

## Success criteria

1. All three spike gates pass (R7).
2. A fresh Nuxt app can `extends` the layer and an agent builds a compliant page unprompted.
3. Social House runs on the layer in production with no visual regression.

## Assumptions & outstanding questions

* Shared-domain code lives inside the layer (revisit only if a non-Nuxt consumer appears).

* Adoption order: Social House first, Varro second.

* Docus coexistence is the main technical unknown — the spike answers it; a negative result forces a Social House shell decision before R9.

* Manifest schema shape is a plan-time decision.

## Sub-items

*Index added when the lfg breakdown is created.*

## Work breakdown (Plane, lfg cycles)

Module: "Design System Layer (ds-layer)" in the Projects space. Parent: [PRO-226](https://app.plane.so/ccm-design/browse/PRO-226/). Cycle order: PRO-227 scaffold → PRO-228 tokens → PRO-229 composition components / PRO-230 skin+gallery / PRO-231 domain foundations → PRO-232 agent-steering stack → PRO-233 validation spike (kill switch) → PRO-234 Social House adoption (runs in the SH repo, gated on GO).

**Every Layout review (2026-07-05):** all 13 layouts + 6 rudiments archived at ccm-ops/reference/every-layout/. Keep 7 (Stack, Cluster, Grid, Reel, Switcher, Cover, Frame — Switcher/Grid via container queries). Skip 6 as manifest anti-pattern entries: Icon→UIcon, Imposter→UModal/UPopover, Box→utilities+tokens, Center→UContainer, Container→CQ guidance, Sidebar→deferred to first real need.

Proof Shared Document

Proof Shared Document

This is a collaborative document on Proof. To read or edit it programmatically:

  • Fetch this URL with Accept: application/json to get content + API links.
  • Fetch this URL with Accept: text/markdown to get raw markdown.
  • Read endpoint: GET /api/agent/usd6lwl4/v3/document (one call: markdown + comments + suggestions)
  • Edit endpoint: POST /api/agent/usd6lwl4/v3/edit (intent-level operations including comment/suggest; no base token)
  • Bug reporting: POST /api/bridge/report_bug (or /d/usd6lwl4/bridge/report_bug)
  • Full API docs: /agent-docs
  • Open source reference: https://github.com/EveryInc/proof-sdk
  • No browser automation needed — use plain HTTP requests (curl/web_fetch).

Auth: If this URL includes ?token=, send it as Authorization: Bearer <token>.