sonance-brand-mcp 1.3.98 → 1.3.100

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.
@@ -88,6 +88,40 @@ interface BackupManifest {
88
88
  const BACKUP_ROOT = ".sonance-backups";
89
89
  const DEBUG_LOG_FILE = "sonance-debug.log";
90
90
 
91
+ /**
92
+ * TWO-PHASE DESIGN ANALYSIS TYPES
93
+ * Phase 1 identifies problems, Phase 2 generates patches that reference those problems
94
+ */
95
+
96
+ /** Categories of visual/UX problems that can be identified */
97
+ type DesignProblemCategory = 'hierarchy' | 'spacing' | 'alignment' | 'feedback' | 'grouping';
98
+
99
+ /** A specific visual problem identified in Phase 1 analysis */
100
+ interface DesignProblem {
101
+ /** Unique identifier (e.g., "hierarchy-1", "spacing-2") */
102
+ id: string;
103
+ /** Problem category */
104
+ category: DesignProblemCategory;
105
+ /** Specific description of the issue */
106
+ description: string;
107
+ /** Visual impact severity */
108
+ severity: 'high' | 'medium' | 'low';
109
+ /** Hint for what to look for in the code */
110
+ codeHint?: string;
111
+ }
112
+
113
+ /** Extended patch that references a specific problem from Phase 1 */
114
+ interface DesignPatch {
115
+ /** Reference to the problem this patch fixes */
116
+ problemId: string;
117
+ /** Exact code to find */
118
+ search: string;
119
+ /** Replacement code */
120
+ replace: string;
121
+ /** Explanation of how this fixes the problem */
122
+ rationale: string;
123
+ }
124
+
91
125
  /**
92
126
  * Debug logging utility - writes to sonance-debug.log in project root
93
127
  * This helps diagnose issues with file discovery, validation, and revert
@@ -889,6 +923,139 @@ Be smart - use your knowledge of React patterns to identify what type of element
889
923
  }
890
924
  }
891
925
 
926
+ /**
927
+ * PHASE 1: Design Problem Analysis
928
+ *
929
+ * Analyzes the screenshot to identify specific visual/UX problems.
930
+ * This is a SEPARATE LLM call that ONLY identifies problems - no code changes.
931
+ * The problems are then passed to Phase 2 to generate targeted patches.
932
+ */
933
+ async function analyzeDesignProblems(
934
+ screenshot: string,
935
+ fileContent: string,
936
+ userPrompt: string,
937
+ apiKey: string
938
+ ): Promise<DesignProblem[]> {
939
+ const anthropic = new Anthropic({ apiKey });
940
+ const base64Data = screenshot.split(",")[1] || screenshot;
941
+
942
+ debugLog("Phase 1 Design Analysis: Starting visual problem identification", {
943
+ promptPreview: userPrompt?.substring(0, 50),
944
+ fileContentLength: fileContent.length
945
+ });
946
+
947
+ try {
948
+ const response = await anthropic.messages.create({
949
+ model: "claude-sonnet-4-20250514",
950
+ max_tokens: 2048,
951
+ messages: [
952
+ {
953
+ role: "user",
954
+ content: [
955
+ {
956
+ type: "image",
957
+ source: {
958
+ type: "base64",
959
+ media_type: "image/png",
960
+ data: base64Data,
961
+ },
962
+ },
963
+ {
964
+ type: "text",
965
+ text: `You are a senior UI/UX designer. Analyze this screenshot and identify SPECIFIC visual problems.
966
+
967
+ User's request: "${userPrompt}"
968
+
969
+ Examine the UI for issues in these categories:
970
+
971
+ 1. HIERARCHY: Is there a clear visual focus? Are elements competing for attention?
972
+ - Look for: competing headings, unclear primary actions, visual clutter
973
+
974
+ 2. SPACING: Are related elements grouped? Is there awkward empty space?
975
+ - Look for: disconnected labels/values, uneven gaps, cramped or overly spread content
976
+
977
+ 3. ALIGNMENT: Do elements align naturally? Are there visual disconnects?
978
+ - Look for: misaligned text, floating elements, broken visual flow
979
+
980
+ 4. FEEDBACK: Are progress indicators, states, and actions clear?
981
+ - Look for: weak progress visualization, unclear states, orphaned status indicators
982
+
983
+ 5. GROUPING: Are related items visually connected? Do containers make sense?
984
+ - Look for: related content that appears separate, missing visual boundaries
985
+
986
+ For each problem found, provide:
987
+ - id: unique identifier (e.g., "hierarchy-1", "spacing-2")
988
+ - category: one of [hierarchy, spacing, alignment, feedback, grouping]
989
+ - description: specific description of what's wrong
990
+ - severity: high/medium/low based on visual impact
991
+ - codeHint: what to look for in the code to fix it
992
+
993
+ Return ONLY a JSON array of problems. Example:
994
+ [
995
+ {
996
+ "id": "grouping-1",
997
+ "category": "grouping",
998
+ "description": "The 'Essence Quality' label and '0% COMPLETE' badge appear visually disconnected - they should be grouped together",
999
+ "severity": "high",
1000
+ "codeHint": "Check if label and badge are in separate containers or using absolute positioning"
1001
+ },
1002
+ {
1003
+ "id": "spacing-1",
1004
+ "category": "spacing",
1005
+ "description": "Too much vertical gap between the header section and the quality indicator",
1006
+ "severity": "medium",
1007
+ "codeHint": "Look for large margin or padding values like mt-6, mb-8, py-6"
1008
+ }
1009
+ ]
1010
+
1011
+ IMPORTANT:
1012
+ - Only identify problems that are actually visible in the screenshot
1013
+ - Be specific - vague problems lead to vague fixes
1014
+ - Maximum 5 problems - focus on the most impactful ones
1015
+ - If no significant problems are found, return an empty array []
1016
+ - Do NOT suggest code changes - only identify problems
1017
+
1018
+ Return ONLY the JSON array, no other text.`,
1019
+ },
1020
+ ],
1021
+ },
1022
+ ],
1023
+ });
1024
+
1025
+ const textBlock = response.content.find((block) => block.type === "text");
1026
+ if (!textBlock || textBlock.type !== "text") {
1027
+ debugLog("Phase 1 Design Analysis: No text response from LLM");
1028
+ return [];
1029
+ }
1030
+
1031
+ let jsonText = textBlock.text.trim();
1032
+
1033
+ // Extract JSON from code fences if present
1034
+ const jsonMatch = jsonText.match(/```(?:json)?\n?([\s\S]*?)\n?```/) ||
1035
+ jsonText.match(/\[[\s\S]*\]/);
1036
+ if (jsonMatch) {
1037
+ jsonText = jsonMatch[1] || jsonMatch[0];
1038
+ }
1039
+
1040
+ const problems: DesignProblem[] = JSON.parse(jsonText);
1041
+
1042
+ // Validate the structure
1043
+ const validatedProblems = problems.filter(p =>
1044
+ p.id && p.category && p.description && p.severity
1045
+ ).slice(0, 5); // Maximum 5 problems
1046
+
1047
+ debugLog("Phase 1 Design Analysis: Identified problems", {
1048
+ count: validatedProblems.length,
1049
+ problems: validatedProblems.map(p => ({ id: p.id, category: p.category, severity: p.severity }))
1050
+ });
1051
+
1052
+ return validatedProblems;
1053
+ } catch (e) {
1054
+ debugLog("Phase 1 Design Analysis: Failed to analyze", { error: String(e) });
1055
+ return [];
1056
+ }
1057
+ }
1058
+
892
1059
  /**
893
1060
  * Search candidate files for JSX code matching the focused element
894
1061
  * This helps identify which file actually contains the element the user clicked on
@@ -1342,65 +1509,166 @@ Output format:
1342
1509
  The "search" field must match the file EXACTLY (copy-paste from the code provided).`;
1343
1510
 
1344
1511
  /**
1345
- * DESIGN REASONING PROMPT
1346
- * Forces the LLM through a structured analysis before making design changes.
1347
- * This prevents over-engineering and ensures thoughtful, targeted modifications.
1512
+ * PHASE 2: Targeted Patch Generation Prompt
1513
+ *
1514
+ * This prompt is used AFTER Phase 1 has identified specific problems.
1515
+ * It requires each patch to reference a problem ID from Phase 1.
1348
1516
  */
1349
- const DESIGN_REASONING_PROMPT = `
1517
+ function buildPhase2DesignPrompt(problems: DesignProblem[]): string {
1518
+ if (problems.length === 0) {
1519
+ return ''; // No problems identified, skip design protocol
1520
+ }
1521
+
1522
+ const problemsJson = JSON.stringify(problems, null, 2);
1523
+
1524
+ return `
1350
1525
  ═══════════════════════════════════════════════════════════════════════════════
1351
- DESIGN REASONING PROTOCOL (Follow BEFORE generating patches)
1526
+ PHASE 2: TARGETED PATCH GENERATION
1352
1527
  ═══════════════════════════════════════════════════════════════════════════════
1353
1528
 
1354
- STEP 1 - VISUAL ANALYSIS:
1355
- Before making any changes, analyze the current UI in the screenshot:
1356
- • What type of component is this? (form wizard, modal, card, data table, sidebar, etc.)
1357
- • What is currently working well that should be PRESERVED?
1358
- What specific visual issues exist? (inconsistent spacing, unclear hierarchy, alignment problems, clutter)
1359
-
1360
- STEP 2 - MINIMAL CHANGE PLAN:
1361
- Identify the MINIMUM changes needed to address the user's request:
1362
- What 1-3 targeted fixes would solve the issue?
1363
- What should you explicitly NOT change?
1364
- Will these changes break existing functionality or visual balance?
1365
-
1366
- STEP 3 - GUARDRAILS CHECK:
1367
- Before implementing, verify you are following these rules:
1368
- • Am I preserving the existing layout structure? (flex stays flex, grid stays grid)
1369
- Am I keeping the existing color palette unless specifically asked to change it?
1370
- • Am I making incremental improvements, NOT a complete restructure?
1371
- Am I keeping the same visual hierarchy and element positions?
1372
-
1373
- STEP 4 - IMPLEMENT:
1374
- Only now generate patches for the targeted changes from Step 2.
1375
- Each patch should be small and focused - prefer multiple small patches over one large rewrite.
1529
+ A visual analysis identified these specific problems in the UI:
1530
+
1531
+ ${problemsJson}
1532
+
1533
+ YOUR TASK: Generate patches that fix ONLY these specific problems.
1534
+
1535
+ STRICT RULES:
1536
+ 1. Each patch MUST include a "problemId" field referencing one of the problems above
1537
+ 2. Do NOT make changes unrelated to the identified problems
1538
+ 3. Make the SMALLEST change that fixes each problem
1539
+ 4. Do NOT change layout systems (flex→grid) unless absolutely necessary to fix a problem
1540
+ 5. Preserve existing structure - adjust properties, don't restructure
1541
+
1542
+ For each problem, find the exact code that causes it and create a targeted fix.
1543
+
1544
+ OUTPUT FORMAT (note the problemId and rationale fields):
1545
+ {
1546
+ "modifications": [{
1547
+ "filePath": "components/Example.tsx",
1548
+ "patches": [
1549
+ {
1550
+ "problemId": "grouping-1",
1551
+ "search": "<Badge className=\"absolute right-4\">",
1552
+ "replace": "<Badge className=\"ml-2\">",
1553
+ "rationale": "Removes absolute positioning to group badge with label"
1554
+ },
1555
+ {
1556
+ "problemId": "spacing-1",
1557
+ "search": "className=\"mt-8\"",
1558
+ "replace": "className=\"mt-4\"",
1559
+ "rationale": "Reduces excessive vertical gap"
1560
+ }
1561
+ ]
1562
+ }]
1563
+ }
1564
+
1565
+ VALIDATION:
1566
+ - Patches without a valid problemId will be REJECTED
1567
+ - Patches that don't trace to an identified problem will be REJECTED
1568
+ - Each patch must have search, replace, problemId, and rationale fields
1376
1569
  `;
1570
+ }
1377
1571
 
1378
1572
  /**
1379
- * DESIGN GUARDRAILS
1380
- * Explicit constraints to prevent the LLM from over-engineering design changes.
1573
+ * DESIGN DECISION RULES for Phase 2
1381
1574
  */
1382
1575
  const DESIGN_GUARDRAILS = `
1383
1576
  ═══════════════════════════════════════════════════════════════════════════════
1384
- DESIGN GUARDRAILS (MUST FOLLOW)
1577
+ DESIGN CONSTRAINTS
1385
1578
  ═══════════════════════════════════════════════════════════════════════════════
1386
1579
 
1387
- DO NOT:
1388
- Convert layout systems (do NOT change flex to grid or vice versa)
1389
- Restructure component hierarchy unless explicitly asked
1390
- Change color schemes unless specifically requested
1391
- Move elements to different positions unless asked
1392
- Rewrite entire sections - make targeted changes only
1393
- • Add new wrapper elements or change semantic structure
1394
-
1395
- DO:
1396
- Focus on spacing, padding, and margins for "cleaner" requests
1397
- Improve typography (font size, weight, line-height) for readability
1398
- Fix alignment issues within the existing structure
1399
- Adjust visual hierarchy through subtle styling, not restructuring
1400
- • Make the smallest change that addresses the user's request
1401
- • Preserve what is already working well
1580
+ ALLOWED when fixing identified problems:
1581
+ Adjust spacing (margin, padding) - e.g., mt-4 mt-2
1582
+ Adjust alignment within containers - e.g., items-start → items-center
1583
+ Move elements within the same container to improve grouping
1584
+ Adjust typography - font size, weight, line-height
1585
+ Add/remove whitespace classes
1586
+
1587
+ ✗ FORBIDDEN (will cause rejection):
1588
+ Changing layout system (flex → grid, grid → flex)
1589
+ Restructuring component hierarchy
1590
+ Wrapping elements in new containers (unless specifically needed for a grouping problem)
1591
+ Removing or changing functionality
1592
+ Changes without a problemId reference
1593
+
1594
+ ═══════════════════════════════════════════════════════════════════════════════
1402
1595
  `;
1403
1596
 
1597
+ /**
1598
+ * Validate patches against identified problems
1599
+ * Rejects patches that don't reference a valid problem ID
1600
+ */
1601
+ function validateDesignPatches(
1602
+ patches: Array<{ search: string; replace: string; problemId?: string; rationale?: string }>,
1603
+ problems: DesignProblem[]
1604
+ ): { valid: Array<{ search: string; replace: string; problemId: string; rationale: string }>; rejected: Array<{ patch: unknown; reason: string }> } {
1605
+ const validProblemIds = new Set(problems.map(p => p.id));
1606
+ const valid: Array<{ search: string; replace: string; problemId: string; rationale: string }> = [];
1607
+ const rejected: Array<{ patch: unknown; reason: string }> = [];
1608
+
1609
+ for (const patch of patches) {
1610
+ // Check for required fields
1611
+ if (!patch.search || !patch.replace) {
1612
+ rejected.push({ patch, reason: "Missing search or replace field" });
1613
+ continue;
1614
+ }
1615
+
1616
+ // For design-heavy requests, require problemId
1617
+ if (!patch.problemId) {
1618
+ rejected.push({ patch, reason: "Missing problemId - patch doesn't reference an identified problem" });
1619
+ continue;
1620
+ }
1621
+
1622
+ // Validate problemId exists in the problems list
1623
+ if (!validProblemIds.has(patch.problemId)) {
1624
+ rejected.push({ patch, reason: `Invalid problemId "${patch.problemId}" - not in identified problems list` });
1625
+ continue;
1626
+ }
1627
+
1628
+ // Check for forbidden structural changes
1629
+ const searchLower = patch.search.toLowerCase();
1630
+ const replaceLower = patch.replace.toLowerCase();
1631
+
1632
+ // Detect flex → grid or grid → flex conversions
1633
+ const hadFlex = searchLower.includes('flex') || searchLower.includes('items-') || searchLower.includes('justify-');
1634
+ const hasGrid = replaceLower.includes('grid') && !searchLower.includes('grid');
1635
+ const hadGrid = searchLower.includes('grid');
1636
+ const hasFlex = (replaceLower.includes('flex') || replaceLower.includes('items-') || replaceLower.includes('justify-')) && !hadFlex;
1637
+
1638
+ if ((hadFlex && hasGrid) || (hadGrid && hasFlex)) {
1639
+ rejected.push({ patch, reason: "Forbidden: Changing layout system (flex ↔ grid)" });
1640
+ continue;
1641
+ }
1642
+
1643
+ // Detect space-y/space-x additions that fundamentally change layout
1644
+ const addedSpaceY = replaceLower.includes('space-y') && !searchLower.includes('space-y');
1645
+ const addedSpaceX = replaceLower.includes('space-x') && !searchLower.includes('space-x');
1646
+ const hadJustifyBetween = searchLower.includes('justify-between');
1647
+
1648
+ if ((addedSpaceY || addedSpaceX) && hadJustifyBetween) {
1649
+ rejected.push({ patch, reason: "Forbidden: Converting flex layout to stack layout" });
1650
+ continue;
1651
+ }
1652
+
1653
+ valid.push({
1654
+ search: patch.search,
1655
+ replace: patch.replace,
1656
+ problemId: patch.problemId,
1657
+ rationale: patch.rationale || 'No rationale provided'
1658
+ });
1659
+ }
1660
+
1661
+ if (rejected.length > 0) {
1662
+ debugLog("Design patch validation: Some patches rejected", {
1663
+ validCount: valid.length,
1664
+ rejectedCount: rejected.length,
1665
+ rejected: rejected.map(r => ({ reason: r.reason, searchPreview: String(r.patch).substring(0, 50) }))
1666
+ });
1667
+ }
1668
+
1669
+ return { valid, rejected };
1670
+ }
1671
+
1404
1672
  /**
1405
1673
  * Detect if a user request is design-heavy and needs the reasoning protocol.
1406
1674
  * Simple requests like "change button color to blue" don't need it.
@@ -1827,21 +2095,42 @@ User Request: "${userPrompt}"
1827
2095
 
1828
2096
  `;
1829
2097
 
1830
- // ========== DESIGN REASONING PROTOCOL ==========
1831
- // For design-heavy requests, add structured reasoning and guardrails
1832
- // This prevents over-engineering and ensures thoughtful changes
2098
+ // ========== TWO-PHASE DESIGN ANALYSIS ==========
2099
+ // Phase 1: Identify specific visual problems (separate LLM call)
2100
+ // Phase 2: Generate patches that reference those problems
1833
2101
  const isDesignRequest = isDesignHeavyRequest(userPrompt || '');
2102
+ let identifiedProblems: DesignProblem[] = [];
1834
2103
 
1835
- if (isDesignRequest) {
1836
- debugLog("Design-heavy request detected, adding reasoning protocol", {
2104
+ if (isDesignRequest && screenshot) {
2105
+ debugLog("Design-heavy request detected, running Phase 1 analysis", {
1837
2106
  prompt: userPrompt?.substring(0, 50),
1838
2107
  triggerKeywords: ['redesign', 'better', 'improve', 'cleaner', 'layout', 'modernize']
1839
2108
  .filter(k => userPrompt?.toLowerCase().includes(k))
1840
2109
  });
1841
2110
 
1842
- textContent += DESIGN_REASONING_PROMPT;
1843
- textContent += DESIGN_GUARDRAILS;
1844
- textContent += '\n';
2111
+ // Phase 1: Analyze the screenshot to identify specific problems
2112
+ // This is a SEPARATE LLM call that only identifies problems
2113
+ const fileContentForAnalysis = recommendedFileContent?.content || pageContext.pageContent || '';
2114
+ identifiedProblems = await analyzeDesignProblems(
2115
+ screenshot,
2116
+ fileContentForAnalysis,
2117
+ userPrompt || '',
2118
+ apiKey
2119
+ );
2120
+
2121
+ debugLog("Phase 1 complete: Problems identified", {
2122
+ problemCount: identifiedProblems.length,
2123
+ problems: identifiedProblems.map(p => `${p.id}: ${p.description.substring(0, 50)}`)
2124
+ });
2125
+
2126
+ // Add Phase 2 prompt with the identified problems
2127
+ if (identifiedProblems.length > 0) {
2128
+ textContent += buildPhase2DesignPrompt(identifiedProblems);
2129
+ textContent += DESIGN_GUARDRAILS;
2130
+ textContent += '\n';
2131
+ } else {
2132
+ debugLog("No problems identified - proceeding with standard edit");
2133
+ }
1845
2134
  }
1846
2135
 
1847
2136
  // ========== TARGET COMPONENT ONLY (with line numbers) ==========
@@ -2366,9 +2655,41 @@ This is better than generating patches with made-up code.`,
2366
2655
  // New patch-based approach
2367
2656
  console.log(`[Apply-First] Applying ${mod.patches.length} patches to ${mod.filePath}`);
2368
2657
 
2658
+ // DESIGN VALIDATION: For design-heavy requests, validate patches have problemIds
2659
+ let patchesToApply = mod.patches;
2660
+ if (isDesignRequest && identifiedProblems.length > 0) {
2661
+ const validationResult = validateDesignPatches(mod.patches, identifiedProblems);
2662
+
2663
+ if (validationResult.rejected.length > 0) {
2664
+ debugLog("Design patch validation rejected some patches", {
2665
+ filePath: mod.filePath,
2666
+ validCount: validationResult.valid.length,
2667
+ rejectedCount: validationResult.rejected.length,
2668
+ rejectedReasons: validationResult.rejected.map(r => r.reason)
2669
+ });
2670
+ console.warn(`[Apply-First] ${validationResult.rejected.length} patches rejected for missing/invalid problemId`);
2671
+ }
2672
+
2673
+ // Only use validated patches
2674
+ if (validationResult.valid.length === 0) {
2675
+ patchErrors.push(`${mod.filePath}: All patches were rejected - they don't reference identified problems`);
2676
+ continue;
2677
+ }
2678
+
2679
+ // Log which problems are being fixed
2680
+ const problemsBeingFixed = [...new Set(validationResult.valid.map(p => p.problemId))];
2681
+ debugLog("Design patches validated", {
2682
+ filePath: mod.filePath,
2683
+ problemsBeingFixed,
2684
+ patchCount: validationResult.valid.length
2685
+ });
2686
+
2687
+ patchesToApply = validationResult.valid;
2688
+ }
2689
+
2369
2690
  // PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
2370
2691
  const preValidationErrors: string[] = [];
2371
- for (const patch of mod.patches) {
2692
+ for (const patch of patchesToApply) {
2372
2693
  const normalizedSearch = patch.search.replace(/\\n/g, "\n");
2373
2694
  if (!originalContent.includes(normalizedSearch)) {
2374
2695
  // Try fuzzy match as fallback
@@ -2407,7 +2728,7 @@ This is better than generating patches with made-up code.`,
2407
2728
  continue;
2408
2729
  }
2409
2730
 
2410
- const patchResult = applyPatches(originalContent, mod.patches);
2731
+ const patchResult = applyPatches(originalContent, patchesToApply);
2411
2732
 
2412
2733
  if (!patchResult.success) {
2413
2734
  const failedMessages = patchResult.failedPatches.map(
@@ -2417,15 +2738,15 @@ This is better than generating patches with made-up code.`,
2417
2738
 
2418
2739
  // If some patches succeeded, use partial result
2419
2740
  if (patchResult.appliedPatches > 0) {
2420
- console.warn(`[Apply-First] ${patchResult.appliedPatches}/${mod.patches.length} patches applied to ${mod.filePath}`);
2741
+ console.warn(`[Apply-First] ${patchResult.appliedPatches}/${patchesToApply.length} patches applied to ${mod.filePath}`);
2421
2742
  modifiedContent = patchResult.modifiedContent;
2422
- explanation += ` (${patchResult.appliedPatches}/${mod.patches.length} patches applied)`;
2743
+ explanation += ` (${patchResult.appliedPatches}/${patchesToApply.length} patches applied)`;
2423
2744
  } else {
2424
2745
  continue; // Skip this file entirely if no patches worked
2425
2746
  }
2426
2747
  } else {
2427
2748
  modifiedContent = patchResult.modifiedContent;
2428
- console.log(`[Apply-First] All ${mod.patches.length} patches applied successfully to ${mod.filePath}`);
2749
+ console.log(`[Apply-First] All ${patchesToApply.length} patches applied successfully to ${mod.filePath}`);
2429
2750
  }
2430
2751
 
2431
2752
  // AST VALIDATION: Use Babel parser to catch actual syntax errors
@@ -84,6 +84,40 @@ interface VisionEditResponse {
84
84
 
85
85
  const DEBUG_LOG_FILE = "sonance-debug.log";
86
86
 
87
+ /**
88
+ * TWO-PHASE DESIGN ANALYSIS TYPES
89
+ * Phase 1 identifies problems, Phase 2 generates patches that reference those problems
90
+ */
91
+
92
+ /** Categories of visual/UX problems that can be identified */
93
+ type DesignProblemCategory = 'hierarchy' | 'spacing' | 'alignment' | 'feedback' | 'grouping';
94
+
95
+ /** A specific visual problem identified in Phase 1 analysis */
96
+ interface DesignProblem {
97
+ /** Unique identifier (e.g., "hierarchy-1", "spacing-2") */
98
+ id: string;
99
+ /** Problem category */
100
+ category: DesignProblemCategory;
101
+ /** Specific description of the issue */
102
+ description: string;
103
+ /** Visual impact severity */
104
+ severity: 'high' | 'medium' | 'low';
105
+ /** Hint for what to look for in the code */
106
+ codeHint?: string;
107
+ }
108
+
109
+ /** Extended patch that references a specific problem from Phase 1 */
110
+ interface DesignPatch {
111
+ /** Reference to the problem this patch fixes */
112
+ problemId: string;
113
+ /** Exact code to find */
114
+ search: string;
115
+ /** Replacement code */
116
+ replace: string;
117
+ /** Explanation of how this fixes the problem */
118
+ rationale: string;
119
+ }
120
+
87
121
  /**
88
122
  * Debug logging utility - writes to sonance-debug.log in project root
89
123
  * This helps diagnose issues with file discovery, validation, and revert
@@ -885,6 +919,139 @@ Be smart - use your knowledge of React patterns to identify what type of element
885
919
  }
886
920
  }
887
921
 
922
+ /**
923
+ * PHASE 1: Design Problem Analysis
924
+ *
925
+ * Analyzes the screenshot to identify specific visual/UX problems.
926
+ * This is a SEPARATE LLM call that ONLY identifies problems - no code changes.
927
+ * The problems are then passed to Phase 2 to generate targeted patches.
928
+ */
929
+ async function analyzeDesignProblems(
930
+ screenshot: string,
931
+ fileContent: string,
932
+ userPrompt: string,
933
+ apiKey: string
934
+ ): Promise<DesignProblem[]> {
935
+ const anthropic = new Anthropic({ apiKey });
936
+ const base64Data = screenshot.split(",")[1] || screenshot;
937
+
938
+ debugLog("Phase 1 Design Analysis: Starting visual problem identification", {
939
+ promptPreview: userPrompt?.substring(0, 50),
940
+ fileContentLength: fileContent.length
941
+ });
942
+
943
+ try {
944
+ const response = await anthropic.messages.create({
945
+ model: "claude-sonnet-4-20250514",
946
+ max_tokens: 2048,
947
+ messages: [
948
+ {
949
+ role: "user",
950
+ content: [
951
+ {
952
+ type: "image",
953
+ source: {
954
+ type: "base64",
955
+ media_type: "image/png",
956
+ data: base64Data,
957
+ },
958
+ },
959
+ {
960
+ type: "text",
961
+ text: `You are a senior UI/UX designer. Analyze this screenshot and identify SPECIFIC visual problems.
962
+
963
+ User's request: "${userPrompt}"
964
+
965
+ Examine the UI for issues in these categories:
966
+
967
+ 1. HIERARCHY: Is there a clear visual focus? Are elements competing for attention?
968
+ - Look for: competing headings, unclear primary actions, visual clutter
969
+
970
+ 2. SPACING: Are related elements grouped? Is there awkward empty space?
971
+ - Look for: disconnected labels/values, uneven gaps, cramped or overly spread content
972
+
973
+ 3. ALIGNMENT: Do elements align naturally? Are there visual disconnects?
974
+ - Look for: misaligned text, floating elements, broken visual flow
975
+
976
+ 4. FEEDBACK: Are progress indicators, states, and actions clear?
977
+ - Look for: weak progress visualization, unclear states, orphaned status indicators
978
+
979
+ 5. GROUPING: Are related items visually connected? Do containers make sense?
980
+ - Look for: related content that appears separate, missing visual boundaries
981
+
982
+ For each problem found, provide:
983
+ - id: unique identifier (e.g., "hierarchy-1", "spacing-2")
984
+ - category: one of [hierarchy, spacing, alignment, feedback, grouping]
985
+ - description: specific description of what's wrong
986
+ - severity: high/medium/low based on visual impact
987
+ - codeHint: what to look for in the code to fix it
988
+
989
+ Return ONLY a JSON array of problems. Example:
990
+ [
991
+ {
992
+ "id": "grouping-1",
993
+ "category": "grouping",
994
+ "description": "The 'Essence Quality' label and '0% COMPLETE' badge appear visually disconnected - they should be grouped together",
995
+ "severity": "high",
996
+ "codeHint": "Check if label and badge are in separate containers or using absolute positioning"
997
+ },
998
+ {
999
+ "id": "spacing-1",
1000
+ "category": "spacing",
1001
+ "description": "Too much vertical gap between the header section and the quality indicator",
1002
+ "severity": "medium",
1003
+ "codeHint": "Look for large margin or padding values like mt-6, mb-8, py-6"
1004
+ }
1005
+ ]
1006
+
1007
+ IMPORTANT:
1008
+ - Only identify problems that are actually visible in the screenshot
1009
+ - Be specific - vague problems lead to vague fixes
1010
+ - Maximum 5 problems - focus on the most impactful ones
1011
+ - If no significant problems are found, return an empty array []
1012
+ - Do NOT suggest code changes - only identify problems
1013
+
1014
+ Return ONLY the JSON array, no other text.`,
1015
+ },
1016
+ ],
1017
+ },
1018
+ ],
1019
+ });
1020
+
1021
+ const textBlock = response.content.find((block) => block.type === "text");
1022
+ if (!textBlock || textBlock.type !== "text") {
1023
+ debugLog("Phase 1 Design Analysis: No text response from LLM");
1024
+ return [];
1025
+ }
1026
+
1027
+ let jsonText = textBlock.text.trim();
1028
+
1029
+ // Extract JSON from code fences if present
1030
+ const jsonMatch = jsonText.match(/```(?:json)?\n?([\s\S]*?)\n?```/) ||
1031
+ jsonText.match(/\[[\s\S]*\]/);
1032
+ if (jsonMatch) {
1033
+ jsonText = jsonMatch[1] || jsonMatch[0];
1034
+ }
1035
+
1036
+ const problems: DesignProblem[] = JSON.parse(jsonText);
1037
+
1038
+ // Validate the structure
1039
+ const validatedProblems = problems.filter(p =>
1040
+ p.id && p.category && p.description && p.severity
1041
+ ).slice(0, 5); // Maximum 5 problems
1042
+
1043
+ debugLog("Phase 1 Design Analysis: Identified problems", {
1044
+ count: validatedProblems.length,
1045
+ problems: validatedProblems.map(p => ({ id: p.id, category: p.category, severity: p.severity }))
1046
+ });
1047
+
1048
+ return validatedProblems;
1049
+ } catch (e) {
1050
+ debugLog("Phase 1 Design Analysis: Failed to analyze", { error: String(e) });
1051
+ return [];
1052
+ }
1053
+ }
1054
+
888
1055
  /**
889
1056
  * Search candidate files for JSX code matching the focused element
890
1057
  * This helps identify which file actually contains the element the user clicked on
@@ -1338,65 +1505,166 @@ Output format:
1338
1505
  The "search" field must match the file EXACTLY (copy-paste from the code provided).`;
1339
1506
 
1340
1507
  /**
1341
- * DESIGN REASONING PROMPT
1342
- * Forces the LLM through a structured analysis before making design changes.
1343
- * This prevents over-engineering and ensures thoughtful, targeted modifications.
1508
+ * PHASE 2: Targeted Patch Generation Prompt
1509
+ *
1510
+ * This prompt is used AFTER Phase 1 has identified specific problems.
1511
+ * It requires each patch to reference a problem ID from Phase 1.
1344
1512
  */
1345
- const DESIGN_REASONING_PROMPT = `
1513
+ function buildPhase2DesignPrompt(problems: DesignProblem[]): string {
1514
+ if (problems.length === 0) {
1515
+ return ''; // No problems identified, skip design protocol
1516
+ }
1517
+
1518
+ const problemsJson = JSON.stringify(problems, null, 2);
1519
+
1520
+ return `
1346
1521
  ═══════════════════════════════════════════════════════════════════════════════
1347
- DESIGN REASONING PROTOCOL (Follow BEFORE generating patches)
1522
+ PHASE 2: TARGETED PATCH GENERATION
1348
1523
  ═══════════════════════════════════════════════════════════════════════════════
1349
1524
 
1350
- STEP 1 - VISUAL ANALYSIS:
1351
- Before making any changes, analyze the current UI in the screenshot:
1352
- • What type of component is this? (form wizard, modal, card, data table, sidebar, etc.)
1353
- • What is currently working well that should be PRESERVED?
1354
- What specific visual issues exist? (inconsistent spacing, unclear hierarchy, alignment problems, clutter)
1355
-
1356
- STEP 2 - MINIMAL CHANGE PLAN:
1357
- Identify the MINIMUM changes needed to address the user's request:
1358
- What 1-3 targeted fixes would solve the issue?
1359
- What should you explicitly NOT change?
1360
- Will these changes break existing functionality or visual balance?
1361
-
1362
- STEP 3 - GUARDRAILS CHECK:
1363
- Before implementing, verify you are following these rules:
1364
- • Am I preserving the existing layout structure? (flex stays flex, grid stays grid)
1365
- Am I keeping the existing color palette unless specifically asked to change it?
1366
- • Am I making incremental improvements, NOT a complete restructure?
1367
- Am I keeping the same visual hierarchy and element positions?
1368
-
1369
- STEP 4 - IMPLEMENT:
1370
- Only now generate patches for the targeted changes from Step 2.
1371
- Each patch should be small and focused - prefer multiple small patches over one large rewrite.
1525
+ A visual analysis identified these specific problems in the UI:
1526
+
1527
+ ${problemsJson}
1528
+
1529
+ YOUR TASK: Generate patches that fix ONLY these specific problems.
1530
+
1531
+ STRICT RULES:
1532
+ 1. Each patch MUST include a "problemId" field referencing one of the problems above
1533
+ 2. Do NOT make changes unrelated to the identified problems
1534
+ 3. Make the SMALLEST change that fixes each problem
1535
+ 4. Do NOT change layout systems (flex→grid) unless absolutely necessary to fix a problem
1536
+ 5. Preserve existing structure - adjust properties, don't restructure
1537
+
1538
+ For each problem, find the exact code that causes it and create a targeted fix.
1539
+
1540
+ OUTPUT FORMAT (note the problemId and rationale fields):
1541
+ {
1542
+ "modifications": [{
1543
+ "filePath": "components/Example.tsx",
1544
+ "patches": [
1545
+ {
1546
+ "problemId": "grouping-1",
1547
+ "search": "<Badge className=\"absolute right-4\">",
1548
+ "replace": "<Badge className=\"ml-2\">",
1549
+ "rationale": "Removes absolute positioning to group badge with label"
1550
+ },
1551
+ {
1552
+ "problemId": "spacing-1",
1553
+ "search": "className=\"mt-8\"",
1554
+ "replace": "className=\"mt-4\"",
1555
+ "rationale": "Reduces excessive vertical gap"
1556
+ }
1557
+ ]
1558
+ }]
1559
+ }
1560
+
1561
+ VALIDATION:
1562
+ - Patches without a valid problemId will be REJECTED
1563
+ - Patches that don't trace to an identified problem will be REJECTED
1564
+ - Each patch must have search, replace, problemId, and rationale fields
1372
1565
  `;
1566
+ }
1373
1567
 
1374
1568
  /**
1375
- * DESIGN GUARDRAILS
1376
- * Explicit constraints to prevent the LLM from over-engineering design changes.
1569
+ * DESIGN DECISION RULES for Phase 2
1377
1570
  */
1378
1571
  const DESIGN_GUARDRAILS = `
1379
1572
  ═══════════════════════════════════════════════════════════════════════════════
1380
- DESIGN GUARDRAILS (MUST FOLLOW)
1573
+ DESIGN CONSTRAINTS
1381
1574
  ═══════════════════════════════════════════════════════════════════════════════
1382
1575
 
1383
- DO NOT:
1384
- Convert layout systems (do NOT change flex to grid or vice versa)
1385
- Restructure component hierarchy unless explicitly asked
1386
- Change color schemes unless specifically requested
1387
- Move elements to different positions unless asked
1388
- Rewrite entire sections - make targeted changes only
1389
- • Add new wrapper elements or change semantic structure
1390
-
1391
- DO:
1392
- Focus on spacing, padding, and margins for "cleaner" requests
1393
- Improve typography (font size, weight, line-height) for readability
1394
- Fix alignment issues within the existing structure
1395
- Adjust visual hierarchy through subtle styling, not restructuring
1396
- • Make the smallest change that addresses the user's request
1397
- • Preserve what is already working well
1576
+ ALLOWED when fixing identified problems:
1577
+ Adjust spacing (margin, padding) - e.g., mt-4 mt-2
1578
+ Adjust alignment within containers - e.g., items-start → items-center
1579
+ Move elements within the same container to improve grouping
1580
+ Adjust typography - font size, weight, line-height
1581
+ Add/remove whitespace classes
1582
+
1583
+ ✗ FORBIDDEN (will cause rejection):
1584
+ Changing layout system (flex → grid, grid → flex)
1585
+ Restructuring component hierarchy
1586
+ Wrapping elements in new containers (unless specifically needed for a grouping problem)
1587
+ Removing or changing functionality
1588
+ Changes without a problemId reference
1589
+
1590
+ ═══════════════════════════════════════════════════════════════════════════════
1398
1591
  `;
1399
1592
 
1593
+ /**
1594
+ * Validate patches against identified problems
1595
+ * Rejects patches that don't reference a valid problem ID
1596
+ */
1597
+ function validateDesignPatches(
1598
+ patches: Array<{ search: string; replace: string; problemId?: string; rationale?: string }>,
1599
+ problems: DesignProblem[]
1600
+ ): { valid: Array<{ search: string; replace: string; problemId: string; rationale: string }>; rejected: Array<{ patch: unknown; reason: string }> } {
1601
+ const validProblemIds = new Set(problems.map(p => p.id));
1602
+ const valid: Array<{ search: string; replace: string; problemId: string; rationale: string }> = [];
1603
+ const rejected: Array<{ patch: unknown; reason: string }> = [];
1604
+
1605
+ for (const patch of patches) {
1606
+ // Check for required fields
1607
+ if (!patch.search || !patch.replace) {
1608
+ rejected.push({ patch, reason: "Missing search or replace field" });
1609
+ continue;
1610
+ }
1611
+
1612
+ // For design-heavy requests, require problemId
1613
+ if (!patch.problemId) {
1614
+ rejected.push({ patch, reason: "Missing problemId - patch doesn't reference an identified problem" });
1615
+ continue;
1616
+ }
1617
+
1618
+ // Validate problemId exists in the problems list
1619
+ if (!validProblemIds.has(patch.problemId)) {
1620
+ rejected.push({ patch, reason: `Invalid problemId "${patch.problemId}" - not in identified problems list` });
1621
+ continue;
1622
+ }
1623
+
1624
+ // Check for forbidden structural changes
1625
+ const searchLower = patch.search.toLowerCase();
1626
+ const replaceLower = patch.replace.toLowerCase();
1627
+
1628
+ // Detect flex → grid or grid → flex conversions
1629
+ const hadFlex = searchLower.includes('flex') || searchLower.includes('items-') || searchLower.includes('justify-');
1630
+ const hasGrid = replaceLower.includes('grid') && !searchLower.includes('grid');
1631
+ const hadGrid = searchLower.includes('grid');
1632
+ const hasFlex = (replaceLower.includes('flex') || replaceLower.includes('items-') || replaceLower.includes('justify-')) && !hadFlex;
1633
+
1634
+ if ((hadFlex && hasGrid) || (hadGrid && hasFlex)) {
1635
+ rejected.push({ patch, reason: "Forbidden: Changing layout system (flex ↔ grid)" });
1636
+ continue;
1637
+ }
1638
+
1639
+ // Detect space-y/space-x additions that fundamentally change layout
1640
+ const addedSpaceY = replaceLower.includes('space-y') && !searchLower.includes('space-y');
1641
+ const addedSpaceX = replaceLower.includes('space-x') && !searchLower.includes('space-x');
1642
+ const hadJustifyBetween = searchLower.includes('justify-between');
1643
+
1644
+ if ((addedSpaceY || addedSpaceX) && hadJustifyBetween) {
1645
+ rejected.push({ patch, reason: "Forbidden: Converting flex layout to stack layout" });
1646
+ continue;
1647
+ }
1648
+
1649
+ valid.push({
1650
+ search: patch.search,
1651
+ replace: patch.replace,
1652
+ problemId: patch.problemId,
1653
+ rationale: patch.rationale || 'No rationale provided'
1654
+ });
1655
+ }
1656
+
1657
+ if (rejected.length > 0) {
1658
+ debugLog("Design patch validation: Some patches rejected", {
1659
+ validCount: valid.length,
1660
+ rejectedCount: rejected.length,
1661
+ rejected: rejected.map(r => ({ reason: r.reason, searchPreview: String(r.patch).substring(0, 50) }))
1662
+ });
1663
+ }
1664
+
1665
+ return { valid, rejected };
1666
+ }
1667
+
1400
1668
  /**
1401
1669
  * Detect if a user request is design-heavy and needs the reasoning protocol.
1402
1670
  * Simple requests like "change button color to blue" don't need it.
@@ -1796,21 +2064,42 @@ User Request: "${userPrompt}"
1796
2064
 
1797
2065
  `;
1798
2066
 
1799
- // ========== DESIGN REASONING PROTOCOL ==========
1800
- // For design-heavy requests, add structured reasoning and guardrails
1801
- // This prevents over-engineering and ensures thoughtful changes
2067
+ // ========== TWO-PHASE DESIGN ANALYSIS ==========
2068
+ // Phase 1: Identify specific visual problems (separate LLM call)
2069
+ // Phase 2: Generate patches that reference those problems
1802
2070
  const isDesignRequest = isDesignHeavyRequest(userPrompt || '');
2071
+ let identifiedProblems: DesignProblem[] = [];
1803
2072
 
1804
- if (isDesignRequest) {
1805
- debugLog("Design-heavy request detected, adding reasoning protocol", {
2073
+ if (isDesignRequest && screenshot) {
2074
+ debugLog("Design-heavy request detected, running Phase 1 analysis", {
1806
2075
  prompt: userPrompt?.substring(0, 50),
1807
2076
  triggerKeywords: ['redesign', 'better', 'improve', 'cleaner', 'layout', 'modernize']
1808
2077
  .filter(k => userPrompt?.toLowerCase().includes(k))
1809
2078
  });
1810
2079
 
1811
- textContent += DESIGN_REASONING_PROMPT;
1812
- textContent += DESIGN_GUARDRAILS;
1813
- textContent += '\n';
2080
+ // Phase 1: Analyze the screenshot to identify specific problems
2081
+ // This is a SEPARATE LLM call that only identifies problems
2082
+ const fileContentForAnalysis = recommendedFileContent?.content || pageContext.pageContent || '';
2083
+ identifiedProblems = await analyzeDesignProblems(
2084
+ screenshot,
2085
+ fileContentForAnalysis,
2086
+ userPrompt || '',
2087
+ apiKey
2088
+ );
2089
+
2090
+ debugLog("Phase 1 complete: Problems identified", {
2091
+ problemCount: identifiedProblems.length,
2092
+ problems: identifiedProblems.map(p => `${p.id}: ${p.description.substring(0, 50)}`)
2093
+ });
2094
+
2095
+ // Add Phase 2 prompt with the identified problems
2096
+ if (identifiedProblems.length > 0) {
2097
+ textContent += buildPhase2DesignPrompt(identifiedProblems);
2098
+ textContent += DESIGN_GUARDRAILS;
2099
+ textContent += '\n';
2100
+ } else {
2101
+ debugLog("No problems identified - proceeding with standard edit");
2102
+ }
1814
2103
  }
1815
2104
 
1816
2105
  // ========== TARGET COMPONENT ONLY (with line numbers) ==========
@@ -2339,9 +2628,46 @@ This is better than generating patches with made-up code.`,
2339
2628
  // New patch-based approach
2340
2629
  console.log(`[Vision Mode] Applying ${mod.patches.length} patches to ${mod.filePath}`);
2341
2630
 
2631
+ // DESIGN VALIDATION: For design-heavy requests, validate patches have problemIds
2632
+ let patchesToApply = mod.patches;
2633
+ if (isDesignRequest && identifiedProblems.length > 0) {
2634
+ const validationResult = validateDesignPatches(mod.patches, identifiedProblems);
2635
+
2636
+ if (validationResult.rejected.length > 0) {
2637
+ debugLog("Design patch validation rejected some patches", {
2638
+ filePath: mod.filePath,
2639
+ validCount: validationResult.valid.length,
2640
+ rejectedCount: validationResult.rejected.length,
2641
+ rejectedReasons: validationResult.rejected.map(r => r.reason)
2642
+ });
2643
+ console.warn(`[Vision Mode] ${validationResult.rejected.length} patches rejected for missing/invalid problemId`);
2644
+ }
2645
+
2646
+ // Only use validated patches
2647
+ if (validationResult.valid.length === 0) {
2648
+ patchErrors.push(`${mod.filePath}: All patches were rejected - they don't reference identified problems`);
2649
+ continue;
2650
+ }
2651
+
2652
+ // Log which problems are being fixed
2653
+ const problemsBeingFixed = [...new Set(validationResult.valid.map(p => p.problemId))];
2654
+ debugLog("Design patches validated", {
2655
+ filePath: mod.filePath,
2656
+ problemsBeingFixed,
2657
+ patchCount: validationResult.valid.length
2658
+ });
2659
+
2660
+ // Map validated patches to Patch interface (rationale -> explanation)
2661
+ patchesToApply = validationResult.valid.map(p => ({
2662
+ search: p.search,
2663
+ replace: p.replace,
2664
+ explanation: `[${p.problemId}] ${p.rationale}`
2665
+ }));
2666
+ }
2667
+
2342
2668
  // PRE-VALIDATION: Check if all search strings exist in the file BEFORE applying
2343
2669
  const preValidationErrors: string[] = [];
2344
- for (const patch of mod.patches) {
2670
+ for (const patch of patchesToApply) {
2345
2671
  const normalizedSearch = patch.search.replace(/\\n/g, "\n");
2346
2672
  if (!originalContent.includes(normalizedSearch)) {
2347
2673
  // Try fuzzy match as fallback
@@ -2380,7 +2706,7 @@ This is better than generating patches with made-up code.`,
2380
2706
  continue;
2381
2707
  }
2382
2708
 
2383
- const patchResult = applyPatches(originalContent, mod.patches);
2709
+ const patchResult = applyPatches(originalContent, patchesToApply);
2384
2710
 
2385
2711
  if (!patchResult.success) {
2386
2712
  const failedMessages = patchResult.failedPatches.map(
@@ -2390,15 +2716,15 @@ This is better than generating patches with made-up code.`,
2390
2716
 
2391
2717
  // If some patches succeeded, use partial result
2392
2718
  if (patchResult.appliedPatches > 0) {
2393
- console.warn(`[Vision Mode] ${patchResult.appliedPatches}/${mod.patches.length} patches applied to ${mod.filePath}`);
2719
+ console.warn(`[Vision Mode] ${patchResult.appliedPatches}/${patchesToApply.length} patches applied to ${mod.filePath}`);
2394
2720
  modifiedContent = patchResult.modifiedContent;
2395
- explanation += ` (${patchResult.appliedPatches}/${mod.patches.length} patches applied)`;
2721
+ explanation += ` (${patchResult.appliedPatches}/${patchesToApply.length} patches applied)`;
2396
2722
  } else {
2397
2723
  continue; // Skip this file entirely if no patches worked
2398
2724
  }
2399
2725
  } else {
2400
2726
  modifiedContent = patchResult.modifiedContent;
2401
- console.log(`[Vision Mode] All ${mod.patches.length} patches applied successfully to ${mod.filePath}`);
2727
+ console.log(`[Vision Mode] All ${patchesToApply.length} patches applied successfully to ${mod.filePath}`);
2402
2728
  }
2403
2729
 
2404
2730
  // AST VALIDATION: Use Babel parser to catch actual syntax errors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.98",
3
+ "version": "1.3.100",
4
4
  "description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",