@vizejs/vite-plugin-musea 0.0.1-alpha.75 → 0.0.1-alpha.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a11y-C6xqILwZ.js.map +1 -1
- package/dist/a11y-ClVMxbud.d.ts.map +1 -1
- package/dist/autogen-ymQnARZK.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/vrt-CC29JrCN.d.ts.map +1 -1
- package/dist/vrt-DP87vGIA.js.map +1 -1
- package/package.json +5 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a11y-C6xqILwZ.js","names":["options: A11yOptions","artFiles: ArtFileInfo[]","baseUrl: string","vrtRunner?: MuseaVrtRunner","results: A11yResult[]","defaultViewport: ViewportConfig","page: Page | null","context: { page: Page; context: { close(): Promise<void> } } | null","page: Page","artPath: string","variantName: string","violations: A11yViolation[]","impact: string","runOnly: Record<string, unknown>","tags: string[]","rules: Record<string, { enabled: boolean }>","str: string"],"sources":["../src/a11y.ts"],"sourcesContent":["/**\n * Accessibility (a11y) testing module for Musea.\n * Uses axe-core for automated accessibility auditing via Playwright.\n */\n\nimport type { Page } from \"playwright\";\nimport type { ArtFileInfo, A11yResult, A11yViolation, A11yOptions, ViewportConfig } from \"./types.js\";\nimport type { MuseaVrtRunner } from \"./vrt.js\";\nimport path from \"node:path\";\n\n/**\n * A11y audit summary.\n */\nexport interface A11ySummary {\n totalComponents: number;\n totalVariants: number;\n totalViolations: number;\n criticalCount: number;\n seriousCount: number;\n moderateCount: number;\n minorCount: number;\n}\n\n/**\n * axe-core result shape (subset).\n */\ninterface AxeResult {\n violations: Array<{\n id: string;\n impact: string;\n description: string;\n helpUrl: string;\n nodes: Array<unknown>;\n }>;\n passes: Array<unknown>;\n incomplete: Array<unknown>;\n}\n\n/**\n * A11y runner using axe-core via Playwright.\n */\nexport class MuseaA11yRunner {\n private options: Required<A11yOptions>;\n\n constructor(options: A11yOptions = {}) {\n this.options = {\n enabled: options.enabled ?? true,\n includeRules: options.includeRules ?? [],\n excludeRules: options.excludeRules ?? [],\n level: options.level ?? \"AA\",\n };\n }\n\n /**\n * Run a11y audits on all art file variants.\n * Reuses VRT runner's browser if available.\n */\n async runAudits(\n artFiles: ArtFileInfo[],\n baseUrl: string,\n vrtRunner?: MuseaVrtRunner,\n ): Promise<A11yResult[]> {\n const results: A11yResult[] = [];\n const defaultViewport: ViewportConfig = { width: 1280, height: 720, name: \"desktop\" };\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n\n let page: Page | null = null;\n let context: { page: Page; context: { close(): Promise<void> } } | null = null;\n\n try {\n if (vrtRunner) {\n context = await vrtRunner.createPage(defaultViewport);\n page = context.page;\n } else {\n // Standalone mode: launch own browser\n const { chromium } = await import(\"playwright\");\n const browser = await chromium.launch({ headless: true });\n const ctx = await browser.newContext({\n viewport: { width: defaultViewport.width, height: defaultViewport.height },\n });\n page = await ctx.newPage();\n context = { page, context: ctx };\n }\n\n const variantUrl = this.buildVariantUrl(baseUrl, art.path, variant.name);\n await page.goto(variantUrl, { waitUntil: \"networkidle\" });\n await page.waitForSelector(\".musea-variant\", { timeout: 10000 });\n await page.waitForTimeout(200);\n\n const result = await this.auditPage(page, art.path, variant.name);\n results.push(result);\n } catch (error) {\n results.push({\n artPath: art.path,\n variantName: variant.name,\n violations: [\n {\n id: \"audit-error\",\n impact: \"critical\",\n description: `Audit failed: ${error instanceof Error ? error.message : String(error)}`,\n helpUrl: \"\",\n nodes: 0,\n },\n ],\n passes: 0,\n incomplete: 0,\n });\n } finally {\n if (context) {\n await context.context.close();\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Audit a single page using axe-core.\n */\n async auditPage(page: Page, artPath: string, variantName: string): Promise<A11yResult> {\n // Inject axe-core into the page\n const axeSource = await this.getAxeSource();\n await page.evaluate(axeSource);\n\n // Build axe-core run options\n const runOptions = this.buildAxeOptions();\n\n // Run axe-core\n const axeResult = await page.evaluate((opts) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (window as any).axe.run(document, opts);\n }, runOptions) as AxeResult;\n\n // Map to our result format\n const violations: A11yViolation[] = axeResult.violations.map((v) => ({\n id: v.id,\n impact: v.impact as A11yViolation[\"impact\"],\n description: v.description,\n helpUrl: v.helpUrl,\n nodes: v.nodes.length,\n }));\n\n return {\n artPath,\n variantName,\n violations,\n passes: axeResult.passes.length,\n incomplete: axeResult.incomplete.length,\n };\n }\n\n /**\n * Get summary statistics from results.\n */\n getSummary(results: A11yResult[]): A11ySummary {\n const components = new Set(results.map((r) => r.artPath));\n const allViolations = results.flatMap((r) => r.violations);\n\n return {\n totalComponents: components.size,\n totalVariants: results.length,\n totalViolations: allViolations.length,\n criticalCount: allViolations.filter((v) => v.impact === \"critical\").length,\n seriousCount: allViolations.filter((v) => v.impact === \"serious\").length,\n moderateCount: allViolations.filter((v) => v.impact === \"moderate\").length,\n minorCount: allViolations.filter((v) => v.impact === \"minor\").length,\n };\n }\n\n /**\n * Generate HTML report.\n */\n generateHtmlReport(results: A11yResult[]): string {\n const summary = this.getSummary(results);\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const impactColor = (impact: string): string => {\n switch (impact) {\n case \"critical\":\n return \"#f87171\";\n case \"serious\":\n return \"#fb923c\";\n case \"moderate\":\n return \"#fbbf24\";\n case \"minor\":\n return \"#60a5fa\";\n default:\n return \"#7b8494\";\n }\n };\n\n const resultItems = results\n .filter((r) => r.violations.length > 0)\n .map((r) => {\n const artName = path.basename(r.artPath, \".art.vue\");\n const violationRows = r.violations\n .map(\n (v) => `\n <tr>\n <td><span style=\"color:${impactColor(v.impact)};font-weight:600;text-transform:uppercase;font-size:0.6875rem\">${escapeHtml(v.impact)}</span></td>\n <td><code>${escapeHtml(v.id)}</code></td>\n <td>${escapeHtml(v.description)}</td>\n <td>${v.nodes}</td>\n <td>${v.helpUrl ? `<a href=\"${escapeHtml(v.helpUrl)}\" target=\"_blank\" style=\"color:#60a5fa\">docs</a>` : \"\"}</td>\n </tr>`,\n )\n .join(\"\");\n\n return `\n <div class=\"result\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <span class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</span>\n <span class=\"result-count\">${r.violations.length} violation(s)</span>\n </div>\n </div>\n <table class=\"violations-table\">\n <thead><tr><th>Impact</th><th>Rule</th><th>Description</th><th>Nodes</th><th>Help</th></tr></thead>\n <tbody>${violationRows}</tbody>\n </table>\n </div>`;\n })\n .join(\"\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>A11y Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-meta { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .main { max-width: 1200px; margin: 0 auto; padding: 2rem; }\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1rem; text-align: center; }\n .stat-value { font-size: 1.75rem; font-weight: 700; font-variant-numeric: tabular-nums; }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; }\n .stat.critical .stat-value { color: #f87171; }\n .stat.serious .stat-value { color: #fb923c; }\n .stat.moderate .stat-value { color: #fbbf24; }\n .stat.minor .stat-value { color: #60a5fa; }\n .stat.total .stat-value { color: var(--musea-text); }\n .results { display: flex; flex-direction: column; gap: 1rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; }\n .result-header { padding: 1rem; background: var(--musea-bg-tertiary); display: flex; justify-content: space-between; align-items: center; }\n .result-name { font-weight: 600; }\n .result-count { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .violations-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }\n .violations-table th { padding: 0.75rem 1rem; text-align: left; color: var(--musea-text-muted); font-weight: 500; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 1px solid var(--musea-border); }\n .violations-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--musea-border); }\n .violations-table code { background: var(--musea-bg-tertiary); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.75rem; }\n .all-clear { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 2rem; text-align: center; }\n .all-clear-text { color: #4ade80; font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div><span class=\"logo\">Musea</span> <span style=\"color:var(--musea-text-muted);font-size:0.875rem;margin-left:1rem\">Accessibility Report</span></div>\n <div class=\"header-meta\">${timestamp}</div>\n </header>\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat total\"><div class=\"stat-value\">${summary.totalViolations}</div><div class=\"stat-label\">Violations</div></div>\n <div class=\"stat critical\"><div class=\"stat-value\">${summary.criticalCount}</div><div class=\"stat-label\">Critical</div></div>\n <div class=\"stat serious\"><div class=\"stat-value\">${summary.seriousCount}</div><div class=\"stat-label\">Serious</div></div>\n <div class=\"stat moderate\"><div class=\"stat-value\">${summary.moderateCount}</div><div class=\"stat-label\">Moderate</div></div>\n <div class=\"stat minor\"><div class=\"stat-value\">${summary.minorCount}</div><div class=\"stat-label\">Minor</div></div>\n </div>\n ${\n summary.totalViolations === 0\n ? `<div class=\"all-clear\"><div class=\"all-clear-text\">No accessibility violations found across ${summary.totalVariants} variant(s)</div></div>`\n : `<div class=\"results\">${resultItems}</div>`\n }\n </main>\n</body>\n</html>`;\n }\n\n /**\n * Generate JSON report for CI integration.\n */\n generateJsonReport(results: A11yResult[]): string {\n const summary = this.getSummary(results);\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n violations: r.violations,\n passes: r.passes,\n incomplete: r.incomplete,\n })),\n },\n null,\n 2,\n );\n }\n\n /**\n * Get axe-core source code for injection.\n */\n private async getAxeSource(): Promise<string> {\n try {\n const axeCore = await import(\"axe-core\");\n return axeCore.source;\n } catch {\n throw new Error(\n \"axe-core is not installed. Install it as a peer dependency: npm install axe-core\",\n );\n }\n }\n\n /**\n * Build axe-core run options from configuration.\n */\n private buildAxeOptions(): Record<string, unknown> {\n const runOnly: Record<string, unknown> = {};\n\n // Set WCAG level\n const tags: string[] = [];\n switch (this.options.level) {\n case \"A\":\n tags.push(\"wcag2a\", \"wcag21a\");\n break;\n case \"AA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n case \"AAA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag2aaa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n }\n\n if (tags.length > 0) {\n runOnly.type = \"tag\";\n runOnly.values = tags;\n }\n\n const rules: Record<string, { enabled: boolean }> = {};\n\n for (const ruleId of this.options.includeRules) {\n rules[ruleId] = { enabled: true };\n }\n for (const ruleId of this.options.excludeRules) {\n rules[ruleId] = { enabled: false };\n }\n\n return {\n ...(Object.keys(runOnly).length > 0 ? { runOnly } : {}),\n ...(Object.keys(rules).length > 0 ? { rules } : {}),\n };\n }\n\n private buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n }\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"a11y-C6xqILwZ.js","names":["options: A11yOptions","artFiles: ArtFileInfo[]","baseUrl: string","vrtRunner?: MuseaVrtRunner","results: A11yResult[]","defaultViewport: ViewportConfig","page: Page | null","context: { page: Page; context: { close(): Promise<void> } } | null","page: Page","artPath: string","variantName: string","violations: A11yViolation[]","impact: string","runOnly: Record<string, unknown>","tags: string[]","rules: Record<string, { enabled: boolean }>","str: string"],"sources":["../src/a11y.ts"],"sourcesContent":["/**\n * Accessibility (a11y) testing module for Musea.\n * Uses axe-core for automated accessibility auditing via Playwright.\n */\n\nimport type { Page } from \"playwright\";\nimport type {\n ArtFileInfo,\n A11yResult,\n A11yViolation,\n A11yOptions,\n ViewportConfig,\n} from \"./types.js\";\nimport type { MuseaVrtRunner } from \"./vrt.js\";\nimport path from \"node:path\";\n\n/**\n * A11y audit summary.\n */\nexport interface A11ySummary {\n totalComponents: number;\n totalVariants: number;\n totalViolations: number;\n criticalCount: number;\n seriousCount: number;\n moderateCount: number;\n minorCount: number;\n}\n\n/**\n * axe-core result shape (subset).\n */\ninterface AxeResult {\n violations: Array<{\n id: string;\n impact: string;\n description: string;\n helpUrl: string;\n nodes: Array<unknown>;\n }>;\n passes: Array<unknown>;\n incomplete: Array<unknown>;\n}\n\n/**\n * A11y runner using axe-core via Playwright.\n */\nexport class MuseaA11yRunner {\n private options: Required<A11yOptions>;\n\n constructor(options: A11yOptions = {}) {\n this.options = {\n enabled: options.enabled ?? true,\n includeRules: options.includeRules ?? [],\n excludeRules: options.excludeRules ?? [],\n level: options.level ?? \"AA\",\n };\n }\n\n /**\n * Run a11y audits on all art file variants.\n * Reuses VRT runner's browser if available.\n */\n async runAudits(\n artFiles: ArtFileInfo[],\n baseUrl: string,\n vrtRunner?: MuseaVrtRunner,\n ): Promise<A11yResult[]> {\n const results: A11yResult[] = [];\n const defaultViewport: ViewportConfig = { width: 1280, height: 720, name: \"desktop\" };\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n\n let page: Page | null = null;\n let context: { page: Page; context: { close(): Promise<void> } } | null = null;\n\n try {\n if (vrtRunner) {\n context = await vrtRunner.createPage(defaultViewport);\n page = context.page;\n } else {\n // Standalone mode: launch own browser\n const { chromium } = await import(\"playwright\");\n const browser = await chromium.launch({ headless: true });\n const ctx = await browser.newContext({\n viewport: { width: defaultViewport.width, height: defaultViewport.height },\n });\n page = await ctx.newPage();\n context = { page, context: ctx };\n }\n\n const variantUrl = this.buildVariantUrl(baseUrl, art.path, variant.name);\n await page.goto(variantUrl, { waitUntil: \"networkidle\" });\n await page.waitForSelector(\".musea-variant\", { timeout: 10000 });\n await page.waitForTimeout(200);\n\n const result = await this.auditPage(page, art.path, variant.name);\n results.push(result);\n } catch (error) {\n results.push({\n artPath: art.path,\n variantName: variant.name,\n violations: [\n {\n id: \"audit-error\",\n impact: \"critical\",\n description: `Audit failed: ${error instanceof Error ? error.message : String(error)}`,\n helpUrl: \"\",\n nodes: 0,\n },\n ],\n passes: 0,\n incomplete: 0,\n });\n } finally {\n if (context) {\n await context.context.close();\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Audit a single page using axe-core.\n */\n async auditPage(page: Page, artPath: string, variantName: string): Promise<A11yResult> {\n // Inject axe-core into the page\n const axeSource = await this.getAxeSource();\n await page.evaluate(axeSource);\n\n // Build axe-core run options\n const runOptions = this.buildAxeOptions();\n\n // Run axe-core\n const axeResult = (await page.evaluate((opts) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (window as any).axe.run(document, opts);\n }, runOptions)) as AxeResult;\n\n // Map to our result format\n const violations: A11yViolation[] = axeResult.violations.map((v) => ({\n id: v.id,\n impact: v.impact as A11yViolation[\"impact\"],\n description: v.description,\n helpUrl: v.helpUrl,\n nodes: v.nodes.length,\n }));\n\n return {\n artPath,\n variantName,\n violations,\n passes: axeResult.passes.length,\n incomplete: axeResult.incomplete.length,\n };\n }\n\n /**\n * Get summary statistics from results.\n */\n getSummary(results: A11yResult[]): A11ySummary {\n const components = new Set(results.map((r) => r.artPath));\n const allViolations = results.flatMap((r) => r.violations);\n\n return {\n totalComponents: components.size,\n totalVariants: results.length,\n totalViolations: allViolations.length,\n criticalCount: allViolations.filter((v) => v.impact === \"critical\").length,\n seriousCount: allViolations.filter((v) => v.impact === \"serious\").length,\n moderateCount: allViolations.filter((v) => v.impact === \"moderate\").length,\n minorCount: allViolations.filter((v) => v.impact === \"minor\").length,\n };\n }\n\n /**\n * Generate HTML report.\n */\n generateHtmlReport(results: A11yResult[]): string {\n const summary = this.getSummary(results);\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const impactColor = (impact: string): string => {\n switch (impact) {\n case \"critical\":\n return \"#f87171\";\n case \"serious\":\n return \"#fb923c\";\n case \"moderate\":\n return \"#fbbf24\";\n case \"minor\":\n return \"#60a5fa\";\n default:\n return \"#7b8494\";\n }\n };\n\n const resultItems = results\n .filter((r) => r.violations.length > 0)\n .map((r) => {\n const artName = path.basename(r.artPath, \".art.vue\");\n const violationRows = r.violations\n .map(\n (v) => `\n <tr>\n <td><span style=\"color:${impactColor(v.impact)};font-weight:600;text-transform:uppercase;font-size:0.6875rem\">${escapeHtml(v.impact)}</span></td>\n <td><code>${escapeHtml(v.id)}</code></td>\n <td>${escapeHtml(v.description)}</td>\n <td>${v.nodes}</td>\n <td>${v.helpUrl ? `<a href=\"${escapeHtml(v.helpUrl)}\" target=\"_blank\" style=\"color:#60a5fa\">docs</a>` : \"\"}</td>\n </tr>`,\n )\n .join(\"\");\n\n return `\n <div class=\"result\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <span class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</span>\n <span class=\"result-count\">${r.violations.length} violation(s)</span>\n </div>\n </div>\n <table class=\"violations-table\">\n <thead><tr><th>Impact</th><th>Rule</th><th>Description</th><th>Nodes</th><th>Help</th></tr></thead>\n <tbody>${violationRows}</tbody>\n </table>\n </div>`;\n })\n .join(\"\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>A11y Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-meta { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .main { max-width: 1200px; margin: 0 auto; padding: 2rem; }\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1rem; text-align: center; }\n .stat-value { font-size: 1.75rem; font-weight: 700; font-variant-numeric: tabular-nums; }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; }\n .stat.critical .stat-value { color: #f87171; }\n .stat.serious .stat-value { color: #fb923c; }\n .stat.moderate .stat-value { color: #fbbf24; }\n .stat.minor .stat-value { color: #60a5fa; }\n .stat.total .stat-value { color: var(--musea-text); }\n .results { display: flex; flex-direction: column; gap: 1rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; }\n .result-header { padding: 1rem; background: var(--musea-bg-tertiary); display: flex; justify-content: space-between; align-items: center; }\n .result-name { font-weight: 600; }\n .result-count { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .violations-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }\n .violations-table th { padding: 0.75rem 1rem; text-align: left; color: var(--musea-text-muted); font-weight: 500; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 1px solid var(--musea-border); }\n .violations-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--musea-border); }\n .violations-table code { background: var(--musea-bg-tertiary); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.75rem; }\n .all-clear { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 2rem; text-align: center; }\n .all-clear-text { color: #4ade80; font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div><span class=\"logo\">Musea</span> <span style=\"color:var(--musea-text-muted);font-size:0.875rem;margin-left:1rem\">Accessibility Report</span></div>\n <div class=\"header-meta\">${timestamp}</div>\n </header>\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat total\"><div class=\"stat-value\">${summary.totalViolations}</div><div class=\"stat-label\">Violations</div></div>\n <div class=\"stat critical\"><div class=\"stat-value\">${summary.criticalCount}</div><div class=\"stat-label\">Critical</div></div>\n <div class=\"stat serious\"><div class=\"stat-value\">${summary.seriousCount}</div><div class=\"stat-label\">Serious</div></div>\n <div class=\"stat moderate\"><div class=\"stat-value\">${summary.moderateCount}</div><div class=\"stat-label\">Moderate</div></div>\n <div class=\"stat minor\"><div class=\"stat-value\">${summary.minorCount}</div><div class=\"stat-label\">Minor</div></div>\n </div>\n ${\n summary.totalViolations === 0\n ? `<div class=\"all-clear\"><div class=\"all-clear-text\">No accessibility violations found across ${summary.totalVariants} variant(s)</div></div>`\n : `<div class=\"results\">${resultItems}</div>`\n }\n </main>\n</body>\n</html>`;\n }\n\n /**\n * Generate JSON report for CI integration.\n */\n generateJsonReport(results: A11yResult[]): string {\n const summary = this.getSummary(results);\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n violations: r.violations,\n passes: r.passes,\n incomplete: r.incomplete,\n })),\n },\n null,\n 2,\n );\n }\n\n /**\n * Get axe-core source code for injection.\n */\n private async getAxeSource(): Promise<string> {\n try {\n const axeCore = await import(\"axe-core\");\n return axeCore.source;\n } catch {\n throw new Error(\n \"axe-core is not installed. Install it as a peer dependency: npm install axe-core\",\n );\n }\n }\n\n /**\n * Build axe-core run options from configuration.\n */\n private buildAxeOptions(): Record<string, unknown> {\n const runOnly: Record<string, unknown> = {};\n\n // Set WCAG level\n const tags: string[] = [];\n switch (this.options.level) {\n case \"A\":\n tags.push(\"wcag2a\", \"wcag21a\");\n break;\n case \"AA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n case \"AAA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag2aaa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n }\n\n if (tags.length > 0) {\n runOnly.type = \"tag\";\n runOnly.values = tags;\n }\n\n const rules: Record<string, { enabled: boolean }> = {};\n\n for (const ruleId of this.options.includeRules) {\n rules[ruleId] = { enabled: true };\n }\n for (const ruleId of this.options.excludeRules) {\n rules[ruleId] = { enabled: false };\n }\n\n return {\n ...(Object.keys(runOnly).length > 0 ? { runOnly } : {}),\n ...(Object.keys(rules).length > 0 ? { rules } : {}),\n };\n }\n\n private buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n }\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a11y-ClVMxbud.d.ts","names":[],"sources":["../src/a11y.ts"],"sourcesContent":null,"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"a11y-ClVMxbud.d.ts","names":[],"sources":["../src/a11y.ts"],"sourcesContent":null,"mappings":";;;;;;;;AAmBA;;;UAAiB,WAAA;EA4BJ,eAAA,EAAA,MAAe;EAAA,aAAA,EAAA,MAAA;EAAA,eAGL,EAAA,MAAA;EAAgB,aAczB,EAAA,MAAA;EAAW,YAET,EAAA,MAAA;EAAc,aACjB,EAAA,MAAA;EAAU,UAAlB,EAAA,MAAA;;;;;AAkGgC,cAtHxB,eAAA,CAsHwB;EAAW,QAkBlB,OAAA;EAAU,WA6IV,CAAA,OAAA,CAAA,EAlRP,WAkRO;EAAU;;;;sBApQ1B,4CAEE,iBACX,QAAQ;;;;kBA+DW,6CAA6C,QAAQ;;;;sBAmCvD,eAAe;;;;8BAkBP;;;;8BA6IA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autogen-ymQnARZK.js","names":["native: NativeAutogen | null","componentPath: string","options: AutogenOptions","props: PropDefinition[]","outputPath?: string","source: string","componentName: string","variants: GeneratedVariant[]","defaultProps: Record<string, unknown>","typeStr: string","str: string"],"sources":["../src/autogen.ts"],"sourcesContent":["/**\n * Variant auto-generation module.\n * Generates .art.vue files from component prop analysis.\n */\n\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Autogen configuration options.\n */\nexport interface AutogenOptions {\n /** Maximum number of variants to generate (default: 20) */\n maxVariants?: number;\n /** Include a \"Default\" variant with all default values (default: true) */\n includeDefault?: boolean;\n /** Include boolean toggle variants (default: true) */\n includeBooleanToggles?: boolean;\n /** Include enum/union variants (default: true) */\n includeEnumVariants?: boolean;\n /** Include boundary value variants for numbers (default: false) */\n includeBoundaryValues?: boolean;\n /** Include empty string variants for optional strings (default: false) */\n includeEmptyStrings?: boolean;\n}\n\n/**\n * Prop definition for variant generation.\n */\nexport interface PropDefinition {\n name: string;\n propType: string;\n required: boolean;\n defaultValue?: unknown;\n}\n\n/**\n * Generated variant.\n */\nexport interface GeneratedVariant {\n name: string;\n isDefault: boolean;\n props: Record<string, unknown>;\n description?: string;\n}\n\n/**\n * Autogen output.\n */\nexport interface AutogenOutput {\n variants: GeneratedVariant[];\n artFileContent: string;\n componentName: string;\n}\n\n// Native binding types\ninterface NativeAutogen {\n generateVariants?: (\n componentPath: string,\n props: Array<{\n name: string;\n prop_type: string;\n required: boolean;\n default_value?: unknown;\n }>,\n config?: {\n max_variants?: number;\n include_default?: boolean;\n include_boolean_toggles?: boolean;\n include_enum_variants?: boolean;\n include_boundary_values?: boolean;\n include_empty_strings?: boolean;\n },\n ) => {\n variants: Array<{\n name: string;\n is_default: boolean;\n props: Record<string, unknown>;\n description?: string;\n }>;\n art_file_content: string;\n component_name: string;\n };\n analyzeSfc?: (\n source: string,\n options?: { filename?: string },\n ) => {\n props: Array<{ name: string; type: string; required: boolean; default_value?: unknown }>;\n emits: string[];\n };\n}\n\nlet native: NativeAutogen | null = null;\n\nfunction loadNative(): NativeAutogen {\n if (native) return native;\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeAutogen;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Generate .art.vue file for a component.\n *\n * @param componentPath - Path to the Vue component file\n * @param options - Auto-generation options\n * @returns Generated .art.vue content and metadata\n */\nexport async function generateArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n): Promise<AutogenOutput> {\n const absolutePath = path.resolve(componentPath);\n const source = await fs.promises.readFile(absolutePath, \"utf-8\");\n\n const binding = loadNative();\n\n // Analyze component to extract props\n let props: PropDefinition[];\n if (binding.analyzeSfc) {\n const analysis = binding.analyzeSfc(source, { filename: absolutePath });\n props = analysis.props.map((p) => ({\n name: p.name,\n propType: p.type,\n required: p.required,\n defaultValue: p.default_value,\n }));\n } else {\n // Fallback: simple regex-based prop extraction\n props = extractPropsSimple(source);\n }\n\n if (props.length === 0) {\n // No props found: generate minimal art file\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n return {\n variants: [{ name: \"Default\", isDefault: true, props: {} }],\n artFileContent: generateMinimalArt(componentName, relPath),\n componentName,\n };\n }\n\n // Use native variant generation if available\n if (binding.generateVariants) {\n const nativeProps = props.map((p) => ({\n name: p.name,\n prop_type: p.propType,\n required: p.required,\n default_value: p.defaultValue,\n }));\n\n const relPath = `./${path.basename(componentPath)}`;\n const result = binding.generateVariants(relPath, nativeProps, {\n max_variants: options.maxVariants,\n include_default: options.includeDefault,\n include_boolean_toggles: options.includeBooleanToggles,\n include_enum_variants: options.includeEnumVariants,\n include_boundary_values: options.includeBoundaryValues,\n include_empty_strings: options.includeEmptyStrings,\n });\n\n return {\n variants: result.variants.map((v) => ({\n name: v.name,\n isDefault: v.is_default,\n props: v.props,\n description: v.description,\n })),\n artFileContent: result.art_file_content,\n componentName: result.component_name,\n };\n }\n\n // Fallback: JS-based generation\n return generateArtFileJs(componentPath, props, options);\n}\n\n/**\n * Write generated .art.vue file to disk.\n */\nexport async function writeArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n outputPath?: string,\n): Promise<string> {\n const output = await generateArtFile(componentPath, options);\n\n const targetPath =\n outputPath ?? componentPath.replace(/\\.vue$/, \".art.vue\");\n\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.writeFile(targetPath, output.artFileContent, \"utf-8\");\n\n return targetPath;\n}\n\n// Simple prop extraction fallback (when native binding not available)\nfunction extractPropsSimple(source: string): PropDefinition[] {\n const props: PropDefinition[] = [];\n\n // Match defineProps<{ ... }>() or defineProps({ ... })\n const propsMatch = source.match(\n /defineProps\\s*<\\s*\\{([^}]*)\\}\\s*>/s,\n );\n\n if (propsMatch) {\n const propsBlock = propsMatch[1];\n const propLines = propsBlock.split(\"\\n\");\n\n for (const line of propLines) {\n const propMatch = line.trim().match(/^(\\w+)(\\?)?:\\s*(.+?)\\s*;?\\s*$/);\n if (propMatch) {\n props.push({\n name: propMatch[1],\n propType: propMatch[3].replace(/,\\s*$/, \"\"),\n required: !propMatch[2],\n });\n }\n }\n }\n\n return props;\n}\n\n// Minimal art file for components with no props\nfunction generateMinimalArt(componentName: string, componentPath: string): string {\n return `<art title=\"${componentName}\" component=\"${componentPath}\">\n <variant name=\"Default\" default>\n <${componentName} />\n </variant>\n</art>\n\n<script setup lang=\"ts\">\nimport ${componentName} from '${componentPath}'\n</script>\n`;\n}\n\n// JS-based variant generation fallback\nfunction generateArtFileJs(\n componentPath: string,\n props: PropDefinition[],\n options: AutogenOptions,\n): AutogenOutput {\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n const maxVariants = options.maxVariants ?? 20;\n const variants: GeneratedVariant[] = [];\n\n // Default variant\n if (options.includeDefault !== false) {\n const defaultProps: Record<string, unknown> = {};\n for (const prop of props) {\n if (prop.defaultValue !== undefined) {\n defaultProps[prop.name] = prop.defaultValue;\n }\n }\n variants.push({\n name: \"Default\",\n isDefault: true,\n props: defaultProps,\n description: `${componentName} with default props`,\n });\n }\n\n // Enum variants\n if (options.includeEnumVariants !== false) {\n for (const prop of props) {\n const unionValues = parseUnionType(prop.propType);\n for (const val of unionValues) {\n if (variants.length >= maxVariants) break;\n const name = typeof val === \"string\" ? toPascalCase(val) : `${toPascalCase(prop.name)}_${String(val)}`;\n variants.push({\n name,\n isDefault: false,\n props: { [prop.name]: val },\n description: `${prop.name} = ${JSON.stringify(val)}`,\n });\n }\n }\n }\n\n // Boolean toggle variants\n if (options.includeBooleanToggles !== false) {\n for (const prop of props) {\n if (variants.length >= maxVariants) break;\n if (prop.propType.toLowerCase() === \"boolean\") {\n const nonDefault = prop.defaultValue === true ? false : true;\n variants.push({\n name: nonDefault ? toPascalCase(prop.name) : `No${toPascalCase(prop.name)}`,\n isDefault: false,\n props: { [prop.name]: nonDefault },\n description: `${prop.name} = ${nonDefault}`,\n });\n }\n }\n }\n\n // Generate art file content\n let content = `<art title=\"${componentName}\" component=\"${relPath}\">\\n`;\n for (const variant of variants) {\n const attrs = variant.isDefault\n ? `name=\"${variant.name}\" default`\n : `name=\"${variant.name}\"`;\n content += ` <variant ${attrs}>\\n`;\n\n const propsStr = Object.entries(variant.props)\n .map(([k, v]) => {\n if (typeof v === \"string\") return `${k}=\"${v}\"`;\n if (typeof v === \"boolean\" && v) return k;\n if (typeof v === \"boolean\" && !v) return `:${k}=\"false\"`;\n return `:${k}=\"${JSON.stringify(v)}\"`;\n })\n .join(\" \");\n\n content += ` <${componentName}${propsStr ? \" \" + propsStr : \"\"} />\\n`;\n content += ` </variant>\\n\\n`;\n }\n content += `</art>\\n\\n<script setup lang=\"ts\">\\nimport ${componentName} from '${relPath}'\\n</script>\\n`;\n\n return {\n variants,\n artFileContent: content,\n componentName,\n };\n}\n\nfunction parseUnionType(typeStr: string): unknown[] {\n const trimmed = typeStr.trim();\n if (!trimmed.includes(\"|\")) return [];\n\n if (trimmed.includes(\"'\") || trimmed.includes('\"')) {\n return trimmed\n .split(\"|\")\n .map((s) => s.trim().replace(/^['\"]|['\"]$/g, \"\"))\n .filter((s) => s.length > 0);\n }\n\n const parts = trimmed.split(\"|\").map((s) => s.trim());\n if (parts.every((p) => !isNaN(Number(p)))) {\n return parts.map(Number);\n }\n\n return [];\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"autogen-ymQnARZK.js","names":["native: NativeAutogen | null","componentPath: string","options: AutogenOptions","props: PropDefinition[]","outputPath?: string","source: string","componentName: string","variants: GeneratedVariant[]","defaultProps: Record<string, unknown>","typeStr: string","str: string"],"sources":["../src/autogen.ts"],"sourcesContent":["/**\n * Variant auto-generation module.\n * Generates .art.vue files from component prop analysis.\n */\n\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Autogen configuration options.\n */\nexport interface AutogenOptions {\n /** Maximum number of variants to generate (default: 20) */\n maxVariants?: number;\n /** Include a \"Default\" variant with all default values (default: true) */\n includeDefault?: boolean;\n /** Include boolean toggle variants (default: true) */\n includeBooleanToggles?: boolean;\n /** Include enum/union variants (default: true) */\n includeEnumVariants?: boolean;\n /** Include boundary value variants for numbers (default: false) */\n includeBoundaryValues?: boolean;\n /** Include empty string variants for optional strings (default: false) */\n includeEmptyStrings?: boolean;\n}\n\n/**\n * Prop definition for variant generation.\n */\nexport interface PropDefinition {\n name: string;\n propType: string;\n required: boolean;\n defaultValue?: unknown;\n}\n\n/**\n * Generated variant.\n */\nexport interface GeneratedVariant {\n name: string;\n isDefault: boolean;\n props: Record<string, unknown>;\n description?: string;\n}\n\n/**\n * Autogen output.\n */\nexport interface AutogenOutput {\n variants: GeneratedVariant[];\n artFileContent: string;\n componentName: string;\n}\n\n// Native binding types\ninterface NativeAutogen {\n generateVariants?: (\n componentPath: string,\n props: Array<{\n name: string;\n prop_type: string;\n required: boolean;\n default_value?: unknown;\n }>,\n config?: {\n max_variants?: number;\n include_default?: boolean;\n include_boolean_toggles?: boolean;\n include_enum_variants?: boolean;\n include_boundary_values?: boolean;\n include_empty_strings?: boolean;\n },\n ) => {\n variants: Array<{\n name: string;\n is_default: boolean;\n props: Record<string, unknown>;\n description?: string;\n }>;\n art_file_content: string;\n component_name: string;\n };\n analyzeSfc?: (\n source: string,\n options?: { filename?: string },\n ) => {\n props: Array<{ name: string; type: string; required: boolean; default_value?: unknown }>;\n emits: string[];\n };\n}\n\nlet native: NativeAutogen | null = null;\n\nfunction loadNative(): NativeAutogen {\n if (native) return native;\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeAutogen;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Generate .art.vue file for a component.\n *\n * @param componentPath - Path to the Vue component file\n * @param options - Auto-generation options\n * @returns Generated .art.vue content and metadata\n */\nexport async function generateArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n): Promise<AutogenOutput> {\n const absolutePath = path.resolve(componentPath);\n const source = await fs.promises.readFile(absolutePath, \"utf-8\");\n\n const binding = loadNative();\n\n // Analyze component to extract props\n let props: PropDefinition[];\n if (binding.analyzeSfc) {\n const analysis = binding.analyzeSfc(source, { filename: absolutePath });\n props = analysis.props.map((p) => ({\n name: p.name,\n propType: p.type,\n required: p.required,\n defaultValue: p.default_value,\n }));\n } else {\n // Fallback: simple regex-based prop extraction\n props = extractPropsSimple(source);\n }\n\n if (props.length === 0) {\n // No props found: generate minimal art file\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n return {\n variants: [{ name: \"Default\", isDefault: true, props: {} }],\n artFileContent: generateMinimalArt(componentName, relPath),\n componentName,\n };\n }\n\n // Use native variant generation if available\n if (binding.generateVariants) {\n const nativeProps = props.map((p) => ({\n name: p.name,\n prop_type: p.propType,\n required: p.required,\n default_value: p.defaultValue,\n }));\n\n const relPath = `./${path.basename(componentPath)}`;\n const result = binding.generateVariants(relPath, nativeProps, {\n max_variants: options.maxVariants,\n include_default: options.includeDefault,\n include_boolean_toggles: options.includeBooleanToggles,\n include_enum_variants: options.includeEnumVariants,\n include_boundary_values: options.includeBoundaryValues,\n include_empty_strings: options.includeEmptyStrings,\n });\n\n return {\n variants: result.variants.map((v) => ({\n name: v.name,\n isDefault: v.is_default,\n props: v.props,\n description: v.description,\n })),\n artFileContent: result.art_file_content,\n componentName: result.component_name,\n };\n }\n\n // Fallback: JS-based generation\n return generateArtFileJs(componentPath, props, options);\n}\n\n/**\n * Write generated .art.vue file to disk.\n */\nexport async function writeArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n outputPath?: string,\n): Promise<string> {\n const output = await generateArtFile(componentPath, options);\n\n const targetPath = outputPath ?? componentPath.replace(/\\.vue$/, \".art.vue\");\n\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.writeFile(targetPath, output.artFileContent, \"utf-8\");\n\n return targetPath;\n}\n\n// Simple prop extraction fallback (when native binding not available)\nfunction extractPropsSimple(source: string): PropDefinition[] {\n const props: PropDefinition[] = [];\n\n // Match defineProps<{ ... }>() or defineProps({ ... })\n const propsMatch = source.match(/defineProps\\s*<\\s*\\{([^}]*)\\}\\s*>/s);\n\n if (propsMatch) {\n const propsBlock = propsMatch[1];\n const propLines = propsBlock.split(\"\\n\");\n\n for (const line of propLines) {\n const propMatch = line.trim().match(/^(\\w+)(\\?)?:\\s*(.+?)\\s*;?\\s*$/);\n if (propMatch) {\n props.push({\n name: propMatch[1],\n propType: propMatch[3].replace(/,\\s*$/, \"\"),\n required: !propMatch[2],\n });\n }\n }\n }\n\n return props;\n}\n\n// Minimal art file for components with no props\nfunction generateMinimalArt(componentName: string, componentPath: string): string {\n return `<art title=\"${componentName}\" component=\"${componentPath}\">\n <variant name=\"Default\" default>\n <${componentName} />\n </variant>\n</art>\n\n<script setup lang=\"ts\">\nimport ${componentName} from '${componentPath}'\n</script>\n`;\n}\n\n// JS-based variant generation fallback\nfunction generateArtFileJs(\n componentPath: string,\n props: PropDefinition[],\n options: AutogenOptions,\n): AutogenOutput {\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n const maxVariants = options.maxVariants ?? 20;\n const variants: GeneratedVariant[] = [];\n\n // Default variant\n if (options.includeDefault !== false) {\n const defaultProps: Record<string, unknown> = {};\n for (const prop of props) {\n if (prop.defaultValue !== undefined) {\n defaultProps[prop.name] = prop.defaultValue;\n }\n }\n variants.push({\n name: \"Default\",\n isDefault: true,\n props: defaultProps,\n description: `${componentName} with default props`,\n });\n }\n\n // Enum variants\n if (options.includeEnumVariants !== false) {\n for (const prop of props) {\n const unionValues = parseUnionType(prop.propType);\n for (const val of unionValues) {\n if (variants.length >= maxVariants) break;\n const name =\n typeof val === \"string\" ? toPascalCase(val) : `${toPascalCase(prop.name)}_${String(val)}`;\n variants.push({\n name,\n isDefault: false,\n props: { [prop.name]: val },\n description: `${prop.name} = ${JSON.stringify(val)}`,\n });\n }\n }\n }\n\n // Boolean toggle variants\n if (options.includeBooleanToggles !== false) {\n for (const prop of props) {\n if (variants.length >= maxVariants) break;\n if (prop.propType.toLowerCase() === \"boolean\") {\n const nonDefault = prop.defaultValue === true ? false : true;\n variants.push({\n name: nonDefault ? toPascalCase(prop.name) : `No${toPascalCase(prop.name)}`,\n isDefault: false,\n props: { [prop.name]: nonDefault },\n description: `${prop.name} = ${nonDefault}`,\n });\n }\n }\n }\n\n // Generate art file content\n let content = `<art title=\"${componentName}\" component=\"${relPath}\">\\n`;\n for (const variant of variants) {\n const attrs = variant.isDefault ? `name=\"${variant.name}\" default` : `name=\"${variant.name}\"`;\n content += ` <variant ${attrs}>\\n`;\n\n const propsStr = Object.entries(variant.props)\n .map(([k, v]) => {\n if (typeof v === \"string\") return `${k}=\"${v}\"`;\n if (typeof v === \"boolean\" && v) return k;\n if (typeof v === \"boolean\" && !v) return `:${k}=\"false\"`;\n return `:${k}=\"${JSON.stringify(v)}\"`;\n })\n .join(\" \");\n\n content += ` <${componentName}${propsStr ? \" \" + propsStr : \"\"} />\\n`;\n content += ` </variant>\\n\\n`;\n }\n content += `</art>\\n\\n<script setup lang=\"ts\">\\nimport ${componentName} from '${relPath}'\\n</script>\\n`;\n\n return {\n variants,\n artFileContent: content,\n componentName,\n };\n}\n\nfunction parseUnionType(typeStr: string): unknown[] {\n const trimmed = typeStr.trim();\n if (!trimmed.includes(\"|\")) return [];\n\n if (trimmed.includes(\"'\") || trimmed.includes('\"')) {\n return trimmed\n .split(\"|\")\n .map((s) => s.trim().replace(/^['\"]|['\"]$/g, \"\"))\n .filter((s) => s.length > 0);\n }\n\n const parts = trimmed.split(\"|\").map((s) => s.trim());\n if (parts.every((p) => !isNaN(Number(p)))) {\n return parts.map(Number);\n }\n\n return [];\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n"],"mappings":";;;;;AA6FA,IAAIA,SAA+B;AAEnC,SAAS,aAA4B;AACnC,KAAI,OAAQ,QAAO;CACnB,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAC9C,KAAI;AACF,WAAS,QAAQ,iBAAiB;AAClC,SAAO;CACR,SAAQ,GAAG;AACV,QAAM,IAAI,OACP,sEAAsE,OAAO,EAAE,CAAC;CAEpF;AACF;;;;;;;;AASD,eAAsB,gBACpBC,eACAC,UAA0B,CAAE,GACJ;CACxB,MAAM,eAAe,KAAK,QAAQ,cAAc;CAChD,MAAM,SAAS,MAAM,GAAG,SAAS,SAAS,cAAc,QAAQ;CAEhE,MAAM,UAAU,YAAY;CAG5B,IAAIC;AACJ,KAAI,QAAQ,YAAY;EACtB,MAAM,WAAW,QAAQ,WAAW,QAAQ,EAAE,UAAU,aAAc,EAAC;AACvE,UAAQ,SAAS,MAAM,IAAI,CAAC,OAAO;GACjC,MAAM,EAAE;GACR,UAAU,EAAE;GACZ,UAAU,EAAE;GACZ,cAAc,EAAE;EACjB,GAAE;CACJ,MAEC,SAAQ,mBAAmB,OAAO;AAGpC,KAAI,MAAM,WAAW,GAAG;EAEtB,MAAM,gBAAgB,KAAK,SAAS,eAAe,OAAO;EAC1D,MAAM,WAAW,IAAI,KAAK,SAAS,cAAc,CAAC;AAClD,SAAO;GACL,UAAU,CAAC;IAAE,MAAM;IAAW,WAAW;IAAM,OAAO,CAAE;GAAE,CAAC;GAC3D,gBAAgB,mBAAmB,eAAe,QAAQ;GAC1D;EACD;CACF;AAGD,KAAI,QAAQ,kBAAkB;EAC5B,MAAM,cAAc,MAAM,IAAI,CAAC,OAAO;GACpC,MAAM,EAAE;GACR,WAAW,EAAE;GACb,UAAU,EAAE;GACZ,eAAe,EAAE;EAClB,GAAE;EAEH,MAAM,WAAW,IAAI,KAAK,SAAS,cAAc,CAAC;EAClD,MAAM,SAAS,QAAQ,iBAAiB,SAAS,aAAa;GAC5D,cAAc,QAAQ;GACtB,iBAAiB,QAAQ;GACzB,yBAAyB,QAAQ;GACjC,uBAAuB,QAAQ;GAC/B,yBAAyB,QAAQ;GACjC,uBAAuB,QAAQ;EAChC,EAAC;AAEF,SAAO;GACL,UAAU,OAAO,SAAS,IAAI,CAAC,OAAO;IACpC,MAAM,EAAE;IACR,WAAW,EAAE;IACb,OAAO,EAAE;IACT,aAAa,EAAE;GAChB,GAAE;GACH,gBAAgB,OAAO;GACvB,eAAe,OAAO;EACvB;CACF;AAGD,QAAO,kBAAkB,eAAe,OAAO,QAAQ;AACxD;;;;AAKD,eAAsB,aACpBF,eACAC,UAA0B,CAAE,GAC5BE,YACiB;CACjB,MAAM,SAAS,MAAM,gBAAgB,eAAe,QAAQ;CAE5D,MAAM,aAAa,cAAc,cAAc,QAAQ,UAAU,WAAW;AAE5E,OAAM,GAAG,SAAS,MAAM,KAAK,QAAQ,WAAW,EAAE,EAAE,WAAW,KAAM,EAAC;AACtE,OAAM,GAAG,SAAS,UAAU,YAAY,OAAO,gBAAgB,QAAQ;AAEvE,QAAO;AACR;AAGD,SAAS,mBAAmBC,QAAkC;CAC5D,MAAMF,QAA0B,CAAE;CAGlC,MAAM,aAAa,OAAO,MAAM,qCAAqC;AAErE,KAAI,YAAY;EACd,MAAM,aAAa,WAAW;EAC9B,MAAM,YAAY,WAAW,MAAM,KAAK;AAExC,OAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,YAAY,KAAK,MAAM,CAAC,MAAM,gCAAgC;AACpE,OAAI,UACF,OAAM,KAAK;IACT,MAAM,UAAU;IAChB,UAAU,UAAU,GAAG,QAAQ,SAAS,GAAG;IAC3C,WAAW,UAAU;GACtB,EAAC;EAEL;CACF;AAED,QAAO;AACR;AAGD,SAAS,mBAAmBG,eAAuBL,eAA+B;AAChF,SAAQ,cAAc,cAAc,eAAe,cAAc;;OAE5D,cAAc;;;;;SAKZ,cAAc,SAAS,cAAc;;;AAG7C;AAGD,SAAS,kBACPA,eACAE,OACAD,SACe;CACf,MAAM,gBAAgB,KAAK,SAAS,eAAe,OAAO;CAC1D,MAAM,WAAW,IAAI,KAAK,SAAS,cAAc,CAAC;CAClD,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAMK,WAA+B,CAAE;AAGvC,KAAI,QAAQ,mBAAmB,OAAO;EACpC,MAAMC,eAAwC,CAAE;AAChD,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,wBACP,cAAa,KAAK,QAAQ,KAAK;AAGnC,WAAS,KAAK;GACZ,MAAM;GACN,WAAW;GACX,OAAO;GACP,cAAc,EAAE,cAAc;EAC/B,EAAC;CACH;AAGD,KAAI,QAAQ,wBAAwB,MAClC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,cAAc,eAAe,KAAK,SAAS;AACjD,OAAK,MAAM,OAAO,aAAa;AAC7B,OAAI,SAAS,UAAU,YAAa;GACpC,MAAM,cACG,QAAQ,WAAW,aAAa,IAAI,IAAI,EAAE,aAAa,KAAK,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC;AAC1F,YAAS,KAAK;IACZ;IACA,WAAW;IACX,OAAO,GAAG,KAAK,OAAO,IAAK;IAC3B,cAAc,EAAE,KAAK,KAAK,KAAK,KAAK,UAAU,IAAI,CAAC;GACpD,EAAC;EACH;CACF;AAIH,KAAI,QAAQ,0BAA0B,MACpC,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,SAAS,UAAU,YAAa;AACpC,MAAI,KAAK,SAAS,aAAa,KAAK,WAAW;GAC7C,MAAM,aAAa,KAAK,iBAAiB,OAAO,QAAQ;AACxD,YAAS,KAAK;IACZ,MAAM,aAAa,aAAa,KAAK,KAAK,IAAI,IAAI,aAAa,KAAK,KAAK,CAAC;IAC1E,WAAW;IACX,OAAO,GAAG,KAAK,OAAO,WAAY;IAClC,cAAc,EAAE,KAAK,KAAK,KAAK,WAAW;GAC3C,EAAC;EACH;CACF;CAIH,IAAI,WAAW,cAAc,cAAc,eAAe,QAAQ;AAClE,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,KAAK,cAAc,QAAQ,QAAQ,KAAK;AAC3F,cAAY,aAAa,MAAM;EAE/B,MAAM,WAAW,OAAO,QAAQ,QAAQ,MAAM,CAC3C,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK;AACf,cAAW,MAAM,SAAU,SAAQ,EAAE,EAAE,IAAI,EAAE;AAC7C,cAAW,MAAM,aAAa,EAAG,QAAO;AACxC,cAAW,MAAM,cAAc,EAAG,SAAQ,GAAG,EAAE;AAC/C,WAAQ,GAAG,EAAE,IAAI,KAAK,UAAU,EAAE,CAAC;EACpC,EAAC,CACD,KAAK,IAAI;AAEZ,cAAY,OAAO,cAAc,EAAE,WAAW,MAAM,WAAW,GAAG;AAClE,cAAY;CACb;AACD,aAAY,6CAA6C,cAAc,SAAS,QAAQ;AAExF,QAAO;EACL;EACA,gBAAgB;EAChB;CACD;AACF;AAED,SAAS,eAAeC,SAA4B;CAClD,MAAM,UAAU,QAAQ,MAAM;AAC9B,MAAK,QAAQ,SAAS,IAAI,CAAE,QAAO,CAAE;AAErC,KAAI,QAAQ,SAAS,IAAI,IAAI,QAAQ,SAAS,KAAI,CAChD,QAAO,QACJ,MAAM,IAAI,CACV,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,gBAAgB,GAAG,CAAC,CAChD,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE;CAGhC,MAAM,QAAQ,QAAQ,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AACrD,KAAI,MAAM,MAAM,CAAC,OAAO,MAAM,OAAO,EAAE,CAAC,CAAC,CACvC,QAAO,MAAM,IAAI,OAAO;AAG1B,QAAO,CAAE;AACV;AAED,SAAS,aAAaC,KAAqB;AACzC,QAAO,IACJ,MAAM,WAAW,CACjB,OAAO,QAAQ,CACf,IAAI,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CAC3D,KAAK,GAAG;AACZ"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;;;UAWiB,WAAA;;;;eAIF;;AAJf;;;UAUiB,aAAA;EAAA,IAAA,EAAA,MAAA;EAAa,MAAA,EAEpB,MAFoB,CAAA,MAAA,EAEL,WAFK,CAAA;EAAA,aAEL,CAAA,EACP,aADO,EAAA;;;AACM;;UAMd,qBAAA;cACH;EADG,QAAA,EAAA;;;;EAYA,CAAA;;;;AA2BjB;AAA0B,UA3BT,qBAAA,CA2BS;EAAA;;AAAsD;;;;AAKhF;;EAAiC,YAA8B,CAAA,EAAA,MAAA,GAAA,MAAA,GAAA,UAAA;EAAa;AAAd;;;;EAgJ9C;;;eA3JD;AA6Rf;;;;AAsCsB,KA7TV,cAAA,GA6TgC,CAAA,KAAA,EA7TP,WA6TO,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,GA7TyB,WA6TzB;;;;AAEzC,iBA1TmB,WAAA,CA0TnB,UAAA,EAAA,MAAA,CAAA,EA1ToD,OA0TpD,CA1T4D,aA0T5D,EAAA,CAAA;AAAO;;;iBA1KM,kBAAA,aAA+B;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;;;UAWiB,WAAA;;;;eAIF;;AAJf;;;UAUiB,aAAA;EAAA,IAAA,EAAA,MAAA;EAAa,MAAA,EAEpB,MAFoB,CAAA,MAAA,EAEL,WAFK,CAAA;EAAA,aAEL,CAAA,EACP,aADO,EAAA;;;AACM;;UAMd,qBAAA;cACH;EADG,QAAA,EAAA;;;;EAYA,CAAA;;;;AA2BjB;AAA0B,UA3BT,qBAAA,CA2BS;EAAA;;AAAsD;;;;AAKhF;;EAAiC,YAA8B,CAAA,EAAA,MAAA,GAAA,MAAA,GAAA,UAAA;EAAa;AAAd;;;;EAgJ9C;;;eA3JD;AA6Rf;;;;AAsCsB,KA7TV,cAAA,GA6TgC,CAAA,KAAA,EA7TP,WA6TO,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,GA7TyB,WA6TzB;;;;AAEzC,iBA1TmB,WAAA,CA0TnB,UAAA,EAAA,MAAA,CAAA,EA1ToD,OA0TpD,CA1T4D,aA0T5D,EAAA,CAAA;AAAO;;;iBA1KM,kBAAA,aAA+B;;;;ACjC/B,iBDmKA,sBAAA,CCnKK,UAAA,EDmK8B,aCnK9B,EAAA,CAAA,EAAA,MAAA;;;;AAAoC,iBDyMnC,sBAAA,CCzMmC,MAAA,ED0M/C,qBC1M+C,CAAA,ED2MtD,OC3MsD,CD2M9C,qBC3M8C,CAAA,CAAA;;;ADjK1B;;iBCiKf,KAAA,WAAe,eAAoB"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { MuseaVrtRunner, generateVrtJsonReport, generateVrtReport } from "./vrt-
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import fs from "node:fs";
|
|
7
|
+
import { vizeConfigStore } from "@vizejs/vite-plugin";
|
|
7
8
|
|
|
8
9
|
//#region src/style-dictionary.ts
|
|
9
10
|
/**
|
|
@@ -295,12 +296,12 @@ function loadNative() {
|
|
|
295
296
|
* Create Musea Vite plugin.
|
|
296
297
|
*/
|
|
297
298
|
function musea(options = {}) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
let include = options.include ?? ["**/*.art.vue"];
|
|
300
|
+
let exclude = options.exclude ?? ["node_modules/**", "dist/**"];
|
|
301
|
+
let basePath = options.basePath ?? "/__musea__";
|
|
302
|
+
let storybookCompat = options.storybookCompat ?? false;
|
|
302
303
|
const storybookOutDir = options.storybookOutDir ?? ".storybook/stories";
|
|
303
|
-
|
|
304
|
+
let inlineArt = options.inlineArt ?? false;
|
|
304
305
|
let config;
|
|
305
306
|
let server = null;
|
|
306
307
|
const artFiles = new Map();
|
|
@@ -312,6 +313,15 @@ function musea(options = {}) {
|
|
|
312
313
|
},
|
|
313
314
|
configResolved(resolvedConfig) {
|
|
314
315
|
config = resolvedConfig;
|
|
316
|
+
const vizeConfig = vizeConfigStore.get(resolvedConfig.root);
|
|
317
|
+
if (vizeConfig?.musea) {
|
|
318
|
+
const mc = vizeConfig.musea;
|
|
319
|
+
if (!options.include && mc.include) include = mc.include;
|
|
320
|
+
if (!options.exclude && mc.exclude) exclude = mc.exclude;
|
|
321
|
+
if (!options.basePath && mc.basePath) basePath = mc.basePath;
|
|
322
|
+
if (options.storybookCompat === void 0 && mc.storybookCompat !== void 0) storybookCompat = mc.storybookCompat;
|
|
323
|
+
if (options.inlineArt === void 0 && mc.inlineArt !== void 0) inlineArt = mc.inlineArt;
|
|
324
|
+
}
|
|
315
325
|
},
|
|
316
326
|
configureServer(devServer) {
|
|
317
327
|
server = devServer;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["tokensPath: string","dirPath: string","categories: TokenCategory[]","tokens: Record<string, unknown>","prefix: string[]","obj: Record<string, unknown>","tokens: Record<string, DesignToken>","subcategories: TokenCategory[]","value: unknown","raw: Record<string, unknown>","name: string","token: DesignToken","category: TokenCategory","level: number","config: StyleDictionaryConfig","content: string","filename: string","native: NativeBinding | null","options: MuseaOptions","config: ResolvedConfig","server: ViteDevServer | null","mainPlugin: Plugin","mimeTypes: Record<string, string>","data: unknown","message: string","artPath","art","filePath: string","info: ArtFileInfo","file: string","include: string[]","exclude: string[]","root: string","filepath: string","pattern: string","files: string[]","dir: string","basePath: string","art: ArtFileInfo","variantComponentName: string","variantName: string","artFiles: Map<string, ArtFileInfo>","componentImportPath: string | undefined","componentName: string | undefined","outDir: string","str: string","propsOverride: Record<string, unknown>","variant: ArtVariant"],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":["/**\n * Style Dictionary integration for Musea.\n * Generates design token documentation from Style Dictionary format.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Design token value.\n */\nexport interface DesignToken {\n value: string | number;\n type?: string;\n description?: string;\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Token category (e.g., colors, spacing, typography).\n */\nexport interface TokenCategory {\n name: string;\n tokens: Record<string, DesignToken>;\n subcategories?: TokenCategory[];\n}\n\n/**\n * Style Dictionary output format.\n */\nexport interface StyleDictionaryOutput {\n categories: TokenCategory[];\n metadata: {\n name: string;\n version?: string;\n generatedAt: string;\n };\n}\n\n/**\n * Configuration for Style Dictionary integration.\n */\nexport interface StyleDictionaryConfig {\n /**\n * Path to tokens JSON/JS file or directory.\n */\n tokensPath: string;\n\n /**\n * Output format for documentation.\n * @default 'html'\n */\n outputFormat?: \"html\" | \"json\" | \"markdown\";\n\n /**\n * Output directory for generated documentation.\n * @default '.vize/tokens'\n */\n outputDir?: string;\n\n /**\n * Custom token transformations.\n */\n transforms?: TokenTransform[];\n}\n\n/**\n * Token transformation function.\n */\nexport type TokenTransform = (token: DesignToken, path: string[]) => DesignToken;\n\n/**\n * Parse Style Dictionary tokens file.\n */\nexport async function parseTokens(tokensPath: string): Promise<TokenCategory[]> {\n const absolutePath = path.resolve(tokensPath);\n const stat = await fs.promises.stat(absolutePath);\n\n if (stat.isDirectory()) {\n return parseTokenDirectory(absolutePath);\n }\n\n const content = await fs.promises.readFile(absolutePath, \"utf-8\");\n const tokens = JSON.parse(content);\n return flattenTokens(tokens);\n}\n\n/**\n * Parse tokens from a directory.\n */\nasync function parseTokenDirectory(dirPath: string): Promise<TokenCategory[]> {\n const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });\n const categories: TokenCategory[] = [];\n\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith(\".json\") || entry.name.endsWith(\".tokens.json\"))) {\n const filePath = path.join(dirPath, entry.name);\n const content = await fs.promises.readFile(filePath, \"utf-8\");\n const tokens = JSON.parse(content);\n const categoryName = path\n .basename(entry.name, path.extname(entry.name))\n .replace(\".tokens\", \"\");\n\n categories.push({\n name: formatCategoryName(categoryName),\n tokens: extractTokens(tokens),\n subcategories: extractSubcategories(tokens),\n });\n }\n }\n\n return categories;\n}\n\n/**\n * Flatten nested token structure into categories.\n */\nfunction flattenTokens(tokens: Record<string, unknown>, prefix: string[] = []): TokenCategory[] {\n const categories: TokenCategory[] = [];\n\n for (const [key, value] of Object.entries(tokens)) {\n if (isTokenValue(value)) {\n // This is a token leaf node\n continue;\n }\n\n if (typeof value === \"object\" && value !== null) {\n const categoryTokens = extractTokens(value as Record<string, unknown>);\n const subcategories = flattenTokens(value as Record<string, unknown>, [...prefix, key]);\n\n if (Object.keys(categoryTokens).length > 0 || subcategories.length > 0) {\n categories.push({\n name: formatCategoryName(key),\n tokens: categoryTokens,\n subcategories: subcategories.length > 0 ? subcategories : undefined,\n });\n }\n }\n }\n\n return categories;\n}\n\n/**\n * Extract token values from an object.\n */\nfunction extractTokens(obj: Record<string, unknown>): Record<string, DesignToken> {\n const tokens: Record<string, DesignToken> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isTokenValue(value)) {\n tokens[key] = normalizeToken(value as Record<string, unknown>);\n }\n }\n\n return tokens;\n}\n\n/**\n * Extract subcategories from an object.\n */\nfunction extractSubcategories(obj: Record<string, unknown>): TokenCategory[] | undefined {\n const subcategories: TokenCategory[] = [];\n\n for (const [key, value] of Object.entries(obj)) {\n if (!isTokenValue(value) && typeof value === \"object\" && value !== null) {\n const categoryTokens = extractTokens(value as Record<string, unknown>);\n const nested = extractSubcategories(value as Record<string, unknown>);\n\n if (Object.keys(categoryTokens).length > 0 || (nested && nested.length > 0)) {\n subcategories.push({\n name: formatCategoryName(key),\n tokens: categoryTokens,\n subcategories: nested,\n });\n }\n }\n }\n\n return subcategories.length > 0 ? subcategories : undefined;\n}\n\n/**\n * Check if a value is a token definition.\n */\nfunction isTokenValue(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return \"value\" in obj && (typeof obj.value === \"string\" || typeof obj.value === \"number\");\n}\n\n/**\n * Normalize token to DesignToken interface.\n */\nfunction normalizeToken(raw: Record<string, unknown>): DesignToken {\n return {\n value: raw.value as string | number,\n type: raw.type as string | undefined,\n description: raw.description as string | undefined,\n attributes: raw.attributes as Record<string, unknown> | undefined,\n };\n}\n\n/**\n * Format category name for display.\n */\nfunction formatCategoryName(name: string): string {\n return name\n .replace(/[-_]/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \");\n}\n\n/**\n * Generate HTML documentation for tokens.\n */\nexport function generateTokensHtml(categories: TokenCategory[]): string {\n const renderToken = (name: string, token: DesignToken): string => {\n const isColor =\n typeof token.value === \"string\" &&\n (token.value.startsWith(\"#\") ||\n token.value.startsWith(\"rgb\") ||\n token.value.startsWith(\"hsl\") ||\n token.type === \"color\");\n\n return `\n <div class=\"token\">\n <div class=\"token-preview\">\n ${isColor ? `<div class=\"color-swatch\" style=\"background: ${token.value}\"></div>` : \"\"}\n </div>\n <div class=\"token-info\">\n <div class=\"token-name\">${name}</div>\n <div class=\"token-value\">${token.value}</div>\n ${token.description ? `<div class=\"token-description\">${token.description}</div>` : \"\"}\n </div>\n </div>\n `;\n };\n\n const renderCategory = (category: TokenCategory, level: number = 2): string => {\n const heading = `h${Math.min(level, 6)}`;\n let html = `<${heading}>${category.name}</${heading}>`;\n html += '<div class=\"tokens-grid\">';\n\n for (const [name, token] of Object.entries(category.tokens)) {\n html += renderToken(name, token);\n }\n\n html += \"</div>\";\n\n if (category.subcategories) {\n for (const sub of category.subcategories) {\n html += renderCategory(sub, level + 1);\n }\n }\n\n return html;\n };\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Design Tokens - Musea</title>\n <style>\n :root {\n --musea-bg: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-accent: #a34828;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, sans-serif;\n background: var(--musea-bg);\n color: var(--musea-text);\n line-height: 1.6;\n padding: 2rem;\n }\n h1 { margin-bottom: 2rem; color: var(--musea-accent); }\n h2 { margin: 2rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--musea-border); }\n h3, h4, h5, h6 { margin: 1.5rem 0 0.75rem; }\n .tokens-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 1rem;\n margin-bottom: 1.5rem;\n }\n .token {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: 8px;\n padding: 1rem;\n display: flex;\n gap: 1rem;\n align-items: center;\n }\n .token-preview {\n flex-shrink: 0;\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .color-swatch {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n border: 1px solid var(--musea-border);\n }\n .token-info {\n flex: 1;\n min-width: 0;\n }\n .token-name {\n font-weight: 600;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.875rem;\n }\n .token-value {\n color: var(--musea-text-muted);\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.75rem;\n word-break: break-all;\n }\n .token-description {\n color: var(--musea-text-muted);\n font-size: 0.75rem;\n margin-top: 0.25rem;\n }\n </style>\n</head>\n<body>\n <h1>Design Tokens</h1>\n ${categories.map((cat) => renderCategory(cat)).join(\"\")}\n</body>\n</html>`;\n}\n\n/**\n * Generate Markdown documentation for tokens.\n */\nexport function generateTokensMarkdown(categories: TokenCategory[]): string {\n const renderCategory = (category: TokenCategory, level: number = 2): string => {\n const heading = \"#\".repeat(level);\n let md = `\\n${heading} ${category.name}\\n\\n`;\n\n if (Object.keys(category.tokens).length > 0) {\n md += \"| Token | Value | Description |\\n\";\n md += \"|-------|-------|-------------|\\n\";\n\n for (const [name, token] of Object.entries(category.tokens)) {\n const desc = token.description || \"-\";\n md += `| \\`${name}\\` | \\`${token.value}\\` | ${desc} |\\n`;\n }\n md += \"\\n\";\n }\n\n if (category.subcategories) {\n for (const sub of category.subcategories) {\n md += renderCategory(sub, level + 1);\n }\n }\n\n return md;\n };\n\n let markdown = \"# Design Tokens\\n\\n\";\n markdown += `> Generated by Musea on ${new Date().toISOString()}\\n`;\n\n for (const category of categories) {\n markdown += renderCategory(category);\n }\n\n return markdown;\n}\n\n/**\n * Style Dictionary plugin for Musea.\n */\nexport async function processStyleDictionary(\n config: StyleDictionaryConfig,\n): Promise<StyleDictionaryOutput> {\n const categories = await parseTokens(config.tokensPath);\n const outputDir = config.outputDir ?? \".vize/tokens\";\n const outputFormat = config.outputFormat ?? \"html\";\n\n // Ensure output directory exists\n await fs.promises.mkdir(outputDir, { recursive: true });\n\n // Generate documentation\n let content: string;\n let filename: string;\n\n switch (outputFormat) {\n case \"html\":\n content = generateTokensHtml(categories);\n filename = \"tokens.html\";\n break;\n case \"markdown\":\n content = generateTokensMarkdown(categories);\n filename = \"tokens.md\";\n break;\n case \"json\":\n default:\n content = JSON.stringify({ categories }, null, 2);\n filename = \"tokens.json\";\n }\n\n const outputPath = path.join(outputDir, filename);\n await fs.promises.writeFile(outputPath, content, \"utf-8\");\n\n console.log(`[musea] Generated token documentation: ${outputPath}`);\n\n return {\n categories,\n metadata: {\n name: path.basename(config.tokensPath),\n generatedAt: new Date().toISOString(),\n },\n };\n}\n\nexport default processStyleDictionary;\n","/**\n * Vite plugin for Musea - Component gallery for Vue components.\n *\n * @example\n * ```ts\n * import { defineConfig } from 'vite';\n * import { vize } from '@vizejs/vite-plugin';\n * import { musea } from '@vizejs/vite-plugin-musea';\n *\n * export default defineConfig({\n * plugins: [vize(), musea()],\n * });\n * ```\n */\n\nimport type { Plugin, ViteDevServer, ResolvedConfig } from \"vite\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type {\n MuseaOptions,\n ArtFileInfo,\n ArtMetadata,\n ArtVariant,\n CsfOutput,\n PaletteApiResponse,\n AnalysisApiResponse,\n A11yOptions,\n A11yResult,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"./types.js\";\n\nexport type {\n MuseaOptions,\n ArtFileInfo,\n ArtMetadata,\n ArtVariant,\n CsfOutput,\n VrtOptions,\n ViewportConfig,\n PaletteApiResponse,\n AnalysisApiResponse,\n A11yOptions,\n A11yResult,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"./types.js\";\n\nexport {\n MuseaVrtRunner,\n generateVrtReport,\n generateVrtJsonReport,\n type VrtResult,\n type VrtSummary,\n} from \"./vrt.js\";\n\nexport {\n processStyleDictionary,\n parseTokens,\n generateTokensHtml,\n generateTokensMarkdown,\n type DesignToken,\n type TokenCategory,\n type StyleDictionaryConfig,\n type StyleDictionaryOutput,\n} from \"./style-dictionary.js\";\n\nexport {\n MuseaA11yRunner,\n type A11ySummary,\n} from \"./a11y.js\";\n\nexport {\n generateArtFile,\n writeArtFile,\n type AutogenOptions,\n type AutogenOutput,\n type PropDefinition,\n type GeneratedVariant,\n} from \"./autogen.js\";\n\n// Virtual module prefixes\nconst VIRTUAL_MUSEA_PREFIX = \"\\0musea:\";\nconst VIRTUAL_GALLERY = \"\\0musea-gallery\";\nconst VIRTUAL_MANIFEST = \"\\0musea-manifest\";\n\n// Native binding types\ninterface NativeBinding {\n parseArt: (\n source: string,\n options?: { filename?: string },\n ) => {\n filename: string;\n metadata: {\n title: string;\n description?: string;\n component?: string;\n category?: string;\n tags: string[];\n status: string;\n order?: number;\n };\n variants: Array<{\n name: string;\n template: string;\n is_default: boolean;\n skip_vrt: boolean;\n }>;\n has_script_setup: boolean;\n has_script: boolean;\n style_count: number;\n };\n artToCsf: (\n source: string,\n options?: { filename?: string },\n ) => {\n code: string;\n filename: string;\n };\n generateArtPalette?: (\n source: string,\n artOptions?: { filename?: string },\n paletteOptions?: { infer_options?: boolean; group_by_type?: boolean },\n ) => {\n title: string;\n controls: Array<{\n name: string;\n control: string;\n default_value?: unknown;\n description?: string;\n required: boolean;\n options: Array<{ label: string; value: unknown }>;\n range?: { min: number; max: number; step?: number };\n group?: string;\n }>;\n groups: string[];\n json: string;\n typescript: string;\n };\n generateArtDoc?: (\n source: string,\n artOptions?: { filename?: string },\n docOptions?: { include_source?: boolean; include_templates?: boolean; include_metadata?: boolean },\n ) => {\n markdown: string;\n filename: string;\n title: string;\n category?: string;\n variant_count: number;\n };\n analyzeSfc?: (\n source: string,\n options?: { filename?: string },\n ) => {\n props: Array<{ name: string; type: string; required: boolean; default_value?: unknown }>;\n emits: string[];\n };\n}\n\n// Lazy-load native binding\nlet native: NativeBinding | null = null;\n\nfunction loadNative(): NativeBinding {\n if (native) return native;\n\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeBinding;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Create Musea Vite plugin.\n */\nexport function musea(options: MuseaOptions = {}): Plugin[] {\n const include = options.include ?? [\"**/*.art.vue\"];\n const exclude = options.exclude ?? [\"node_modules/**\", \"dist/**\"];\n const basePath = options.basePath ?? \"/__musea__\";\n const storybookCompat = options.storybookCompat ?? false;\n const storybookOutDir = options.storybookOutDir ?? \".storybook/stories\";\n const inlineArt = options.inlineArt ?? false;\n\n let config: ResolvedConfig;\n let server: ViteDevServer | null = null;\n const artFiles = new Map<string, ArtFileInfo>();\n\n // Main plugin\n const mainPlugin: Plugin = {\n name: \"vite-plugin-musea\",\n enforce: \"pre\",\n\n config() {\n // Add Vue alias for runtime template compilation\n // This is needed because variant templates are compiled at runtime\n return {\n resolve: {\n alias: {\n vue: \"vue/dist/vue.esm-bundler.js\",\n },\n },\n };\n },\n\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n },\n\n configureServer(devServer) {\n server = devServer;\n\n // Gallery SPA route - serves built SPA or falls back to inline HTML\n devServer.middlewares.use(basePath, async (req, res, next) => {\n const url = req.url || \"/\";\n\n // Serve SPA for gallery routes (not /api/, /preview, /preview-module, /art)\n if (\n url === \"/\" ||\n url === \"/index.html\" ||\n url.startsWith(\"/tokens\") ||\n url.startsWith(\"/component/\")\n ) {\n // Try serving built SPA first\n const galleryDistDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"gallery\",\n );\n const indexHtmlPath = path.join(galleryDistDir, \"index.html\");\n\n try {\n await fs.promises.access(indexHtmlPath);\n let html = await fs.promises.readFile(indexHtmlPath, \"utf-8\");\n // Inject basePath for runtime use\n html = html.replace(\n \"</head>\",\n `<script>window.__MUSEA_BASE_PATH__='${basePath}';</script></head>`,\n );\n // Transform through Vite for HMR\n html = await devServer.transformIndexHtml(basePath + url, html);\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n return;\n } catch {\n // Fall back to inline gallery HTML\n const html = generateGalleryHtml(basePath);\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n return;\n }\n }\n // Serve gallery static assets (JS, CSS) from built SPA\n if (url.startsWith(\"/assets/\")) {\n const galleryDistDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"gallery\",\n );\n const filePath = path.join(galleryDistDir, url);\n try {\n const stat = await fs.promises.stat(filePath);\n if (stat.isFile()) {\n const content = await fs.promises.readFile(filePath);\n const ext = path.extname(filePath);\n const mimeTypes: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff2\": \"font/woff2\",\n \".woff\": \"font/woff\",\n };\n res.setHeader(\"Content-Type\", mimeTypes[ext] || \"application/octet-stream\");\n res.setHeader(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n res.end(content);\n return;\n }\n } catch {\n // File not found, fall through\n }\n }\n\n next();\n });\n\n // Preview module route - serves the JavaScript module for a specific variant\n devServer.middlewares.use(`${basePath}/preview-module`, async (req, res, _next) => {\n const url = new URL(req.url || \"\", `http://localhost`);\n const artPath = url.searchParams.get(\"art\");\n const variantName = url.searchParams.get(\"variant\");\n\n if (!artPath || !variantName) {\n res.statusCode = 400;\n res.end(\"Missing art or variant parameter\");\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found\");\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n res.statusCode = 404;\n res.end(\"Variant not found\");\n return;\n }\n\n const variantComponentName = toPascalCase(variant.name);\n const moduleCode = generatePreviewModule(art, variantComponentName, variant.name);\n\n // Transform the module through Vite to resolve imports\n try {\n const result = await devServer.transformRequest(\n `virtual:musea-preview:${artPath}:${variantName}`,\n );\n if (result) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.code);\n return;\n }\n } catch {\n // Fall through to manual response\n }\n\n // Fallback: serve the module directly (imports won't be resolved)\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(moduleCode);\n });\n\n // VRT preview route - renders a single variant for screenshot\n devServer.middlewares.use(`${basePath}/preview`, async (req, res, _next) => {\n const url = new URL(req.url || \"\", `http://localhost`);\n const artPath = url.searchParams.get(\"art\");\n const variantName = url.searchParams.get(\"variant\");\n\n if (!artPath || !variantName) {\n res.statusCode = 400;\n res.end(\"Missing art or variant parameter\");\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found\");\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n res.statusCode = 404;\n res.end(\"Variant not found\");\n return;\n }\n\n const rawHtml = generatePreviewHtml(art, variant, basePath);\n // Transform HTML through Vite to properly resolve module imports\n const html = await devServer.transformIndexHtml(\n `${basePath}/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`,\n rawHtml,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n });\n\n // Art module route - serves transformed art file as ES module\n devServer.middlewares.use(`${basePath}/art`, async (req, res, next) => {\n const url = new URL(req.url || \"\", \"http://localhost\");\n const artPath = decodeURIComponent(url.pathname.slice(1)); // Remove leading /\n\n if (!artPath) {\n next();\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found: \" + artPath);\n return;\n }\n\n // Transform through Vite for proper imports\n try {\n const virtualId = `virtual:musea-art:${artPath}`;\n const result = await devServer.transformRequest(virtualId);\n if (result) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.code);\n } else {\n // Fallback: generate and serve the module directly\n const moduleCode = generateArtModule(art, artPath);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n }\n } catch (err) {\n console.error(\"[musea] Failed to transform art module:\", err);\n // Fallback if transform fails\n const moduleCode = generateArtModule(art, artPath);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n }\n });\n\n // API endpoints\n devServer.middlewares.use(`${basePath}/api`, async (req, res, next) => {\n const sendJson = (data: unknown, status = 200) => {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n };\n\n const sendError = (message: string, status = 500) => {\n sendJson({ error: message }, status);\n };\n\n // GET /api/arts - List all arts\n if (req.url === \"/arts\" && req.method === \"GET\") {\n sendJson(Array.from(artFiles.values()));\n return;\n }\n\n // GET /api/tokens - Get design tokens\n if (req.url === \"/tokens\" && req.method === \"GET\") {\n sendJson({ categories: [] });\n return;\n }\n\n // Arts sub-routes: /api/arts/:encodedPath/...\n if (req.url?.startsWith(\"/arts/\") && req.method === \"GET\") {\n const rest = req.url.slice(6); // after \"/arts/\"\n\n // Check for sub-resource patterns\n const paletteMatch = rest.match(/^(.+)\\/palette$/);\n const analysisMatch = rest.match(/^(.+)\\/analysis$/);\n const docsMatch = rest.match(/^(.+)\\/docs$/);\n const a11yMatch = rest.match(/^(.+)\\/variants\\/([^/]+)\\/a11y$/);\n\n if (paletteMatch) {\n // GET /api/arts/:path/palette\n const artPath = decodeURIComponent(paletteMatch[1]);\n const art = artFiles.get(artPath);\n if (!art) { sendError(\"Art not found\", 404); return; }\n\n try {\n const source = await fs.promises.readFile(artPath, \"utf-8\");\n const binding = loadNative();\n if (binding.generateArtPalette) {\n const palette = binding.generateArtPalette(source, { filename: artPath });\n sendJson(palette);\n } else {\n sendJson({ title: art.metadata.title, controls: [], groups: [], json: \"{}\", typescript: \"\" });\n }\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n return;\n }\n\n if (analysisMatch) {\n // GET /api/arts/:path/analysis\n const artPath = decodeURIComponent(analysisMatch[1]);\n const art = artFiles.get(artPath);\n if (!art) { sendError(\"Art not found\", 404); return; }\n\n try {\n // Determine the component file path: inline art uses the file itself, .art.vue uses the component attribute\n const resolvedComponentPath = art.isInline && art.componentPath\n ? art.componentPath\n : art.metadata.component\n ? (path.isAbsolute(art.metadata.component)\n ? art.metadata.component\n : path.resolve(path.dirname(artPath), art.metadata.component))\n : null;\n\n if (resolvedComponentPath) {\n const source = await fs.promises.readFile(resolvedComponentPath, \"utf-8\");\n const binding = loadNative();\n if (binding.analyzeSfc) {\n const analysis = binding.analyzeSfc(source, { filename: resolvedComponentPath });\n sendJson(analysis);\n } else {\n sendJson({ props: [], emits: [] });\n }\n } else {\n sendJson({ props: [], emits: [] });\n }\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n return;\n }\n\n if (docsMatch) {\n // GET /api/arts/:path/docs\n const artPath = decodeURIComponent(docsMatch[1]);\n const art = artFiles.get(artPath);\n if (!art) { sendError(\"Art not found\", 404); return; }\n\n try {\n const source = await fs.promises.readFile(artPath, \"utf-8\");\n const binding = loadNative();\n if (binding.generateArtDoc) {\n const doc = binding.generateArtDoc(source, { filename: artPath });\n sendJson(doc);\n } else {\n sendJson({ markdown: \"\", title: art.metadata.title, variant_count: art.variants.length });\n }\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n return;\n }\n\n if (a11yMatch) {\n // GET /api/arts/:path/variants/:name/a11y\n const artPath = decodeURIComponent(a11yMatch[1]);\n const _variantName = decodeURIComponent(a11yMatch[2]);\n const art = artFiles.get(artPath);\n if (!art) { sendError(\"Art not found\", 404); return; }\n\n // Return empty a11y results (populated after VRT --a11y run)\n sendJson({ violations: [], passes: 0, incomplete: 0 });\n return;\n }\n\n // GET /api/arts/:path - Get single art (no sub-resource)\n const artPath = decodeURIComponent(rest);\n const art = artFiles.get(artPath);\n if (art) {\n sendJson(art);\n } else {\n sendError(\"Art not found\", 404);\n }\n return;\n }\n\n // POST /api/preview-with-props\n if (req.url === \"/preview-with-props\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", () => {\n try {\n const { artPath: reqArtPath, variantName, props: propsOverride } = JSON.parse(body);\n const art = artFiles.get(reqArtPath);\n if (!art) { sendError(\"Art not found\", 404); return; }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) { sendError(\"Variant not found\", 404); return; }\n\n // Generate preview module with props override\n const variantComponentName = toPascalCase(variant.name);\n const moduleCode = generatePreviewModuleWithProps(art, variantComponentName, variant.name, propsOverride);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n });\n return;\n }\n\n // POST /api/generate\n if (req.url === \"/generate\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => { body += chunk; });\n req.on(\"end\", async () => {\n try {\n const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);\n const { generateArtFile: genArt } = await import(\"./autogen.js\");\n const result = await genArt(reqComponentPath, autogenOptions);\n sendJson({\n generated: true,\n componentName: result.componentName,\n variants: result.variants,\n artFileContent: result.artFileContent,\n });\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n });\n return;\n }\n\n next();\n });\n\n // Watch for Art file changes\n devServer.watcher.on(\"change\", async (file) => {\n if (file.endsWith(\".art.vue\") && shouldProcess(file, include, exclude, config.root)) {\n await processArtFile(file);\n console.log(`[musea] Reloaded: ${path.relative(config.root, file)}`);\n }\n // Inline art: re-check .vue files on change\n if (inlineArt && file.endsWith(\".vue\") && !file.endsWith(\".art.vue\")) {\n const hadArt = artFiles.has(file);\n const source = await fs.promises.readFile(file, \"utf-8\");\n if (source.includes(\"<art\")) {\n await processArtFile(file);\n console.log(`[musea] Reloaded inline art: ${path.relative(config.root, file)}`);\n } else if (hadArt) {\n artFiles.delete(file);\n console.log(`[musea] Removed inline art: ${path.relative(config.root, file)}`);\n }\n }\n });\n\n devServer.watcher.on(\"add\", async (file) => {\n if (file.endsWith(\".art.vue\") && shouldProcess(file, include, exclude, config.root)) {\n await processArtFile(file);\n console.log(`[musea] Added: ${path.relative(config.root, file)}`);\n }\n // Inline art: check new .vue files\n if (inlineArt && file.endsWith(\".vue\") && !file.endsWith(\".art.vue\")) {\n const source = await fs.promises.readFile(file, \"utf-8\");\n if (source.includes(\"<art\")) {\n await processArtFile(file);\n console.log(`[musea] Added inline art: ${path.relative(config.root, file)}`);\n }\n }\n });\n\n devServer.watcher.on(\"unlink\", (file) => {\n if (artFiles.has(file)) {\n artFiles.delete(file);\n console.log(`[musea] Removed: ${path.relative(config.root, file)}`);\n }\n });\n },\n\n async buildStart() {\n // Scan for Art files\n const files = await scanArtFiles(config.root, include, exclude, inlineArt);\n\n console.log(`[musea] Found ${files.length} art files`);\n\n for (const file of files) {\n await processArtFile(file);\n }\n\n // Generate Storybook CSF if enabled\n if (storybookCompat) {\n await generateStorybookFiles(artFiles, config.root, storybookOutDir);\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_GALLERY) {\n return VIRTUAL_GALLERY;\n }\n if (id === VIRTUAL_MANIFEST) {\n return VIRTUAL_MANIFEST;\n }\n // Handle virtual:musea-preview: prefix for preview modules\n if (id.startsWith(\"virtual:musea-preview:\")) {\n return \"\\0musea-preview:\" + id.slice(\"virtual:musea-preview:\".length);\n }\n // Handle virtual:musea-art: prefix for preview modules\n if (id.startsWith(\"virtual:musea-art:\")) {\n const artPath = id.slice(\"virtual:musea-art:\".length);\n if (artFiles.has(artPath)) {\n return \"\\0musea-art:\" + artPath;\n }\n }\n if (id.endsWith(\".art.vue\")) {\n const resolved = path.resolve(config.root, id);\n if (artFiles.has(resolved)) {\n return VIRTUAL_MUSEA_PREFIX + resolved;\n }\n }\n // Inline art: resolve .vue files that have <art> blocks\n if (inlineArt && id.endsWith(\".vue\") && !id.endsWith(\".art.vue\")) {\n const resolved = path.resolve(config.root, id);\n if (artFiles.has(resolved)) {\n return VIRTUAL_MUSEA_PREFIX + resolved;\n }\n }\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_GALLERY) {\n return generateGalleryModule(basePath);\n }\n if (id === VIRTUAL_MANIFEST) {\n return generateManifestModule(artFiles);\n }\n // Handle \\0musea-preview: prefix for preview modules\n if (id.startsWith(\"\\0musea-preview:\")) {\n const rest = id.slice(\"\\0musea-preview:\".length);\n const lastColonIndex = rest.lastIndexOf(\":\");\n if (lastColonIndex !== -1) {\n const artPath = rest.slice(0, lastColonIndex);\n const variantName = rest.slice(lastColonIndex + 1);\n const art = artFiles.get(artPath);\n if (art) {\n const variantComponentName = toPascalCase(variantName);\n return generatePreviewModule(art, variantComponentName, variantName);\n }\n }\n }\n // Handle \\0musea-art: prefix for preview modules\n if (id.startsWith(\"\\0musea-art:\")) {\n const artPath = id.slice(\"\\0musea-art:\".length);\n const art = artFiles.get(artPath);\n if (art) {\n return generateArtModule(art, artPath);\n }\n }\n if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {\n const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length);\n const art = artFiles.get(realPath);\n if (art) {\n return generateArtModule(art, realPath);\n }\n }\n return null;\n },\n\n async handleHotUpdate(ctx) {\n const { file } = ctx;\n if (file.endsWith(\".art.vue\") && artFiles.has(file)) {\n await processArtFile(file);\n\n // Invalidate virtual modules\n const virtualId = VIRTUAL_MUSEA_PREFIX + file;\n const modules = server?.moduleGraph.getModulesByFile(virtualId);\n if (modules) {\n return [...modules];\n }\n }\n\n // Inline art: HMR for .vue files with <art> blocks\n if (inlineArt && file.endsWith(\".vue\") && !file.endsWith(\".art.vue\") && artFiles.has(file)) {\n await processArtFile(file);\n\n const virtualId = VIRTUAL_MUSEA_PREFIX + file;\n const modules = server?.moduleGraph.getModulesByFile(virtualId);\n if (modules) {\n return [...modules];\n }\n }\n\n return undefined;\n },\n };\n\n // Helper functions scoped to this plugin instance\n\n async function processArtFile(filePath: string): Promise<void> {\n try {\n const source = await fs.promises.readFile(filePath, \"utf-8\");\n const binding = loadNative();\n const parsed = binding.parseArt(source, { filename: filePath });\n\n // Skip files with no variants (e.g. .vue files without <art> block)\n if (!parsed.variants || parsed.variants.length === 0) return;\n\n const isInline = !filePath.endsWith(\".art.vue\");\n\n const info: ArtFileInfo = {\n path: filePath,\n metadata: {\n title: parsed.metadata.title || (isInline ? path.basename(filePath, \".vue\") : \"\"),\n description: parsed.metadata.description,\n component: isInline ? undefined : parsed.metadata.component,\n category: parsed.metadata.category,\n tags: parsed.metadata.tags,\n status: parsed.metadata.status as \"draft\" | \"ready\" | \"deprecated\",\n order: parsed.metadata.order,\n },\n variants: parsed.variants.map((v) => ({\n name: v.name,\n template: v.template,\n isDefault: v.is_default,\n skipVrt: v.skip_vrt,\n })),\n hasScriptSetup: parsed.has_script_setup,\n hasScript: parsed.has_script,\n styleCount: parsed.style_count,\n isInline,\n componentPath: isInline ? filePath : undefined,\n };\n\n artFiles.set(filePath, info);\n } catch (e) {\n console.error(`[musea] Failed to process ${filePath}:`, e);\n }\n }\n\n return [mainPlugin];\n}\n\n// Utility functions\n\nfunction shouldProcess(file: string, include: string[], exclude: string[], root: string): boolean {\n const relative = path.relative(root, file);\n\n // Check exclude patterns\n for (const pattern of exclude) {\n if (matchGlob(relative, pattern)) {\n return false;\n }\n }\n\n // Check include patterns\n for (const pattern of include) {\n if (matchGlob(relative, pattern)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction matchGlob(filepath: string, pattern: string): boolean {\n // Simple glob matching (supports * and **)\n // Escape . first, then replace glob patterns\n const regex = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \".*\")\n .replace(/\\*(?!\\*)/g, \"[^/]*\");\n\n return new RegExp(`^${regex}$`).test(filepath);\n}\n\nasync function scanArtFiles(root: string, include: string[], exclude: string[], scanInlineArt = false): Promise<string[]> {\n const files: string[] = [];\n\n async function scan(dir: string): Promise<void> {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relative = path.relative(root, fullPath);\n\n // Check exclude\n let excluded = false;\n for (const pattern of exclude) {\n if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {\n excluded = true;\n break;\n }\n }\n\n if (excluded) continue;\n\n if (entry.isDirectory()) {\n await scan(fullPath);\n } else if (entry.isFile() && entry.name.endsWith(\".art.vue\")) {\n // Check include\n for (const pattern of include) {\n if (matchGlob(relative, pattern)) {\n files.push(fullPath);\n break;\n }\n }\n } else if (scanInlineArt && entry.isFile() && entry.name.endsWith(\".vue\") && !entry.name.endsWith(\".art.vue\")) {\n // Inline art: check if .vue file contains <art block\n const content = await fs.promises.readFile(fullPath, \"utf-8\");\n if (content.includes(\"<art\")) {\n files.push(fullPath);\n }\n }\n }\n }\n\n await scan(root);\n return files;\n}\n\nfunction generateGalleryHtml(basePath: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Musea - Component Gallery</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-bg-elevated: #2d2a27;\n --musea-accent: #a34828;\n --musea-accent-hover: #c45a32;\n --musea-accent-subtle: rgba(163, 72, 40, 0.15);\n --musea-text: #e6e9f0;\n --musea-text-secondary: #c4c9d4;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n --musea-border-subtle: #2a2725;\n --musea-success: #4ade80;\n --musea-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);\n --musea-radius-sm: 6px;\n --musea-radius-md: 8px;\n --musea-radius-lg: 12px;\n --musea-transition: 0.15s ease;\n }\n\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n }\n\n /* Header */\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 0 1.5rem;\n height: 56px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n\n .header-left {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n }\n\n .logo {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 1.125rem;\n font-weight: 700;\n color: var(--musea-accent);\n text-decoration: none;\n }\n\n .logo-svg {\n width: 32px;\n height: 32px;\n flex-shrink: 0;\n }\n\n .logo-icon svg {\n width: 16px;\n height: 16px;\n color: white;\n }\n\n .header-subtitle {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n font-weight: 500;\n padding-left: 1.5rem;\n border-left: 1px solid var(--musea-border);\n }\n\n .search-container {\n position: relative;\n width: 280px;\n }\n\n .search-input {\n width: 100%;\n background: var(--musea-bg-tertiary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-md);\n padding: 0.5rem 0.75rem 0.5rem 2.25rem;\n color: var(--musea-text);\n font-size: 0.8125rem;\n outline: none;\n transition: border-color var(--musea-transition), background var(--musea-transition);\n }\n\n .search-input::placeholder {\n color: var(--musea-text-muted);\n }\n\n .search-input:focus {\n border-color: var(--musea-accent);\n background: var(--musea-bg-elevated);\n }\n\n .search-icon {\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--musea-text-muted);\n pointer-events: none;\n }\n\n /* Layout */\n .main {\n display: grid;\n grid-template-columns: 260px 1fr;\n min-height: calc(100vh - 56px);\n }\n\n /* Sidebar */\n .sidebar {\n background: var(--musea-bg-secondary);\n border-right: 1px solid var(--musea-border);\n overflow-y: auto;\n overflow-x: hidden;\n }\n\n .sidebar::-webkit-scrollbar {\n width: 6px;\n }\n\n .sidebar::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .sidebar::-webkit-scrollbar-thumb {\n background: var(--musea-border);\n border-radius: 3px;\n }\n\n .sidebar-section {\n padding: 0.75rem;\n }\n\n .category-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.625rem 0.75rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--musea-text-muted);\n cursor: pointer;\n user-select: none;\n border-radius: var(--musea-radius-sm);\n transition: background var(--musea-transition);\n }\n\n .category-header:hover {\n background: var(--musea-bg-tertiary);\n }\n\n .category-icon {\n width: 16px;\n height: 16px;\n transition: transform var(--musea-transition);\n }\n\n .category-header.collapsed .category-icon {\n transform: rotate(-90deg);\n }\n\n .category-count {\n margin-left: auto;\n background: var(--musea-bg-tertiary);\n padding: 0.125rem 0.375rem;\n border-radius: 4px;\n font-size: 0.625rem;\n }\n\n .art-list {\n list-style: none;\n margin-top: 0.25rem;\n }\n\n .art-item {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.5rem 0.75rem 0.5rem 1.75rem;\n border-radius: var(--musea-radius-sm);\n cursor: pointer;\n font-size: 0.8125rem;\n color: var(--musea-text-secondary);\n transition: all var(--musea-transition);\n position: relative;\n }\n\n .art-item::before {\n content: '';\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--musea-border);\n transition: background var(--musea-transition);\n }\n\n .art-item:hover {\n background: var(--musea-bg-tertiary);\n color: var(--musea-text);\n }\n\n .art-item:hover::before {\n background: var(--musea-text-muted);\n }\n\n .art-item.active {\n background: var(--musea-accent-subtle);\n color: var(--musea-accent-hover);\n }\n\n .art-item.active::before {\n background: var(--musea-accent);\n }\n\n .art-variant-count {\n margin-left: auto;\n font-size: 0.6875rem;\n color: var(--musea-text-muted);\n opacity: 0;\n transition: opacity var(--musea-transition);\n }\n\n .art-item:hover .art-variant-count {\n opacity: 1;\n }\n\n /* Content */\n .content {\n background: var(--musea-bg-primary);\n overflow-y: auto;\n }\n\n .content-inner {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n }\n\n .content-header {\n margin-bottom: 2rem;\n }\n\n .content-title {\n font-size: 1.5rem;\n font-weight: 700;\n margin-bottom: 0.5rem;\n }\n\n .content-description {\n color: var(--musea-text-muted);\n font-size: 0.9375rem;\n max-width: 600px;\n }\n\n .content-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-top: 1rem;\n }\n\n .meta-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.625rem;\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-sm);\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n }\n\n .meta-tag svg {\n width: 12px;\n height: 12px;\n }\n\n /* Gallery Grid */\n .gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.25rem;\n }\n\n /* Variant Card */\n .variant-card {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-lg);\n overflow: hidden;\n transition: all var(--musea-transition);\n }\n\n .variant-card:hover {\n border-color: var(--musea-text-muted);\n box-shadow: var(--musea-shadow);\n transform: translateY(-2px);\n }\n\n .variant-preview {\n aspect-ratio: 16 / 10;\n background: var(--musea-bg-tertiary);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n }\n\n .variant-preview iframe {\n width: 100%;\n height: 100%;\n border: none;\n background: white;\n }\n\n .variant-preview-placeholder {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n text-align: center;\n padding: 1rem;\n }\n\n .variant-preview-code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n background: var(--musea-bg-primary);\n padding: 1rem;\n overflow: auto;\n max-height: 100%;\n width: 100%;\n }\n\n .variant-info {\n padding: 1rem;\n border-top: 1px solid var(--musea-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .variant-name {\n font-weight: 600;\n font-size: 0.875rem;\n }\n\n .variant-badge {\n font-size: 0.625rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n padding: 0.1875rem 0.5rem;\n border-radius: 4px;\n background: var(--musea-accent-subtle);\n color: var(--musea-accent);\n }\n\n .variant-actions {\n display: flex;\n gap: 0.5rem;\n }\n\n .variant-action-btn {\n width: 28px;\n height: 28px;\n border: none;\n background: var(--musea-bg-tertiary);\n border-radius: var(--musea-radius-sm);\n color: var(--musea-text-muted);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all var(--musea-transition);\n }\n\n .variant-action-btn:hover {\n background: var(--musea-bg-elevated);\n color: var(--musea-text);\n }\n\n .variant-action-btn svg {\n width: 14px;\n height: 14px;\n }\n\n /* Empty State */\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 400px;\n text-align: center;\n padding: 2rem;\n }\n\n .empty-state-icon {\n width: 80px;\n height: 80px;\n background: var(--musea-bg-secondary);\n border-radius: var(--musea-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 1.5rem;\n }\n\n .empty-state-icon svg {\n width: 40px;\n height: 40px;\n color: var(--musea-text-muted);\n }\n\n .empty-state-title {\n font-size: 1.125rem;\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n\n .empty-state-text {\n color: var(--musea-text-muted);\n font-size: 0.875rem;\n max-width: 300px;\n }\n\n /* Loading */\n .loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n color: var(--musea-text-muted);\n gap: 0.75rem;\n }\n\n .loading-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--musea-border);\n border-top-color: var(--musea-accent);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .main {\n grid-template-columns: 1fr;\n }\n .sidebar {\n display: none;\n }\n .header-subtitle {\n display: none;\n }\n }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div class=\"header-left\">\n <a href=\"${basePath}\" class=\"logo\">\n <svg class=\"logo-svg\" width=\"32\" height=\"32\" viewBox=\"0 0 200 200\" fill=\"none\">\n <defs>\n <linearGradient id=\"metal-grad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"20%\">\n <stop offset=\"0%\" stop-color=\"#f0f2f5\"/>\n <stop offset=\"50%\" stop-color=\"#9ca3b0\"/>\n <stop offset=\"100%\" stop-color=\"#e07048\"/>\n </linearGradient>\n <linearGradient id=\"metal-grad-dark\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"30%\">\n <stop offset=\"0%\" stop-color=\"#d0d4dc\"/>\n <stop offset=\"60%\" stop-color=\"#6b7280\"/>\n <stop offset=\"100%\" stop-color=\"#c45530\"/>\n </linearGradient>\n </defs>\n <g transform=\"translate(40, 40)\">\n <g transform=\"skewX(-12)\">\n <path d=\"M 100 0 L 60 120 L 105 30 L 100 0 Z\" fill=\"url(#metal-grad-dark)\" stroke=\"#4b5563\" stroke-width=\"0.5\"/>\n <path d=\"M 30 0 L 60 120 L 80 20 L 30 0 Z\" fill=\"url(#metal-grad)\" stroke-width=\"0.5\" stroke-opacity=\"0.4\"/>\n </g>\n </g>\n <g transform=\"translate(110, 120)\">\n <line x1=\"5\" y1=\"10\" x2=\"5\" y2=\"50\" stroke=\"#e07048\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <line x1=\"60\" y1=\"10\" x2=\"60\" y2=\"50\" stroke=\"#e07048\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <path d=\"M 0 10 L 32.5 0 L 65 10\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <rect x=\"15\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"36\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"23\" y=\"35\" width=\"18\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.6\"/>\n </g>\n </svg>\n Musea\n </a>\n <span class=\"header-subtitle\">Component Gallery</span>\n </div>\n <div class=\"search-container\">\n <svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input type=\"text\" class=\"search-input\" placeholder=\"Search components...\" id=\"search\">\n </div>\n </header>\n\n <main class=\"main\">\n <aside class=\"sidebar\" id=\"sidebar\">\n <div class=\"loading\">\n <div class=\"loading-spinner\"></div>\n Loading...\n </div>\n </aside>\n <section class=\"content\" id=\"content\">\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants and documentation</div>\n </div>\n </section>\n </main>\n\n <script type=\"module\">\n const basePath = '${basePath}';\n let arts = [];\n let selectedArt = null;\n let searchQuery = '';\n\n async function loadArts() {\n try {\n const res = await fetch(basePath + '/api/arts');\n arts = await res.json();\n renderSidebar();\n } catch (e) {\n console.error('Failed to load arts:', e);\n document.getElementById('sidebar').innerHTML = '<div class=\"loading\">Failed to load</div>';\n }\n }\n\n function renderSidebar() {\n const sidebar = document.getElementById('sidebar');\n const categories = {};\n\n const filtered = searchQuery\n ? arts.filter(a => a.metadata.title.toLowerCase().includes(searchQuery.toLowerCase()))\n : arts;\n\n for (const art of filtered) {\n const cat = art.metadata.category || 'Components';\n if (!categories[cat]) categories[cat] = [];\n categories[cat].push(art);\n }\n\n if (Object.keys(categories).length === 0) {\n sidebar.innerHTML = '<div class=\"sidebar-section\"><div class=\"loading\">No components found</div></div>';\n return;\n }\n\n let html = '';\n for (const [category, items] of Object.entries(categories)) {\n html += '<div class=\"sidebar-section\">';\n html += '<div class=\"category-header\" data-category=\"' + category + '\">';\n html += '<svg class=\"category-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m9 18 6-6-6-6\"/></svg>';\n html += '<span>' + category + '</span>';\n html += '<span class=\"category-count\">' + items.length + '</span>';\n html += '</div>';\n html += '<ul class=\"art-list\" data-category=\"' + category + '\">';\n for (const art of items) {\n const active = selectedArt?.path === art.path ? 'active' : '';\n const variantCount = art.variants?.length || 0;\n html += '<li class=\"art-item ' + active + '\" data-path=\"' + art.path + '\">';\n html += '<span>' + escapeHtml(art.metadata.title) + '</span>';\n html += '<span class=\"art-variant-count\">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n html += '</li>';\n }\n html += '</ul>';\n html += '</div>';\n }\n\n sidebar.innerHTML = html;\n\n sidebar.querySelectorAll('.art-item').forEach(item => {\n item.addEventListener('click', () => {\n const artPath = item.dataset.path;\n selectedArt = arts.find(a => a.path === artPath);\n renderSidebar();\n renderContent();\n });\n });\n\n sidebar.querySelectorAll('.category-header').forEach(header => {\n header.addEventListener('click', () => {\n header.classList.toggle('collapsed');\n const list = sidebar.querySelector('.art-list[data-category=\"' + header.dataset.category + '\"]');\n if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';\n });\n });\n }\n\n function renderContent() {\n const content = document.getElementById('content');\n if (!selectedArt) {\n content.innerHTML = \\`\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants</div>\n </div>\n \\`;\n return;\n }\n\n const meta = selectedArt.metadata;\n const tags = meta.tags || [];\n const variantCount = selectedArt.variants?.length || 0;\n\n let html = '<div class=\"content-inner\">';\n html += '<div class=\"content-header\">';\n html += '<h1 class=\"content-title\">' + escapeHtml(meta.title) + '</h1>';\n if (meta.description) {\n html += '<p class=\"content-description\">' + escapeHtml(meta.description) + '</p>';\n }\n html += '<div class=\"content-meta\">';\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/></svg>' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n if (meta.category) {\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>' + escapeHtml(meta.category) + '</span>';\n }\n for (const tag of tags) {\n html += '<span class=\"meta-tag\">#' + escapeHtml(tag) + '</span>';\n }\n html += '</div>';\n html += '</div>';\n\n html += '<div class=\"gallery\">';\n for (const variant of selectedArt.variants) {\n const previewUrl = basePath + '/preview?art=' + encodeURIComponent(selectedArt.path) + '&variant=' + encodeURIComponent(variant.name);\n\n html += '<div class=\"variant-card\">';\n html += '<div class=\"variant-preview\">';\n html += '<iframe src=\"' + previewUrl + '\" loading=\"lazy\" title=\"' + escapeHtml(variant.name) + '\"></iframe>';\n html += '</div>';\n html += '<div class=\"variant-info\">';\n html += '<div>';\n html += '<span class=\"variant-name\">' + escapeHtml(variant.name) + '</span>';\n if (variant.isDefault) html += ' <span class=\"variant-badge\">Default</span>';\n html += '</div>';\n html += '<div class=\"variant-actions\">';\n html += '<button class=\"variant-action-btn\" title=\"Open in new tab\" onclick=\"window.open(\\\\'' + previewUrl + '\\\\', \\\\'_blank\\\\')\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg></button>';\n html += '</div>';\n html += '</div>';\n html += '</div>';\n }\n html += '</div>';\n html += '</div>';\n\n content.innerHTML = html;\n }\n\n function escapeHtml(str) {\n if (!str) return '';\n return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n }\n\n // Search\n document.getElementById('search').addEventListener('input', (e) => {\n searchQuery = e.target.value;\n renderSidebar();\n });\n\n // Keyboard shortcut for search\n document.addEventListener('keydown', (e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault();\n document.getElementById('search').focus();\n }\n });\n\n loadArts();\n </script>\n</body>\n</html>`;\n}\n\nfunction generateGalleryModule(basePath: string): string {\n return `\nexport const basePath = '${basePath}';\nexport async function loadArts() {\n const res = await fetch(basePath + '/api/arts');\n return res.json();\n}\n`;\n}\n\n// Addon initialization code injected into preview iframe modules.\n// Shared between generatePreviewModule and generatePreviewModuleWithProps.\nconst MUSEA_ADDONS_INIT_CODE = `\nfunction __museaInitAddons(container) {\n // === DOM event capture ===\n const CAPTURE_EVENTS = ['click','dblclick','input','change','submit','focus','blur','keydown','keyup'];\n for (const evt of CAPTURE_EVENTS) {\n container.addEventListener(evt, (e) => {\n const payload = {\n name: evt,\n target: e.target?.tagName,\n timestamp: Date.now(),\n source: 'dom'\n };\n if (e.target && 'value' in e.target) {\n payload.value = e.target.value;\n }\n window.parent.postMessage({ type: 'musea:event', payload }, '*');\n }, true);\n }\n\n // === Message handler for parent commands ===\n let measureActive = false;\n let measureOverlay = null;\n let measureLabel = null;\n\n function toggleStyleById(id, enabled, css) {\n let el = document.getElementById(id);\n if (enabled) {\n if (!el) {\n el = document.createElement('style');\n el.id = id;\n el.textContent = css;\n document.head.appendChild(el);\n }\n } else {\n if (el) el.remove();\n }\n }\n\n function createMeasureOverlay() {\n if (measureOverlay) return;\n measureOverlay = document.createElement('div');\n measureOverlay.id = 'musea-measure-overlay';\n measureOverlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:99999;';\n document.body.appendChild(measureOverlay);\n\n measureLabel = document.createElement('div');\n measureLabel.className = 'musea-measure-label';\n measureLabel.style.cssText = 'position:fixed;background:#333;color:#fff;font-size:11px;padding:2px 6px;border-radius:3px;pointer-events:none;z-index:100000;display:none;';\n document.body.appendChild(measureLabel);\n }\n\n function removeMeasureOverlay() {\n if (measureOverlay) { measureOverlay.remove(); measureOverlay = null; }\n if (measureLabel) { measureLabel.remove(); measureLabel = null; }\n }\n\n function onMeasureMouseMove(e) {\n if (!measureActive || !measureOverlay) return;\n const el = document.elementFromPoint(e.clientX, e.clientY);\n if (!el || el === measureOverlay || el === measureLabel) return;\n\n const rect = el.getBoundingClientRect();\n const cs = getComputedStyle(el);\n const mt = parseFloat(cs.marginTop) || 0;\n const mr = parseFloat(cs.marginRight) || 0;\n const mb = parseFloat(cs.marginBottom) || 0;\n const ml = parseFloat(cs.marginLeft) || 0;\n const bt = parseFloat(cs.borderTopWidth) || 0;\n const br = parseFloat(cs.borderRightWidth) || 0;\n const bb = parseFloat(cs.borderBottomWidth) || 0;\n const blw = parseFloat(cs.borderLeftWidth) || 0;\n const pt = parseFloat(cs.paddingTop) || 0;\n const pr = parseFloat(cs.paddingRight) || 0;\n const pb = parseFloat(cs.paddingBottom) || 0;\n const pl = parseFloat(cs.paddingLeft) || 0;\n\n const cw = rect.width - blw - br - pl - pr;\n const ch = rect.height - bt - bb - pt - pb;\n\n measureOverlay.innerHTML = ''\n // Margin\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + (rect.left - ml) + 'px;top:' + (rect.top - mt) + 'px;'\n + 'width:' + (rect.width + ml + mr) + 'px;height:' + mt + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + (rect.left - ml) + 'px;top:' + (rect.bottom) + 'px;'\n + 'width:' + (rect.width + ml + mr) + 'px;height:' + mb + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + (rect.left - ml) + 'px;top:' + rect.top + 'px;'\n + 'width:' + ml + 'px;height:' + rect.height + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + rect.right + 'px;top:' + rect.top + 'px;'\n + 'width:' + mr + 'px;height:' + rect.height + 'px;\"></div>'\n // Border\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + rect.left + 'px;top:' + rect.top + 'px;'\n + 'width:' + rect.width + 'px;height:' + bt + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + rect.left + 'px;top:' + (rect.bottom - bb) + 'px;'\n + 'width:' + rect.width + 'px;height:' + bb + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + rect.left + 'px;top:' + (rect.top + bt) + 'px;'\n + 'width:' + blw + 'px;height:' + (rect.height - bt - bb) + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + (rect.right - br) + 'px;top:' + (rect.top + bt) + 'px;'\n + 'width:' + br + 'px;height:' + (rect.height - bt - bb) + 'px;\"></div>'\n // Padding\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt) + 'px;'\n + 'width:' + (rect.width - blw - br) + 'px;height:' + pt + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.left + blw) + 'px;top:' + (rect.bottom - bb - pb) + 'px;'\n + 'width:' + (rect.width - blw - br) + 'px;height:' + pb + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt + pt) + 'px;'\n + 'width:' + pl + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.right - br - pr) + 'px;top:' + (rect.top + bt + pt) + 'px;'\n + 'width:' + pr + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;\"></div>'\n // Content\n + '<div style=\"position:fixed;background:rgba(100,149,237,0.3);'\n + 'left:' + (rect.left + blw + pl) + 'px;top:' + (rect.top + bt + pt) + 'px;'\n + 'width:' + cw + 'px;height:' + ch + 'px;\"></div>';\n\n // Label\n measureLabel.textContent = Math.round(rect.width) + ' x ' + Math.round(rect.height);\n measureLabel.style.display = 'block';\n measureLabel.style.left = (rect.right + 8) + 'px';\n measureLabel.style.top = rect.top + 'px';\n }\n\n window.addEventListener('message', (e) => {\n if (!e.data?.type?.startsWith('musea:')) return;\n const { type, payload } = e.data;\n switch (type) {\n case 'musea:set-background': {\n if (payload.pattern === 'checkerboard') {\n document.body.style.background = '';\n document.body.classList.add('musea-bg-checkerboard');\n } else {\n document.body.classList.remove('musea-bg-checkerboard');\n document.body.style.background = payload.color || '';\n }\n break;\n }\n case 'musea:toggle-outline': {\n toggleStyleById('musea-outline', payload.enabled,\n '* { outline: 1px solid rgba(255, 0, 0, 0.3) !important; }');\n break;\n }\n case 'musea:toggle-measure': {\n measureActive = payload.enabled;\n if (measureActive) {\n createMeasureOverlay();\n document.addEventListener('mousemove', onMeasureMouseMove);\n } else {\n document.removeEventListener('mousemove', onMeasureMouseMove);\n removeMeasureOverlay();\n }\n break;\n }\n case 'musea:set-props': {\n // Store props for remount - handled by preview module\n if (window.__museaSetProps) {\n window.__museaSetProps(payload.props || {});\n }\n break;\n }\n case 'musea:set-slots': {\n // Store slots for remount - handled by preview module\n if (window.__museaSetSlots) {\n window.__museaSetSlots(payload.slots || {});\n }\n break;\n }\n }\n });\n\n // Notify parent that iframe is ready\n window.parent.postMessage({ type: 'musea:ready', payload: {} }, '*');\n}\n`;\n\nfunction generatePreviewModule(\n art: ArtFileInfo,\n variantComponentName: string,\n variantName: string,\n): string {\n const artModuleId = `virtual:musea-art:${art.path}`;\n const escapedVariantName = escapeTemplate(variantName);\n\n return `\nimport { createApp, reactive, h } from 'vue';\nimport * as artModule from '${artModuleId}';\n\nconst container = document.getElementById('app');\n\n${MUSEA_ADDONS_INIT_CODE}\n\nlet currentApp = null;\nconst propsOverride = reactive({});\nconst slotsOverride = reactive({ default: '' });\n\nwindow.__museaSetProps = (props) => {\n // Clear old keys\n for (const key of Object.keys(propsOverride)) {\n delete propsOverride[key];\n }\n Object.assign(propsOverride, props);\n};\n\nwindow.__museaSetSlots = (slots) => {\n Object.assign(slotsOverride, slots);\n};\n\nasync function mount() {\n try {\n // Get the specific variant component\n const VariantComponent = artModule['${variantComponentName}'];\n const RawComponent = artModule.__component__;\n\n if (!VariantComponent) {\n throw new Error('Variant component \"${variantComponentName}\" not found in art module');\n }\n\n // Create and mount the app\n const app = createApp(VariantComponent);\n container.innerHTML = '';\n container.className = 'musea-variant';\n app.mount(container);\n currentApp = app;\n\n console.log('[musea-preview] Mounted variant: ${escapedVariantName}');\n __museaInitAddons(container);\n\n // Override set-props to remount with raw component + props\n if (RawComponent) {\n window.__museaSetProps = (props) => {\n for (const key of Object.keys(propsOverride)) {\n delete propsOverride[key];\n }\n Object.assign(propsOverride, props);\n remountWithProps(RawComponent);\n };\n window.__museaSetSlots = (slots) => {\n Object.assign(slotsOverride, slots);\n remountWithProps(RawComponent);\n };\n }\n } catch (error) {\n console.error('[musea-preview] Failed to mount:', error);\n container.innerHTML = \\`\n <div class=\"musea-error\">\n <div class=\"musea-error-title\">Failed to render component</div>\n <div>\\${error.message}</div>\n <pre>\\${error.stack || ''}</pre>\n </div>\n \\`;\n }\n}\n\nfunction remountWithProps(Component) {\n if (currentApp) {\n currentApp.unmount();\n }\n const app = createApp({\n setup() {\n return () => {\n const slotFns = {};\n if (slotsOverride.default) {\n slotFns.default = () => h('span', { innerHTML: slotsOverride.default });\n }\n return h('div', { class: 'musea-variant' }, [\n h(Component, { ...propsOverride }, slotFns)\n ]);\n };\n }\n });\n container.innerHTML = '';\n app.mount(container);\n currentApp = app;\n}\n\nmount();\n`;\n}\n\nfunction generateManifestModule(artFiles: Map<string, ArtFileInfo>): string {\n const arts = Array.from(artFiles.values());\n return `export const arts = ${JSON.stringify(arts, null, 2)};`;\n}\n\nfunction generateArtModule(art: ArtFileInfo, filePath: string): string {\n let componentImportPath: string | undefined;\n let componentName: string | undefined;\n\n if (art.isInline && art.componentPath) {\n // Inline art: import the host .vue file itself as the component\n componentImportPath = art.componentPath;\n componentName = path.basename(art.componentPath, \".vue\");\n } else if (art.metadata.component) {\n // Traditional .art.vue: resolve component from the component attribute\n const comp = art.metadata.component;\n componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);\n componentName = path.basename(comp, \".vue\");\n }\n\n let code = `\n// Auto-generated module for: ${path.basename(filePath)}\nimport { defineComponent, h } from 'vue';\n`;\n\n if (componentImportPath && componentName) {\n code += `import ${componentName} from '${componentImportPath}';\\n`;\n code += `export const __component__ = ${componentName};\\n`;\n }\n\n code += `\nexport const metadata = ${JSON.stringify(art.metadata)};\nexport const variants = ${JSON.stringify(art.variants)};\n`;\n\n // Generate variant components\n for (const variant of art.variants) {\n const variantComponentName = toPascalCase(variant.name);\n\n let template = variant.template;\n\n // Replace <Self> with the actual component name (for inline art)\n if (componentName) {\n template = template.replace(/<Self/g, `<${componentName}`).replace(/<\\/Self>/g, `</${componentName}>`);\n }\n\n // Escape the template for use in a JS string\n const escapedTemplate = template\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/`/g, \"\\\\`\")\n .replace(/\\$/g, \"\\\\$\");\n\n // Wrap template with the variant container\n const fullTemplate = `<div class=\"musea-variant\" data-variant=\"${variant.name}\">${escapedTemplate}</div>`;\n\n if (componentName) {\n code += `\nexport const ${variantComponentName} = {\n name: '${variantComponentName}',\n components: { ${componentName} },\n template: \\`${fullTemplate}\\`,\n};\n`;\n } else {\n code += `\nexport const ${variantComponentName} = {\n name: '${variantComponentName}',\n template: \\`${fullTemplate}\\`,\n};\n`;\n }\n }\n\n // Default export\n const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];\n if (defaultVariant) {\n code += `\nexport default ${toPascalCase(defaultVariant.name)};\n`;\n }\n\n return code;\n}\n\nasync function generateStorybookFiles(\n artFiles: Map<string, ArtFileInfo>,\n root: string,\n outDir: string,\n): Promise<void> {\n const binding = loadNative();\n const outputDir = path.resolve(root, outDir);\n\n // Ensure output directory exists\n await fs.promises.mkdir(outputDir, { recursive: true });\n\n for (const [filePath, _art] of artFiles) {\n try {\n const source = await fs.promises.readFile(filePath, \"utf-8\");\n const csf = binding.artToCsf(source, { filename: filePath });\n\n const outputPath = path.join(outputDir, csf.filename);\n await fs.promises.writeFile(outputPath, csf.code, \"utf-8\");\n\n console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);\n } catch (e) {\n console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);\n }\n }\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nfunction escapeTemplate(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\").replace(/\\n/g, \"\\\\n\");\n}\n\nfunction generatePreviewModuleWithProps(\n art: ArtFileInfo,\n variantComponentName: string,\n variantName: string,\n propsOverride: Record<string, unknown>,\n): string {\n const artModuleId = `virtual:musea-art:${art.path}`;\n const escapedVariantName = escapeTemplate(variantName);\n const propsJson = JSON.stringify(propsOverride);\n\n return `\nimport { createApp, h } from 'vue';\nimport * as artModule from '${artModuleId}';\n\nconst container = document.getElementById('app');\nconst propsOverride = ${propsJson};\n\n${MUSEA_ADDONS_INIT_CODE}\n\nasync function mount() {\n try {\n const VariantComponent = artModule['${variantComponentName}'];\n if (!VariantComponent) {\n throw new Error('Variant component \"${variantComponentName}\" not found');\n }\n\n const WrappedComponent = {\n render() {\n return h(VariantComponent, propsOverride);\n }\n };\n\n const app = createApp(WrappedComponent);\n container.innerHTML = '';\n container.className = 'musea-variant';\n app.mount(container);\n console.log('[musea-preview] Mounted variant: ${escapedVariantName} with props override');\n __museaInitAddons(container);\n } catch (error) {\n console.error('[musea-preview] Failed to mount:', error);\n container.innerHTML = '<div class=\"musea-error\"><div class=\"musea-error-title\">Failed to render</div><div>' + error.message + '</div></div>';\n }\n}\n\nmount();\n`;\n}\n\nfunction generatePreviewHtml(art: ArtFileInfo, variant: ArtVariant, basePath: string): string {\n // Create a unique module URL for each variant to avoid caching issues\n const previewModuleUrl = `${basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n html, body {\n width: 100%;\n height: 100%;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #ffffff;\n }\n .musea-variant {\n padding: 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .musea-error {\n color: #dc2626;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n padding: 1rem;\n font-size: 0.875rem;\n max-width: 400px;\n }\n .musea-error-title {\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n .musea-error pre {\n font-family: monospace;\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-all;\n margin-top: 0.5rem;\n padding: 0.5rem;\n background: #fff;\n border-radius: 4px;\n }\n .musea-loading {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n color: #6b7280;\n font-size: 0.875rem;\n }\n .musea-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid #e5e7eb;\n border-top-color: #3b82f6;\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n @keyframes spin { to { transform: rotate(360deg); } }\n\n /* Musea Addons: Checkerboard background for transparent mode */\n .musea-bg-checkerboard {\n background-image:\n linear-gradient(45deg, #ccc 25%, transparent 25%),\n linear-gradient(-45deg, #ccc 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #ccc 75%),\n linear-gradient(-45deg, transparent 75%, #ccc 75%) !important;\n background-size: 20px 20px !important;\n background-position: 0 0, 0 10px, 10px -10px, -10px 0 !important;\n }\n\n /* Musea Addons: Measure label */\n .musea-measure-label {\n position: fixed;\n background: #333;\n color: #fff;\n font-size: 11px;\n padding: 2px 6px;\n border-radius: 3px;\n pointer-events: none;\n z-index: 100000;\n }\n </style>\n</head>\n<body>\n <div id=\"app\" class=\"musea-variant\" data-art=\"${escapeHtml(art.path)}\" data-variant=\"${escapeHtml(variant.name)}\">\n <div class=\"musea-loading\">\n <div class=\"musea-spinner\"></div>\n Loading component...\n </div>\n </div>\n <script type=\"module\" src=\"${previewModuleUrl}\"></script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nexport default musea;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["tokensPath: string","dirPath: string","categories: TokenCategory[]","tokens: Record<string, unknown>","prefix: string[]","obj: Record<string, unknown>","tokens: Record<string, DesignToken>","subcategories: TokenCategory[]","value: unknown","raw: Record<string, unknown>","name: string","token: DesignToken","category: TokenCategory","level: number","config: StyleDictionaryConfig","content: string","filename: string","native: NativeBinding | null","options: MuseaOptions","config: ResolvedConfig","server: ViteDevServer | null","mainPlugin: Plugin","mimeTypes: Record<string, string>","data: unknown","message: string","artPath","art","filePath: string","info: ArtFileInfo","file: string","include: string[]","exclude: string[]","root: string","filepath: string","pattern: string","files: string[]","dir: string","basePath: string","art: ArtFileInfo","variantComponentName: string","variantName: string","artFiles: Map<string, ArtFileInfo>","componentImportPath: string | undefined","componentName: string | undefined","outDir: string","str: string","propsOverride: Record<string, unknown>","variant: ArtVariant"],"sources":["../src/style-dictionary.ts","../src/index.ts"],"sourcesContent":["/**\n * Style Dictionary integration for Musea.\n * Generates design token documentation from Style Dictionary format.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Design token value.\n */\nexport interface DesignToken {\n value: string | number;\n type?: string;\n description?: string;\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Token category (e.g., colors, spacing, typography).\n */\nexport interface TokenCategory {\n name: string;\n tokens: Record<string, DesignToken>;\n subcategories?: TokenCategory[];\n}\n\n/**\n * Style Dictionary output format.\n */\nexport interface StyleDictionaryOutput {\n categories: TokenCategory[];\n metadata: {\n name: string;\n version?: string;\n generatedAt: string;\n };\n}\n\n/**\n * Configuration for Style Dictionary integration.\n */\nexport interface StyleDictionaryConfig {\n /**\n * Path to tokens JSON/JS file or directory.\n */\n tokensPath: string;\n\n /**\n * Output format for documentation.\n * @default 'html'\n */\n outputFormat?: \"html\" | \"json\" | \"markdown\";\n\n /**\n * Output directory for generated documentation.\n * @default '.vize/tokens'\n */\n outputDir?: string;\n\n /**\n * Custom token transformations.\n */\n transforms?: TokenTransform[];\n}\n\n/**\n * Token transformation function.\n */\nexport type TokenTransform = (token: DesignToken, path: string[]) => DesignToken;\n\n/**\n * Parse Style Dictionary tokens file.\n */\nexport async function parseTokens(tokensPath: string): Promise<TokenCategory[]> {\n const absolutePath = path.resolve(tokensPath);\n const stat = await fs.promises.stat(absolutePath);\n\n if (stat.isDirectory()) {\n return parseTokenDirectory(absolutePath);\n }\n\n const content = await fs.promises.readFile(absolutePath, \"utf-8\");\n const tokens = JSON.parse(content);\n return flattenTokens(tokens);\n}\n\n/**\n * Parse tokens from a directory.\n */\nasync function parseTokenDirectory(dirPath: string): Promise<TokenCategory[]> {\n const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });\n const categories: TokenCategory[] = [];\n\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith(\".json\") || entry.name.endsWith(\".tokens.json\"))) {\n const filePath = path.join(dirPath, entry.name);\n const content = await fs.promises.readFile(filePath, \"utf-8\");\n const tokens = JSON.parse(content);\n const categoryName = path\n .basename(entry.name, path.extname(entry.name))\n .replace(\".tokens\", \"\");\n\n categories.push({\n name: formatCategoryName(categoryName),\n tokens: extractTokens(tokens),\n subcategories: extractSubcategories(tokens),\n });\n }\n }\n\n return categories;\n}\n\n/**\n * Flatten nested token structure into categories.\n */\nfunction flattenTokens(tokens: Record<string, unknown>, prefix: string[] = []): TokenCategory[] {\n const categories: TokenCategory[] = [];\n\n for (const [key, value] of Object.entries(tokens)) {\n if (isTokenValue(value)) {\n // This is a token leaf node\n continue;\n }\n\n if (typeof value === \"object\" && value !== null) {\n const categoryTokens = extractTokens(value as Record<string, unknown>);\n const subcategories = flattenTokens(value as Record<string, unknown>, [...prefix, key]);\n\n if (Object.keys(categoryTokens).length > 0 || subcategories.length > 0) {\n categories.push({\n name: formatCategoryName(key),\n tokens: categoryTokens,\n subcategories: subcategories.length > 0 ? subcategories : undefined,\n });\n }\n }\n }\n\n return categories;\n}\n\n/**\n * Extract token values from an object.\n */\nfunction extractTokens(obj: Record<string, unknown>): Record<string, DesignToken> {\n const tokens: Record<string, DesignToken> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isTokenValue(value)) {\n tokens[key] = normalizeToken(value as Record<string, unknown>);\n }\n }\n\n return tokens;\n}\n\n/**\n * Extract subcategories from an object.\n */\nfunction extractSubcategories(obj: Record<string, unknown>): TokenCategory[] | undefined {\n const subcategories: TokenCategory[] = [];\n\n for (const [key, value] of Object.entries(obj)) {\n if (!isTokenValue(value) && typeof value === \"object\" && value !== null) {\n const categoryTokens = extractTokens(value as Record<string, unknown>);\n const nested = extractSubcategories(value as Record<string, unknown>);\n\n if (Object.keys(categoryTokens).length > 0 || (nested && nested.length > 0)) {\n subcategories.push({\n name: formatCategoryName(key),\n tokens: categoryTokens,\n subcategories: nested,\n });\n }\n }\n }\n\n return subcategories.length > 0 ? subcategories : undefined;\n}\n\n/**\n * Check if a value is a token definition.\n */\nfunction isTokenValue(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const obj = value as Record<string, unknown>;\n return \"value\" in obj && (typeof obj.value === \"string\" || typeof obj.value === \"number\");\n}\n\n/**\n * Normalize token to DesignToken interface.\n */\nfunction normalizeToken(raw: Record<string, unknown>): DesignToken {\n return {\n value: raw.value as string | number,\n type: raw.type as string | undefined,\n description: raw.description as string | undefined,\n attributes: raw.attributes as Record<string, unknown> | undefined,\n };\n}\n\n/**\n * Format category name for display.\n */\nfunction formatCategoryName(name: string): string {\n return name\n .replace(/[-_]/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .split(\" \")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\" \");\n}\n\n/**\n * Generate HTML documentation for tokens.\n */\nexport function generateTokensHtml(categories: TokenCategory[]): string {\n const renderToken = (name: string, token: DesignToken): string => {\n const isColor =\n typeof token.value === \"string\" &&\n (token.value.startsWith(\"#\") ||\n token.value.startsWith(\"rgb\") ||\n token.value.startsWith(\"hsl\") ||\n token.type === \"color\");\n\n return `\n <div class=\"token\">\n <div class=\"token-preview\">\n ${isColor ? `<div class=\"color-swatch\" style=\"background: ${token.value}\"></div>` : \"\"}\n </div>\n <div class=\"token-info\">\n <div class=\"token-name\">${name}</div>\n <div class=\"token-value\">${token.value}</div>\n ${token.description ? `<div class=\"token-description\">${token.description}</div>` : \"\"}\n </div>\n </div>\n `;\n };\n\n const renderCategory = (category: TokenCategory, level: number = 2): string => {\n const heading = `h${Math.min(level, 6)}`;\n let html = `<${heading}>${category.name}</${heading}>`;\n html += '<div class=\"tokens-grid\">';\n\n for (const [name, token] of Object.entries(category.tokens)) {\n html += renderToken(name, token);\n }\n\n html += \"</div>\";\n\n if (category.subcategories) {\n for (const sub of category.subcategories) {\n html += renderCategory(sub, level + 1);\n }\n }\n\n return html;\n };\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Design Tokens - Musea</title>\n <style>\n :root {\n --musea-bg: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-accent: #a34828;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, sans-serif;\n background: var(--musea-bg);\n color: var(--musea-text);\n line-height: 1.6;\n padding: 2rem;\n }\n h1 { margin-bottom: 2rem; color: var(--musea-accent); }\n h2 { margin: 2rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--musea-border); }\n h3, h4, h5, h6 { margin: 1.5rem 0 0.75rem; }\n .tokens-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 1rem;\n margin-bottom: 1.5rem;\n }\n .token {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: 8px;\n padding: 1rem;\n display: flex;\n gap: 1rem;\n align-items: center;\n }\n .token-preview {\n flex-shrink: 0;\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .color-swatch {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n border: 1px solid var(--musea-border);\n }\n .token-info {\n flex: 1;\n min-width: 0;\n }\n .token-name {\n font-weight: 600;\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.875rem;\n }\n .token-value {\n color: var(--musea-text-muted);\n font-family: 'JetBrains Mono', monospace;\n font-size: 0.75rem;\n word-break: break-all;\n }\n .token-description {\n color: var(--musea-text-muted);\n font-size: 0.75rem;\n margin-top: 0.25rem;\n }\n </style>\n</head>\n<body>\n <h1>Design Tokens</h1>\n ${categories.map((cat) => renderCategory(cat)).join(\"\")}\n</body>\n</html>`;\n}\n\n/**\n * Generate Markdown documentation for tokens.\n */\nexport function generateTokensMarkdown(categories: TokenCategory[]): string {\n const renderCategory = (category: TokenCategory, level: number = 2): string => {\n const heading = \"#\".repeat(level);\n let md = `\\n${heading} ${category.name}\\n\\n`;\n\n if (Object.keys(category.tokens).length > 0) {\n md += \"| Token | Value | Description |\\n\";\n md += \"|-------|-------|-------------|\\n\";\n\n for (const [name, token] of Object.entries(category.tokens)) {\n const desc = token.description || \"-\";\n md += `| \\`${name}\\` | \\`${token.value}\\` | ${desc} |\\n`;\n }\n md += \"\\n\";\n }\n\n if (category.subcategories) {\n for (const sub of category.subcategories) {\n md += renderCategory(sub, level + 1);\n }\n }\n\n return md;\n };\n\n let markdown = \"# Design Tokens\\n\\n\";\n markdown += `> Generated by Musea on ${new Date().toISOString()}\\n`;\n\n for (const category of categories) {\n markdown += renderCategory(category);\n }\n\n return markdown;\n}\n\n/**\n * Style Dictionary plugin for Musea.\n */\nexport async function processStyleDictionary(\n config: StyleDictionaryConfig,\n): Promise<StyleDictionaryOutput> {\n const categories = await parseTokens(config.tokensPath);\n const outputDir = config.outputDir ?? \".vize/tokens\";\n const outputFormat = config.outputFormat ?? \"html\";\n\n // Ensure output directory exists\n await fs.promises.mkdir(outputDir, { recursive: true });\n\n // Generate documentation\n let content: string;\n let filename: string;\n\n switch (outputFormat) {\n case \"html\":\n content = generateTokensHtml(categories);\n filename = \"tokens.html\";\n break;\n case \"markdown\":\n content = generateTokensMarkdown(categories);\n filename = \"tokens.md\";\n break;\n case \"json\":\n default:\n content = JSON.stringify({ categories }, null, 2);\n filename = \"tokens.json\";\n }\n\n const outputPath = path.join(outputDir, filename);\n await fs.promises.writeFile(outputPath, content, \"utf-8\");\n\n console.log(`[musea] Generated token documentation: ${outputPath}`);\n\n return {\n categories,\n metadata: {\n name: path.basename(config.tokensPath),\n generatedAt: new Date().toISOString(),\n },\n };\n}\n\nexport default processStyleDictionary;\n","/**\n * Vite plugin for Musea - Component gallery for Vue components.\n *\n * @example\n * ```ts\n * import { defineConfig } from 'vite';\n * import { vize } from '@vizejs/vite-plugin';\n * import { musea } from '@vizejs/vite-plugin-musea';\n *\n * export default defineConfig({\n * plugins: [vize(), musea()],\n * });\n * ```\n */\n\nimport type { Plugin, ViteDevServer, ResolvedConfig } from \"vite\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { vizeConfigStore } from \"@vizejs/vite-plugin\";\n\nimport type {\n MuseaOptions,\n ArtFileInfo,\n ArtMetadata,\n ArtVariant,\n CsfOutput,\n PaletteApiResponse,\n AnalysisApiResponse,\n A11yOptions,\n A11yResult,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"./types.js\";\n\nexport type {\n MuseaOptions,\n ArtFileInfo,\n ArtMetadata,\n ArtVariant,\n CsfOutput,\n VrtOptions,\n ViewportConfig,\n PaletteApiResponse,\n AnalysisApiResponse,\n A11yOptions,\n A11yResult,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"./types.js\";\n\nexport {\n MuseaVrtRunner,\n generateVrtReport,\n generateVrtJsonReport,\n type VrtResult,\n type VrtSummary,\n} from \"./vrt.js\";\n\nexport {\n processStyleDictionary,\n parseTokens,\n generateTokensHtml,\n generateTokensMarkdown,\n type DesignToken,\n type TokenCategory,\n type StyleDictionaryConfig,\n type StyleDictionaryOutput,\n} from \"./style-dictionary.js\";\n\nexport { MuseaA11yRunner, type A11ySummary } from \"./a11y.js\";\n\nexport {\n generateArtFile,\n writeArtFile,\n type AutogenOptions,\n type AutogenOutput,\n type PropDefinition,\n type GeneratedVariant,\n} from \"./autogen.js\";\n\n// Virtual module prefixes\nconst VIRTUAL_MUSEA_PREFIX = \"\\0musea:\";\nconst VIRTUAL_GALLERY = \"\\0musea-gallery\";\nconst VIRTUAL_MANIFEST = \"\\0musea-manifest\";\n\n// Native binding types\ninterface NativeBinding {\n parseArt: (\n source: string,\n options?: { filename?: string },\n ) => {\n filename: string;\n metadata: {\n title: string;\n description?: string;\n component?: string;\n category?: string;\n tags: string[];\n status: string;\n order?: number;\n };\n variants: Array<{\n name: string;\n template: string;\n is_default: boolean;\n skip_vrt: boolean;\n }>;\n has_script_setup: boolean;\n has_script: boolean;\n style_count: number;\n };\n artToCsf: (\n source: string,\n options?: { filename?: string },\n ) => {\n code: string;\n filename: string;\n };\n generateArtPalette?: (\n source: string,\n artOptions?: { filename?: string },\n paletteOptions?: { infer_options?: boolean; group_by_type?: boolean },\n ) => {\n title: string;\n controls: Array<{\n name: string;\n control: string;\n default_value?: unknown;\n description?: string;\n required: boolean;\n options: Array<{ label: string; value: unknown }>;\n range?: { min: number; max: number; step?: number };\n group?: string;\n }>;\n groups: string[];\n json: string;\n typescript: string;\n };\n generateArtDoc?: (\n source: string,\n artOptions?: { filename?: string },\n docOptions?: {\n include_source?: boolean;\n include_templates?: boolean;\n include_metadata?: boolean;\n },\n ) => {\n markdown: string;\n filename: string;\n title: string;\n category?: string;\n variant_count: number;\n };\n analyzeSfc?: (\n source: string,\n options?: { filename?: string },\n ) => {\n props: Array<{ name: string; type: string; required: boolean; default_value?: unknown }>;\n emits: string[];\n };\n}\n\n// Lazy-load native binding\nlet native: NativeBinding | null = null;\n\nfunction loadNative(): NativeBinding {\n if (native) return native;\n\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeBinding;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Create Musea Vite plugin.\n */\nexport function musea(options: MuseaOptions = {}): Plugin[] {\n let include = options.include ?? [\"**/*.art.vue\"];\n let exclude = options.exclude ?? [\"node_modules/**\", \"dist/**\"];\n let basePath = options.basePath ?? \"/__musea__\";\n let storybookCompat = options.storybookCompat ?? false;\n const storybookOutDir = options.storybookOutDir ?? \".storybook/stories\";\n let inlineArt = options.inlineArt ?? false;\n\n let config: ResolvedConfig;\n let server: ViteDevServer | null = null;\n const artFiles = new Map<string, ArtFileInfo>();\n\n // Main plugin\n const mainPlugin: Plugin = {\n name: \"vite-plugin-musea\",\n enforce: \"pre\",\n\n config() {\n // Add Vue alias for runtime template compilation\n // This is needed because variant templates are compiled at runtime\n return {\n resolve: {\n alias: {\n vue: \"vue/dist/vue.esm-bundler.js\",\n },\n },\n };\n },\n\n configResolved(resolvedConfig) {\n config = resolvedConfig;\n\n // Merge musea config from vize.config.ts (plugin args > config file > defaults)\n const vizeConfig = vizeConfigStore.get(resolvedConfig.root);\n if (vizeConfig?.musea) {\n const mc = vizeConfig.musea;\n // Only apply config file values when plugin options were not explicitly set\n if (!options.include && mc.include) include = mc.include;\n if (!options.exclude && mc.exclude) exclude = mc.exclude;\n if (!options.basePath && mc.basePath) basePath = mc.basePath;\n if (options.storybookCompat === undefined && mc.storybookCompat !== undefined)\n storybookCompat = mc.storybookCompat;\n if (options.inlineArt === undefined && mc.inlineArt !== undefined) inlineArt = mc.inlineArt;\n }\n },\n\n configureServer(devServer) {\n server = devServer;\n\n // Gallery SPA route - serves built SPA or falls back to inline HTML\n devServer.middlewares.use(basePath, async (req, res, next) => {\n const url = req.url || \"/\";\n\n // Serve SPA for gallery routes (not /api/, /preview, /preview-module, /art)\n if (\n url === \"/\" ||\n url === \"/index.html\" ||\n url.startsWith(\"/tokens\") ||\n url.startsWith(\"/component/\")\n ) {\n // Try serving built SPA first\n const galleryDistDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"gallery\",\n );\n const indexHtmlPath = path.join(galleryDistDir, \"index.html\");\n\n try {\n await fs.promises.access(indexHtmlPath);\n let html = await fs.promises.readFile(indexHtmlPath, \"utf-8\");\n // Inject basePath for runtime use\n html = html.replace(\n \"</head>\",\n `<script>window.__MUSEA_BASE_PATH__='${basePath}';</script></head>`,\n );\n // Transform through Vite for HMR\n html = await devServer.transformIndexHtml(basePath + url, html);\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n return;\n } catch {\n // Fall back to inline gallery HTML\n const html = generateGalleryHtml(basePath);\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n return;\n }\n }\n // Serve gallery static assets (JS, CSS) from built SPA\n if (url.startsWith(\"/assets/\")) {\n const galleryDistDir = path.resolve(\n path.dirname(new URL(import.meta.url).pathname),\n \"gallery\",\n );\n const filePath = path.join(galleryDistDir, url);\n try {\n const stat = await fs.promises.stat(filePath);\n if (stat.isFile()) {\n const content = await fs.promises.readFile(filePath);\n const ext = path.extname(filePath);\n const mimeTypes: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".css\": \"text/css\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".ico\": \"image/x-icon\",\n \".woff2\": \"font/woff2\",\n \".woff\": \"font/woff\",\n };\n res.setHeader(\"Content-Type\", mimeTypes[ext] || \"application/octet-stream\");\n res.setHeader(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n res.end(content);\n return;\n }\n } catch {\n // File not found, fall through\n }\n }\n\n next();\n });\n\n // Preview module route - serves the JavaScript module for a specific variant\n devServer.middlewares.use(`${basePath}/preview-module`, async (req, res, _next) => {\n const url = new URL(req.url || \"\", `http://localhost`);\n const artPath = url.searchParams.get(\"art\");\n const variantName = url.searchParams.get(\"variant\");\n\n if (!artPath || !variantName) {\n res.statusCode = 400;\n res.end(\"Missing art or variant parameter\");\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found\");\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n res.statusCode = 404;\n res.end(\"Variant not found\");\n return;\n }\n\n const variantComponentName = toPascalCase(variant.name);\n const moduleCode = generatePreviewModule(art, variantComponentName, variant.name);\n\n // Transform the module through Vite to resolve imports\n try {\n const result = await devServer.transformRequest(\n `virtual:musea-preview:${artPath}:${variantName}`,\n );\n if (result) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.code);\n return;\n }\n } catch {\n // Fall through to manual response\n }\n\n // Fallback: serve the module directly (imports won't be resolved)\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(moduleCode);\n });\n\n // VRT preview route - renders a single variant for screenshot\n devServer.middlewares.use(`${basePath}/preview`, async (req, res, _next) => {\n const url = new URL(req.url || \"\", `http://localhost`);\n const artPath = url.searchParams.get(\"art\");\n const variantName = url.searchParams.get(\"variant\");\n\n if (!artPath || !variantName) {\n res.statusCode = 400;\n res.end(\"Missing art or variant parameter\");\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found\");\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n res.statusCode = 404;\n res.end(\"Variant not found\");\n return;\n }\n\n const rawHtml = generatePreviewHtml(art, variant, basePath);\n // Transform HTML through Vite to properly resolve module imports\n const html = await devServer.transformIndexHtml(\n `${basePath}/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`,\n rawHtml,\n );\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(html);\n });\n\n // Art module route - serves transformed art file as ES module\n devServer.middlewares.use(`${basePath}/art`, async (req, res, next) => {\n const url = new URL(req.url || \"\", \"http://localhost\");\n const artPath = decodeURIComponent(url.pathname.slice(1)); // Remove leading /\n\n if (!artPath) {\n next();\n return;\n }\n\n const art = artFiles.get(artPath);\n if (!art) {\n res.statusCode = 404;\n res.end(\"Art not found: \" + artPath);\n return;\n }\n\n // Transform through Vite for proper imports\n try {\n const virtualId = `virtual:musea-art:${artPath}`;\n const result = await devServer.transformRequest(virtualId);\n if (result) {\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(result.code);\n } else {\n // Fallback: generate and serve the module directly\n const moduleCode = generateArtModule(art, artPath);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n }\n } catch (err) {\n console.error(\"[musea] Failed to transform art module:\", err);\n // Fallback if transform fails\n const moduleCode = generateArtModule(art, artPath);\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n }\n });\n\n // API endpoints\n devServer.middlewares.use(`${basePath}/api`, async (req, res, next) => {\n const sendJson = (data: unknown, status = 200) => {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n };\n\n const sendError = (message: string, status = 500) => {\n sendJson({ error: message }, status);\n };\n\n // GET /api/arts - List all arts\n if (req.url === \"/arts\" && req.method === \"GET\") {\n sendJson(Array.from(artFiles.values()));\n return;\n }\n\n // GET /api/tokens - Get design tokens\n if (req.url === \"/tokens\" && req.method === \"GET\") {\n sendJson({ categories: [] });\n return;\n }\n\n // Arts sub-routes: /api/arts/:encodedPath/...\n if (req.url?.startsWith(\"/arts/\") && req.method === \"GET\") {\n const rest = req.url.slice(6); // after \"/arts/\"\n\n // Check for sub-resource patterns\n const paletteMatch = rest.match(/^(.+)\\/palette$/);\n const analysisMatch = rest.match(/^(.+)\\/analysis$/);\n const docsMatch = rest.match(/^(.+)\\/docs$/);\n const a11yMatch = rest.match(/^(.+)\\/variants\\/([^/]+)\\/a11y$/);\n\n if (paletteMatch) {\n // GET /api/arts/:path/palette\n const artPath = decodeURIComponent(paletteMatch[1]);\n const art = artFiles.get(artPath);\n if (!art) {\n sendError(\"Art not found\", 404);\n return;\n }\n\n try {\n const source = await fs.promises.readFile(artPath, \"utf-8\");\n const binding = loadNative();\n if (binding.generateArtPalette) {\n const palette = binding.generateArtPalette(source, { filename: artPath });\n sendJson(palette);\n } else {\n sendJson({\n title: art.metadata.title,\n controls: [],\n groups: [],\n json: \"{}\",\n typescript: \"\",\n });\n }\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n return;\n }\n\n if (analysisMatch) {\n // GET /api/arts/:path/analysis\n const artPath = decodeURIComponent(analysisMatch[1]);\n const art = artFiles.get(artPath);\n if (!art) {\n sendError(\"Art not found\", 404);\n return;\n }\n\n try {\n // Determine the component file path: inline art uses the file itself, .art.vue uses the component attribute\n const resolvedComponentPath =\n art.isInline && art.componentPath\n ? art.componentPath\n : art.metadata.component\n ? path.isAbsolute(art.metadata.component)\n ? art.metadata.component\n : path.resolve(path.dirname(artPath), art.metadata.component)\n : null;\n\n if (resolvedComponentPath) {\n const source = await fs.promises.readFile(resolvedComponentPath, \"utf-8\");\n const binding = loadNative();\n if (binding.analyzeSfc) {\n const analysis = binding.analyzeSfc(source, { filename: resolvedComponentPath });\n sendJson(analysis);\n } else {\n sendJson({ props: [], emits: [] });\n }\n } else {\n sendJson({ props: [], emits: [] });\n }\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n return;\n }\n\n if (docsMatch) {\n // GET /api/arts/:path/docs\n const artPath = decodeURIComponent(docsMatch[1]);\n const art = artFiles.get(artPath);\n if (!art) {\n sendError(\"Art not found\", 404);\n return;\n }\n\n try {\n const source = await fs.promises.readFile(artPath, \"utf-8\");\n const binding = loadNative();\n if (binding.generateArtDoc) {\n const doc = binding.generateArtDoc(source, { filename: artPath });\n sendJson(doc);\n } else {\n sendJson({\n markdown: \"\",\n title: art.metadata.title,\n variant_count: art.variants.length,\n });\n }\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n return;\n }\n\n if (a11yMatch) {\n // GET /api/arts/:path/variants/:name/a11y\n const artPath = decodeURIComponent(a11yMatch[1]);\n const _variantName = decodeURIComponent(a11yMatch[2]);\n const art = artFiles.get(artPath);\n if (!art) {\n sendError(\"Art not found\", 404);\n return;\n }\n\n // Return empty a11y results (populated after VRT --a11y run)\n sendJson({ violations: [], passes: 0, incomplete: 0 });\n return;\n }\n\n // GET /api/arts/:path - Get single art (no sub-resource)\n const artPath = decodeURIComponent(rest);\n const art = artFiles.get(artPath);\n if (art) {\n sendJson(art);\n } else {\n sendError(\"Art not found\", 404);\n }\n return;\n }\n\n // POST /api/preview-with-props\n if (req.url === \"/preview-with-props\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk;\n });\n req.on(\"end\", () => {\n try {\n const { artPath: reqArtPath, variantName, props: propsOverride } = JSON.parse(body);\n const art = artFiles.get(reqArtPath);\n if (!art) {\n sendError(\"Art not found\", 404);\n return;\n }\n\n const variant = art.variants.find((v) => v.name === variantName);\n if (!variant) {\n sendError(\"Variant not found\", 404);\n return;\n }\n\n // Generate preview module with props override\n const variantComponentName = toPascalCase(variant.name);\n const moduleCode = generatePreviewModuleWithProps(\n art,\n variantComponentName,\n variant.name,\n propsOverride,\n );\n res.setHeader(\"Content-Type\", \"application/javascript\");\n res.end(moduleCode);\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n });\n return;\n }\n\n // POST /api/generate\n if (req.url === \"/generate\" && req.method === \"POST\") {\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk;\n });\n req.on(\"end\", async () => {\n try {\n const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);\n const { generateArtFile: genArt } = await import(\"./autogen.js\");\n const result = await genArt(reqComponentPath, autogenOptions);\n sendJson({\n generated: true,\n componentName: result.componentName,\n variants: result.variants,\n artFileContent: result.artFileContent,\n });\n } catch (e) {\n sendError(e instanceof Error ? e.message : String(e));\n }\n });\n return;\n }\n\n next();\n });\n\n // Watch for Art file changes\n devServer.watcher.on(\"change\", async (file) => {\n if (file.endsWith(\".art.vue\") && shouldProcess(file, include, exclude, config.root)) {\n await processArtFile(file);\n console.log(`[musea] Reloaded: ${path.relative(config.root, file)}`);\n }\n // Inline art: re-check .vue files on change\n if (inlineArt && file.endsWith(\".vue\") && !file.endsWith(\".art.vue\")) {\n const hadArt = artFiles.has(file);\n const source = await fs.promises.readFile(file, \"utf-8\");\n if (source.includes(\"<art\")) {\n await processArtFile(file);\n console.log(`[musea] Reloaded inline art: ${path.relative(config.root, file)}`);\n } else if (hadArt) {\n artFiles.delete(file);\n console.log(`[musea] Removed inline art: ${path.relative(config.root, file)}`);\n }\n }\n });\n\n devServer.watcher.on(\"add\", async (file) => {\n if (file.endsWith(\".art.vue\") && shouldProcess(file, include, exclude, config.root)) {\n await processArtFile(file);\n console.log(`[musea] Added: ${path.relative(config.root, file)}`);\n }\n // Inline art: check new .vue files\n if (inlineArt && file.endsWith(\".vue\") && !file.endsWith(\".art.vue\")) {\n const source = await fs.promises.readFile(file, \"utf-8\");\n if (source.includes(\"<art\")) {\n await processArtFile(file);\n console.log(`[musea] Added inline art: ${path.relative(config.root, file)}`);\n }\n }\n });\n\n devServer.watcher.on(\"unlink\", (file) => {\n if (artFiles.has(file)) {\n artFiles.delete(file);\n console.log(`[musea] Removed: ${path.relative(config.root, file)}`);\n }\n });\n },\n\n async buildStart() {\n // Scan for Art files\n const files = await scanArtFiles(config.root, include, exclude, inlineArt);\n\n console.log(`[musea] Found ${files.length} art files`);\n\n for (const file of files) {\n await processArtFile(file);\n }\n\n // Generate Storybook CSF if enabled\n if (storybookCompat) {\n await generateStorybookFiles(artFiles, config.root, storybookOutDir);\n }\n },\n\n resolveId(id) {\n if (id === VIRTUAL_GALLERY) {\n return VIRTUAL_GALLERY;\n }\n if (id === VIRTUAL_MANIFEST) {\n return VIRTUAL_MANIFEST;\n }\n // Handle virtual:musea-preview: prefix for preview modules\n if (id.startsWith(\"virtual:musea-preview:\")) {\n return \"\\0musea-preview:\" + id.slice(\"virtual:musea-preview:\".length);\n }\n // Handle virtual:musea-art: prefix for preview modules\n if (id.startsWith(\"virtual:musea-art:\")) {\n const artPath = id.slice(\"virtual:musea-art:\".length);\n if (artFiles.has(artPath)) {\n return \"\\0musea-art:\" + artPath;\n }\n }\n if (id.endsWith(\".art.vue\")) {\n const resolved = path.resolve(config.root, id);\n if (artFiles.has(resolved)) {\n return VIRTUAL_MUSEA_PREFIX + resolved;\n }\n }\n // Inline art: resolve .vue files that have <art> blocks\n if (inlineArt && id.endsWith(\".vue\") && !id.endsWith(\".art.vue\")) {\n const resolved = path.resolve(config.root, id);\n if (artFiles.has(resolved)) {\n return VIRTUAL_MUSEA_PREFIX + resolved;\n }\n }\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_GALLERY) {\n return generateGalleryModule(basePath);\n }\n if (id === VIRTUAL_MANIFEST) {\n return generateManifestModule(artFiles);\n }\n // Handle \\0musea-preview: prefix for preview modules\n if (id.startsWith(\"\\0musea-preview:\")) {\n const rest = id.slice(\"\\0musea-preview:\".length);\n const lastColonIndex = rest.lastIndexOf(\":\");\n if (lastColonIndex !== -1) {\n const artPath = rest.slice(0, lastColonIndex);\n const variantName = rest.slice(lastColonIndex + 1);\n const art = artFiles.get(artPath);\n if (art) {\n const variantComponentName = toPascalCase(variantName);\n return generatePreviewModule(art, variantComponentName, variantName);\n }\n }\n }\n // Handle \\0musea-art: prefix for preview modules\n if (id.startsWith(\"\\0musea-art:\")) {\n const artPath = id.slice(\"\\0musea-art:\".length);\n const art = artFiles.get(artPath);\n if (art) {\n return generateArtModule(art, artPath);\n }\n }\n if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {\n const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length);\n const art = artFiles.get(realPath);\n if (art) {\n return generateArtModule(art, realPath);\n }\n }\n return null;\n },\n\n async handleHotUpdate(ctx) {\n const { file } = ctx;\n if (file.endsWith(\".art.vue\") && artFiles.has(file)) {\n await processArtFile(file);\n\n // Invalidate virtual modules\n const virtualId = VIRTUAL_MUSEA_PREFIX + file;\n const modules = server?.moduleGraph.getModulesByFile(virtualId);\n if (modules) {\n return [...modules];\n }\n }\n\n // Inline art: HMR for .vue files with <art> blocks\n if (inlineArt && file.endsWith(\".vue\") && !file.endsWith(\".art.vue\") && artFiles.has(file)) {\n await processArtFile(file);\n\n const virtualId = VIRTUAL_MUSEA_PREFIX + file;\n const modules = server?.moduleGraph.getModulesByFile(virtualId);\n if (modules) {\n return [...modules];\n }\n }\n\n return undefined;\n },\n };\n\n // Helper functions scoped to this plugin instance\n\n async function processArtFile(filePath: string): Promise<void> {\n try {\n const source = await fs.promises.readFile(filePath, \"utf-8\");\n const binding = loadNative();\n const parsed = binding.parseArt(source, { filename: filePath });\n\n // Skip files with no variants (e.g. .vue files without <art> block)\n if (!parsed.variants || parsed.variants.length === 0) return;\n\n const isInline = !filePath.endsWith(\".art.vue\");\n\n const info: ArtFileInfo = {\n path: filePath,\n metadata: {\n title: parsed.metadata.title || (isInline ? path.basename(filePath, \".vue\") : \"\"),\n description: parsed.metadata.description,\n component: isInline ? undefined : parsed.metadata.component,\n category: parsed.metadata.category,\n tags: parsed.metadata.tags,\n status: parsed.metadata.status as \"draft\" | \"ready\" | \"deprecated\",\n order: parsed.metadata.order,\n },\n variants: parsed.variants.map((v) => ({\n name: v.name,\n template: v.template,\n isDefault: v.is_default,\n skipVrt: v.skip_vrt,\n })),\n hasScriptSetup: parsed.has_script_setup,\n hasScript: parsed.has_script,\n styleCount: parsed.style_count,\n isInline,\n componentPath: isInline ? filePath : undefined,\n };\n\n artFiles.set(filePath, info);\n } catch (e) {\n console.error(`[musea] Failed to process ${filePath}:`, e);\n }\n }\n\n return [mainPlugin];\n}\n\n// Utility functions\n\nfunction shouldProcess(file: string, include: string[], exclude: string[], root: string): boolean {\n const relative = path.relative(root, file);\n\n // Check exclude patterns\n for (const pattern of exclude) {\n if (matchGlob(relative, pattern)) {\n return false;\n }\n }\n\n // Check include patterns\n for (const pattern of include) {\n if (matchGlob(relative, pattern)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction matchGlob(filepath: string, pattern: string): boolean {\n // Simple glob matching (supports * and **)\n // Escape . first, then replace glob patterns\n const regex = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \".*\")\n .replace(/\\*(?!\\*)/g, \"[^/]*\");\n\n return new RegExp(`^${regex}$`).test(filepath);\n}\n\nasync function scanArtFiles(\n root: string,\n include: string[],\n exclude: string[],\n scanInlineArt = false,\n): Promise<string[]> {\n const files: string[] = [];\n\n async function scan(dir: string): Promise<void> {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relative = path.relative(root, fullPath);\n\n // Check exclude\n let excluded = false;\n for (const pattern of exclude) {\n if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {\n excluded = true;\n break;\n }\n }\n\n if (excluded) continue;\n\n if (entry.isDirectory()) {\n await scan(fullPath);\n } else if (entry.isFile() && entry.name.endsWith(\".art.vue\")) {\n // Check include\n for (const pattern of include) {\n if (matchGlob(relative, pattern)) {\n files.push(fullPath);\n break;\n }\n }\n } else if (\n scanInlineArt &&\n entry.isFile() &&\n entry.name.endsWith(\".vue\") &&\n !entry.name.endsWith(\".art.vue\")\n ) {\n // Inline art: check if .vue file contains <art block\n const content = await fs.promises.readFile(fullPath, \"utf-8\");\n if (content.includes(\"<art\")) {\n files.push(fullPath);\n }\n }\n }\n }\n\n await scan(root);\n return files;\n}\n\nfunction generateGalleryHtml(basePath: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Musea - Component Gallery</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-bg-elevated: #2d2a27;\n --musea-accent: #a34828;\n --musea-accent-hover: #c45a32;\n --musea-accent-subtle: rgba(163, 72, 40, 0.15);\n --musea-text: #e6e9f0;\n --musea-text-secondary: #c4c9d4;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n --musea-border-subtle: #2a2725;\n --musea-success: #4ade80;\n --musea-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);\n --musea-radius-sm: 6px;\n --musea-radius-md: 8px;\n --musea-radius-lg: 12px;\n --musea-transition: 0.15s ease;\n }\n\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n }\n\n /* Header */\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 0 1.5rem;\n height: 56px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n\n .header-left {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n }\n\n .logo {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 1.125rem;\n font-weight: 700;\n color: var(--musea-accent);\n text-decoration: none;\n }\n\n .logo-svg {\n width: 32px;\n height: 32px;\n flex-shrink: 0;\n }\n\n .logo-icon svg {\n width: 16px;\n height: 16px;\n color: white;\n }\n\n .header-subtitle {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n font-weight: 500;\n padding-left: 1.5rem;\n border-left: 1px solid var(--musea-border);\n }\n\n .search-container {\n position: relative;\n width: 280px;\n }\n\n .search-input {\n width: 100%;\n background: var(--musea-bg-tertiary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-md);\n padding: 0.5rem 0.75rem 0.5rem 2.25rem;\n color: var(--musea-text);\n font-size: 0.8125rem;\n outline: none;\n transition: border-color var(--musea-transition), background var(--musea-transition);\n }\n\n .search-input::placeholder {\n color: var(--musea-text-muted);\n }\n\n .search-input:focus {\n border-color: var(--musea-accent);\n background: var(--musea-bg-elevated);\n }\n\n .search-icon {\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--musea-text-muted);\n pointer-events: none;\n }\n\n /* Layout */\n .main {\n display: grid;\n grid-template-columns: 260px 1fr;\n min-height: calc(100vh - 56px);\n }\n\n /* Sidebar */\n .sidebar {\n background: var(--musea-bg-secondary);\n border-right: 1px solid var(--musea-border);\n overflow-y: auto;\n overflow-x: hidden;\n }\n\n .sidebar::-webkit-scrollbar {\n width: 6px;\n }\n\n .sidebar::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .sidebar::-webkit-scrollbar-thumb {\n background: var(--musea-border);\n border-radius: 3px;\n }\n\n .sidebar-section {\n padding: 0.75rem;\n }\n\n .category-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.625rem 0.75rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--musea-text-muted);\n cursor: pointer;\n user-select: none;\n border-radius: var(--musea-radius-sm);\n transition: background var(--musea-transition);\n }\n\n .category-header:hover {\n background: var(--musea-bg-tertiary);\n }\n\n .category-icon {\n width: 16px;\n height: 16px;\n transition: transform var(--musea-transition);\n }\n\n .category-header.collapsed .category-icon {\n transform: rotate(-90deg);\n }\n\n .category-count {\n margin-left: auto;\n background: var(--musea-bg-tertiary);\n padding: 0.125rem 0.375rem;\n border-radius: 4px;\n font-size: 0.625rem;\n }\n\n .art-list {\n list-style: none;\n margin-top: 0.25rem;\n }\n\n .art-item {\n display: flex;\n align-items: center;\n gap: 0.625rem;\n padding: 0.5rem 0.75rem 0.5rem 1.75rem;\n border-radius: var(--musea-radius-sm);\n cursor: pointer;\n font-size: 0.8125rem;\n color: var(--musea-text-secondary);\n transition: all var(--musea-transition);\n position: relative;\n }\n\n .art-item::before {\n content: '';\n position: absolute;\n left: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--musea-border);\n transition: background var(--musea-transition);\n }\n\n .art-item:hover {\n background: var(--musea-bg-tertiary);\n color: var(--musea-text);\n }\n\n .art-item:hover::before {\n background: var(--musea-text-muted);\n }\n\n .art-item.active {\n background: var(--musea-accent-subtle);\n color: var(--musea-accent-hover);\n }\n\n .art-item.active::before {\n background: var(--musea-accent);\n }\n\n .art-variant-count {\n margin-left: auto;\n font-size: 0.6875rem;\n color: var(--musea-text-muted);\n opacity: 0;\n transition: opacity var(--musea-transition);\n }\n\n .art-item:hover .art-variant-count {\n opacity: 1;\n }\n\n /* Content */\n .content {\n background: var(--musea-bg-primary);\n overflow-y: auto;\n }\n\n .content-inner {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n }\n\n .content-header {\n margin-bottom: 2rem;\n }\n\n .content-title {\n font-size: 1.5rem;\n font-weight: 700;\n margin-bottom: 0.5rem;\n }\n\n .content-description {\n color: var(--musea-text-muted);\n font-size: 0.9375rem;\n max-width: 600px;\n }\n\n .content-meta {\n display: flex;\n align-items: center;\n gap: 1rem;\n margin-top: 1rem;\n }\n\n .meta-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.625rem;\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-sm);\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n }\n\n .meta-tag svg {\n width: 12px;\n height: 12px;\n }\n\n /* Gallery Grid */\n .gallery {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.25rem;\n }\n\n /* Variant Card */\n .variant-card {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-lg);\n overflow: hidden;\n transition: all var(--musea-transition);\n }\n\n .variant-card:hover {\n border-color: var(--musea-text-muted);\n box-shadow: var(--musea-shadow);\n transform: translateY(-2px);\n }\n\n .variant-preview {\n aspect-ratio: 16 / 10;\n background: var(--musea-bg-tertiary);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n overflow: hidden;\n }\n\n .variant-preview iframe {\n width: 100%;\n height: 100%;\n border: none;\n background: white;\n }\n\n .variant-preview-placeholder {\n color: var(--musea-text-muted);\n font-size: 0.8125rem;\n text-align: center;\n padding: 1rem;\n }\n\n .variant-preview-code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 0.75rem;\n color: var(--musea-text-muted);\n background: var(--musea-bg-primary);\n padding: 1rem;\n overflow: auto;\n max-height: 100%;\n width: 100%;\n }\n\n .variant-info {\n padding: 1rem;\n border-top: 1px solid var(--musea-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .variant-name {\n font-weight: 600;\n font-size: 0.875rem;\n }\n\n .variant-badge {\n font-size: 0.625rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n padding: 0.1875rem 0.5rem;\n border-radius: 4px;\n background: var(--musea-accent-subtle);\n color: var(--musea-accent);\n }\n\n .variant-actions {\n display: flex;\n gap: 0.5rem;\n }\n\n .variant-action-btn {\n width: 28px;\n height: 28px;\n border: none;\n background: var(--musea-bg-tertiary);\n border-radius: var(--musea-radius-sm);\n color: var(--musea-text-muted);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all var(--musea-transition);\n }\n\n .variant-action-btn:hover {\n background: var(--musea-bg-elevated);\n color: var(--musea-text);\n }\n\n .variant-action-btn svg {\n width: 14px;\n height: 14px;\n }\n\n /* Empty State */\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n min-height: 400px;\n text-align: center;\n padding: 2rem;\n }\n\n .empty-state-icon {\n width: 80px;\n height: 80px;\n background: var(--musea-bg-secondary);\n border-radius: var(--musea-radius-lg);\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 1.5rem;\n }\n\n .empty-state-icon svg {\n width: 40px;\n height: 40px;\n color: var(--musea-text-muted);\n }\n\n .empty-state-title {\n font-size: 1.125rem;\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n\n .empty-state-text {\n color: var(--musea-text-muted);\n font-size: 0.875rem;\n max-width: 300px;\n }\n\n /* Loading */\n .loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 200px;\n color: var(--musea-text-muted);\n gap: 0.75rem;\n }\n\n .loading-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid var(--musea-border);\n border-top-color: var(--musea-accent);\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .main {\n grid-template-columns: 1fr;\n }\n .sidebar {\n display: none;\n }\n .header-subtitle {\n display: none;\n }\n }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div class=\"header-left\">\n <a href=\"${basePath}\" class=\"logo\">\n <svg class=\"logo-svg\" width=\"32\" height=\"32\" viewBox=\"0 0 200 200\" fill=\"none\">\n <defs>\n <linearGradient id=\"metal-grad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"20%\">\n <stop offset=\"0%\" stop-color=\"#f0f2f5\"/>\n <stop offset=\"50%\" stop-color=\"#9ca3b0\"/>\n <stop offset=\"100%\" stop-color=\"#e07048\"/>\n </linearGradient>\n <linearGradient id=\"metal-grad-dark\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"30%\">\n <stop offset=\"0%\" stop-color=\"#d0d4dc\"/>\n <stop offset=\"60%\" stop-color=\"#6b7280\"/>\n <stop offset=\"100%\" stop-color=\"#c45530\"/>\n </linearGradient>\n </defs>\n <g transform=\"translate(40, 40)\">\n <g transform=\"skewX(-12)\">\n <path d=\"M 100 0 L 60 120 L 105 30 L 100 0 Z\" fill=\"url(#metal-grad-dark)\" stroke=\"#4b5563\" stroke-width=\"0.5\"/>\n <path d=\"M 30 0 L 60 120 L 80 20 L 30 0 Z\" fill=\"url(#metal-grad)\" stroke-width=\"0.5\" stroke-opacity=\"0.4\"/>\n </g>\n </g>\n <g transform=\"translate(110, 120)\">\n <line x1=\"5\" y1=\"10\" x2=\"5\" y2=\"50\" stroke=\"#e07048\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <line x1=\"60\" y1=\"10\" x2=\"60\" y2=\"50\" stroke=\"#e07048\" stroke-width=\"3\" stroke-linecap=\"round\"/>\n <path d=\"M 0 10 L 32.5 0 L 65 10\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n <rect x=\"15\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"36\" y=\"18\" width=\"14\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.7\"/>\n <rect x=\"23\" y=\"35\" width=\"18\" height=\"12\" rx=\"1\" fill=\"none\" stroke=\"#e07048\" stroke-width=\"1.5\" opacity=\"0.6\"/>\n </g>\n </svg>\n Musea\n </a>\n <span class=\"header-subtitle\">Component Gallery</span>\n </div>\n <div class=\"search-container\">\n <svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input type=\"text\" class=\"search-input\" placeholder=\"Search components...\" id=\"search\">\n </div>\n </header>\n\n <main class=\"main\">\n <aside class=\"sidebar\" id=\"sidebar\">\n <div class=\"loading\">\n <div class=\"loading-spinner\"></div>\n Loading...\n </div>\n </aside>\n <section class=\"content\" id=\"content\">\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants and documentation</div>\n </div>\n </section>\n </main>\n\n <script type=\"module\">\n const basePath = '${basePath}';\n let arts = [];\n let selectedArt = null;\n let searchQuery = '';\n\n async function loadArts() {\n try {\n const res = await fetch(basePath + '/api/arts');\n arts = await res.json();\n renderSidebar();\n } catch (e) {\n console.error('Failed to load arts:', e);\n document.getElementById('sidebar').innerHTML = '<div class=\"loading\">Failed to load</div>';\n }\n }\n\n function renderSidebar() {\n const sidebar = document.getElementById('sidebar');\n const categories = {};\n\n const filtered = searchQuery\n ? arts.filter(a => a.metadata.title.toLowerCase().includes(searchQuery.toLowerCase()))\n : arts;\n\n for (const art of filtered) {\n const cat = art.metadata.category || 'Components';\n if (!categories[cat]) categories[cat] = [];\n categories[cat].push(art);\n }\n\n if (Object.keys(categories).length === 0) {\n sidebar.innerHTML = '<div class=\"sidebar-section\"><div class=\"loading\">No components found</div></div>';\n return;\n }\n\n let html = '';\n for (const [category, items] of Object.entries(categories)) {\n html += '<div class=\"sidebar-section\">';\n html += '<div class=\"category-header\" data-category=\"' + category + '\">';\n html += '<svg class=\"category-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"m9 18 6-6-6-6\"/></svg>';\n html += '<span>' + category + '</span>';\n html += '<span class=\"category-count\">' + items.length + '</span>';\n html += '</div>';\n html += '<ul class=\"art-list\" data-category=\"' + category + '\">';\n for (const art of items) {\n const active = selectedArt?.path === art.path ? 'active' : '';\n const variantCount = art.variants?.length || 0;\n html += '<li class=\"art-item ' + active + '\" data-path=\"' + art.path + '\">';\n html += '<span>' + escapeHtml(art.metadata.title) + '</span>';\n html += '<span class=\"art-variant-count\">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n html += '</li>';\n }\n html += '</ul>';\n html += '</div>';\n }\n\n sidebar.innerHTML = html;\n\n sidebar.querySelectorAll('.art-item').forEach(item => {\n item.addEventListener('click', () => {\n const artPath = item.dataset.path;\n selectedArt = arts.find(a => a.path === artPath);\n renderSidebar();\n renderContent();\n });\n });\n\n sidebar.querySelectorAll('.category-header').forEach(header => {\n header.addEventListener('click', () => {\n header.classList.toggle('collapsed');\n const list = sidebar.querySelector('.art-list[data-category=\"' + header.dataset.category + '\"]');\n if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';\n });\n });\n }\n\n function renderContent() {\n const content = document.getElementById('content');\n if (!selectedArt) {\n content.innerHTML = \\`\n <div class=\"empty-state\">\n <div class=\"empty-state-icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z\"/>\n <path d=\"M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z\"/>\n <path d=\"M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z\"/>\n </svg>\n </div>\n <div class=\"empty-state-title\">Select a component</div>\n <div class=\"empty-state-text\">Choose a component from the sidebar to view its variants</div>\n </div>\n \\`;\n return;\n }\n\n const meta = selectedArt.metadata;\n const tags = meta.tags || [];\n const variantCount = selectedArt.variants?.length || 0;\n\n let html = '<div class=\"content-inner\">';\n html += '<div class=\"content-header\">';\n html += '<h1 class=\"content-title\">' + escapeHtml(meta.title) + '</h1>';\n if (meta.description) {\n html += '<p class=\"content-description\">' + escapeHtml(meta.description) + '</p>';\n }\n html += '<div class=\"content-meta\">';\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/></svg>' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';\n if (meta.category) {\n html += '<span class=\"meta-tag\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/></svg>' + escapeHtml(meta.category) + '</span>';\n }\n for (const tag of tags) {\n html += '<span class=\"meta-tag\">#' + escapeHtml(tag) + '</span>';\n }\n html += '</div>';\n html += '</div>';\n\n html += '<div class=\"gallery\">';\n for (const variant of selectedArt.variants) {\n const previewUrl = basePath + '/preview?art=' + encodeURIComponent(selectedArt.path) + '&variant=' + encodeURIComponent(variant.name);\n\n html += '<div class=\"variant-card\">';\n html += '<div class=\"variant-preview\">';\n html += '<iframe src=\"' + previewUrl + '\" loading=\"lazy\" title=\"' + escapeHtml(variant.name) + '\"></iframe>';\n html += '</div>';\n html += '<div class=\"variant-info\">';\n html += '<div>';\n html += '<span class=\"variant-name\">' + escapeHtml(variant.name) + '</span>';\n if (variant.isDefault) html += ' <span class=\"variant-badge\">Default</span>';\n html += '</div>';\n html += '<div class=\"variant-actions\">';\n html += '<button class=\"variant-action-btn\" title=\"Open in new tab\" onclick=\"window.open(\\\\'' + previewUrl + '\\\\', \\\\'_blank\\\\')\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg></button>';\n html += '</div>';\n html += '</div>';\n html += '</div>';\n }\n html += '</div>';\n html += '</div>';\n\n content.innerHTML = html;\n }\n\n function escapeHtml(str) {\n if (!str) return '';\n return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n }\n\n // Search\n document.getElementById('search').addEventListener('input', (e) => {\n searchQuery = e.target.value;\n renderSidebar();\n });\n\n // Keyboard shortcut for search\n document.addEventListener('keydown', (e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault();\n document.getElementById('search').focus();\n }\n });\n\n loadArts();\n </script>\n</body>\n</html>`;\n}\n\nfunction generateGalleryModule(basePath: string): string {\n return `\nexport const basePath = '${basePath}';\nexport async function loadArts() {\n const res = await fetch(basePath + '/api/arts');\n return res.json();\n}\n`;\n}\n\n// Addon initialization code injected into preview iframe modules.\n// Shared between generatePreviewModule and generatePreviewModuleWithProps.\nconst MUSEA_ADDONS_INIT_CODE = `\nfunction __museaInitAddons(container) {\n // === DOM event capture ===\n const CAPTURE_EVENTS = ['click','dblclick','input','change','submit','focus','blur','keydown','keyup'];\n for (const evt of CAPTURE_EVENTS) {\n container.addEventListener(evt, (e) => {\n const payload = {\n name: evt,\n target: e.target?.tagName,\n timestamp: Date.now(),\n source: 'dom'\n };\n if (e.target && 'value' in e.target) {\n payload.value = e.target.value;\n }\n window.parent.postMessage({ type: 'musea:event', payload }, '*');\n }, true);\n }\n\n // === Message handler for parent commands ===\n let measureActive = false;\n let measureOverlay = null;\n let measureLabel = null;\n\n function toggleStyleById(id, enabled, css) {\n let el = document.getElementById(id);\n if (enabled) {\n if (!el) {\n el = document.createElement('style');\n el.id = id;\n el.textContent = css;\n document.head.appendChild(el);\n }\n } else {\n if (el) el.remove();\n }\n }\n\n function createMeasureOverlay() {\n if (measureOverlay) return;\n measureOverlay = document.createElement('div');\n measureOverlay.id = 'musea-measure-overlay';\n measureOverlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:99999;';\n document.body.appendChild(measureOverlay);\n\n measureLabel = document.createElement('div');\n measureLabel.className = 'musea-measure-label';\n measureLabel.style.cssText = 'position:fixed;background:#333;color:#fff;font-size:11px;padding:2px 6px;border-radius:3px;pointer-events:none;z-index:100000;display:none;';\n document.body.appendChild(measureLabel);\n }\n\n function removeMeasureOverlay() {\n if (measureOverlay) { measureOverlay.remove(); measureOverlay = null; }\n if (measureLabel) { measureLabel.remove(); measureLabel = null; }\n }\n\n function onMeasureMouseMove(e) {\n if (!measureActive || !measureOverlay) return;\n const el = document.elementFromPoint(e.clientX, e.clientY);\n if (!el || el === measureOverlay || el === measureLabel) return;\n\n const rect = el.getBoundingClientRect();\n const cs = getComputedStyle(el);\n const mt = parseFloat(cs.marginTop) || 0;\n const mr = parseFloat(cs.marginRight) || 0;\n const mb = parseFloat(cs.marginBottom) || 0;\n const ml = parseFloat(cs.marginLeft) || 0;\n const bt = parseFloat(cs.borderTopWidth) || 0;\n const br = parseFloat(cs.borderRightWidth) || 0;\n const bb = parseFloat(cs.borderBottomWidth) || 0;\n const blw = parseFloat(cs.borderLeftWidth) || 0;\n const pt = parseFloat(cs.paddingTop) || 0;\n const pr = parseFloat(cs.paddingRight) || 0;\n const pb = parseFloat(cs.paddingBottom) || 0;\n const pl = parseFloat(cs.paddingLeft) || 0;\n\n const cw = rect.width - blw - br - pl - pr;\n const ch = rect.height - bt - bb - pt - pb;\n\n measureOverlay.innerHTML = ''\n // Margin\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + (rect.left - ml) + 'px;top:' + (rect.top - mt) + 'px;'\n + 'width:' + (rect.width + ml + mr) + 'px;height:' + mt + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + (rect.left - ml) + 'px;top:' + (rect.bottom) + 'px;'\n + 'width:' + (rect.width + ml + mr) + 'px;height:' + mb + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + (rect.left - ml) + 'px;top:' + rect.top + 'px;'\n + 'width:' + ml + 'px;height:' + rect.height + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,165,0,0.3);'\n + 'left:' + rect.right + 'px;top:' + rect.top + 'px;'\n + 'width:' + mr + 'px;height:' + rect.height + 'px;\"></div>'\n // Border\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + rect.left + 'px;top:' + rect.top + 'px;'\n + 'width:' + rect.width + 'px;height:' + bt + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + rect.left + 'px;top:' + (rect.bottom - bb) + 'px;'\n + 'width:' + rect.width + 'px;height:' + bb + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + rect.left + 'px;top:' + (rect.top + bt) + 'px;'\n + 'width:' + blw + 'px;height:' + (rect.height - bt - bb) + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(255,255,0,0.3);'\n + 'left:' + (rect.right - br) + 'px;top:' + (rect.top + bt) + 'px;'\n + 'width:' + br + 'px;height:' + (rect.height - bt - bb) + 'px;\"></div>'\n // Padding\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt) + 'px;'\n + 'width:' + (rect.width - blw - br) + 'px;height:' + pt + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.left + blw) + 'px;top:' + (rect.bottom - bb - pb) + 'px;'\n + 'width:' + (rect.width - blw - br) + 'px;height:' + pb + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt + pt) + 'px;'\n + 'width:' + pl + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;\"></div>'\n + '<div style=\"position:fixed;background:rgba(144,238,144,0.3);'\n + 'left:' + (rect.right - br - pr) + 'px;top:' + (rect.top + bt + pt) + 'px;'\n + 'width:' + pr + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;\"></div>'\n // Content\n + '<div style=\"position:fixed;background:rgba(100,149,237,0.3);'\n + 'left:' + (rect.left + blw + pl) + 'px;top:' + (rect.top + bt + pt) + 'px;'\n + 'width:' + cw + 'px;height:' + ch + 'px;\"></div>';\n\n // Label\n measureLabel.textContent = Math.round(rect.width) + ' x ' + Math.round(rect.height);\n measureLabel.style.display = 'block';\n measureLabel.style.left = (rect.right + 8) + 'px';\n measureLabel.style.top = rect.top + 'px';\n }\n\n window.addEventListener('message', (e) => {\n if (!e.data?.type?.startsWith('musea:')) return;\n const { type, payload } = e.data;\n switch (type) {\n case 'musea:set-background': {\n if (payload.pattern === 'checkerboard') {\n document.body.style.background = '';\n document.body.classList.add('musea-bg-checkerboard');\n } else {\n document.body.classList.remove('musea-bg-checkerboard');\n document.body.style.background = payload.color || '';\n }\n break;\n }\n case 'musea:toggle-outline': {\n toggleStyleById('musea-outline', payload.enabled,\n '* { outline: 1px solid rgba(255, 0, 0, 0.3) !important; }');\n break;\n }\n case 'musea:toggle-measure': {\n measureActive = payload.enabled;\n if (measureActive) {\n createMeasureOverlay();\n document.addEventListener('mousemove', onMeasureMouseMove);\n } else {\n document.removeEventListener('mousemove', onMeasureMouseMove);\n removeMeasureOverlay();\n }\n break;\n }\n case 'musea:set-props': {\n // Store props for remount - handled by preview module\n if (window.__museaSetProps) {\n window.__museaSetProps(payload.props || {});\n }\n break;\n }\n case 'musea:set-slots': {\n // Store slots for remount - handled by preview module\n if (window.__museaSetSlots) {\n window.__museaSetSlots(payload.slots || {});\n }\n break;\n }\n }\n });\n\n // Notify parent that iframe is ready\n window.parent.postMessage({ type: 'musea:ready', payload: {} }, '*');\n}\n`;\n\nfunction generatePreviewModule(\n art: ArtFileInfo,\n variantComponentName: string,\n variantName: string,\n): string {\n const artModuleId = `virtual:musea-art:${art.path}`;\n const escapedVariantName = escapeTemplate(variantName);\n\n return `\nimport { createApp, reactive, h } from 'vue';\nimport * as artModule from '${artModuleId}';\n\nconst container = document.getElementById('app');\n\n${MUSEA_ADDONS_INIT_CODE}\n\nlet currentApp = null;\nconst propsOverride = reactive({});\nconst slotsOverride = reactive({ default: '' });\n\nwindow.__museaSetProps = (props) => {\n // Clear old keys\n for (const key of Object.keys(propsOverride)) {\n delete propsOverride[key];\n }\n Object.assign(propsOverride, props);\n};\n\nwindow.__museaSetSlots = (slots) => {\n Object.assign(slotsOverride, slots);\n};\n\nasync function mount() {\n try {\n // Get the specific variant component\n const VariantComponent = artModule['${variantComponentName}'];\n const RawComponent = artModule.__component__;\n\n if (!VariantComponent) {\n throw new Error('Variant component \"${variantComponentName}\" not found in art module');\n }\n\n // Create and mount the app\n const app = createApp(VariantComponent);\n container.innerHTML = '';\n container.className = 'musea-variant';\n app.mount(container);\n currentApp = app;\n\n console.log('[musea-preview] Mounted variant: ${escapedVariantName}');\n __museaInitAddons(container);\n\n // Override set-props to remount with raw component + props\n if (RawComponent) {\n window.__museaSetProps = (props) => {\n for (const key of Object.keys(propsOverride)) {\n delete propsOverride[key];\n }\n Object.assign(propsOverride, props);\n remountWithProps(RawComponent);\n };\n window.__museaSetSlots = (slots) => {\n Object.assign(slotsOverride, slots);\n remountWithProps(RawComponent);\n };\n }\n } catch (error) {\n console.error('[musea-preview] Failed to mount:', error);\n container.innerHTML = \\`\n <div class=\"musea-error\">\n <div class=\"musea-error-title\">Failed to render component</div>\n <div>\\${error.message}</div>\n <pre>\\${error.stack || ''}</pre>\n </div>\n \\`;\n }\n}\n\nfunction remountWithProps(Component) {\n if (currentApp) {\n currentApp.unmount();\n }\n const app = createApp({\n setup() {\n return () => {\n const slotFns = {};\n if (slotsOverride.default) {\n slotFns.default = () => h('span', { innerHTML: slotsOverride.default });\n }\n return h('div', { class: 'musea-variant' }, [\n h(Component, { ...propsOverride }, slotFns)\n ]);\n };\n }\n });\n container.innerHTML = '';\n app.mount(container);\n currentApp = app;\n}\n\nmount();\n`;\n}\n\nfunction generateManifestModule(artFiles: Map<string, ArtFileInfo>): string {\n const arts = Array.from(artFiles.values());\n return `export const arts = ${JSON.stringify(arts, null, 2)};`;\n}\n\nfunction generateArtModule(art: ArtFileInfo, filePath: string): string {\n let componentImportPath: string | undefined;\n let componentName: string | undefined;\n\n if (art.isInline && art.componentPath) {\n // Inline art: import the host .vue file itself as the component\n componentImportPath = art.componentPath;\n componentName = path.basename(art.componentPath, \".vue\");\n } else if (art.metadata.component) {\n // Traditional .art.vue: resolve component from the component attribute\n const comp = art.metadata.component;\n componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);\n componentName = path.basename(comp, \".vue\");\n }\n\n let code = `\n// Auto-generated module for: ${path.basename(filePath)}\nimport { defineComponent, h } from 'vue';\n`;\n\n if (componentImportPath && componentName) {\n code += `import ${componentName} from '${componentImportPath}';\\n`;\n code += `export const __component__ = ${componentName};\\n`;\n }\n\n code += `\nexport const metadata = ${JSON.stringify(art.metadata)};\nexport const variants = ${JSON.stringify(art.variants)};\n`;\n\n // Generate variant components\n for (const variant of art.variants) {\n const variantComponentName = toPascalCase(variant.name);\n\n let template = variant.template;\n\n // Replace <Self> with the actual component name (for inline art)\n if (componentName) {\n template = template\n .replace(/<Self/g, `<${componentName}`)\n .replace(/<\\/Self>/g, `</${componentName}>`);\n }\n\n // Escape the template for use in a JS string\n const escapedTemplate = template\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/`/g, \"\\\\`\")\n .replace(/\\$/g, \"\\\\$\");\n\n // Wrap template with the variant container\n const fullTemplate = `<div class=\"musea-variant\" data-variant=\"${variant.name}\">${escapedTemplate}</div>`;\n\n if (componentName) {\n code += `\nexport const ${variantComponentName} = {\n name: '${variantComponentName}',\n components: { ${componentName} },\n template: \\`${fullTemplate}\\`,\n};\n`;\n } else {\n code += `\nexport const ${variantComponentName} = {\n name: '${variantComponentName}',\n template: \\`${fullTemplate}\\`,\n};\n`;\n }\n }\n\n // Default export\n const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];\n if (defaultVariant) {\n code += `\nexport default ${toPascalCase(defaultVariant.name)};\n`;\n }\n\n return code;\n}\n\nasync function generateStorybookFiles(\n artFiles: Map<string, ArtFileInfo>,\n root: string,\n outDir: string,\n): Promise<void> {\n const binding = loadNative();\n const outputDir = path.resolve(root, outDir);\n\n // Ensure output directory exists\n await fs.promises.mkdir(outputDir, { recursive: true });\n\n for (const [filePath, _art] of artFiles) {\n try {\n const source = await fs.promises.readFile(filePath, \"utf-8\");\n const csf = binding.artToCsf(source, { filename: filePath });\n\n const outputPath = path.join(outputDir, csf.filename);\n await fs.promises.writeFile(outputPath, csf.code, \"utf-8\");\n\n console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);\n } catch (e) {\n console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);\n }\n }\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nfunction escapeTemplate(str: string): string {\n return str.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\").replace(/\\n/g, \"\\\\n\");\n}\n\nfunction generatePreviewModuleWithProps(\n art: ArtFileInfo,\n variantComponentName: string,\n variantName: string,\n propsOverride: Record<string, unknown>,\n): string {\n const artModuleId = `virtual:musea-art:${art.path}`;\n const escapedVariantName = escapeTemplate(variantName);\n const propsJson = JSON.stringify(propsOverride);\n\n return `\nimport { createApp, h } from 'vue';\nimport * as artModule from '${artModuleId}';\n\nconst container = document.getElementById('app');\nconst propsOverride = ${propsJson};\n\n${MUSEA_ADDONS_INIT_CODE}\n\nasync function mount() {\n try {\n const VariantComponent = artModule['${variantComponentName}'];\n if (!VariantComponent) {\n throw new Error('Variant component \"${variantComponentName}\" not found');\n }\n\n const WrappedComponent = {\n render() {\n return h(VariantComponent, propsOverride);\n }\n };\n\n const app = createApp(WrappedComponent);\n container.innerHTML = '';\n container.className = 'musea-variant';\n app.mount(container);\n console.log('[musea-preview] Mounted variant: ${escapedVariantName} with props override');\n __museaInitAddons(container);\n } catch (error) {\n console.error('[musea-preview] Failed to mount:', error);\n container.innerHTML = '<div class=\"musea-error\"><div class=\"musea-error-title\">Failed to render</div><div>' + error.message + '</div></div>';\n }\n}\n\nmount();\n`;\n}\n\nfunction generatePreviewHtml(art: ArtFileInfo, variant: ArtVariant, basePath: string): string {\n // Create a unique module URL for each variant to avoid caching issues\n const previewModuleUrl = `${basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n html, body {\n width: 100%;\n height: 100%;\n }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #ffffff;\n }\n .musea-variant {\n padding: 1.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 100vh;\n }\n .musea-error {\n color: #dc2626;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n padding: 1rem;\n font-size: 0.875rem;\n max-width: 400px;\n }\n .musea-error-title {\n font-weight: 600;\n margin-bottom: 0.5rem;\n }\n .musea-error pre {\n font-family: monospace;\n font-size: 0.75rem;\n white-space: pre-wrap;\n word-break: break-all;\n margin-top: 0.5rem;\n padding: 0.5rem;\n background: #fff;\n border-radius: 4px;\n }\n .musea-loading {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n color: #6b7280;\n font-size: 0.875rem;\n }\n .musea-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid #e5e7eb;\n border-top-color: #3b82f6;\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n }\n @keyframes spin { to { transform: rotate(360deg); } }\n\n /* Musea Addons: Checkerboard background for transparent mode */\n .musea-bg-checkerboard {\n background-image:\n linear-gradient(45deg, #ccc 25%, transparent 25%),\n linear-gradient(-45deg, #ccc 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #ccc 75%),\n linear-gradient(-45deg, transparent 75%, #ccc 75%) !important;\n background-size: 20px 20px !important;\n background-position: 0 0, 0 10px, 10px -10px, -10px 0 !important;\n }\n\n /* Musea Addons: Measure label */\n .musea-measure-label {\n position: fixed;\n background: #333;\n color: #fff;\n font-size: 11px;\n padding: 2px 6px;\n border-radius: 3px;\n pointer-events: none;\n z-index: 100000;\n }\n </style>\n</head>\n<body>\n <div id=\"app\" class=\"musea-variant\" data-art=\"${escapeHtml(art.path)}\" data-variant=\"${escapeHtml(variant.name)}\">\n <div class=\"musea-loading\">\n <div class=\"musea-spinner\"></div>\n Loading component...\n </div>\n </div>\n <script type=\"module\" src=\"${previewModuleUrl}\"></script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nexport default musea;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vrt-CC29JrCN.d.ts","names":[],"sources":["../src/types.ts","../src/vrt.ts"],"sourcesContent":null,"mappings":";;;;;;;;;UAGiB,YAAA;;;AAAjB;;;;AAgDA;;;;EAuBiB;;;;EAcA,QAAA,CAAA,EAAA,MAAW;;;;AAa5B;;;;AAWA;;EAA4B,eAIhB,CAAA,EAAA,MAAA;EAAW;AAED;;;;AAgBtB;;;;AAcA;QAvGQ;;;AAkHR;;AAEW,UA9GM,UAAA,CA8GN;EAAW;AAIN;;;;EAQJ;;;;EAiBK,SAAA,CAAA,EAAA,MAAA;;;;AAiBjB;cA3Ic;;;AA6Jd;;UAvJiB,cAAA;;EAqKA,KAAA,EAAA,MAAQ;;;;EAkBR,iBAAW,CAAA,EAAA,MAAA;;;;AAc5B;;;UAvLiB,WAAA;EAkMA,KAAA,EAAA,MAAA;;;;;;;ACrQjB;;;;AAkBiB,UD8DA,UAAA,CC9DU;;;;EAYV,OAAA,EAAA,OAAA;EAAmB,IAAA,CAAA,EDuD3B,MCvD2B,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;AAAkB,UD6DrC,WAAA,CC7DqC;;;;EAWrC,QAAA,EDsDL,WCtDK;;YDwDL;;EC1CC,cAAA,EAAA,OAAc;EAAA;EAAA,SAQJ,EAAA,OAAA;EAAuB;EAwBvB,UASN,EAAA,MAAA;EAAO;EAUiB,QAA8B,CAAA,EAAA,OAAA;EAAS;EAAV,aAkD7D,CAAA,EAAA,MAAA;;;;;
|
|
1
|
+
{"version":3,"file":"vrt-CC29JrCN.d.ts","names":[],"sources":["../src/types.ts","../src/vrt.ts"],"sourcesContent":null,"mappings":"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"}
|
package/dist/vrt-DP87vGIA.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vrt-DP87vGIA.js","names":["options: ExtendedVrtOptions","artFiles: ArtFileInfo[]","baseUrl: string","results: VrtResult[]","result: VrtResult | null","art: ArtFileInfo","variantName: string","viewport: ViewportConfig","context: BrowserContext | null","page: Page | null","pattern?: string","artPath: string","baselinePath: string","currentPath: string","diffPath: string","width","height","diff","summary: VrtSummary","ms: number","filepath: string","png: PNG","r1: number","g1: number","b1: number","a1: number","r2: number","g2: number","b2: number","a2: number","fg: number","bg: number","alpha: number","img1: PNG","img2: PNG","x: number","y: number","width: number","height: number","pattern: string","str: string"],"sources":["../src/vrt.ts"],"sourcesContent":["/**\n * Visual Regression Testing (VRT) module for Musea.\n * Uses Playwright for browser automation and pixel comparison.\n */\n\nimport type { Browser, BrowserContext, Page } from \"playwright\";\nimport type {\n ArtFileInfo,\n VrtOptions,\n ViewportConfig,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"./types.js\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { PNG } from \"pngjs\";\n\n/**\n * VRT test result for a single variant.\n */\nexport interface VrtResult {\n artPath: string;\n variantName: string;\n viewport: ViewportConfig;\n passed: boolean;\n snapshotPath: string;\n currentPath?: string;\n diffPath?: string;\n diffPercentage?: number;\n diffPixels?: number;\n totalPixels?: number;\n error?: string;\n isNew?: boolean;\n}\n\n/**\n * VRT summary for reporting.\n */\nexport interface VrtSummary {\n total: number;\n passed: number;\n failed: number;\n new: number;\n skipped: number;\n duration: number;\n}\n\n/**\n * Extended VRT options aligned with Rust VrtConfig.\n */\nexport interface ExtendedVrtOptions extends VrtOptions {\n capture?: CaptureConfig;\n comparison?: ComparisonConfig;\n ci?: CiConfig;\n /** Enable a11y auditing during VRT */\n a11y?: boolean;\n}\n\n/**\n * Pixel comparison options.\n */\nexport interface PixelCompareOptions {\n /** Threshold for color difference (0-1). Default: 0.1 */\n threshold?: number;\n /** Include anti-aliasing in diff. Default: false */\n includeAA?: boolean;\n /** Alpha channel comparison. Default: true */\n alpha?: boolean;\n /** Diff highlight color */\n diffColor?: { r: number; g: number; b: number };\n}\n\n/**\n * VRT runner using Playwright.\n */\nexport class MuseaVrtRunner {\n private options: Required<VrtOptions>;\n private capture: Required<CaptureConfig>;\n private comparison: ComparisonConfig;\n private ci: CiConfig;\n private browser: Browser | null = null;\n private startTime: number = 0;\n\n constructor(options: ExtendedVrtOptions = {}) {\n this.options = {\n snapshotDir: options.snapshotDir ?? \".vize/snapshots\",\n threshold: options.threshold ?? 0.1,\n viewports: options.viewports ?? [\n { width: 1280, height: 720, name: \"desktop\" },\n { width: 375, height: 667, name: \"mobile\" },\n ],\n };\n this.capture = {\n fullPage: options.capture?.fullPage ?? false,\n waitForNetwork: options.capture?.waitForNetwork ?? true,\n settleTime: options.capture?.settleTime ?? 100,\n waitSelector: options.capture?.waitSelector ?? \".musea-variant\",\n hideElements: options.capture?.hideElements ?? [],\n maskElements: options.capture?.maskElements ?? [],\n };\n this.comparison = options.comparison ?? {};\n this.ci = options.ci ?? {};\n }\n\n /**\n * Initialize Playwright browser.\n */\n async init(): Promise<void> {\n const { chromium } = await import(\"playwright\");\n this.browser = await chromium.launch({ headless: true });\n this.startTime = Date.now();\n }\n\n /**\n * Close browser and cleanup.\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close();\n this.browser = null;\n }\n }\n\n /**\n * Run VRT tests for all Art files.\n */\n async runAllTests(artFiles: ArtFileInfo[], baseUrl: string): Promise<VrtResult[]> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n\n const results: VrtResult[] = [];\n const retries = this.ci.retries ?? 0;\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) {\n continue;\n }\n\n // Determine viewports: use per-variant viewport if defined, else global viewports\n const viewports = variant.args?.viewport\n ? [variant.args.viewport as ViewportConfig]\n : this.options.viewports;\n\n for (const viewport of viewports) {\n let result: VrtResult | null = null;\n let attempts = 0;\n\n while (attempts <= retries) {\n result = await this.captureAndCompare(art, variant.name, viewport, baseUrl);\n if (result.passed || result.isNew || !result.error) {\n break;\n }\n attempts++;\n if (attempts <= retries) {\n console.log(\n `[vrt] Retry ${attempts}/${retries}: ${path.basename(art.path)}/${variant.name}`,\n );\n }\n }\n\n if (result) {\n results.push(result);\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Capture screenshot and compare with baseline.\n */\n async captureAndCompare(\n art: ArtFileInfo,\n variantName: string,\n viewport: ViewportConfig,\n baseUrl: string,\n ): Promise<VrtResult> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n\n const snapshotDir = this.options.snapshotDir;\n const artBaseName = path.basename(art.path, \".art.vue\");\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n const snapshotName = `${artBaseName}--${variantName}--${viewportName}.png`;\n const snapshotPath = path.join(snapshotDir, snapshotName);\n const currentPath = path.join(snapshotDir, \"current\", snapshotName);\n const diffPath = path.join(snapshotDir, \"diff\", snapshotName);\n\n // Ensure directories exist\n await fs.promises.mkdir(path.dirname(snapshotPath), { recursive: true });\n await fs.promises.mkdir(path.join(snapshotDir, \"current\"), { recursive: true });\n await fs.promises.mkdir(path.join(snapshotDir, \"diff\"), { recursive: true });\n\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n context = await this.browser.newContext({\n viewport: {\n width: viewport.width,\n height: viewport.height,\n },\n deviceScaleFactor: viewport.deviceScaleFactor ?? 1,\n });\n page = await context.newPage();\n\n // Navigate to variant preview URL\n const variantUrl = this.buildVariantUrl(baseUrl, art.path, variantName);\n const waitUntil = this.capture.waitForNetwork ? \"networkidle\" as const : \"load\" as const;\n await page.goto(variantUrl, { waitUntil });\n\n // Wait for content to render\n await page.waitForSelector(this.capture.waitSelector, { timeout: 10000 });\n\n // Additional wait for animations to settle\n await page.waitForTimeout(this.capture.settleTime);\n\n // Hide elements before capture\n if (this.capture.hideElements.length > 0) {\n for (const selector of this.capture.hideElements) {\n await page.evaluate(\n (sel) => {\n document.querySelectorAll(sel).forEach((el) => {\n (el as HTMLElement).style.visibility = \"hidden\";\n });\n },\n selector,\n );\n }\n }\n\n // Mask elements before capture (replace with colored box)\n if (this.capture.maskElements.length > 0) {\n for (const selector of this.capture.maskElements) {\n await page.evaluate(\n (sel) => {\n document.querySelectorAll(sel).forEach((el) => {\n const htmlEl = el as HTMLElement;\n htmlEl.style.background = \"#ff00ff\";\n htmlEl.style.color = \"transparent\";\n htmlEl.innerHTML = \"\";\n });\n },\n selector,\n );\n }\n }\n\n // Take screenshot\n await page.screenshot({\n path: currentPath,\n fullPage: this.capture.fullPage,\n });\n\n // Check if baseline exists\n const hasBaseline = await fileExists(snapshotPath);\n\n if (!hasBaseline) {\n // First run - save as baseline\n await fs.promises.copyFile(currentPath, snapshotPath);\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed: true,\n snapshotPath,\n currentPath,\n isNew: true,\n };\n }\n\n // Compare images using pixel comparison\n const comparison = await this.compareImages(snapshotPath, currentPath, diffPath);\n\n const passed = comparison.diffPercentage <= this.options.threshold;\n\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed,\n snapshotPath,\n currentPath,\n diffPath: passed ? undefined : diffPath,\n diffPercentage: comparison.diffPercentage,\n diffPixels: comparison.diffPixels,\n totalPixels: comparison.totalPixels,\n };\n } catch (error) {\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed: false,\n snapshotPath,\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n if (page) await page.close();\n if (context) await context.close();\n }\n }\n\n /**\n * Get the Playwright Page for external use (e.g., a11y auditing).\n */\n async createPage(viewport: ViewportConfig): Promise<{ page: Page; context: BrowserContext }> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n const context = await this.browser.newContext({\n viewport: { width: viewport.width, height: viewport.height },\n deviceScaleFactor: viewport.deviceScaleFactor ?? 1,\n });\n const page = await context.newPage();\n return { page, context };\n }\n\n /**\n * Update baseline snapshots with current screenshots.\n */\n async updateBaselines(results: VrtResult[]): Promise<number> {\n let updated = 0;\n const snapshotDir = this.options.snapshotDir;\n const currentDir = path.join(snapshotDir, \"current\");\n\n for (const result of results) {\n const currentPath = path.join(currentDir, path.basename(result.snapshotPath));\n\n if (await fileExists(currentPath)) {\n await fs.promises.copyFile(currentPath, result.snapshotPath);\n updated++;\n console.log(`[vrt] Updated: ${path.basename(result.snapshotPath)}`);\n }\n }\n\n return updated;\n }\n\n /**\n * Approve specific failed results (update their baselines).\n */\n async approveResults(results: VrtResult[], pattern?: string): Promise<number> {\n const toApprove = pattern\n ? results.filter((r) => {\n const name = `${path.basename(r.artPath, \".art.vue\")}/${r.variantName}`;\n return name.includes(pattern) || matchGlob(name, pattern);\n })\n : results.filter((r) => !r.passed && !r.error);\n\n return this.updateBaselines(toApprove);\n }\n\n /**\n * Clean orphaned snapshots (no corresponding art/variant).\n */\n async cleanOrphans(artFiles: ArtFileInfo[]): Promise<number> {\n const snapshotDir = this.options.snapshotDir;\n let cleaned = 0;\n\n try {\n const files = await fs.promises.readdir(snapshotDir);\n const validNames = new Set<string>();\n\n for (const art of artFiles) {\n const artBaseName = path.basename(art.path, \".art.vue\");\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n for (const viewport of this.options.viewports) {\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n validNames.add(`${artBaseName}--${variant.name}--${viewportName}.png`);\n }\n }\n }\n\n for (const file of files) {\n if (file.endsWith(\".png\") && !validNames.has(file)) {\n await fs.promises.unlink(path.join(snapshotDir, file));\n cleaned++;\n console.log(`[vrt] Cleaned: ${file}`);\n }\n }\n } catch {\n // Directory may not exist yet\n }\n\n return cleaned;\n }\n\n /**\n * Get VRT summary statistics.\n */\n getSummary(results: VrtResult[]): VrtSummary {\n return {\n total: results.length,\n passed: results.filter((r) => r.passed && !r.isNew).length,\n failed: results.filter((r) => !r.passed && !r.error).length,\n new: results.filter((r) => r.isNew).length,\n skipped: results.filter((r) => r.error).length,\n duration: Date.now() - this.startTime,\n };\n }\n\n /**\n * Build URL for variant preview.\n */\n private buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n }\n\n /**\n * Compare two PNG images and generate a diff image.\n * Returns pixel difference statistics.\n */\n private async compareImages(\n baselinePath: string,\n currentPath: string,\n diffPath: string,\n ): Promise<{ diffPixels: number; totalPixels: number; diffPercentage: number }> {\n const baseline = await readPng(baselinePath);\n const current = await readPng(currentPath);\n\n // Handle size mismatch\n if (baseline.width !== current.width || baseline.height !== current.height) {\n // Create a diff showing the size mismatch\n const width = Math.max(baseline.width, current.width);\n const height = Math.max(baseline.height, current.height);\n const diff = new PNG({ width, height });\n\n // Fill with red to indicate size mismatch\n for (let i = 0; i < diff.data.length; i += 4) {\n diff.data[i] = 255; // R\n diff.data[i + 1] = 0; // G\n diff.data[i + 2] = 0; // B\n diff.data[i + 3] = 255; // A\n }\n\n await writePng(diff, diffPath);\n\n return {\n diffPixels: width * height,\n totalPixels: width * height,\n diffPercentage: 100,\n };\n }\n\n const width = baseline.width;\n const height = baseline.height;\n const totalPixels = width * height;\n const diff = new PNG({ width, height });\n\n const useAntiAliasing = this.comparison.antiAliasing ?? true;\n const useAlpha = this.comparison.alpha ?? true;\n const diffColor = this.comparison.diffColor ?? { r: 255, g: 0, b: 0 };\n\n // Pixel comparison\n let diffPixels = 0;\n const threshold = 0.1; // Color difference threshold\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = (y * width + x) * 4;\n\n const r1 = baseline.data[idx];\n const g1 = baseline.data[idx + 1];\n const b1 = baseline.data[idx + 2];\n const a1 = useAlpha ? baseline.data[idx + 3] : 255;\n\n const r2 = current.data[idx];\n const g2 = current.data[idx + 1];\n const b2 = current.data[idx + 2];\n const a2 = useAlpha ? current.data[idx + 3] : 255;\n\n // Calculate color difference using YIQ color space\n const delta = colorDelta(r1, g1, b1, a1, r2, g2, b2, a2);\n\n if (delta > threshold * 255 * 255) {\n // Anti-aliasing detection: check if pixel is likely AA\n if (useAntiAliasing && isAntiAliased(baseline, current, x, y, width, height)) {\n // Mark as AA (yellow)\n diff.data[idx] = 255;\n diff.data[idx + 1] = 200;\n diff.data[idx + 2] = 0;\n diff.data[idx + 3] = 128;\n } else {\n // Mark as different\n diffPixels++;\n diff.data[idx] = diffColor.r;\n diff.data[idx + 1] = diffColor.g;\n diff.data[idx + 2] = diffColor.b;\n diff.data[idx + 3] = 255;\n }\n } else {\n // Grayscale for matching pixels\n const gray = Math.round((r2 + g2 + b2) / 3);\n diff.data[idx] = gray;\n diff.data[idx + 1] = gray;\n diff.data[idx + 2] = gray;\n diff.data[idx + 3] = 128; // Semi-transparent\n }\n }\n }\n\n // Only write diff if there are differences\n if (diffPixels > 0) {\n await writePng(diff, diffPath);\n }\n\n const diffPercentage = (diffPixels / totalPixels) * 100;\n\n return {\n diffPixels,\n totalPixels,\n diffPercentage,\n };\n }\n}\n\n/**\n * Generate VRT report in HTML format.\n * Supports side-by-side, overlay, and slider comparison modes.\n */\nexport function generateVrtReport(results: VrtResult[], summary: VrtSummary): string {\n const formatDuration = (ms: number): string => {\n if (ms < 1000) return `${ms}ms`;\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n if (minutes === 0) return `${seconds}s`;\n return `${minutes}m ${seconds % 60}s`;\n };\n\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>VRT Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-accent-hover: #c45a32;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n --musea-success: #4ade80;\n --musea-error: #f87171;\n --musea-info: #60a5fa;\n --musea-warning: #fbbf24;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n .header-left { display: flex; align-items: center; gap: 1rem; }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-title { color: var(--musea-text-muted); font-size: 0.875rem; }\n .header-meta { display: flex; align-items: center; gap: 1.5rem; font-size: 0.8125rem; color: var(--musea-text-muted); }\n .header-meta span { display: flex; align-items: center; gap: 0.375rem; }\n\n .main { max-width: 1400px; margin: 0 auto; padding: 2rem; }\n\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1.25rem; position: relative; overflow: hidden; }\n .stat::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }\n .stat.passed::before { background: var(--musea-success); }\n .stat.failed::before { background: var(--musea-error); }\n .stat.new::before { background: var(--musea-info); }\n .stat.skipped::before { background: var(--musea-warning); }\n .stat-value { font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 0.25rem; }\n .stat.passed .stat-value { color: var(--musea-success); }\n .stat.failed .stat-value { color: var(--musea-error); }\n .stat.new .stat-value { color: var(--musea-info); }\n .stat.skipped .stat-value { color: var(--musea-warning); }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; }\n\n .filters { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; }\n .filter-btn { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); color: var(--musea-text); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.8125rem; font-weight: 500; transition: all 0.15s ease; }\n .filter-btn:hover { background: var(--musea-bg-tertiary); border-color: var(--musea-text-muted); }\n .filter-btn.active { background: var(--musea-accent); border-color: var(--musea-accent); color: #fff; }\n .filter-btn .count { opacity: 0.7; margin-left: 0.25rem; }\n\n /* Comparison mode toggle */\n .compare-modes { display: flex; gap: 0.25rem; margin-bottom: 1.5rem; background: var(--musea-bg-secondary); border-radius: 6px; padding: 0.25rem; width: fit-content; }\n .compare-mode-btn { background: none; border: none; color: var(--musea-text-muted); padding: 0.375rem 0.75rem; border-radius: 4px; cursor: pointer; font-size: 0.75rem; font-weight: 500; transition: all 0.15s ease; }\n .compare-mode-btn.active { background: var(--musea-bg-tertiary); color: var(--musea-text); }\n\n .results { display: flex; flex-direction: column; gap: 0.75rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; transition: border-color 0.15s ease; }\n .result:hover { border-color: var(--musea-text-muted); }\n .result-header { padding: 1rem 1.25rem; display: flex; justify-content: space-between; align-items: center; cursor: pointer; border-left: 3px solid transparent; background: var(--musea-bg-tertiary); }\n .result.passed .result-header { border-left-color: var(--musea-success); }\n .result.failed .result-header { border-left-color: var(--musea-error); }\n .result.new .result-header { border-left-color: var(--musea-info); }\n .result.error .result-header { border-left-color: var(--musea-warning); }\n\n .result-info { display: flex; align-items: center; gap: 1rem; }\n .result-name { font-weight: 600; font-size: 0.9375rem; }\n .result-meta { color: var(--musea-text-muted); font-size: 0.8125rem; padding: 0.125rem 0.5rem; background: var(--musea-bg-secondary); border-radius: 4px; }\n .result-badge { padding: 0.25rem 0.625rem; border-radius: 4px; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }\n .result.passed .result-badge { background: rgba(74, 222, 128, 0.15); color: var(--musea-success); }\n .result.failed .result-badge { background: rgba(248, 113, 113, 0.15); color: var(--musea-error); }\n .result.new .result-badge { background: rgba(96, 165, 250, 0.15); color: var(--musea-info); }\n .result.error .result-badge { background: rgba(251, 191, 36, 0.15); color: var(--musea-warning); }\n\n .result-body { border-top: 1px solid var(--musea-border); }\n .result-details { padding: 0.875rem 1.25rem; font-size: 0.8125rem; color: var(--musea-text-muted); font-family: 'SF Mono', 'Fira Code', monospace; background: var(--musea-bg-primary); }\n .result-details.error { color: var(--musea-error); }\n\n .result-images { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; padding: 1.25rem; background: var(--musea-bg-primary); }\n .result-images.overlay { grid-template-columns: 1fr; }\n .image-container { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 6px; overflow: hidden; }\n .image-label { padding: 0.625rem 0.875rem; font-size: 0.6875rem; font-weight: 600; color: var(--musea-text-muted); text-transform: uppercase; letter-spacing: 0.08em; background: var(--musea-bg-tertiary); border-bottom: 1px solid var(--musea-border); }\n .image-wrapper { padding: 0.5rem; background: repeating-conic-gradient(var(--musea-bg-tertiary) 0% 25%, var(--musea-bg-secondary) 0% 50%) 50% / 16px 16px; }\n .image-container img { width: 100%; height: auto; display: block; border-radius: 2px; }\n\n /* Slider comparison */\n .slider-compare { position: relative; overflow: hidden; }\n .slider-compare img { display: block; width: 100%; }\n .slider-overlay { position: absolute; top: 0; left: 0; bottom: 0; overflow: hidden; border-right: 2px solid var(--musea-accent); }\n .slider-overlay img { display: block; min-width: 100%; height: 100%; object-fit: cover; }\n\n .empty-state { text-align: center; padding: 4rem 2rem; color: var(--musea-text-muted); }\n .all-passed { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 1.5rem; text-align: center; margin-bottom: 1.5rem; }\n .all-passed-icon { font-size: 2.5rem; margin-bottom: 0.5rem; }\n .all-passed-text { color: var(--musea-success); font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div class=\"header-left\">\n <div class=\"logo\">Musea</div>\n <span class=\"header-title\">Visual Regression Report</span>\n </div>\n <div class=\"header-meta\">\n <span>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/>\n </svg>\n ${formatDuration(summary.duration)}\n </span>\n <span>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/>\n </svg>\n ${timestamp}\n </span>\n </div>\n </header>\n\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat passed\"><div class=\"stat-value\">${summary.passed}</div><div class=\"stat-label\">Passed</div></div>\n <div class=\"stat failed\"><div class=\"stat-value\">${summary.failed}</div><div class=\"stat-label\">Failed</div></div>\n <div class=\"stat new\"><div class=\"stat-value\">${summary.new}</div><div class=\"stat-label\">New</div></div>\n <div class=\"stat skipped\"><div class=\"stat-value\">${summary.skipped}</div><div class=\"stat-label\">Skipped</div></div>\n </div>\n\n ${\n summary.failed === 0 && summary.skipped === 0 && summary.total > 0\n ? `<div class=\"all-passed\">\n <div class=\"all-passed-icon\">✓</div>\n <div class=\"all-passed-text\">All ${summary.total} visual tests passed</div>\n </div>`\n : \"\"\n }\n\n <div class=\"filters\">\n <button class=\"filter-btn active\" data-filter=\"all\">All<span class=\"count\">(${summary.total})</span></button>\n <button class=\"filter-btn\" data-filter=\"failed\">Failed<span class=\"count\">(${summary.failed})</span></button>\n <button class=\"filter-btn\" data-filter=\"passed\">Passed<span class=\"count\">(${summary.passed})</span></button>\n <button class=\"filter-btn\" data-filter=\"new\">New<span class=\"count\">(${summary.new})</span></button>\n </div>\n\n <div class=\"compare-modes\">\n <button class=\"compare-mode-btn active\" data-mode=\"side-by-side\">Side by Side</button>\n <button class=\"compare-mode-btn\" data-mode=\"overlay\">Overlay</button>\n <button class=\"compare-mode-btn\" data-mode=\"slider\">Slider</button>\n </div>\n\n <div class=\"results\">\n ${\n results.length === 0\n ? `<div class=\"empty-state\"><p>No visual tests found</p></div>`\n : results\n .map((r) => {\n const status = r.error ? \"error\" : r.isNew ? \"new\" : r.passed ? \"passed\" : \"failed\";\n const badge = r.error ? \"Error\" : r.isNew ? \"New\" : r.passed ? \"Passed\" : \"Failed\";\n const artName = path.basename(r.artPath, \".art.vue\");\n const viewportName = r.viewport.name || `${r.viewport.width}×${r.viewport.height}`;\n\n let details = \"\";\n if (r.error) {\n details = `<div class=\"result-details error\">${escapeHtml(r.error)}</div>`;\n } else if (r.diffPercentage !== undefined) {\n const diffFormatted = r.diffPercentage.toFixed(3);\n const pixelsFormatted = r.diffPixels?.toLocaleString() ?? \"0\";\n const totalFormatted = r.totalPixels?.toLocaleString() ?? \"0\";\n details = `<div class=\"result-details\">diff: ${diffFormatted}% (${pixelsFormatted} / ${totalFormatted} pixels)</div>`;\n }\n\n let images = \"\";\n if (!r.error && !r.passed && r.diffPath) {\n images = `<div class=\"result-images\" data-baseline=\"file://${r.snapshotPath}\" data-current=\"file://${r.currentPath}\" data-diff=\"file://${r.diffPath}\">\n ${r.snapshotPath ? `<div class=\"image-container\"><div class=\"image-label\">Baseline</div><div class=\"image-wrapper\"><img src=\"file://${r.snapshotPath}\" alt=\"Baseline\" loading=\"lazy\" /></div></div>` : \"\"}\n ${r.currentPath ? `<div class=\"image-container\"><div class=\"image-label\">Current</div><div class=\"image-wrapper\"><img src=\"file://${r.currentPath}\" alt=\"Current\" loading=\"lazy\" /></div></div>` : \"\"}\n ${r.diffPath ? `<div class=\"image-container\"><div class=\"image-label\">Diff</div><div class=\"image-wrapper\"><img src=\"file://${r.diffPath}\" alt=\"Diff\" loading=\"lazy\" /></div></div>` : \"\"}\n </div>`;\n }\n\n const hasBody = details || images;\n\n return `<div class=\"result ${status}\" data-status=\"${status}\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <div class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</div>\n <div class=\"result-meta\">${escapeHtml(viewportName)}</div>\n </div>\n <span class=\"result-badge\">${badge}</span>\n </div>\n ${hasBody ? `<div class=\"result-body\">${details}${images}</div>` : \"\"}\n </div>`;\n })\n .join(\"\")\n }\n </div>\n </main>\n\n <script>\n // Filter buttons\n document.querySelectorAll('.filter-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n const filter = btn.dataset.filter;\n document.querySelectorAll('.result').forEach(result => {\n result.style.display = (filter === 'all' || result.dataset.status === filter) ? 'block' : 'none';\n });\n });\n });\n\n // Compare mode buttons\n document.querySelectorAll('.compare-mode-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.compare-mode-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n // Mode switching would update result-images display; this is a static report for now\n });\n });\n </script>\n</body>\n</html>`;\n\n return html;\n}\n\n/**\n * Generate VRT JSON report for CI integration.\n */\nexport function generateVrtJsonReport(results: VrtResult[], summary: VrtSummary): string {\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n viewport: r.viewport.name || `${r.viewport.width}x${r.viewport.height}`,\n status: r.error ? \"error\" : r.isNew ? \"new\" : r.passed ? \"passed\" : \"failed\",\n diffPercentage: r.diffPercentage,\n error: r.error,\n })),\n },\n null,\n 2,\n );\n}\n\n// Utility functions\n\n/**\n * Read PNG file and return PNG object.\n */\nasync function readPng(filepath: string): Promise<PNG> {\n return new Promise((resolve, reject) => {\n fs.createReadStream(filepath)\n .pipe(new PNG())\n .on(\"parsed\", function (this: PNG) {\n resolve(this);\n })\n .on(\"error\", reject);\n });\n}\n\n/**\n * Write PNG object to file.\n */\nasync function writePng(png: PNG, filepath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n png.pack().pipe(fs.createWriteStream(filepath)).on(\"finish\", resolve).on(\"error\", reject);\n });\n}\n\n/**\n * Calculate color delta using YIQ color space.\n */\nfunction colorDelta(\n r1: number,\n g1: number,\n b1: number,\n a1: number,\n r2: number,\n g2: number,\n b2: number,\n a2: number,\n): number {\n if (a1 !== 255) {\n r1 = blend(r1, 255, a1 / 255);\n g1 = blend(g1, 255, a1 / 255);\n b1 = blend(b1, 255, a1 / 255);\n }\n if (a2 !== 255) {\n r2 = blend(r2, 255, a2 / 255);\n g2 = blend(g2, 255, a2 / 255);\n b2 = blend(b2, 255, a2 / 255);\n }\n\n const y1 = r1 * 0.29889531 + g1 * 0.58662247 + b1 * 0.11448223;\n const i1 = r1 * 0.59597799 - g1 * 0.2741761 - b1 * 0.32180189;\n const q1 = r1 * 0.21147017 - g1 * 0.52261711 + b1 * 0.31114694;\n\n const y2 = r2 * 0.29889531 + g2 * 0.58662247 + b2 * 0.11448223;\n const i2 = r2 * 0.59597799 - g2 * 0.2741761 - b2 * 0.32180189;\n const q2 = r2 * 0.21147017 - g2 * 0.52261711 + b2 * 0.31114694;\n\n const dy = y1 - y2;\n const di = i1 - i2;\n const dq = q1 - q2;\n\n return dy * dy * 0.5053 + di * di * 0.299 + dq * dq * 0.1957;\n}\n\n/**\n * Blend foreground with background using alpha.\n */\nfunction blend(fg: number, bg: number, alpha: number): number {\n return bg + (fg - bg) * alpha;\n}\n\n/**\n * Check if file exists.\n */\nasync function fileExists(filepath: string): Promise<boolean> {\n try {\n await fs.promises.access(filepath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Simple anti-aliasing detection.\n * A pixel is likely anti-aliased if its neighbors have high contrast in opposite directions.\n */\nfunction isAntiAliased(\n img1: PNG,\n img2: PNG,\n x: number,\n y: number,\n width: number,\n height: number,\n): boolean {\n const minX = Math.max(0, x - 1);\n const maxX = Math.min(width - 1, x + 1);\n const minY = Math.max(0, y - 1);\n const maxY = Math.min(height - 1, y + 1);\n\n let zeroes = 0;\n let positives = 0;\n let negatives = 0;\n\n for (let ny = minY; ny <= maxY; ny++) {\n for (let nx = minX; nx <= maxX; nx++) {\n if (nx === x && ny === y) continue;\n const idx = (ny * width + nx) * 4;\n\n const delta = colorDelta(\n img1.data[idx],\n img1.data[idx + 1],\n img1.data[idx + 2],\n img1.data[idx + 3],\n img2.data[idx],\n img2.data[idx + 1],\n img2.data[idx + 2],\n img2.data[idx + 3],\n );\n\n if (delta === 0) {\n zeroes++;\n } else if (delta > 0) {\n positives++;\n } else {\n negatives++;\n }\n }\n }\n\n // If neighbors are mixed (some match, some differ), it's likely AA\n return zeroes > 0 && (positives > 0 || negatives > 0) && positives + negatives < 4;\n}\n\n/**\n * Simple glob matching for pattern-based filtering.\n */\nfunction matchGlob(filepath: string, pattern: string): boolean {\n const regex = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \".*\")\n .replace(/\\*(?!\\*)/g, \"[^/]*\");\n return new RegExp(`^${regex}$`).test(filepath);\n}\n\n/**\n * Escape HTML special characters.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nexport default MuseaVrtRunner;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vrt-DP87vGIA.js","names":["options: ExtendedVrtOptions","artFiles: ArtFileInfo[]","baseUrl: string","results: VrtResult[]","result: VrtResult | null","art: ArtFileInfo","variantName: string","viewport: ViewportConfig","context: BrowserContext | null","page: Page | null","pattern?: string","artPath: string","baselinePath: string","currentPath: string","diffPath: string","width","height","diff","summary: VrtSummary","ms: number","filepath: string","png: PNG","r1: number","g1: number","b1: number","a1: number","r2: number","g2: number","b2: number","a2: number","fg: number","bg: number","alpha: number","img1: PNG","img2: PNG","x: number","y: number","width: number","height: number","pattern: string","str: string"],"sources":["../src/vrt.ts"],"sourcesContent":["/**\n * Visual Regression Testing (VRT) module for Musea.\n * Uses Playwright for browser automation and pixel comparison.\n */\n\nimport type { Browser, BrowserContext, Page } from \"playwright\";\nimport type {\n ArtFileInfo,\n VrtOptions,\n ViewportConfig,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"./types.js\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { PNG } from \"pngjs\";\n\n/**\n * VRT test result for a single variant.\n */\nexport interface VrtResult {\n artPath: string;\n variantName: string;\n viewport: ViewportConfig;\n passed: boolean;\n snapshotPath: string;\n currentPath?: string;\n diffPath?: string;\n diffPercentage?: number;\n diffPixels?: number;\n totalPixels?: number;\n error?: string;\n isNew?: boolean;\n}\n\n/**\n * VRT summary for reporting.\n */\nexport interface VrtSummary {\n total: number;\n passed: number;\n failed: number;\n new: number;\n skipped: number;\n duration: number;\n}\n\n/**\n * Extended VRT options aligned with Rust VrtConfig.\n */\nexport interface ExtendedVrtOptions extends VrtOptions {\n capture?: CaptureConfig;\n comparison?: ComparisonConfig;\n ci?: CiConfig;\n /** Enable a11y auditing during VRT */\n a11y?: boolean;\n}\n\n/**\n * Pixel comparison options.\n */\nexport interface PixelCompareOptions {\n /** Threshold for color difference (0-1). Default: 0.1 */\n threshold?: number;\n /** Include anti-aliasing in diff. Default: false */\n includeAA?: boolean;\n /** Alpha channel comparison. Default: true */\n alpha?: boolean;\n /** Diff highlight color */\n diffColor?: { r: number; g: number; b: number };\n}\n\n/**\n * VRT runner using Playwright.\n */\nexport class MuseaVrtRunner {\n private options: Required<VrtOptions>;\n private capture: Required<CaptureConfig>;\n private comparison: ComparisonConfig;\n private ci: CiConfig;\n private browser: Browser | null = null;\n private startTime: number = 0;\n\n constructor(options: ExtendedVrtOptions = {}) {\n this.options = {\n snapshotDir: options.snapshotDir ?? \".vize/snapshots\",\n threshold: options.threshold ?? 0.1,\n viewports: options.viewports ?? [\n { width: 1280, height: 720, name: \"desktop\" },\n { width: 375, height: 667, name: \"mobile\" },\n ],\n };\n this.capture = {\n fullPage: options.capture?.fullPage ?? false,\n waitForNetwork: options.capture?.waitForNetwork ?? true,\n settleTime: options.capture?.settleTime ?? 100,\n waitSelector: options.capture?.waitSelector ?? \".musea-variant\",\n hideElements: options.capture?.hideElements ?? [],\n maskElements: options.capture?.maskElements ?? [],\n };\n this.comparison = options.comparison ?? {};\n this.ci = options.ci ?? {};\n }\n\n /**\n * Initialize Playwright browser.\n */\n async init(): Promise<void> {\n const { chromium } = await import(\"playwright\");\n this.browser = await chromium.launch({ headless: true });\n this.startTime = Date.now();\n }\n\n /**\n * Close browser and cleanup.\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close();\n this.browser = null;\n }\n }\n\n /**\n * Run VRT tests for all Art files.\n */\n async runAllTests(artFiles: ArtFileInfo[], baseUrl: string): Promise<VrtResult[]> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n\n const results: VrtResult[] = [];\n const retries = this.ci.retries ?? 0;\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) {\n continue;\n }\n\n // Determine viewports: use per-variant viewport if defined, else global viewports\n const viewports = variant.args?.viewport\n ? [variant.args.viewport as ViewportConfig]\n : this.options.viewports;\n\n for (const viewport of viewports) {\n let result: VrtResult | null = null;\n let attempts = 0;\n\n while (attempts <= retries) {\n result = await this.captureAndCompare(art, variant.name, viewport, baseUrl);\n if (result.passed || result.isNew || !result.error) {\n break;\n }\n attempts++;\n if (attempts <= retries) {\n console.log(\n `[vrt] Retry ${attempts}/${retries}: ${path.basename(art.path)}/${variant.name}`,\n );\n }\n }\n\n if (result) {\n results.push(result);\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Capture screenshot and compare with baseline.\n */\n async captureAndCompare(\n art: ArtFileInfo,\n variantName: string,\n viewport: ViewportConfig,\n baseUrl: string,\n ): Promise<VrtResult> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n\n const snapshotDir = this.options.snapshotDir;\n const artBaseName = path.basename(art.path, \".art.vue\");\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n const snapshotName = `${artBaseName}--${variantName}--${viewportName}.png`;\n const snapshotPath = path.join(snapshotDir, snapshotName);\n const currentPath = path.join(snapshotDir, \"current\", snapshotName);\n const diffPath = path.join(snapshotDir, \"diff\", snapshotName);\n\n // Ensure directories exist\n await fs.promises.mkdir(path.dirname(snapshotPath), { recursive: true });\n await fs.promises.mkdir(path.join(snapshotDir, \"current\"), { recursive: true });\n await fs.promises.mkdir(path.join(snapshotDir, \"diff\"), { recursive: true });\n\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n context = await this.browser.newContext({\n viewport: {\n width: viewport.width,\n height: viewport.height,\n },\n deviceScaleFactor: viewport.deviceScaleFactor ?? 1,\n });\n page = await context.newPage();\n\n // Navigate to variant preview URL\n const variantUrl = this.buildVariantUrl(baseUrl, art.path, variantName);\n const waitUntil = this.capture.waitForNetwork ? (\"networkidle\" as const) : (\"load\" as const);\n await page.goto(variantUrl, { waitUntil });\n\n // Wait for content to render\n await page.waitForSelector(this.capture.waitSelector, { timeout: 10000 });\n\n // Additional wait for animations to settle\n await page.waitForTimeout(this.capture.settleTime);\n\n // Hide elements before capture\n if (this.capture.hideElements.length > 0) {\n for (const selector of this.capture.hideElements) {\n await page.evaluate((sel) => {\n document.querySelectorAll(sel).forEach((el) => {\n (el as HTMLElement).style.visibility = \"hidden\";\n });\n }, selector);\n }\n }\n\n // Mask elements before capture (replace with colored box)\n if (this.capture.maskElements.length > 0) {\n for (const selector of this.capture.maskElements) {\n await page.evaluate((sel) => {\n document.querySelectorAll(sel).forEach((el) => {\n const htmlEl = el as HTMLElement;\n htmlEl.style.background = \"#ff00ff\";\n htmlEl.style.color = \"transparent\";\n htmlEl.innerHTML = \"\";\n });\n }, selector);\n }\n }\n\n // Take screenshot\n await page.screenshot({\n path: currentPath,\n fullPage: this.capture.fullPage,\n });\n\n // Check if baseline exists\n const hasBaseline = await fileExists(snapshotPath);\n\n if (!hasBaseline) {\n // First run - save as baseline\n await fs.promises.copyFile(currentPath, snapshotPath);\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed: true,\n snapshotPath,\n currentPath,\n isNew: true,\n };\n }\n\n // Compare images using pixel comparison\n const comparison = await this.compareImages(snapshotPath, currentPath, diffPath);\n\n const passed = comparison.diffPercentage <= this.options.threshold;\n\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed,\n snapshotPath,\n currentPath,\n diffPath: passed ? undefined : diffPath,\n diffPercentage: comparison.diffPercentage,\n diffPixels: comparison.diffPixels,\n totalPixels: comparison.totalPixels,\n };\n } catch (error) {\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed: false,\n snapshotPath,\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n if (page) await page.close();\n if (context) await context.close();\n }\n }\n\n /**\n * Get the Playwright Page for external use (e.g., a11y auditing).\n */\n async createPage(viewport: ViewportConfig): Promise<{ page: Page; context: BrowserContext }> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n const context = await this.browser.newContext({\n viewport: { width: viewport.width, height: viewport.height },\n deviceScaleFactor: viewport.deviceScaleFactor ?? 1,\n });\n const page = await context.newPage();\n return { page, context };\n }\n\n /**\n * Update baseline snapshots with current screenshots.\n */\n async updateBaselines(results: VrtResult[]): Promise<number> {\n let updated = 0;\n const snapshotDir = this.options.snapshotDir;\n const currentDir = path.join(snapshotDir, \"current\");\n\n for (const result of results) {\n const currentPath = path.join(currentDir, path.basename(result.snapshotPath));\n\n if (await fileExists(currentPath)) {\n await fs.promises.copyFile(currentPath, result.snapshotPath);\n updated++;\n console.log(`[vrt] Updated: ${path.basename(result.snapshotPath)}`);\n }\n }\n\n return updated;\n }\n\n /**\n * Approve specific failed results (update their baselines).\n */\n async approveResults(results: VrtResult[], pattern?: string): Promise<number> {\n const toApprove = pattern\n ? results.filter((r) => {\n const name = `${path.basename(r.artPath, \".art.vue\")}/${r.variantName}`;\n return name.includes(pattern) || matchGlob(name, pattern);\n })\n : results.filter((r) => !r.passed && !r.error);\n\n return this.updateBaselines(toApprove);\n }\n\n /**\n * Clean orphaned snapshots (no corresponding art/variant).\n */\n async cleanOrphans(artFiles: ArtFileInfo[]): Promise<number> {\n const snapshotDir = this.options.snapshotDir;\n let cleaned = 0;\n\n try {\n const files = await fs.promises.readdir(snapshotDir);\n const validNames = new Set<string>();\n\n for (const art of artFiles) {\n const artBaseName = path.basename(art.path, \".art.vue\");\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n for (const viewport of this.options.viewports) {\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n validNames.add(`${artBaseName}--${variant.name}--${viewportName}.png`);\n }\n }\n }\n\n for (const file of files) {\n if (file.endsWith(\".png\") && !validNames.has(file)) {\n await fs.promises.unlink(path.join(snapshotDir, file));\n cleaned++;\n console.log(`[vrt] Cleaned: ${file}`);\n }\n }\n } catch {\n // Directory may not exist yet\n }\n\n return cleaned;\n }\n\n /**\n * Get VRT summary statistics.\n */\n getSummary(results: VrtResult[]): VrtSummary {\n return {\n total: results.length,\n passed: results.filter((r) => r.passed && !r.isNew).length,\n failed: results.filter((r) => !r.passed && !r.error).length,\n new: results.filter((r) => r.isNew).length,\n skipped: results.filter((r) => r.error).length,\n duration: Date.now() - this.startTime,\n };\n }\n\n /**\n * Build URL for variant preview.\n */\n private buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n }\n\n /**\n * Compare two PNG images and generate a diff image.\n * Returns pixel difference statistics.\n */\n private async compareImages(\n baselinePath: string,\n currentPath: string,\n diffPath: string,\n ): Promise<{ diffPixels: number; totalPixels: number; diffPercentage: number }> {\n const baseline = await readPng(baselinePath);\n const current = await readPng(currentPath);\n\n // Handle size mismatch\n if (baseline.width !== current.width || baseline.height !== current.height) {\n // Create a diff showing the size mismatch\n const width = Math.max(baseline.width, current.width);\n const height = Math.max(baseline.height, current.height);\n const diff = new PNG({ width, height });\n\n // Fill with red to indicate size mismatch\n for (let i = 0; i < diff.data.length; i += 4) {\n diff.data[i] = 255; // R\n diff.data[i + 1] = 0; // G\n diff.data[i + 2] = 0; // B\n diff.data[i + 3] = 255; // A\n }\n\n await writePng(diff, diffPath);\n\n return {\n diffPixels: width * height,\n totalPixels: width * height,\n diffPercentage: 100,\n };\n }\n\n const width = baseline.width;\n const height = baseline.height;\n const totalPixels = width * height;\n const diff = new PNG({ width, height });\n\n const useAntiAliasing = this.comparison.antiAliasing ?? true;\n const useAlpha = this.comparison.alpha ?? true;\n const diffColor = this.comparison.diffColor ?? { r: 255, g: 0, b: 0 };\n\n // Pixel comparison\n let diffPixels = 0;\n const threshold = 0.1; // Color difference threshold\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = (y * width + x) * 4;\n\n const r1 = baseline.data[idx];\n const g1 = baseline.data[idx + 1];\n const b1 = baseline.data[idx + 2];\n const a1 = useAlpha ? baseline.data[idx + 3] : 255;\n\n const r2 = current.data[idx];\n const g2 = current.data[idx + 1];\n const b2 = current.data[idx + 2];\n const a2 = useAlpha ? current.data[idx + 3] : 255;\n\n // Calculate color difference using YIQ color space\n const delta = colorDelta(r1, g1, b1, a1, r2, g2, b2, a2);\n\n if (delta > threshold * 255 * 255) {\n // Anti-aliasing detection: check if pixel is likely AA\n if (useAntiAliasing && isAntiAliased(baseline, current, x, y, width, height)) {\n // Mark as AA (yellow)\n diff.data[idx] = 255;\n diff.data[idx + 1] = 200;\n diff.data[idx + 2] = 0;\n diff.data[idx + 3] = 128;\n } else {\n // Mark as different\n diffPixels++;\n diff.data[idx] = diffColor.r;\n diff.data[idx + 1] = diffColor.g;\n diff.data[idx + 2] = diffColor.b;\n diff.data[idx + 3] = 255;\n }\n } else {\n // Grayscale for matching pixels\n const gray = Math.round((r2 + g2 + b2) / 3);\n diff.data[idx] = gray;\n diff.data[idx + 1] = gray;\n diff.data[idx + 2] = gray;\n diff.data[idx + 3] = 128; // Semi-transparent\n }\n }\n }\n\n // Only write diff if there are differences\n if (diffPixels > 0) {\n await writePng(diff, diffPath);\n }\n\n const diffPercentage = (diffPixels / totalPixels) * 100;\n\n return {\n diffPixels,\n totalPixels,\n diffPercentage,\n };\n }\n}\n\n/**\n * Generate VRT report in HTML format.\n * Supports side-by-side, overlay, and slider comparison modes.\n */\nexport function generateVrtReport(results: VrtResult[], summary: VrtSummary): string {\n const formatDuration = (ms: number): string => {\n if (ms < 1000) return `${ms}ms`;\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n if (minutes === 0) return `${seconds}s`;\n return `${minutes}m ${seconds % 60}s`;\n };\n\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>VRT Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-accent-hover: #c45a32;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n --musea-success: #4ade80;\n --musea-error: #f87171;\n --musea-info: #60a5fa;\n --musea-warning: #fbbf24;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n .header-left { display: flex; align-items: center; gap: 1rem; }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-title { color: var(--musea-text-muted); font-size: 0.875rem; }\n .header-meta { display: flex; align-items: center; gap: 1.5rem; font-size: 0.8125rem; color: var(--musea-text-muted); }\n .header-meta span { display: flex; align-items: center; gap: 0.375rem; }\n\n .main { max-width: 1400px; margin: 0 auto; padding: 2rem; }\n\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1.25rem; position: relative; overflow: hidden; }\n .stat::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }\n .stat.passed::before { background: var(--musea-success); }\n .stat.failed::before { background: var(--musea-error); }\n .stat.new::before { background: var(--musea-info); }\n .stat.skipped::before { background: var(--musea-warning); }\n .stat-value { font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 0.25rem; }\n .stat.passed .stat-value { color: var(--musea-success); }\n .stat.failed .stat-value { color: var(--musea-error); }\n .stat.new .stat-value { color: var(--musea-info); }\n .stat.skipped .stat-value { color: var(--musea-warning); }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; }\n\n .filters { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; }\n .filter-btn { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); color: var(--musea-text); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.8125rem; font-weight: 500; transition: all 0.15s ease; }\n .filter-btn:hover { background: var(--musea-bg-tertiary); border-color: var(--musea-text-muted); }\n .filter-btn.active { background: var(--musea-accent); border-color: var(--musea-accent); color: #fff; }\n .filter-btn .count { opacity: 0.7; margin-left: 0.25rem; }\n\n /* Comparison mode toggle */\n .compare-modes { display: flex; gap: 0.25rem; margin-bottom: 1.5rem; background: var(--musea-bg-secondary); border-radius: 6px; padding: 0.25rem; width: fit-content; }\n .compare-mode-btn { background: none; border: none; color: var(--musea-text-muted); padding: 0.375rem 0.75rem; border-radius: 4px; cursor: pointer; font-size: 0.75rem; font-weight: 500; transition: all 0.15s ease; }\n .compare-mode-btn.active { background: var(--musea-bg-tertiary); color: var(--musea-text); }\n\n .results { display: flex; flex-direction: column; gap: 0.75rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; transition: border-color 0.15s ease; }\n .result:hover { border-color: var(--musea-text-muted); }\n .result-header { padding: 1rem 1.25rem; display: flex; justify-content: space-between; align-items: center; cursor: pointer; border-left: 3px solid transparent; background: var(--musea-bg-tertiary); }\n .result.passed .result-header { border-left-color: var(--musea-success); }\n .result.failed .result-header { border-left-color: var(--musea-error); }\n .result.new .result-header { border-left-color: var(--musea-info); }\n .result.error .result-header { border-left-color: var(--musea-warning); }\n\n .result-info { display: flex; align-items: center; gap: 1rem; }\n .result-name { font-weight: 600; font-size: 0.9375rem; }\n .result-meta { color: var(--musea-text-muted); font-size: 0.8125rem; padding: 0.125rem 0.5rem; background: var(--musea-bg-secondary); border-radius: 4px; }\n .result-badge { padding: 0.25rem 0.625rem; border-radius: 4px; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }\n .result.passed .result-badge { background: rgba(74, 222, 128, 0.15); color: var(--musea-success); }\n .result.failed .result-badge { background: rgba(248, 113, 113, 0.15); color: var(--musea-error); }\n .result.new .result-badge { background: rgba(96, 165, 250, 0.15); color: var(--musea-info); }\n .result.error .result-badge { background: rgba(251, 191, 36, 0.15); color: var(--musea-warning); }\n\n .result-body { border-top: 1px solid var(--musea-border); }\n .result-details { padding: 0.875rem 1.25rem; font-size: 0.8125rem; color: var(--musea-text-muted); font-family: 'SF Mono', 'Fira Code', monospace; background: var(--musea-bg-primary); }\n .result-details.error { color: var(--musea-error); }\n\n .result-images { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; padding: 1.25rem; background: var(--musea-bg-primary); }\n .result-images.overlay { grid-template-columns: 1fr; }\n .image-container { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 6px; overflow: hidden; }\n .image-label { padding: 0.625rem 0.875rem; font-size: 0.6875rem; font-weight: 600; color: var(--musea-text-muted); text-transform: uppercase; letter-spacing: 0.08em; background: var(--musea-bg-tertiary); border-bottom: 1px solid var(--musea-border); }\n .image-wrapper { padding: 0.5rem; background: repeating-conic-gradient(var(--musea-bg-tertiary) 0% 25%, var(--musea-bg-secondary) 0% 50%) 50% / 16px 16px; }\n .image-container img { width: 100%; height: auto; display: block; border-radius: 2px; }\n\n /* Slider comparison */\n .slider-compare { position: relative; overflow: hidden; }\n .slider-compare img { display: block; width: 100%; }\n .slider-overlay { position: absolute; top: 0; left: 0; bottom: 0; overflow: hidden; border-right: 2px solid var(--musea-accent); }\n .slider-overlay img { display: block; min-width: 100%; height: 100%; object-fit: cover; }\n\n .empty-state { text-align: center; padding: 4rem 2rem; color: var(--musea-text-muted); }\n .all-passed { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 1.5rem; text-align: center; margin-bottom: 1.5rem; }\n .all-passed-icon { font-size: 2.5rem; margin-bottom: 0.5rem; }\n .all-passed-text { color: var(--musea-success); font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div class=\"header-left\">\n <div class=\"logo\">Musea</div>\n <span class=\"header-title\">Visual Regression Report</span>\n </div>\n <div class=\"header-meta\">\n <span>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/>\n </svg>\n ${formatDuration(summary.duration)}\n </span>\n <span>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/>\n </svg>\n ${timestamp}\n </span>\n </div>\n </header>\n\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat passed\"><div class=\"stat-value\">${summary.passed}</div><div class=\"stat-label\">Passed</div></div>\n <div class=\"stat failed\"><div class=\"stat-value\">${summary.failed}</div><div class=\"stat-label\">Failed</div></div>\n <div class=\"stat new\"><div class=\"stat-value\">${summary.new}</div><div class=\"stat-label\">New</div></div>\n <div class=\"stat skipped\"><div class=\"stat-value\">${summary.skipped}</div><div class=\"stat-label\">Skipped</div></div>\n </div>\n\n ${\n summary.failed === 0 && summary.skipped === 0 && summary.total > 0\n ? `<div class=\"all-passed\">\n <div class=\"all-passed-icon\">✓</div>\n <div class=\"all-passed-text\">All ${summary.total} visual tests passed</div>\n </div>`\n : \"\"\n }\n\n <div class=\"filters\">\n <button class=\"filter-btn active\" data-filter=\"all\">All<span class=\"count\">(${summary.total})</span></button>\n <button class=\"filter-btn\" data-filter=\"failed\">Failed<span class=\"count\">(${summary.failed})</span></button>\n <button class=\"filter-btn\" data-filter=\"passed\">Passed<span class=\"count\">(${summary.passed})</span></button>\n <button class=\"filter-btn\" data-filter=\"new\">New<span class=\"count\">(${summary.new})</span></button>\n </div>\n\n <div class=\"compare-modes\">\n <button class=\"compare-mode-btn active\" data-mode=\"side-by-side\">Side by Side</button>\n <button class=\"compare-mode-btn\" data-mode=\"overlay\">Overlay</button>\n <button class=\"compare-mode-btn\" data-mode=\"slider\">Slider</button>\n </div>\n\n <div class=\"results\">\n ${\n results.length === 0\n ? `<div class=\"empty-state\"><p>No visual tests found</p></div>`\n : results\n .map((r) => {\n const status = r.error ? \"error\" : r.isNew ? \"new\" : r.passed ? \"passed\" : \"failed\";\n const badge = r.error ? \"Error\" : r.isNew ? \"New\" : r.passed ? \"Passed\" : \"Failed\";\n const artName = path.basename(r.artPath, \".art.vue\");\n const viewportName = r.viewport.name || `${r.viewport.width}×${r.viewport.height}`;\n\n let details = \"\";\n if (r.error) {\n details = `<div class=\"result-details error\">${escapeHtml(r.error)}</div>`;\n } else if (r.diffPercentage !== undefined) {\n const diffFormatted = r.diffPercentage.toFixed(3);\n const pixelsFormatted = r.diffPixels?.toLocaleString() ?? \"0\";\n const totalFormatted = r.totalPixels?.toLocaleString() ?? \"0\";\n details = `<div class=\"result-details\">diff: ${diffFormatted}% (${pixelsFormatted} / ${totalFormatted} pixels)</div>`;\n }\n\n let images = \"\";\n if (!r.error && !r.passed && r.diffPath) {\n images = `<div class=\"result-images\" data-baseline=\"file://${r.snapshotPath}\" data-current=\"file://${r.currentPath}\" data-diff=\"file://${r.diffPath}\">\n ${r.snapshotPath ? `<div class=\"image-container\"><div class=\"image-label\">Baseline</div><div class=\"image-wrapper\"><img src=\"file://${r.snapshotPath}\" alt=\"Baseline\" loading=\"lazy\" /></div></div>` : \"\"}\n ${r.currentPath ? `<div class=\"image-container\"><div class=\"image-label\">Current</div><div class=\"image-wrapper\"><img src=\"file://${r.currentPath}\" alt=\"Current\" loading=\"lazy\" /></div></div>` : \"\"}\n ${r.diffPath ? `<div class=\"image-container\"><div class=\"image-label\">Diff</div><div class=\"image-wrapper\"><img src=\"file://${r.diffPath}\" alt=\"Diff\" loading=\"lazy\" /></div></div>` : \"\"}\n </div>`;\n }\n\n const hasBody = details || images;\n\n return `<div class=\"result ${status}\" data-status=\"${status}\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <div class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</div>\n <div class=\"result-meta\">${escapeHtml(viewportName)}</div>\n </div>\n <span class=\"result-badge\">${badge}</span>\n </div>\n ${hasBody ? `<div class=\"result-body\">${details}${images}</div>` : \"\"}\n </div>`;\n })\n .join(\"\")\n }\n </div>\n </main>\n\n <script>\n // Filter buttons\n document.querySelectorAll('.filter-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n const filter = btn.dataset.filter;\n document.querySelectorAll('.result').forEach(result => {\n result.style.display = (filter === 'all' || result.dataset.status === filter) ? 'block' : 'none';\n });\n });\n });\n\n // Compare mode buttons\n document.querySelectorAll('.compare-mode-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.compare-mode-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n // Mode switching would update result-images display; this is a static report for now\n });\n });\n </script>\n</body>\n</html>`;\n\n return html;\n}\n\n/**\n * Generate VRT JSON report for CI integration.\n */\nexport function generateVrtJsonReport(results: VrtResult[], summary: VrtSummary): string {\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n viewport: r.viewport.name || `${r.viewport.width}x${r.viewport.height}`,\n status: r.error ? \"error\" : r.isNew ? \"new\" : r.passed ? \"passed\" : \"failed\",\n diffPercentage: r.diffPercentage,\n error: r.error,\n })),\n },\n null,\n 2,\n );\n}\n\n// Utility functions\n\n/**\n * Read PNG file and return PNG object.\n */\nasync function readPng(filepath: string): Promise<PNG> {\n return new Promise((resolve, reject) => {\n fs.createReadStream(filepath)\n .pipe(new PNG())\n .on(\"parsed\", function (this: PNG) {\n resolve(this);\n })\n .on(\"error\", reject);\n });\n}\n\n/**\n * Write PNG object to file.\n */\nasync function writePng(png: PNG, filepath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n png.pack().pipe(fs.createWriteStream(filepath)).on(\"finish\", resolve).on(\"error\", reject);\n });\n}\n\n/**\n * Calculate color delta using YIQ color space.\n */\nfunction colorDelta(\n r1: number,\n g1: number,\n b1: number,\n a1: number,\n r2: number,\n g2: number,\n b2: number,\n a2: number,\n): number {\n if (a1 !== 255) {\n r1 = blend(r1, 255, a1 / 255);\n g1 = blend(g1, 255, a1 / 255);\n b1 = blend(b1, 255, a1 / 255);\n }\n if (a2 !== 255) {\n r2 = blend(r2, 255, a2 / 255);\n g2 = blend(g2, 255, a2 / 255);\n b2 = blend(b2, 255, a2 / 255);\n }\n\n const y1 = r1 * 0.29889531 + g1 * 0.58662247 + b1 * 0.11448223;\n const i1 = r1 * 0.59597799 - g1 * 0.2741761 - b1 * 0.32180189;\n const q1 = r1 * 0.21147017 - g1 * 0.52261711 + b1 * 0.31114694;\n\n const y2 = r2 * 0.29889531 + g2 * 0.58662247 + b2 * 0.11448223;\n const i2 = r2 * 0.59597799 - g2 * 0.2741761 - b2 * 0.32180189;\n const q2 = r2 * 0.21147017 - g2 * 0.52261711 + b2 * 0.31114694;\n\n const dy = y1 - y2;\n const di = i1 - i2;\n const dq = q1 - q2;\n\n return dy * dy * 0.5053 + di * di * 0.299 + dq * dq * 0.1957;\n}\n\n/**\n * Blend foreground with background using alpha.\n */\nfunction blend(fg: number, bg: number, alpha: number): number {\n return bg + (fg - bg) * alpha;\n}\n\n/**\n * Check if file exists.\n */\nasync function fileExists(filepath: string): Promise<boolean> {\n try {\n await fs.promises.access(filepath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Simple anti-aliasing detection.\n * A pixel is likely anti-aliased if its neighbors have high contrast in opposite directions.\n */\nfunction isAntiAliased(\n img1: PNG,\n img2: PNG,\n x: number,\n y: number,\n width: number,\n height: number,\n): boolean {\n const minX = Math.max(0, x - 1);\n const maxX = Math.min(width - 1, x + 1);\n const minY = Math.max(0, y - 1);\n const maxY = Math.min(height - 1, y + 1);\n\n let zeroes = 0;\n let positives = 0;\n let negatives = 0;\n\n for (let ny = minY; ny <= maxY; ny++) {\n for (let nx = minX; nx <= maxX; nx++) {\n if (nx === x && ny === y) continue;\n const idx = (ny * width + nx) * 4;\n\n const delta = colorDelta(\n img1.data[idx],\n img1.data[idx + 1],\n img1.data[idx + 2],\n img1.data[idx + 3],\n img2.data[idx],\n img2.data[idx + 1],\n img2.data[idx + 2],\n img2.data[idx + 3],\n );\n\n if (delta === 0) {\n zeroes++;\n } else if (delta > 0) {\n positives++;\n } else {\n negatives++;\n }\n }\n }\n\n // If neighbors are mixed (some match, some differ), it's likely AA\n return zeroes > 0 && (positives > 0 || negatives > 0) && positives + negatives < 4;\n}\n\n/**\n * Simple glob matching for pattern-based filtering.\n */\nfunction matchGlob(filepath: string, pattern: string): boolean {\n const regex = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \".*\")\n .replace(/\\*(?!\\*)/g, \"[^/]*\");\n return new RegExp(`^${regex}$`).test(filepath);\n}\n\n/**\n * Escape HTML special characters.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nexport default MuseaVrtRunner;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/vite-plugin-musea",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.77",
|
|
4
4
|
"description": "Vite plugin for Musea - Component gallery for Vue components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"pngjs": "^7.0.0",
|
|
55
|
-
"@vizejs/native": "0.0.1-alpha.
|
|
55
|
+
"@vizejs/native": "0.0.1-alpha.77"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.14.0",
|
|
@@ -66,12 +66,13 @@
|
|
|
66
66
|
"vite": "^8.0.0-beta.0",
|
|
67
67
|
"vue": "^3.5.0",
|
|
68
68
|
"vue-router": "^4.5.0",
|
|
69
|
-
"@vizejs/vite-plugin": "0.0.1-alpha.
|
|
69
|
+
"@vizejs/vite-plugin": "0.0.1-alpha.77"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"axe-core": "^4.7.0",
|
|
73
73
|
"playwright": "^1.40.0",
|
|
74
|
-
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
|
|
74
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0",
|
|
75
|
+
"@vizejs/vite-plugin": "0.0.1-alpha.77"
|
|
75
76
|
},
|
|
76
77
|
"peerDependenciesMeta": {
|
|
77
78
|
"playwright": {
|