diff --git a/.claude/commands/workflow/ui-design/layout-extract.md b/.claude/commands/workflow/ui-design/layout-extract.md index 4a154ee3..4c1770b0 100644 --- a/.claude/commands/workflow/ui-design/layout-extract.md +++ b/.claude/commands/workflow/ui-design/layout-extract.md @@ -137,15 +137,50 @@ ELSE: **Usage**: Read the script file and use content directly in `mcp__chrome-devtools__evaluate_script()` **Script returns**: -- `metadata`: Extraction timestamp, URL, method +- `metadata`: Extraction timestamp, URL, method, version - `patterns`: Layout pattern statistics (flexColumn, flexRow, grid counts) - `structure`: Hierarchical DOM tree with layout properties +- `exploration`: (Optional) Progressive exploration results when standard selectors fail **Benefits**: - ✅ Real flex/grid configuration (justifyContent, alignItems, gap, etc.) - ✅ Accurate element bounds (x, y, width, height) - ✅ Structural hierarchy with depth control - ✅ Layout pattern identification (flex-row, flex-column, grid-NCol) +- ✅ Progressive exploration: Auto-discovers missing selectors + +**Progressive Exploration Strategy** (v2.2.0+): + +When script finds <3 main containers, it automatically: +1. **Scans** all large visible containers (≥500×300px) +2. **Extracts** class patterns matching: `main|content|wrapper|container|page|layout|app` +3. **Suggests** new selectors to add to script +4. **Returns** exploration data in `result.exploration`: + ```json + { + "triggered": true, + "discoveredCandidates": [{classes, bounds, display}], + "suggestedSelectors": [".wrapper", ".page-index"], + "recommendation": ".wrapper, .page-index, .app-container" + } + ``` + +**Using Exploration Results**: +```javascript +// After extraction, check for suggestions +IF result.exploration?.triggered: + REPORT: result.exploration.warning + REPORT: "Suggested selectors: " + result.exploration.recommendation + + // Update script by adding to commonClassSelectors array + // Then re-run extraction for better coverage +``` + +**Selector Update Workflow**: +1. Run extraction on unfamiliar site +2. Check `result.exploration.suggestedSelectors` +3. Add relevant selectors to script's `commonClassSelectors` +4. Re-run extraction → improved container detection ### Step 3: Memory Check ```bash diff --git a/.claude/scripts/extract-layout-structure.js b/.claude/scripts/extract-layout-structure.js index 2957e1a0..28e38204 100644 --- a/.claude/scripts/extract-layout-structure.js +++ b/.claude/scripts/extract-layout-structure.js @@ -1,10 +1,27 @@ /** - * Extract Layout Structure from DOM + * Extract Layout Structure from DOM - Enhanced Version * * Extracts real layout information from DOM to provide accurate * structural data for UI replication. * + * Features: + * - Framework detection (Nuxt.js, Next.js, React, Vue, Angular) + * - Multi-strategy container detection (strict → relaxed → class-based → framework-specific) + * - Intelligent main content detection with common class names support + * - Supports modern SPA frameworks + * - Detects non-semantic main containers (.main, .content, etc.) + * - Progressive exploration: Auto-discovers missing selectors when standard patterns fail + * - Suggests new class names to add to script based on actual page structure + * + * Progressive Exploration: + * When fewer than 3 main containers are found, the script automatically: + * 1. Analyzes all large visible containers (≥500×300px) + * 2. Extracts class name patterns (main/content/wrapper/container/page/etc.) + * 3. Suggests new selectors to add to the script + * 4. Returns exploration data in result.exploration + * * Usage: Execute via Chrome DevTools evaluate_script + * Version: 2.2.0 */ (() => { @@ -62,7 +79,7 @@ const identifyPattern = (props) => { const { display, flexDirection, gridTemplateColumns } = props; - if (display === 'flex') { + if (display === 'flex' || display === 'inline-flex') { if (flexDirection === 'column') return 'flex-column'; if (flexDirection === 'row') return 'flex-row'; return 'flex'; @@ -77,12 +94,23 @@ return 'grid'; } - if (display === 'inline-flex') return 'inline-flex'; if (display === 'block') return 'block'; return display; }; + /** + * Detect frontend framework + */ + const detectFramework = () => { + if (document.querySelector('#__nuxt')) return { name: 'Nuxt.js', version: 'unknown' }; + if (document.querySelector('#__next')) return { name: 'Next.js', version: 'unknown' }; + if (document.querySelector('[data-reactroot]')) return { name: 'React', version: 'unknown' }; + if (document.querySelector('[ng-version]')) return { name: 'Angular', version: 'unknown' }; + if (window.Vue) return { name: 'Vue.js', version: window.Vue.version || 'unknown' }; + return { name: 'Unknown', version: 'unknown' }; + }; + /** * Build layout tree recursively */ @@ -139,35 +167,165 @@ }; /** - * Find main layout containers + * Find main layout containers with multi-strategy approach */ const findMainContainers = () => { - const selectors = [ + const containers = []; + const found = new Set(); + + // Strategy 1: Strict selectors (body direct children) + const strictSelectors = [ 'body > header', 'body > nav', 'body > main', - 'body > footer', + 'body > footer' + ]; + + // Strategy 2: Relaxed selectors (any level) + const relaxedSelectors = [ + 'header', + 'nav', + 'main', + 'footer', '[role="banner"]', '[role="navigation"]', '[role="main"]', '[role="contentinfo"]' ]; - const containers = []; + // Strategy 3: Common class-based main content selectors + const commonClassSelectors = [ + '.main', + '.content', + '.main-content', + '.page-content', + '.container.main', + '.wrapper > .main', + 'div[class*="main-wrapper"]', + 'div[class*="content-wrapper"]' + ]; - selectors.forEach(selector => { - const element = document.querySelector(selector); - if (element) { - const tree = buildLayoutTree(element, 0, 3); - if (tree) { - containers.push(tree); - } + // Strategy 4: Framework-specific selectors + const frameworkSelectors = [ + '#__nuxt header', '#__nuxt .main', '#__nuxt main', '#__nuxt footer', + '#__next header', '#__next .main', '#__next main', '#__next footer', + '#app header', '#app .main', '#app main', '#app footer', + '[data-app] header', '[data-app] .main', '[data-app] main', '[data-app] footer' + ]; + + // Try all strategies + const allSelectors = [...strictSelectors, ...relaxedSelectors, ...commonClassSelectors, ...frameworkSelectors]; + + allSelectors.forEach(selector => { + try { + const elements = document.querySelectorAll(selector); + elements.forEach(element => { + // Avoid duplicates and invisible elements + if (!found.has(element) && element.offsetParent !== null) { + found.add(element); + const tree = buildLayoutTree(element, 0, 3); + if (tree && tree.bounds.width > 0 && tree.bounds.height > 0) { + containers.push(tree); + } + } + }); + } catch (e) { + console.warn(`Selector failed: ${selector}`, e); } }); + // Fallback: If no containers found, use body's direct children + if (containers.length === 0) { + Array.from(document.body.children).forEach(child => { + if (child.offsetParent !== null && !found.has(child)) { + const tree = buildLayoutTree(child, 0, 2); + if (tree && tree.bounds.width > 100 && tree.bounds.height > 100) { + containers.push(tree); + } + } + }); + } + return containers; }; + /** + * Progressive exploration: Discover main containers when standard selectors fail + * Analyzes large visible containers and suggests class name patterns + */ + const exploreMainContainers = () => { + const candidates = []; + const minWidth = 500; + const minHeight = 300; + + // Find all large visible divs + const allDivs = document.querySelectorAll('div'); + allDivs.forEach(div => { + const rect = div.getBoundingClientRect(); + const style = window.getComputedStyle(div); + + // Filter: large size, visible, not header/footer + if (rect.width >= minWidth && + rect.height >= minHeight && + div.offsetParent !== null && + !div.closest('header') && + !div.closest('footer')) { + + const classes = Array.from(div.classList); + const area = rect.width * rect.height; + + candidates.push({ + element: div, + classes: classes, + area: area, + bounds: { + width: Math.round(rect.width), + height: Math.round(rect.height) + }, + display: style.display, + depth: getElementDepth(div) + }); + } + }); + + // Sort by area (largest first) and take top candidates + candidates.sort((a, b) => b.area - a.area); + + // Extract unique class patterns from top candidates + const classPatterns = new Set(); + candidates.slice(0, 20).forEach(c => { + c.classes.forEach(cls => { + // Identify potential main content class patterns + if (cls.match(/main|content|container|wrapper|page|body|layout|app/i)) { + classPatterns.add(cls); + } + }); + }); + + return { + candidates: candidates.slice(0, 10).map(c => ({ + classes: c.classes, + bounds: c.bounds, + display: c.display, + depth: c.depth + })), + suggestedSelectors: Array.from(classPatterns).map(cls => `.${cls}`) + }; + }; + + /** + * Get element depth in DOM tree + */ + const getElementDepth = (element) => { + let depth = 0; + let current = element; + while (current.parentElement) { + depth++; + current = current.parentElement; + } + return depth; + }; + /** * Analyze layout patterns */ @@ -199,21 +357,53 @@ }; /** - * Main extraction function + * Main extraction function with progressive exploration */ const extractLayout = () => { + const framework = detectFramework(); const containers = findMainContainers(); const patterns = analyzePatterns(containers); - return { + // Progressive exploration: if too few containers found, explore and suggest + let exploration = null; + const minExpectedContainers = 3; // At least header, main, footer + + if (containers.length < minExpectedContainers) { + exploration = exploreMainContainers(); + + // Add warning message + exploration.warning = `Only ${containers.length} containers found. Consider adding these selectors to the script:`; + exploration.recommendation = exploration.suggestedSelectors.join(', '); + } + + const result = { metadata: { extractedAt: new Date().toISOString(), url: window.location.href, - method: 'layout-structure' + framework: framework, + method: 'layout-structure-enhanced', + version: '2.2.0' + }, + statistics: { + totalContainers: containers.length, + patterns: patterns }, - patterns: patterns, structure: containers }; + + // Add exploration results if triggered + if (exploration) { + result.exploration = { + triggered: true, + reason: 'Insufficient containers found with standard selectors', + discoveredCandidates: exploration.candidates, + suggestedSelectors: exploration.suggestedSelectors, + warning: exploration.warning, + recommendation: exploration.recommendation + }; + } + + return result; }; // Execute and return results