@champpaba/claude-agent-kit 3.2.0 → 3.4.0

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.
@@ -38,49 +38,33 @@ Extract ALL design data from a website and save to `design-system/extracted/{sit
38
38
 
39
39
  ## 🔍 STEP 0: Parse Input & Setup
40
40
 
41
- ```javascript
42
- // Parse URL
43
- const input = args[0];
44
- if (!input) {
45
- return error('URL required. Usage: /extract https://airbnb.com');
46
- }
47
-
48
- // Normalize URL
49
- let url = input.trim();
50
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
51
- url = 'https://' + url;
52
- }
53
-
54
- // Auto-detect site name
55
- const siteName = new URL(url).hostname
56
- .replace('www.', '')
57
- .replace(/\.[^.]+$/, ''); // Remove TLD
58
-
59
- // Check if already extracted
60
- const extractedPath = `design-system/extracted/${siteName}`;
61
- if (exists(extractedPath + '/data.yaml')) {
62
- const existingData = YAML.parse(Read(extractedPath + '/data.yaml'));
63
- const extractedDate = existingData.meta.extracted_at;
64
-
65
- const response = await AskUserQuestion({
66
- questions: [{
67
- question: `Site "${siteName}" was already extracted on ${extractedDate}. Re-extract?`,
68
- header: "Re-extract?",
69
- multiSelect: false,
70
- options: [
71
- { label: "Yes, re-extract", description: "Overwrite previous data" },
72
- { label: "No, cancel", description: "Keep existing data" }
73
- ]
74
- }]
75
- });
76
-
77
- if (response.answers["Re-extract?"] === "No, cancel") {
78
- return output('Extraction cancelled. Existing data preserved.');
79
- }
80
- }
81
-
82
- // Create directories
83
- Bash: mkdir -p design-system/extracted/${siteName}/screenshots
41
+ ### 0.1: Validate and Normalize URL
42
+
43
+ 1. Check if URL argument is provided
44
+ - If missing, return error: "URL required. Usage: /extract https://airbnb.com"
45
+ 2. Trim whitespace from URL
46
+ 3. Add "https://" prefix if URL doesn't start with "http://" or "https://"
47
+ 4. Parse hostname and auto-detect site name:
48
+ - Remove "www." prefix if present
49
+ - Remove top-level domain (TLD) to get clean site name
50
+ - Example: "www.airbnb.com" "airbnb"
51
+
52
+ ### 0.2: Check for Existing Extraction
53
+
54
+ 1. Build path: `design-system/extracted/{siteName}/data.yaml`
55
+ 2. If file exists:
56
+ - Read existing YAML file
57
+ - Extract `meta.extracted_at` field
58
+ - Ask user via AskUserQuestion:
59
+ - Question: "Site '{siteName}' was already extracted on {extractedDate}. Re-extract?"
60
+ - Options: "Yes, re-extract" (overwrite) or "No, cancel" (keep existing)
61
+ - If user chooses "No, cancel", exit with message: "Extraction cancelled. Existing data preserved."
62
+
63
+ ### 0.3: Create Output Directories
64
+
65
+ Use Bash to create directory structure:
66
+ ```bash
67
+ mkdir -p design-system/extracted/{siteName}/screenshots
84
68
  ```
85
69
 
86
70
  **Report:**
@@ -98,42 +82,27 @@ Bash: mkdir -p design-system/extracted/${siteName}/screenshots
98
82
 
99
83
  ## STEP 1: Navigate & Wait
100
84
 
101
- ```javascript
102
- // Navigate
103
- await mcp__chrome-devtools__navigate_page({ url });
104
-
105
- // Smart wait
106
- try {
107
- const snapshot = await mcp__chrome-devtools__take_snapshot({ verbose: false });
108
-
109
- // Find main heading
110
- const headings = snapshot.split('\n')
111
- .filter(line => line.includes('[heading]'))
112
- .slice(0, 3);
113
-
114
- if (headings.length > 0) {
115
- const mainText = headings[0].split('"')[1];
116
-
117
- await mcp__chrome-devtools__wait_for({
118
- text: mainText,
119
- timeout: 15000
120
- });
121
- } else {
122
- await sleep(5000);
123
- }
124
- } catch {
125
- await sleep(5000);
126
- }
127
-
128
- // Verify loaded
129
- const readyState = await mcp__chrome-devtools__evaluate_script({
130
- function: '() => document.readyState'
131
- });
132
-
133
- if (readyState !== 'complete') {
134
- await sleep(3000);
135
- }
136
- ```
85
+ ### 1.1: Navigate to URL
86
+
87
+ Use Chrome DevTools to navigate to the target URL:
88
+ - Tool: `mcp__chrome-devtools__navigate_page`
89
+ - Parameter: `url` (from Step 0)
90
+
91
+ ### 1.2: Smart Wait for Page Load
92
+
93
+ 1. Take DOM snapshot (verbose: false) to analyze page structure
94
+ 2. From snapshot, find heading elements (filter lines containing `[heading]`)
95
+ 3. If headings found:
96
+ - Extract text from first heading
97
+ - Use Chrome DevTools wait_for to wait for that text (timeout: 15000ms)
98
+ - This ensures the main content is loaded
99
+ 4. If no headings found or wait fails:
100
+ - Fallback to sleep 5000ms
101
+
102
+ ### 1.3: Verify Document Ready
103
+
104
+ 1. Evaluate script to check document.readyState
105
+ 2. If not "complete", sleep additional 3000ms to ensure full page load
137
106
 
138
107
  **Report:**
139
108
  ```
@@ -146,432 +115,230 @@ if (readyState !== 'complete') {
146
115
 
147
116
  ## STEP 2: Extract CSS Data (17 Sections in Parallel)
148
117
 
149
- Run all extraction scripts in parallel for speed:
150
-
151
- ```javascript
152
- const extractionPromises = [
153
- extractColors(),
154
- extractTypography(),
155
- extractShadows(),
156
- extractSpacing(),
157
- extractButtons(),
158
- extractCards(),
159
- extractInputs(),
160
- extractAnimations()
161
- ];
162
-
163
- const [
164
- colors,
165
- typography,
166
- shadows,
167
- spacing,
168
- buttons,
169
- cards,
170
- inputs,
171
- animations
172
- ] = await Promise.all(extractionPromises);
173
- ```
174
-
175
- ### 2.1: Extract Colors (with usage detection)
176
-
177
- ```javascript
178
- async function extractColors() {
179
- return await mcp__chrome-devtools__evaluate_script({
180
- function: `() => {
181
- const allElements = document.querySelectorAll('*');
182
- const colorMap = {
183
- backgrounds: new Map(),
184
- texts: new Map(),
185
- borders: new Map()
186
- };
187
-
188
- // Convert RGB to HEX
189
- const rgbToHex = (rgb) => {
190
- const match = rgb.match(/\\d+/g);
191
- if (!match) return rgb;
192
- const hex = match.slice(0, 3).map(x => {
193
- const h = parseInt(x).toString(16);
194
- return h.length === 1 ? '0' + h : h;
195
- });
196
- return '#' + hex.join('').toUpperCase();
197
- };
198
-
199
- // Detect usage based on element context
200
- const detectUsage = (el, type) => {
201
- const tag = el.tagName.toLowerCase();
202
- const classes = el.className || '';
203
-
204
- if (type === 'background') {
205
- if (tag === 'button' || classes.includes('btn') || classes.includes('button')) return 'button-bg';
206
- if (tag === 'nav' || classes.includes('nav') || classes.includes('header')) return 'nav-bg';
207
- if (classes.includes('card') || classes.includes('box')) return 'card-bg';
208
- if (classes.includes('hero') || classes.includes('banner')) return 'hero-bg';
209
- if (tag === 'body' || classes.includes('main')) return 'page-bg';
210
- return 'surface';
211
- }
212
-
213
- if (type === 'text') {
214
- if (tag.match(/^h[1-6]$/)) return 'heading';
215
- if (tag === 'a') return 'link';
216
- if (tag === 'button') return 'button-text';
217
- if (classes.includes('muted') || classes.includes('secondary')) return 'muted-text';
218
- return 'body-text';
219
- }
220
-
221
- if (type === 'border') {
222
- if (tag === 'input' || tag === 'textarea') return 'input-border';
223
- if (classes.includes('card')) return 'card-border';
224
- return 'divider';
225
- }
226
-
227
- return 'general';
228
- };
229
-
230
- allElements.forEach(el => {
231
- const s = window.getComputedStyle(el);
232
-
233
- if (s.backgroundColor && s.backgroundColor !== 'rgba(0, 0, 0, 0)') {
234
- const hex = rgbToHex(s.backgroundColor);
235
- if (!colorMap.backgrounds.has(hex)) {
236
- colorMap.backgrounds.set(hex, {
237
- rgb: s.backgroundColor,
238
- hex: hex,
239
- usage: detectUsage(el, 'background'),
240
- count: 1
241
- });
242
- } else {
243
- colorMap.backgrounds.get(hex).count++;
244
- }
245
- }
246
-
247
- if (s.color) {
248
- const hex = rgbToHex(s.color);
249
- if (!colorMap.texts.has(hex)) {
250
- colorMap.texts.set(hex, {
251
- rgb: s.color,
252
- hex: hex,
253
- usage: detectUsage(el, 'text'),
254
- count: 1
255
- });
256
- } else {
257
- colorMap.texts.get(hex).count++;
258
- }
259
- }
260
-
261
- if (s.borderColor && s.borderColor !== 'rgba(0, 0, 0, 0)') {
262
- const hex = rgbToHex(s.borderColor);
263
- if (!colorMap.borders.has(hex)) {
264
- colorMap.borders.set(hex, {
265
- rgb: s.borderColor,
266
- hex: hex,
267
- usage: detectUsage(el, 'border'),
268
- count: 1
269
- });
270
- } else {
271
- colorMap.borders.get(hex).count++;
272
- }
273
- }
274
- });
275
-
276
- // Sort by count (most used first)
277
- const sortByCount = (map) => Array.from(map.values())
278
- .sort((a, b) => b.count - a.count)
279
- .slice(0, 20);
280
-
281
- return {
282
- backgrounds: sortByCount(colorMap.backgrounds),
283
- texts: sortByCount(colorMap.texts),
284
- borders: sortByCount(colorMap.borders)
285
- };
286
- }`
287
- });
288
- }
118
+ Run all extraction evaluations in parallel for speed. Use Chrome DevTools `evaluate_script` for each extraction function below.
119
+
120
+ **Parallel Execution Strategy:**
121
+ - Execute all 8 extraction functions concurrently
122
+ - Collect results: colors, typography, shadows, spacing, buttons, cards, inputs, animations
123
+ - Non-critical failures should not block other extractions (use fallback empty arrays)
124
+
125
+ ### 2.1: Extract Colors
126
+
127
+ Use Chrome DevTools to evaluate script that:
128
+
129
+ 1. **Query all elements**: `document.querySelectorAll('*')`
130
+ 2. **For each element**, extract using `window.getComputedStyle()`:
131
+ - Background color (skip transparent: `rgba(0, 0, 0, 0)`)
132
+ - Text color
133
+ - Border color (skip transparent)
134
+ 3. **Convert RGB to HEX**: Parse RGB values and convert to uppercase hex format
135
+ 4. **Detect usage context** based on element tag/class:
136
+ - Background usage: button-bg, nav-bg, card-bg, hero-bg, page-bg, surface
137
+ - Text usage: heading, link, button-text, muted-text, body-text
138
+ - Border usage: input-border, card-border, divider
139
+ 5. **Count frequency** of each color (how many times used)
140
+ 6. **Sort by count** (most used first) and take top 20 per category
141
+
142
+ **Output format:**
143
+ ```yaml
144
+ colors:
145
+ backgrounds:
146
+ - hex: "#FFFFFF"
147
+ rgb: "rgb(255, 255, 255)"
148
+ usage: "page-bg"
149
+ count: 45
150
+ texts:
151
+ - hex: "#000000"
152
+ usage: "body-text"
153
+ count: 32
154
+ borders:
155
+ - hex: "#E5E7EB"
156
+ usage: "divider"
157
+ count: 12
289
158
  ```
290
159
 
291
160
  ### 2.2: Extract Typography
292
161
 
293
- ```javascript
294
- async function extractTypography() {
295
- return await mcp__chrome-devtools__evaluate_script({
296
- function: `() => {
297
- const fonts = new Set();
298
- const weights = new Set();
299
- const sizes = new Set();
300
-
301
- const typography = {
302
- h1: [],
303
- h2: [],
304
- h3: [],
305
- body: []
306
- };
307
-
308
- // Headings
309
- ['h1', 'h2', 'h3'].forEach(tag => {
310
- Array.from(document.querySelectorAll(tag)).slice(0, 3).forEach(el => {
311
- const s = window.getComputedStyle(el);
312
- typography[tag].push({
313
- text: el.textContent.trim().substring(0, 50),
314
- fontSize: s.fontSize,
315
- fontWeight: s.fontWeight,
316
- fontFamily: s.fontFamily,
317
- lineHeight: s.lineHeight,
318
- letterSpacing: s.letterSpacing,
319
- textTransform: s.textTransform,
320
- color: s.color
321
- });
322
- fonts.add(s.fontFamily);
323
- weights.add(s.fontWeight);
324
- sizes.add(s.fontSize);
325
- });
326
- });
327
-
328
- // Body text
329
- Array.from(document.querySelectorAll('p, div, span')).slice(0, 20).forEach(el => {
330
- const s = window.getComputedStyle(el);
331
- if (el.textContent.trim().length > 20) {
332
- typography.body.push({
333
- fontSize: s.fontSize,
334
- fontWeight: s.fontWeight,
335
- lineHeight: s.lineHeight,
336
- fontFamily: s.fontFamily,
337
- color: s.color
338
- });
339
- fonts.add(s.fontFamily);
340
- weights.add(s.fontWeight);
341
- sizes.add(s.fontSize);
342
- }
343
- });
344
-
345
- return {
346
- ...typography,
347
- allFonts: Array.from(fonts),
348
- allWeights: Array.from(weights).sort((a, b) => parseInt(a) - parseInt(b)),
349
- allSizes: Array.from(sizes).sort((a, b) => parseFloat(a) - parseFloat(b))
350
- };
351
- }`
352
- });
353
- }
162
+ Use Chrome DevTools to evaluate script that:
163
+
164
+ 1. **Extract heading styles** (h1, h2, h3):
165
+ - Query first 3 instances of each heading tag
166
+ - For each, extract using `window.getComputedStyle()`:
167
+ - Sample text (first 50 characters)
168
+ - fontSize, fontWeight, fontFamily
169
+ - lineHeight, letterSpacing, textTransform
170
+ - color
171
+ 2. **Extract body text styles** (p, div, span):
172
+ - Query first 20 elements with text content > 20 characters
173
+ - Extract: fontSize, fontWeight, lineHeight, fontFamily, color
174
+ 3. **Collect unique values**:
175
+ - All font families used
176
+ - All font weights (sorted numerically)
177
+ - All font sizes (sorted by value)
178
+
179
+ **Output format:**
180
+ ```yaml
181
+ typography:
182
+ h1:
183
+ - text: "Welcome to our site"
184
+ fontSize: "48px"
185
+ fontWeight: "700"
186
+ fontFamily: "Inter, sans-serif"
187
+ h2:
188
+ - fontSize: "32px"
189
+ fontWeight: "600"
190
+ body:
191
+ - fontSize: "16px"
192
+ fontWeight: "400"
193
+ lineHeight: "1.5"
194
+ allFonts: ["Inter", "Roboto"]
195
+ allWeights: ["400", "500", "600", "700"]
196
+ allSizes: ["14px", "16px", "24px", "32px", "48px"]
354
197
  ```
355
198
 
356
199
  ### 2.3: Extract Shadows & Effects
357
200
 
358
- ```javascript
359
- async function extractShadows() {
360
- return await mcp__chrome-devtools__evaluate_script({
361
- function: `() => {
362
- const allElements = document.querySelectorAll('*');
363
- const effects = {
364
- shadows: new Set(),
365
- borderRadii: new Set(),
366
- borderWidths: new Set()
367
- };
368
-
369
- allElements.forEach(el => {
370
- const s = window.getComputedStyle(el);
371
- if (s.boxShadow && s.boxShadow !== 'none') {
372
- effects.shadows.add(s.boxShadow);
373
- }
374
- if (s.borderRadius && s.borderRadius !== '0px') {
375
- effects.borderRadii.add(s.borderRadius);
376
- }
377
- if (s.borderWidth && s.borderWidth !== '0px') {
378
- effects.borderWidths.add(s.borderWidth);
379
- }
380
- });
381
-
382
- return {
383
- shadows: Array.from(effects.shadows).slice(0, 15),
384
- borderRadii: Array.from(effects.borderRadii).slice(0, 15),
385
- borderWidths: Array.from(effects.borderWidths).slice(0, 10)
386
- };
387
- }`
388
- });
389
- }
201
+ Use Chrome DevTools to evaluate script that:
202
+
203
+ 1. **Query all elements**: `document.querySelectorAll('*')`
204
+ 2. **For each element**, extract using `window.getComputedStyle()`:
205
+ - boxShadow (skip "none")
206
+ - borderRadius (skip "0px")
207
+ - borderWidth (skip "0px")
208
+ 3. **Collect unique values** using Set to avoid duplicates
209
+ 4. **Limit results**:
210
+ - Top 15 unique box shadows
211
+ - Top 15 unique border radii
212
+ - Top 10 unique border widths
213
+
214
+ **Output format:**
215
+ ```yaml
216
+ shadows:
217
+ - "0 1px 3px rgba(0, 0, 0, 0.1)"
218
+ - "0 4px 6px rgba(0, 0, 0, 0.1)"
219
+ borderRadii:
220
+ - "4px"
221
+ - "8px"
222
+ - "12px"
223
+ borderWidths:
224
+ - "1px"
225
+ - "2px"
390
226
  ```
391
227
 
392
228
  ### 2.4: Extract Spacing
393
229
 
394
- ```javascript
395
- async function extractSpacing() {
396
- return await mcp__chrome-devtools__evaluate_script({
397
- function: `() => {
398
- const spacing = {
399
- paddings: new Set(),
400
- margins: new Set(),
401
- gaps: new Set()
402
- };
403
-
404
- Array.from(document.querySelectorAll('*')).slice(0, 100).forEach(el => {
405
- const s = window.getComputedStyle(el);
406
-
407
- if (s.padding && s.padding !== '0px') {
408
- [s.padding, s.paddingTop, s.paddingRight, s.paddingBottom, s.paddingLeft]
409
- .forEach(v => spacing.paddings.add(v));
410
- }
411
-
412
- if (s.margin && s.margin !== '0px') {
413
- [s.marginTop, s.marginBottom].forEach(v => {
414
- if (v && v !== '0px' && v !== 'auto') spacing.margins.add(v);
415
- });
416
- }
417
-
418
- if (s.gap && s.gap !== 'normal' && s.gap !== '0px') {
419
- spacing.gaps.add(s.gap);
420
- }
421
- });
422
-
423
- // Detect grid pattern
424
- const allValues = [
425
- ...spacing.paddings,
426
- ...spacing.margins,
427
- ...spacing.gaps
428
- ]
429
- .map(v => parseFloat(v))
430
- .filter(v => !isNaN(v) && v > 0)
431
- .sort((a, b) => a - b);
432
-
433
- const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
434
- const gridBase = allValues.length > 1
435
- ? allValues.reduce((acc, val) => gcd(acc, val), allValues[0])
436
- : 8;
437
-
438
- return {
439
- paddings: Array.from(spacing.paddings).slice(0, 20),
440
- margins: Array.from(spacing.margins).slice(0, 20),
441
- gaps: Array.from(spacing.gaps).slice(0, 10),
442
- detectedGrid: Math.round(gridBase) || 8,
443
- commonValues: [...new Set(allValues)].slice(0, 15)
444
- };
445
- }`
446
- });
447
- }
230
+ Use Chrome DevTools to evaluate script that:
231
+
232
+ 1. **Query first 100 elements** for spacing analysis
233
+ 2. **For each element**, extract using `window.getComputedStyle()`:
234
+ - Padding (all sides: top, right, bottom, left, shorthand) - skip "0px"
235
+ - Margin (top, bottom only) - skip "0px" and "auto"
236
+ - Gap (flexbox/grid) - skip "normal" and "0px"
237
+ 3. **Detect spacing grid pattern**:
238
+ - Parse all spacing values to numbers
239
+ - Calculate Greatest Common Divisor (GCD) to find base unit
240
+ - Common pattern: 4px or 8px base grid
241
+ - Fallback to 8px if pattern unclear
242
+ 4. **Limit results**:
243
+ - Top 20 unique padding values
244
+ - Top 20 unique margin values
245
+ - Top 10 unique gap values
246
+ - Top 15 most common spacing values overall
247
+
248
+ **Output format:**
249
+ ```yaml
250
+ spacing:
251
+ detectedGrid: 8
252
+ paddings: ["8px", "16px", "24px", "32px"]
253
+ margins: ["8px", "16px", "24px"]
254
+ gaps: ["8px", "16px"]
255
+ commonValues: [8, 16, 24, 32, 40, 48]
448
256
  ```
449
257
 
450
- ### 2.5-2.7: Extract Components
451
-
452
- ```javascript
453
- async function extractButtons() {
454
- return await mcp__chrome-devtools__evaluate_script({
455
- function: `() => {
456
- return Array.from(document.querySelectorAll('button, a[role="button"], .btn, [class*="button"], [class*="Button"]'))
457
- .slice(0, 10)
458
- .map((btn, i) => {
459
- btn.setAttribute('data-extract-id', 'button-' + i);
460
- const s = window.getComputedStyle(btn);
461
- return {
462
- id: 'button-' + i,
463
- text: btn.textContent.trim().substring(0, 30),
464
- backgroundColor: s.backgroundColor,
465
- color: s.color,
466
- padding: s.padding,
467
- border: s.border,
468
- borderRadius: s.borderRadius,
469
- fontSize: s.fontSize,
470
- fontWeight: s.fontWeight,
471
- boxShadow: s.boxShadow,
472
- transition: s.transition
473
- };
474
- });
475
- }`
476
- });
477
- }
478
-
479
- async function extractCards() {
480
- return await mcp__chrome-devtools__evaluate_script({
481
- function: `() => {
482
- const selectors = [
483
- '[class*="card"]', '[class*="Card"]',
484
- 'article', 'section',
485
- '[class*="box"]', '[class*="Box"]'
486
- ];
487
-
488
- return Array.from(document.querySelectorAll(selectors.join(', ')))
489
- .slice(0, 10)
490
- .map((card, i) => {
491
- card.setAttribute('data-extract-id', 'card-' + i);
492
- const s = window.getComputedStyle(card);
493
- return {
494
- id: 'card-' + i,
495
- className: card.className,
496
- backgroundColor: s.backgroundColor,
497
- padding: s.padding,
498
- border: s.border,
499
- borderRadius: s.borderRadius,
500
- boxShadow: s.boxShadow,
501
- transition: s.transition
502
- };
503
- });
504
- }`
505
- });
506
- }
507
-
508
- async function extractInputs() {
509
- return await mcp__chrome-devtools__evaluate_script({
510
- function: `() => {
511
- return Array.from(document.querySelectorAll('input[type="text"], input[type="email"], input[type="password"], textarea'))
512
- .slice(0, 5)
513
- .map((input, i) => {
514
- input.setAttribute('data-extract-id', 'input-' + i);
515
- const s = window.getComputedStyle(input);
516
- return {
517
- id: 'input-' + i,
518
- type: input.type || 'textarea',
519
- height: s.height,
520
- padding: s.padding,
521
- border: s.border,
522
- borderRadius: s.borderRadius,
523
- fontSize: s.fontSize,
524
- backgroundColor: s.backgroundColor,
525
- transition: s.transition
526
- };
527
- });
528
- }`
529
- });
530
- }
531
- ```
258
+ ### 2.5: Extract Buttons
259
+
260
+ Use Chrome DevTools to evaluate script that:
261
+
262
+ 1. **Query button elements** with selectors:
263
+ - `button`, `a[role="button"]`
264
+ - `.btn`, `[class*="button"]`, `[class*="Button"]`
265
+ 2. **Take first 10 buttons** found
266
+ 3. **For each button**:
267
+ - Add `data-extract-id` attribute (e.g., "button-0", "button-1")
268
+ - Extract using `window.getComputedStyle()`:
269
+ - Text content (first 30 characters)
270
+ - backgroundColor, color, padding
271
+ - border, borderRadius
272
+ - fontSize, fontWeight
273
+ - boxShadow, transition
274
+
275
+ **Why add data-extract-id**: Enables later re-querying for hover/focus state extraction.
276
+
277
+ ### 2.6: Extract Cards
278
+
279
+ Use Chrome DevTools to evaluate script that:
280
+
281
+ 1. **Query card-like elements** with selectors:
282
+ - `[class*="card"]`, `[class*="Card"]`
283
+ - `article`, `section`
284
+ - `[class*="box"]`, `[class*="Box"]`
285
+ 2. **Take first 10 cards** found
286
+ 3. **For each card**:
287
+ - Add `data-extract-id` attribute (e.g., "card-0", "card-1")
288
+ - Extract className for reference
289
+ - Extract using `window.getComputedStyle()`:
290
+ - backgroundColor, padding
291
+ - border, borderRadius
292
+ - boxShadow, transition
293
+
294
+ ### 2.7: Extract Input Fields
295
+
296
+ Use Chrome DevTools to evaluate script that:
297
+
298
+ 1. **Query input elements** with selectors:
299
+ - `input[type="text"]`, `input[type="email"]`, `input[type="password"]`
300
+ - `textarea`
301
+ 2. **Take first 5 inputs** found
302
+ 3. **For each input**:
303
+ - Add `data-extract-id` attribute (e.g., "input-0", "input-1")
304
+ - Extract type (text/email/password/textarea)
305
+ - Extract using `window.getComputedStyle()`:
306
+ - height, padding
307
+ - border, borderRadius
308
+ - fontSize, backgroundColor
309
+ - transition
532
310
 
533
311
  ### 2.8: Extract Animations
534
312
 
535
- ```javascript
536
- async function extractAnimations() {
537
- return await mcp__chrome-devtools__evaluate_script({
538
- function: `() => {
539
- const keyframes = [];
540
- const transitions = [];
541
-
542
- // Extract @keyframes
543
- Array.from(document.styleSheets).forEach(sheet => {
544
- try {
545
- Array.from(sheet.cssRules || []).forEach(rule => {
546
- if (rule.type === CSSRule.KEYFRAMES_RULE) {
547
- keyframes.push({
548
- name: rule.name,
549
- css: rule.cssText
550
- });
551
- }
552
- });
553
- } catch(e) {
554
- // CORS - skip
555
- }
556
- });
557
-
558
- // Extract elements with transitions
559
- Array.from(document.querySelectorAll('*')).slice(0, 50).forEach(el => {
560
- const s = window.getComputedStyle(el);
561
- if (s.transition && s.transition !== 'all 0s ease 0s') {
562
- transitions.push({
563
- selector: el.className || el.tagName,
564
- transition: s.transition,
565
- transitionDuration: s.transitionDuration,
566
- transitionTimingFunction: s.transitionTimingFunction
567
- });
568
- }
569
- });
570
-
571
- return { keyframes, transitions };
572
- }`
573
- });
574
- }
313
+ Use Chrome DevTools to evaluate script that:
314
+
315
+ 1. **Extract CSS @keyframes animations**:
316
+ - Loop through all document.styleSheets
317
+ - For each stylesheet, check cssRules
318
+ - Find rules with type `CSSRule.KEYFRAMES_RULE`
319
+ - Extract: animation name and full CSS text
320
+ - Handle CORS errors gracefully (skip external stylesheets)
321
+
322
+ 2. **Extract CSS transitions**:
323
+ - Query first 50 elements
324
+ - For each, extract using `window.getComputedStyle()`:
325
+ - transition property
326
+ - transitionDuration
327
+ - transitionTimingFunction
328
+ - Skip default value: "all 0s ease 0s"
329
+ - Record element className or tagName for reference
330
+
331
+ **Output format:**
332
+ ```yaml
333
+ animations:
334
+ keyframes:
335
+ - name: "fadeIn"
336
+ css: "@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }"
337
+ transitions:
338
+ - selector: "button"
339
+ transition: "all 0.3s ease"
340
+ transitionDuration: "0.3s"
341
+ transitionTimingFunction: "ease"
575
342
  ```
576
343
 
577
344
  **Report:**
@@ -595,318 +362,275 @@ async function extractAnimations() {
595
362
 
596
363
  ## STEP 3: Extract Component Animations (Interactive States)
597
364
 
598
- For each component, capture before/after states:
599
-
600
- ```javascript
601
- const componentAnimations = {};
602
-
603
- // Buttons
604
- for (let i = 0; i < Math.min(buttons.length, 3); i++) {
605
- const btnId = `button-${i}`;
606
-
607
- // Scroll into view
608
- await mcp__chrome-devtools__evaluate_script({
609
- function: `() => {
610
- const el = document.querySelector('[data-extract-id="${btnId}"]');
611
- if (el) el.scrollIntoView({ block: 'center' });
612
- }`
613
- });
614
- await sleep(500);
615
-
616
- // Screenshot: Default state
617
- await mcp__chrome-devtools__take_screenshot({
618
- filePath: `design-system/extracted/${siteName}/screenshots/${btnId}-default.png`
619
- });
620
-
621
- // Get default computed styles
622
- const defaultStyle = await mcp__chrome-devtools__evaluate_script({
623
- function: `() => {
624
- const el = document.querySelector('[data-extract-id="${btnId}"]');
625
- if (!el) return null;
626
- const s = window.getComputedStyle(el);
627
- return {
628
- background: s.backgroundColor,
629
- color: s.color,
630
- boxShadow: s.boxShadow,
631
- transform: s.transform
632
- };
633
- }`
634
- });
635
-
636
- // Trigger hover
637
- await mcp__chrome-devtools__evaluate_script({
638
- function: `() => {
639
- const el = document.querySelector('[data-extract-id="${btnId}"]');
640
- if (el) el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
641
- }`
642
- });
643
- await sleep(500);
644
-
645
- // Screenshot: Hover state
646
- await mcp__chrome-devtools__take_screenshot({
647
- filePath: `design-system/extracted/${siteName}/screenshots/${btnId}-hover.png`
648
- });
649
-
650
- // Get hover computed styles
651
- const hoverStyle = await mcp__chrome-devtools__evaluate_script({
652
- function: `() => {
653
- const el = document.querySelector('[data-extract-id="${btnId}"]');
654
- if (!el) return null;
655
- const s = window.getComputedStyle(el);
656
- return {
657
- background: s.backgroundColor,
658
- color: s.color,
659
- boxShadow: s.boxShadow,
660
- transform: s.transform
661
- };
662
- }`
663
- });
664
-
665
- // Remove hover
666
- await mcp__chrome-devtools__evaluate_script({
667
- function: `() => {
668
- const el = document.querySelector('[data-extract-id="${btnId}"]');
669
- if (el) el.dispatchEvent(new MouseEvent('mouseleave'));
670
- }`
671
- });
672
-
673
- // Generate description
674
- const changes = [];
675
- if (defaultStyle.boxShadow !== hoverStyle.boxShadow) changes.push('Shadow changes');
676
- if (defaultStyle.transform !== hoverStyle.transform) changes.push('Transform changes');
677
- if (defaultStyle.background !== hoverStyle.background) changes.push('Background changes');
678
-
679
- componentAnimations[btnId] = {
680
- type: 'button',
681
- states: { default: defaultStyle, hover: hoverStyle },
682
- transition: buttons[i].transition,
683
- description: changes.length > 0 ? changes.join(' + ') : 'No visible changes'
684
- };
685
- }
686
-
687
- // Repeat for cards and inputs...
365
+ For each component type, capture default and hover states to understand animations.
366
+
367
+ ### 3.1: Button Hover States
368
+
369
+ For the **first 3 buttons** (to limit execution time):
370
+
371
+ 1. **Find element by data-extract-id** (e.g., "button-0")
372
+ 2. **Scroll element into view** (block: center) for visibility
373
+ 3. **Wait 500ms** for scroll animation
374
+ 4. **Take screenshot** of default state
375
+ - Save to: `design-system/extracted/{siteName}/screenshots/{btnId}-default.png`
376
+ 5. **Capture default computed styles**:
377
+ - backgroundColor, color, boxShadow, transform
378
+ 6. **Trigger hover state**:
379
+ - Dispatch `MouseEvent('mouseenter', { bubbles: true })` to element
380
+ 7. **Wait 500ms** for transition to complete
381
+ 8. **Take screenshot** of hover state
382
+ - Save to: `design-system/extracted/{siteName}/screenshots/{btnId}-hover.png`
383
+ 9. **Capture hover computed styles**:
384
+ - backgroundColor, color, boxShadow, transform
385
+ 10. **Remove hover state**:
386
+ - Dispatch `MouseEvent('mouseleave')` to element
387
+ 11. **Compare states** and generate description:
388
+ - If boxShadow changed → "Shadow changes"
389
+ - If transform changed → "Transform changes"
390
+ - If background changed → "Background changes"
391
+ - Join changes with " + " or return "No visible changes"
392
+
393
+ ### 3.2: Card Hover States
394
+
395
+ Repeat same process for **first 3 cards** with `data-extract-id="card-{i}"`.
396
+
397
+ ### 3.3: Input Focus States
398
+
399
+ Similar process for **first 3 inputs** but use:
400
+ - Focus event instead of hover: `dispatchEvent(new FocusEvent('focus'))`
401
+ - Blur event to remove: `dispatchEvent(new FocusEvent('blur'))`
402
+ - Screenshot names: `{inputId}-default.png`, `{inputId}-focus.png`
403
+
404
+ **Store results** in componentAnimations object with structure:
405
+ ```yaml
406
+ componentAnimations:
407
+ button-0:
408
+ type: "button"
409
+ description: "Shadow changes + Background changes"
410
+ transition: "all 0.3s ease"
411
+ states:
412
+ default:
413
+ background: "rgb(59, 130, 246)"
414
+ boxShadow: "none"
415
+ hover:
416
+ background: "rgb(37, 99, 235)"
417
+ boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)"
688
418
  ```
689
419
 
690
420
  ---
691
421
 
692
422
  ## STEP 4: Full-Page Screenshot
693
423
 
694
- ```javascript
695
- await Bash: mkdir -p design-system/extracted/${siteName}/screenshots
696
-
697
- try {
698
- await mcp__chrome-devtools__take_screenshot({
699
- fullPage: true,
700
- format: 'png',
701
- filePath: `design-system/extracted/${siteName}/screenshots/full-page.png`
702
- });
703
- } catch {
704
- await mcp__chrome-devtools__take_screenshot({
705
- fullPage: false,
706
- format: 'png',
707
- filePath: `design-system/extracted/${siteName}/screenshots/viewport.png`
708
- });
709
- }
424
+ ### 4.1: Ensure Screenshot Directory Exists
425
+
426
+ ```bash
427
+ mkdir -p design-system/extracted/{siteName}/screenshots
710
428
  ```
711
429
 
430
+ ### 4.2: Capture Full-Page Screenshot
431
+
432
+ Use Chrome DevTools to take screenshot:
433
+
434
+ 1. **First attempt**: Full-page screenshot
435
+ - Tool: `mcp__chrome-devtools__take_screenshot`
436
+ - Parameters: `fullPage: true`, `format: 'png'`
437
+ - Save to: `design-system/extracted/{siteName}/screenshots/full-page.png`
438
+
439
+ 2. **If full-page fails**: Fallback to viewport-only
440
+ - Parameters: `fullPage: false`, `format: 'png'`
441
+ - Save to: `design-system/extracted/{siteName}/screenshots/viewport.png`
442
+
443
+ **Why fallback**: Some sites have infinite scroll or very long pages that cause full-page screenshots to fail.
444
+
712
445
  ---
713
446
 
714
447
  ## STEP 5: AI Psychology Analysis
715
448
 
716
- Read screenshot and analyze:
449
+ ### 5.1: Determine Screenshot Path
717
450
 
718
- ```javascript
719
- const screenshotPath = exists(`design-system/extracted/${siteName}/screenshots/full-page.png`)
720
- ? `design-system/extracted/${siteName}/screenshots/full-page.png`
721
- : `design-system/extracted/${siteName}/screenshots/viewport.png`;
451
+ Check which screenshot exists:
452
+ - Prefer: `design-system/extracted/{siteName}/screenshots/full-page.png`
453
+ - Fallback: `design-system/extracted/{siteName}/screenshots/viewport.png`
722
454
 
723
- const screenshot = Read(screenshotPath);
455
+ ### 5.2: Read Screenshot
724
456
 
725
- const analysisPrompt = `
726
- You are a UX/UI design psychologist.
457
+ Use Read tool to load the screenshot image for visual analysis.
727
458
 
728
- Analyze this website's design and provide insights in YAML format.
459
+ ### 5.3: Generate Psychology Analysis Prompt
729
460
 
730
- Visual Screenshot: [attached]
461
+ Create analysis request with:
731
462
 
732
- Extracted CSS Data:
733
- - Colors: ${JSON.stringify(colors, null, 2)}
734
- - Typography: ${JSON.stringify(typography.allFonts, null, 2)}
463
+ **Context provided:**
464
+ - The screenshot (visual attachment)
465
+ - Extracted CSS colors data (JSON formatted)
466
+ - Extracted typography fonts (JSON formatted)
735
467
 
736
- Return YAML format:
468
+ **Request UX/UI psychology insights in YAML format covering:**
737
469
 
738
- \`\`\`yaml
739
- psychology:
740
- style_classification: # e.g., Neo-Brutalism, Minimalist, Modern SaaS
470
+ 1. **style_classification**: Design style (Neo-Brutalism, Minimalist, Glassmorphism, Modern SaaS, etc.)
741
471
 
742
- emotions_evoked:
743
- - emotion: Trust
744
- reason: "Soft rounded corners reduce anxiety"
745
- - emotion: Adventure
746
- reason: "Vibrant colors suggest excitement"
472
+ 2. **emotions_evoked**: List of emotions with reasons
473
+ - emotion: What feeling the design triggers
474
+ - reason: Specific design elements causing this emotion
747
475
 
748
- target_audience:
749
- primary:
750
- description: "Travelers seeking unique accommodations"
751
- age_range: "25-45"
752
- tech_savvy: high
753
- secondary:
754
- description: "Hosts listing their properties"
755
-
756
- visual_principles:
757
- - name: "Photo-First Design"
758
- description: "Large images dominate, UI recedes"
759
- - name: "Soft & Approachable"
760
- description: "Rounded corners, light grays feel warm"
761
-
762
- why_it_works:
763
- - "Marketplace requires trust Clear info, ratings, soft design"
764
- - "Travel is emotional → Photo-first, inspirational imagery"
765
-
766
- design_philosophy:
767
- core_belief: "Let the content shine"
768
- key_principles:
769
- - "Photography is hero"
770
- - "Consistency builds trust"
771
- - "Subtle brand presence"
772
- \`\`\`
773
-
774
- Be specific with examples from the visual.
775
- `;
776
-
777
- const psychologyYaml = await LLM({
778
- prompt: analysisPrompt,
779
- images: [screenshot]
780
- });
781
- ```
476
+ 3. **target_audience**: Who this design appeals to
477
+ - primary: Main user demographic (description, age_range, tech_savvy)
478
+ - secondary: Secondary users (if applicable)
479
+
480
+ 4. **visual_principles**: Key design patterns observed
481
+ - name: Principle name
482
+ - description: How it's applied
483
+
484
+ 5. **why_it_works**: Strategic design decisions
485
+ - List of business/psychological reasons the design is effective
486
+
487
+ 6. **design_philosophy**: Underlying beliefs
488
+ - core_belief: Central design philosophy
489
+ - key_principles: List of guiding principles
490
+
491
+ **Instruction**: Be specific with examples from the visual.
492
+
493
+ ### 5.4: Extract YAML Response
494
+
495
+ Parse the LLM response to extract the YAML block (between triple backticks).
782
496
 
783
497
  ---
784
498
 
785
499
  ## STEP 6: Generate data.yaml (17 Sections + Psychology)
786
500
 
787
- ```javascript
788
- const yamlContent = `# Design Extraction: ${siteName}
789
- # Extracted: ${new Date().toISOString()}
790
- # URL: ${url}
501
+ ### 6.1: Calculate Coverage Metrics
502
+
503
+ Count how many of the 17 standard sections were successfully detected:
504
+ - Overview, Color Palette, Typography, Spacing System, Component Styles
505
+ - Shadows/Elevation, Animations/Transitions, Border Radius, Border Styles
506
+ - Layout Patterns, etc.
791
507
 
508
+ Calculate percentage: `(detectedSections / 17) * 100`
509
+
510
+ ### 6.2: Build YAML Structure
511
+
512
+ Construct comprehensive YAML file with these sections:
513
+
514
+ **Header Comments:**
515
+ ```yaml
516
+ # Design Extraction: {siteName}
517
+ # Extracted: {ISO timestamp}
518
+ # URL: {url}
519
+ ```
520
+
521
+ **Meta Section:**
522
+ ```yaml
792
523
  meta:
793
- site_name: ${siteName}
794
- url: ${url}
795
- extracted_at: ${new Date().toISOString()}
524
+ site_name: {siteName}
525
+ url: {url}
526
+ extracted_at: {ISO timestamp}
796
527
  extractor_version: "2.1.0"
797
528
  coverage:
798
529
  total_sections: 17
799
- detected_sections: ${countDetectedSections()}
800
- percentage: ${Math.round((countDetectedSections() / 17) * 100)}
801
-
802
- # ============================================
803
- # PSYCHOLOGY & ANALYSIS
804
- # ============================================
805
-
806
- ${psychologyYaml}
530
+ detected_sections: {count}
531
+ percentage: {percentage}
532
+ ```
807
533
 
808
- # ============================================
809
- # DESIGN TOKENS
810
- # ============================================
534
+ **Psychology Section:**
535
+ Insert the psychology YAML from Step 5.4
811
536
 
537
+ **Design Tokens Sections:**
538
+ ```yaml
812
539
  sections:
813
540
  overview:
814
541
  detected: true
815
- style: "${detectStyle()}"
542
+ style: {from psychology.style_classification}
816
543
  tech_stack: Framework-agnostic
817
544
 
818
545
  color_palette:
819
546
  detected: true
820
547
  primary:
821
- ${colors.backgrounds.slice(0, 5).map(c => ` - hex: "${c.hex}"
822
- rgb: "${c.rgb}"
823
- usage: "${c.usage}"`).join('\n')}
824
-
548
+ - hex: {top 5 background colors}
549
+ rgb: {rgb value}
550
+ usage: {usage context}
825
551
  text_colors:
826
- ${colors.texts.slice(0, 5).map(c => ` - hex: "${c.hex}"
827
- usage: "${c.usage}"`).join('\n')}
828
-
552
+ - hex: {top 5 text colors}
553
+ usage: {usage context}
829
554
  border_colors:
830
- ${colors.borders.slice(0, 3).map(c => ` - hex: "${c.hex}"
831
- usage: "${c.usage}"`).join('\n')}
555
+ - hex: {top 3 border colors}
556
+ usage: {usage context}
832
557
 
833
558
  typography:
834
559
  detected: true
835
- fonts:
836
- ${typography.allFonts.slice(0, 3).map(f => ` - "${f}"`).join('\n')}
837
- weights: [${typography.allWeights.join(', ')}]
838
- sizes: [${typography.allSizes.join(', ')}]
560
+ fonts: [{top 3 font families}]
561
+ weights: [{all weights, sorted}]
562
+ sizes: [{all sizes, sorted}]
839
563
 
840
564
  spacing_system:
841
565
  detected: true
842
- grid_base: ${spacing.detectedGrid}
843
- common_values: [${spacing.commonValues.join(', ')}]
566
+ grid_base: {detectedGrid}
567
+ common_values: [{spacing values}]
844
568
 
845
569
  component_styles:
846
570
  detected: true
847
571
  buttons:
848
- ${buttons.slice(0, 3).map(btn => ` - id: "${btn.id}"
849
- text: "${btn.text}"
850
- backgroundColor: "${btn.backgroundColor}"
851
- color: "${btn.color}"
852
- padding: "${btn.padding}"
853
- borderRadius: "${btn.borderRadius}"
854
- transition: "${btn.transition}"
855
- hover_animation: "${componentAnimations[btn.id]?.description || 'none'}"`).join('\n')}
856
-
572
+ - id: {button-0}
573
+ text: {button text}
574
+ backgroundColor: {color}
575
+ color: {text color}
576
+ padding: {padding}
577
+ borderRadius: {radius}
578
+ transition: {transition}
579
+ hover_animation: {description from Step 3}
857
580
  cards:
858
- ${cards.slice(0, 3).map(card => ` - id: "${card.id}"
859
- backgroundColor: "${card.backgroundColor}"
860
- padding: "${card.padding}"
861
- borderRadius: "${card.borderRadius}"
862
- boxShadow: "${card.boxShadow}"
863
- hover_animation: "${componentAnimations[card.id]?.description || 'none'}"`).join('\n')}
581
+ - {similar structure}
582
+ inputs:
583
+ - {similar structure}
864
584
 
865
585
  shadows_elevation:
866
586
  detected: true
867
- values:
868
- ${shadows.shadows.slice(0, 5).map(s => ` - "${s}"`).join('\n')}
587
+ values: [{top 5 shadow values}]
869
588
 
870
589
  animations_transitions:
871
590
  detected: true
872
591
  keyframes:
873
- ${animations.keyframes.slice(0, 5).map(k => ` - name: "${k.name}"`).join('\n')}
592
+ - name: {animation name}
874
593
  transitions:
875
- ${animations.transitions.slice(0, 5).map(t => ` - duration: "${t.transitionDuration}"
876
- timing: "${t.transitionTimingFunction}"`).join('\n')}
594
+ - duration: {duration}
595
+ timing: {timing function}
877
596
 
878
597
  border_radius:
879
598
  detected: true
880
- values: [${shadows.borderRadii.slice(0, 8).join(', ')}]
599
+ values: [{top 8 radius values}]
881
600
 
882
601
  border_styles:
883
602
  detected: true
884
- widths: [${shadows.borderWidths.join(', ')}]
603
+ widths: [{border widths}]
885
604
 
886
605
  layout_patterns:
887
606
  detected: true
888
607
  container_width: "1280px"
889
608
  grid_columns: 12
609
+ ```
890
610
 
891
- # ============================================
892
- # COMPONENT ANIMATIONS (DETAILED)
893
- # ============================================
894
-
611
+ **Component Animations (Detailed):**
612
+ ```yaml
895
613
  animations:
896
- ${Object.entries(componentAnimations).map(([id, anim]) => ` ${id}:
897
- type: "${anim.type}"
898
- description: "${anim.description}"
899
- transition: "${anim.transition}"
614
+ button-0:
615
+ type: "button"
616
+ description: {from Step 3}
617
+ transition: {transition value}
900
618
  states:
901
619
  default:
902
- background: "${anim.states.default?.background || 'none'}"
903
- boxShadow: "${anim.states.default?.boxShadow || 'none'}"
620
+ background: {color}
621
+ boxShadow: {shadow}
904
622
  hover:
905
- background: "${anim.states.hover?.background || 'none'}"
906
- boxShadow: "${anim.states.hover?.boxShadow || 'none'}"`).join('\n')}
907
- `;
623
+ background: {color}
624
+ boxShadow: {shadow}
625
+ card-0:
626
+ {similar structure}
627
+ ```
628
+
629
+ ### 6.3: Write File
908
630
 
909
- Write(`design-system/extracted/${siteName}/data.yaml`, yamlContent);
631
+ Use Write tool to save the YAML content to:
632
+ ```
633
+ design-system/extracted/{siteName}/data.yaml
910
634
  ```
911
635
 
912
636
  ---
@@ -965,29 +689,42 @@ Write(`design-system/extracted/${siteName}/data.yaml`, yamlContent);
965
689
 
966
690
  ## Error Handling
967
691
 
968
- ```javascript
969
- try {
970
- await mcp__chrome-devtools__navigate_page({ url });
971
- } catch (error) {
972
- return error(`
973
- ❌ Failed to load URL: ${url}
974
-
975
- Error: ${error.message}
976
-
977
- Check:
978
- - Is the URL accessible?
979
- - Is Chrome DevTools MCP running?
980
- `);
981
- }
982
-
983
- // Extraction failures are non-critical
984
- try {
985
- const colors = await extractColors();
986
- } catch (error) {
987
- console.warn('Color extraction failed:', error.message);
988
- colors = { backgrounds: [], texts: [], borders: [] };
989
- }
990
- ```
692
+ ### Critical Errors (Stop Execution)
693
+
694
+ **Navigation failures** - If Chrome DevTools navigation fails:
695
+ 1. Catch the error from `mcp__chrome-devtools__navigate_page`
696
+ 2. Return error message:
697
+ ```
698
+ ❌ Failed to load URL: {url}
699
+
700
+ Error: {error.message}
701
+
702
+ Check:
703
+ - Is the URL accessible?
704
+ - Is Chrome DevTools MCP running?
705
+ ```
706
+ 3. Stop execution (cannot proceed without page loaded)
707
+
708
+ ### Non-Critical Errors (Continue with Fallbacks)
709
+
710
+ **Extraction failures** - If individual extraction steps fail:
711
+ 1. Log warning message (e.g., "Color extraction failed: {error.message}")
712
+ 2. Use fallback empty data:
713
+ - Colors: `{ backgrounds: [], texts: [], borders: [] }`
714
+ - Typography: `{ h1: [], h2: [], h3: [], body: [], allFonts: [], allWeights: [], allSizes: [] }`
715
+ - Shadows: `{ shadows: [], borderRadii: [], borderWidths: [] }`
716
+ - Components: `[]` (empty array)
717
+ 3. Continue with other extractions (parallel execution means one failure doesn't block others)
718
+ 4. Final YAML will mark section as `detected: false` if no data extracted
719
+
720
+ **Screenshot failures**:
721
+ - Full-page screenshot fails → Fallback to viewport screenshot
722
+ - Component screenshot fails → Skip that component, continue with others
723
+ - Psychology analysis screenshot missing → Use viewport screenshot as fallback
724
+
725
+ **YAML generation**:
726
+ - Missing data sections → Mark `detected: false` in YAML
727
+ - Invalid data → Use empty defaults, note in coverage percentage
991
728
 
992
729
  ---
993
730