sonance-brand-mcp 1.3.90 → 1.3.92
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.
|
@@ -322,46 +322,228 @@ function findElementLineInFile(
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
// PRIORITY
|
|
326
|
-
if (focusedElement.
|
|
327
|
-
//
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
for (const cls of classes) {
|
|
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) {
|
|
332
330
|
for (let i = 0; i < lines.length; i++) {
|
|
333
|
-
if (lines[i].includes(
|
|
331
|
+
if (lines[i].includes(pattern)) {
|
|
334
332
|
return {
|
|
335
333
|
lineNumber: i + 1,
|
|
336
334
|
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
337
|
-
confidence: '
|
|
338
|
-
matchedBy: `
|
|
335
|
+
confidence: 'low',
|
|
336
|
+
matchedBy: `icon pattern "${pattern}"`
|
|
339
337
|
};
|
|
340
338
|
}
|
|
341
339
|
}
|
|
342
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
|
+
];
|
|
343
469
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
.filter(c => c.length > 4 && !c.match(/^(p[xytblr]?-|m[xytblr]?-|w-|h-|bg-|text-|flex|grid|block|hidden)/));
|
|
470
|
+
const isTailwindUtility = (cls: string) =>
|
|
471
|
+
tailwindPatterns.some(pattern => pattern.test(cls));
|
|
347
472
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
+
}
|
|
357
489
|
}
|
|
358
490
|
}
|
|
359
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
|
+
}
|
|
360
498
|
}
|
|
361
499
|
|
|
362
500
|
return null;
|
|
363
501
|
}
|
|
364
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Search imported component files for the focused element
|
|
505
|
+
* Reuses findElementLineInFile() for consistent detection
|
|
506
|
+
*
|
|
507
|
+
* This is called when the element cannot be found in the main target file,
|
|
508
|
+
* allowing us to redirect to the actual file containing the element.
|
|
509
|
+
*/
|
|
510
|
+
function findElementInImportedFiles(
|
|
511
|
+
focusedElement: VisionFocusedElement,
|
|
512
|
+
importedFiles: { path: string; content: string }[]
|
|
513
|
+
): { path: string; lineNumber: number; matchedBy: string; content: string; confidence: string } | null {
|
|
514
|
+
debugLog("Searching imported components for element", {
|
|
515
|
+
elementType: focusedElement.type,
|
|
516
|
+
textContent: focusedElement.textContent?.substring(0, 30),
|
|
517
|
+
filesCount: importedFiles.length
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
for (const file of importedFiles) {
|
|
521
|
+
// Focus on component files (where UI elements live)
|
|
522
|
+
// Skip types, stores, utils, hooks - they don't contain JSX elements
|
|
523
|
+
if (!file.path.includes('components/') && !file.path.includes('/ui/')) continue;
|
|
524
|
+
|
|
525
|
+
const result = findElementLineInFile(file.content, focusedElement);
|
|
526
|
+
if (result && result.confidence !== 'low') {
|
|
527
|
+
debugLog("Found element in imported component", {
|
|
528
|
+
path: file.path,
|
|
529
|
+
lineNumber: result.lineNumber,
|
|
530
|
+
matchedBy: result.matchedBy,
|
|
531
|
+
confidence: result.confidence
|
|
532
|
+
});
|
|
533
|
+
return {
|
|
534
|
+
path: file.path,
|
|
535
|
+
lineNumber: result.lineNumber,
|
|
536
|
+
matchedBy: result.matchedBy,
|
|
537
|
+
content: file.content,
|
|
538
|
+
confidence: result.confidence
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
debugLog("Element not found in any imported component files");
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
|
|
365
547
|
/**
|
|
366
548
|
* PHASE 0: Deterministic Element ID Search (Cursor-style)
|
|
367
549
|
* Grep entire codebase for the element ID. If found in multiple files,
|
|
@@ -1466,6 +1648,8 @@ User Request: "${userPrompt}"
|
|
|
1466
1648
|
// Search for focused element in the file using multiple strategies
|
|
1467
1649
|
// Priority: DOM id > textContent > className patterns
|
|
1468
1650
|
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
1651
|
+
let actualTargetFile = recommendedFileContent; // May change if we redirect
|
|
1652
|
+
|
|
1469
1653
|
if (focusedElements && focusedElements.length > 0) {
|
|
1470
1654
|
for (const el of focusedElements) {
|
|
1471
1655
|
elementLocation = findElementLineInFile(content, el);
|
|
@@ -1479,6 +1663,35 @@ User Request: "${userPrompt}"
|
|
|
1479
1663
|
break;
|
|
1480
1664
|
}
|
|
1481
1665
|
}
|
|
1666
|
+
|
|
1667
|
+
// DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
|
|
1668
|
+
if (!elementLocation) {
|
|
1669
|
+
debugLog("Element not in main file, searching imported components...", {
|
|
1670
|
+
mainFile: recommendedFileContent.path,
|
|
1671
|
+
importedFilesCount: pageContext.componentSources.length
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
const importedMatch = findElementInImportedFiles(
|
|
1675
|
+
focusedElements[0],
|
|
1676
|
+
pageContext.componentSources
|
|
1677
|
+
);
|
|
1678
|
+
|
|
1679
|
+
if (importedMatch) {
|
|
1680
|
+
debugLog("REDIRECT: Element found in imported component", {
|
|
1681
|
+
originalFile: recommendedFileContent.path,
|
|
1682
|
+
redirectTo: importedMatch.path,
|
|
1683
|
+
matchedBy: importedMatch.matchedBy,
|
|
1684
|
+
lineNumber: importedMatch.lineNumber
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
// Switch target file to where element actually is
|
|
1688
|
+
actualTargetFile = {
|
|
1689
|
+
path: importedMatch.path,
|
|
1690
|
+
content: importedMatch.content
|
|
1691
|
+
};
|
|
1692
|
+
elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1482
1695
|
}
|
|
1483
1696
|
|
|
1484
1697
|
// Build focused elements section with precise targeting info
|
|
@@ -1511,9 +1724,11 @@ ${elementLocation.snippet}
|
|
|
1511
1724
|
|
|
1512
1725
|
`;
|
|
1513
1726
|
} else {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1727
|
+
// Element NOT found in main file OR any imported components
|
|
1728
|
+
// BLOCK the LLM from guessing - require empty modifications
|
|
1729
|
+
debugLog("BLOCK: Could not locate focused element anywhere", {
|
|
1730
|
+
mainFile: recommendedFileContent.path,
|
|
1731
|
+
searchedImports: pageContext.componentSources.length,
|
|
1517
1732
|
focusedElements: focusedElements.map(el => ({
|
|
1518
1733
|
name: el.name,
|
|
1519
1734
|
type: el.type,
|
|
@@ -1522,16 +1737,39 @@ ${elementLocation.snippet}
|
|
|
1522
1737
|
elementId: el.elementId,
|
|
1523
1738
|
}))
|
|
1524
1739
|
});
|
|
1740
|
+
|
|
1741
|
+
// STRONG BLOCK instruction - tell LLM to NOT guess
|
|
1742
|
+
textContent += `
|
|
1743
|
+
⛔ STOP: CANNOT LOCATE THE CLICKED ELEMENT
|
|
1744
|
+
|
|
1745
|
+
The user clicked on a specific element, but it could NOT be found in:
|
|
1746
|
+
- ${recommendedFileContent.path} (main target file)
|
|
1747
|
+
- Any of the ${pageContext.componentSources.length} imported component files
|
|
1748
|
+
|
|
1749
|
+
The element may be:
|
|
1750
|
+
- Deeply nested in a component not in the import tree
|
|
1751
|
+
- Dynamically generated at runtime
|
|
1752
|
+
- Part of a third-party library component
|
|
1753
|
+
|
|
1754
|
+
DO NOT GUESS. Return this exact response:
|
|
1755
|
+
{
|
|
1756
|
+
"modifications": [],
|
|
1757
|
+
"explanation": "Could not locate the clicked element in this file or any of its ${pageContext.componentSources.length} imported components. The element may be rendered by a deeply nested or third-party component."
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
`;
|
|
1525
1761
|
}
|
|
1526
1762
|
}
|
|
1527
1763
|
|
|
1528
1764
|
// Add line numbers to make it easy for LLM to reference exact code
|
|
1529
|
-
|
|
1765
|
+
// Use actualTargetFile which may have been redirected to an imported component
|
|
1766
|
+
const targetContent = actualTargetFile.content;
|
|
1767
|
+
const linesWithNumbers = targetContent.split('\n').map((line, i) =>
|
|
1530
1768
|
`${String(i + 1).padStart(4, ' ')}| ${line}`
|
|
1531
1769
|
).join('\n');
|
|
1532
1770
|
|
|
1533
1771
|
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
1534
|
-
FILE TO EDIT: ${
|
|
1772
|
+
FILE TO EDIT: ${actualTargetFile.path}
|
|
1535
1773
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1536
1774
|
|
|
1537
1775
|
IMPORTANT: Copy code EXACTLY as shown below (including line numbers for reference).
|
|
@@ -1542,11 +1780,12 @@ ${linesWithNumbers}
|
|
|
1542
1780
|
\`\`\`
|
|
1543
1781
|
|
|
1544
1782
|
`;
|
|
1545
|
-
usedContext +=
|
|
1783
|
+
usedContext += targetContent.length;
|
|
1546
1784
|
debugLog("Added TARGET COMPONENT with line numbers", {
|
|
1547
|
-
path:
|
|
1548
|
-
lines:
|
|
1549
|
-
size:
|
|
1785
|
+
path: actualTargetFile.path,
|
|
1786
|
+
lines: targetContent.split('\n').length,
|
|
1787
|
+
size: targetContent.length,
|
|
1788
|
+
wasRedirected: actualTargetFile.path !== recommendedFileContent.path
|
|
1550
1789
|
});
|
|
1551
1790
|
} else if (pageContext.pageContent) {
|
|
1552
1791
|
// Fallback: use page file if no recommended file
|
|
@@ -318,43 +318,225 @@ function findElementLineInFile(
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
// PRIORITY
|
|
322
|
-
if (focusedElement.
|
|
323
|
-
//
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for (const cls of classes) {
|
|
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) {
|
|
328
326
|
for (let i = 0; i < lines.length; i++) {
|
|
329
|
-
if (lines[i].includes(
|
|
327
|
+
if (lines[i].includes(pattern)) {
|
|
330
328
|
return {
|
|
331
329
|
lineNumber: i + 1,
|
|
332
330
|
snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
|
|
333
|
-
confidence: '
|
|
334
|
-
matchedBy: `
|
|
331
|
+
confidence: 'low',
|
|
332
|
+
matchedBy: `icon pattern "${pattern}"`
|
|
335
333
|
};
|
|
336
334
|
}
|
|
337
335
|
}
|
|
338
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
|
+
];
|
|
339
465
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
.filter(c => c.length > 4 && !c.match(/^(p[xytblr]?-|m[xytblr]?-|w-|h-|bg-|text-|flex|grid|block|hidden)/));
|
|
466
|
+
const isTailwindUtility = (cls: string) =>
|
|
467
|
+
tailwindPatterns.some(pattern => pattern.test(cls));
|
|
343
468
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
+
}
|
|
353
485
|
}
|
|
354
486
|
}
|
|
355
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
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Search imported component files for the focused element
|
|
501
|
+
* Reuses findElementLineInFile() for consistent detection
|
|
502
|
+
*
|
|
503
|
+
* This is called when the element cannot be found in the main target file,
|
|
504
|
+
* allowing us to redirect to the actual file containing the element.
|
|
505
|
+
*/
|
|
506
|
+
function findElementInImportedFiles(
|
|
507
|
+
focusedElement: VisionFocusedElement,
|
|
508
|
+
importedFiles: { path: string; content: string }[]
|
|
509
|
+
): { path: string; lineNumber: number; matchedBy: string; content: string; confidence: string } | null {
|
|
510
|
+
debugLog("Searching imported components for element", {
|
|
511
|
+
elementType: focusedElement.type,
|
|
512
|
+
textContent: focusedElement.textContent?.substring(0, 30),
|
|
513
|
+
filesCount: importedFiles.length
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
for (const file of importedFiles) {
|
|
517
|
+
// Focus on component files (where UI elements live)
|
|
518
|
+
// Skip types, stores, utils, hooks - they don't contain JSX elements
|
|
519
|
+
if (!file.path.includes('components/') && !file.path.includes('/ui/')) continue;
|
|
520
|
+
|
|
521
|
+
const result = findElementLineInFile(file.content, focusedElement);
|
|
522
|
+
if (result && result.confidence !== 'low') {
|
|
523
|
+
debugLog("Found element in imported component", {
|
|
524
|
+
path: file.path,
|
|
525
|
+
lineNumber: result.lineNumber,
|
|
526
|
+
matchedBy: result.matchedBy,
|
|
527
|
+
confidence: result.confidence
|
|
528
|
+
});
|
|
529
|
+
return {
|
|
530
|
+
path: file.path,
|
|
531
|
+
lineNumber: result.lineNumber,
|
|
532
|
+
matchedBy: result.matchedBy,
|
|
533
|
+
content: file.content,
|
|
534
|
+
confidence: result.confidence
|
|
535
|
+
};
|
|
536
|
+
}
|
|
356
537
|
}
|
|
357
538
|
|
|
539
|
+
debugLog("Element not found in any imported component files");
|
|
358
540
|
return null;
|
|
359
541
|
}
|
|
360
542
|
|
|
@@ -1435,6 +1617,8 @@ User Request: "${userPrompt}"
|
|
|
1435
1617
|
// Search for focused element in the file using multiple strategies
|
|
1436
1618
|
// Priority: DOM id > textContent > className patterns
|
|
1437
1619
|
let elementLocation: { lineNumber: number; snippet: string; confidence: 'high' | 'medium' | 'low'; matchedBy: string } | null = null;
|
|
1620
|
+
let actualTargetFile = recommendedFileContent; // May change if we redirect
|
|
1621
|
+
|
|
1438
1622
|
if (focusedElements && focusedElements.length > 0) {
|
|
1439
1623
|
for (const el of focusedElements) {
|
|
1440
1624
|
elementLocation = findElementLineInFile(content, el);
|
|
@@ -1448,6 +1632,35 @@ User Request: "${userPrompt}"
|
|
|
1448
1632
|
break;
|
|
1449
1633
|
}
|
|
1450
1634
|
}
|
|
1635
|
+
|
|
1636
|
+
// DYNAMIC IMPORT SEARCH: If not found in main file, search imported components
|
|
1637
|
+
if (!elementLocation) {
|
|
1638
|
+
debugLog("Element not in main file, searching imported components...", {
|
|
1639
|
+
mainFile: recommendedFileContent.path,
|
|
1640
|
+
importedFilesCount: pageContext.componentSources.length
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
const importedMatch = findElementInImportedFiles(
|
|
1644
|
+
focusedElements[0],
|
|
1645
|
+
pageContext.componentSources
|
|
1646
|
+
);
|
|
1647
|
+
|
|
1648
|
+
if (importedMatch) {
|
|
1649
|
+
debugLog("REDIRECT: Element found in imported component", {
|
|
1650
|
+
originalFile: recommendedFileContent.path,
|
|
1651
|
+
redirectTo: importedMatch.path,
|
|
1652
|
+
matchedBy: importedMatch.matchedBy,
|
|
1653
|
+
lineNumber: importedMatch.lineNumber
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
// Switch target file to where element actually is
|
|
1657
|
+
actualTargetFile = {
|
|
1658
|
+
path: importedMatch.path,
|
|
1659
|
+
content: importedMatch.content
|
|
1660
|
+
};
|
|
1661
|
+
elementLocation = findElementLineInFile(importedMatch.content, focusedElements[0]);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1451
1664
|
}
|
|
1452
1665
|
|
|
1453
1666
|
// Build focused elements section with precise targeting info
|
|
@@ -1480,9 +1693,11 @@ ${elementLocation.snippet}
|
|
|
1480
1693
|
|
|
1481
1694
|
`;
|
|
1482
1695
|
} else {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1696
|
+
// Element NOT found in main file OR any imported components
|
|
1697
|
+
// BLOCK the LLM from guessing - require empty modifications
|
|
1698
|
+
debugLog("BLOCK: Could not locate focused element anywhere", {
|
|
1699
|
+
mainFile: recommendedFileContent.path,
|
|
1700
|
+
searchedImports: pageContext.componentSources.length,
|
|
1486
1701
|
focusedElements: focusedElements.map(el => ({
|
|
1487
1702
|
name: el.name,
|
|
1488
1703
|
type: el.type,
|
|
@@ -1491,16 +1706,39 @@ ${elementLocation.snippet}
|
|
|
1491
1706
|
elementId: el.elementId,
|
|
1492
1707
|
}))
|
|
1493
1708
|
});
|
|
1709
|
+
|
|
1710
|
+
// STRONG BLOCK instruction - tell LLM to NOT guess
|
|
1711
|
+
textContent += `
|
|
1712
|
+
⛔ STOP: CANNOT LOCATE THE CLICKED ELEMENT
|
|
1713
|
+
|
|
1714
|
+
The user clicked on a specific element, but it could NOT be found in:
|
|
1715
|
+
- ${recommendedFileContent.path} (main target file)
|
|
1716
|
+
- Any of the ${pageContext.componentSources.length} imported component files
|
|
1717
|
+
|
|
1718
|
+
The element may be:
|
|
1719
|
+
- Deeply nested in a component not in the import tree
|
|
1720
|
+
- Dynamically generated at runtime
|
|
1721
|
+
- Part of a third-party library component
|
|
1722
|
+
|
|
1723
|
+
DO NOT GUESS. Return this exact response:
|
|
1724
|
+
{
|
|
1725
|
+
"modifications": [],
|
|
1726
|
+
"explanation": "Could not locate the clicked element in this file or any of its ${pageContext.componentSources.length} imported components. The element may be rendered by a deeply nested or third-party component."
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
`;
|
|
1494
1730
|
}
|
|
1495
1731
|
}
|
|
1496
1732
|
|
|
1497
1733
|
// Add line numbers to make it easy for LLM to reference exact code
|
|
1498
|
-
|
|
1734
|
+
// Use actualTargetFile which may have been redirected to an imported component
|
|
1735
|
+
const targetContent = actualTargetFile.content;
|
|
1736
|
+
const linesWithNumbers = targetContent.split('\n').map((line, i) =>
|
|
1499
1737
|
`${String(i + 1).padStart(4, ' ')}| ${line}`
|
|
1500
1738
|
).join('\n');
|
|
1501
1739
|
|
|
1502
1740
|
textContent += `═══════════════════════════════════════════════════════════════════════════════
|
|
1503
|
-
FILE TO EDIT: ${
|
|
1741
|
+
FILE TO EDIT: ${actualTargetFile.path}
|
|
1504
1742
|
═══════════════════════════════════════════════════════════════════════════════
|
|
1505
1743
|
|
|
1506
1744
|
IMPORTANT: Copy code EXACTLY as shown below (including line numbers for reference).
|
|
@@ -1511,11 +1749,12 @@ ${linesWithNumbers}
|
|
|
1511
1749
|
\`\`\`
|
|
1512
1750
|
|
|
1513
1751
|
`;
|
|
1514
|
-
usedContext +=
|
|
1752
|
+
usedContext += targetContent.length;
|
|
1515
1753
|
debugLog("Added TARGET COMPONENT with line numbers", {
|
|
1516
|
-
path:
|
|
1517
|
-
lines:
|
|
1518
|
-
size:
|
|
1754
|
+
path: actualTargetFile.path,
|
|
1755
|
+
lines: targetContent.split('\n').length,
|
|
1756
|
+
size: targetContent.length,
|
|
1757
|
+
wasRedirected: actualTargetFile.path !== recommendedFileContent.path
|
|
1519
1758
|
});
|
|
1520
1759
|
} else if (pageContext.pageContent) {
|
|
1521
1760
|
// Fallback: use page file if no recommended file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.92",
|
|
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",
|