/** * UI Generate Preview Tool * Generate compare.html and index.html for UI prototypes */ import { readdirSync, existsSync, readFileSync, writeFileSync } from 'fs'; import { resolve, basename } from 'path'; /** * Auto-detect matrix dimensions from file patterns * Pattern: {target}-style-{s}-layout-{l}.html */ function detectMatrixDimensions(prototypesDir) { const files = readdirSync(prototypesDir).filter(f => f.match(/.*-style-\d+-layout-\d+\.html$/)); const styles = new Set(); const layouts = new Set(); const targets = new Set(); files.forEach(file => { const styleMatch = file.match(/-style-(\d+)-/); const layoutMatch = file.match(/-layout-(\d+)\.html/); const targetMatch = file.match(/^(.+)-style-/); if (styleMatch) styles.add(parseInt(styleMatch[1])); if (layoutMatch) layouts.add(parseInt(layoutMatch[1])); if (targetMatch) targets.add(targetMatch[1]); }); return { styles: Math.max(...Array.from(styles)), layouts: Math.max(...Array.from(layouts)), targets: Array.from(targets).sort() }; } /** * Load template from file */ function loadTemplate(templatePath) { const defaultPath = resolve( process.env.HOME || process.env.USERPROFILE, '.claude/workflows/_template-compare-matrix.html' ); const path = templatePath || defaultPath; if (!existsSync(path)) { // Return minimal fallback template return ` UI Prototypes Comparison

UI Prototypes Matrix

Styles: {{style_variants}} | Layouts: {{layout_variants}}

Pages: {{pages_json}}

Generated: {{timestamp}}

`; } return readFileSync(path, 'utf8'); } /** * Generate compare.html from template */ function generateCompareHtml(template, metadata) { const { runId, sessionId, timestamp, styles, layouts, targets } = metadata; const pagesJson = JSON.stringify(targets); return template .replace(/\{\{run_id\}\}/g, runId) .replace(/\{\{session_id\}\}/g, sessionId) .replace(/\{\{timestamp\}\}/g, timestamp) .replace(/\{\{style_variants\}\}/g, styles.toString()) .replace(/\{\{layout_variants\}\}/g, layouts.toString()) .replace(/\{\{pages_json\}\}/g, pagesJson); } /** * Generate index.html */ function generateIndexHtml(metadata) { const { styles, layouts, targets } = metadata; const total = styles * layouts * targets.length; return ` UI Prototypes Index

UI Prototypes

Interactive design exploration matrix

📊 Interactive Comparison

View all prototypes side-by-side with synchronized scrolling

Open Comparison Matrix →
Style Variants
${styles}
Layout Variants
${layouts}
Pages/Components
${targets.length}
Total Prototypes
${total}

Individual Prototypes

`; } /** * Generate PREVIEW.md */ function generatePreviewMd(metadata) { const { styles, layouts, targets } = metadata; return `# UI Prototypes Preview ## Matrix Dimensions - **Style Variants**: ${styles} - **Layout Variants**: ${layouts} - **Pages/Components**: ${targets.join(', ')} - **Total Prototypes**: ${styles * layouts * targets.length} ## Quick Start 1. **Interactive Comparison**: Open \`compare.html\` for side-by-side view with synchronized scrolling 2. **Browse Index**: Open \`index.html\` for a navigable list of all prototypes 3. **Individual Files**: Access specific prototypes directly (e.g., \`${targets[0]}-style-1-layout-1.html\`) ## File Naming Convention \`\`\` {page}-style-{s}-layout-{l}.html \`\`\` - **page**: Component/page name (${targets.join(', ')}) - **s**: Style variant number (1-${styles}) - **l**: Layout variant number (1-${layouts}) ## Tips - Use compare.html for quick visual comparison across all variants - Synchronized scrolling helps identify consistency issues - Check responsive behavior across different layout variants `; } /** * Main execute function */ async function execute(params) { const { prototypesDir = '.', template: templatePath, runId: runIdParam, sessionId: sessionIdParam, timestamp: timestampParam, } = params; const targetPath = resolve(process.cwd(), prototypesDir); if (!existsSync(targetPath)) { throw new Error(`Directory not found: ${targetPath}`); } // Auto-detect matrix dimensions const { styles, layouts, targets } = detectMatrixDimensions(targetPath); if (styles === 0 || layouts === 0 || targets.length === 0) { throw new Error('No prototype files found matching pattern {target}-style-{s}-layout-{l}.html'); } const now = new Date(); const runId = runIdParam || `run-${now.toISOString().replace(/[:.]/g, '-').slice(0, -5)}`; const sessionId = sessionIdParam || 'standalone'; const timestamp = timestampParam || now.toISOString(); // Generate metadata const metadata = { runId, sessionId, timestamp, styles, layouts, targets }; // Load template const template = loadTemplate(templatePath); // Generate files const compareHtml = generateCompareHtml(template, metadata); const indexHtml = generateIndexHtml(metadata); const previewMd = generatePreviewMd(metadata); // Write files writeFileSync(resolve(targetPath, 'compare.html'), compareHtml, 'utf8'); writeFileSync(resolve(targetPath, 'index.html'), indexHtml, 'utf8'); writeFileSync(resolve(targetPath, 'PREVIEW.md'), previewMd, 'utf8'); return { success: true, prototypes_dir: prototypesDir, styles, layouts, targets, total_prototypes: styles * layouts * targets.length, files_generated: ['compare.html', 'index.html', 'PREVIEW.md'] }; } /** * Tool Definition */ export const uiGeneratePreviewTool = { name: 'ui_generate_preview', description: `Generate interactive preview files for UI prototypes. Generates: - compare.html: Interactive matrix view with synchronized scrolling - index.html: Navigation and statistics - PREVIEW.md: Usage guide Auto-detects matrix dimensions from file pattern: {target}-style-{s}-layout-{l}.html`, parameters: { type: 'object', properties: { prototypesDir: { type: 'string', description: 'Prototypes directory path (default: current directory)', default: '.' }, template: { type: 'string', description: 'Optional path to compare.html template' }, runId: { type: 'string', description: 'Optional run identifier to inject into compare.html (defaults to generated timestamp-based run id)' }, sessionId: { type: 'string', description: 'Optional session identifier to inject into compare.html (default: standalone)' }, timestamp: { type: 'string', description: 'Optional ISO timestamp to inject into compare.html (defaults to current time)' } }, required: [] }, execute };