feat(live): v2 sidecar upgrade + preserve per-project config on build

Unify the design-system panel's data shape around DESIGN.md frontmatter
as the primary source of truth; the sidecar carries only what Stitch's
frontmatter schema can't (extensions + live component HTML + narrative).
Also fix a long-standing build bug that destroyed per-project config.

Shape changes:
- Server /design-system.json now returns { parsed, sidecar, hasMd,
  hasSidecar, mdNewerThanJson, parseError?, sidecarError? }. No more
  mode switching; both layers ship when present and the panel merges.
- Panel consolidates renderSidecarVisual + renderParsedMdVisual into a
  single renderDesignVisual that merges frontmatter primitives with
  sidecar extensions.colorMeta / typographyMeta. Helpers for color,
  typography, radii model-building. Parsed-md narrative synthesis
  survives as a fallback when no sidecar.
- DESIGN.json rewritten at schemaVersion 2: extensions.{colorMeta,
  typographyMeta, shadows, motion, breakpoints} + components (with
  refersTo pointing back to frontmatter component keys) + narrative.
  Token primitives no longer duplicated in the sidecar.

Build fix:
- scripts/build.js:634 wiped .claude/skills/ (and every other harness
  dir) on each rebuild, then recopied from dist. After commit b0feed0
  unbundled per-project config.json from dist, the sync destroyed the
  user's live-mode config on every build without replacing it.
- Added stashPerProjectArtifacts / restorePerProjectArtifacts in
  scripts/lib/utils.js. Hoisted PER_PROJECT_SCRIPT_ARTIFACTS to a
  module-level export so build.js and readSourceFiles share one
  source of truth. Build now preserves config.json across the sync.

Verified in browser: panel renders 10 colors, 9 typography roles, 3
shadows, 6 grouped components, 9 rules, 25 do/don't items, all merged
correctly from frontmatter + v2 sidecar with zero console errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-23 12:09:55 -07:00
co-authored by Claude Opus 4.7
parent a5cd7bf859
commit c160ffc38d
27 changed files with 2063 additions and 1170 deletions
+118 -79
View File
@@ -1,14 +1,13 @@
{
"schemaVersion": 1,
"generatedAt": "2026-04-15T00:00:00Z",
"schemaVersion": 2,
"generatedAt": "2026-04-23T00:00:00Z",
"title": "Design System: Impeccable",
"tokens": {
"colors": [
{
"extensions": {
"colorMeta": {
"editorial-magenta": {
"role": "primary",
"name": "Editorial Magenta",
"value": "oklch(60% 0.25 350)",
"description": "The one vibrant voice. Primary CTAs, active navigation, live-state indicators.",
"displayName": "Editorial Magenta",
"description": "The one vibrant voice. Primary CTAs, active navigation, live-state indicators. Rarity is the design choice.",
"tonalRamp": [
"oklch(22% 0.12 350)",
"oklch(32% 0.18 350)",
@@ -20,11 +19,15 @@
"oklch(94% 0.04 350)"
]
},
{
"editorial-magenta-deep": {
"role": "accent",
"displayName": "Editorial Magenta Deep",
"description": "Hover/active state for Editorial Magenta. Small darkening, confirms interaction without shouting."
},
"warm-ash-cream": {
"role": "neutral",
"name": "Warm Ash Cream",
"value": "oklch(96% 0.005 350)",
"description": "Primary page background. Almost-imperceptible magenta tint.",
"displayName": "Warm Ash Cream",
"description": "Primary page background. Almost-imperceptible magenta tint that creates subconscious cohesion with the accent.",
"tonalRamp": [
"oklch(15% 0.005 350)",
"oklch(25% 0.005 350)",
@@ -36,11 +39,15 @@
"oklch(96% 0.005 350)"
]
},
{
"crisp-paper-white": {
"role": "neutral",
"name": "Deep Graphite",
"value": "oklch(10% 0 0)",
"description": "Primary text, body CTAs. Softer than pure black.",
"displayName": "Crisp Paper White",
"description": "Pure background for inverted text moments (white-on-dark CTAs) and maximum-contrast surfaces."
},
"deep-graphite": {
"role": "neutral",
"displayName": "Deep Graphite",
"description": "Primary text and primary-CTA background. Softer than pure black, reads as confident-but-not-aggressive on warm paper.",
"tonalRamp": [
"oklch(10% 0 0)",
"oklch(25% 0 0)",
@@ -51,64 +58,73 @@
"oklch(92% 0 0)",
"oklch(98% 0 0)"
]
},
"soft-charcoal": {
"role": "neutral",
"displayName": "Soft Charcoal",
"description": "Secondary text — taglines, hook paragraphs, supporting copy."
},
"mid-ash": {
"role": "neutral",
"displayName": "Mid Ash",
"description": "Tertiary text — micro-labels, captions, meta lines. Reads as intentionally recessed metadata."
},
"paper-mist": {
"role": "neutral",
"displayName": "Paper Mist",
"description": "Hairline borders, section dividers, barely-visible structural seams."
},
"magenta-whisper": {
"role": "accent",
"displayName": "Magenta Whisper",
"description": "Diffuse glow backdrop under accent elements on hover; subtle selection highlights."
},
"magenta-veil": {
"role": "accent",
"displayName": "Magenta Veil",
"description": "Stronger translucent tint for focus rings and emphasis shells."
}
],
"typography": [
{
"role": "display",
"name": "Display",
"family": "Cormorant Garamond",
"fallback": "Georgia, serif",
"weight": 300,
"style": "italic",
"sampleSize": "4.5rem",
"lineHeight": "1",
"purpose": "Hero title. Light italic for an author-signature feel."
},
"typographyMeta": {
"display": {
"displayName": "Display",
"purpose": "Hero title only. Light italic for an author-signature feel.",
"style": "italic"
},
{
"role": "body",
"name": "Body",
"family": "Instrument Sans",
"fallback": "system-ui, sans-serif",
"weight": 400,
"style": "normal",
"sampleSize": "1rem",
"lineHeight": "1.6",
"purpose": "All paragraph copy. Capped at 6575ch."
"headline": {
"displayName": "Headline",
"purpose": "Section headings. Larger editorial moments."
},
{
"role": "label",
"name": "Label",
"family": "Instrument Sans",
"fallback": "system-ui, sans-serif",
"weight": 500,
"style": "normal",
"sampleSize": "0.9rem",
"lineHeight": "1",
"letterSpacing": "0.05em",
"textTransform": "uppercase",
"purpose": "CTA labels. Short, declarative."
"title": {
"displayName": "Title",
"purpose": "Hero tagline / section leads. A quieter second display voice.",
"style": "italic"
},
{
"role": "mono",
"name": "Mono",
"family": "Space Grotesk",
"fallback": "ui-monospace, monospace",
"weight": 500,
"style": "normal",
"sampleSize": "0.75rem",
"lineHeight": "1.4",
"letterSpacing": "0.1em",
"textTransform": "uppercase",
"purpose": "Micro-labels: 'What's included', 'v3.0 Changelog'."
"body": {
"displayName": "Body",
"purpose": "Paragraph copy. Capped at 6575ch for readability."
},
"body-lead": {
"displayName": "Body Lead",
"purpose": "The one or two lead paragraphs on each page. Slightly relaxed leading."
},
"supporting": {
"displayName": "Supporting",
"purpose": "Captions, footnotes, supporting context."
},
"label": {
"displayName": "Label",
"purpose": "CTA labels. Short, declarative. Uppercase, letter-tracked."
},
"micro-label": {
"displayName": "Micro-Label",
"purpose": "\"Works with\", \"What's included\", \"v3.0 Changelog\"."
},
"mono": {
"displayName": "Monospace Meta",
"purpose": "Command names in inline prose, periodic-table tile labels."
}
],
"radii": [
{ "name": "sm", "value": "4px" },
{ "name": "md", "value": "8px" },
{ "name": "lg", "value": "12px" },
{ "name": "xl", "value": "16px" }
],
},
"shadows": [
{
"name": "Soft Hover Lift",
@@ -123,23 +139,43 @@
{
"name": "Accent Glow",
"value": "0 20px 60px oklch(60% 0.25 350 / 0.15)",
"purpose": "Magenta-tinted ambient shadow. Used sparingly."
"purpose": "Magenta-tinted ambient shadow under the rare magnetic moment. Used sparingly."
}
],
"spacing": [
{ "name": "xs", "value": "8px" },
{ "name": "sm", "value": "16px" },
{ "name": "md", "value": "24px" },
{ "name": "lg", "value": "32px" },
{ "name": "xl", "value": "48px" },
{ "name": "2xl", "value": "80px" },
{ "name": "3xl", "value": "120px" }
]
"motion": [
{
"name": "ease-out",
"value": "cubic-bezier(0.16, 1, 0.3, 1)",
"purpose": "Primary curve. Expo-out feel — objects decelerate smoothly."
},
{
"name": "ease-out-quint",
"value": "cubic-bezier(0.22, 1, 0.36, 1)",
"purpose": "Slightly sharper alternative. Use for transforms on compact elements."
},
{
"name": "duration-fast",
"value": "0.15s",
"purpose": "State transitions (color, opacity)."
},
{
"name": "duration-base",
"value": "0.3s",
"purpose": "Default for transforms and non-color changes."
},
{
"name": "duration-slow",
"value": "0.6s",
"purpose": "Orchestrated entrances."
}
],
"breakpoints": []
},
"components": [
{
"name": "Primary CTA",
"kind": "button",
"refersTo": "button-primary",
"description": "Sharp, squared, uppercase. The editorial signature.",
"html": "<button class=\"ds-btn-primary\">GET STARTED</button>",
"css": ".ds-btn-primary { display: inline-block; padding: 16px 48px; font-family: 'Instrument Sans', system-ui, sans-serif; font-size: 0.9rem; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; color: oklch(98% 0 0); background: oklch(10% 0 0); border: none; border-radius: 0; cursor: pointer; transition: transform 0.2s ease, background 0.2s ease; } .ds-btn-primary:hover { transform: translateY(-2px); background: oklch(60% 0.25 350); }"
@@ -154,6 +190,7 @@
{
"name": "Email Input",
"kind": "input",
"refersTo": "input-text",
"description": "Hairline border, transparent background, magenta focus glow.",
"html": "<input type=\"email\" class=\"ds-input-email\" placeholder=\"you@example.com\">",
"css": ".ds-input-email { width: 100%; padding: 10px 14px; font-family: 'Instrument Sans', system-ui, sans-serif; font-size: 0.9rem; color: oklch(10% 0 0); background: transparent; border: 1px solid oklch(92% 0 0); border-radius: 6px; outline: none; transition: border-color 0.15s ease, box-shadow 0.15s ease; } .ds-input-email::placeholder { color: oklch(55% 0 0); } .ds-input-email:focus { border-color: oklch(60% 0.25 350); box-shadow: 0 0 0 3px oklch(60% 0.25 350 / 0.15); }"
@@ -168,6 +205,7 @@
{
"name": "Site Navigation",
"kind": "nav",
"refersTo": "nav-link",
"description": "62px compact bar. No underline at rest; accent underline on active.",
"html": "<nav class=\"ds-nav\"><span class=\"ds-nav-brand\">/ Impeccable</span><div class=\"ds-nav-links\"><a href=\"#\" class=\"ds-nav-link ds-nav-active\">Home</a><a href=\"#\" class=\"ds-nav-link\">Docs</a><a href=\"#\" class=\"ds-nav-link\">Anti-patterns</a></div></nav>",
"css": ".ds-nav { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 12px 20px; background: oklch(96% 0.005 350); font-family: 'Instrument Sans', system-ui, sans-serif; } .ds-nav-brand { font-family: 'Cormorant Garamond', Georgia, serif; font-style: italic; font-size: 1.05rem; color: oklch(10% 0 0); } .ds-nav-links { display: flex; gap: 16px; } .ds-nav-link { font-size: 0.85rem; font-weight: 500; color: oklch(10% 0 0); text-decoration: none; padding-bottom: 3px; border-bottom: 1px solid transparent; transition: color 0.2s ease, border-color 0.2s ease; } .ds-nav-link:hover { color: oklch(60% 0.25 350); } .ds-nav-active { border-bottom-color: oklch(60% 0.25 350); }"
@@ -175,6 +213,7 @@
{
"name": "Feature Card",
"kind": "card",
"refersTo": "card-feature",
"description": "Standard card: 12px radius, hairline border, hover lift with soft shadow.",
"html": "<article class=\"ds-card\"><h3 class=\"ds-card-title\">/impeccable polish</h3><p class=\"ds-card-body\">Sweep a site for AI-tool tells and refine typography, spacing, color in one pass.</p></article>",
"css": ".ds-card { padding: 20px 22px; background: oklch(98% 0 0); border: 1px solid oklch(92% 0 0); border-radius: 12px; transition: transform 0.2s ease, box-shadow 0.2s ease; font-family: 'Instrument Sans', system-ui, sans-serif; } .ds-card:hover { transform: translateY(-2px); box-shadow: 0 4px 24px -4px rgba(0,0,0,0.12), 0 1px 3px rgba(0,0,0,0.06); } .ds-card-title { font-family: 'Space Grotesk', ui-monospace, monospace; font-size: 0.85rem; font-weight: 500; color: oklch(10% 0 0); margin: 0 0 6px; } .ds-card-body { font-size: 0.85rem; line-height: 1.55; color: oklch(25% 0 0); margin: 0; }"