sonance-brand-mcp 1.3.62 → 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 {
|
|
@@ -106,7 +110,7 @@ async function analyzeScreenshotForSearch(
|
|
|
106
110
|
|
|
107
111
|
const response = await anthropic.messages.create({
|
|
108
112
|
model: "claude-sonnet-4-20250514",
|
|
109
|
-
max_tokens:
|
|
113
|
+
max_tokens: 2048, // Increased for better analysis
|
|
110
114
|
messages: [
|
|
111
115
|
{
|
|
112
116
|
role: "user",
|
|
@@ -174,10 +178,16 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
174
178
|
/**
|
|
175
179
|
* Search candidate files for JSX code matching the focused element
|
|
176
180
|
* This helps identify which file actually contains the element the user clicked on
|
|
181
|
+
*
|
|
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
|
|
177
186
|
*/
|
|
178
187
|
function findFilesContainingElement(
|
|
179
188
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
180
|
-
candidateFiles: { path: string; content: string }[]
|
|
189
|
+
candidateFiles: { path: string; content: string }[],
|
|
190
|
+
phase2aMatches: string[] = [] // Files that Phase 2a identified by component name
|
|
181
191
|
): { path: string; score: number; matches: string[] }[] {
|
|
182
192
|
if (!focusedElements || focusedElements.length === 0) {
|
|
183
193
|
return [];
|
|
@@ -188,44 +198,88 @@ function findFilesContainingElement(
|
|
|
188
198
|
for (const file of candidateFiles) {
|
|
189
199
|
let score = 0;
|
|
190
200
|
const matches: string[] = [];
|
|
191
|
-
const content = file.content
|
|
201
|
+
const content = file.content;
|
|
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
|
+
}
|
|
192
209
|
|
|
193
210
|
for (const el of focusedElements) {
|
|
194
211
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
195
212
|
const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
|
|
196
213
|
const componentType = el.type.toLowerCase();
|
|
214
|
+
const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
|
|
197
215
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
// HIGH VALUE: Component is EXPORTED from this file (this is likely THE file)
|
|
217
|
+
if (content.includes(`export function ${capitalizedType}`) ||
|
|
218
|
+
content.includes(`export const ${capitalizedType}`) ||
|
|
219
|
+
content.includes(`export default function ${capitalizedType}`)) {
|
|
220
|
+
score += 100;
|
|
221
|
+
matches.push(`EXPORTS ${capitalizedType}`);
|
|
203
222
|
}
|
|
204
223
|
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
224
|
+
// HIGH VALUE: Component is DEFINED in this file
|
|
225
|
+
if (content.includes(`function ${capitalizedType}(`) ||
|
|
226
|
+
content.includes(`const ${capitalizedType} =`) ||
|
|
227
|
+
content.includes(`const ${capitalizedType}:`) ||
|
|
228
|
+
content.includes(`function ${capitalizedType} (`)) {
|
|
229
|
+
score += 50;
|
|
230
|
+
matches.push(`defines ${capitalizedType}`);
|
|
210
231
|
}
|
|
211
232
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
215
256
|
}
|
|
216
257
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
+
}
|
|
221
269
|
}
|
|
222
270
|
}
|
|
223
271
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
score +=
|
|
228
|
-
matches.push(`
|
|
272
|
+
// BONUS: File name contains the element type
|
|
273
|
+
const fileName = file.path.toLowerCase();
|
|
274
|
+
if (fileName.includes(elementType) || fileName.includes(componentType)) {
|
|
275
|
+
score += 30;
|
|
276
|
+
matches.push(`filename contains ${elementType}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// BONUS: File is a Row/Cell component (likely contains action buttons)
|
|
280
|
+
if (fileName.includes('row') || fileName.includes('cell') || fileName.includes('item')) {
|
|
281
|
+
score += 15;
|
|
282
|
+
matches.push('row/cell component');
|
|
229
283
|
}
|
|
230
284
|
}
|
|
231
285
|
|
|
@@ -386,7 +440,7 @@ function searchFilesSmart(
|
|
|
386
440
|
analysis: ScreenshotAnalysis,
|
|
387
441
|
projectRoot: string,
|
|
388
442
|
maxFiles: number = 10
|
|
389
|
-
): { path: string; content: string; score: number }[] {
|
|
443
|
+
): { path: string; content: string; score: number; filenameMatch: boolean }[] {
|
|
390
444
|
const { visibleText, componentNames, codePatterns } = analysis;
|
|
391
445
|
|
|
392
446
|
// Combine all search terms
|
|
@@ -508,7 +562,7 @@ function searchFilesSmart(
|
|
|
508
562
|
}))
|
|
509
563
|
});
|
|
510
564
|
|
|
511
|
-
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 }));
|
|
512
566
|
}
|
|
513
567
|
|
|
514
568
|
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
@@ -652,10 +706,19 @@ export async function POST(request: Request) {
|
|
|
652
706
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
653
707
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
654
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
|
+
|
|
655
717
|
// PHASE 2.5: Find which files contain the focused element
|
|
656
718
|
const focusedElementHints = findFilesContainingElement(
|
|
657
719
|
focusedElements,
|
|
658
|
-
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
|
|
659
722
|
);
|
|
660
723
|
|
|
661
724
|
if (focusedElementHints.length > 0) {
|
|
@@ -668,8 +731,19 @@ export async function POST(request: Request) {
|
|
|
668
731
|
});
|
|
669
732
|
}
|
|
670
733
|
|
|
671
|
-
//
|
|
672
|
-
|
|
734
|
+
// CURSOR-STYLE: If focused element search has high confidence, use it directly
|
|
735
|
+
// Skip the LLM guessing phase - we found the file that contains the code
|
|
736
|
+
const HIGH_CONFIDENCE_THRESHOLD = 50; // Score of 50+ means we found exact matches
|
|
737
|
+
|
|
738
|
+
if (focusedElementHints.length > 0 && focusedElementHints[0].score >= HIGH_CONFIDENCE_THRESHOLD) {
|
|
739
|
+
// HIGH CONFIDENCE: Use focused element match directly
|
|
740
|
+
recommendedFile = {
|
|
741
|
+
path: focusedElementHints[0].path,
|
|
742
|
+
reason: `Content search found element (score: ${focusedElementHints[0].score}, matches: ${focusedElementHints[0].matches.join(', ')})`
|
|
743
|
+
};
|
|
744
|
+
debugLog("HIGH CONFIDENCE: Using focused element match directly", recommendedFile);
|
|
745
|
+
} else if (searchResults.length > 0) {
|
|
746
|
+
// LOW CONFIDENCE: Fall back to LLM selection
|
|
673
747
|
const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
|
|
674
748
|
const selectedFile = await selectBestFileFromList(
|
|
675
749
|
screenshot,
|
|
@@ -2422,3 +2496,4 @@ function applyPatches(originalContent: string, patches: Patch[]): ApplyPatchesRe
|
|
|
2422
2496
|
failedPatches,
|
|
2423
2497
|
};
|
|
2424
2498
|
}
|
|
2499
|
+
|
|
@@ -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 {
|
|
@@ -102,7 +106,7 @@ async function analyzeScreenshotForSearch(
|
|
|
102
106
|
|
|
103
107
|
const response = await anthropic.messages.create({
|
|
104
108
|
model: "claude-sonnet-4-20250514",
|
|
105
|
-
max_tokens:
|
|
109
|
+
max_tokens: 2048, // Increased for better analysis
|
|
106
110
|
messages: [
|
|
107
111
|
{
|
|
108
112
|
role: "user",
|
|
@@ -170,10 +174,16 @@ Be smart - use your knowledge of React patterns to make educated guesses about c
|
|
|
170
174
|
/**
|
|
171
175
|
* Search candidate files for JSX code matching the focused element
|
|
172
176
|
* This helps identify which file actually contains the element the user clicked on
|
|
177
|
+
*
|
|
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
|
|
173
182
|
*/
|
|
174
183
|
function findFilesContainingElement(
|
|
175
184
|
focusedElements: VisionFocusedElement[] | undefined,
|
|
176
|
-
candidateFiles: { path: string; content: string }[]
|
|
185
|
+
candidateFiles: { path: string; content: string }[],
|
|
186
|
+
phase2aMatches: string[] = [] // Files that Phase 2a identified by component name
|
|
177
187
|
): { path: string; score: number; matches: string[] }[] {
|
|
178
188
|
if (!focusedElements || focusedElements.length === 0) {
|
|
179
189
|
return [];
|
|
@@ -184,44 +194,88 @@ function findFilesContainingElement(
|
|
|
184
194
|
for (const file of candidateFiles) {
|
|
185
195
|
let score = 0;
|
|
186
196
|
const matches: string[] = [];
|
|
187
|
-
const content = file.content
|
|
197
|
+
const content = file.content;
|
|
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
|
+
}
|
|
188
205
|
|
|
189
206
|
for (const el of focusedElements) {
|
|
190
207
|
// Extract element type from the name (e.g., "button #123" -> "button")
|
|
191
208
|
const elementType = el.name.split(/[\s#]/)[0].toLowerCase();
|
|
192
209
|
const componentType = el.type.toLowerCase();
|
|
210
|
+
const capitalizedType = el.type.charAt(0).toUpperCase() + el.type.slice(1);
|
|
193
211
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
// HIGH VALUE: Component is EXPORTED from this file (this is likely THE file)
|
|
213
|
+
if (content.includes(`export function ${capitalizedType}`) ||
|
|
214
|
+
content.includes(`export const ${capitalizedType}`) ||
|
|
215
|
+
content.includes(`export default function ${capitalizedType}`)) {
|
|
216
|
+
score += 100;
|
|
217
|
+
matches.push(`EXPORTS ${capitalizedType}`);
|
|
199
218
|
}
|
|
200
219
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
// HIGH VALUE: Component is DEFINED in this file
|
|
221
|
+
if (content.includes(`function ${capitalizedType}(`) ||
|
|
222
|
+
content.includes(`const ${capitalizedType} =`) ||
|
|
223
|
+
content.includes(`const ${capitalizedType}:`) ||
|
|
224
|
+
content.includes(`function ${capitalizedType} (`)) {
|
|
225
|
+
score += 50;
|
|
226
|
+
matches.push(`defines ${capitalizedType}`);
|
|
206
227
|
}
|
|
207
228
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
}
|
|
211
252
|
}
|
|
212
253
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
}
|
|
217
265
|
}
|
|
218
266
|
}
|
|
219
267
|
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
score +=
|
|
224
|
-
matches.push(`
|
|
268
|
+
// BONUS: File name contains the element type
|
|
269
|
+
const fileName = file.path.toLowerCase();
|
|
270
|
+
if (fileName.includes(elementType) || fileName.includes(componentType)) {
|
|
271
|
+
score += 30;
|
|
272
|
+
matches.push(`filename contains ${elementType}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// BONUS: File is a Row/Cell component (likely contains action buttons)
|
|
276
|
+
if (fileName.includes('row') || fileName.includes('cell') || fileName.includes('item')) {
|
|
277
|
+
score += 15;
|
|
278
|
+
matches.push('row/cell component');
|
|
225
279
|
}
|
|
226
280
|
}
|
|
227
281
|
|
|
@@ -382,7 +436,7 @@ function searchFilesSmart(
|
|
|
382
436
|
analysis: ScreenshotAnalysis,
|
|
383
437
|
projectRoot: string,
|
|
384
438
|
maxFiles: number = 10
|
|
385
|
-
): { path: string; content: string; score: number }[] {
|
|
439
|
+
): { path: string; content: string; score: number; filenameMatch: boolean }[] {
|
|
386
440
|
const { visibleText, componentNames, codePatterns } = analysis;
|
|
387
441
|
|
|
388
442
|
// Combine all search terms
|
|
@@ -504,7 +558,7 @@ function searchFilesSmart(
|
|
|
504
558
|
}))
|
|
505
559
|
});
|
|
506
560
|
|
|
507
|
-
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 }));
|
|
508
562
|
}
|
|
509
563
|
|
|
510
564
|
const VISION_SYSTEM_PROMPT = `You edit code. Return ONLY valid JSON - no explanation, no preamble, no markdown.
|
|
@@ -621,10 +675,19 @@ export async function POST(request: Request) {
|
|
|
621
675
|
const searchResults = searchFilesSmart(analysis, projectRoot, 10);
|
|
622
676
|
smartSearchFiles = searchResults.map(r => ({ path: r.path, content: r.content }));
|
|
623
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
|
+
|
|
624
686
|
// PHASE 2.5: Find which files contain the focused element
|
|
625
687
|
const focusedElementHints = findFilesContainingElement(
|
|
626
688
|
focusedElements,
|
|
627
|
-
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
|
|
628
691
|
);
|
|
629
692
|
|
|
630
693
|
if (focusedElementHints.length > 0) {
|
|
@@ -637,8 +700,19 @@ export async function POST(request: Request) {
|
|
|
637
700
|
});
|
|
638
701
|
}
|
|
639
702
|
|
|
640
|
-
//
|
|
641
|
-
|
|
703
|
+
// CURSOR-STYLE: If focused element search has high confidence, use it directly
|
|
704
|
+
// Skip the LLM guessing phase - we found the file that contains the code
|
|
705
|
+
const HIGH_CONFIDENCE_THRESHOLD = 50; // Score of 50+ means we found exact matches
|
|
706
|
+
|
|
707
|
+
if (focusedElementHints.length > 0 && focusedElementHints[0].score >= HIGH_CONFIDENCE_THRESHOLD) {
|
|
708
|
+
// HIGH CONFIDENCE: Use focused element match directly
|
|
709
|
+
recommendedFile = {
|
|
710
|
+
path: focusedElementHints[0].path,
|
|
711
|
+
reason: `Content search found element (score: ${focusedElementHints[0].score}, matches: ${focusedElementHints[0].matches.join(', ')})`
|
|
712
|
+
};
|
|
713
|
+
debugLog("HIGH CONFIDENCE: Using focused element match directly", recommendedFile);
|
|
714
|
+
} else if (searchResults.length > 0) {
|
|
715
|
+
// LOW CONFIDENCE: Fall back to LLM selection
|
|
642
716
|
const candidateFiles = searchResults.slice(0, 10).map(r => r.path);
|
|
643
717
|
const selectedFile = await selectBestFileFromList(
|
|
644
718
|
screenshot,
|
|
@@ -2231,3 +2305,4 @@ function applyPatches(originalContent: string, patches: Patch[]): ApplyPatchesRe
|
|
|
2231
2305
|
failedPatches,
|
|
2232
2306
|
};
|
|
2233
2307
|
}
|
|
2308
|
+
|
|
@@ -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",
|