Skip to content

RFC: How Claude Handles OnRamp Design Docs & AI-Generated UI

StatusAccepted — R1–R4, R6, R7 implemented in this PR; R5 documented (follow-up)
NotePoint-in-time RFC. Brand values cited below (#893eff primary, the monotonic purple ramp, the --color-primary-500 token name) are superseded by the Daybreak/Midnight re-anchor (PR #9229) — the live source is design-system/DESIGN.md (Core Purple, --color-primary).
AuthorJeff Stolz
Created2026-06-01
JiraTBD

A factual map of how the design/AI tooling fits together, plus the refinement backlog (R1–R7). This PR implements the backlog and lands the design-system foundation team-wide, so the "Proposal" section below doubles as a changelog.

Summary

Over the last few PRs the design/AI tooling in this repo changed shape:

  • #8834 made design-system/DESIGN.md the canonical token source and added a generator that emits a PrimeVue preset + CSS/Tailwind/JSON artifacts.
  • #8828 added the opt-in /primevue migration skill + MCP server, and — after rebasing onto #8834 — dropped its own hand-written preset so everything points at the generated one.
  • This PR lands the design-system foundation (monotonic purple ramp, the org-app preset override layer, the body-text contrast guard) for the whole team, and implements the R1–R7 guidance backlog so a cold Claude session follows OnRamp's design direction without a skill being invoked.

How it works today

Token pipeline (single source of truth)

design-system/DESIGN.md            ← hand-edited canonical source (YAML frontmatter + markdown)
        │  bun run tokens   (design-system/scripts/transform-tokens.ts, @google/design.md linter)
        │  hash-gated via .tokens.hash; auto-runs on postinstall / predev / prebuild

design-system/generated/
   ├── preset.ts            → exports BrandPreset (org base) + PortalPrimeVuePreset (portal)
   ├── tokens.css           → :root CSS custom properties — runtime authority
   ├── tailwind.theme.css   → Tailwind v4 @theme block (literal values, build-time)
   └── tokens.json          → DTCG-format export (portability hedge, not consumed yet)
  • DESIGN.md carries tokens only. Primary = #893eff (--color-primary-500); also brand-purple #5d13fa, brand-green #5cefc5. The purple ramp is now monotonic (purple-50 #f4eeff → 500 #893eff → 950 #260a52).
  • Never hand-edit generated/*. Edit DESIGN.md, run bun run tokens (bun run tokens:force to bypass the hash gate), commit the regenerated artifacts.

App consumption (two presets, both layered)

AppActive presetImport pathcssLayerDark-mode selector
app/ui-organization/OrgPrimeVuePreset = definePreset(BrandPreset, {…})app/ui-organization/theme/preset.jstrue — layer order reset, primevue, portal-primevue-overrides, tw-utilities (base resets relocated into @layer reset).or-dark on <html>
app/ui-customer/PortalPrimeVuePreset (vendor-injectable, primary+surface only)app/ui-customer/theme/preset.js → re-exports generated presettrue (layered resets in theme/layers.css)#ui-customer.portal-theme-dark
  • Both apps run the layered model. Per the CSS spec an unlayered rule beats any layered rule regardless of specificity, so PrimeVue registers into the primevue layer and Tailwind utilities land in tw-utilities (last) — a utility on markup overrides a PrimeVue component without !important. Base element resets that match inside PrimeVue subtrees live in @layer reset (lowest priority); legacy component SCSS stays unlayered and keeps winning, so the page-by-page rollout is backward-compatible. The shared layer order is declared once in app/onramp/styles/tailwind.css. See ui-design-system.md's "The cssLayer model" section, app/ui-organization/index.js, and the primevue skill's theming-patterns.md.
  • Portal per-tenant branding: PortalPrimeVuePreset points primary.500 at var(--vendor-color). ui-organization has no vendor injection → falls back to #893eff.

App-level override layer (new)

The org app no longer consumes BrandPreset directly — it wraps it in OrgPrimeVuePreset (app/ui-organization/theme/preset.js). BrandPreset is the brand-token layer (same for every consumer, generated from DESIGN.md); OrgPrimeVuePreset is the app-level layer for semantics Aura doesn't ship and for deliberate, app-specific deviations:

  • Severity palettes (danger/warn/success/info) pinned to Aura Tag shades.
  • surface.ground — the page "ground" behind cards (scheme-aware).
  • togglebutton + button.text.* component tweaks.
  • Light-mode body text.color pin to var(--color-black-950). Aura derives body text from surface.700, which BrandPreset intentionally remaps to the brand black-700 (#808898) for surfaces/borders — too light to read as body text. The pin restores AA-readable type without reshaping the surface ramp (the ramp is a deliberate partial brand+Aura blend).

The pin is locked by app/ui-organization/theme/__tests__/preset-text-contrast.test.js (WCAG-AA body text on a white card). Where-to-fix-what rule: a brand value → fix in DESIGN.md + regenerate; an app-level semantic or deliberate deviation → add it to the app preset. Never patch at the call site with :dt/scoped CSS.

Skill system (three tiers of UI guidance)

SkillActivationAuthoritative for
frontend-developmentAlways-on (any Vue/CSS/icon work)Vue 3.5 patterns, --or- CSS vars, icon registry, OR/O conventions, auto-generated references
ui-customer-primevuePath-triggered (portal v2 paths)Portal PrimeVue v4 APIs, PortalPrimeVuePreset, visual-drift guardrail
primevueOpt-in (/primevue)OR/O → PrimeVue migration; has a now-measurable exit ramp
  • MCP: .mcp.json wires @primevue/mcp for live PrimeVue v4.5.4 docs.
  • Gap (now closed by R1): CLAUDE.md/AGENTS.md previously had no "UI & Design System" section, so a cold "what are our brand colors?" wouldn't reliably find DESIGN.md.

AI-generated UI handoff (Claude Design / artifacts → code)

  • No automated converter. Process: Claude generates a PrimeVue artifact → a human adapts it into Vue SFCs → brand compliance via skill guardrails + review. R3/R4 turn this from tribal knowledge into documented workflow.

Proposal — refinements (implemented in this PR unless noted)

R1 — Wire DESIGN.md into always-on context. ✅ Done. Added a "UI & Design System" section to AGENTS.md (symlinked as CLAUDE.md) naming DESIGN.md as canonical, the bun run tokens flow, the "never edit generated/*" rule, the preset/cssLayer model, and the app-level override layer.

R2 — Resolve the stray docs/claude-ai-skill-export/ bundle. ✅ Done. Deleted the untracked older copy of the primevue skill and added docs/claude-ai-skill-export/ to .gitignore so it can't be recommitted.

R3 — Add an "Agent Prompt Guide" to the design system. ✅ Done. New sibling design-system/AGENT-GUIDE.md (keeps the linted token file clean): stack, import aliases, the token-source-vs-app-override rule, cssLayer/dark-mode per app, "bundle is reference → emit SFCs," guardrails, and prompt templates. Plus a "Semantic & component usage" section — the "which token/severity where" layer — codifying the proven Projects-page conventions (role→severity map, token-or-severity-never-hex, DataTable data-driven pattern, the registry+composable+wrapper meta-pattern for customizable pages, and passthrough discipline) so developers building without designer guidance ship consistently.

R4 — Document the AI-design → Vue SFC workflow. ✅ Done. New docs/design-system/ai-design-to-vue-workflow.md: a repeatable playbook with the light/dark/vendor-color verification checklist.

R5 — Confirm the two token pipelines can't drift. ⚠️ Investigated — drift confirmed; reconciliation is a follow-up. Finding: the two pipelines read different sources.

  • bun run tokens reads design-system/DESIGN.md.
  • bun run context:fabricate:ui (devtools/context-fabrication/ui/) reads legacy app/onramp/styles/maps/colors.json and app/onramp/styles/main.scssnot DESIGN.md — and emits the frontend-development skill's references/design-system/*.md.

They have already drifted: main.scss still carries --or-brand-purple: #5d13fa (legacy), which surfaces in the generated css-variables.md, while DESIGN.md's primary is #893eff. (The --or-brand-primary: #893eff value is aligned, so the primary itself matches; the purple ramp and brand-purple anchor do not.) Recommended follow-up: point context:fabricate:ui at DESIGN.md (or consume design-system/generated/tokens.json) instead of the legacy SCSS/JSON, or add a CI check asserting the two agree. Deferred from this PR to avoid entangling the guidance work with a rewrite of the legacy fabricate pipeline.

R6 — Make the primevue skill exit ramp measurable. ✅ Done. Added a migration-progress signal (count of remaining OR/O imports under app/ui-organization + app/ui-customer/pages/private; baseline 616 as of 2026-06-01) and a concrete fold-into-frontend-development trigger (< ~25 imports, or contradictory preference language, or portal adopts the brand stack).

R7 — Keep one canonical brand-color reference. ✅ Done. Updated .agents/skills/primevue/references/theming-patterns.md to cite token names (--color-primary-500) rather than restating hex, and to reflect the OrgPrimeVuePreset override layer + the text.color pin so the skill never advises something that overrides these decisions.

Alternatives considered

  • Adopt the 9-section "Stitch" DESIGN.md format. Rejected: #8834 shipped a token-focused DESIGN.md driven by the @google/design.md linter; R3 adds the agent-guide value as a sibling file without re-tooling the generator.
  • Fix body text by reshaping the surface ramp. Rejected: the surface ramp is an intentional partial brand+Aura blend; the targeted text.color pin at the app layer is surgical and is locked by the contrast guard.
  • Leave DESIGN.md skill-only (status quo). Rejected: the cold-prompt gap (R1) is cheap to close and high-value.

Risks & open questions

  • R5 reconciliation is the one open item — until context:fabricate:ui and bun run tokens share a source, the always-on skill's token docs can drift from the generated preset. Tracked as follow-up.

Verification (this PR)

  • After editing DESIGN.md: bun run tokens:force regenerates design-system/generated/* with no linter errors; the diff shows the intended monotonic purple ramp only.
  • Contrast guard: run preset-text-contrast.test.js (CI: node + default pool; locally on a bun-only machine use --pool=forks).
  • bun run build succeeds.
  • Live preview (https://localhost:3000/dashboard/): a non-Projects org-admin page in light and dark (.or-dark) — body text is a dark neutral (--p-text-color#2b3954), severities/buttons render as tonal chips.
  • Cold-prompt check (R1): a fresh Claude session asked "what's our brand primary color and where is it defined?" finds DESIGN.md without invoking a skill.

Internal documentation — gated behind Cloudflare Access.