sonance-brand-mcp 1.3.89 → 1.3.91
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.
|
@@ -229,6 +229,277 @@ function findElementIdInFile(
|
|
|
229
229
|
return null;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Find the line number of a focused element in the source code
|
|
234
|
+
* Uses multiple strategies in priority order:
|
|
235
|
+
* 1. DOM id (highest confidence)
|
|
236
|
+
* 2. Text content
|
|
237
|
+
* 3. ClassName patterns
|
|
238
|
+
*
|
|
239
|
+
* This extends the basic ID matching to handle elements without IDs.
|
|
240
|
+
*/
|
|
241
|
+
function findElementLineInFile(
|
|
242
|
+
fileContent: string,
|
|
243
|
+
focusedElement: VisionFocusedElement
|
|
244
|
+
): { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null {
|
|
245
|
+
if (!fileContent) return null;
|
|
246
|
+
|
|
247
|
+
const lines = fileContent.split('\n');
|
|
248
|
+
|
|
249
|
+
// PRIORITY 1: DOM id - highest confidence (exact match)
|
|
250
|
+
if (focusedElement.elementId) {
|
|
251
|
+
const idPattern = new RegExp(`id=["'\`]${focusedElement.elementId}["'\`]`);
|
|
252
|
+
for (let i = 0; i < lines.length; i++) {
|
|
253
|
+
if (idPattern.test(lines[i])) {
|
|
254
|
+
return {
|
|
255
|
+
lineNumber: i + 1,
|
|
256
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
257
|
+
confidence: 'high',
|
|
258
|
+
matchedBy: `DOM id="${focusedElement.elementId}"`
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Also try child IDs with high confidence
|
|
265
|
+
if (focusedElement.childIds && focusedElement.childIds.length > 0) {
|
|
266
|
+
for (const childId of focusedElement.childIds) {
|
|
267
|
+
const idPattern = new RegExp(`id=["'\`]${childId}["'\`]`);
|
|
268
|
+
for (let i = 0; i < lines.length; i++) {
|
|
269
|
+
if (idPattern.test(lines[i])) {
|
|
270
|
+
return {
|
|
271
|
+
lineNumber: i + 1,
|
|
272
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
273
|
+
confidence: 'high',
|
|
274
|
+
matchedBy: `child id="${childId}"`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// PRIORITY 2: Exact text content in JSX
|
|
282
|
+
if (focusedElement.textContent && focusedElement.textContent.trim().length >= 2) {
|
|
283
|
+
const text = focusedElement.textContent.trim();
|
|
284
|
+
// Escape special regex characters in the text
|
|
285
|
+
const escapedText = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < lines.length; i++) {
|
|
288
|
+
const line = lines[i];
|
|
289
|
+
// Match patterns like: >Text<, >Text</tag, "Text", 'Text', {`Text`}
|
|
290
|
+
const textPatterns = [
|
|
291
|
+
`>${escapedText}<`, // JSX content: >Text<
|
|
292
|
+
`"${escapedText}"`, // String literal
|
|
293
|
+
`'${escapedText}'`, // String literal
|
|
294
|
+
`\`${escapedText}\``, // Template literal
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
for (const pattern of textPatterns) {
|
|
298
|
+
if (line.includes(pattern.replace(/\\/g, ''))) {
|
|
299
|
+
return {
|
|
300
|
+
lineNumber: i + 1,
|
|
301
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
302
|
+
confidence: 'high',
|
|
303
|
+
matchedBy: `textContent="${text.substring(0, 30)}${text.length > 30 ? '...' : ''}"`
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Try partial match for longer text (first 15+ chars)
|
|
310
|
+
if (text.length > 15) {
|
|
311
|
+
const partialText = text.substring(0, 15);
|
|
312
|
+
for (let i = 0; i < lines.length; i++) {
|
|
313
|
+
if (lines[i].includes(`>${partialText}`) || lines[i].includes(`"${partialText}`)) {
|
|
314
|
+
return {
|
|
315
|
+
lineNumber: i + 1,
|
|
316
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
317
|
+
confidence: 'medium',
|
|
318
|
+
matchedBy: `partial textContent starting with "${partialText}..."`
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// PRIORITY 2b: Look for icon patterns if type is button and no text (like X buttons)
|
|
326
|
+
if (focusedElement.type === 'button' && (!focusedElement.textContent || focusedElement.textContent.trim().length === 0)) {
|
|
327
|
+
// Common close/icon button patterns
|
|
328
|
+
const iconPatterns = ['<X ', '<X/', 'DialogClose', 'DialogPrimitive.Close', 'closeButton', 'CloseButton'];
|
|
329
|
+
for (const pattern of iconPatterns) {
|
|
330
|
+
for (let i = 0; i < lines.length; i++) {
|
|
331
|
+
if (lines[i].includes(pattern)) {
|
|
332
|
+
return {
|
|
333
|
+
lineNumber: i + 1,
|
|
334
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
335
|
+
confidence: 'low',
|
|
336
|
+
matchedBy: `icon pattern "${pattern}"`
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// PRIORITY 3: Distinctive className patterns (ONLY semantic/custom classes)
|
|
344
|
+
if (focusedElement.className) {
|
|
345
|
+
// Filter OUT all Tailwind utility patterns - these are too generic and match wrong elements
|
|
346
|
+
const tailwindPatterns = [
|
|
347
|
+
/^p[xytblr]?-/, // padding: px-4, py-2, pt-1
|
|
348
|
+
/^m[xytblr]?-/, // margin: mx-auto, my-4
|
|
349
|
+
/^-?m[xytblr]?-/, // negative margins: -mt-4
|
|
350
|
+
/^w-/, // width: w-full, w-4
|
|
351
|
+
/^h-/, // height: h-10, h-full
|
|
352
|
+
/^min-/, // min-width/height
|
|
353
|
+
/^max-/, // max-width/height
|
|
354
|
+
/^size-/, // size utilities
|
|
355
|
+
/^bg-/, // background
|
|
356
|
+
/^text-/, // text color/size
|
|
357
|
+
/^font-/, // font-weight
|
|
358
|
+
/^leading-/, // line-height
|
|
359
|
+
/^tracking-/, // letter-spacing
|
|
360
|
+
/^rounded/, // border-radius: rounded-sm, rounded-lg
|
|
361
|
+
/^border/, // border
|
|
362
|
+
/^outline/, // outline
|
|
363
|
+
/^ring/, // ring
|
|
364
|
+
/^shadow/, // shadow
|
|
365
|
+
/^flex/, // flex utilities
|
|
366
|
+
/^grow/, // flex-grow
|
|
367
|
+
/^shrink/, // flex-shrink
|
|
368
|
+
/^basis-/, // flex-basis
|
|
369
|
+
/^grid/, // grid utilities
|
|
370
|
+
/^col-/, // grid columns
|
|
371
|
+
/^row-/, // grid rows
|
|
372
|
+
/^gap-/, // gap
|
|
373
|
+
/^items-/, // align-items
|
|
374
|
+
/^justify-/, // justify-content
|
|
375
|
+
/^self-/, // align-self
|
|
376
|
+
/^place-/, // place utilities
|
|
377
|
+
/^space-/, // space-x, space-y
|
|
378
|
+
/^overflow/, // overflow
|
|
379
|
+
/^scroll/, // scroll utilities
|
|
380
|
+
/^cursor-/, // cursor
|
|
381
|
+
/^pointer-/, // pointer-events
|
|
382
|
+
/^select-/, // user-select
|
|
383
|
+
/^opacity-/, // opacity
|
|
384
|
+
/^visible/, // visibility
|
|
385
|
+
/^invisible/, // visibility
|
|
386
|
+
/^z-/, // z-index
|
|
387
|
+
/^inset/, // inset utilities
|
|
388
|
+
/^top-/, // positioning
|
|
389
|
+
/^right-/, // positioning
|
|
390
|
+
/^bottom-/, // positioning
|
|
391
|
+
/^left-/, // positioning
|
|
392
|
+
/^static/, // position
|
|
393
|
+
/^fixed/, // position
|
|
394
|
+
/^absolute/, // position
|
|
395
|
+
/^relative/, // position
|
|
396
|
+
/^sticky/, // position
|
|
397
|
+
/^transition/, // transitions
|
|
398
|
+
/^duration-/, // duration
|
|
399
|
+
/^ease-/, // timing function
|
|
400
|
+
/^delay-/, // delay
|
|
401
|
+
/^animate-/, // animations
|
|
402
|
+
/^transform/, // transform
|
|
403
|
+
/^scale-/, // scale
|
|
404
|
+
/^rotate-/, // rotate
|
|
405
|
+
/^translate-/, // translate
|
|
406
|
+
/^skew-/, // skew
|
|
407
|
+
/^origin-/, // transform-origin
|
|
408
|
+
/^appearance-/, // appearance
|
|
409
|
+
/^accent-/, // accent-color
|
|
410
|
+
/^caret-/, // caret-color
|
|
411
|
+
/^fill-/, // SVG fill
|
|
412
|
+
/^stroke-/, // SVG stroke
|
|
413
|
+
/^object-/, // object-fit/position
|
|
414
|
+
/^aspect-/, // aspect-ratio
|
|
415
|
+
/^container/, // container
|
|
416
|
+
/^columns-/, // columns
|
|
417
|
+
/^break-/, // word-break
|
|
418
|
+
/^truncate/, // text-overflow
|
|
419
|
+
/^whitespace-/, // whitespace
|
|
420
|
+
/^list-/, // list-style
|
|
421
|
+
/^decoration-/, // text-decoration
|
|
422
|
+
/^underline/, // underline
|
|
423
|
+
/^overline/, // overline
|
|
424
|
+
/^line-through/, // line-through
|
|
425
|
+
/^no-underline/, // no underline
|
|
426
|
+
/^antialiased/, // font-smoothing
|
|
427
|
+
/^subpixel/, // subpixel-antialiased
|
|
428
|
+
/^italic/, // font-style
|
|
429
|
+
/^not-italic/, // font-style
|
|
430
|
+
/^uppercase/, // text-transform
|
|
431
|
+
/^lowercase/, // text-transform
|
|
432
|
+
/^capitalize/, // text-transform
|
|
433
|
+
/^normal-case/, // text-transform
|
|
434
|
+
/^align-/, // vertical-align
|
|
435
|
+
/^indent-/, // text-indent
|
|
436
|
+
/^content-/, // content
|
|
437
|
+
/^will-change/, // will-change
|
|
438
|
+
/^hover:/, // hover states
|
|
439
|
+
/^focus:/, // focus states
|
|
440
|
+
/^focus-within:/, // focus-within states
|
|
441
|
+
/^focus-visible:/, // focus-visible states
|
|
442
|
+
/^active:/, // active states
|
|
443
|
+
/^visited:/, // visited states
|
|
444
|
+
/^disabled:/, // disabled states
|
|
445
|
+
/^checked:/, // checked states
|
|
446
|
+
/^group-/, // group utilities
|
|
447
|
+
/^peer-/, // peer utilities
|
|
448
|
+
/^dark:/, // dark mode
|
|
449
|
+
/^light:/, // light mode
|
|
450
|
+
/^motion-/, // motion utilities
|
|
451
|
+
/^print:/, // print utilities
|
|
452
|
+
/^portrait:/, // orientation
|
|
453
|
+
/^landscape:/, // orientation
|
|
454
|
+
/^sm:|^md:|^lg:|^xl:|^2xl:/, // responsive prefixes
|
|
455
|
+
/^data-\[/, // data attribute variants
|
|
456
|
+
/^aria-/, // aria variants
|
|
457
|
+
/^supports-/, // supports variants
|
|
458
|
+
/^has-/, // has variants
|
|
459
|
+
/^group$/, // group class itself
|
|
460
|
+
/^peer$/, // peer class itself
|
|
461
|
+
/^sr-only/, // screen reader only
|
|
462
|
+
/^not-sr-only/, // not screen reader only
|
|
463
|
+
/^isolate/, // isolation
|
|
464
|
+
/^block$/, // display
|
|
465
|
+
/^inline/, // display
|
|
466
|
+
/^hidden$/, // display
|
|
467
|
+
/^table/, // display table
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
const isTailwindUtility = (cls: string) =>
|
|
471
|
+
tailwindPatterns.some(pattern => pattern.test(cls));
|
|
472
|
+
|
|
473
|
+
// Only use classes that are NOT Tailwind utilities (likely custom/semantic)
|
|
474
|
+
const semanticClasses = focusedElement.className.split(/\s+/)
|
|
475
|
+
.filter(c => c.length > 3 && !isTailwindUtility(c));
|
|
476
|
+
|
|
477
|
+
// Only proceed if we found semantic classes
|
|
478
|
+
if (semanticClasses.length > 0) {
|
|
479
|
+
for (const cls of semanticClasses) {
|
|
480
|
+
for (let i = 0; i < lines.length; i++) {
|
|
481
|
+
if (lines[i].includes(cls)) {
|
|
482
|
+
return {
|
|
483
|
+
lineNumber: i + 1,
|
|
484
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
485
|
+
confidence: 'medium',
|
|
486
|
+
matchedBy: `semantic className "${cls}"`
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Log when we filtered out all classes (helpful for debugging)
|
|
494
|
+
if (semanticClasses.length === 0 && focusedElement.className.trim().length > 0) {
|
|
495
|
+
console.log('[findElementLineInFile] All classes filtered as Tailwind utilities:',
|
|
496
|
+
focusedElement.className.substring(0, 100));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
|
|
232
503
|
/**
|
|
233
504
|
* PHASE 0: Deterministic Element ID Search (Cursor-style)
|
|
234
505
|
* Grep entire codebase for the element ID. If found in multiple files,
|
|
@@ -1330,12 +1601,21 @@ User Request: "${userPrompt}"
|
|
|
1330
1601
|
if (recommendedFileContent) {
|
|
1331
1602
|
const content = recommendedFileContent.content;
|
|
1332
1603
|
|
|
1333
|
-
// Search for element
|
|
1334
|
-
|
|
1335
|
-
|
|
1604
|
+
// Search for focused element in the file using multiple strategies
|
|
1605
|
+
// Priority: DOM id > textContent > className patterns
|
|
1606
|
+
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
1607
|
+
if (focusedElements && focusedElements.length > 0) {
|
|
1336
1608
|
for (const el of focusedElements) {
|
|
1337
|
-
|
|
1338
|
-
if (
|
|
1609
|
+
elementLocation = findElementLineInFile(content, el);
|
|
1610
|
+
if (elementLocation) {
|
|
1611
|
+
debugLog("Found focused element in file", {
|
|
1612
|
+
matchedBy: elementLocation.matchedBy,
|
|
1613
|
+
lineNumber: elementLocation.lineNumber,
|
|
1614
|
+
confidence: elementLocation.confidence,
|
|
1615
|
+
file: recommendedFileContent.path,
|
|
1616
|
+
});
|
|
1617
|
+
break;
|
|
1618
|
+
}
|
|
1339
1619
|
}
|
|
1340
1620
|
}
|
|
1341
1621
|
|
|
@@ -1350,22 +1630,49 @@ User Request: "${userPrompt}"
|
|
|
1350
1630
|
textContent += `\n`;
|
|
1351
1631
|
}
|
|
1352
1632
|
|
|
1353
|
-
// Add precise targeting
|
|
1354
|
-
if (
|
|
1633
|
+
// Add precise targeting with line number and snippet
|
|
1634
|
+
if (elementLocation) {
|
|
1355
1635
|
textContent += `
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
→
|
|
1636
|
+
══════════════════════════════════════════════════════════════════════════════
|
|
1637
|
+
PRECISE TARGET LOCATION (${elementLocation.confidence} confidence)
|
|
1638
|
+
══════════════════════════════════════════════════════════════════════════════
|
|
1639
|
+
→ Matched by: ${elementLocation.matchedBy}
|
|
1640
|
+
→ Line: ${elementLocation.lineNumber}
|
|
1641
|
+
|
|
1642
|
+
THE USER CLICKED ON THE ELEMENT AT LINE ${elementLocation.lineNumber}.
|
|
1643
|
+
Here is the exact code around that element:
|
|
1644
|
+
\`\`\`
|
|
1645
|
+
${elementLocation.snippet}
|
|
1646
|
+
\`\`\`
|
|
1647
|
+
|
|
1648
|
+
⚠️ IMPORTANT: Modify ONLY the element at line ${elementLocation.lineNumber}, NOT other similar elements in the file.
|
|
1360
1649
|
|
|
1361
1650
|
`;
|
|
1362
|
-
debugLog("Found element ID in file", {
|
|
1363
|
-
matchedId: idMatch.matchedId,
|
|
1364
|
-
lineNumber: idMatch.lineNumber,
|
|
1365
|
-
file: recommendedFileContent.path,
|
|
1366
|
-
});
|
|
1367
1651
|
} else {
|
|
1368
1652
|
textContent += `\n`;
|
|
1653
|
+
debugLog("WARNING: Could not locate focused element in file", {
|
|
1654
|
+
file: recommendedFileContent.path,
|
|
1655
|
+
hint: "The clicked element may be rendered by a child component (e.g., DialogContent, Button component). Check the component's source file instead.",
|
|
1656
|
+
focusedElements: focusedElements.map(el => ({
|
|
1657
|
+
name: el.name,
|
|
1658
|
+
type: el.type,
|
|
1659
|
+
textContent: el.textContent?.substring(0, 30),
|
|
1660
|
+
className: el.className?.substring(0, 50),
|
|
1661
|
+
elementId: el.elementId,
|
|
1662
|
+
}))
|
|
1663
|
+
});
|
|
1664
|
+
|
|
1665
|
+
// Add a hint to the LLM that we couldn't precisely locate the element
|
|
1666
|
+
textContent += `
|
|
1667
|
+
⚠️ WARNING: Could not precisely locate the clicked element in this file.
|
|
1668
|
+
The element may be:
|
|
1669
|
+
- Rendered by a child component (e.g., DialogContent, Button)
|
|
1670
|
+
- Dynamically generated
|
|
1671
|
+
- In a different file imported by this component
|
|
1672
|
+
|
|
1673
|
+
Please proceed with caution and verify you're modifying the correct element.
|
|
1674
|
+
|
|
1675
|
+
`;
|
|
1369
1676
|
}
|
|
1370
1677
|
}
|
|
1371
1678
|
|
|
@@ -225,6 +225,277 @@ function findElementIdInFile(
|
|
|
225
225
|
return null;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Find the line number of a focused element in the source code
|
|
230
|
+
* Uses multiple strategies in priority order:
|
|
231
|
+
* 1. DOM id (highest confidence)
|
|
232
|
+
* 2. Text content
|
|
233
|
+
* 3. ClassName patterns
|
|
234
|
+
*
|
|
235
|
+
* This extends the basic ID matching to handle elements without IDs.
|
|
236
|
+
*/
|
|
237
|
+
function findElementLineInFile(
|
|
238
|
+
fileContent: string,
|
|
239
|
+
focusedElement: VisionFocusedElement
|
|
240
|
+
): { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null {
|
|
241
|
+
if (!fileContent) return null;
|
|
242
|
+
|
|
243
|
+
const lines = fileContent.split('\n');
|
|
244
|
+
|
|
245
|
+
// PRIORITY 1: DOM id - highest confidence (exact match)
|
|
246
|
+
if (focusedElement.elementId) {
|
|
247
|
+
const idPattern = new RegExp(`id=["'\`]${focusedElement.elementId}["'\`]`);
|
|
248
|
+
for (let i = 0; i < lines.length; i++) {
|
|
249
|
+
if (idPattern.test(lines[i])) {
|
|
250
|
+
return {
|
|
251
|
+
lineNumber: i + 1,
|
|
252
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
253
|
+
confidence: 'high',
|
|
254
|
+
matchedBy: `DOM id="${focusedElement.elementId}"`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Also try child IDs with high confidence
|
|
261
|
+
if (focusedElement.childIds && focusedElement.childIds.length > 0) {
|
|
262
|
+
for (const childId of focusedElement.childIds) {
|
|
263
|
+
const idPattern = new RegExp(`id=["'\`]${childId}["'\`]`);
|
|
264
|
+
for (let i = 0; i < lines.length; i++) {
|
|
265
|
+
if (idPattern.test(lines[i])) {
|
|
266
|
+
return {
|
|
267
|
+
lineNumber: i + 1,
|
|
268
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
269
|
+
confidence: 'high',
|
|
270
|
+
matchedBy: `child id="${childId}"`
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// PRIORITY 2: Exact text content in JSX
|
|
278
|
+
if (focusedElement.textContent && focusedElement.textContent.trim().length >= 2) {
|
|
279
|
+
const text = focusedElement.textContent.trim();
|
|
280
|
+
// Escape special regex characters in the text
|
|
281
|
+
const escapedText = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < lines.length; i++) {
|
|
284
|
+
const line = lines[i];
|
|
285
|
+
// Match patterns like: >Text<, >Text</tag, "Text", 'Text', {`Text`}
|
|
286
|
+
const textPatterns = [
|
|
287
|
+
`>${escapedText}<`, // JSX content: >Text<
|
|
288
|
+
`"${escapedText}"`, // String literal
|
|
289
|
+
`'${escapedText}'`, // String literal
|
|
290
|
+
`\`${escapedText}\``, // Template literal
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
for (const pattern of textPatterns) {
|
|
294
|
+
if (line.includes(pattern.replace(/\\/g, ''))) {
|
|
295
|
+
return {
|
|
296
|
+
lineNumber: i + 1,
|
|
297
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
298
|
+
confidence: 'high',
|
|
299
|
+
matchedBy: `textContent="${text.substring(0, 30)}${text.length > 30 ? '...' : ''}"`
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Try partial match for longer text (first 15+ chars)
|
|
306
|
+
if (text.length > 15) {
|
|
307
|
+
const partialText = text.substring(0, 15);
|
|
308
|
+
for (let i = 0; i < lines.length; i++) {
|
|
309
|
+
if (lines[i].includes(`>${partialText}`) || lines[i].includes(`"${partialText}`)) {
|
|
310
|
+
return {
|
|
311
|
+
lineNumber: i + 1,
|
|
312
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
313
|
+
confidence: 'medium',
|
|
314
|
+
matchedBy: `partial textContent starting with "${partialText}..."`
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// PRIORITY 2b: Look for icon patterns if type is button and no text (like X buttons)
|
|
322
|
+
if (focusedElement.type === 'button' && (!focusedElement.textContent || focusedElement.textContent.trim().length === 0)) {
|
|
323
|
+
// Common close/icon button patterns
|
|
324
|
+
const iconPatterns = ['<X ', '<X/', 'DialogClose', 'DialogPrimitive.Close', 'closeButton', 'CloseButton'];
|
|
325
|
+
for (const pattern of iconPatterns) {
|
|
326
|
+
for (let i = 0; i < lines.length; i++) {
|
|
327
|
+
if (lines[i].includes(pattern)) {
|
|
328
|
+
return {
|
|
329
|
+
lineNumber: i + 1,
|
|
330
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
331
|
+
confidence: 'low',
|
|
332
|
+
matchedBy: `icon pattern "${pattern}"`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// PRIORITY 3: Distinctive className patterns (ONLY semantic/custom classes)
|
|
340
|
+
if (focusedElement.className) {
|
|
341
|
+
// Filter OUT all Tailwind utility patterns - these are too generic and match wrong elements
|
|
342
|
+
const tailwindPatterns = [
|
|
343
|
+
/^p[xytblr]?-/, // padding: px-4, py-2, pt-1
|
|
344
|
+
/^m[xytblr]?-/, // margin: mx-auto, my-4
|
|
345
|
+
/^-?m[xytblr]?-/, // negative margins: -mt-4
|
|
346
|
+
/^w-/, // width: w-full, w-4
|
|
347
|
+
/^h-/, // height: h-10, h-full
|
|
348
|
+
/^min-/, // min-width/height
|
|
349
|
+
/^max-/, // max-width/height
|
|
350
|
+
/^size-/, // size utilities
|
|
351
|
+
/^bg-/, // background
|
|
352
|
+
/^text-/, // text color/size
|
|
353
|
+
/^font-/, // font-weight
|
|
354
|
+
/^leading-/, // line-height
|
|
355
|
+
/^tracking-/, // letter-spacing
|
|
356
|
+
/^rounded/, // border-radius: rounded-sm, rounded-lg
|
|
357
|
+
/^border/, // border
|
|
358
|
+
/^outline/, // outline
|
|
359
|
+
/^ring/, // ring
|
|
360
|
+
/^shadow/, // shadow
|
|
361
|
+
/^flex/, // flex utilities
|
|
362
|
+
/^grow/, // flex-grow
|
|
363
|
+
/^shrink/, // flex-shrink
|
|
364
|
+
/^basis-/, // flex-basis
|
|
365
|
+
/^grid/, // grid utilities
|
|
366
|
+
/^col-/, // grid columns
|
|
367
|
+
/^row-/, // grid rows
|
|
368
|
+
/^gap-/, // gap
|
|
369
|
+
/^items-/, // align-items
|
|
370
|
+
/^justify-/, // justify-content
|
|
371
|
+
/^self-/, // align-self
|
|
372
|
+
/^place-/, // place utilities
|
|
373
|
+
/^space-/, // space-x, space-y
|
|
374
|
+
/^overflow/, // overflow
|
|
375
|
+
/^scroll/, // scroll utilities
|
|
376
|
+
/^cursor-/, // cursor
|
|
377
|
+
/^pointer-/, // pointer-events
|
|
378
|
+
/^select-/, // user-select
|
|
379
|
+
/^opacity-/, // opacity
|
|
380
|
+
/^visible/, // visibility
|
|
381
|
+
/^invisible/, // visibility
|
|
382
|
+
/^z-/, // z-index
|
|
383
|
+
/^inset/, // inset utilities
|
|
384
|
+
/^top-/, // positioning
|
|
385
|
+
/^right-/, // positioning
|
|
386
|
+
/^bottom-/, // positioning
|
|
387
|
+
/^left-/, // positioning
|
|
388
|
+
/^static/, // position
|
|
389
|
+
/^fixed/, // position
|
|
390
|
+
/^absolute/, // position
|
|
391
|
+
/^relative/, // position
|
|
392
|
+
/^sticky/, // position
|
|
393
|
+
/^transition/, // transitions
|
|
394
|
+
/^duration-/, // duration
|
|
395
|
+
/^ease-/, // timing function
|
|
396
|
+
/^delay-/, // delay
|
|
397
|
+
/^animate-/, // animations
|
|
398
|
+
/^transform/, // transform
|
|
399
|
+
/^scale-/, // scale
|
|
400
|
+
/^rotate-/, // rotate
|
|
401
|
+
/^translate-/, // translate
|
|
402
|
+
/^skew-/, // skew
|
|
403
|
+
/^origin-/, // transform-origin
|
|
404
|
+
/^appearance-/, // appearance
|
|
405
|
+
/^accent-/, // accent-color
|
|
406
|
+
/^caret-/, // caret-color
|
|
407
|
+
/^fill-/, // SVG fill
|
|
408
|
+
/^stroke-/, // SVG stroke
|
|
409
|
+
/^object-/, // object-fit/position
|
|
410
|
+
/^aspect-/, // aspect-ratio
|
|
411
|
+
/^container/, // container
|
|
412
|
+
/^columns-/, // columns
|
|
413
|
+
/^break-/, // word-break
|
|
414
|
+
/^truncate/, // text-overflow
|
|
415
|
+
/^whitespace-/, // whitespace
|
|
416
|
+
/^list-/, // list-style
|
|
417
|
+
/^decoration-/, // text-decoration
|
|
418
|
+
/^underline/, // underline
|
|
419
|
+
/^overline/, // overline
|
|
420
|
+
/^line-through/, // line-through
|
|
421
|
+
/^no-underline/, // no underline
|
|
422
|
+
/^antialiased/, // font-smoothing
|
|
423
|
+
/^subpixel/, // subpixel-antialiased
|
|
424
|
+
/^italic/, // font-style
|
|
425
|
+
/^not-italic/, // font-style
|
|
426
|
+
/^uppercase/, // text-transform
|
|
427
|
+
/^lowercase/, // text-transform
|
|
428
|
+
/^capitalize/, // text-transform
|
|
429
|
+
/^normal-case/, // text-transform
|
|
430
|
+
/^align-/, // vertical-align
|
|
431
|
+
/^indent-/, // text-indent
|
|
432
|
+
/^content-/, // content
|
|
433
|
+
/^will-change/, // will-change
|
|
434
|
+
/^hover:/, // hover states
|
|
435
|
+
/^focus:/, // focus states
|
|
436
|
+
/^focus-within:/, // focus-within states
|
|
437
|
+
/^focus-visible:/, // focus-visible states
|
|
438
|
+
/^active:/, // active states
|
|
439
|
+
/^visited:/, // visited states
|
|
440
|
+
/^disabled:/, // disabled states
|
|
441
|
+
/^checked:/, // checked states
|
|
442
|
+
/^group-/, // group utilities
|
|
443
|
+
/^peer-/, // peer utilities
|
|
444
|
+
/^dark:/, // dark mode
|
|
445
|
+
/^light:/, // light mode
|
|
446
|
+
/^motion-/, // motion utilities
|
|
447
|
+
/^print:/, // print utilities
|
|
448
|
+
/^portrait:/, // orientation
|
|
449
|
+
/^landscape:/, // orientation
|
|
450
|
+
/^sm:|^md:|^lg:|^xl:|^2xl:/, // responsive prefixes
|
|
451
|
+
/^data-\[/, // data attribute variants
|
|
452
|
+
/^aria-/, // aria variants
|
|
453
|
+
/^supports-/, // supports variants
|
|
454
|
+
/^has-/, // has variants
|
|
455
|
+
/^group$/, // group class itself
|
|
456
|
+
/^peer$/, // peer class itself
|
|
457
|
+
/^sr-only/, // screen reader only
|
|
458
|
+
/^not-sr-only/, // not screen reader only
|
|
459
|
+
/^isolate/, // isolation
|
|
460
|
+
/^block$/, // display
|
|
461
|
+
/^inline/, // display
|
|
462
|
+
/^hidden$/, // display
|
|
463
|
+
/^table/, // display table
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
const isTailwindUtility = (cls: string) =>
|
|
467
|
+
tailwindPatterns.some(pattern => pattern.test(cls));
|
|
468
|
+
|
|
469
|
+
// Only use classes that are NOT Tailwind utilities (likely custom/semantic)
|
|
470
|
+
const semanticClasses = focusedElement.className.split(/\s+/)
|
|
471
|
+
.filter(c => c.length > 3 && !isTailwindUtility(c));
|
|
472
|
+
|
|
473
|
+
// Only proceed if we found semantic classes
|
|
474
|
+
if (semanticClasses.length > 0) {
|
|
475
|
+
for (const cls of semanticClasses) {
|
|
476
|
+
for (let i = 0; i < lines.length; i++) {
|
|
477
|
+
if (lines[i].includes(cls)) {
|
|
478
|
+
return {
|
|
479
|
+
lineNumber: i + 1,
|
|
480
|
+
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
481
|
+
confidence: 'medium',
|
|
482
|
+
matchedBy: `semantic className "${cls}"`
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Log when we filtered out all classes (helpful for debugging)
|
|
490
|
+
if (semanticClasses.length === 0 && focusedElement.className.trim().length > 0) {
|
|
491
|
+
console.log('[findElementLineInFile] All classes filtered as Tailwind utilities:',
|
|
492
|
+
focusedElement.className.substring(0, 100));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
228
499
|
/**
|
|
229
500
|
* PHASE 0: Deterministic Element ID Search (Cursor-style)
|
|
230
501
|
* Grep entire codebase for the element ID. If found in multiple files,
|
|
@@ -1299,12 +1570,21 @@ User Request: "${userPrompt}"
|
|
|
1299
1570
|
if (recommendedFileContent) {
|
|
1300
1571
|
const content = recommendedFileContent.content;
|
|
1301
1572
|
|
|
1302
|
-
// Search for element
|
|
1303
|
-
|
|
1304
|
-
|
|
1573
|
+
// Search for focused element in the file using multiple strategies
|
|
1574
|
+
// Priority: DOM id > textContent > className patterns
|
|
1575
|
+
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
1576
|
+
if (focusedElements && focusedElements.length > 0) {
|
|
1305
1577
|
for (const el of focusedElements) {
|
|
1306
|
-
|
|
1307
|
-
if (
|
|
1578
|
+
elementLocation = findElementLineInFile(content, el);
|
|
1579
|
+
if (elementLocation) {
|
|
1580
|
+
debugLog("Found focused element in file", {
|
|
1581
|
+
matchedBy: elementLocation.matchedBy,
|
|
1582
|
+
lineNumber: elementLocation.lineNumber,
|
|
1583
|
+
confidence: elementLocation.confidence,
|
|
1584
|
+
file: recommendedFileContent.path,
|
|
1585
|
+
});
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1308
1588
|
}
|
|
1309
1589
|
}
|
|
1310
1590
|
|
|
@@ -1319,22 +1599,49 @@ User Request: "${userPrompt}"
|
|
|
1319
1599
|
textContent += `\n`;
|
|
1320
1600
|
}
|
|
1321
1601
|
|
|
1322
|
-
// Add precise targeting
|
|
1323
|
-
if (
|
|
1602
|
+
// Add precise targeting with line number and snippet
|
|
1603
|
+
if (elementLocation) {
|
|
1324
1604
|
textContent += `
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
→
|
|
1605
|
+
══════════════════════════════════════════════════════════════════════════════
|
|
1606
|
+
PRECISE TARGET LOCATION (${elementLocation.confidence} confidence)
|
|
1607
|
+
══════════════════════════════════════════════════════════════════════════════
|
|
1608
|
+
→ Matched by: ${elementLocation.matchedBy}
|
|
1609
|
+
→ Line: ${elementLocation.lineNumber}
|
|
1610
|
+
|
|
1611
|
+
THE USER CLICKED ON THE ELEMENT AT LINE ${elementLocation.lineNumber}.
|
|
1612
|
+
Here is the exact code around that element:
|
|
1613
|
+
\`\`\`
|
|
1614
|
+
${elementLocation.snippet}
|
|
1615
|
+
\`\`\`
|
|
1616
|
+
|
|
1617
|
+
⚠️ IMPORTANT: Modify ONLY the element at line ${elementLocation.lineNumber}, NOT other similar elements in the file.
|
|
1329
1618
|
|
|
1330
1619
|
`;
|
|
1331
|
-
debugLog("Found element ID in file", {
|
|
1332
|
-
matchedId: idMatch.matchedId,
|
|
1333
|
-
lineNumber: idMatch.lineNumber,
|
|
1334
|
-
file: recommendedFileContent.path,
|
|
1335
|
-
});
|
|
1336
1620
|
} else {
|
|
1337
1621
|
textContent += `\n`;
|
|
1622
|
+
debugLog("WARNING: Could not locate focused element in file", {
|
|
1623
|
+
file: recommendedFileContent.path,
|
|
1624
|
+
hint: "The clicked element may be rendered by a child component (e.g., DialogContent, Button component). Check the component's source file instead.",
|
|
1625
|
+
focusedElements: focusedElements.map(el => ({
|
|
1626
|
+
name: el.name,
|
|
1627
|
+
type: el.type,
|
|
1628
|
+
textContent: el.textContent?.substring(0, 30),
|
|
1629
|
+
className: el.className?.substring(0, 50),
|
|
1630
|
+
elementId: el.elementId,
|
|
1631
|
+
}))
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
// Add a hint to the LLM that we couldn't precisely locate the element
|
|
1635
|
+
textContent += `
|
|
1636
|
+
⚠️ WARNING: Could not precisely locate the clicked element in this file.
|
|
1637
|
+
The element may be:
|
|
1638
|
+
- Rendered by a child component (e.g., DialogContent, Button)
|
|
1639
|
+
- Dynamically generated
|
|
1640
|
+
- In a different file imported by this component
|
|
1641
|
+
|
|
1642
|
+
Please proceed with caution and verify you're modifying the correct element.
|
|
1643
|
+
|
|
1644
|
+
`;
|
|
1338
1645
|
}
|
|
1339
1646
|
}
|
|
1340
1647
|
|
|
@@ -1215,10 +1215,11 @@ export function SonanceDevTools() {
|
|
|
1215
1215
|
console.warn("[Apply-First] Failed to persist session:", e);
|
|
1216
1216
|
}
|
|
1217
1217
|
|
|
1218
|
-
//
|
|
1218
|
+
// Force page refresh to ensure changes are visible
|
|
1219
|
+
// Session is already persisted to localStorage, so it survives refresh
|
|
1219
1220
|
setTimeout(() => {
|
|
1220
|
-
|
|
1221
|
-
},
|
|
1221
|
+
window.location.reload();
|
|
1222
|
+
}, 500);
|
|
1222
1223
|
}, [visionFocusedElements]);
|
|
1223
1224
|
|
|
1224
1225
|
// Accept changes - delete backups
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.91",
|
|
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",
|