{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Canvas metadata sidecar",
  "description": "Per-canvas .meta.json sidecar that mirrors Claude Design's `.design-canvas.state.json` pattern. Stores section + artboard labels, brief, and provenance separate from the HTML so file tree and handoff bundle can render rich descriptions without parsing JSX.",
  "type": "object",
  "additionalProperties": false,
  "required": ["title", "sections"],
  "properties": {
    "title": {
      "type": "string",
      "description": "Canvas project title (e.g. 'Match Recap' or 'Sparta Studio'). Shown as H1 in handoff bundle."
    },
    "subtitle": {
      "type": "string",
      "description": "One-line description of the canvas project's purpose."
    },
    "brief": {
      "type": "string",
      "description": "The full brief that produced this canvas (from /design:new). Preserved for handoff."
    },
    "brief_sha": {
      "type": "string",
      "description": "Hash of the brief that produced this canvas (stamped at /design:new step 11). Used by /design:new to short-circuit regeneration when a byte-identical brief in the same DS already produced a canvas."
    },
    "tags": {
      "type": "array",
      "uniqueItems": true,
      "items": { "type": "string" },
      "description": "Free-form labels for the canvas — feature slugs ('dark-mode'), surfaces ('settings'), or themes. Read by flow's /flow:plan design-canvas detection (Phase 11) to match a feature name against canvases via exact tag match (the canvas slug substring match is the other half of the heuristic). Optional."
    },
    "status": {
      "type": "string",
      "enum": ["draft", "in-review", "ready-for-handoff", "handed-off"],
      "default": "draft",
      "description": "Lifecycle state of the canvas. 'draft' = work in progress; 'in-review' = awaiting feedback; 'ready-for-handoff' = approved, eligible for /design:handoff; 'handed-off' = shipped to production code (see handoffCommit). Hand-set by the user, or flipped to 'handed-off' by flow's /flow:done handoff sweep (Phase 11) after a successful /design:handoff. Optional — absent means 'draft'."
    },
    "handoffCommit": {
      "type": "string",
      "description": "Git commit SHA (full or short) of the commit that handed this canvas off to production code. Written by /flow:done's handoff sweep when it flips status to 'handed-off'. Optional."
    },
    "platform": {
      "type": "string",
      "enum": ["desktop", "mobile", "tablet", "responsive", "tv", "watch", "other"],
      "description": "Primary intended platform for this canvas project."
    },
    "created": {
      "type": "string",
      "format": "date-time",
      "description": "ISO timestamp when /design:new generated the canvas."
    },
    "last_modified": {
      "type": "string",
      "format": "date-time",
      "description": "ISO timestamp of the last /design edit."
    },
    "sections": {
      "type": "array",
      "description": "Top-level sections inside the canvas (DCSection wrapper instances). Order matches reading order in the canvas.",
      "items": {
        "type": "object",
        "required": ["id", "label"],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable slug for the section (matches DCSection id prop)."
          },
          "label": { "type": "string", "description": "Human-readable section title." },
          "subtitle": {
            "type": "string",
            "description": "Optional one-line subtitle / brief for the section."
          },
          "artboards": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "label"],
              "additionalProperties": false,
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Stable slug (matches DCArtboard id prop)."
                },
                "label": {
                  "type": "string",
                  "description": "Artboard title shown in canvas + handoff."
                },
                "subtitle": { "type": "string", "description": "Optional short description." },
                "platform": {
                  "type": "string",
                  "enum": ["desktop", "mobile", "tablet", "watch", "tv"]
                },
                "width": { "type": "number", "description": "Design width in CSS px." },
                "height": { "type": "number", "description": "Design height in CSS px." },
                "screen_route": {
                  "type": "string",
                  "description": "Production route this artboard maps to (e.g. '/team/[id]/roster'). Optional — used by /design:handoff."
                }
              }
            }
          }
        }
      }
    },
    "iteration_count": {
      "type": "integer",
      "description": "Total /design iterations applied to this canvas. Maintained by the orchestrator."
    },
    "tokens_used": {
      "type": "array",
      "description": "CSS variables referenced by the canvas (auto-extracted on each /design:edit). Used by /design:handoff to know which tokens the canvas depends on.",
      "items": { "type": "string" }
    },
    "designSystem": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9-]*$",
      "description": "Which design system this canvas was built against. Kebab-case slug matching a name in config.designSystems[]. Read by skill 'design-system' (read mode) + design-system-completeness-critic + flow's design-system-guard to scope rules to the right tokens / philosophy. Optional in single-DS projects (falls back to config.defaultDesignSystem); REQUIRED in multi-DS projects to avoid cross-DS contamination."
    },
    "opt_out_scope": {
      "type": "string",
      "enum": ["palette", "aesthetic", "full"],
      "description": "Persisted DS opt-out scope for this canvas. Subsequent /design:edit iterations inherit this; --opt-out=<scope> on the flag overrides for that iteration and persists the new value. a11y is enforced at every scope.",
      "default": "palette"
    },
    "css_mode": {
      "type": "string",
      "enum": ["inline", "tailwind", "modules"],
      "description": "How this canvas writes CSS. 'inline' (MDCC-DSN/01 default) uses bespoke classes from `_components.css` + ad-hoc `style={{}}` for one-off values. 'tailwind' opts the canvas into Tailwind utilities (requires `bun-plugin-tailwind` on the dev-server). 'modules' uses a sibling `<Slug>.module.css`. The /design:handoff CSS-bundling path only fires for 'inline' (Phase 3.6 Task 12b).",
      "default": "inline"
    },
    "data_cd_id_version": {
      "type": "integer",
      "description": "Major version of the data-cd-id scheme this canvas was last transpiled against. Bumped when the ID derivation rule changes (renumbering the universe of IDs). Future-proofing for v2 of the inspector; v1 = current Bun.hash(componentName + ':' + idx) → 8 hex.",
      "default": 1,
      "minimum": 1
    },
    "layout": {
      "type": "object",
      "additionalProperties": false,
      "description": "Phase 4 canvas-v2 spatial layout. Optional — when absent the canvas runtime synthesizes a default grid (3 cols × 1280 × 820, 80 px gutter, alphabetical by DCArtboard id). Authoring state only — stripped from /design:handoff emitted registry items. Phase 4.2 (DDR-027): artboard size is JSX-authoritative (props.width / props.height on <DCArtboard>); `layout.artboards[]` persists positions only. Writes strip any incoming `w`/`h`; reads tolerate them for back-compat with Phase 4 default-grid snapshots.",
      "properties": {
        "artboards": {
          "type": "array",
          "description": "Per-DCArtboard world-coord rects keyed by `id`. ids not listed fall back to the default grid slot for their render-order index.",
          "items": {
            "type": "object",
            "required": ["id", "x", "y"],
            "additionalProperties": false,
            "properties": {
              "id": { "type": "string" },
              "x": { "type": "number" },
              "y": { "type": "number" },
              "w": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Read-only legacy field — Phase 4 default-grid snapshots wrote this; Phase 4.2 writers strip it. JSX width prop is the source of truth."
              },
              "h": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Read-only legacy field — see `w`."
              }
            }
          }
        }
      }
    },
    "viewport": {
      "type": "object",
      "required": ["x", "y", "zoom"],
      "additionalProperties": false,
      "description": "Phase 4 canvas-v2 pan/zoom state for the world plane. Optional — when absent the canvas runtime fits-to-screen on the artboard union. Authoring state — stripped from /design:handoff.",
      "properties": {
        "x": { "type": "number" },
        "y": { "type": "number" },
        "zoom": { "type": "number", "minimum": 0.1, "maximum": 4.0 }
      }
    },
    "ai_context": {
      "type": "object",
      "description": "Opt-in metadata for AI cold-read. Persists hard-won context that doesn't live in the canvas source itself — pinned decisions, known quirks, rationale for why this canvas exists. Read by /design:edit's pre-flight to seed the model. Hand-edited; never auto-filled (auto-fill would defeat the purpose).",
      "additionalProperties": false,
      "properties": {
        "pinned_decisions": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Short one-liners — decisions tried, accepted or rejected, that shaped the canvas (e.g. 'tried gradient hero — rejected: phosphor-dark theme clashed')."
        },
        "known_quirks": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Non-obvious behaviour callers might trip over (e.g. 'docs-article artboard intentionally taller than the rest — reserves room for fold-line annotations')."
        },
        "why_this_exists": {
          "type": "string",
          "description": "One paragraph explaining the canvas's reason for being. Surfaced in the generated JSDoc header as `@notes` when present."
        }
      }
    }
  }
}
