sonance-brand-mcp 1.3.57 → 1.3.59

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.
@@ -167,6 +167,73 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
167
167
  }
168
168
  }
169
169
 
170
+ /**
171
+ * Search candidate files for JSX code matching the focused element
172
+ * This helps identify which file actually contains the element the user clicked on
173
+ */
174
+ function findFilesContainingElement(
175
+ focusedElements: VisionFocusedElement[] | undefined,
176
+ candidateFiles: { path: string; content: string }[]
177
+ ): { path: string; score: number; matches: string[] }[] {
178
+ if (!focusedElements || focusedElements.length === 0) {
179
+ return [];
180
+ }
181
+
182
+ const results: { path: string; score: number; matches: string[] }[] = [];
183
+
184
+ for (const file of candidateFiles) {
185
+ let score = 0;
186
+ const matches: string[] = [];
187
+ const content = file.content.toLowerCase();
188
+
189
+ for (const el of focusedElements) {
190
+ // Extract element type from the name (e.g., "button #123" -> "button")
191
+ const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
192
+ const componentType = el.type.toLowerCase();
193
+
194
+ // Search for JSX patterns
195
+ // Check for HTML tags like <button, <div, etc.
196
+ if (content.includes(`<${elementType}`)) {
197
+ score += 10;
198
+ matches.push(`<${elementType}>`);
199
+ }
200
+
201
+ // Check for React component like <Button, <Card, etc.
202
+ const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
203
+ if (file.content.includes(`<${capitalizedType}`)) {
204
+ score += 15;
205
+ matches.push(`<${capitalizedType}>`);
206
+ }
207
+
208
+ // Check for component imports
209
+ if (content.includes(`import`) && content.includes(componentType)) {
210
+ score += 5;
211
+ }
212
+
213
+ // Check for event handlers commonly associated with buttons
214
+ if (elementType === "button" || componentType === "button") {
215
+ if (content.includes("onclick") || content.includes("onpress")) {
216
+ score += 3;
217
+ }
218
+ }
219
+
220
+ // Check for the component being defined in this file
221
+ if (file.content.includes(`function ${capitalizedType}`) ||
222
+ file.content.includes(`const ${capitalizedType}`)) {
223
+ score += 20;
224
+ matches.push(`defines ${capitalizedType}`);
225
+ }
226
+ }
227
+
228
+ if (score > 0) {
229
+ results.push({ path: file.path, score, matches });
230
+ }
231
+ }
232
+
233
+ // Sort by score descending
234
+ return results.sort((a, b) => b.score - a.score);
235
+ }
236
+
170
237
  /**
171
238
  * Phase 3: Ask LLM to select the best file from actual file list
172
239
  * This replaces guessing - the LLM sees the real filenames and picks one
@@ -175,7 +242,8 @@ async function selectBestFileFromList(
175
242
  screenshot: string,
176
243
  userPrompt: string,
177
244
  candidateFiles: string[],
178
- apiKey: string
245
+ apiKey: string,
246
+ focusedElementHints?: { path: string; score: number; matches: string[] }[]
179
247
  ): Promise<string | null> {
180
248
  if (candidateFiles.length === 0) return null;
181
249
 
@@ -199,7 +267,12 @@ async function selectBestFileFromList(
199
267
 
200
268
  Here are the actual component files found in this codebase:
201
269
  ${candidateFiles.map((f, i) => `${i + 1}. ${f}`).join('\n')}
270
+ ${focusedElementHints && focusedElementHints.length > 0 ? `
271
+ FOCUSED ELEMENT ANALYSIS - Files that contain the element the user clicked on:
272
+ ${focusedElementHints.slice(0, 3).map(h => `- ${h.path} (confidence: ${h.score}, found: ${h.matches.join(", ")})`).join('\n')}
202
273
 
274
+ IMPORTANT: Prefer files from the FOCUSED ELEMENT ANALYSIS above, as they contain the actual element the user wants to modify.
275
+ ` : ''}
203
276
  Looking at the screenshot, which file MOST LIKELY contains the UI elements the user wants to modify?
204
277
 
205
278
  IMPORTANT: Return ONLY the exact file path from the list above (e.g., "components/ProcessCatalogue/ProcessDetailPanel.tsx").
@@ -434,104 +507,50 @@ function searchFilesSmart(
434
507
  return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
435
508
  }
436
509
 
437
- const VISION_SYSTEM_PROMPT = `You are an expert React/TypeScript developer with vision capabilities. You can see screenshots and modify code.
438
-
439
- ═══════════════════════════════════════════════════════════════════════════════
440
- UNDERSTAND THE USER'S REQUEST NATURALLY
441
- ═══════════════════════════════════════════════════════════════════════════════
442
-
443
- Read the user's request and do exactly what they ask. You know React, TypeScript, Tailwind CSS, and JSX - use that knowledge naturally.
444
-
445
- - If they say "add X", ADD X to the code (don't replace or remove anything else)
446
- - If they say "change X to Y", find X and change it to Y
447
- - If they say "make X bigger/smaller/different color", adjust the relevant properties
448
- - If they say "remove X", remove X
449
- - If they say "wrap X with Y", add Y as a parent around X
450
-
451
- For any change:
452
- 1. Read the code context provided
453
- 2. Understand what the user wants
454
- 3. Generate patches that accomplish exactly that
455
- 4. If adding a component/icon requires an import, include a patch for the import too
456
-
457
- Don't overthink - just make the change the user requested.
458
-
459
- ═══════════════════════════════════════════════════════════════════════════════
460
- PATCH FORMAT
461
- ═══════════════════════════════════════════════════════════════════════════════
462
-
463
- Return search/replace patches (NOT full files). The system applies your patches to the original.
464
-
465
- **Patch Rules:**
466
- - "search" must match the original code EXACTLY (including whitespace/indentation)
467
- - "replace" contains your modified version
468
- - Include 2-4 lines of context in "search" to make it unique
469
- - You may ONLY edit files provided in the PAGE CONTEXT section
470
- - CRITICAL: NEVER invent or guess code. Your "search" string MUST be copied EXACTLY from the provided file content. If you cannot find the exact code to modify, return an empty modifications array.
471
- - If the file content appears truncated, only modify code that is visible in the provided content.
472
-
473
- ═══════════════════════════════════════════════════════════════════════════════
474
- SONANCE BRAND COLOR SYSTEM
475
- ═══════════════════════════════════════════════════════════════════════════════
476
-
477
- **Core Colors:**
478
- - Primary (Charcoal): #333F48 - main text, dark backgrounds
479
- - Accent (Cyan "The Beam"): #00D3C8 - highlights, interactive elements, CTAs
480
- - Success: Green tones - positive states, confirmations
481
- - Warning: Amber/Orange tones - caution states, alerts
482
- - Destructive: Red tones - errors, delete actions
483
-
484
- **CRITICAL CONTRAST RULES:**
485
- When using colored backgrounds, ALWAYS use high-contrast text:
486
-
487
- | Background | Use This Text | NEVER Use |
488
- |-------------------|------------------------------------|-----------------------|
489
- | bg-accent | text-white | text-accent-foreground |
490
- | bg-primary | text-primary-foreground, text-white | text-primary |
491
- | bg-success | text-white | text-success |
492
- | bg-warning | text-white | text-warning |
493
- | bg-destructive | text-white | text-destructive |
494
-
495
- **SEMANTIC TOKEN PATTERNS:**
496
- - text-{color} = the color itself (use on NEUTRAL backgrounds like white/gray)
497
- - text-{color}-foreground = INTENDED for text on {color} backgrounds, but MAY have contrast issues
498
- - bg-{color} = background in that color
499
- - WHEN IN DOUBT: Use text-white on any colored background for guaranteed contrast
500
-
501
- **BUTTON PATTERNS (Sonance Standard):**
502
- - Primary CTA: bg-primary text-primary-foreground
503
- - Accent/Highlight: bg-accent text-white (NOT text-accent-foreground)
504
- - Success: bg-success text-white
505
- - Warning: bg-warning text-white
506
- - Destructive: bg-destructive text-white
507
- - Outlined: border-border bg-transparent text-foreground
508
-
509
- **COMMON MISTAKES TO AVOID:**
510
- - WRONG: bg-accent text-accent-foreground (accent-foreground is often dark = invisible text)
511
- - RIGHT: bg-accent text-white (white text on cyan = visible)
512
- - WRONG: bg-primary text-primary (same color = invisible)
513
- - RIGHT: bg-primary text-primary-foreground OR text-white
514
-
515
- **RESPONSE FORMAT:**
516
- CRITICAL: Return ONLY the JSON object below. Do NOT include any text, explanation, or thinking before or after the JSON. No preamble. No "Looking at the screenshot..." No markdown code blocks. Just raw JSON:
510
+ const VISION_SYSTEM_PROMPT = `You are a code editor. Make ONLY the change the user requested.
511
+
512
+ RULES:
513
+ 1. Make the SMALLEST possible change to accomplish the request
514
+ 2. Do NOT refactor, rebrand, or "improve" anything else
515
+ 3. Do NOT change import statements unless explicitly required
516
+ 4. Do NOT change component libraries (e.g., heroui to shadcn)
517
+ 5. If fixing a color/visibility issue, change ONLY that element's classes
518
+ 6. Your patch should typically be 1-5 lines, not 50+
519
+ 7. NEVER invent or guess code - your "search" string MUST match the file EXACTLY
520
+ 8. NEVER change data mappings (icon names, keys, enum values) unless user explicitly provides the new values
521
+ 9. If you don't know what values exist in a database or data source, DO NOT guess - ask for clarification
522
+
523
+ CRITICAL - ELEMENT VERIFICATION:
524
+ - When a focused element is mentioned, SEARCH the provided file content for that EXACT element
525
+ - Look for the actual JSX/HTML code: <button, <Button, <div, className, onClick, etc.
526
+ - If you cannot find the element in the TARGET COMPONENT section, it may be in a child component
527
+ - NEVER guess what element code looks like - find it in the file or report "element not found"
528
+ - If the element is not in the provided file, return: {"modifications": [], "explanation": "The focused element appears to be in a child component, not in [filename]"}
529
+
530
+ CRITICAL - DATA INTEGRITY:
531
+ - If code references database values (like icon_name, type, status), DO NOT change the mapping keys
532
+ - You cannot see database content - only change STRUCTURE, not DATA MAPPINGS
533
+ - Example: If you see iconMap["EyeOff"] = SomeIcon, do NOT change "EyeOff" to something else
534
+ - If user wants different icons, they must tell you the EXACT icon names they want
535
+
536
+ PATCH FORMAT:
537
+ Return ONLY raw JSON (no markdown, no preamble):
517
538
  {
518
- "reasoning": "What you understood from the request and your plan",
519
- "modifications": [
520
- {
521
- "filePath": "path/to/file.tsx",
522
- "patches": [
523
- {
524
- "search": "exact code to find",
525
- "replace": "the replacement code",
526
- "explanation": "what this patch does"
527
- }
528
- ],
529
- "previewCSS": "optional CSS for live preview"
530
- }
531
- ],
539
+ "reasoning": "brief explanation",
540
+ "modifications": [{
541
+ "filePath": "path/to/file.tsx",
542
+ "patches": [{
543
+ "search": "exact original code (copy from provided file)",
544
+ "replace": "minimal change",
545
+ "explanation": "what this does"
546
+ }],
547
+ "previewCSS": "optional CSS for live preview"
548
+ }],
532
549
  "aggregatedPreviewCSS": "combined CSS for all changes",
533
- "explanation": "summary of changes made"
534
- }`;
550
+ "explanation": "summary"
551
+ }
552
+
553
+ If you cannot find the exact code to modify, OR if you would need to guess data values, return empty modifications array with explanation.`;
535
554
 
536
555
  export async function POST(request: Request) {
537
556
  // Only allow in development
@@ -636,6 +655,22 @@ export async function POST(request: Request) {
636
655
  const searchResults = searchFilesSmart(analysis, projectRoot, 10);
637
656
  smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
638
657
 
658
+ // PHASE 2.5: Find which files contain the focused element
659
+ const focusedElementHints = findFilesContainingElement(
660
+ focusedElements,
661
+ searchResults.map(r => ({ path: r.path, content: r.content }))
662
+ );
663
+
664
+ if (focusedElementHints.length > 0) {
665
+ debugLog("Focused element search results", {
666
+ topMatches: focusedElementHints.slice(0, 3).map(h => ({
667
+ path: h.path,
668
+ score: h.score,
669
+ matches: h.matches
670
+ }))
671
+ });
672
+ }
673
+
639
674
  // PHASE 3: Ask LLM to pick the best file from actual file list
640
675
  if (searchResults.length > 0) {
641
676
  const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
@@ -643,7 +678,8 @@ export async function POST(request: Request) {
643
678
  screenshot,
644
679
  userPrompt,
645
680
  candidateFiles,
646
- apiKey
681
+ apiKey,
682
+ focusedElementHints
647
683
  );
648
684
 
649
685
  if (selectedFile) {
@@ -708,141 +744,6 @@ export async function POST(request: Request) {
708
744
  });
709
745
  }
710
746
 
711
- // ========== PHASE A: VISUAL PROBLEM ANALYSIS ==========
712
- // Before generating patches, analyze WHAT is visually wrong
713
- // This forces the LLM to articulate the problem before trying to fix it
714
- interface VisualAnalysis {
715
- element: string;
716
- elementText: string; // The visible text content of the element (for finding in code)
717
- problem: string;
718
- changeType: "styling" | "structural" | "content" | "other";
719
- currentClasses?: string; // For styling changes: what classes appear to be applied
720
- classChange?: {
721
- remove?: string[]; // Classes to remove
722
- add?: string[]; // Classes to add
723
- };
724
- structuralChange?: string; // For structural changes: what to add/remove/wrap
725
- solution: string;
726
- confidence: "high" | "medium" | "low";
727
- }
728
-
729
- let visualAnalysis: VisualAnalysis | null = null;
730
-
731
- if (screenshot && userPrompt) {
732
- debugLog("Phase A: Starting visual problem analysis");
733
-
734
- const analysisClient = new Anthropic({ apiKey });
735
-
736
- try {
737
- const analysisResponse = await analysisClient.messages.create({
738
- model: "claude-sonnet-4-20250514",
739
- max_tokens: 1024,
740
- messages: [
741
- {
742
- role: "user",
743
- content: [
744
- {
745
- type: "image",
746
- source: {
747
- type: "base64",
748
- media_type: "image/png",
749
- data: base64Data,
750
- },
751
- },
752
- {
753
- type: "text",
754
- text: `You are analyzing a UI screenshot to understand what the user wants changed.
755
-
756
- User request: "${userPrompt}"
757
-
758
- ${focusedElements && focusedElements.length > 0 ? `User clicked on these elements:
759
- ${focusedElements.map((el) => `- ${el.name} (${el.type}) at position (${el.coordinates.x}, ${el.coordinates.y})`).join("\n")}
760
- ` : ""}
761
-
762
- Analyze the request and determine:
763
- 1. ELEMENT: Which specific UI element needs to change? (describe location and appearance)
764
- 2. ELEMENT TEXT: What text is visible inside this element? (EXACT text for finding in code)
765
- 3. PROBLEM: What is wrong or what does the user want changed?
766
- 4. CHANGE TYPE: Is this a "styling" (colors/size/spacing), "structural" (add/remove elements), "content" (text changes), or "other" change?
767
- 5. For STYLING changes: What Tailwind classes should be REMOVED and what should be ADDED?
768
- 6. For STRUCTURAL changes: What element should be added, removed, or wrapped?
769
- 7. SOLUTION: Describe the complete fix
770
- 8. CONFIDENCE: high/medium/low
771
-
772
- IMPORTANT for styling fixes:
773
- - If text is invisible on a background, typically REMOVE the problematic text color class and ADD a contrasting one
774
- - Common fix for invisible text on colored backgrounds: REMOVE "text-accent-foreground" or similar, ADD "text-white" or "text-black"
775
- - Be SPECIFIC about exact class names
776
-
777
- Return ONLY valid JSON:
778
- {
779
- "element": "description of the UI element",
780
- "elementText": "exact visible text in the element",
781
- "problem": "what is wrong",
782
- "changeType": "styling",
783
- "currentClasses": "classes that appear to be applied (best guess)",
784
- "classChange": {
785
- "remove": ["text-accent-foreground"],
786
- "add": ["text-white"]
787
- },
788
- "solution": "complete description of the fix",
789
- "confidence": "high"
790
- }
791
-
792
- For structural changes:
793
- {
794
- "element": "description of the UI element",
795
- "elementText": "text near the element",
796
- "problem": "what is wrong",
797
- "changeType": "structural",
798
- "structuralChange": "Add a new button after the existing one",
799
- "solution": "complete description of the fix",
800
- "confidence": "high"
801
- }`,
802
- },
803
- ],
804
- },
805
- ],
806
- });
807
-
808
- const analysisText = analysisResponse.content.find((block) => block.type === "text");
809
- if (analysisText && analysisText.type === "text") {
810
- // Parse the JSON response
811
- let jsonText = analysisText.text.trim();
812
- // Extract JSON if wrapped in code blocks
813
- const jsonMatch = jsonText.match(/```(?:json)?\s*([\s\S]*?)\s*```/) || jsonText.match(/(\{[\s\S]*\})/);
814
- if (jsonMatch) {
815
- jsonText = jsonMatch[1];
816
- }
817
-
818
- try {
819
- visualAnalysis = JSON.parse(jsonText) as VisualAnalysis;
820
- debugLog("Phase A: Visual problem analysis complete", visualAnalysis);
821
-
822
- // If low confidence, return early with clarification request
823
- if (visualAnalysis.confidence === "low") {
824
- debugLog("Phase A: Low confidence - requesting clarification");
825
- return NextResponse.json({
826
- success: false,
827
- needsClarification: true,
828
- analysis: visualAnalysis,
829
- message: `I can see "${visualAnalysis.element}" but I'm not certain about the problem: "${visualAnalysis.problem}". Can you be more specific about what needs to change?`,
830
- });
831
- }
832
- } catch (parseError) {
833
- debugLog("Phase A: Failed to parse analysis response", {
834
- error: String(parseError),
835
- response: jsonText.substring(0, 500)
836
- });
837
- // Continue without analysis if parsing fails
838
- }
839
- }
840
- } catch (analysisError) {
841
- debugLog("Phase A: Analysis call failed", { error: String(analysisError) });
842
- // Continue without analysis - fall back to existing behavior
843
- }
844
- }
845
-
846
747
  // ========== SMART CONTEXT BUDGETING ==========
847
748
  // Claude can handle 200k tokens (~800k chars), so we can safely include large files
848
749
  // Priority: Recommended file (NEVER truncate) > Page file (limited) > Other components (dynamic)
@@ -879,58 +780,6 @@ ${focusedElements.map((el) => `- ${el.name} (${el.type}) at (${el.coordinates.x}
879
780
  `;
880
781
  }
881
782
 
882
- // ========== VISUAL PROBLEM ANALYSIS (from Phase A) ==========
883
- if (visualAnalysis) {
884
- let analysisInstructions = `═══════════════════════════════════════════════════════════════════════════════
885
- 🔍 VISUAL PROBLEM ANALYSIS (I analyzed the screenshot first)
886
- ═══════════════════════════════════════════════════════════════════════════════
887
-
888
- **Element:** ${visualAnalysis.element}
889
- **Element Text:** "${visualAnalysis.elementText || "unknown"}"
890
- **Problem:** ${visualAnalysis.problem}
891
- **Change Type:** ${visualAnalysis.changeType}
892
- **Solution:** ${visualAnalysis.solution}
893
- **Confidence:** ${visualAnalysis.confidence}
894
-
895
- `;
896
-
897
- // Add specific instructions based on change type
898
- if (visualAnalysis.changeType === "styling" && visualAnalysis.classChange) {
899
- analysisInstructions += `🎯 EXACT CLASS CHANGES REQUIRED:
900
- `;
901
- if (visualAnalysis.classChange.remove && visualAnalysis.classChange.remove.length > 0) {
902
- analysisInstructions += ` REMOVE these classes: ${visualAnalysis.classChange.remove.join(", ")}
903
- `;
904
- }
905
- if (visualAnalysis.classChange.add && visualAnalysis.classChange.add.length > 0) {
906
- analysisInstructions += ` ADD these classes: ${visualAnalysis.classChange.add.join(", ")}
907
- `;
908
- }
909
- analysisInstructions += `
910
- ⚠️ YOUR PATCH MUST:
911
- 1. Find the element containing "${visualAnalysis.elementText || visualAnalysis.element}"
912
- 2. In its className, ${visualAnalysis.classChange.remove?.length ? `REMOVE: ${visualAnalysis.classChange.remove.join(", ")}` : ""}
913
- 3. ${visualAnalysis.classChange.add?.length ? `ADD: ${visualAnalysis.classChange.add.join(", ")}` : ""}
914
- 4. Do NOT add unrelated changes like font-weight or hover states unless specifically requested
915
-
916
- `;
917
- } else if (visualAnalysis.changeType === "structural" && visualAnalysis.structuralChange) {
918
- analysisInstructions += `🔧 STRUCTURAL CHANGE REQUIRED:
919
- ${visualAnalysis.structuralChange}
920
-
921
- ⚠️ YOUR PATCH MUST implement this structural change exactly as described.
922
-
923
- `;
924
- } else {
925
- analysisInstructions += `⚠️ IMPORTANT: Your patches MUST implement the fix described above.
926
- Find the code that renders "${visualAnalysis.element}" and apply: ${visualAnalysis.solution}
927
-
928
- `;
929
- }
930
-
931
- textContent += analysisInstructions;
932
- }
933
-
934
783
  // ========== TARGET COMPONENT (RECOMMENDED FILE) - SHOWN FIRST ==========
935
784
  if (recommendedFileContent) {
936
785
  // Never truncate the recommended file - AI needs full context to avoid hallucination
@@ -995,17 +844,27 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
995
844
  }
996
845
  }
997
846
 
998
- // ========== THEME DISCOVERY ==========
847
+ // ========== THEME DISCOVERY (REFERENCE ONLY) ==========
999
848
  // Dynamically discover theme tokens from the target codebase
849
+ // This is marked as REFERENCE ONLY so the LLM doesn't use it to justify extra changes
1000
850
  const discoveredTheme = await discoverTheme(projectRoot);
1001
851
  const themeContext = formatThemeForPrompt(discoveredTheme);
1002
852
 
1003
853
  if (discoveredTheme.discoveredFiles.length > 0) {
1004
854
  textContent += `
1005
855
  ═══════════════════════════════════════════════════════════════════════════════
1006
- ${themeContext}
856
+ REFERENCE ONLY (do not use this to justify additional changes)
1007
857
  ═══════════════════════════════════════════════════════════════════════════════
1008
858
 
859
+ If you need to pick a color for a VISIBILITY fix, these are safe choices:
860
+ - bg-accent text-white (cyan button with white text)
861
+ - bg-primary text-white (charcoal button with white text)
862
+ - bg-success text-white (green button with white text)
863
+ - bg-destructive text-white (red button with white text)
864
+
865
+ But ONLY use these if the user is asking for a color/visibility change.
866
+ Do NOT rebrand or change other elements to match.
867
+
1009
868
  `;
1010
869
  debugLog("Theme discovery complete", {
1011
870
  filesFound: discoveredTheme.discoveredFiles,