sonance-brand-mcp 1.3.110 → 1.3.112

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.
Files changed (84) hide show
  1. package/dist/assets/api/sonance-ai-edit/route.ts +30 -7
  2. package/dist/assets/api/sonance-save-image/route.ts +625 -0
  3. package/dist/assets/api/sonance-vision-apply/image-styling-detection.ts +1360 -0
  4. package/dist/assets/api/sonance-vision-apply/route.ts +1020 -64
  5. package/dist/assets/api/sonance-vision-apply/styling-detection.ts +730 -0
  6. package/dist/assets/api/sonance-vision-apply/theme-discovery.ts +1 -1
  7. package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
  8. package/dist/assets/brand-system.ts +13 -12
  9. package/dist/assets/components/accordion.tsx +15 -7
  10. package/dist/assets/components/alert-dialog.tsx +35 -10
  11. package/dist/assets/components/alert.tsx +11 -10
  12. package/dist/assets/components/avatar.tsx +4 -4
  13. package/dist/assets/components/badge.tsx +16 -12
  14. package/dist/assets/components/button.stories.tsx +3 -3
  15. package/dist/assets/components/button.tsx +50 -31
  16. package/dist/assets/components/calendar.tsx +12 -8
  17. package/dist/assets/components/card.tsx +35 -29
  18. package/dist/assets/components/checkbox.tsx +9 -8
  19. package/dist/assets/components/code.tsx +19 -11
  20. package/dist/assets/components/command.tsx +32 -13
  21. package/dist/assets/components/context-menu.tsx +37 -16
  22. package/dist/assets/components/dialog.tsx +8 -5
  23. package/dist/assets/components/divider.tsx +15 -5
  24. package/dist/assets/components/drawer.tsx +4 -3
  25. package/dist/assets/components/dropdown-menu.tsx +15 -13
  26. package/dist/assets/components/hover-card.tsx +4 -1
  27. package/dist/assets/components/image.tsx +1 -1
  28. package/dist/assets/components/input.tsx +29 -14
  29. package/dist/assets/components/kbd.stories.tsx +3 -3
  30. package/dist/assets/components/kbd.tsx +29 -13
  31. package/dist/assets/components/listbox.tsx +8 -8
  32. package/dist/assets/components/menubar.tsx +50 -23
  33. package/dist/assets/components/navbar.stories.tsx +140 -13
  34. package/dist/assets/components/navbar.tsx +22 -5
  35. package/dist/assets/components/navigation-menu.tsx +28 -6
  36. package/dist/assets/components/pagination.tsx +10 -10
  37. package/dist/assets/components/popover.tsx +10 -8
  38. package/dist/assets/components/progress.tsx +6 -4
  39. package/dist/assets/components/radio-group.tsx +5 -5
  40. package/dist/assets/components/select.tsx +49 -29
  41. package/dist/assets/components/separator.tsx +3 -3
  42. package/dist/assets/components/sheet.tsx +4 -4
  43. package/dist/assets/components/sidebar.tsx +10 -10
  44. package/dist/assets/components/skeleton.tsx +13 -5
  45. package/dist/assets/components/slider.tsx +12 -10
  46. package/dist/assets/components/switch.tsx +4 -4
  47. package/dist/assets/components/table.tsx +5 -5
  48. package/dist/assets/components/tabs.tsx +8 -8
  49. package/dist/assets/components/textarea.tsx +11 -9
  50. package/dist/assets/components/toast.tsx +7 -7
  51. package/dist/assets/components/toggle.tsx +27 -7
  52. package/dist/assets/components/tooltip.tsx +10 -8
  53. package/dist/assets/components/user.tsx +8 -6
  54. package/dist/assets/dev-tools/SonanceDevTools.tsx +851 -708
  55. package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
  56. package/dist/assets/dev-tools/components/ChatHistory.tsx +145 -0
  57. package/dist/assets/dev-tools/components/ChatInterface.tsx +444 -295
  58. package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
  59. package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
  60. package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +528 -0
  61. package/dist/assets/dev-tools/components/InspectorOverlay.tsx +21 -18
  62. package/dist/assets/dev-tools/components/PropertiesPanel.tsx +1345 -0
  63. package/dist/assets/dev-tools/components/ScreenshotAnnotator.tsx +1 -1
  64. package/dist/assets/dev-tools/components/SectionHighlight.tsx +1 -1
  65. package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +7 -7
  66. package/dist/assets/dev-tools/components/VisionModeBorder.tsx +12 -63
  67. package/dist/assets/dev-tools/constants.ts +38 -6
  68. package/dist/assets/dev-tools/hooks/index.ts +69 -0
  69. package/dist/assets/dev-tools/hooks/useComponentDetection.ts +132 -0
  70. package/dist/assets/dev-tools/hooks/useComputedStyles.ts +471 -0
  71. package/dist/assets/dev-tools/hooks/useContentHash.ts +212 -0
  72. package/dist/assets/dev-tools/hooks/useElementScanner.ts +398 -0
  73. package/dist/assets/dev-tools/hooks/useImageDetection.ts +162 -0
  74. package/dist/assets/dev-tools/hooks/useTextDetection.ts +217 -0
  75. package/dist/assets/dev-tools/index.ts +3 -0
  76. package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
  77. package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +384 -131
  78. package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
  79. package/dist/assets/dev-tools/types.ts +93 -2
  80. package/dist/assets/globals.css +225 -9
  81. package/dist/assets/styles/brand-overrides.css +3 -2
  82. package/dist/assets/utils.ts +2 -1
  83. package/dist/index.js +22 -3
  84. package/package.json +2 -1
@@ -1,15 +1,18 @@
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
+ import { useTheme } from "next-themes";
5
6
  import { cn } from "../../../lib/utils";
6
- import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus } from "../types";
7
+ import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus, ElementFilters, ImageOverride, PublicImageAsset, LogoAsset } from "../types";
7
8
  import { COMPONENT_CONFIG_MAP, getVisibleSections } from "../constants";
8
9
  import { componentSnippets, ThemeConfig } from "../../../lib/brand-system";
9
10
  import { ChatInterface } from "../components/ChatInterface";
10
11
  import { DiffPreview } from "../components/DiffPreview";
11
12
  import { VisionDiffPreview } from "../components/VisionDiffPreview";
12
13
  import { ApplyFirstPreview } from "../components/ApplyFirstPreview";
14
+ import { PropertiesPanel, PropertyEdits } from "../components/PropertiesPanel";
15
+ import { useElementFromCoordinates } from "../hooks/useComputedStyles";
13
16
 
14
17
  type ComponentsViewMode = "visual" | "inspector";
15
18
 
@@ -54,10 +57,26 @@ export interface ComponentsPanelProps {
54
57
  applyFirstSession?: ApplyFirstSession | null;
55
58
  applyFirstStatus?: ApplyFirstStatus;
56
59
  onApplyFirstComplete?: (session: ApplyFirstSession) => void;
57
- onApplyFirstAccept?: () => void;
58
- onApplyFirstRevert?: () => void;
60
+ onApplyFirstAccept?: () => Promise<void>;
61
+ onApplyFirstRevert?: () => Promise<void>;
59
62
  onApplyFirstForceClear?: () => void;
60
63
  onApplyPreview?: () => void; // Apply previewed modifications to files
64
+ // Unified element filters (new)
65
+ elementFilters?: ElementFilters;
66
+ selectedLogoId?: string | null;
67
+ onSelectLogo?: (logoId: string | null) => void;
68
+ selectedTextId?: string | null;
69
+ onSelectText?: (textId: string | null) => void;
70
+ // Image editing props
71
+ imageOverrides?: Record<string, ImageOverride>;
72
+ onImageOverrideChange?: (imageId: string, override: ImageOverride) => void;
73
+ onSaveImageChanges?: (imageId: string) => void;
74
+ isImageSaving?: boolean;
75
+ publicImages?: PublicImageAsset[];
76
+ publicImagesByFolder?: Record<string, PublicImageAsset[]>;
77
+ logoAssets?: LogoAsset[];
78
+ logoAssetsByBrand?: Record<string, LogoAsset[]>;
79
+ onImageUpload?: (file: File) => Promise<string | null>;
61
80
  }
62
81
 
63
82
  export function ComponentsPanel({
@@ -99,7 +118,27 @@ export function ComponentsPanel({
99
118
  onApplyFirstRevert,
100
119
  onApplyFirstForceClear,
101
120
  onApplyPreview,
121
+ // Unified element filters
122
+ elementFilters,
123
+ selectedLogoId,
124
+ onSelectLogo,
125
+ selectedTextId,
126
+ onSelectText,
127
+ // Image editing
128
+ imageOverrides = {},
129
+ onImageOverrideChange,
130
+ onSaveImageChanges,
131
+ isImageSaving = false,
132
+ publicImages = [],
133
+ publicImagesByFolder = {},
134
+ logoAssets = [],
135
+ logoAssetsByBrand = {},
136
+ onImageUpload,
102
137
  }: ComponentsPanelProps) {
138
+ // Theme detection for mode-specific color changes
139
+ const { resolvedTheme } = useTheme();
140
+ const currentTheme = resolvedTheme || "light";
141
+
103
142
  // Auto-activate inspector when entering this tab
104
143
  useEffect(() => {
105
144
  if (!inspectorEnabled && selectedComponentType === "all") {
@@ -404,45 +443,261 @@ export function ComponentsPanel({
404
443
  }
405
444
  };
406
445
 
446
+ // Handle property changes from PropertiesPanel (AI-assisted save)
447
+ const handlePropertySave = useCallback(async (edits: PropertyEdits, element: VisionFocusedElement) => {
448
+ // Build a natural language description of the changes
449
+ const changeDescriptions: string[] = [];
450
+
451
+ // Text content change (put first as it's the most significant change)
452
+ if (edits.textContent) {
453
+ const truncated = edits.textContent.length > 50
454
+ ? `${edits.textContent.substring(0, 50)}...`
455
+ : edits.textContent;
456
+ changeDescriptions.push(`text content to "${truncated}"`);
457
+ }
458
+ if (edits.width) changeDescriptions.push(`width to ${edits.width}`);
459
+ if (edits.height) changeDescriptions.push(`height to ${edits.height}`);
460
+ if (edits.opacity) changeDescriptions.push(`opacity to ${edits.opacity}%`);
461
+ if (edits.borderRadius) changeDescriptions.push(`border-radius to ${edits.borderRadius}`);
462
+ if (edits.fontSize) changeDescriptions.push(`font-size to ${edits.fontSize}`);
463
+ if (edits.fontWeight) changeDescriptions.push(`font-weight to ${edits.fontWeight}`);
464
+ if (edits.lineHeight) changeDescriptions.push(`line-height to ${edits.lineHeight}`);
465
+ if (edits.letterSpacing) changeDescriptions.push(`letter-spacing to ${edits.letterSpacing}`);
466
+ if (edits.color) changeDescriptions.push(`text color to ${edits.color}`);
467
+ // Theme-specific color changes
468
+ if (edits.colorLight) changeDescriptions.push(`text color (light mode) to ${edits.colorLight}`);
469
+ if (edits.colorDark) changeDescriptions.push(`text color (dark mode) to ${edits.colorDark}`);
470
+ if (edits.backgroundColor) changeDescriptions.push(`background color to ${edits.backgroundColor}`);
471
+ if (edits.padding) changeDescriptions.push(`padding to ${edits.padding}`);
472
+ if (edits.margin) changeDescriptions.push(`margin to ${edits.margin}`);
473
+ if (edits.gap) changeDescriptions.push(`gap to ${edits.gap}`);
474
+
475
+ if (changeDescriptions.length === 0) return;
476
+
477
+ // Detect if this is a text-only change (only textContent is set, no style properties)
478
+ const editKeys = Object.keys(edits).filter(k => edits[k as keyof PropertyEdits] !== undefined);
479
+ const isTextOnlyChange = editKeys.length === 1 && editKeys[0] === 'textContent';
480
+
481
+ const prompt = `Change the ${changeDescriptions.join(", ")} for this ${element.name} element.`;
482
+
483
+ try {
484
+ // Use "apply" action so changes are written immediately with backups
485
+ // This allows HMR to show the changes, then user can accept/revert
486
+ const response = await fetch("/api/sonance-vision-apply", {
487
+ method: "POST",
488
+ headers: { "Content-Type": "application/json" },
489
+ body: JSON.stringify({
490
+ action: "apply",
491
+ focusedElements: [element],
492
+ userPrompt: prompt,
493
+ propertyEdits: edits,
494
+ pageRoute: window.location.pathname,
495
+ currentTheme: currentTheme, // Include theme for mode-specific color changes
496
+ }),
497
+ });
498
+
499
+ const result = await response.json();
500
+
501
+ if (result.success && result.modifications && result.modifications.length > 0) {
502
+ if (isTextOnlyChange) {
503
+ // For text-only changes, auto-accept immediately (user already clicked "Save Changes")
504
+ // This deletes the backup - no need to ask again
505
+ try {
506
+ await fetch("/api/sonance-vision-apply", {
507
+ method: "POST",
508
+ headers: { "Content-Type": "application/json" },
509
+ body: JSON.stringify({
510
+ action: "accept",
511
+ sessionId: result.sessionId,
512
+ }),
513
+ });
514
+ console.log("[Text Edit] Auto-accepted text change, backup deleted");
515
+ } catch (acceptError) {
516
+ console.warn("[Text Edit] Failed to auto-accept:", acceptError);
517
+ }
518
+ // Don't trigger applyFirstSession - change is complete
519
+ } else if (onApplyFirstComplete) {
520
+ // For component/style changes, trigger the apply-first flow for review
521
+ onApplyFirstComplete({
522
+ sessionId: result.sessionId,
523
+ modifications: result.modifications,
524
+ appliedAt: Date.now(),
525
+ status: "applied",
526
+ backupPaths: result.backupPaths || [],
527
+ isPreview: false,
528
+ isTextOnlyChange: false,
529
+ });
530
+ }
531
+ } else if (!result.success) {
532
+ console.error("Failed to save property changes:", result.error);
533
+ } else if (result.modifications?.length === 0) {
534
+ console.log("No changes needed or AI could not locate the element");
535
+ }
536
+ } catch (error) {
537
+ console.error("Failed to save property changes:", error);
538
+ }
539
+ }, [onApplyFirstComplete, currentTheme]);
540
+
541
+ // Get the first focused element for properties panel
542
+ const primaryFocusedElement = visionFocusedElements.length > 0 ? visionFocusedElements[0] : null;
543
+ // Pass full element to hook for multi-strategy element finding (robust after HMR)
544
+ const computedStyles = useElementFromCoordinates(primaryFocusedElement);
545
+
546
+ // Determine if we should show the properties panel (Vision mode with focused elements)
547
+ const showPropertiesPanel = visionMode && visionFocusedElements.length > 0;
548
+
549
+ // Panel tab state - "details" or "chat"
550
+ // Default to "chat" for AI-first workflow
551
+ type PanelTab = "details" | "chat";
552
+ const [panelTab, setPanelTab] = useState<PanelTab>("details");
553
+
554
+ // NOTE: Removed auto-switch to details when element is focused
555
+ // Users want to stay on the AI chat tab when clicking elements
556
+ // The element info is shown in the chat context anyway
557
+
407
558
  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">
559
+ <div className="flex flex-col h-full">
560
+ {/* ===== TAB BAR - Dark theme matching the app ===== */}
561
+ <div className="flex items-center gap-1 px-3 py-1.5 bg-[#1a1d21] border-b border-white/10 flex-shrink-0">
562
+ <button
563
+ onClick={() => setPanelTab("details")}
564
+ className={cn(
565
+ "relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 rounded",
566
+ panelTab === "details"
567
+ ? "text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
568
+ : "text-gray-400 hover:text-white hover:bg-white/5"
569
+ )}
570
+ >
571
+ <Info className="h-3 w-3" />
572
+ <span id="span-details">Details</span>
573
+ </button>
574
+ <button
575
+ onClick={() => setPanelTab("chat")}
576
+ className={cn(
577
+ "relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 rounded",
578
+ panelTab === "chat"
579
+ ? "text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
580
+ : "text-gray-400 hover:text-white hover:bg-white/5"
581
+ )}
582
+ >
583
+ <MessageCircle className="h-3 w-3" />
584
+ <span id="span-ai-chat">AI Chat</span>
585
+ {visionMode && visionFocusedElements.length > 0 && (
586
+ <span id="span-visionfocusedelement" className="ml-0.5 min-w-[16px] h-4 px-1 flex items-center justify-center text-[9px] font-semibold bg-[#00A3E1]/20 text-[#00A3E1] rounded-full">
587
+ {visionFocusedElements.length}
588
+ </span>
589
+ )}
590
+ </button>
591
+ </div>
592
+
593
+ {/* ===== TAB CONTENT ===== */}
594
+ <div className="flex-1 overflow-y-auto min-h-0 bg-background">
595
+ {/* DETAILS TAB */}
596
+ {panelTab === "details" && (
597
+ <>
598
+ {/* Vision Mode: Properties Inspector */}
599
+ {showPropertiesPanel && (
600
+ <div className="border-b border-border">
601
+ <PropertiesPanel
602
+ element={primaryFocusedElement}
603
+ styles={computedStyles}
604
+ onSaveChanges={handlePropertySave}
605
+ textChangeSession={applyFirstSession?.isTextOnlyChange ? applyFirstSession : null}
606
+ onTextChangeAccept={onApplyFirstAccept}
607
+ onTextChangeRevert={onApplyFirstRevert}
608
+ imageOverride={(() => {
609
+ // Use elementId, variantId, or generate from name for image identification
610
+ const imageId = primaryFocusedElement?.elementId || primaryFocusedElement?.variantId || primaryFocusedElement?.name;
611
+ return imageId ? imageOverrides[imageId] : undefined;
612
+ })()}
613
+ onImageOverrideChange={onImageOverrideChange
614
+ ? (override) => {
615
+ // Use elementId, variantId, or generate from name for image identification
616
+ const imageId = primaryFocusedElement?.elementId || primaryFocusedElement?.variantId || primaryFocusedElement?.name;
617
+ if (imageId) {
618
+ onImageOverrideChange(imageId, override);
619
+ }
620
+ }
621
+ : undefined
622
+ }
623
+ onSaveImageChanges={onSaveImageChanges
624
+ ? () => {
625
+ const imageId = primaryFocusedElement?.elementId || primaryFocusedElement?.variantId || primaryFocusedElement?.name;
626
+ if (imageId) {
627
+ onSaveImageChanges(imageId);
628
+ }
629
+ }
630
+ : undefined
631
+ }
632
+ isImageSaving={isImageSaving}
633
+ publicImages={publicImages}
634
+ publicImagesByFolder={publicImagesByFolder}
635
+ logoAssets={logoAssets}
636
+ logoAssetsByBrand={logoAssetsByBrand}
637
+ onImageUpload={onImageUpload}
638
+ />
639
+ {/* Multiple Selections Indicator */}
640
+ {visionFocusedElements.length > 1 && (
641
+ <div className="px-2 py-1.5 bg-purple-50 dark:bg-purple-900/30 border-t border-purple-100 dark:border-purple-800">
642
+ <p id="p-visionfocusedelement" className="text-[10px] text-purple-600 dark:text-purple-400 font-medium">
643
+ +{visionFocusedElements.length - 1} more selected
644
+ </p>
645
+ </div>
646
+ )}
647
+ </div>
648
+ )}
649
+
650
+ {/* Empty State for Vision Mode */}
651
+ {visionMode && visionFocusedElements.length === 0 && (
652
+ <div className="flex flex-col items-center justify-center py-6 text-center px-3">
653
+ <div className="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center mb-2">
654
+ <MousePointer className="h-5 w-5 text-purple-500 dark:text-purple-400" />
655
+ </div>
656
+ <p id="p-vision-mode-active" className="text-[11px] font-medium text-foreground mb-1">
657
+ Vision Mode Active
658
+ </p>
659
+ <p id="p-click-elements-on-th" className="text-[10px] text-foreground-secondary">
660
+ Click elements on the page to inspect and select them for AI-powered editing.
661
+ </p>
662
+ </div>
663
+ )}
664
+
665
+ {/* Non-Vision Mode: Component Selection Interface */}
666
+ {!visionMode && selectedComponentType !== "all" && (
667
+ <div className="space-y-3 p-2">
668
+ {/* Selected Component Header - Compact */}
669
+ <div className="p-2 rounded border border-[#00A3E1] bg-[#00A3E1]/5">
414
670
  <div className="flex items-center justify-between">
415
- <div className="flex items-center gap-2">
416
- <Box className="h-4 w-4 text-[#00A3E1]" />
671
+ <div className="flex items-center gap-1.5">
672
+ <Box className="h-3.5 w-3.5 text-[#00A3E1]" />
417
673
  <div className="flex flex-col">
418
- <span id="brand-panel-span-selectedcomponentnam" className="text-sm font-semibold text-gray-900">{selectedComponentName}</span>
674
+ <span id="brand-panel-span-selectedcomponentnam" className="text-[11px] font-semibold text-foreground">{selectedComponentName}</span>
419
675
  {selectedVariantId && (
420
- <span id="brand-panel-span-variant-selectedvari" className="text-[10px] text-gray-500 font-mono">
421
- Variant: {selectedVariantId.substring(0, 6)}
676
+ <span id="brand-panel-span-variant-selectedvari" className="text-[9px] text-foreground-secondary font-mono">
677
+ #{selectedVariantId.substring(0, 6)}
422
678
  </span>
423
679
  )}
424
680
  </div>
425
681
  </div>
426
682
  <button
427
683
  onClick={() => onSelectComponentType("all")}
428
- className="text-xs text-gray-400 hover:text-gray-600"
684
+ className="p-1 text-foreground-muted hover:text-foreground hover:bg-secondary rounded"
429
685
  >
430
- <X className="h-4 w-4" />
686
+ <X className="h-3.5 w-3.5" />
431
687
  </button>
432
688
  </div>
433
689
 
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>
690
+ {/* Stats - Compact */}
691
+ <div className="flex items-center gap-3 mt-1.5 text-[10px] text-foreground-secondary">
692
+ <span id="brand-panel-span"><strong className="text-foreground">{pageCount}</strong> on page</span>
437
693
  {appCount > 0 && (
438
- <span id="brand-panel-span"><strong className="text-gray-700">{appCount}</strong> in entire app</span>
694
+ <span id="brand-panel-span"><strong className="text-foreground">{appCount}</strong> in app</span>
439
695
  )}
440
696
  </div>
441
697
  </div>
442
698
 
443
- {/* Variants List - show all unique variants */}
699
+ {/* Variants Grid - Compact */}
444
700
  {(() => {
445
- // Get unique variants for this component type
446
701
  const variantMap = new Map<string, { count: number; variantId: string }>();
447
702
  pageInstances.forEach((el) => {
448
703
  if (el.variantId) {
@@ -459,32 +714,32 @@ export function ComponentsPanel({
459
714
 
460
715
  if (variants.length > 1) {
461
716
  return (
462
- <div className="space-y-2">
717
+ <div className="space-y-1.5">
463
718
  <div className="flex items-center justify-between">
464
- <span className="text-xs font-medium text-gray-600">Variants ({variants.length})</span>
719
+ <span id="span-variants" className="text-[10px] font-medium text-foreground-secondary">Variants</span>
465
720
  {selectedVariantId && (
466
721
  <button
467
722
  onClick={() => onSelectVariant(null)}
468
- className="text-xs text-[#00A3E1] hover:underline"
723
+ className="text-[10px] text-[#00A3E1] hover:underline"
469
724
  >
470
- Clear selection
725
+ Clear
471
726
  </button>
472
727
  )}
473
728
  </div>
474
- <div className="grid grid-cols-2 gap-2">
729
+ <div className="flex flex-wrap gap-1">
475
730
  {variants.map(({ variantId, count }) => (
476
731
  <button
477
732
  key={variantId}
478
733
  onClick={() => onSelectVariant(selectedVariantId === variantId ? null : variantId)}
479
734
  className={cn(
480
- "p-2 rounded border text-left transition-colors",
735
+ "px-1.5 py-1 rounded text-[9px] font-mono transition-colors",
481
736
  selectedVariantId === variantId
482
- ? "border-[#00A3E1] bg-[#00A3E1]/10"
483
- : "border-gray-200 hover:border-gray-300 bg-white"
737
+ ? "bg-[#00A3E1] text-white"
738
+ : "bg-secondary text-foreground-secondary hover:bg-secondary-hover"
484
739
  )}
485
740
  >
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>
741
+ #{variantId.substring(0, 4)}
742
+ <span id="span-count" className="opacity-60 ml-0.5">({count})</span>
488
743
  </button>
489
744
  ))}
490
745
  </div>
@@ -495,137 +750,135 @@ export function ComponentsPanel({
495
750
  return null;
496
751
  })()}
497
752
 
498
- {/* No instances on page */}
753
+ {/* No instances warning */}
499
754
  {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.
755
+ <div className="p-1.5 rounded border border-amber-200 bg-amber-50 text-center">
756
+ <span id="brand-panel-span-no-instances-on-this" className="text-[10px] text-amber-700">
757
+ No instances on this page
503
758
  </span>
504
759
  </div>
505
760
  )}
506
761
 
507
- {/* Edit Scope Toggle - only show when a variant is selected */}
762
+ {/* Edit Scope Toggle - Compact */}
508
763
  {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">
764
+ <div className="flex items-center gap-1 p-1.5 bg-secondary/50 rounded border border-border">
765
+ <span id="span-scope" className="text-[9px] text-foreground-secondary">Scope:</span>
766
+ <div className="flex gap-0.5 flex-1">
512
767
  <button
513
768
  onClick={() => setEditScope("component")}
514
769
  className={cn(
515
- "flex-1 px-2 py-1.5 text-xs font-medium rounded transition-colors",
770
+ "flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
516
771
  editScope === "component"
517
772
  ? "bg-[#00A3E1] text-white"
518
- : "bg-white text-gray-600 border border-gray-200 hover:bg-gray-100"
773
+ : "text-foreground-secondary hover:bg-secondary"
519
774
  )}
520
775
  >
521
- All {selectedComponentName}s
776
+ All
522
777
  </button>
523
778
  <button
524
779
  onClick={() => setEditScope("variant")}
525
780
  className={cn(
526
- "flex-1 px-2 py-1.5 text-xs font-medium rounded transition-colors",
781
+ "flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
527
782
  editScope === "variant"
528
783
  ? "bg-[#00A3E1] text-white"
529
- : "bg-white text-gray-600 border border-gray-200 hover:bg-gray-100"
784
+ : "text-foreground-secondary hover:bg-secondary"
530
785
  )}
531
786
  >
532
- This Variant
787
+ Variant
533
788
  </button>
534
789
  </div>
535
790
  </div>
536
791
  )}
792
+ </div>
793
+ )}
537
794
 
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) */}
795
+ {/* Non-Vision Mode: Empty State */}
796
+ {!visionMode && selectedComponentType === "all" && (
797
+ <div className="flex flex-col items-center justify-center py-6 text-center px-3">
798
+ <div className="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
799
+ <Box className="h-5 w-5 text-foreground-muted" />
800
+ </div>
801
+ <p id="p-select-a-component" className="text-[11px] font-medium text-foreground mb-1">
802
+ Select a Component
803
+ </p>
804
+ <p id="p-click-a-component-on" className="text-[10px] text-foreground-secondary">
805
+ Click a component on the page or enable Vision Mode for AI-powered editing.
806
+ </p>
807
+ </div>
808
+ )}
809
+ </>
810
+ )}
811
+
812
+ {/* AI CHAT TAB - Full Height Cursor-Style Interface */}
813
+ {panelTab === "chat" && (
814
+ <div className="flex flex-col h-full bg-background">
815
+ {/* Selected Element Indicator - Shows what's selected without leaving chat */}
816
+ {visionMode && visionFocusedElements.length > 0 && (
817
+ <div className="flex items-center gap-1.5 px-3 py-1.5 bg-background-secondary border-b border-border">
818
+ <MousePointer className="h-3 w-3 text-foreground-muted flex-shrink-0" />
819
+ <span className="text-[11px] font-medium text-foreground truncate">
820
+ {primaryFocusedElement?.name || visionFocusedElements[0]?.type}
821
+ </span>
822
+ {visionFocusedElements.length > 1 && (
823
+ <span className="text-[10px] text-foreground-muted flex-shrink-0">
824
+ +{visionFocusedElements.length - 1}
825
+ </span>
826
+ )}
827
+ </div>
828
+ )}
829
+
830
+ {/* Chat Interface - Always visible with inline diffs */}
831
+ <ChatInterface
832
+ componentType={selectedComponentType}
833
+ componentName={selectedComponentName || selectedComponentType}
834
+ onEditComplete={handleAIEditComplete}
835
+ onSaveRequest={setPendingEdit}
836
+ pendingEdit={pendingEdit}
837
+ onClearPending={handleClearPendingEdit}
838
+ editScope={editScope}
839
+ variantId={selectedVariantId}
840
+ variantStyles={variantStyles}
841
+ visionMode={visionMode}
842
+ visionFocusedElements={visionFocusedElements}
843
+ onVisionEditComplete={onVisionEditComplete}
844
+ onApplyFirstComplete={onApplyFirstComplete}
845
+ applyFirstSession={applyFirstSession}
846
+ applyFirstStatus={applyFirstStatus}
847
+ onApplyFirstAccept={onApplyFirstAccept}
848
+ onApplyFirstRevert={onApplyFirstRevert}
849
+ />
850
+
851
+ {/* Legacy Diff Previews (fallback for non-inline mode) */}
555
852
  {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
- />
853
+ <div className="border-t border-border p-2">
854
+ <DiffPreview
855
+ pendingEdit={pendingEdit}
856
+ componentType={selectedComponentType}
857
+ onSave={handleSaveAIEdit}
858
+ onCancel={handleClearPendingEdit}
859
+ onPreviewToggle={handlePreviewToggle}
860
+ isPreviewActive={isPreviewActive}
861
+ saveStatus={aiSaveStatus}
862
+ saveMessage={aiSaveMessage}
863
+ variantId={selectedVariantId}
864
+ />
865
+ </div>
567
866
  )}
568
867
 
569
- {/* Vision Mode Pending Edit Preview (multi-file) - OLD: CSS preview */}
570
868
  {visionPendingEdit && onSaveVisionEdit && onClearVisionPendingEdit && !applyFirstSession && (
571
- <VisionDiffPreview
572
- pendingEdit={visionPendingEdit}
573
- onSave={onSaveVisionEdit}
574
- onCancel={onClearVisionPendingEdit}
575
- />
576
- )}
577
-
578
- {/* Preview-First Mode - Shows diff, user accepts/rejects before applying */}
579
- {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
- />
869
+ <div className="border-t border-border p-2">
870
+ <VisionDiffPreview
871
+ pendingEdit={visionPendingEdit}
872
+ onSave={onSaveVisionEdit}
873
+ onCancel={onClearVisionPendingEdit}
874
+ />
875
+ </div>
610
876
  )}
611
877
 
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>
878
+ {/* ApplyFirstPreview is now displayed inline in the chat via InlineDiffPreview */}
628
879
  </div>
880
+ )}
881
+ </div>
629
882
  </div>
630
883
  );
631
884
  }