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 3: Distinctive className patterns
326
- if (focusedElement.className) {
327
- // Extract distinctive class names (long, not hover/focus pseudo-classes)
328
- const classes = focusedElement.className.split(/\s+/)
329
- .filter(c => c.length > 8 && !c.startsWith('hover:') && !c.startsWith('focus:') && !c.startsWith('active:'));
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(cls)) {
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: 'medium',
338
- matchedBy: `className contains "${cls}"`
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
- // Try shorter but unique-looking class names (not utility classes)
345
- const uniqueClasses = focusedElement.className.split(/\s+/)
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
- for (const cls of uniqueClasses) {
349
- for (let i = 0; i < lines.length; i++) {
350
- if (lines[i].includes(cls)) {
351
- return {
352
- lineNumber: i + 1,
353
- snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
354
- confidence: 'low',
355
- matchedBy: `className contains "${cls}"`
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
- textContent += `\n`;
1515
- debugLog("Could not locate focused element in file - no ID, textContent, or className match", {
1516
- file: recommendedFileContent.path,
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
- const linesWithNumbers = content.split('\n').map((line, i) =>
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: ${recommendedFileContent.path}
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 += content.length;
1783
+ usedContext += targetContent.length;
1546
1784
  debugLog("Added TARGET COMPONENT with line numbers", {
1547
- path: recommendedFileContent.path,
1548
- lines: content.split('\n').length,
1549
- size: content.length
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 3: Distinctive className patterns
322
- if (focusedElement.className) {
323
- // Extract distinctive class names (long, not hover/focus pseudo-classes)
324
- const classes = focusedElement.className.split(/\s+/)
325
- .filter(c => c.length > 8 && !c.startsWith('hover:') && !c.startsWith('focus:') && !c.startsWith('active:'));
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(cls)) {
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: 'medium',
334
- matchedBy: `className contains "${cls}"`
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
- // Try shorter but unique-looking class names (not utility classes)
341
- const uniqueClasses = focusedElement.className.split(/\s+/)
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
- for (const cls of uniqueClasses) {
345
- for (let i = 0; i < lines.length; i++) {
346
- if (lines[i].includes(cls)) {
347
- return {
348
- lineNumber: i + 1,
349
- snippet: lines.slice(Math.max(0, i - 3), i + 5).join('\n'),
350
- confidence: 'low',
351
- matchedBy: `className contains "${cls}"`
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
- textContent += `\n`;
1484
- debugLog("Could not locate focused element in file - no ID, textContent, or className match", {
1485
- file: recommendedFileContent.path,
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
- const linesWithNumbers = content.split('\n').map((line, i) =>
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: ${recommendedFileContent.path}
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 += content.length;
1752
+ usedContext += targetContent.length;
1515
1753
  debugLog("Added TARGET COMPONENT with line numbers", {
1516
- path: recommendedFileContent.path,
1517
- lines: content.split('\n').length,
1518
- size: content.length
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.90",
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",