sonance-brand-mcp 1.3.110 → 1.3.111

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.
@@ -1,15 +1,17 @@
1
1
  "use client";
2
2
 
3
3
  import React, { useState, useMemo, useCallback, useEffect } from "react";
4
- import { Box, X, RotateCcw } from "lucide-react";
4
+ import { Box, X, RotateCcw, MousePointer, Sparkles, Info, MessageCircle } from "lucide-react";
5
5
  import { cn } from "../../../lib/utils";
6
- import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus } from "../types";
6
+ import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus, ElementFilters } from "../types";
7
7
  import { COMPONENT_CONFIG_MAP, getVisibleSections } from "../constants";
8
8
  import { componentSnippets, ThemeConfig } from "../../../lib/brand-system";
9
9
  import { ChatInterface } from "../components/ChatInterface";
10
10
  import { DiffPreview } from "../components/DiffPreview";
11
11
  import { VisionDiffPreview } from "../components/VisionDiffPreview";
12
12
  import { ApplyFirstPreview } from "../components/ApplyFirstPreview";
13
+ import { PropertiesPanel, PropertyEdits } from "../components/PropertiesPanel";
14
+ import { useElementFromCoordinates } from "../hooks/useComputedStyles";
13
15
 
14
16
  type ComponentsViewMode = "visual" | "inspector";
15
17
 
@@ -58,6 +60,12 @@ export interface ComponentsPanelProps {
58
60
  onApplyFirstRevert?: () => void;
59
61
  onApplyFirstForceClear?: () => void;
60
62
  onApplyPreview?: () => void; // Apply previewed modifications to files
63
+ // Unified element filters (new)
64
+ elementFilters?: ElementFilters;
65
+ selectedLogoId?: string | null;
66
+ onSelectLogo?: (logoId: string | null) => void;
67
+ selectedTextId?: string | null;
68
+ onSelectText?: (textId: string | null) => void;
61
69
  }
62
70
 
63
71
  export function ComponentsPanel({
@@ -99,6 +107,12 @@ export function ComponentsPanel({
99
107
  onApplyFirstRevert,
100
108
  onApplyFirstForceClear,
101
109
  onApplyPreview,
110
+ // Unified element filters
111
+ elementFilters,
112
+ selectedLogoId,
113
+ onSelectLogo,
114
+ selectedTextId,
115
+ onSelectText,
102
116
  }: ComponentsPanelProps) {
103
117
  // Auto-activate inspector when entering this tab
104
118
  useEffect(() => {
@@ -404,45 +418,191 @@ export function ComponentsPanel({
404
418
  }
405
419
  };
406
420
 
421
+ // Handle property changes from PropertiesPanel (AI-assisted save)
422
+ const handlePropertySave = useCallback(async (edits: PropertyEdits, element: VisionFocusedElement) => {
423
+ // Build a natural language description of the changes
424
+ const changeDescriptions: string[] = [];
425
+
426
+ if (edits.width) changeDescriptions.push(`width to ${edits.width}`);
427
+ if (edits.height) changeDescriptions.push(`height to ${edits.height}`);
428
+ if (edits.opacity) changeDescriptions.push(`opacity to ${edits.opacity}%`);
429
+ if (edits.borderRadius) changeDescriptions.push(`border-radius to ${edits.borderRadius}`);
430
+ if (edits.fontSize) changeDescriptions.push(`font-size to ${edits.fontSize}`);
431
+ if (edits.fontWeight) changeDescriptions.push(`font-weight to ${edits.fontWeight}`);
432
+ if (edits.lineHeight) changeDescriptions.push(`line-height to ${edits.lineHeight}`);
433
+ if (edits.letterSpacing) changeDescriptions.push(`letter-spacing to ${edits.letterSpacing}`);
434
+ if (edits.color) changeDescriptions.push(`text color to ${edits.color}`);
435
+ if (edits.backgroundColor) changeDescriptions.push(`background color to ${edits.backgroundColor}`);
436
+ if (edits.padding) changeDescriptions.push(`padding to ${edits.padding}`);
437
+ if (edits.margin) changeDescriptions.push(`margin to ${edits.margin}`);
438
+ if (edits.gap) changeDescriptions.push(`gap to ${edits.gap}`);
439
+
440
+ if (changeDescriptions.length === 0) return;
441
+
442
+ const prompt = `Change the ${changeDescriptions.join(", ")} for this ${element.name} element.`;
443
+
444
+ try {
445
+ const response = await fetch("/api/sonance-vision-apply", {
446
+ method: "POST",
447
+ headers: { "Content-Type": "application/json" },
448
+ body: JSON.stringify({
449
+ action: "preview",
450
+ focusedElements: [element],
451
+ userPrompt: prompt,
452
+ propertyEdits: edits,
453
+ }),
454
+ });
455
+
456
+ const result = await response.json();
457
+
458
+ if (result.success && onApplyFirstComplete) {
459
+ // Trigger the apply-first preview flow
460
+ onApplyFirstComplete({
461
+ id: `property-edit-${Date.now()}`,
462
+ userPrompt: prompt,
463
+ focusedElements: [element],
464
+ originalCode: result.originalCode || "",
465
+ modifiedCode: result.modifiedCode || "",
466
+ filePath: result.filePath || "",
467
+ diff: result.diff || "",
468
+ backupId: result.backupId,
469
+ createdAt: new Date(),
470
+ });
471
+ }
472
+ } catch (error) {
473
+ console.error("Failed to save property changes:", error);
474
+ }
475
+ }, [onApplyFirstComplete]);
476
+
477
+ // Get the first focused element for properties panel
478
+ const primaryFocusedElement = visionFocusedElements.length > 0 ? visionFocusedElements[0] : null;
479
+ const focusedCoords = primaryFocusedElement?.coordinates || null;
480
+ const computedStyles = useElementFromCoordinates(focusedCoords);
481
+
482
+ // Determine if we should show the properties panel (Vision mode with focused elements)
483
+ const showPropertiesPanel = visionMode && visionFocusedElements.length > 0;
484
+
485
+ // Panel tab state - "details" or "chat"
486
+ type PanelTab = "details" | "chat";
487
+ const [panelTab, setPanelTab] = useState<PanelTab>("details");
488
+
489
+ // Auto-switch to details when an element is focused
490
+ useEffect(() => {
491
+ if (showPropertiesPanel) {
492
+ setPanelTab("details");
493
+ }
494
+ }, [showPropertiesPanel]);
495
+
407
496
  return (
408
- <div className="space-y-4">
409
- {/* Selected Component State */}
410
- {selectedComponentType !== "all" && (
411
- <>
412
- {/* Selected Component Header */}
413
- <div className="p-3 rounded border border-[#00A3E1] bg-[#00A3E1]/5">
497
+ <div className="flex flex-col h-full">
498
+ {/* ===== TAB BAR - Cursor-style underline tabs ===== */}
499
+ <div className="flex items-center gap-1 px-3 py-1.5 bg-transparent border-b border-gray-200/50 dark:border-white/10 flex-shrink-0">
500
+ <button
501
+ onClick={() => setPanelTab("details")}
502
+ className={cn(
503
+ "relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 bg-transparent",
504
+ panelTab === "details"
505
+ ? "text-gray-900 dark:text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
506
+ : "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100/30 dark:hover:bg-white/10"
507
+ )}
508
+ >
509
+ <Info className="h-3 w-3" />
510
+ <span>Details</span>
511
+ </button>
512
+ <button
513
+ onClick={() => setPanelTab("chat")}
514
+ className={cn(
515
+ "relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 bg-transparent",
516
+ panelTab === "chat"
517
+ ? "text-gray-900 dark:text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
518
+ : "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100/30 dark:hover:bg-white/10"
519
+ )}
520
+ >
521
+ <MessageCircle className="h-3 w-3" />
522
+ <span>AI Chat</span>
523
+ {visionMode && visionFocusedElements.length > 0 && (
524
+ <span className="ml-0.5 min-w-[16px] h-4 px-1 flex items-center justify-center text-[9px] font-semibold bg-purple-500/20 text-purple-600 dark:text-purple-400 rounded-full">
525
+ {visionFocusedElements.length}
526
+ </span>
527
+ )}
528
+ </button>
529
+ </div>
530
+
531
+ {/* ===== TAB CONTENT ===== */}
532
+ <div className="flex-1 overflow-y-auto min-h-0 bg-background">
533
+ {/* DETAILS TAB */}
534
+ {panelTab === "details" && (
535
+ <>
536
+ {/* Vision Mode: Properties Inspector */}
537
+ {showPropertiesPanel && (
538
+ <div className="border-b border-border">
539
+ <PropertiesPanel
540
+ element={primaryFocusedElement}
541
+ styles={computedStyles}
542
+ onSaveChanges={handlePropertySave}
543
+ />
544
+ {/* Multiple Selections Indicator */}
545
+ {visionFocusedElements.length > 1 && (
546
+ <div className="px-2 py-1.5 bg-purple-50 dark:bg-purple-900/30 border-t border-purple-100 dark:border-purple-800">
547
+ <p className="text-[10px] text-purple-600 dark:text-purple-400 font-medium">
548
+ +{visionFocusedElements.length - 1} more selected
549
+ </p>
550
+ </div>
551
+ )}
552
+ </div>
553
+ )}
554
+
555
+ {/* Empty State for Vision Mode */}
556
+ {visionMode && visionFocusedElements.length === 0 && (
557
+ <div className="flex flex-col items-center justify-center py-6 text-center px-3">
558
+ <div className="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center mb-2">
559
+ <MousePointer className="h-5 w-5 text-purple-500 dark:text-purple-400" />
560
+ </div>
561
+ <p className="text-[11px] font-medium text-foreground mb-1">
562
+ Vision Mode Active
563
+ </p>
564
+ <p className="text-[10px] text-foreground-secondary">
565
+ Click elements on the page to inspect and select them for AI-powered editing.
566
+ </p>
567
+ </div>
568
+ )}
569
+
570
+ {/* Non-Vision Mode: Component Selection Interface */}
571
+ {!visionMode && selectedComponentType !== "all" && (
572
+ <div className="space-y-3 p-2">
573
+ {/* Selected Component Header - Compact */}
574
+ <div className="p-2 rounded border border-[#00A3E1] bg-[#00A3E1]/5">
414
575
  <div className="flex items-center justify-between">
415
- <div className="flex items-center gap-2">
416
- <Box className="h-4 w-4 text-[#00A3E1]" />
576
+ <div className="flex items-center gap-1.5">
577
+ <Box className="h-3.5 w-3.5 text-[#00A3E1]" />
417
578
  <div className="flex flex-col">
418
- <span id="brand-panel-span-selectedcomponentnam" className="text-sm font-semibold text-gray-900">{selectedComponentName}</span>
579
+ <span id="brand-panel-span-selectedcomponentnam" className="text-[11px] font-semibold text-foreground">{selectedComponentName}</span>
419
580
  {selectedVariantId && (
420
- <span id="brand-panel-span-variant-selectedvari" className="text-[10px] text-gray-500 font-mono">
421
- Variant: {selectedVariantId.substring(0, 6)}
581
+ <span id="brand-panel-span-variant-selectedvari" className="text-[9px] text-foreground-secondary font-mono">
582
+ #{selectedVariantId.substring(0, 6)}
422
583
  </span>
423
584
  )}
424
585
  </div>
425
586
  </div>
426
587
  <button
427
588
  onClick={() => onSelectComponentType("all")}
428
- className="text-xs text-gray-400 hover:text-gray-600"
589
+ className="p-1 text-foreground-muted hover:text-foreground hover:bg-secondary rounded"
429
590
  >
430
- <X className="h-4 w-4" />
591
+ <X className="h-3.5 w-3.5" />
431
592
  </button>
432
593
  </div>
433
594
 
434
- {/* Stats */}
435
- <div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
436
- <span id="brand-panel-span"><strong className="text-gray-700">{pageCount}</strong> on this page</span>
595
+ {/* Stats - Compact */}
596
+ <div className="flex items-center gap-3 mt-1.5 text-[10px] text-foreground-secondary">
597
+ <span id="brand-panel-span"><strong className="text-foreground">{pageCount}</strong> on page</span>
437
598
  {appCount > 0 && (
438
- <span id="brand-panel-span"><strong className="text-gray-700">{appCount}</strong> in entire app</span>
599
+ <span id="brand-panel-span"><strong className="text-foreground">{appCount}</strong> in app</span>
439
600
  )}
440
601
  </div>
441
602
  </div>
442
603
 
443
- {/* Variants List - show all unique variants */}
604
+ {/* Variants Grid - Compact */}
444
605
  {(() => {
445
- // Get unique variants for this component type
446
606
  const variantMap = new Map<string, { count: number; variantId: string }>();
447
607
  pageInstances.forEach((el) => {
448
608
  if (el.variantId) {
@@ -459,32 +619,32 @@ export function ComponentsPanel({
459
619
 
460
620
  if (variants.length > 1) {
461
621
  return (
462
- <div className="space-y-2">
622
+ <div className="space-y-1.5">
463
623
  <div className="flex items-center justify-between">
464
- <span className="text-xs font-medium text-gray-600">Variants ({variants.length})</span>
624
+ <span className="text-[10px] font-medium text-foreground-secondary">Variants</span>
465
625
  {selectedVariantId && (
466
626
  <button
467
627
  onClick={() => onSelectVariant(null)}
468
- className="text-xs text-[#00A3E1] hover:underline"
628
+ className="text-[10px] text-[#00A3E1] hover:underline"
469
629
  >
470
- Clear selection
630
+ Clear
471
631
  </button>
472
632
  )}
473
633
  </div>
474
- <div className="grid grid-cols-2 gap-2">
634
+ <div className="flex flex-wrap gap-1">
475
635
  {variants.map(({ variantId, count }) => (
476
636
  <button
477
637
  key={variantId}
478
638
  onClick={() => onSelectVariant(selectedVariantId === variantId ? null : variantId)}
479
639
  className={cn(
480
- "p-2 rounded border text-left transition-colors",
640
+ "px-1.5 py-1 rounded text-[9px] font-mono transition-colors",
481
641
  selectedVariantId === variantId
482
- ? "border-[#00A3E1] bg-[#00A3E1]/10"
483
- : "border-gray-200 hover:border-gray-300 bg-white"
642
+ ? "bg-[#00A3E1] text-white"
643
+ : "bg-secondary text-foreground-secondary hover:bg-secondary-hover"
484
644
  )}
485
645
  >
486
- <span className="text-xs font-mono text-gray-700">#{variantId.substring(0, 6)}</span>
487
- <span className="text-[10px] text-gray-400 ml-1">({count})</span>
646
+ #{variantId.substring(0, 4)}
647
+ <span className="opacity-60 ml-0.5">({count})</span>
488
648
  </button>
489
649
  ))}
490
650
  </div>
@@ -495,137 +655,127 @@ export function ComponentsPanel({
495
655
  return null;
496
656
  })()}
497
657
 
498
- {/* No instances on page */}
658
+ {/* No instances warning */}
499
659
  {pageInstances.length === 0 && (
500
- <div className="p-2 rounded border border-amber-200 bg-amber-50 text-center">
501
- <span id="brand-panel-span-no-instances-on-this" className="text-xs text-amber-700">
502
- No instances on this page. Navigate to a page with this component.
660
+ <div className="p-1.5 rounded border border-amber-200 bg-amber-50 text-center">
661
+ <span id="brand-panel-span-no-instances-on-this" className="text-[10px] text-amber-700">
662
+ No instances on this page
503
663
  </span>
504
664
  </div>
505
665
  )}
506
666
 
507
- {/* Edit Scope Toggle - only show when a variant is selected */}
667
+ {/* Edit Scope Toggle - Compact */}
508
668
  {selectedVariantId && (
509
- <div className="flex items-center gap-2 p-2 bg-gray-50 rounded border border-gray-200">
510
- <span className="text-xs text-gray-500">Apply changes to:</span>
511
- <div className="flex gap-1 flex-1">
669
+ <div className="flex items-center gap-1 p-1.5 bg-secondary/50 rounded border border-border">
670
+ <span className="text-[9px] text-foreground-secondary">Scope:</span>
671
+ <div className="flex gap-0.5 flex-1">
512
672
  <button
513
673
  onClick={() => setEditScope("component")}
514
674
  className={cn(
515
- "flex-1 px-2 py-1.5 text-xs font-medium rounded transition-colors",
675
+ "flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
516
676
  editScope === "component"
517
677
  ? "bg-[#00A3E1] text-white"
518
- : "bg-white text-gray-600 border border-gray-200 hover:bg-gray-100"
678
+ : "text-foreground-secondary hover:bg-secondary"
519
679
  )}
520
680
  >
521
- All {selectedComponentName}s
681
+ All
522
682
  </button>
523
683
  <button
524
684
  onClick={() => setEditScope("variant")}
525
685
  className={cn(
526
- "flex-1 px-2 py-1.5 text-xs font-medium rounded transition-colors",
686
+ "flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
527
687
  editScope === "variant"
528
688
  ? "bg-[#00A3E1] text-white"
529
- : "bg-white text-gray-600 border border-gray-200 hover:bg-gray-100"
689
+ : "text-foreground-secondary hover:bg-secondary"
530
690
  )}
531
691
  >
532
- This Variant
692
+ Variant
533
693
  </button>
534
694
  </div>
535
695
  </div>
536
696
  )}
697
+ </div>
698
+ )}
537
699
 
538
- </>
539
- )}
540
-
541
- {/* How it works - only when no component selected */}
542
- {selectedComponentType === "all" && (
543
- <div className="bg-blue-50/50 rounded-lg p-3 border border-blue-100">
544
- <h4 id="h4-how-it-works" className="text-xs font-semibold text-blue-900 mb-2 uppercase tracking-wide">How it works</h4>
545
- <ol className="text-xs text-blue-800 space-y-1 list-decimal list-inside">
546
- <li>Hover & click any element on the page to select it</li>
547
- <li>Describe how you want to change it below</li>
548
- </ol>
549
- </div>
550
- )}
551
-
552
- {/* AI Component Editor */}
553
- <div className="space-y-4 pt-2">
554
- {/* Pending Edit Preview (single-file component edit) */}
700
+ {/* Non-Vision Mode: Empty State */}
701
+ {!visionMode && selectedComponentType === "all" && (
702
+ <div className="flex flex-col items-center justify-center py-6 text-center px-3">
703
+ <div className="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
704
+ <Box className="h-5 w-5 text-foreground-muted" />
705
+ </div>
706
+ <p className="text-[11px] font-medium text-foreground mb-1">
707
+ Select a Component
708
+ </p>
709
+ <p className="text-[10px] text-foreground-secondary">
710
+ Click a component on the page or enable Vision Mode for AI-powered editing.
711
+ </p>
712
+ </div>
713
+ )}
714
+ </>
715
+ )}
716
+
717
+ {/* AI CHAT TAB - Full Height Cursor-Style Interface */}
718
+ {panelTab === "chat" && (
719
+ <div className="flex flex-col h-full bg-background">
720
+ {/* Chat Interface - Always visible with inline diffs */}
721
+ <ChatInterface
722
+ componentType={selectedComponentType}
723
+ componentName={selectedComponentName || selectedComponentType}
724
+ onEditComplete={handleAIEditComplete}
725
+ onSaveRequest={setPendingEdit}
726
+ pendingEdit={pendingEdit}
727
+ onClearPending={handleClearPendingEdit}
728
+ editScope={editScope}
729
+ variantId={selectedVariantId}
730
+ variantStyles={variantStyles}
731
+ visionMode={visionMode}
732
+ visionFocusedElements={visionFocusedElements}
733
+ onVisionEditComplete={onVisionEditComplete}
734
+ onApplyFirstComplete={onApplyFirstComplete}
735
+ />
736
+
737
+ {/* Legacy Diff Previews (fallback for non-inline mode) */}
555
738
  {pendingEdit && (
556
- <DiffPreview
557
- pendingEdit={pendingEdit}
558
- componentType={selectedComponentType}
559
- onSave={handleSaveAIEdit}
560
- onCancel={handleClearPendingEdit}
561
- onPreviewToggle={handlePreviewToggle}
562
- isPreviewActive={isPreviewActive}
563
- saveStatus={aiSaveStatus}
564
- saveMessage={aiSaveMessage}
565
- variantId={selectedVariantId}
566
- />
739
+ <div className="border-t border-border p-2">
740
+ <DiffPreview
741
+ pendingEdit={pendingEdit}
742
+ componentType={selectedComponentType}
743
+ onSave={handleSaveAIEdit}
744
+ onCancel={handleClearPendingEdit}
745
+ onPreviewToggle={handlePreviewToggle}
746
+ isPreviewActive={isPreviewActive}
747
+ saveStatus={aiSaveStatus}
748
+ saveMessage={aiSaveMessage}
749
+ variantId={selectedVariantId}
750
+ />
751
+ </div>
567
752
  )}
568
753
 
569
- {/* Vision Mode Pending Edit Preview (multi-file) - OLD: CSS preview */}
570
754
  {visionPendingEdit && onSaveVisionEdit && onClearVisionPendingEdit && !applyFirstSession && (
571
- <VisionDiffPreview
572
- pendingEdit={visionPendingEdit}
573
- onSave={onSaveVisionEdit}
574
- onCancel={onClearVisionPendingEdit}
575
- />
755
+ <div className="border-t border-border p-2">
756
+ <VisionDiffPreview
757
+ pendingEdit={visionPendingEdit}
758
+ onSave={onSaveVisionEdit}
759
+ onCancel={onClearVisionPendingEdit}
760
+ />
761
+ </div>
576
762
  )}
577
763
 
578
- {/* Preview-First Mode - Shows diff, user accepts/rejects before applying */}
579
764
  {applyFirstSession && onApplyFirstAccept && onApplyFirstRevert && (
580
- <ApplyFirstPreview
581
- session={applyFirstSession}
582
- status={applyFirstStatus}
583
- onAccept={onApplyFirstAccept}
584
- onRevert={onApplyFirstRevert}
585
- onForceClear={onApplyFirstForceClear}
586
- onApplyPreview={onApplyPreview}
587
- />
588
- )}
589
-
590
- {/* AI Chat Interface - hide when any pending edit is present */}
591
- {/* Apply-First mode: Files written immediately with backups for instant HMR preview
592
- User sees structural + CSS changes live, then clicks Accept or Revert
593
- Original files are always backed up and can be restored */}
594
- {!pendingEdit && !visionPendingEdit && !applyFirstSession && (
595
- <ChatInterface
596
- componentType={selectedComponentType}
597
- componentName={selectedComponentName || selectedComponentType}
598
- onEditComplete={handleAIEditComplete}
599
- onSaveRequest={setPendingEdit}
600
- pendingEdit={pendingEdit}
601
- onClearPending={handleClearPendingEdit}
602
- editScope={editScope}
603
- variantId={selectedVariantId}
604
- variantStyles={variantStyles}
605
- visionMode={visionMode}
606
- visionFocusedElements={visionFocusedElements}
607
- onVisionEditComplete={onVisionEditComplete}
608
- onApplyFirstComplete={onApplyFirstComplete}
609
- />
765
+ <div className="border-t border-border p-2">
766
+ <ApplyFirstPreview
767
+ session={applyFirstSession}
768
+ status={applyFirstStatus}
769
+ onAccept={onApplyFirstAccept}
770
+ onRevert={onApplyFirstRevert}
771
+ onForceClear={onApplyFirstForceClear}
772
+ onApplyPreview={onApplyPreview}
773
+ />
774
+ </div>
610
775
  )}
611
-
612
- {/* Reset Button */}
613
- <button
614
- onClick={() => {
615
- onReset();
616
- onResetComponentOverrides();
617
- handleClearPendingEdit();
618
- }}
619
- className={cn(
620
- "w-full flex items-center justify-center gap-2 py-2.5",
621
- "text-xs font-medium text-gray-500 hover:text-gray-700",
622
- "border border-gray-200 rounded hover:bg-gray-50 transition-colors"
623
- )}
624
- >
625
- <RotateCcw className="h-3.5 w-3.5" />
626
- Reset
627
- </button>
628
776
  </div>
777
+ )}
778
+ </div>
629
779
  </div>
630
780
  );
631
781
  }
@@ -1,7 +1,17 @@
1
1
 
2
2
  import React from "react";
3
3
 
4
- export type TabId = "analysis" | "components" | "logos" | "text";
4
+ export type TabId = "analysis" | "elements";
5
+
6
+ // Element type filters for the unified element view
7
+ export interface ElementFilters {
8
+ components: boolean;
9
+ images: boolean;
10
+ text: boolean;
11
+ }
12
+
13
+ // Type for the currently selected element in the unified view
14
+ export type SelectedElementType = "component" | "logo" | "text" | null;
5
15
 
6
16
  export interface TabDefinition {
7
17
  id: TabId;
@@ -108,12 +118,51 @@ export type ComponentStyleOverride = Record<string, ComponentStyle>;
108
118
 
109
119
  // ---- AI Chat Interfaces ----
110
120
 
121
+ /** Action status for inline diff display */
122
+ export type ChatActionStatus = "pending" | "accepted" | "reverted" | "error";
123
+
124
+ /** Action type for inline display in chat messages */
125
+ export type ChatActionType = "diff" | "applied" | "reverted" | "error";
126
+
127
+ /** File change info for inline diff display */
128
+ export interface ChatFileChange {
129
+ path: string;
130
+ diff: string;
131
+ originalContent?: string;
132
+ modifiedContent?: string;
133
+ }
134
+
135
+ /** Inline action display attached to assistant messages */
136
+ export interface ChatMessageAction {
137
+ type: ChatActionType;
138
+ files?: ChatFileChange[];
139
+ status: ChatActionStatus;
140
+ sessionId?: string;
141
+ explanation?: string;
142
+ }
143
+
111
144
  export interface ChatMessage {
112
145
  id: string;
113
- role: "user" | "assistant";
146
+ role: "user" | "assistant" | "system";
114
147
  content: string;
115
148
  timestamp: Date;
116
149
  editResult?: AIEditResult;
150
+ /** Inline action display for diffs/changes */
151
+ action?: ChatMessageAction;
152
+ }
153
+
154
+ /** Chat session for multi-conversation support */
155
+ export interface ChatSession {
156
+ id: string;
157
+ name: string;
158
+ messages: ChatMessage[];
159
+ createdAt: Date;
160
+ updatedAt: Date;
161
+ /** Context for the session (component type, vision mode, etc.) */
162
+ context?: {
163
+ componentType?: string;
164
+ visionMode?: boolean;
165
+ };
117
166
  }
118
167
 
119
168
  export interface AIEditResult {
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import * as path from "path";
7
7
  import * as os from "os";
8
8
  import { fileURLToPath } from "url";
9
9
  import { execSync } from "child_process";
10
+ import sharp from "sharp";
10
11
  // ============================================
11
12
  // INSTALLER (--init flag)
12
13
  // ============================================
@@ -1486,10 +1487,28 @@ async function embedLogo(logoPath) {
1486
1487
  };
1487
1488
  }
1488
1489
  try {
1489
- // Read file as base64
1490
- const fileBuffer = fs.readFileSync(resolvedPath);
1491
- const base64Data = fileBuffer.toString('base64');
1490
+ const MAX_DIMENSION = 7500; // Leave margin below Claude's 8000px limit
1491
+ // Read the image file
1492
+ let imageBuffer = fs.readFileSync(resolvedPath);
1492
1493
  const mimeType = getImageMimeType(resolvedPath);
1494
+ // Only process raster images (not SVG)
1495
+ if (mimeType !== 'image/svg+xml') {
1496
+ const image = sharp(imageBuffer);
1497
+ const metadata = await image.metadata();
1498
+ // Check if resizing is needed
1499
+ if (metadata.width && metadata.height) {
1500
+ if (metadata.width > MAX_DIMENSION || metadata.height > MAX_DIMENSION) {
1501
+ // Resize maintaining aspect ratio
1502
+ imageBuffer = await image
1503
+ .resize(MAX_DIMENSION, MAX_DIMENSION, {
1504
+ fit: 'inside',
1505
+ withoutEnlargement: true,
1506
+ })
1507
+ .toBuffer();
1508
+ }
1509
+ }
1510
+ }
1511
+ const base64Data = imageBuffer.toString('base64');
1493
1512
  return {
1494
1513
  success: true,
1495
1514
  type: "image",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.110",
3
+ "version": "1.3.111",
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",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "sharp": "^0.33.5",
42
43
  "zod": "^3.23.8"
43
44
  },
44
45
  "devDependencies": {