{"version":3,"sources":["../src/index.ts","../src/diagnose.ts","../src/shared.ts","../src/fix.ts","../src/layer4.ts"],"sourcesContent":["// @domphy/doctor — static analyzer for Domphy element trees. Catches\n// non-idiomatic patterns (inline typography, literal theme colors, unknown\n// tones, void-tag content, missing/duplicate/unstable _key on lists, unknown\n// tags) so humans and AI agents get a feedback loop to self-correct generated\n// code. `validate()` is the aggregate entry point.\n\nexport type {\n  CustomRule,\n  DiagnoseOptions,\n  Diagnostic,\n  RuleCategory,\n  Severity,\n  ValidationReport,\n  ValidationSummary,\n} from \"./diagnose.js\";\nexport { diagnose, format, validate } from \"./diagnose.js\";\nexport type { AppliedFix, FixResult } from \"./fix.js\";\nexport { fix } from \"./fix.js\";\nexport type { Layer4Options } from \"./layer4.js\";\nexport { auditOutput } from \"./layer4.js\";\n","import { cssRgbToRgb, hexToRgb, labToLch, rgbToLab } from \"@domphy/palette\";\nimport { findTag, isPlainObject, VOID } from \"./shared.js\";\n\nexport type Severity = \"error\" | \"warning\" | \"info\";\n\n/**\n * Broad structural category for a rule. Mirrors Biome's lint category model.\n * Built-in rules always set this; custom rules may omit it.\n */\nexport type RuleCategory =\n  | \"structure\" // void-content, unknown-tag\n  | \"key\" // missing-key, duplicate-key, unstable-key\n  | \"theme\" // raw-theme-value, raw-spacing-value\n  | \"typography\" // inline-typography\n  | \"data-attr\" // unknown-tone, middle-surface-anchor, unknown-density, unknown-size\n  | \"visual\"; // low-opacity\n\nexport interface Diagnostic {\n  /** Rule id, e.g. \"inline-typography\". */\n  rule: string;\n  severity: Severity;\n  /**\n   * Broad structural category. Built-in rules always set this.\n   * Custom rules may omit it.\n   */\n  category?: RuleCategory;\n  /** Human path to the offending node, e.g. \"div > ul > li\". */\n  path: string;\n  message: string;\n  /** How to fix it. */\n  hint?: string;\n}\n\n/**\n * A custom rule that extends the doctor with project-specific checks.\n * Custom rules run alongside the built-in rules; their ids must not\n * clash with any built-in id.\n *\n * @example\n * ```ts\n * const noEmptyContent: CustomRule = {\n *   id: \"no-empty-content\",\n *   severity: \"warning\",\n *   category: \"structure\",\n *   check: (element, path, tag) => {\n *     if (element[tag] === \"\") {\n *       return [{ message: `Empty string on <${tag}> — use null or text.` }]\n *     }\n *     return []\n *   },\n * }\n *\n * diagnose(tree, { rules: [noEmptyContent] })\n * ```\n */\nexport interface CustomRule {\n  /** Unique id shown in diagnostics. Must not clash with any built-in rule id. */\n  id: string;\n  /** Default severity for violations produced by this rule. */\n  severity: Severity;\n  /** Category for display and filtering. Optional. */\n  category?: RuleCategory;\n  /**\n   * Called once per element node (nodes that have a valid HTML/SVG tag).\n   * Return an array of violation descriptors. The engine fills in `rule`,\n   * `severity`, `category`, and `path`; provide `message` and optionally\n   * `hint`. Pass `severity` in the descriptor to override the rule default.\n   */\n  check: (\n    element: Record<string, unknown>,\n    path: string,\n    tag: string,\n  ) => Array<{ message: string; hint?: string; severity?: Severity }>;\n}\n\nexport interface DiagnoseOptions {\n  /**\n   * Invoke reactive content functions `(listener) => …` with a no-op listener to\n   * analyze their output (catches missing `_key` in dynamic lists). Default true.\n   * Set false if your reactive functions have side effects.\n   */\n  runReactive?: boolean;\n  /**\n   * If set, only emit diagnostics whose rule id is in this list.\n   * Takes precedence over `exclude`.\n   * Applies to both built-in and custom rules.\n   */\n  only?: string[];\n  /**\n   * Rule ids to skip entirely.\n   * Ignored when `only` is also set.\n   * Applies to both built-in and custom rules.\n   */\n  exclude?: string[];\n  /**\n   * Additional custom rules to run alongside the built-in rules.\n   * Custom rule ids are also subject to `only`/`exclude` filtering.\n   */\n  rules?: CustomRule[];\n}\n\nconst RESERVED = new Set([\n  \"$\",\n  \"style\",\n  \"_key\",\n  \"_portal\",\n  \"_context\",\n  \"_metadata\",\n  \"_doctorDisable\", // suppress annotation — treated as metadata, not a tag candidate\n]);\n\n// Typography style properties that must not be set inline — use patches instead.\n// Expanded from bench data: fontFamily + textDecoration were missing and caused\n// agents to write { style: { fontFamily: \"...\" } } without correction.\nconst TYPOGRAPHY_STYLE = new Set([\n  \"fontSize\",\n  \"lineHeight\",\n  \"fontWeight\",\n  \"letterSpacing\",\n  \"fontFamily\",\n  \"textDecoration\",\n]);\n\n// Color-bearing style props that should resolve through a theme token rather\n// than a literal value, so theming and dark mode apply. Shorthands\n// (background/border/outline) are included because they often carry a color.\nconst COLOR_STYLE = new Set([\n  \"color\",\n  \"backgroundColor\",\n  \"background\",\n  \"borderColor\",\n  \"border\",\n  \"outlineColor\",\n  \"outline\",\n  \"fill\",\n  \"stroke\",\n]);\n\n// Direct (non-shorthand) color-only style properties. For these, ANY plain\n// string value that is not a CSS function or semantic keyword is treated as a\n// raw color — not just hex/rgb, but also named CSS colors like \"red\" or \"white\"\n// that agents frequently write instead of themeColor().\nconst DIRECT_COLOR_PROPS = new Set([\n  \"color\",\n  \"fill\",\n  \"stroke\",\n  \"backgroundColor\",\n  \"outlineColor\",\n  \"borderColor\",\n  \"caretColor\",\n  \"accentColor\",\n  \"columnRuleColor\",\n  \"textDecorationColor\",\n]);\n\n// CSS keyword values that carry no color meaning. These must never be flagged\n// even though they appear on color properties.\nconst CSS_SEMANTIC_VALUES = new Set([\n  \"transparent\",\n  \"currentcolor\",\n  \"inherit\",\n  \"initial\",\n  \"unset\",\n  \"none\",\n  \"auto\",\n  \"revert\",\n  \"revert-layer\",\n  \"\",\n]);\n\n// A literal color value: hex (#rgb … #rrggbbaa) or an rgb()/rgba()/hsl()/hsla()\n// function. Keywords like transparent/currentColor/inherit are intentionally\n// allowed — they carry no theme meaning.\nconst LITERAL_COLOR = /#[0-9a-fA-F]{3,8}\\b|\\b(?:rgba?|hsla?)\\s*\\(/;\n\n// Spacing style properties where literal rem/em/px values should use themeSpacing().\n// These are layout, not typography, but themeSpacing() ensures density consistency.\n// Logical properties (paddingBlock, paddingInline, etc.) are included — they are\n// used in Domphy patches and must also go through themeSpacing() for density scaling.\nconst SPACING_STYLE = new Set([\n  \"margin\",\n  \"marginTop\",\n  \"marginRight\",\n  \"marginBottom\",\n  \"marginLeft\",\n  \"marginInline\",\n  \"marginBlock\",\n  \"marginInlineStart\",\n  \"marginInlineEnd\",\n  \"marginBlockStart\",\n  \"marginBlockEnd\",\n  \"padding\",\n  \"paddingTop\",\n  \"paddingRight\",\n  \"paddingBottom\",\n  \"paddingLeft\",\n  \"paddingInline\",\n  \"paddingBlock\",\n  \"paddingInlineStart\",\n  \"paddingInlineEnd\",\n  \"paddingBlockStart\",\n  \"paddingBlockEnd\",\n  \"gap\",\n  \"rowGap\",\n  \"columnGap\",\n]);\n\n// Matches literal spacing values like \"16px\", \"1.5rem\", \"2em\" but not \"auto\",\n// \"inherit\", \"0\" (unitless zero is fine), or computed values.\nconst LITERAL_SPACING = /^(\\d+(?:\\.\\d+)?)(rem|em|px)$/;\n\n// Semantic tone aliases, mirrored from @domphy/theme's `ToneAliases`\n// (packages/theme/src/tone.ts). Doctor has no runtime dependency on\n// @domphy/theme — this is a static duplicate kept in sync by hand, since the\n// doctor only needs the grammar (name -> underlying shift-N), not resolution.\nconst TONE_ALIASES: Record<string, string> = {\n  surface: \"shift-1\",\n  hover: \"shift-2\",\n  border: \"shift-3\",\n  \"border-strong\": \"shift-4\",\n  muted: \"shift-8\",\n  text: \"shift-9\",\n};\n\n// Parses \"increase-N\" / \"decrease-N\" / \"shift-N\" — or a semantic alias that\n// resolves to one of those — into family + numeric offset. Returns null when\n// the pattern doesn't match (grammar error).\nfunction parseOffset(\n  value: string,\n): { family: \"increase\" | \"decrease\" | \"shift\"; n: number } | null {\n  const resolved = TONE_ALIASES[value] ?? value;\n  const m = resolved.match(/^(increase|decrease|shift)-(\\d+)$/);\n  if (!m) return null;\n  return {\n    family: m[1] as \"increase\" | \"decrease\" | \"shift\",\n    n: parseInt(m[2], 10),\n  };\n}\n\n// Valid `dataTone` grammar AND range:\n//   \"inherit\", \"base\", a bare integer, a semantic alias (surface/hover/border/\n//   border-strong/muted/text), or shift-N/increase-N/decrease-N where N ≤ 17.\n//   The default Domphy theme has 18 tone steps (0–17). Values with valid grammar\n//   but N > 17 are also rejected here so they surface as `unknown-tone` errors.\nfunction isValidTone(value: string): boolean {\n  if (value === \"inherit\" || value === \"base\") return true;\n  if (/^-?\\d+$/.test(value)) return true;\n  const parsed = parseOffset(value);\n  if (!parsed) return false;\n  return parsed.n <= 17; // tone ramp has 18 steps: 0–17\n}\n\n// ─── Chromametry integration ─────────────────────────────────────────────────\n\n/**\n * Parses a CSS color literal (hex or rgb/rgba) into LCH [L, C, h].\n * Returns null if parsing fails or the format is unsupported (named colors, hsl).\n * Uses @domphy/palette math (CIELAB via D65 reference white).\n */\nfunction parseLiteralToLch(value: string): [number, number, number] | null {\n  try {\n    const trimmed = value.trim();\n    let rgb: number[];\n\n    if (trimmed.startsWith(\"#\")) {\n      let hex = trimmed;\n      if (hex.length === 9) hex = hex.slice(0, 7); // strip alpha #rrggbbaa → #rrggbb\n      if (hex.length === 5) hex = hex.slice(0, 4); // strip alpha #rgba → #rgb\n      if (hex.length === 4) {\n        hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;\n      }\n      if (hex.length !== 7) return null;\n      rgb = hexToRgb(hex);\n    } else if (/^rgba?\\s*\\(/.test(trimmed)) {\n      rgb = cssRgbToRgb(trimmed);\n    } else {\n      return null; // hsl, named colors, custom-properties — skip\n    }\n\n    const lab = rgbToLab(rgb);\n    const lch = labToLch(lab);\n    return [lch[0], lch[1], lch[2]];\n  } catch {\n    return null;\n  }\n}\n\n// Pulls the first parseable color token out of a (possibly shorthand) value.\n// Shorthands such as \"1px solid #ccc\" or \"0 0 4px rgba(0,0,0,.5)\" embed the\n// color among other tokens, so `parseLiteralToLch` (which expects the value to\n// START with the color) would otherwise miss it. Matches a #hex literal or a\n// complete rgb()/rgba() call (with its arguments). hsl()/named colors are left\n// for the generic fallback, matching parseLiteralToLch's own coverage.\nconst EMBEDDED_COLOR = /#[0-9a-fA-F]{3,8}\\b|rgba?\\s*\\([^)]*\\)/;\n\nfunction extractColorLiteral(value: string): string | null {\n  const match = EMBEDDED_COLOR.exec(value);\n  return match ? match[0] : null;\n}\n\n/**\n * Converts LCH coordinates into a concrete `themeColor()` call suggestion plus\n * a perceptual description. The tone and color-family are approximations for the\n * default Domphy theme (light, 10 neutral tones, base at mid-lightness).\n */\nfunction buildColorHint(lch: [number, number, number]): string {\n  const [L, C, h] = lch;\n\n  // Map lightness to a Domphy tone relative to base (~L50).\n  // Each step ≈ 10 lightness units — clamp to ±9 (max offset in a 10-step ramp).\n  const rawOffset = Math.round((L - 50) / 10);\n  const offset = Math.max(-9, Math.min(9, rawOffset));\n  let toneStr: string;\n  if (Math.abs(offset) <= 1) toneStr = '\"base\"';\n  else if (offset < 0) toneStr = `\"decrease-${Math.abs(offset)}\"`;\n  else toneStr = `\"increase-${offset}\"`;\n\n  // Infer the most likely semantic color family from chroma + hue.\n  let colorFamily: string;\n  if (C < 12) colorFamily = \"neutral\";\n  else if (h < 30 || h >= 330)\n    colorFamily = \"error\"; // red spectrum\n  else if (h < 75)\n    colorFamily = \"warning\"; // orange-yellow\n  else if (h < 165)\n    colorFamily = \"success\"; // green\n  else if (h < 265)\n    colorFamily = \"primary\"; // blue-indigo\n  else colorFamily = \"primary\"; // violet → treat as primary\n\n  return (\n    `(l) => themeColor(l, ${toneStr}, \"${colorFamily}\") ` +\n    `[perceptual LCH L=${Math.round(L)} C=${Math.round(C)} h=${Math.round(h)}°]`\n  );\n}\n\n/**\n * Converts a literal spacing value like \"16px\" / \"1.5rem\" / \"2em\" into a\n * themeSpacing(n) suggestion. themeSpacing(n) = n/4 em, so n=4 → 1em ≈ 16px.\n */\nfunction buildSpacingHint(prop: string, value: string): string | null {\n  const match = LITERAL_SPACING.exec(value);\n  if (!match) return null;\n  const amount = parseFloat(match[1]);\n  const unit = match[2];\n  let n: number;\n  if (unit === \"rem\" || unit === \"em\") {\n    n = Math.round(amount * 4);\n  } else {\n    // px: assume default 16px/rem → 1em = 16px\n    n = Math.round(amount / 4);\n  }\n  if (n <= 0) return null;\n  return `${prop}: themeSpacing(${n})  — themeSpacing(n)=n/4em, so ${n}/4=${n / 4}em ≈ ${value} at default density`;\n}\n\n// ─── Style resolution helpers ─────────────────────────────────────────────────\n\n/**\n * Resolves a style property's value without building any live UI object: a\n * literal string passes through; a reactive `(listener) => value` function is\n * invoked with a bare no-op listener (the same pattern the tone-background-inherit\n * and low-contrast checks below already use), never by constructing a real\n * ElementNode. An earlier version built a full recursive ElementNode per element\n * just to read one resolved string — that recurses into every descendant and\n * fires Init/Insert lifecycle hooks on a detached subtree, and leaks a State\n * listener per reactive prop per element visited. Returns null for non-string\n * results, or when `runReactive` is false and the value is a function.\n */\nfunction resolveStyleValue(value: unknown, runReactive: boolean): string | null {\n  if (typeof value === \"string\") return value;\n  if (typeof value !== \"function\" || !runReactive) return null;\n  try {\n    const result = (value as (l: unknown) => unknown)(() => {});\n    return typeof result === \"string\" ? result : null;\n  } catch {\n    return null; // reactive fn threw without a real runtime — skip\n  }\n}\n\n/**\n * True when `style` declares `prop` as an own, non-nested value — mirrors\n * StyleList.addCSS's own split: a plain-object value under a style key is a\n * nested selector block (e.g. `&:hover`), not a literal/reactive style value.\n */\nfunction hasStyleProp(style: Record<string, unknown>, prop: string): boolean {\n  return prop in style && !isPlainObject(style[prop]);\n}\n\n/**\n * Extract the numeric tone step from a resolved CSS var string like `var(--neutral-9)`.\n * Returns null when the pattern doesn't match.\n */\nfunction extractToneStep(value: string): number | null {\n  const match = value.match(/var\\(--[\\w-]+-(\\d+)\\)$/);\n  return match ? parseInt(match[1], 10) : null;\n}\n\n// ─── Suppress helper ─────────────────────────────────────────────────────────\n\n/**\n * Applies `_doctorDisable` filtering. `elementDiags` are diagnostics produced\n * directly by this element's checks (always subject to suppression). `contentDiags`\n * are diagnostics produced by walking the element's reactive content (array-level\n * rules like missing-key fire at the element's path, so they are also suppressed\n * when they match `here`; deeper-nested diagnostics pass through unconditionally).\n */\nfunction applyDisable(\n  disable: unknown,\n  elementDiags: Diagnostic[],\n  contentDiags: Diagnostic[],\n  here: string,\n  out: Diagnostic[],\n): void {\n  if (disable === true) {\n    // Suppress all diagnostics at this path (element-level + array-level).\n    // Let diagnostics from deeper nodes through unconditionally.\n    for (const d of contentDiags) {\n      if (d.path !== here) out.push(d);\n    }\n    return;\n  }\n  if (disable !== undefined && disable !== null && disable !== false) {\n    const disabled = new Set(\n      Array.isArray(disable) ? (disable as string[]) : [String(disable)],\n    );\n    for (const d of elementDiags) {\n      if (!disabled.has(d.rule)) out.push(d);\n    }\n    for (const d of contentDiags) {\n      // Only suppress at THIS element's path; deeper diagnostics pass through.\n      if (d.path === here && disabled.has(d.rule)) continue;\n      out.push(d);\n    }\n    return;\n  }\n  // No disable — pass everything through.\n  out.push(...elementDiags);\n  out.push(...contentDiags);\n}\n\n// ─── Tree walkers ─────────────────────────────────────────────────────────────\n\n/** Statically analyzes a Domphy element tree and returns idiomatic-usage diagnostics. */\nexport function diagnose(\n  root: unknown,\n  options: DiagnoseOptions = {},\n): Diagnostic[] {\n  const out: Diagnostic[] = [];\n  walk(root, \"\", out, false, options);\n\n  // Apply only/exclude post-filter (covers both built-in and custom rule ids).\n  // `only` being set (even empty) activates whitelist mode: only listed rule ids pass.\n  if (options.only !== undefined) {\n    if (options.only.length === 0) return [];\n    const only = new Set(options.only);\n    return out.filter((d) => only.has(d.rule));\n  }\n  if (options.exclude && options.exclude.length > 0) {\n    const exclude = new Set(options.exclude);\n    return out.filter((d) => !exclude.has(d.rule));\n  }\n  return out;\n}\n\nfunction walk(\n  node: unknown,\n  path: string,\n  out: Diagnostic[],\n  dynamic: boolean,\n  options: DiagnoseOptions,\n): void {\n  const runReactive = options.runReactive !== false;\n\n  if (typeof node === \"function\") {\n    if (!runReactive) return;\n    let result: unknown;\n    try {\n      result = (node as (listener: unknown) => unknown)(() => {});\n    } catch {\n      return; // reactive fn threw without a real runtime — skip\n    }\n    walk(result, path, out, true, options);\n    return;\n  }\n\n  if (Array.isArray(node)) {\n    const elementItems = node.filter(\n      (child) => isPlainObject(child) && findTag(child),\n    ) as Record<string, unknown>[];\n\n    if (dynamic) {\n      if (\n        elementItems.length > 1 &&\n        elementItems.some((item) => item._key === undefined)\n      ) {\n        out.push({\n          rule: \"missing-key\",\n          severity: \"warning\",\n          category: \"key\",\n          path: path || \"(list)\",\n          message:\n            \"Dynamic list child without `_key` — reordered/keyed lists need a stable `_key` for correct reconcile.\",\n          hint: \"Add `_key: <stable id>` to each item produced by the reactive function.\",\n        });\n      }\n\n      // unstable-key (heuristic): in a dynamic list every `_key` equals its\n      // sibling position (0, 1, 2, …). That is the runtime footprint of\n      // `items.map((item, i) => ({ …, _key: i }))` — an array-index key, which\n      // defeats the point of keying because keys shift when the list reorders.\n      if (\n        elementItems.length > 1 &&\n        elementItems.every((item, index) => item._key === index)\n      ) {\n        out.push({\n          rule: \"unstable-key\",\n          severity: \"warning\",\n          category: \"key\",\n          path: path || \"(list)\",\n          message:\n            \"Dynamic list `_key` values are the array index (0, 1, 2, …) — index keys are unstable across reorders/inserts.\",\n          hint: \"Key by a stable identity from the data (e.g. `_key: item.id`), not the loop index.\",\n        });\n      }\n    }\n\n    // duplicate-key: two siblings sharing the same `_key` value break reconcile\n    const seenKeys = new Map<string, number>();\n    for (const item of elementItems) {\n      const key = item._key;\n      if (key === undefined || key === null) continue;\n      const literalKey = `${typeof key}:${String(key)}`;\n      seenKeys.set(literalKey, (seenKeys.get(literalKey) ?? 0) + 1);\n    }\n    for (const [literalKey, count] of seenKeys) {\n      if (count > 1) {\n        const value = literalKey.slice(literalKey.indexOf(\":\") + 1);\n        out.push({\n          rule: \"duplicate-key\",\n          severity: \"error\",\n          category: \"key\",\n          path: path || \"(list)\",\n          message: `Duplicate \\`_key\\` \"${value}\" among ${count} siblings — keys must be unique within a list.`,\n          hint: \"Give each sibling a distinct stable `_key` (e.g. a record id, not a constant).\",\n        });\n      }\n    }\n\n    node.forEach((child, index) => {\n      walk(child, `${path}[${index}]`, out, false, options);\n    });\n    return;\n  }\n\n  if (!isPlainObject(node)) return;\n\n  const element = node;\n  const tag = findTag(element);\n  const here = tag ? (path ? `${path} > ${tag}` : tag) : path || \"(root)\";\n\n  // Collect element-level diagnostics in a local buffer so `_doctorDisable`\n  // can filter them before they reach `out`.\n  const elementDiags: Diagnostic[] = [];\n\n  if (!tag) {\n    const contentKeys = Object.keys(element).filter(\n      (key) =>\n        !RESERVED.has(key) &&\n        !key.startsWith(\"_on\") &&\n        !key.startsWith(\"on\") &&\n        !key.startsWith(\"data\") &&\n        !key.startsWith(\"aria\"),\n    );\n    if (contentKeys.length === 1) {\n      elementDiags.push({\n        rule: \"unknown-tag\",\n        severity: \"warning\",\n        category: \"structure\",\n        path: here,\n        message: `\"${contentKeys[0]}\" is not a known HTML/SVG tag — likely a typo.`,\n        hint: \"An element's first key must be a valid tag (div, button, span, …).\",\n      });\n    }\n    applyDisable(element._doctorDisable, elementDiags, [], here, out);\n    return;\n  }\n\n  const content = element[tag];\n\n  if (VOID.has(tag) && content !== null && content !== undefined) {\n    elementDiags.push({\n      rule: \"void-content\",\n      severity: \"error\",\n      category: \"structure\",\n      path: here,\n      message: `Void tag \"${tag}\" must have null content (got ${Array.isArray(content) ? \"array\" : typeof content}).`,\n      hint: `Write { ${tag}: null, … } and put attributes as sibling keys.`,\n    });\n  }\n\n  // walkStyleProps: checks a flat style object (or pseudo-class nested style\n  // like \"&:hover\") for theme/visual violations. Called for the element's own\n  // style AND for any nested pseudo-class objects found inside it.\n  const walkStyleProps = (style: Record<string, unknown>, stylePath: string) => {\n    for (const prop in style) {\n      // Skip nested pseudo-class objects at this level — they are walked\n      // separately after the outer loop so their rules fire at the right path.\n      if (prop.startsWith(\"&\") || prop.startsWith(\":\")) continue;\n      const value = style[prop];\n\n      if (TYPOGRAPHY_STYLE.has(prop) && typeof value !== \"function\") {\n        elementDiags.push({\n          rule: \"inline-typography\",\n          severity: \"warning\",\n          category: \"typography\",\n          path: stylePath,\n          message: `Inline \\`${prop}\\` — avoid inline typography styles.`,\n          hint: \"Use a typography patch (paragraph()/heading()/small()/strong()/…) via $ so the theme owns the type scale.\",\n        });\n      }\n\n      if (COLOR_STYLE.has(prop) && typeof value === \"string\" && LITERAL_COLOR.test(value)) {\n        const colorLiteral = extractColorLiteral(value) ?? value;\n        const lch = parseLiteralToLch(colorLiteral);\n        const colorHint = lch ? buildColorHint(lch) : \"(l) => themeColor(l, tone, colorName)\";\n        elementDiags.push({\n          rule: \"raw-theme-value\",\n          severity: \"info\",\n          category: \"theme\",\n          path: stylePath,\n          message: `Inline \\`${prop}\\` uses a literal color (${value}).`,\n          hint: `Prefer a theme token — ${colorHint} — so theming and dark mode apply.`,\n        });\n      }\n\n      if (\n        DIRECT_COLOR_PROPS.has(prop) &&\n        typeof value === \"string\" &&\n        !LITERAL_COLOR.test(value) &&\n        !value.includes(\"(\") &&\n        !value.startsWith(\"--\") &&\n        !CSS_SEMANTIC_VALUES.has(value.trim().toLowerCase())\n      ) {\n        elementDiags.push({\n          rule: \"raw-theme-value\",\n          severity: \"info\",\n          category: \"theme\",\n          path: stylePath,\n          message: `Inline \\`${prop}\\` uses a CSS named color (\"${value}\").`,\n          hint: `CSS named colors like \"${value}\" bypass theming and dark mode. Prefer (l) => themeColor(l, tone, colorName).`,\n        });\n      }\n\n      if (SPACING_STYLE.has(prop) && typeof value === \"string\") {\n        const spacingHint = buildSpacingHint(prop, value);\n        if (spacingHint) {\n          elementDiags.push({\n            rule: \"raw-spacing-value\",\n            severity: \"info\",\n            category: \"theme\",\n            path: stylePath,\n            message: `Inline \\`${prop}: \"${value}\"\\` uses a literal spacing value.`,\n            hint: `Prefer themeSpacing() for theme density: ${spacingHint}`,\n          });\n        }\n      }\n\n    }\n  };\n\n  if (isPlainObject(element.style)) {\n    const style = element.style as Record<string, unknown>;\n    walkStyleProps(style, here);\n    // Walk pseudo-class nested objects (&:hover, &:focus, &:active, etc.)\n    for (const prop in style) {\n      if ((prop.startsWith(\"&\") || prop.startsWith(\":\")) && isPlainObject(style[prop])) {\n        walkStyleProps(style[prop] as Record<string, unknown>, `${here}[${prop}]`);\n      }\n    }\n\n    // low-opacity: only checked on the MAIN style (not pseudo-classes), because\n    // hover/focus states intentionally enhance or reveal — opacity inside &:hover\n    // is the UX response, not the resting UX. Reactive opacity functions are\n    // skipped (can't evaluate without a real runtime).\n    const opacityValue = style.opacity;\n    if (typeof opacityValue === \"string\") {\n      const opacity = parseFloat(opacityValue);\n      if (!Number.isNaN(opacity) && opacity > 0 && opacity < 0.6) {\n        const hoverStyle = style[\"&:hover\"] as Record<string, unknown> | undefined;\n        const hoverOpacity = hoverStyle?.opacity;\n        const hasFullHoverRestore = hoverOpacity === \"1\" || hoverOpacity === 1;\n        elementDiags.push({\n          rule: \"low-opacity\",\n          severity: hasFullHoverRestore ? \"info\" : \"warning\",\n          category: \"visual\",\n          path: here,\n          message: `\\`style.opacity: \"${opacityValue}\"\\` — ${opacity < 0.5 ? \"very dim\" : \"dim\"} (${Math.round(opacity * 100)}%); interactive controls below 60% opacity are hard to see.`,\n          hint: hasFullHoverRestore\n            ? \"Hover-reveal pattern detected (&:hover restores opacity:1). Consider raising the resting opacity to ≥ 0.6 so the control is discoverable without hovering.\"\n            : \"Use opacity ≥ 0.6 for always-visible controls and icons. For hover-reveal patterns set opacity:0 as the base and add &:hover: { opacity: '1' }.\",\n        });\n      }\n    }\n  }\n\n  // tone-background-inherit: backgroundColor should always resolve to the current\n  // surface tone via themeColor(l, \"inherit\"), not a fixed shifted tone.\n  // Detected by running the reactive function at context=0 (no-op listener\n  // has no elementNode → contextTone returns 0): if the result is a\n  // var(--X-N) reference with N > 0, the function uses a non-inherit tone.\n  // This catches backgroundColor: (l) => themeColor(l, \"shift-N\") — which\n  // double-shifts when the element itself also has dataTone set, but is also\n  // wrong in general: use dataTone to shift the surface, not backgroundColor.\n  const bgProp = isPlainObject(element.style)\n    ? (element.style as Record<string, unknown>).backgroundColor\n    : undefined;\n  if (typeof bgProp === \"function\" && runReactive) {\n    let bgResult: unknown;\n    try {\n      bgResult = (bgProp as (l: unknown) => unknown)(() => {});\n    } catch {\n      // reactive fn threw without a real runtime — skip\n    }\n    if (typeof bgResult === \"string\") {\n      const bgMatch = bgResult.match(/var\\(--[\\w-]+-(\\d+)\\)$/);\n      if (bgMatch && parseInt(bgMatch[1], 10) > 0) {\n        elementDiags.push({\n          rule: \"tone-background-inherit\",\n          severity: \"warning\",\n          category: \"theme\",\n          path: here,\n          message: `\\`style.backgroundColor\\` uses a fixed tone (resolves to \"${bgResult}\" at base context) instead of \"inherit\".`,\n          hint: 'backgroundColor should always be (l) => themeColor(l, \"inherit\"). To shift the surface tone, set dataTone on the container — it applies to all children uniformly.',\n        });\n      }\n    }\n  }\n\n  // missing-color: element uses themeColor for at least one style property\n  // (detected by CSS custom-property var() in the resolved value) but does NOT\n  // set `color`. Theme token usage signals an intentional visual surface — text\n  // color must also be reactive so it follows the same tone context. CSS `color`\n  // inheritance carries the COMPUTED value from the parent; it does not re-run\n  // themeColor() when the tone context shifts, so the text can mismatch its surface.\n  {\n    const styleForColorCheck = isPlainObject(element.style)\n      ? (element.style as Record<string, unknown>)\n      : null;\n    if (styleForColorCheck) {\n      const themedProps: string[] = [];\n      for (const prop in styleForColorCheck) {\n        if (prop === \"color\" || prop.startsWith(\"&\") || prop.startsWith(\":\")) continue;\n        const resolved = resolveStyleValue(styleForColorCheck[prop], runReactive);\n        if (resolved && resolved.includes(\"var(\")) themedProps.push(prop);\n      }\n      if (themedProps.length > 0 && !hasStyleProp(styleForColorCheck, \"color\")) {\n        elementDiags.push({\n          rule: \"missing-color\",\n          severity: \"warning\",\n          category: \"theme\",\n          path: here,\n          message: `Element uses themeColor for \\`${themedProps.join(\", \")}\\` but \\`style.color\\` is missing — text color won't re-evaluate when the tone context shifts.`,\n          hint: \"Add `color: (l) => themeColor(l, 'shift-9')` so text always contrasts the themed surface.\",\n        });\n      }\n    }\n  }\n\n  // low-contrast: detect insufficient contrast between `color` and `backgroundColor`\n  // by comparing their shift numbers extracted from the `var(--X-N)` strings that\n  // themeColor() returns. Both props must be reactive and resolve to theme vars for\n  // this check to fire. A shift difference < 9 violates WCAG-level legibility.\n  {\n    const styleProp = isPlainObject(element.style)\n      ? (element.style as Record<string, unknown>)\n      : null;\n    const colorFn = styleProp?.color;\n    const bgFn = styleProp?.backgroundColor;\n\n    if (runReactive && typeof colorFn === \"function\" && typeof bgFn === \"function\") {\n      let colorVar: unknown;\n      let bgVar: unknown;\n      try {\n        colorVar = (colorFn as (l: unknown) => unknown)(() => {});\n        bgVar = (bgFn as (l: unknown) => unknown)(() => {});\n      } catch { /* reactive fn threw without a runtime — skip */ }\n\n      // Captures both the CSS-var family (e.g. \"neutral\") and the numeric shift,\n      // so two vars from different families (var(--error-3) vs var(--success-9))\n      // are never compared — only same-family shifts are a real contrast signal.\n      const extractShift = (v: unknown): { family: string; shift: number } | null => {\n        if (typeof v !== \"string\") return null;\n        const match = v.match(/var\\(--([\\w-]+)-(\\d+)\\)$/);\n        return match ? { family: match[1], shift: parseInt(match[2], 10) } : null;\n      };\n\n      const textShift = extractShift(colorVar);\n      const bgShift = extractShift(bgVar);\n\n      if (textShift && bgShift && textShift.family === bgShift.family) {\n        const diff = Math.abs(textShift.shift - bgShift.shift);\n        if (diff < 9) {\n          elementDiags.push({\n            rule: \"low-contrast\",\n            severity: \"warning\",\n            category: \"theme\",\n            path: here,\n            message: `Text/background shift gap is ${diff} (shift-${textShift.shift} vs shift-${bgShift.shift}) — contrast may be insufficient.`,\n            hint: `Aim for ≥9 shift steps between text and surface. E.g. shift-0 bg + shift-9 text, or shift-11 text on a shift-0 surface. Increase the gap or rely on a parent dataTone to open it.`,\n          });\n        }\n      }\n    }\n  }\n\n  // unknown-tone: dataTone is not valid grammar, or it's valid grammar but the\n  // numeric offset is out of the 18-step ramp range (0–17).\n  const dataTone = element.dataTone;\n  if (typeof dataTone === \"string\") {\n    if (!isValidTone(dataTone)) {\n      elementDiags.push({\n        rule: \"unknown-tone\",\n        severity: \"warning\",\n        category: \"data-attr\",\n        path: here,\n        message: `\\`dataTone\\` \"${dataTone}\" is not a valid tone.`,\n        hint: 'Use \"inherit\", \"base\", a number, \"shift-N\"/\"increase-N\"/\"decrease-N\" (N ≤ 17), or a semantic alias: \"surface\", \"hover\", \"border\", \"border-strong\", \"muted\", \"text\".',\n      });\n    } else {\n      // middle-surface-anchor: shift-4 through shift-13 sets a mid-ramp surface\n      // anchor. Children's tones may clamp and fold back, collapsing the contrast\n      // between background and text. Edge anchors (0–3 light, 14–17 dark) are safe.\n      const parsed = parseOffset(dataTone);\n      if (parsed?.family === \"shift\" && parsed.n >= 4 && parsed.n <= 13) {\n        elementDiags.push({\n          rule: \"middle-surface-anchor\",\n          severity: \"warning\",\n          category: \"data-attr\",\n          path: here,\n          message: `\\`dataTone: \"${dataTone}\"\\` uses a mid-ramp surface anchor (steps 4–13). Child tones derived from this surface may clamp and collapse contrast.`,\n          hint: \"Prefer edge anchors: shift-0–3 for light surfaces, shift-14–17 for dark. Mid anchors are only correct for intentionally inverted/highlighted regions.\",\n        });\n      }\n    }\n  }\n\n  // dataTone-surface-contract: an element that sets dataTone creates a new tone\n  // context for all its children. For that surface to be self-contained it MUST\n  // declare both backgroundColor (to paint the surface at the new tone) and color\n  // (to set the baseline text color, guaranteeing minimum legibility without\n  // relying on CSS inheritance from a different context). \"inherit\" is exempt —\n  // it passes the parent context through without creating a new surface.\n  if (typeof dataTone === \"string\" && dataTone !== \"inherit\" && isValidTone(dataTone)) {\n    const styleForToneCheck = isPlainObject(element.style)\n      ? (element.style as Record<string, unknown>)\n      : null;\n    const missingBg = !styleForToneCheck || !hasStyleProp(styleForToneCheck, \"backgroundColor\");\n    const missingColor = !styleForToneCheck || !hasStyleProp(styleForToneCheck, \"color\");\n    if (missingBg || missingColor) {\n      const missing = [\n        missingBg ? \"backgroundColor\" : null,\n        missingColor ? \"color\" : null,\n      ].filter(Boolean).join(\" and \");\n      elementDiags.push({\n        rule: \"dataTone-surface-contract\",\n        severity: \"warning\",\n        category: \"theme\",\n        path: here,\n        message: `\\`dataTone: \"${dataTone}\"\\` creates a new tone surface but \\`style.${missing}\\` is missing — children cannot guarantee readable contrast.`,\n        hint: `Surface contract: set \\`backgroundColor: (l) => themeColor(l, \"inherit\")\\`${missingColor ? ` and \\`color: (l) => themeColor(l, \"shift-9\")\\`` : \"\"} so the surface is fully defined at the new tone.`,\n      });\n    }\n\n    // color-shift-minimum: when color IS set, verify its resolved tone step is ≥ 9\n    // (minimum legibility against any standard surface). Extracted from the CSS var\n    // that themeColor() emits; skipped if the value isn't a recognizable theme var,\n    // or if `runReactive` is false and color is a reactive function.\n    if (!missingColor && styleForToneCheck) {\n      const colorValue = resolveStyleValue(styleForToneCheck.color, runReactive);\n      const step = colorValue !== null ? extractToneStep(colorValue) : null;\n      if (step !== null && step < 9) {\n        elementDiags.push({\n          rule: \"color-shift-minimum\",\n          severity: \"warning\",\n          category: \"theme\",\n          path: here,\n          message: `\\`style.color\\` resolves to tone step ${step} — below the minimum shift-9 required for legible text on a standard surface.`,\n          hint: \"Use at least `themeColor(l, \\\"shift-9\\\")` for body text. Decorative / secondary text may use shift-7 or shift-8 with explicit justification.\",\n        });\n      }\n    }\n  }\n\n  // unknown-density: dataDensity value is invalid grammar or out of the 5-step\n  // density range (increase/decrease 0–4; the scale factors are 0.75, 1, 1.5, 2, 2.5).\n  const dataDensity = element.dataDensity;\n  if (typeof dataDensity === \"string\" && dataDensity !== \"inherit\") {\n    const parsed = parseOffset(dataDensity);\n    if (!parsed || parsed.family === \"shift\") {\n      elementDiags.push({\n        rule: \"unknown-density\",\n        severity: \"warning\",\n        category: \"data-attr\",\n        path: here,\n        message: `\\`dataDensity\\` \"${dataDensity}\" is not a valid density offset.`,\n        hint: 'Use \"inherit\", \"increase-N\", or \"decrease-N\" where N is 0–4. \"shift-\" is not valid for density.',\n      });\n    } else if (parsed.n > 4) {\n      elementDiags.push({\n        rule: \"unknown-density\",\n        severity: \"error\",\n        category: \"data-attr\",\n        path: here,\n        message: `\\`dataDensity\\` \"${dataDensity}\" N=${parsed.n} is out of range — the density scale has 5 steps (max offset: 4).`,\n        hint: 'Use \"increase-N\" or \"decrease-N\" where N ≤ 4. Density factors: [0.75, 1, 1.5, 2, 2.5].',\n      });\n    }\n  }\n\n  // unknown-size: dataSize value is invalid grammar or out of the 8-step size\n  // range (increase/decrease 0–7).\n  const dataSize = element.dataSize;\n  if (typeof dataSize === \"string\" && dataSize !== \"inherit\") {\n    const parsed = parseOffset(dataSize);\n    if (!parsed || parsed.family === \"shift\") {\n      elementDiags.push({\n        rule: \"unknown-size\",\n        severity: \"warning\",\n        category: \"data-attr\",\n        path: here,\n        message: `\\`dataSize\\` \"${dataSize}\" is not a valid size offset.`,\n        hint: 'Use \"inherit\", \"increase-N\", or \"decrease-N\" where N is 0–7. \"shift-\" is not valid for size.',\n      });\n    } else if (parsed.n > 7) {\n      elementDiags.push({\n        rule: \"unknown-size\",\n        severity: \"error\",\n        category: \"data-attr\",\n        path: here,\n        message: `\\`dataSize\\` \"${dataSize}\" N=${parsed.n} is out of range — the size scale has 8 steps (max offset: 7).`,\n        hint: 'Use \"increase-N\" or \"decrease-N\" where N ≤ 7.',\n      });\n    }\n  }\n\n  // Custom rules: run each user-provided rule against this element.\n  if (options.rules && options.rules.length > 0) {\n    for (const rule of options.rules) {\n      let violations: ReturnType<CustomRule[\"check\"]>;\n      try {\n        violations = rule.check(element, here, tag);\n      } catch {\n        continue; // custom rule threw — skip silently\n      }\n      for (const v of violations) {\n        elementDiags.push({\n          rule: rule.id,\n          severity: v.severity ?? rule.severity,\n          category: rule.category,\n          path: here,\n          message: v.message,\n          hint: v.hint,\n        });\n      }\n    }\n  }\n\n  // Walk the element's content into a separate buffer so _doctorDisable can\n  // filter array-level diagnostics (missing-key / duplicate-key / etc.) that\n  // fire at THIS element's path when the content is a reactive function.\n  const contentDiags: Diagnostic[] = [];\n  walk(content, here, contentDiags, false, options);\n\n  // Apply _doctorDisable and flush into the shared output.\n  applyDisable(element._doctorDisable, elementDiags, contentDiags, here, out);\n}\n\n/** Issue counts by severity, plus the grand total. */\nexport interface ValidationSummary {\n  error: number;\n  warning: number;\n  info: number;\n  total: number;\n}\n\n/** Structured result of {@link validate}: pass/fail flag, issues, and counts. */\nexport interface ValidationReport {\n  /** True when there are no `error`-severity diagnostics. */\n  ok: boolean;\n  /** Every diagnostic found, across all rules (alias of `diagnose` output). */\n  issues: Diagnostic[];\n  summary: ValidationSummary;\n}\n\n/**\n * Runs every diagnose rule and returns a structured report (pass/fail flag,\n * the issue list, and counts by severity). `ok` is false when any `error`\n * diagnostic is present; warnings/info do not flip `ok`. Use this as the single\n * programmatic entry point; `diagnose`/`format` remain available for raw access.\n */\nexport function validate(\n  root: unknown,\n  options: DiagnoseOptions = {},\n): ValidationReport {\n  const issues = diagnose(root, options);\n  const summary: ValidationSummary = {\n    error: 0,\n    warning: 0,\n    info: 0,\n    total: issues.length,\n  };\n  for (const issue of issues) summary[issue.severity] += 1;\n  return { ok: summary.error === 0, issues, summary };\n}\n\n/** Formats diagnostics as a readable report (one line per issue). */\nexport function format(diagnostics: Diagnostic[]): string {\n  if (diagnostics.length === 0) return \"✓ No issues found.\";\n  const icon = (s: Severity) =>\n    s === \"error\" ? \"✗\" : s === \"warning\" ? \"⚠\" : \"i\";\n  return diagnostics\n    .map(\n      (d) =>\n        `${icon(d.severity)} [${d.rule}] ${d.path}\\n  ${d.message}${d.hint ? `\\n  → ${d.hint}` : \"\"}`,\n    )\n    .join(\"\\n\");\n}\n","import { HtmlTags, SvgTags, VoidTags } from \"@domphy/core\";\n\n// Internal helpers shared by diagnose.ts and fix.ts. Kept in one module so the\n// tag tables and the tree-shape predicates have a single source of truth.\n\n/** Every valid HTML and SVG tag name. */\nexport const TAGS = new Set<string>([...HtmlTags, ...SvgTags]);\n\n/** Tags that render no children (input, img, br, …). */\nexport const VOID = new Set<string>(VoidTags);\n\n/** True for a non-array object (a Domphy element or a plain record). */\nexport function isPlainObject(\n  value: unknown,\n): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Returns the element's tag key (the first key that names a valid tag). */\nexport function findTag(element: Record<string, unknown>): string | undefined {\n  for (const key in element) {\n    if (TAGS.has(key)) return key;\n  }\n  return undefined;\n}\n","import {\n  type DiagnoseOptions,\n  type ValidationReport,\n  validate,\n} from \"./diagnose.js\";\nimport { findTag, isPlainObject, VOID } from \"./shared.js\";\n\n// Autofix for Domphy element trees. We ONLY apply transforms that are provably\n// lossless — they fix structurally-invalid input without guessing intent. Every\n// other diagnostic (missing/unstable keys, inline typography, literal colors,\n// unknown tones/tags) needs semantic intent the tree does not carry, so applying\n// a \"fix\" would corrupt the author's meaning (e.g. an index key is itself the\n// unstable-key anti-pattern). Those are returned in `report` for the model or a\n// human to resolve. The fixer set is a registry so safe transforms can be added.\n\n// Structural clone that preserves functions (reactive `(listener) => …` values)\n// by reference — a JSON clone would drop them. Primitives pass through.\nfunction cloneTree(value: unknown): unknown {\n  if (Array.isArray(value)) return value.map(cloneTree);\n  if (isPlainObject(value)) {\n    const out: Record<string, unknown> = {};\n    for (const key in value) out[key] = cloneTree(value[key]);\n    return out;\n  }\n  return value;\n}\n\n/** One applied lossless fix. */\nexport interface AppliedFix {\n  rule: string;\n  /** Human path to the node, e.g. \"div > input\". */\n  path: string;\n  message: string;\n}\n\n/** Result of {@link fix}: the corrected tree, what was applied, and what remains. */\nexport interface FixResult {\n  /** A deep copy of the input with lossless fixes applied (functions preserved). */\n  tree: unknown;\n  /** The lossless fixes that were applied. */\n  applied: AppliedFix[];\n  /** validate() run on the fixed tree — `report.issues` are the manual remainder. */\n  report: ValidationReport;\n}\n\n/**\n * Applies every provably-lossless fix to a copy of the tree and returns the\n * result plus a fresh validation report. Currently fixes `void-content` (a void\n * tag like input/img/br cannot have children, so its content is set to null).\n * Issues that need intent are left untouched and surface in `report` — this\n * includes `raw-spacing-value` and `raw-theme-value` (require semantic choices)\n * and key rules (require stable identity from data, not the tree shape).\n */\nexport function fix(root: unknown, options: DiagnoseOptions = {}): FixResult {\n  const tree = cloneTree(root);\n  const applied: AppliedFix[] = [];\n  walkFix(tree, \"\", applied);\n  return { tree, applied, report: validate(tree, options) };\n}\n\nfunction walkFix(node: unknown, path: string, applied: AppliedFix[]): void {\n  if (Array.isArray(node)) {\n    for (const [index, child] of node.entries()) {\n      walkFix(child, `${path}[${index}]`, applied);\n    }\n    return;\n  }\n  if (!isPlainObject(node)) return;\n\n  const tag = findTag(node);\n  if (!tag) return;\n  const here = path ? `${path} > ${tag}` : tag;\n\n  // void-content: a void tag renders no children, so any content is invalid and\n  // cannot be rendered — clearing it to null is lossless.\n  if (VOID.has(tag) && node[tag] !== null && node[tag] !== undefined) {\n    node[tag] = null;\n    applied.push({\n      rule: \"void-content\",\n      path: here,\n      message: `Void tag <${tag}> cannot have content — cleared to null.`,\n    });\n  }\n\n  walkFix(node[tag], here, applied);\n}\n","/**\n * Layer 4 — HTML + CSS output analysis.\n * Generates HTML/CSS from an ElementNode and runs external linters:\n *   • htmlhint  — structural HTML rules (a11y basics, attribute validity)\n *   • stylelint — CSS quality rules (no named colors, no !important, etc.)\n *\n * Only called from the CLI after Layer 1–3 (diagnose) has already run.\n * Diagnostics produced here have category \"output\".\n */\nimport { ElementNode } from \"@domphy/core\";\nimport type { Diagnostic } from \"./diagnose.js\";\n\n// ─── htmlhint rule set ────────────────────────────────────────────────────────\n\nconst HTMLHINT_RULES: Record<string, boolean | string | number> = {\n  \"alt-require\": true,\n  \"attr-no-duplication\": true,\n  \"button-type-require\": true,\n  \"id-unique\": true,\n  \"input-requires-label\": true,\n  \"src-not-empty\": true,\n  \"spec-char-escape\": true,\n  \"tag-no-obsolete\": true,\n  \"tag-pair\": true,\n  \"tagname-lowercase\": true,\n};\n\n// ─── stylelint rule set ───────────────────────────────────────────────────────\n\nconst STYLELINT_CONFIG = {\n  rules: {\n    // color-named intentionally omitted — Layer 2 raw-theme-value already catches literal colors\n    // at the source level with better context (which property, which element).\n    \"color-no-invalid-hex\": true,\n    \"declaration-no-important\": true,\n    \"no-duplicate-selectors\": true,\n    \"no-empty-source\": null,\n    \"length-zero-no-unit\": true,\n  },\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport interface Layer4Options {\n  path?: string;\n}\n\n/**\n * Runs HTML + CSS linters on the output of a built ElementNode.\n * Returns Diagnostic[] with category \"output\".\n * Silently returns [] if linter packages are not installed.\n */\nexport async function auditOutput(\n  node: ElementNode,\n  options: Layer4Options = {},\n): Promise<Diagnostic[]> {\n  const path = options.path ?? node.tagName;\n  const html = node.generateHTML();\n  const css = node.generateCSS();\n\n  const [htmlDiags, cssDiags] = await Promise.all([\n    checkHtml(html, path),\n    checkCss(css, path),\n  ]);\n\n  return [...htmlDiags, ...cssDiags];\n}\n\n// ─── HTML via htmlhint ────────────────────────────────────────────────────────\n\nasync function checkHtml(html: string, path: string): Promise<Diagnostic[]> {\n  if (!html) return [];\n  type HtmlHintInstance = { verify: (html: string, rules: Record<string, unknown>) => HintMessage[] };\n  let htmlhint: HtmlHintInstance | null = null;\n  try {\n    type HtmlHintMod = { default?: { HTMLHint?: HtmlHintInstance }; HTMLHint?: HtmlHintInstance };\n    const mod = await import(\"htmlhint\" as string) as HtmlHintMod;\n    htmlhint = mod.default?.HTMLHint ?? mod.HTMLHint ?? null;\n  } catch {\n    return [];\n  }\n  if (!htmlhint) return [];\n\n  const messages = htmlhint.verify(html, HTMLHINT_RULES);\n  return messages.map((m) => ({\n    rule: `html/${m.rule.id}`,\n    severity: m.type === \"error\" ? (\"error\" as const) : (\"warning\" as const),\n    category: \"output\" as any,\n    path: `${path} [html:${m.line}:${m.col}]`,\n    message: m.message,\n    hint: m.rule.link ? `See: ${m.rule.link}` : undefined,\n  }));\n}\n\ninterface HintMessage {\n  type: \"warning\" | \"error\";\n  message: string;\n  line: number;\n  col: number;\n  rule: { id: string; link?: string };\n}\n\n// ─── CSS via stylelint ────────────────────────────────────────────────────────\n\nasync function checkCss(css: string, path: string): Promise<Diagnostic[]> {\n  if (!css) return [];\n  let stylelint: { lint: (opts: { code: string; config: typeof STYLELINT_CONFIG }) => Promise<StylelintResult> };\n  try {\n    const mod = await import(\"stylelint\" as string) as { default: typeof stylelint };\n    stylelint = mod.default;\n  } catch {\n    return [];\n  }\n\n  let result: StylelintResult;\n  try {\n    result = await stylelint.lint({ code: css, config: STYLELINT_CONFIG });\n  } catch {\n    return [];\n  }\n\n  const warnings = result.results?.[0]?.warnings ?? [];\n  return warnings.map((w) => ({\n    rule: `css/${w.rule}`,\n    severity: w.severity === \"error\" ? (\"error\" as const) : (\"warning\" as const),\n    category: \"output\" as any,\n    path: `${path} [css:${w.line}:${w.column}]`,\n    message: w.text,\n    hint: undefined,\n  }));\n}\n\ninterface StylelintResult {\n  results: Array<{\n    warnings: Array<{\n      rule: string;\n      severity: \"error\" | \"warning\";\n      text: string;\n      line: number;\n      column: number;\n    }>;\n  }>;\n}\n"],"mappings":"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","names":["src_exports","__export","auditOutput","diagnose","fix","format","validate","__toCommonJS","import_palette","import_core","TAGS","VOID","isPlainObject","value","findTag","element","key","RESERVED","TYPOGRAPHY_STYLE","COLOR_STYLE","DIRECT_COLOR_PROPS","CSS_SEMANTIC_VALUES","LITERAL_COLOR","SPACING_STYLE","LITERAL_SPACING","TONE_ALIASES","parseOffset","value","_a","m","isValidTone","parsed","parseLiteralToLch","trimmed","rgb","hex","lab","lch","e","EMBEDDED_COLOR","extractColorLiteral","match","buildColorHint","L","C","h","rawOffset","offset","toneStr","colorFamily","buildSpacingHint","prop","amount","unit","n","resolveStyleValue","runReactive","result","hasStyleProp","style","isPlainObject","extractToneStep","applyDisable","disable","elementDiags","contentDiags","here","out","d","disabled","diagnose","root","options","walk","only","exclude","node","path","dynamic","_b","elementItems","child","findTag","item","index","seenKeys","key","literalKey","count","element","tag","contentKeys","content","VOID","walkStyleProps","stylePath","colorLiteral","colorHint","spacingHint","opacityValue","opacity","hoverStyle","hoverOpacity","hasFullHoverRestore","bgProp","bgResult","bgMatch","styleForColorCheck","themedProps","resolved","styleProp","colorFn","bgFn","colorVar","bgVar","extractShift","textShift","bgShift","diff","dataTone","styleForToneCheck","missingBg","missingColor","missing","colorValue","step","dataDensity","dataSize","rule","violations","v","validate","issues","summary","issue","format","diagnostics","icon","s","cloneTree","value","isPlainObject","out","key","fix","root","options","tree","applied","walkFix","validate","node","path","index","child","tag","findTag","here","VOID","HTMLHINT_RULES","STYLELINT_CONFIG","auditOutput","_0","__async","node","options","_a","path","html","css","htmlDiags","cssDiags","checkHtml","checkCss","_b","_c","htmlhint","mod","e","m","stylelint","result","w"]}