sonance-brand-mcp 1.3.63 → 1.3.64
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.
|
@@ -29,6 +29,10 @@ interface VisionFocusedElement {
|
|
|
29
29
|
height: number;
|
|
30
30
|
};
|
|
31
31
|
description?: string;
|
|
32
|
+
/** The actual text content of the element (e.g., "REFRESH", "ADD PROCESS") */
|
|
33
|
+
textContent?: string;
|
|
34
|
+
/** The className string of the element for pattern matching */
|
|
35
|
+
className?: string;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
interface VisionFileModification {
|
|
@@ -176,10 +180,14 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
176
180
|
* This helps identify which file actually contains the element the user clicked on
|
|
177
181
|
*
|
|
178
182
|
* CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
|
|
183
|
+
*
|
|
184
|
+
* @param phase2aMatches - File paths that Phase 2a identified by component name matching visible text
|
|
185
|
+
* These get a MASSIVE bonus because they're contextually relevant
|
|
179
186
|
*/
|
|
180
187
|
function findFilesContainingElement(
|
|
181
188
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
182
|
-
candidateFiles: { path: string; content: string }[]
|
|
189
|
+
candidateFiles: { path: string; content: string }[],
|
|
190
|
+
phase2aMatches: string[] = [] // Files that Phase 2a identified by component name
|
|
183
191
|
): { path: string; score: number; matches: string[] }[] {
|
|
184
192
|
if (!focusedElements || focusedElements.length === 0) {
|
|
185
193
|
return [];
|
|
@@ -191,7 +199,13 @@ function findFilesContainingElement(
|
|
|
191
199
|
let score = 0;
|
|
192
200
|
const matches: string[] = [];
|
|
193
201
|
const content = file.content;
|
|
194
|
-
|
|
202
|
+
|
|
203
|
+
// HIGHEST VALUE: Phase 2a identified this file by matching component name to visible text
|
|
204
|
+
// This means the LLM saw "ProcessRow" in the screenshot and this file is ProcessRow.tsx
|
|
205
|
+
if (phase2aMatches.includes(file.path)) {
|
|
206
|
+
score += 200;
|
|
207
|
+
matches.push('Phase2a component match');
|
|
208
|
+
}
|
|
195
209
|
|
|
196
210
|
for (const el of focusedElements) {
|
|
197
211
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
@@ -216,28 +230,42 @@ function findFilesContainingElement(
|
|
|
216
230
|
matches.push(`defines ${capitalizedType}`);
|
|
217
231
|
}
|
|
218
232
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
// NOTE: We intentionally DO NOT count buttons, onClick handlers, or JSX tags
|
|
234
|
+
// because files with MORE of these are often "kitchen sink" components,
|
|
235
|
+
// not the focused component the user is looking at.
|
|
236
|
+
// Instead, we rely on Phase 2a matches and textContent matching.
|
|
237
|
+
|
|
238
|
+
// VERY HIGH VALUE: Exact text content match (e.g., button says "REFRESH")
|
|
239
|
+
// This is THE file if it contains the exact text the user clicked on
|
|
240
|
+
if (el.textContent && el.textContent.length >= 2) {
|
|
241
|
+
// Search for the text in JSX patterns like >REFRESH< or {`REFRESH`} or {"REFRESH"}
|
|
242
|
+
const textPatterns = [
|
|
243
|
+
`>${el.textContent}<`, // JSX text content
|
|
244
|
+
`"${el.textContent}"`, // String literal
|
|
245
|
+
`'${el.textContent}'`, // Single-quoted string
|
|
246
|
+
`\`${el.textContent}\``, // Template literal
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
for (const pattern of textPatterns) {
|
|
250
|
+
if (content.includes(pattern)) {
|
|
251
|
+
score += 150; // Very high - exact match!
|
|
252
|
+
matches.push(`contains "${el.textContent.substring(0, 20)}"`);
|
|
253
|
+
break; // Only add once per element
|
|
254
|
+
}
|
|
255
|
+
}
|
|
233
256
|
}
|
|
234
257
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
// MEDIUM VALUE: className pattern match
|
|
259
|
+
// If the file contains the same className string, it likely defines this element
|
|
260
|
+
if (el.className && el.className.length >= 10) {
|
|
261
|
+
// Extract the first few unique class names (not all to avoid noise)
|
|
262
|
+
const classNames = el.className.split(/\s+/).filter(c => c.length > 3).slice(0, 5);
|
|
263
|
+
for (const cls of classNames) {
|
|
264
|
+
if (content.includes(cls)) {
|
|
265
|
+
score += 25;
|
|
266
|
+
matches.push(`has class "${cls.substring(0, 20)}"`);
|
|
267
|
+
break; // Only add once per element
|
|
268
|
+
}
|
|
241
269
|
}
|
|
242
270
|
}
|
|
243
271
|
|
|
@@ -412,7 +440,7 @@ function searchFilesSmart(
|
|
|
412
440
|
analysis: ScreenshotAnalysis,
|
|
413
441
|
projectRoot: string,
|
|
414
442
|
maxFiles: number = 10
|
|
415
|
-
): { path: string; content: string; score: number }[] {
|
|
443
|
+
): { path: string; content: string; score: number; filenameMatch: boolean }[] {
|
|
416
444
|
const { visibleText, componentNames, codePatterns } = analysis;
|
|
417
445
|
|
|
418
446
|
// Combine all search terms
|
|
@@ -534,7 +562,7 @@ function searchFilesSmart(
|
|
|
534
562
|
}))
|
|
535
563
|
});
|
|
536
564
|
|
|
537
|
-
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
|
|
565
|
+
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score, filenameMatch: r.filenameMatch }));
|
|
538
566
|
}
|
|
539
567
|
|
|
540
568
|
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
@@ -678,10 +706,19 @@ export async function POST(request: Request) {
|
|
|
678
706
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
679
707
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
680
708
|
|
|
709
|
+
// Extract Phase 2a matches - files identified by component name matching visible text
|
|
710
|
+
// These are contextually relevant (e.g., "ProcessRow" seen in screenshot -> ProcessRow.tsx)
|
|
711
|
+
const phase2aMatches = searchResults
|
|
712
|
+
.filter(r => r.filenameMatch)
|
|
713
|
+
.map(r => r.path);
|
|
714
|
+
|
|
715
|
+
debugLog("Phase 2a component-name matches", { phase2aMatches });
|
|
716
|
+
|
|
681
717
|
// PHASE 2.5: Find which files contain the focused element
|
|
682
718
|
const focusedElementHints = findFilesContainingElement(
|
|
683
719
|
focusedElements,
|
|
684
|
-
searchResults.map(r => ({ path: r.path, content: r.content }))
|
|
720
|
+
searchResults.map(r => ({ path: r.path, content: r.content })),
|
|
721
|
+
phase2aMatches // Pass Phase 2a matches for massive bonus
|
|
685
722
|
);
|
|
686
723
|
|
|
687
724
|
if (focusedElementHints.length > 0) {
|
|
@@ -28,6 +28,10 @@ interface VisionFocusedElement {
|
|
|
28
28
|
height: number;
|
|
29
29
|
};
|
|
30
30
|
description?: string;
|
|
31
|
+
/** The actual text content of the element (e.g., "REFRESH", "ADD PROCESS") */
|
|
32
|
+
textContent?: string;
|
|
33
|
+
/** The className string of the element for pattern matching */
|
|
34
|
+
className?: string;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
interface VisionFileModification {
|
|
@@ -172,10 +176,14 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
172
176
|
* This helps identify which file actually contains the element the user clicked on
|
|
173
177
|
*
|
|
174
178
|
* CURSOR-STYLE: Search file CONTENTS for patterns, not just file names
|
|
179
|
+
*
|
|
180
|
+
* @param phase2aMatches - File paths that Phase 2a identified by component name matching visible text
|
|
181
|
+
* These get a MASSIVE bonus because they're contextually relevant
|
|
175
182
|
*/
|
|
176
183
|
function findFilesContainingElement(
|
|
177
184
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
178
|
-
candidateFiles: { path: string; content: string }[]
|
|
185
|
+
candidateFiles: { path: string; content: string }[],
|
|
186
|
+
phase2aMatches: string[] = [] // Files that Phase 2a identified by component name
|
|
179
187
|
): { path: string; score: number; matches: string[] }[] {
|
|
180
188
|
if (!focusedElements || focusedElements.length === 0) {
|
|
181
189
|
return [];
|
|
@@ -187,7 +195,13 @@ function findFilesContainingElement(
|
|
|
187
195
|
let score = 0;
|
|
188
196
|
const matches: string[] = [];
|
|
189
197
|
const content = file.content;
|
|
190
|
-
|
|
198
|
+
|
|
199
|
+
// HIGHEST VALUE: Phase 2a identified this file by matching component name to visible text
|
|
200
|
+
// This means the LLM saw "ProcessRow" in the screenshot and this file is ProcessRow.tsx
|
|
201
|
+
if (phase2aMatches.includes(file.path)) {
|
|
202
|
+
score += 200;
|
|
203
|
+
matches.push('Phase2a component match');
|
|
204
|
+
}
|
|
191
205
|
|
|
192
206
|
for (const el of focusedElements) {
|
|
193
207
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
@@ -212,28 +226,42 @@ function findFilesContainingElement(
|
|
|
212
226
|
matches.push(`defines ${capitalizedType}`);
|
|
213
227
|
}
|
|
214
228
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
// NOTE: We intentionally DO NOT count buttons, onClick handlers, or JSX tags
|
|
230
|
+
// because files with MORE of these are often "kitchen sink" components,
|
|
231
|
+
// not the focused component the user is looking at.
|
|
232
|
+
// Instead, we rely on Phase 2a matches and textContent matching.
|
|
233
|
+
|
|
234
|
+
// VERY HIGH VALUE: Exact text content match (e.g., button says "REFRESH")
|
|
235
|
+
// This is THE file if it contains the exact text the user clicked on
|
|
236
|
+
if (el.textContent && el.textContent.length >= 2) {
|
|
237
|
+
// Search for the text in JSX patterns like >REFRESH< or {`REFRESH`} or {"REFRESH"}
|
|
238
|
+
const textPatterns = [
|
|
239
|
+
`>${el.textContent}<`, // JSX text content
|
|
240
|
+
`"${el.textContent}"`, // String literal
|
|
241
|
+
`'${el.textContent}'`, // Single-quoted string
|
|
242
|
+
`\`${el.textContent}\``, // Template literal
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
for (const pattern of textPatterns) {
|
|
246
|
+
if (content.includes(pattern)) {
|
|
247
|
+
score += 150; // Very high - exact match!
|
|
248
|
+
matches.push(`contains "${el.textContent.substring(0, 20)}"`);
|
|
249
|
+
break; // Only add once per element
|
|
250
|
+
}
|
|
251
|
+
}
|
|
229
252
|
}
|
|
230
253
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
254
|
+
// MEDIUM VALUE: className pattern match
|
|
255
|
+
// If the file contains the same className string, it likely defines this element
|
|
256
|
+
if (el.className && el.className.length >= 10) {
|
|
257
|
+
// Extract the first few unique class names (not all to avoid noise)
|
|
258
|
+
const classNames = el.className.split(/\s+/).filter(c => c.length > 3).slice(0, 5);
|
|
259
|
+
for (const cls of classNames) {
|
|
260
|
+
if (content.includes(cls)) {
|
|
261
|
+
score += 25;
|
|
262
|
+
matches.push(`has class "${cls.substring(0, 20)}"`);
|
|
263
|
+
break; // Only add once per element
|
|
264
|
+
}
|
|
237
265
|
}
|
|
238
266
|
}
|
|
239
267
|
|
|
@@ -408,7 +436,7 @@ function searchFilesSmart(
|
|
|
408
436
|
analysis: ScreenshotAnalysis,
|
|
409
437
|
projectRoot: string,
|
|
410
438
|
maxFiles: number = 10
|
|
411
|
-
): { path: string; content: string; score: number }[] {
|
|
439
|
+
): { path: string; content: string; score: number; filenameMatch: boolean }[] {
|
|
412
440
|
const { visibleText, componentNames, codePatterns } = analysis;
|
|
413
441
|
|
|
414
442
|
// Combine all search terms
|
|
@@ -530,7 +558,7 @@ function searchFilesSmart(
|
|
|
530
558
|
}))
|
|
531
559
|
});
|
|
532
560
|
|
|
533
|
-
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score }));
|
|
561
|
+
return sortedResults.map(r => ({ path: r.path, content: r.content, score: r.score, filenameMatch: r.filenameMatch }));
|
|
534
562
|
}
|
|
535
563
|
|
|
536
564
|
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
@@ -647,10 +675,19 @@ export async function POST(request: Request) {
|
|
|
647
675
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
648
676
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
649
677
|
|
|
678
|
+
// Extract Phase 2a matches - files identified by component name matching visible text
|
|
679
|
+
// These are contextually relevant (e.g., "ProcessRow" seen in screenshot -> ProcessRow.tsx)
|
|
680
|
+
const phase2aMatches = searchResults
|
|
681
|
+
.filter(r => r.filenameMatch)
|
|
682
|
+
.map(r => r.path);
|
|
683
|
+
|
|
684
|
+
debugLog("Phase 2a component-name matches", { phase2aMatches });
|
|
685
|
+
|
|
650
686
|
// PHASE 2.5: Find which files contain the focused element
|
|
651
687
|
const focusedElementHints = findFilesContainingElement(
|
|
652
688
|
focusedElements,
|
|
653
|
-
searchResults.map(r => ({ path: r.path, content: r.content }))
|
|
689
|
+
searchResults.map(r => ({ path: r.path, content: r.content })),
|
|
690
|
+
phase2aMatches // Pass Phase 2a matches for massive bonus
|
|
654
691
|
);
|
|
655
692
|
|
|
656
693
|
if (focusedElementHints.length > 0) {
|
|
@@ -787,7 +787,11 @@ export function SonanceDevTools() {
|
|
|
787
787
|
el.setAttribute("data-sonance-variant", variantId);
|
|
788
788
|
}
|
|
789
789
|
|
|
790
|
-
|
|
790
|
+
// Capture textContent and className for dynamic element matching
|
|
791
|
+
const textContent = (el.textContent?.trim() || "").substring(0, 100); // Cap at 100 chars
|
|
792
|
+
const className = el.className?.toString() || "";
|
|
793
|
+
|
|
794
|
+
newTagged.push({ name, rect, type: "component", variantId, textContent, className });
|
|
791
795
|
}
|
|
792
796
|
}
|
|
793
797
|
});
|
|
@@ -826,7 +830,11 @@ export function SonanceDevTools() {
|
|
|
826
830
|
el.setAttribute("data-sonance-name", genericName);
|
|
827
831
|
}
|
|
828
832
|
|
|
829
|
-
|
|
833
|
+
// Capture textContent and className for dynamic element matching
|
|
834
|
+
const textContent = (el.textContent?.trim() || "").substring(0, 100); // Cap at 100 chars
|
|
835
|
+
const elClassName = el.className?.toString() || "";
|
|
836
|
+
|
|
837
|
+
newTagged.push({ name: genericName, rect, type: "component", variantId, textContent, className: elClassName });
|
|
830
838
|
}
|
|
831
839
|
});
|
|
832
840
|
});
|
|
@@ -1017,6 +1025,9 @@ export function SonanceDevTools() {
|
|
|
1017
1025
|
width: element.rect.width,
|
|
1018
1026
|
height: element.rect.height,
|
|
1019
1027
|
},
|
|
1028
|
+
// NEW: Capture text and className for dynamic file matching
|
|
1029
|
+
textContent: element.textContent,
|
|
1030
|
+
className: element.className,
|
|
1020
1031
|
};
|
|
1021
1032
|
|
|
1022
1033
|
setVisionFocusedElements((prev) => {
|
|
@@ -22,8 +22,10 @@ export interface DetectedElement {
|
|
|
22
22
|
logoId?: string;
|
|
23
23
|
/** Unique ID for text elements (for selection/editing) */
|
|
24
24
|
textId?: string;
|
|
25
|
-
/** The actual text content (for text
|
|
25
|
+
/** The actual text content (for all elements - button text, link text, etc.) */
|
|
26
26
|
textContent?: string;
|
|
27
|
+
/** The className string for pattern matching in code */
|
|
28
|
+
className?: string;
|
|
27
29
|
/** Component variant ID (hash of styles/classes) to distinguish visual styles */
|
|
28
30
|
variantId?: string;
|
|
29
31
|
}
|
|
@@ -220,6 +222,10 @@ export interface VisionFocusedElement {
|
|
|
220
222
|
height: number;
|
|
221
223
|
};
|
|
222
224
|
description?: string;
|
|
225
|
+
/** The actual text content of the element (e.g., "REFRESH", "ADD PROCESS") */
|
|
226
|
+
textContent?: string;
|
|
227
|
+
/** The className string of the element for pattern matching */
|
|
228
|
+
className?: string;
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
export interface VisionEditRequest {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.64",
|
|
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",
|