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.
- package/dist/assets/api/sonance-ai-edit/route.ts +30 -7
- package/dist/assets/api/sonance-save-image/route.ts +625 -0
- package/dist/assets/api/sonance-vision-apply/image-styling-detection.ts +1360 -0
- package/dist/assets/api/sonance-vision-apply/route.ts +1020 -64
- package/dist/assets/api/sonance-vision-apply/styling-detection.ts +730 -0
- package/dist/assets/api/sonance-vision-apply/theme-discovery.ts +1 -1
- package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
- package/dist/assets/brand-system.ts +13 -12
- package/dist/assets/components/accordion.tsx +15 -7
- package/dist/assets/components/alert-dialog.tsx +35 -10
- package/dist/assets/components/alert.tsx +11 -10
- package/dist/assets/components/avatar.tsx +4 -4
- package/dist/assets/components/badge.tsx +16 -12
- package/dist/assets/components/button.stories.tsx +3 -3
- package/dist/assets/components/button.tsx +50 -31
- package/dist/assets/components/calendar.tsx +12 -8
- package/dist/assets/components/card.tsx +35 -29
- package/dist/assets/components/checkbox.tsx +9 -8
- package/dist/assets/components/code.tsx +19 -11
- package/dist/assets/components/command.tsx +32 -13
- package/dist/assets/components/context-menu.tsx +37 -16
- package/dist/assets/components/dialog.tsx +8 -5
- package/dist/assets/components/divider.tsx +15 -5
- package/dist/assets/components/drawer.tsx +4 -3
- package/dist/assets/components/dropdown-menu.tsx +15 -13
- package/dist/assets/components/hover-card.tsx +4 -1
- package/dist/assets/components/image.tsx +1 -1
- package/dist/assets/components/input.tsx +29 -14
- package/dist/assets/components/kbd.stories.tsx +3 -3
- package/dist/assets/components/kbd.tsx +29 -13
- package/dist/assets/components/listbox.tsx +8 -8
- package/dist/assets/components/menubar.tsx +50 -23
- package/dist/assets/components/navbar.stories.tsx +140 -13
- package/dist/assets/components/navbar.tsx +22 -5
- package/dist/assets/components/navigation-menu.tsx +28 -6
- package/dist/assets/components/pagination.tsx +10 -10
- package/dist/assets/components/popover.tsx +10 -8
- package/dist/assets/components/progress.tsx +6 -4
- package/dist/assets/components/radio-group.tsx +5 -5
- package/dist/assets/components/select.tsx +49 -29
- package/dist/assets/components/separator.tsx +3 -3
- package/dist/assets/components/sheet.tsx +4 -4
- package/dist/assets/components/sidebar.tsx +10 -10
- package/dist/assets/components/skeleton.tsx +13 -5
- package/dist/assets/components/slider.tsx +12 -10
- package/dist/assets/components/switch.tsx +4 -4
- package/dist/assets/components/table.tsx +5 -5
- package/dist/assets/components/tabs.tsx +8 -8
- package/dist/assets/components/textarea.tsx +11 -9
- package/dist/assets/components/toast.tsx +7 -7
- package/dist/assets/components/toggle.tsx +27 -7
- package/dist/assets/components/tooltip.tsx +10 -8
- package/dist/assets/components/user.tsx +8 -6
- package/dist/assets/dev-tools/SonanceDevTools.tsx +851 -708
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
- package/dist/assets/dev-tools/components/ChatHistory.tsx +145 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +444 -295
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
- package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +528 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +21 -18
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +1345 -0
- package/dist/assets/dev-tools/components/ScreenshotAnnotator.tsx +1 -1
- package/dist/assets/dev-tools/components/SectionHighlight.tsx +1 -1
- package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +7 -7
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +12 -63
- package/dist/assets/dev-tools/constants.ts +38 -6
- package/dist/assets/dev-tools/hooks/index.ts +69 -0
- package/dist/assets/dev-tools/hooks/useComponentDetection.ts +132 -0
- package/dist/assets/dev-tools/hooks/useComputedStyles.ts +471 -0
- package/dist/assets/dev-tools/hooks/useContentHash.ts +212 -0
- package/dist/assets/dev-tools/hooks/useElementScanner.ts +398 -0
- package/dist/assets/dev-tools/hooks/useImageDetection.ts +162 -0
- package/dist/assets/dev-tools/hooks/useTextDetection.ts +217 -0
- package/dist/assets/dev-tools/index.ts +3 -0
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +384 -131
- package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
- package/dist/assets/dev-tools/types.ts +93 -2
- package/dist/assets/globals.css +225 -9
- package/dist/assets/styles/brand-overrides.css +3 -2
- package/dist/assets/utils.ts +2 -1
- package/dist/index.js +22 -3
- package/package.json +2 -1
|
@@ -368,7 +368,7 @@ export function ScreenshotAnnotator({
|
|
|
368
368
|
}}
|
|
369
369
|
>
|
|
370
370
|
<Crop size={18} />
|
|
371
|
-
<span>Click and drag to select the area you want to focus on</span>
|
|
371
|
+
<span id="span-click-and-drag-to-se">Click and drag to select the area you want to focus on</span>
|
|
372
372
|
</div>
|
|
373
373
|
|
|
374
374
|
{/* Current rectangle selection */}
|
|
@@ -96,7 +96,7 @@ export function SectionHighlight({ active, focusedElements }: SectionHighlightPr
|
|
|
96
96
|
}}
|
|
97
97
|
>
|
|
98
98
|
<Box size={12} />
|
|
99
|
-
<span>Section: {labelText}</span>
|
|
99
|
+
<span id="span-section-labeltext">Section: {labelText}</span>
|
|
100
100
|
</div>
|
|
101
101
|
|
|
102
102
|
{/* Corner markers for emphasis */}
|
|
@@ -34,14 +34,14 @@ function FileModificationCard({
|
|
|
34
34
|
<ChevronRight className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" />
|
|
35
35
|
)}
|
|
36
36
|
<FileCode className="h-3.5 w-3.5 text-purple-500 flex-shrink-0" />
|
|
37
|
-
<span className="text-xs font-mono text-gray-700 truncate flex-1">
|
|
37
|
+
<span id="file-modification-card-span-modificationfilepath" className="text-xs font-mono text-gray-700 truncate flex-1">
|
|
38
38
|
{modification.filePath}
|
|
39
39
|
</span>
|
|
40
40
|
</button>
|
|
41
41
|
|
|
42
42
|
{/* Explanation */}
|
|
43
43
|
<div className="px-2 pb-2">
|
|
44
|
-
<p className="text-[10px] text-gray-500">{modification.explanation}</p>
|
|
44
|
+
<p id="file-modification-card-p-modificationexplanat" className="text-[10px] text-gray-500">{modification.explanation}</p>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
{/* Expanded Diff */}
|
|
@@ -108,10 +108,10 @@ export function VisionDiffPreview({
|
|
|
108
108
|
<div className="flex items-center justify-between">
|
|
109
109
|
<div className="flex items-center gap-2">
|
|
110
110
|
<Eye className="h-4 w-4 text-purple-600" />
|
|
111
|
-
<span className="text-xs font-semibold text-gray-900">
|
|
111
|
+
<span id="vision-diff-preview-span-vision-mode-changes-" className="text-xs font-semibold text-gray-900">
|
|
112
112
|
Vision Mode Changes Ready
|
|
113
113
|
</span>
|
|
114
|
-
<span className="text-[10px] px-1.5 py-0.5 rounded bg-purple-200 text-purple-700 font-medium">
|
|
114
|
+
<span id="vision-diff-preview-span-filecount-filefileco" className="text-[10px] px-1.5 py-0.5 rounded bg-purple-200 text-purple-700 font-medium">
|
|
115
115
|
{fileCount} file{fileCount !== 1 ? "s" : ""}
|
|
116
116
|
</span>
|
|
117
117
|
</div>
|
|
@@ -124,7 +124,7 @@ export function VisionDiffPreview({
|
|
|
124
124
|
</div>
|
|
125
125
|
|
|
126
126
|
{/* Overall Explanation */}
|
|
127
|
-
<p className="text-xs text-gray-600">{pendingEdit.explanation}</p>
|
|
127
|
+
<p id="vision-diff-preview-p-pendingeditexplanati" className="text-xs text-gray-600">{pendingEdit.explanation}</p>
|
|
128
128
|
|
|
129
129
|
{/* File Modifications List */}
|
|
130
130
|
<div className="space-y-2 max-h-80 overflow-y-auto">
|
|
@@ -141,7 +141,7 @@ export function VisionDiffPreview({
|
|
|
141
141
|
{/* Live Preview Indicator */}
|
|
142
142
|
<div className="flex items-center gap-2 p-2 rounded bg-purple-100 border border-purple-200">
|
|
143
143
|
<Eye className="h-3.5 w-3.5 text-purple-600" />
|
|
144
|
-
<span className="text-xs text-purple-700">
|
|
144
|
+
<span id="vision-diff-preview-span-live-preview-active-" className="text-xs text-purple-700">
|
|
145
145
|
Live preview active - scroll to see changes on the page
|
|
146
146
|
</span>
|
|
147
147
|
</div>
|
|
@@ -150,7 +150,7 @@ export function VisionDiffPreview({
|
|
|
150
150
|
{fileCount > 1 && (
|
|
151
151
|
<div className="flex items-start gap-2 p-2 rounded bg-amber-50 border border-amber-200">
|
|
152
152
|
<AlertCircle className="h-3.5 w-3.5 text-amber-600 mt-0.5 flex-shrink-0" />
|
|
153
|
-
<span className="text-xs text-amber-700">
|
|
153
|
+
<span id="vision-diff-preview-span-multiple-files-will-" className="text-xs text-amber-700">
|
|
154
154
|
Multiple files will be modified. Review each file before saving.
|
|
155
155
|
</span>
|
|
156
156
|
</div>
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { Eye } from "lucide-react";
|
|
6
5
|
|
|
7
6
|
interface VisionModeBorderProps {
|
|
8
7
|
active: boolean;
|
|
9
8
|
focusedCount?: number;
|
|
9
|
+
highlightEnabled?: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function VisionModeBorder({
|
|
12
|
+
export function VisionModeBorder({
|
|
13
|
+
active,
|
|
14
|
+
}: VisionModeBorderProps) {
|
|
13
15
|
const [mounted, setMounted] = useState(false);
|
|
14
16
|
|
|
15
17
|
useEffect(() => {
|
|
@@ -39,77 +41,24 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
|
|
|
39
41
|
}
|
|
40
42
|
`}</style>
|
|
41
43
|
|
|
42
|
-
{/* Main border overlay */}
|
|
44
|
+
{/* Main border overlay - respects devtools frame if present */}
|
|
45
|
+
{/* Removed corner pill and bottom instruction to not cover UI elements */}
|
|
43
46
|
<div
|
|
44
47
|
data-vision-mode-border="true"
|
|
45
48
|
style={{
|
|
46
49
|
position: "fixed",
|
|
47
|
-
top:
|
|
50
|
+
top: "var(--devtools-banner-height, 0px)",
|
|
48
51
|
left: 0,
|
|
49
|
-
right:
|
|
52
|
+
right: "var(--devtools-sidebar-width, 0px)",
|
|
50
53
|
bottom: 0,
|
|
51
54
|
pointerEvents: "none",
|
|
52
55
|
zIndex: 9996, // Below DevTools (9999) but above content
|
|
53
|
-
border: "
|
|
54
|
-
boxShadow: "inset 0 0
|
|
56
|
+
border: "3px solid #8B5CF6",
|
|
57
|
+
boxShadow: "inset 0 0 20px rgba(139, 92, 246, 0.2), 0 0 20px rgba(139, 92, 246, 0.2)",
|
|
55
58
|
animation: "vision-pulse 2s ease-in-out infinite, vision-glow 2s ease-in-out infinite",
|
|
59
|
+
transition: "all 0.2s ease-out",
|
|
56
60
|
}}
|
|
57
|
-
|
|
58
|
-
{/* Corner indicator */}
|
|
59
|
-
<div
|
|
60
|
-
style={{
|
|
61
|
-
position: "absolute",
|
|
62
|
-
top: "12px",
|
|
63
|
-
left: "12px",
|
|
64
|
-
display: "flex",
|
|
65
|
-
alignItems: "center",
|
|
66
|
-
gap: "6px",
|
|
67
|
-
backgroundColor: "#8B5CF6",
|
|
68
|
-
color: "white",
|
|
69
|
-
padding: "6px 12px",
|
|
70
|
-
borderRadius: "6px",
|
|
71
|
-
fontSize: "12px",
|
|
72
|
-
fontWeight: 600,
|
|
73
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
74
|
-
boxShadow: "0 2px 8px rgba(139, 92, 246, 0.4)",
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
<Eye size={14} />
|
|
78
|
-
<span>Vision Mode</span>
|
|
79
|
-
{focusedCount > 0 && (
|
|
80
|
-
<span
|
|
81
|
-
style={{
|
|
82
|
-
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
83
|
-
padding: "2px 6px",
|
|
84
|
-
borderRadius: "4px",
|
|
85
|
-
marginLeft: "4px",
|
|
86
|
-
}}
|
|
87
|
-
>
|
|
88
|
-
{focusedCount} focused
|
|
89
|
-
</span>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
{/* Instruction hint at bottom */}
|
|
94
|
-
<div
|
|
95
|
-
style={{
|
|
96
|
-
position: "absolute",
|
|
97
|
-
bottom: "12px",
|
|
98
|
-
left: "50%",
|
|
99
|
-
transform: "translateX(-50%)",
|
|
100
|
-
backgroundColor: "rgba(139, 92, 246, 0.9)",
|
|
101
|
-
color: "white",
|
|
102
|
-
padding: "8px 16px",
|
|
103
|
-
borderRadius: "8px",
|
|
104
|
-
fontSize: "13px",
|
|
105
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
106
|
-
boxShadow: "0 2px 12px rgba(139, 92, 246, 0.5)",
|
|
107
|
-
whiteSpace: "nowrap",
|
|
108
|
-
}}
|
|
109
|
-
>
|
|
110
|
-
Click elements to focus AI attention, then describe your changes in the chat
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
61
|
+
/>
|
|
113
62
|
</>,
|
|
114
63
|
document.body
|
|
115
64
|
);
|
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
|
|
2
2
|
import { Box, Image as ImageIcon, Type, Scan } from "lucide-react";
|
|
3
|
-
import { TabDefinition, ConfigSection } from "./types";
|
|
3
|
+
import { TabDefinition, ConfigSection, ElementFilters } from "./types";
|
|
4
4
|
import { componentSnippets } from "../../lib/brand-system";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
// Note: Tabs have been simplified - elements are now unified with filters
|
|
7
|
+
// Analysis is the only separate "tab" mode
|
|
8
|
+
|
|
9
|
+
// Default element filters - all enabled by default
|
|
10
|
+
export const DEFAULT_ELEMENT_FILTERS: ElementFilters = {
|
|
11
|
+
components: true,
|
|
12
|
+
images: true,
|
|
13
|
+
text: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Element type color scheme for the unified view
|
|
17
|
+
export const ELEMENT_TYPE_COLORS = {
|
|
18
|
+
components: {
|
|
19
|
+
border: "#8B5CF6", // Purple
|
|
20
|
+
bg: "rgba(139, 92, 246, 0.1)",
|
|
21
|
+
activeBg: "bg-purple-100",
|
|
22
|
+
activeText: "text-purple-700",
|
|
23
|
+
activeBorder: "border-purple-300",
|
|
24
|
+
label: "Components",
|
|
25
|
+
},
|
|
26
|
+
images: {
|
|
27
|
+
border: "#3B82F6", // Blue
|
|
28
|
+
bg: "rgba(59, 130, 246, 0.1)",
|
|
29
|
+
activeBg: "bg-blue-100",
|
|
30
|
+
activeText: "text-blue-700",
|
|
31
|
+
activeBorder: "border-blue-300",
|
|
32
|
+
label: "Images",
|
|
33
|
+
},
|
|
34
|
+
text: {
|
|
35
|
+
border: "#F59E0B", // Amber
|
|
36
|
+
bg: "rgba(245, 158, 11, 0.1)",
|
|
37
|
+
activeBg: "bg-amber-100",
|
|
38
|
+
activeText: "text-amber-700",
|
|
39
|
+
activeBorder: "border-amber-300",
|
|
40
|
+
label: "Text",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
11
43
|
|
|
12
44
|
// Quick action presets for common styling changes
|
|
13
45
|
export const QUICK_ACTIONS = [
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev Tools Hooks
|
|
3
|
+
*
|
|
4
|
+
* Modular hooks for element detection and styling in the Sonance DevTools.
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* - useElementScanner: Main orchestrator that combines all detection types
|
|
8
|
+
* - useComponentDetection: Component-specific detection
|
|
9
|
+
* - useImageDetection: Image/logo detection with original state tracking
|
|
10
|
+
* - useTextDetection: Text element detection with filtering
|
|
11
|
+
* - useContentHash: Content-based ID generation for stability
|
|
12
|
+
* - useComputedStyles: Get computed styles for selected elements
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Main scanner hook (use this in most cases)
|
|
16
|
+
export {
|
|
17
|
+
useElementScanner,
|
|
18
|
+
extractLogoName,
|
|
19
|
+
type ElementScannerOptions,
|
|
20
|
+
type ElementScannerResult,
|
|
21
|
+
} from "./useElementScanner";
|
|
22
|
+
|
|
23
|
+
// Individual detection hooks (for custom implementations)
|
|
24
|
+
export {
|
|
25
|
+
useComponentDetection,
|
|
26
|
+
detectComponents,
|
|
27
|
+
type ComponentDetectionConfig,
|
|
28
|
+
} from "./useComponentDetection";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
useImageDetection,
|
|
32
|
+
detectImages,
|
|
33
|
+
type ImageDetectionConfig,
|
|
34
|
+
type ImageDetectionResult,
|
|
35
|
+
} from "./useImageDetection";
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
useTextDetection,
|
|
39
|
+
detectTextElements,
|
|
40
|
+
type TextDetectionConfig,
|
|
41
|
+
type TextDetectionResult,
|
|
42
|
+
} from "./useTextDetection";
|
|
43
|
+
|
|
44
|
+
// Content-based hashing utilities
|
|
45
|
+
export {
|
|
46
|
+
hashString,
|
|
47
|
+
generateTextElementId,
|
|
48
|
+
generateImageElementId,
|
|
49
|
+
generateComponentId,
|
|
50
|
+
generateVariantId,
|
|
51
|
+
migrateLegacyIds,
|
|
52
|
+
isLegacyId,
|
|
53
|
+
isMigrationComplete,
|
|
54
|
+
markMigrationComplete,
|
|
55
|
+
MIGRATION_STORAGE_KEY,
|
|
56
|
+
MIGRATION_VERSION,
|
|
57
|
+
} from "./useContentHash";
|
|
58
|
+
|
|
59
|
+
// Computed styles hook (existing)
|
|
60
|
+
export {
|
|
61
|
+
useComputedStyles,
|
|
62
|
+
useElementFromCoordinates,
|
|
63
|
+
type ComputedStyles,
|
|
64
|
+
type ComputedGeometry,
|
|
65
|
+
type ComputedTypography,
|
|
66
|
+
type ComputedFill,
|
|
67
|
+
type ComputedStroke,
|
|
68
|
+
type ComputedEffect,
|
|
69
|
+
} from "./useComputedStyles";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { DetectedElement } from "../types";
|
|
5
|
+
import { generateVariantId, generateComponentId } from "./useContentHash";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Component detection configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface ComponentDetectionConfig {
|
|
11
|
+
/** Whether to include elements in modals/dialogs */
|
|
12
|
+
includeModals: boolean;
|
|
13
|
+
/** Callback to check if element is in active layer */
|
|
14
|
+
isInActiveLayer: (el: Element) => boolean;
|
|
15
|
+
/** Callback to get visible rect (clipped to scroll container) */
|
|
16
|
+
getVisibleRect: (el: Element, rect: DOMRect) => DOMRect | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generic element selectors for untagged primitives
|
|
21
|
+
*/
|
|
22
|
+
const GENERIC_SELECTORS: Record<string, string> = {
|
|
23
|
+
button: "button:not([data-sonance-name])",
|
|
24
|
+
input: "input:not([data-sonance-name])",
|
|
25
|
+
select: "select:not([data-sonance-name])",
|
|
26
|
+
textarea: "textarea:not([data-sonance-name])",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect components on the page
|
|
31
|
+
* Handles both explicitly tagged components and generic primitives
|
|
32
|
+
*/
|
|
33
|
+
export function detectComponents(config: ComponentDetectionConfig): DetectedElement[] {
|
|
34
|
+
const { isInActiveLayer, getVisibleRect } = config;
|
|
35
|
+
const detected: DetectedElement[] = [];
|
|
36
|
+
|
|
37
|
+
// 1. Scan for explicitly tagged components
|
|
38
|
+
const taggedComponents = document.querySelectorAll("[data-sonance-name]");
|
|
39
|
+
taggedComponents.forEach((el) => {
|
|
40
|
+
if (!isInActiveLayer(el)) return;
|
|
41
|
+
|
|
42
|
+
const name = el.getAttribute("data-sonance-name");
|
|
43
|
+
if (!name) return;
|
|
44
|
+
|
|
45
|
+
const rawRect = el.getBoundingClientRect();
|
|
46
|
+
const rect = getVisibleRect(el, rawRect);
|
|
47
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return;
|
|
48
|
+
|
|
49
|
+
// Get or generate stable variant ID
|
|
50
|
+
let variantId = el.getAttribute("data-sonance-variant");
|
|
51
|
+
if (!variantId) {
|
|
52
|
+
variantId = generateVariantId(el);
|
|
53
|
+
el.setAttribute("data-sonance-variant", variantId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Capture metadata for AI context
|
|
57
|
+
const textContent = (el.textContent?.trim() || "").substring(0, 100);
|
|
58
|
+
const className = el.className?.toString() || "";
|
|
59
|
+
const elementId = el.id || undefined;
|
|
60
|
+
const childIds = Array.from(el.querySelectorAll("[id]"))
|
|
61
|
+
.map((child) => child.id)
|
|
62
|
+
.filter((id) => id) as string[];
|
|
63
|
+
|
|
64
|
+
detected.push({
|
|
65
|
+
name,
|
|
66
|
+
rect,
|
|
67
|
+
type: "component",
|
|
68
|
+
variantId,
|
|
69
|
+
textContent,
|
|
70
|
+
className,
|
|
71
|
+
elementId,
|
|
72
|
+
childIds: childIds.length > 0 ? childIds : undefined,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 2. Scan for untagged primitive elements
|
|
77
|
+
for (const [genericName, selector] of Object.entries(GENERIC_SELECTORS)) {
|
|
78
|
+
const elements = document.querySelectorAll(selector);
|
|
79
|
+
elements.forEach((el) => {
|
|
80
|
+
if (!isInActiveLayer(el)) return;
|
|
81
|
+
|
|
82
|
+
const rawRect = el.getBoundingClientRect();
|
|
83
|
+
const rect = getVisibleRect(el, rawRect);
|
|
84
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return;
|
|
85
|
+
|
|
86
|
+
// Get or generate stable variant ID
|
|
87
|
+
let variantId = el.getAttribute("data-sonance-variant");
|
|
88
|
+
if (!variantId) {
|
|
89
|
+
variantId = generateVariantId(el);
|
|
90
|
+
el.setAttribute("data-sonance-variant", variantId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Auto-tag generic elements for future detection
|
|
94
|
+
if (!el.getAttribute("data-sonance-name")) {
|
|
95
|
+
el.setAttribute("data-sonance-name", genericName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Capture metadata
|
|
99
|
+
const textContent = (el.textContent?.trim() || "").substring(0, 100);
|
|
100
|
+
const elClassName = el.className?.toString() || "";
|
|
101
|
+
const elementId = el.id || undefined;
|
|
102
|
+
const childIds = Array.from(el.querySelectorAll("[id]"))
|
|
103
|
+
.map((child) => child.id)
|
|
104
|
+
.filter((id) => id) as string[];
|
|
105
|
+
|
|
106
|
+
detected.push({
|
|
107
|
+
name: genericName,
|
|
108
|
+
rect,
|
|
109
|
+
type: "component",
|
|
110
|
+
variantId,
|
|
111
|
+
textContent,
|
|
112
|
+
className: elClassName,
|
|
113
|
+
elementId,
|
|
114
|
+
childIds: childIds.length > 0 ? childIds : undefined,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return detected;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Hook for component detection
|
|
124
|
+
* Returns a memoized detection function
|
|
125
|
+
*/
|
|
126
|
+
export function useComponentDetection() {
|
|
127
|
+
const detect = useCallback((config: ComponentDetectionConfig): DetectedElement[] => {
|
|
128
|
+
return detectComponents(config);
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
return { detectComponents: detect };
|
|
132
|
+
}
|