sonance-brand-mcp 1.3.111 → 1.3.113

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 (79) hide show
  1. package/dist/assets/api/sonance-save-image/route.ts +625 -0
  2. package/dist/assets/api/sonance-vision-apply/image-styling-detection.ts +1360 -0
  3. package/dist/assets/api/sonance-vision-apply/route.ts +988 -57
  4. package/dist/assets/api/sonance-vision-apply/styling-detection.ts +730 -0
  5. package/dist/assets/api/sonance-vision-apply/theme-discovery.ts +1 -1
  6. package/dist/assets/brand-system.ts +13 -12
  7. package/dist/assets/components/accordion.tsx +15 -7
  8. package/dist/assets/components/alert-dialog.tsx +35 -10
  9. package/dist/assets/components/alert.tsx +11 -10
  10. package/dist/assets/components/avatar.tsx +4 -4
  11. package/dist/assets/components/badge.tsx +16 -12
  12. package/dist/assets/components/button.stories.tsx +3 -3
  13. package/dist/assets/components/button.tsx +50 -31
  14. package/dist/assets/components/calendar.tsx +12 -8
  15. package/dist/assets/components/card.tsx +35 -29
  16. package/dist/assets/components/checkbox.tsx +9 -8
  17. package/dist/assets/components/code.tsx +19 -11
  18. package/dist/assets/components/command.tsx +32 -13
  19. package/dist/assets/components/context-menu.tsx +37 -16
  20. package/dist/assets/components/dialog.tsx +8 -5
  21. package/dist/assets/components/divider.tsx +15 -5
  22. package/dist/assets/components/drawer.tsx +4 -3
  23. package/dist/assets/components/dropdown-menu.tsx +15 -13
  24. package/dist/assets/components/hover-card.tsx +4 -1
  25. package/dist/assets/components/image.tsx +1 -1
  26. package/dist/assets/components/input.tsx +29 -14
  27. package/dist/assets/components/kbd.stories.tsx +3 -3
  28. package/dist/assets/components/kbd.tsx +29 -13
  29. package/dist/assets/components/listbox.tsx +8 -8
  30. package/dist/assets/components/menubar.tsx +50 -23
  31. package/dist/assets/components/navbar.stories.tsx +140 -13
  32. package/dist/assets/components/navbar.tsx +22 -5
  33. package/dist/assets/components/navigation-menu.tsx +28 -6
  34. package/dist/assets/components/pagination.tsx +10 -10
  35. package/dist/assets/components/popover.tsx +10 -8
  36. package/dist/assets/components/progress.tsx +6 -4
  37. package/dist/assets/components/radio-group.tsx +5 -5
  38. package/dist/assets/components/select.tsx +49 -29
  39. package/dist/assets/components/separator.tsx +3 -3
  40. package/dist/assets/components/sheet.tsx +4 -4
  41. package/dist/assets/components/sidebar.tsx +10 -10
  42. package/dist/assets/components/skeleton.tsx +13 -5
  43. package/dist/assets/components/slider.tsx +12 -10
  44. package/dist/assets/components/switch.tsx +4 -4
  45. package/dist/assets/components/table.tsx +5 -5
  46. package/dist/assets/components/tabs.tsx +8 -8
  47. package/dist/assets/components/textarea.tsx +11 -9
  48. package/dist/assets/components/toast.tsx +7 -7
  49. package/dist/assets/components/toggle.tsx +27 -7
  50. package/dist/assets/components/tooltip.tsx +10 -8
  51. package/dist/assets/components/user.tsx +8 -6
  52. package/dist/assets/dev-tools/SonanceDevTools.tsx +429 -362
  53. package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
  54. package/dist/assets/dev-tools/components/ChatHistory.tsx +11 -7
  55. package/dist/assets/dev-tools/components/ChatInterface.tsx +61 -20
  56. package/dist/assets/dev-tools/components/ChatTabBar.tsx +1 -1
  57. package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
  58. package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +360 -36
  59. package/dist/assets/dev-tools/components/InspectorOverlay.tsx +9 -9
  60. package/dist/assets/dev-tools/components/PropertiesPanel.tsx +743 -93
  61. package/dist/assets/dev-tools/components/ScreenshotAnnotator.tsx +1 -1
  62. package/dist/assets/dev-tools/components/SectionHighlight.tsx +1 -1
  63. package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +7 -7
  64. package/dist/assets/dev-tools/components/VisionModeBorder.tsx +4 -64
  65. package/dist/assets/dev-tools/hooks/index.ts +69 -0
  66. package/dist/assets/dev-tools/hooks/useComponentDetection.ts +132 -0
  67. package/dist/assets/dev-tools/hooks/useComputedStyles.ts +171 -65
  68. package/dist/assets/dev-tools/hooks/useContentHash.ts +212 -0
  69. package/dist/assets/dev-tools/hooks/useElementScanner.ts +398 -0
  70. package/dist/assets/dev-tools/hooks/useImageDetection.ts +162 -0
  71. package/dist/assets/dev-tools/hooks/useTextDetection.ts +217 -0
  72. package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +160 -57
  73. package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
  74. package/dist/assets/dev-tools/types.ts +42 -0
  75. package/dist/assets/globals.css +225 -9
  76. package/dist/assets/styles/brand-overrides.css +3 -2
  77. package/dist/assets/utils.ts +2 -1
  78. package/dist/index.js +32 -1
  79. package/package.json +1 -1
@@ -0,0 +1,162 @@
1
+ "use client";
2
+
3
+ import { useCallback, useRef } from "react";
4
+ import { DetectedElement, OriginalLogoState } from "../types";
5
+ import { generateImageElementId, isLegacyId } from "./useContentHash";
6
+
7
+ /**
8
+ * Image detection configuration
9
+ */
10
+ export interface ImageDetectionConfig {
11
+ /** Callback to check if element is in active layer */
12
+ isInActiveLayer: (el: Element) => boolean;
13
+ /** Current original states (for preserving existing state) */
14
+ existingOriginalStates?: Record<string, OriginalLogoState>;
15
+ }
16
+
17
+ /**
18
+ * Image detection result
19
+ */
20
+ export interface ImageDetectionResult {
21
+ elements: DetectedElement[];
22
+ originalStates: Record<string, OriginalLogoState>;
23
+ }
24
+
25
+ /**
26
+ * Extract logo name from image src
27
+ * Returns name if src contains "logo", null otherwise
28
+ */
29
+ export function extractLogoName(src: string): string | null {
30
+ if (!src) return null;
31
+
32
+ const lowerSrc = src.toLowerCase();
33
+ if (!lowerSrc.includes("logo")) return null;
34
+
35
+ // Extract filename without extension
36
+ const filename = src.split("/").pop()?.split("?")[0] || "";
37
+ const nameWithoutExt = filename.replace(/\.[^/.]+$/, "");
38
+
39
+ // Clean up the name
40
+ return nameWithoutExt
41
+ .replace(/[-_]/g, " ")
42
+ .replace(/\s+/g, " ")
43
+ .trim();
44
+ }
45
+
46
+ /**
47
+ * Detect images/logos on the page
48
+ * Identifies logo images and tracks their original state
49
+ */
50
+ export function detectImages(config: ImageDetectionConfig): ImageDetectionResult {
51
+ const { isInActiveLayer, existingOriginalStates = {} } = config;
52
+ const detected: DetectedElement[] = [];
53
+ const originalStates: Record<string, OriginalLogoState> = { ...existingOriginalStates };
54
+
55
+ const images = document.querySelectorAll("img");
56
+ images.forEach((img) => {
57
+ if (!isInActiveLayer(img)) return;
58
+
59
+ const src = img.src || img.getAttribute("src") || "";
60
+ const alt = img.alt || "";
61
+
62
+ // Check if this is a logo (src or alt contains "logo")
63
+ const logoName = extractLogoName(src);
64
+ const altContainsLogo = alt.toLowerCase().includes("logo");
65
+
66
+ if (!logoName && !altContainsLogo) return;
67
+
68
+ const rect = img.getBoundingClientRect();
69
+ if (rect.width <= 0 || rect.height <= 0) return;
70
+
71
+ // Get or generate stable ID
72
+ let logoId = img.getAttribute("data-sonance-logo-id");
73
+
74
+ // Check if using legacy sequential ID and migrate if needed
75
+ if (logoId && isLegacyId(logoId)) {
76
+ const newId = generateImageElementId(img);
77
+ img.setAttribute("data-sonance-logo-id", newId);
78
+
79
+ // Migrate original state if exists
80
+ if (originalStates[logoId]) {
81
+ originalStates[newId] = originalStates[logoId];
82
+ delete originalStates[logoId];
83
+ }
84
+
85
+ logoId = newId;
86
+ }
87
+
88
+ if (!logoId) {
89
+ logoId = generateImageElementId(img);
90
+ img.setAttribute("data-sonance-logo-id", logoId);
91
+ }
92
+
93
+ // Store original state if not already stored
94
+ if (!originalStates[logoId]) {
95
+ const originalSrc = img.getAttribute("data-original-src") || src;
96
+ const originalSrcset = img.getAttribute("data-original-srcset") || img.srcset || "";
97
+
98
+ originalStates[logoId] = {
99
+ src: originalSrc,
100
+ width: img.naturalWidth || img.width,
101
+ height: img.naturalHeight || img.height,
102
+ srcset: originalSrcset || undefined,
103
+ };
104
+
105
+ // Store original src/srcset as data attributes for reset
106
+ if (!img.getAttribute("data-original-src")) {
107
+ img.setAttribute("data-original-src", src);
108
+ }
109
+ if (!img.getAttribute("data-original-srcset") && img.srcset) {
110
+ img.setAttribute("data-original-srcset", img.srcset);
111
+ }
112
+ }
113
+
114
+ const displayName = logoName || alt || "Logo";
115
+ const imageSrc = img.getAttribute("data-original-src") || src;
116
+
117
+ detected.push({
118
+ name: displayName,
119
+ rect,
120
+ type: "logo",
121
+ logoId,
122
+ imageSrc,
123
+ });
124
+ });
125
+
126
+ return { elements: detected, originalStates };
127
+ }
128
+
129
+ /**
130
+ * Hook for image detection
131
+ * Maintains original state across scans
132
+ */
133
+ export function useImageDetection() {
134
+ const originalStatesRef = useRef<Record<string, OriginalLogoState>>({});
135
+
136
+ const detect = useCallback((config: Omit<ImageDetectionConfig, "existingOriginalStates">): ImageDetectionResult => {
137
+ const result = detectImages({
138
+ ...config,
139
+ existingOriginalStates: originalStatesRef.current,
140
+ });
141
+
142
+ // Update ref with new states
143
+ originalStatesRef.current = result.originalStates;
144
+
145
+ return result;
146
+ }, []);
147
+
148
+ const resetOriginalStates = useCallback(() => {
149
+ originalStatesRef.current = {};
150
+ }, []);
151
+
152
+ const getOriginalStates = useCallback(() => {
153
+ return originalStatesRef.current;
154
+ }, []);
155
+
156
+ return {
157
+ detectImages: detect,
158
+ resetOriginalStates,
159
+ getOriginalStates,
160
+ originalStatesRef,
161
+ };
162
+ }
@@ -0,0 +1,217 @@
1
+ "use client";
2
+
3
+ import { useCallback, useRef } from "react";
4
+ import { DetectedElement, OriginalTextState } from "../types";
5
+ import { generateTextElementId, isLegacyId } from "./useContentHash";
6
+
7
+ /**
8
+ * Text detection configuration
9
+ */
10
+ export interface TextDetectionConfig {
11
+ /** Callback to check if element is in active layer */
12
+ isInActiveLayer: (el: Element) => boolean;
13
+ /** Current original states (for preserving existing state) */
14
+ existingOriginalStates?: Record<string, OriginalTextState>;
15
+ }
16
+
17
+ /**
18
+ * Text detection result
19
+ */
20
+ export interface TextDetectionResult {
21
+ elements: DetectedElement[];
22
+ originalStates: Record<string, OriginalTextState>;
23
+ }
24
+
25
+ /**
26
+ * Semantic text element selectors
27
+ * Only select meaningful text containers - exclude spans as they're usually nested
28
+ */
29
+ const TEXT_SELECTORS = "h1, h2, h3, h4, h5, h6, p, a, label, blockquote, figcaption, li";
30
+
31
+ /**
32
+ * Minimum dimensions to filter out decorative/icon elements
33
+ */
34
+ const MIN_WIDTH = 20;
35
+ const MIN_HEIGHT = 10;
36
+ const MIN_TEXT_LENGTH = 2;
37
+
38
+ /**
39
+ * Check if element has meaningful text content
40
+ */
41
+ function hasMeaningfulContent(element: Element): boolean {
42
+ const textContent = element.textContent?.trim() || "";
43
+
44
+ if (textContent.length < MIN_TEXT_LENGTH) return false;
45
+
46
+ // Skip elements that only contain whitespace/invisible chars
47
+ if (/^[\s\u200B-\u200D\uFEFF]+$/.test(textContent)) return false;
48
+
49
+ return true;
50
+ }
51
+
52
+ /**
53
+ * Check if element is nested inside another text element we'd track
54
+ */
55
+ function isNestedTextElement(element: Element): boolean {
56
+ const parentTextEl = element.parentElement?.closest(TEXT_SELECTORS);
57
+
58
+ if (!parentTextEl) return false;
59
+
60
+ // Skip if parent is part of DevTools
61
+ if (parentTextEl.closest("[data-sonance-devtools]")) return false;
62
+
63
+ // Allow links inside paragraphs to be selectable
64
+ const tagName = element.tagName.toLowerCase();
65
+ const parentTagName = parentTextEl.tagName.toLowerCase();
66
+
67
+ if (tagName === "a" && parentTagName === "p") return false;
68
+
69
+ return true;
70
+ }
71
+
72
+ /**
73
+ * Get display name for text element
74
+ */
75
+ function getDisplayName(element: Element, textContent: string): string {
76
+ const tagName = element.tagName.toLowerCase();
77
+
78
+ let displayName: string;
79
+ switch (tagName) {
80
+ case "a":
81
+ displayName = "Link";
82
+ break;
83
+ case "li":
84
+ displayName = "List Item";
85
+ break;
86
+ default:
87
+ displayName = tagName.toUpperCase();
88
+ }
89
+
90
+ const truncatedContent = textContent.length > 30
91
+ ? textContent.substring(0, 30) + "..."
92
+ : textContent;
93
+
94
+ return `${displayName}: ${truncatedContent}`;
95
+ }
96
+
97
+ /**
98
+ * Detect text elements on the page
99
+ * Filters noise and captures original state
100
+ */
101
+ export function detectTextElements(config: TextDetectionConfig): TextDetectionResult {
102
+ const { isInActiveLayer, existingOriginalStates = {} } = config;
103
+ const detected: DetectedElement[] = [];
104
+ const originalStates: Record<string, OriginalTextState> = { ...existingOriginalStates };
105
+
106
+ // Track added elements to avoid duplicates
107
+ const addedElements = new Set<Element>();
108
+
109
+ const textElements = document.querySelectorAll(TEXT_SELECTORS);
110
+ textElements.forEach((el) => {
111
+ // Skip elements outside active layer
112
+ if (!isInActiveLayer(el)) return;
113
+
114
+ // Skip nested text elements (avoid duplicates)
115
+ if (isNestedTextElement(el)) return;
116
+
117
+ // Skip already processed elements
118
+ if (addedElements.has(el)) return;
119
+
120
+ const rect = el.getBoundingClientRect();
121
+
122
+ // Filter out small/decorative elements
123
+ if (rect.width < MIN_WIDTH || rect.height < MIN_HEIGHT) return;
124
+
125
+ const textContent = el.textContent?.trim() || "";
126
+
127
+ // Skip elements without meaningful content
128
+ if (!hasMeaningfulContent(el)) return;
129
+
130
+ // Skip invisible elements
131
+ if (rect.width <= 0 || rect.height <= 0) return;
132
+
133
+ // Get or generate stable ID
134
+ let textId = el.getAttribute("data-sonance-text-id");
135
+
136
+ // Check if using legacy sequential ID and migrate if needed
137
+ if (textId && isLegacyId(textId)) {
138
+ const newId = generateTextElementId(el);
139
+ el.setAttribute("data-sonance-text-id", newId);
140
+
141
+ // Migrate original state if exists
142
+ if (originalStates[textId]) {
143
+ originalStates[newId] = originalStates[textId];
144
+ delete originalStates[textId];
145
+ }
146
+
147
+ textId = newId;
148
+ }
149
+
150
+ if (!textId) {
151
+ textId = generateTextElementId(el);
152
+ el.setAttribute("data-sonance-text-id", textId);
153
+ }
154
+
155
+ // Capture original state if not already captured
156
+ if (!originalStates[textId]) {
157
+ const computed = window.getComputedStyle(el);
158
+ originalStates[textId] = {
159
+ textContent: el.textContent,
160
+ fontSize: computed.fontSize,
161
+ fontWeight: computed.fontWeight,
162
+ lineHeight: computed.lineHeight,
163
+ letterSpacing: computed.letterSpacing,
164
+ color: computed.color,
165
+ fontFamily: computed.fontFamily,
166
+ };
167
+ }
168
+
169
+ const displayName = getDisplayName(el, textContent);
170
+
171
+ addedElements.add(el);
172
+ detected.push({
173
+ name: displayName,
174
+ rect,
175
+ type: "text",
176
+ textId,
177
+ textContent,
178
+ });
179
+ });
180
+
181
+ return { elements: detected, originalStates };
182
+ }
183
+
184
+ /**
185
+ * Hook for text detection
186
+ * Maintains original state across scans
187
+ */
188
+ export function useTextDetection() {
189
+ const originalStatesRef = useRef<Record<string, OriginalTextState>>({});
190
+
191
+ const detect = useCallback((config: Omit<TextDetectionConfig, "existingOriginalStates">): TextDetectionResult => {
192
+ const result = detectTextElements({
193
+ ...config,
194
+ existingOriginalStates: originalStatesRef.current,
195
+ });
196
+
197
+ // Update ref with new states
198
+ originalStatesRef.current = result.originalStates;
199
+
200
+ return result;
201
+ }, []);
202
+
203
+ const resetOriginalStates = useCallback(() => {
204
+ originalStatesRef.current = {};
205
+ }, []);
206
+
207
+ const getOriginalStates = useCallback(() => {
208
+ return originalStatesRef.current;
209
+ }, []);
210
+
211
+ return {
212
+ detectTextElements: detect,
213
+ resetOriginalStates,
214
+ getOriginalStates,
215
+ originalStatesRef,
216
+ };
217
+ }