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 IDs in the file to enable precise targeting
1334
- let idMatch: { lineNumber: number; matchedId: string; snippet: string } | null = null;
1335
- if (focusedElements && focusedElements.length > 0) {
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
- idMatch = findElementIdInFile(content, el.elementId, el.childIds);
1338
- if (idMatch) break;
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 if we found an ID match
1354
- if (idMatch) {
1633
+ // Add precise targeting with line number and snippet
1634
+ if (elementLocation) {
1355
1635
  textContent += `
1356
- PRECISE TARGET (found by element ID):
1357
- ID: "${idMatch.matchedId}"
1358
- → Line: ${idMatch.lineNumber}
1359
- Look for this ID in the code and modify the element that contains it.
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 IDs in the file to enable precise targeting
1303
- let idMatch: { lineNumber: number; matchedId: string; snippet: string } | null = null;
1304
- if (focusedElements && focusedElements.length > 0) {
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
- idMatch = findElementIdInFile(content, el.elementId, el.childIds);
1307
- if (idMatch) break;
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 if we found an ID match
1323
- if (idMatch) {
1602
+ // Add precise targeting with line number and snippet
1603
+ if (elementLocation) {
1324
1604
  textContent += `
1325
- PRECISE TARGET (found by element ID):
1326
- ID: "${idMatch.matchedId}"
1327
- → Line: ${idMatch.lineNumber}
1328
- Look for this ID in the code and modify the element that contains it.
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
- // After a brief delay, assume HMR has completed
1218
+ // Force page refresh to ensure changes are visible
1219
+ // Session is already persisted to localStorage, so it survives refresh
1219
1220
  setTimeout(() => {
1220
- setApplyFirstStatus("reviewing");
1221
- }, 1500);
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.89",
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",