@nuvio/overlay 0.1.0

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/index.js ADDED
@@ -0,0 +1,2132 @@
1
+ // src/NuvioDevShell.tsx
2
+ import {
3
+ useCallback as useCallback3,
4
+ useEffect as useEffect5,
5
+ useLayoutEffect as useLayoutEffect2,
6
+ useMemo as useMemo2,
7
+ useRef as useRef4,
8
+ useState as useState5
9
+ } from "react";
10
+ import {
11
+ NUVIO_WS_PATH,
12
+ PROTOCOL_VERSION,
13
+ parseServerMessage
14
+ } from "@nuvio/shared";
15
+
16
+ // src/InteractionLayer.tsx
17
+ import { useEffect, useState } from "react";
18
+
19
+ // src/nuvio-dom.ts
20
+ function escapeAttrSelector(id) {
21
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
22
+ return CSS.escape(id);
23
+ }
24
+ return id.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
25
+ }
26
+
27
+ // src/nuvio-outlines.ts
28
+ function clearNuvioOutlines() {
29
+ document.querySelectorAll("[data-nuvio-outline]").forEach((n) => {
30
+ const el = n;
31
+ el.style.outline = "";
32
+ el.removeAttribute("data-nuvio-outline");
33
+ });
34
+ }
35
+ function paintNuvioOutline(id, mode) {
36
+ const el = document.querySelector(
37
+ `[data-nuvio-id="${escapeAttrSelector(id)}"]`
38
+ );
39
+ if (!el) {
40
+ return;
41
+ }
42
+ el.setAttribute("data-nuvio-outline", mode);
43
+ if (mode === "selected") {
44
+ el.style.outline = "2px solid rgb(14 165 233)";
45
+ } else {
46
+ el.style.outline = "2px dashed rgb(56 189 248)";
47
+ }
48
+ }
49
+
50
+ // src/InteractionLayer.tsx
51
+ function isUnderOverlayChrome(node, chromeRootRefs) {
52
+ for (const ref of chromeRootRefs) {
53
+ const root = ref.current;
54
+ if (root?.contains(node)) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ function pickIndexedTarget(clientX, clientY, chromeRootRefs, knownIds) {
61
+ const stack = document.elementsFromPoint(clientX, clientY);
62
+ for (const node of stack) {
63
+ if (!(node instanceof HTMLElement)) {
64
+ continue;
65
+ }
66
+ if (isUnderOverlayChrome(node, chromeRootRefs)) {
67
+ continue;
68
+ }
69
+ const host = node.closest("[data-nuvio-id]");
70
+ if (!(host instanceof HTMLElement)) {
71
+ continue;
72
+ }
73
+ const id = host.getAttribute("data-nuvio-id")?.trim() ?? "";
74
+ if (!id) {
75
+ continue;
76
+ }
77
+ if (knownIds.size > 0 && !knownIds.has(id)) {
78
+ continue;
79
+ }
80
+ return host;
81
+ }
82
+ return null;
83
+ }
84
+ function InteractionLayer({
85
+ enabled,
86
+ chromeRootRefs,
87
+ knownIds,
88
+ selectedId,
89
+ onSelectId
90
+ }) {
91
+ const [hoverId, setHoverId] = useState(null);
92
+ useEffect(() => {
93
+ if (!enabled) {
94
+ setHoverId(null);
95
+ return;
96
+ }
97
+ const onMove = (e) => {
98
+ const el = pickIndexedTarget(e.clientX, e.clientY, chromeRootRefs, knownIds);
99
+ const id = el?.getAttribute("data-nuvio-id") ?? null;
100
+ setHoverId(id);
101
+ };
102
+ const onClick = (e) => {
103
+ const el = pickIndexedTarget(e.clientX, e.clientY, chromeRootRefs, knownIds);
104
+ if (!el) {
105
+ return;
106
+ }
107
+ e.preventDefault();
108
+ e.stopPropagation();
109
+ const id = el.getAttribute("data-nuvio-id");
110
+ if (id) {
111
+ onSelectId(id);
112
+ }
113
+ };
114
+ window.addEventListener("mousemove", onMove, true);
115
+ window.addEventListener("click", onClick, true);
116
+ return () => {
117
+ window.removeEventListener("mousemove", onMove, true);
118
+ window.removeEventListener("click", onClick, true);
119
+ setHoverId(null);
120
+ };
121
+ }, [chromeRootRefs, enabled, knownIds, onSelectId]);
122
+ useEffect(() => {
123
+ clearNuvioOutlines();
124
+ if (!enabled) {
125
+ return;
126
+ }
127
+ const hoverPaint = hoverId && hoverId !== selectedId ? hoverId : null;
128
+ if (hoverPaint) {
129
+ paintNuvioOutline(hoverPaint, "hover");
130
+ }
131
+ if (selectedId) {
132
+ paintNuvioOutline(selectedId, "selected");
133
+ }
134
+ }, [enabled, hoverId, selectedId]);
135
+ return null;
136
+ }
137
+
138
+ // src/overlay-chrome-storage.ts
139
+ var OVERLAY_CHROME_STORAGE_KEY = "nuvio:overlay-chrome:v1";
140
+ var DEFAULT_OVERLAY_CHROME = {
141
+ panel: { collapsed: false, position: null },
142
+ chip: { collapsed: false, corner: "bottom-right" }
143
+ };
144
+ var CHIP_CORNERS = [
145
+ "bottom-right",
146
+ "bottom-left",
147
+ "top-right",
148
+ "top-left"
149
+ ];
150
+ function isChipCorner(v) {
151
+ return typeof v === "string" && CHIP_CORNERS.includes(v);
152
+ }
153
+ function isPoint(v) {
154
+ return typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number" && Number.isFinite(v.x) && Number.isFinite(v.y);
155
+ }
156
+ function parseOverlayChromePersist(raw) {
157
+ if (!raw) {
158
+ return DEFAULT_OVERLAY_CHROME;
159
+ }
160
+ try {
161
+ const data = JSON.parse(raw);
162
+ const panel = data.panel;
163
+ const chip = data.chip;
164
+ return {
165
+ panel: {
166
+ collapsed: panel?.collapsed === true,
167
+ position: isPoint(panel?.position) ? panel.position : null
168
+ },
169
+ chip: {
170
+ collapsed: chip?.collapsed === true,
171
+ corner: isChipCorner(chip?.corner) ? chip.corner : DEFAULT_OVERLAY_CHROME.chip.corner
172
+ }
173
+ };
174
+ } catch {
175
+ return DEFAULT_OVERLAY_CHROME;
176
+ }
177
+ }
178
+ function loadOverlayChromePersist() {
179
+ if (typeof localStorage === "undefined") {
180
+ return DEFAULT_OVERLAY_CHROME;
181
+ }
182
+ return parseOverlayChromePersist(localStorage.getItem(OVERLAY_CHROME_STORAGE_KEY));
183
+ }
184
+ function saveOverlayChromePersist(state) {
185
+ if (typeof localStorage === "undefined") {
186
+ return;
187
+ }
188
+ try {
189
+ localStorage.setItem(OVERLAY_CHROME_STORAGE_KEY, JSON.stringify(state));
190
+ } catch {
191
+ }
192
+ }
193
+ function clampToViewport(x, y, width, height, margin = 16) {
194
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1920;
195
+ const vh = typeof window !== "undefined" ? window.innerHeight : 1080;
196
+ const maxX = Math.max(margin, vw - width - margin);
197
+ const maxY = Math.max(margin, vh - height - margin);
198
+ return {
199
+ x: Math.max(margin, Math.min(x, maxX)),
200
+ y: Math.max(margin, Math.min(y, maxY))
201
+ };
202
+ }
203
+ function snapToNearestCorner(centerX, centerY) {
204
+ const midX = (typeof window !== "undefined" ? window.innerWidth : 1920) / 2;
205
+ const midY = (typeof window !== "undefined" ? window.innerHeight : 1080) / 2;
206
+ const right = centerX >= midX;
207
+ const bottom = centerY >= midY;
208
+ if (bottom && right) {
209
+ return "bottom-right";
210
+ }
211
+ if (bottom && !right) {
212
+ return "bottom-left";
213
+ }
214
+ if (!bottom && right) {
215
+ return "top-right";
216
+ }
217
+ return "top-left";
218
+ }
219
+ function cornerAnchorPosition(corner, width, height, margin = 16) {
220
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1920;
221
+ const vh = typeof window !== "undefined" ? window.innerHeight : 1080;
222
+ switch (corner) {
223
+ case "bottom-right":
224
+ return { x: vw - width - margin, y: vh - height - margin };
225
+ case "bottom-left":
226
+ return { x: margin, y: vh - height - margin };
227
+ case "top-right":
228
+ return { x: vw - width - margin, y: margin };
229
+ case "top-left":
230
+ return { x: margin, y: margin };
231
+ }
232
+ }
233
+
234
+ // src/overlay-chrome-classes.ts
235
+ var NUVO_GLASS_FRAME = "border border-white/20 shadow-2xl shadow-black/50 ring-1 ring-white/10";
236
+ var NUVO_GLASS_HEADER = "border-b border-white/10";
237
+ var NUVO_GLASS_SECTION = "rounded-lg border border-white/10 bg-slate-950/30 backdrop-blur-md";
238
+ var NUVO_GLASS_SURFACE_STYLE = {
239
+ backgroundColor: "rgba(2, 6, 23, 0.45)",
240
+ WebkitBackdropFilter: "blur(24px) saturate(180%)",
241
+ backdropFilter: "blur(24px) saturate(180%)"
242
+ };
243
+ var NUVO_GLASS_CHIP = NUVO_GLASS_FRAME;
244
+
245
+ // src/PropertyPanelShell.tsx
246
+ import {
247
+ useCallback as useCallback2,
248
+ useEffect as useEffect4,
249
+ useLayoutEffect,
250
+ useMemo,
251
+ useRef as useRef3,
252
+ useState as useState4
253
+ } from "react";
254
+
255
+ // src/useChromeDrag.ts
256
+ import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
257
+ function useChromeDrag({
258
+ shellRef,
259
+ enabled,
260
+ position,
261
+ setPosition,
262
+ onDragEnd,
263
+ margin = 16
264
+ }) {
265
+ const [dragging, setDragging] = useState2(false);
266
+ const offsetRef = useRef({ x: 0, y: 0 });
267
+ const positionRef = useRef(position);
268
+ positionRef.current = position;
269
+ const onHeaderPointerDown = useCallback(
270
+ (e) => {
271
+ if (!enabled || e.button !== 0) {
272
+ return;
273
+ }
274
+ const shell = shellRef.current;
275
+ if (!shell) {
276
+ return;
277
+ }
278
+ e.preventDefault();
279
+ const rect = shell.getBoundingClientRect();
280
+ const startPos = positionRef.current ?? { x: rect.left, y: rect.top };
281
+ if (positionRef.current === null) {
282
+ setPosition(startPos);
283
+ }
284
+ offsetRef.current = {
285
+ x: e.clientX - startPos.x,
286
+ y: e.clientY - startPos.y
287
+ };
288
+ e.currentTarget.setPointerCapture(e.pointerId);
289
+ setDragging(true);
290
+ },
291
+ [enabled, shellRef, setPosition]
292
+ );
293
+ useEffect2(() => {
294
+ if (!dragging) {
295
+ return;
296
+ }
297
+ const onMove = (e) => {
298
+ const shell = shellRef.current;
299
+ if (!shell) {
300
+ return;
301
+ }
302
+ const w = shell.offsetWidth;
303
+ const h = shell.offsetHeight;
304
+ const rawX = e.clientX - offsetRef.current.x;
305
+ const rawY = e.clientY - offsetRef.current.y;
306
+ const next = clampToViewport(rawX, rawY, w, h, margin);
307
+ setPosition(next);
308
+ };
309
+ const onUp = () => {
310
+ setDragging(false);
311
+ const shell = shellRef.current;
312
+ const pos = positionRef.current;
313
+ if (shell && pos) {
314
+ const w = shell.offsetWidth;
315
+ const h = shell.offsetHeight;
316
+ const clamped = clampToViewport(pos.x, pos.y, w, h, margin);
317
+ setPosition(clamped);
318
+ onDragEnd?.(clamped);
319
+ }
320
+ };
321
+ window.addEventListener("pointermove", onMove);
322
+ window.addEventListener("pointerup", onUp);
323
+ window.addEventListener("pointercancel", onUp);
324
+ return () => {
325
+ window.removeEventListener("pointermove", onMove);
326
+ window.removeEventListener("pointerup", onUp);
327
+ window.removeEventListener("pointercancel", onUp);
328
+ };
329
+ }, [dragging, margin, onDragEnd, setPosition, shellRef]);
330
+ return { dragging, onHeaderPointerDown };
331
+ }
332
+
333
+ // src/alpha-patch-ops.ts
334
+ var EMPTY_ALPHA_PICKS = {
335
+ fontSize: "",
336
+ fontWeight: "",
337
+ textColor: "",
338
+ bgColor: "",
339
+ rounded: "",
340
+ padding: "",
341
+ margin: "",
342
+ textAlign: "",
343
+ gap: "",
344
+ width: "",
345
+ maxWidth: "",
346
+ height: "",
347
+ minHeight: "",
348
+ opacity: "",
349
+ shadow: ""
350
+ };
351
+ function buildAlphaPatchOps(baselineText, draftText, baselinePicks, draftPicks) {
352
+ const ops = [];
353
+ if (draftText !== baselineText) {
354
+ ops.push({ kind: "setText", text: draftText });
355
+ }
356
+ const keys = Object.keys(EMPTY_ALPHA_PICKS);
357
+ for (const key of keys) {
358
+ const next = draftPicks[key].trim();
359
+ if (!next || next === baselinePicks[key]) {
360
+ continue;
361
+ }
362
+ ops.push({ kind: "mergeTailwindClassName", classNameFragment: next });
363
+ }
364
+ return ops;
365
+ }
366
+
367
+ // src/tailwind-palette-hex.ts
368
+ var TAILWIND_COLOR_FAMILIES = ["slate", "gray", "zinc", "neutral", "stone", "red", "orange", "amber", "yellow", "lime", "green", "emerald", "teal", "cyan", "sky", "blue", "indigo", "violet", "purple", "fuchsia", "pink", "rose"];
369
+ var TAILWIND_COLOR_SHADES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
370
+ var TAILWIND_PALETTE_HEX = { "slate": { "50": "#f8fafc", "100": "#f1f5f9", "200": "#e2e8f0", "300": "#cbd5e1", "400": "#94a3b8", "500": "#64748b", "600": "#475569", "700": "#334155", "800": "#1e293b", "900": "#0f172a", "950": "#020617" }, "gray": { "50": "#f9fafb", "100": "#f3f4f6", "200": "#e5e7eb", "300": "#d1d5db", "400": "#9ca3af", "500": "#6b7280", "600": "#4b5563", "700": "#374151", "800": "#1f2937", "900": "#111827", "950": "#030712" }, "zinc": { "50": "#fafafa", "100": "#f4f4f5", "200": "#e4e4e7", "300": "#d4d4d8", "400": "#a1a1aa", "500": "#71717a", "600": "#52525b", "700": "#3f3f46", "800": "#27272a", "900": "#18181b", "950": "#09090b" }, "neutral": { "50": "#fafafa", "100": "#f5f5f5", "200": "#e5e5e5", "300": "#d4d4d4", "400": "#a3a3a3", "500": "#737373", "600": "#525252", "700": "#404040", "800": "#262626", "900": "#171717", "950": "#0a0a0a" }, "stone": { "50": "#fafaf9", "100": "#f5f5f4", "200": "#e7e5e4", "300": "#d6d3d1", "400": "#a8a29e", "500": "#78716c", "600": "#57534e", "700": "#44403c", "800": "#292524", "900": "#1c1917", "950": "#0c0a09" }, "red": { "50": "#fef2f2", "100": "#fee2e2", "200": "#fecaca", "300": "#fca5a5", "400": "#f87171", "500": "#ef4444", "600": "#dc2626", "700": "#b91c1c", "800": "#991b1b", "900": "#7f1d1d", "950": "#450a0a" }, "orange": { "50": "#fff7ed", "100": "#ffedd5", "200": "#fed7aa", "300": "#fdba74", "400": "#fb923c", "500": "#f97316", "600": "#ea580c", "700": "#c2410c", "800": "#9a3412", "900": "#7c2d12", "950": "#431407" }, "amber": { "50": "#fffbeb", "100": "#fef3c7", "200": "#fde68a", "300": "#fcd34d", "400": "#fbbf24", "500": "#f59e0b", "600": "#d97706", "700": "#b45309", "800": "#92400e", "900": "#78350f", "950": "#451a03" }, "yellow": { "50": "#fefce8", "100": "#fef9c3", "200": "#fef08a", "300": "#fde047", "400": "#facc15", "500": "#eab308", "600": "#ca8a04", "700": "#a16207", "800": "#854d0e", "900": "#713f12", "950": "#422006" }, "lime": { "50": "#f7fee7", "100": "#ecfccb", "200": "#d9f99d", "300": "#bef264", "400": "#a3e635", "500": "#84cc16", "600": "#65a30d", "700": "#4d7c0f", "800": "#3f6212", "900": "#365314", "950": "#1a2e05" }, "green": { "50": "#f0fdf4", "100": "#dcfce7", "200": "#bbf7d0", "300": "#86efac", "400": "#4ade80", "500": "#22c55e", "600": "#16a34a", "700": "#15803d", "800": "#166534", "900": "#14532d", "950": "#052e16" }, "emerald": { "50": "#ecfdf5", "100": "#d1fae5", "200": "#a7f3d0", "300": "#6ee7b7", "400": "#34d399", "500": "#10b981", "600": "#059669", "700": "#047857", "800": "#065f46", "900": "#064e3b", "950": "#022c22" }, "teal": { "50": "#f0fdfa", "100": "#ccfbf1", "200": "#99f6e4", "300": "#5eead4", "400": "#2dd4bf", "500": "#14b8a6", "600": "#0d9488", "700": "#0f766e", "800": "#115e59", "900": "#134e4a", "950": "#042f2e" }, "cyan": { "50": "#ecfeff", "100": "#cffafe", "200": "#a5f3fc", "300": "#67e8f9", "400": "#22d3ee", "500": "#06b6d4", "600": "#0891b2", "700": "#0e7490", "800": "#155e75", "900": "#164e63", "950": "#083344" }, "sky": { "50": "#f0f9ff", "100": "#e0f2fe", "200": "#bae6fd", "300": "#7dd3fc", "400": "#38bdf8", "500": "#0ea5e9", "600": "#0284c7", "700": "#0369a1", "800": "#075985", "900": "#0c4a6e", "950": "#082f49" }, "blue": { "50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd", "400": "#60a5fa", "500": "#3b82f6", "600": "#2563eb", "700": "#1d4ed8", "800": "#1e40af", "900": "#1e3a8a", "950": "#172554" }, "indigo": { "50": "#eef2ff", "100": "#e0e7ff", "200": "#c7d2fe", "300": "#a5b4fc", "400": "#818cf8", "500": "#6366f1", "600": "#4f46e5", "700": "#4338ca", "800": "#3730a3", "900": "#312e81", "950": "#1e1b4b" }, "violet": { "50": "#f5f3ff", "100": "#ede9fe", "200": "#ddd6fe", "300": "#c4b5fd", "400": "#a78bfa", "500": "#8b5cf6", "600": "#7c3aed", "700": "#6d28d9", "800": "#5b21b6", "900": "#4c1d95", "950": "#2e1065" }, "purple": { "50": "#faf5ff", "100": "#f3e8ff", "200": "#e9d5ff", "300": "#d8b4fe", "400": "#c084fc", "500": "#a855f7", "600": "#9333ea", "700": "#7e22ce", "800": "#6b21a8", "900": "#581c87", "950": "#3b0764" }, "fuchsia": { "50": "#fdf4ff", "100": "#fae8ff", "200": "#f5d0fe", "300": "#f0abfc", "400": "#e879f9", "500": "#d946ef", "600": "#c026d3", "700": "#a21caf", "800": "#86198f", "900": "#701a75", "950": "#4a044e" }, "pink": { "50": "#fdf2f8", "100": "#fce7f3", "200": "#fbcfe8", "300": "#f9a8d4", "400": "#f472b6", "500": "#ec4899", "600": "#db2777", "700": "#be185d", "800": "#9d174d", "900": "#831843", "950": "#500724" }, "rose": { "50": "#fff1f2", "100": "#ffe4e6", "200": "#fecdd3", "300": "#fda4af", "400": "#fb7185", "500": "#f43f5e", "600": "#e11d48", "700": "#be123c", "800": "#9f1239", "900": "#881337", "950": "#4c0519" } };
371
+
372
+ // src/tailwind-color-options.ts
373
+ function hexFor(family, shade) {
374
+ return TAILWIND_PALETTE_HEX[family]?.[String(shade)] ?? "#888";
375
+ }
376
+ function buildScaleOptions(prefix) {
377
+ const opts = [];
378
+ for (const family of TAILWIND_COLOR_FAMILIES) {
379
+ for (const shade of TAILWIND_COLOR_SHADES) {
380
+ const value = `${prefix}-${family}-${shade}`;
381
+ opts.push({ value, label: value, hex: hexFor(family, shade) });
382
+ }
383
+ }
384
+ return opts;
385
+ }
386
+ var TEXT_COLOR_OPTIONS = [
387
+ { value: "", label: "\u2014", hex: "transparent" },
388
+ { value: "text-white", label: "text-white", hex: "#ffffff" },
389
+ { value: "text-black", label: "text-black", hex: "#000000" },
390
+ ...buildScaleOptions("text")
391
+ ];
392
+ function buildBgOpacityOptions() {
393
+ const families = ["slate", "sky", "neutral"];
394
+ const shades = [800, 900, 950];
395
+ const ops = [50, 75, 80];
396
+ const out = [];
397
+ for (const family of families) {
398
+ for (const shade of shades) {
399
+ for (const op of ops) {
400
+ const value = `bg-${family}-${shade}/${op}`;
401
+ out.push({ value, label: value, hex: hexFor(family, shade) });
402
+ }
403
+ }
404
+ }
405
+ return out;
406
+ }
407
+ var BACKGROUND_COLOR_OPTIONS = [
408
+ { value: "", label: "\u2014", hex: "transparent" },
409
+ { value: "bg-transparent", label: "bg-transparent", hex: "transparent" },
410
+ { value: "bg-white", label: "bg-white", hex: "#ffffff" },
411
+ { value: "bg-black", label: "bg-black", hex: "#000000" },
412
+ ...buildScaleOptions("bg"),
413
+ ...buildBgOpacityOptions()
414
+ ];
415
+ var TEXT_COLOR_VALUES = TEXT_COLOR_OPTIONS.map((o) => o.value).filter(Boolean);
416
+ var BACKGROUND_COLOR_VALUES = BACKGROUND_COLOR_OPTIONS.map((o) => o.value).filter(
417
+ Boolean
418
+ );
419
+
420
+ // src/read-alpha-picks.ts
421
+ var FONT_SIZE = ["text-sm", "text-base", "text-lg", "text-xl", "text-2xl"];
422
+ var FONT_WEIGHT = ["font-medium", "font-semibold", "font-bold"];
423
+ var TEXT_COLOR = TEXT_COLOR_VALUES;
424
+ var BG_COLOR = BACKGROUND_COLOR_VALUES;
425
+ var ROUNDED = ["rounded-md", "rounded-lg", "rounded-xl", "rounded-full"];
426
+ var PADDING = ["p-2", "p-4", "p-6", "px-4 py-2"];
427
+ var MARGIN = ["m-2", "m-4", "mx-auto", "mt-4", "mb-4"];
428
+ var TEXT_ALIGN = ["text-left", "text-center", "text-right", "text-justify"];
429
+ var GAP = ["gap-1", "gap-2", "gap-4", "gap-6", "gap-8"];
430
+ var WIDTH = ["w-auto", "w-full", "w-1/2", "w-1/3", "w-2/3", "w-1/4", "w-3/4"];
431
+ var MAX_WIDTH = [
432
+ "max-w-sm",
433
+ "max-w-md",
434
+ "max-w-lg",
435
+ "max-w-xl",
436
+ "max-w-2xl",
437
+ "max-w-4xl",
438
+ "max-w-prose",
439
+ "max-w-full"
440
+ ];
441
+ var HEIGHT = ["h-auto", "h-full", "h-8", "h-12", "h-16", "h-24", "h-screen"];
442
+ var MIN_HEIGHT = ["min-h-0", "min-h-full", "min-h-screen", "min-h-16", "min-h-24"];
443
+ var OPACITY = [
444
+ "opacity-0",
445
+ "opacity-25",
446
+ "opacity-50",
447
+ "opacity-75",
448
+ "opacity-100"
449
+ ];
450
+ var SHADOW = ["shadow-none", "shadow-sm", "shadow", "shadow-md", "shadow-lg", "shadow-xl"];
451
+ function tokenizeClassName(className) {
452
+ return className.trim().split(/\s+/).filter(Boolean);
453
+ }
454
+ function lastLiteralMatch(tokens, candidates) {
455
+ let hit = "";
456
+ for (const t of tokens) {
457
+ if (candidates.includes(t)) {
458
+ hit = t;
459
+ }
460
+ }
461
+ return hit;
462
+ }
463
+ function lastCompositeMatch(tokens, composites) {
464
+ let hit = "";
465
+ for (const composite of composites) {
466
+ const parts = composite.split(/\s+/).filter(Boolean);
467
+ if (parts.length > 0 && parts.every((p) => tokens.includes(p))) {
468
+ hit = composite;
469
+ }
470
+ }
471
+ return hit;
472
+ }
473
+ function readAlphaPicksFromClassName(className) {
474
+ const tokens = tokenizeClassName(className);
475
+ if (tokens.length === 0) {
476
+ return { ...EMPTY_ALPHA_PICKS };
477
+ }
478
+ return {
479
+ fontSize: lastLiteralMatch(tokens, FONT_SIZE),
480
+ fontWeight: lastLiteralMatch(tokens, FONT_WEIGHT),
481
+ textColor: lastLiteralMatch(tokens, TEXT_COLOR),
482
+ bgColor: lastLiteralMatch(tokens, BG_COLOR),
483
+ rounded: lastLiteralMatch(tokens, ROUNDED),
484
+ padding: lastCompositeMatch(tokens, PADDING) || lastLiteralMatch(tokens, PADDING),
485
+ margin: lastLiteralMatch(tokens, MARGIN),
486
+ textAlign: lastLiteralMatch(tokens, TEXT_ALIGN),
487
+ gap: lastLiteralMatch(tokens, GAP),
488
+ width: lastLiteralMatch(tokens, WIDTH),
489
+ maxWidth: lastLiteralMatch(tokens, MAX_WIDTH),
490
+ height: lastLiteralMatch(tokens, HEIGHT),
491
+ minHeight: lastLiteralMatch(tokens, MIN_HEIGHT),
492
+ opacity: lastLiteralMatch(tokens, OPACITY),
493
+ shadow: lastLiteralMatch(tokens, SHADOW)
494
+ };
495
+ }
496
+ function readAlphaPicksFromElement(el) {
497
+ return readAlphaPicksFromClassName(el.className);
498
+ }
499
+ function alphaPicksDiffer(a, b) {
500
+ return Object.keys(EMPTY_ALPHA_PICKS).some((k) => a[k] !== b[k]);
501
+ }
502
+
503
+ // src/ComponentTree.tsx
504
+ import { jsx, jsxs } from "react/jsx-runtime";
505
+ function shortPath(file) {
506
+ const norm = file.replace(/\\/g, "/");
507
+ const parts = norm.split("/");
508
+ return parts.length <= 2 ? norm : parts.slice(-2).join("/");
509
+ }
510
+ function ComponentTree({
511
+ entries,
512
+ selectedId,
513
+ onSelectId
514
+ }) {
515
+ const sorted = [...entries].sort((a, b) => a.id.localeCompare(b.id));
516
+ return /* @__PURE__ */ jsxs("section", { children: [
517
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-xs font-medium uppercase tracking-wide text-slate-500", children: "Indexed elements" }),
518
+ sorted.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-600", children: "No ids in dev index." }) : /* @__PURE__ */ jsx("ul", { className: "max-h-40 space-y-0.5 overflow-y-auto rounded border border-slate-800/80 bg-slate-950/50 p-1", children: sorted.map((e) => {
519
+ const active = e.id === selectedId;
520
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
521
+ "button",
522
+ {
523
+ type: "button",
524
+ className: `w-full rounded px-2 py-1 text-left text-[11px] leading-snug ${active ? "bg-sky-900/50 font-medium text-sky-200" : "text-slate-400 hover:bg-slate-800/80 hover:text-slate-200"}`,
525
+ onClick: () => onSelectId(e.id),
526
+ children: [
527
+ /* @__PURE__ */ jsx("span", { className: "block break-all font-mono", children: e.id }),
528
+ /* @__PURE__ */ jsxs("span", { className: "block text-[10px] text-slate-500", children: [
529
+ shortPath(e.file),
530
+ ":",
531
+ e.line
532
+ ] })
533
+ ]
534
+ }
535
+ ) }, e.id);
536
+ }) })
537
+ ] });
538
+ }
539
+
540
+ // src/structural-patch-ops.ts
541
+ var STRUCTURAL_KINDS = /* @__PURE__ */ new Set([
542
+ "moveSibling",
543
+ "setHidden",
544
+ "duplicateHost"
545
+ ]);
546
+ function isStructuralOnlyOps(ops) {
547
+ return ops.length > 0 && ops.every((op) => STRUCTURAL_KINDS.has(op.kind));
548
+ }
549
+ function buildMoveSiblingOp(direction) {
550
+ return [{ kind: "moveSibling", direction }];
551
+ }
552
+ function buildHideOp() {
553
+ return [{ kind: "setHidden", hidden: true }];
554
+ }
555
+ function buildShowOp() {
556
+ return [{ kind: "setHidden", hidden: false }];
557
+ }
558
+ function buildDuplicateOp() {
559
+ return [{ kind: "duplicateHost" }];
560
+ }
561
+
562
+ // src/sibling-move.ts
563
+ function getIndexedSiblingMoveAvailability(selectedId) {
564
+ const el = document.querySelector(
565
+ `[data-nuvio-id="${escapeAttrSelector(selectedId)}"]`
566
+ );
567
+ if (!(el instanceof HTMLElement)) {
568
+ return { canMoveUp: false, canMoveDown: false, peerCount: 0 };
569
+ }
570
+ const parent = el.parentElement;
571
+ if (!parent) {
572
+ return { canMoveUp: false, canMoveDown: false, peerCount: 0 };
573
+ }
574
+ const peers = [...parent.children].filter(
575
+ (c) => c instanceof HTMLElement && c.hasAttribute("data-nuvio-id")
576
+ );
577
+ const idx = peers.indexOf(el);
578
+ if (idx < 0) {
579
+ return { canMoveUp: false, canMoveDown: false, peerCount: peers.length };
580
+ }
581
+ return {
582
+ canMoveUp: idx > 0,
583
+ canMoveDown: idx < peers.length - 1,
584
+ peerCount: peers.length
585
+ };
586
+ }
587
+ function formatPatchUserMessage(message) {
588
+ if (!message) {
589
+ return null;
590
+ }
591
+ return message.replace(/^Error:\s*/i, "").trim();
592
+ }
593
+
594
+ // src/ColorPickerRow.tsx
595
+ import { useEffect as useEffect3, useId, useRef as useRef2, useState as useState3 } from "react";
596
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
597
+ function hexForUtility(value, options) {
598
+ if (!value) {
599
+ return "transparent";
600
+ }
601
+ const hit = options.find((o) => o.value === value);
602
+ if (hit) {
603
+ return hit.hex;
604
+ }
605
+ const m = value.match(/^(text|bg)-([a-z]+)-(\d+)$/);
606
+ if (m) {
607
+ return TAILWIND_PALETTE_HEX[m[2]]?.[m[3]] ?? "#64748b";
608
+ }
609
+ if (value === "text-white" || value === "bg-white") {
610
+ return "#ffffff";
611
+ }
612
+ if (value === "text-black" || value === "bg-black") {
613
+ return "#000000";
614
+ }
615
+ return "#64748b";
616
+ }
617
+ function familyLabel(family) {
618
+ return family.charAt(0).toUpperCase() + family.slice(1);
619
+ }
620
+ function ColorPickerRow({
621
+ label,
622
+ value,
623
+ onChange,
624
+ options,
625
+ utilityPrefix
626
+ }) {
627
+ const [open, setOpen] = useState3(false);
628
+ const rootRef = useRef2(null);
629
+ const listId = useId();
630
+ const swatchHex = hexForUtility(value, options);
631
+ useEffect3(() => {
632
+ if (!open) {
633
+ return;
634
+ }
635
+ const onDoc = (e) => {
636
+ if (rootRef.current && !rootRef.current.contains(e.target)) {
637
+ setOpen(false);
638
+ }
639
+ };
640
+ document.addEventListener("mousedown", onDoc);
641
+ return () => document.removeEventListener("mousedown", onDoc);
642
+ }, [open]);
643
+ const specials = options.filter(
644
+ (o) => !o.value || o.value === `${utilityPrefix}-white` || o.value === `${utilityPrefix}-black` || o.value === "bg-transparent"
645
+ );
646
+ const pick = (next) => {
647
+ onChange(next);
648
+ setOpen(false);
649
+ };
650
+ return /* @__PURE__ */ jsxs2("div", { ref: rootRef, className: "relative grid grid-cols-[minmax(0,6.5rem)_1fr] items-start gap-x-2 gap-y-1", children: [
651
+ /* @__PURE__ */ jsx2("span", { className: "pt-1 text-xs text-slate-500", children: label }),
652
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
653
+ /* @__PURE__ */ jsxs2(
654
+ "button",
655
+ {
656
+ type: "button",
657
+ "aria-expanded": open,
658
+ "aria-haspopup": "listbox",
659
+ "aria-controls": open ? listId : void 0,
660
+ className: "flex w-full items-center gap-2 rounded border border-slate-600 bg-slate-950 px-2 py-1 text-left text-xs text-slate-100 hover:border-slate-500",
661
+ onClick: () => setOpen((v) => !v),
662
+ children: [
663
+ /* @__PURE__ */ jsx2(
664
+ "span",
665
+ {
666
+ className: "h-5 w-5 shrink-0 rounded border border-slate-600/80",
667
+ style: {
668
+ backgroundColor: swatchHex,
669
+ backgroundImage: !value || value.includes("transparent") ? "linear-gradient(45deg, #475569 25%, transparent 25%), linear-gradient(-45deg, #475569 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #475569 75%), linear-gradient(-45deg, transparent 75%, #475569 75%)" : void 0,
670
+ backgroundSize: !value || value.includes("transparent") ? "6px 6px" : void 0,
671
+ backgroundPosition: !value || value.includes("transparent") ? "0 0, 0 3px, 3px -3px, -3px 0" : void 0
672
+ },
673
+ "aria-hidden": "true"
674
+ }
675
+ ),
676
+ /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate font-mono text-[11px]", children: value || "\u2014" })
677
+ ]
678
+ }
679
+ ),
680
+ open ? /* @__PURE__ */ jsxs2(
681
+ "div",
682
+ {
683
+ id: listId,
684
+ role: "listbox",
685
+ "aria-label": `${label} palette`,
686
+ className: "absolute left-0 right-0 z-20 mt-1 max-h-[min(20rem,50vh)] overflow-auto rounded-lg border border-slate-600 bg-slate-950 p-2 shadow-2xl ring-1 ring-slate-700/80",
687
+ children: [
688
+ /* @__PURE__ */ jsxs2("p", { className: "mb-2 text-[10px] leading-snug text-slate-500", children: [
689
+ "Tailwind palette \u2014 picks a utility class (e.g. ",
690
+ utilityPrefix,
691
+ "-sky-500), not a custom hex value."
692
+ ] }),
693
+ /* @__PURE__ */ jsx2("div", { className: "mb-2 flex flex-wrap gap-1", children: specials.map((o) => /* @__PURE__ */ jsxs2(
694
+ "button",
695
+ {
696
+ type: "button",
697
+ role: "option",
698
+ "aria-selected": value === o.value,
699
+ title: o.label,
700
+ className: `flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] ${value === o.value ? "bg-sky-900/60 text-sky-100 ring-1 ring-sky-500/50" : "text-slate-400 hover:bg-slate-800"}`,
701
+ onClick: () => pick(o.value),
702
+ children: [
703
+ /* @__PURE__ */ jsx2(
704
+ "span",
705
+ {
706
+ className: "inline-block h-3.5 w-3.5 rounded border border-slate-600/60",
707
+ style: { backgroundColor: o.hex }
708
+ }
709
+ ),
710
+ o.label
711
+ ]
712
+ },
713
+ o.value || "__none"
714
+ )) }),
715
+ /* @__PURE__ */ jsxs2(
716
+ "div",
717
+ {
718
+ className: "grid gap-px text-[9px] text-slate-600",
719
+ style: {
720
+ gridTemplateColumns: `3.25rem repeat(${TAILWIND_COLOR_SHADES.length}, minmax(0, 1fr))`
721
+ },
722
+ children: [
723
+ /* @__PURE__ */ jsx2("span", {}),
724
+ TAILWIND_COLOR_SHADES.map((s) => /* @__PURE__ */ jsx2("span", { className: "text-center tabular-nums", children: s }, s)),
725
+ TAILWIND_COLOR_FAMILIES.map((family) => /* @__PURE__ */ jsxs2("div", { className: "contents", children: [
726
+ /* @__PURE__ */ jsx2("span", { className: "truncate pr-1 text-right text-slate-500", children: familyLabel(family) }),
727
+ TAILWIND_COLOR_SHADES.map((shade) => {
728
+ const util = `${utilityPrefix}-${family}-${shade}`;
729
+ const hex = TAILWIND_PALETTE_HEX[family][String(shade)];
730
+ const selected = value === util;
731
+ return /* @__PURE__ */ jsx2(
732
+ "button",
733
+ {
734
+ type: "button",
735
+ role: "option",
736
+ "aria-selected": selected,
737
+ title: util,
738
+ className: `aspect-square min-h-[1.125rem] min-w-0 rounded-sm border ${selected ? "border-sky-400 ring-2 ring-sky-400/80 ring-offset-1 ring-offset-slate-950" : "border-slate-800/80 hover:scale-110 hover:border-slate-500"}`,
739
+ style: { backgroundColor: hex },
740
+ onClick: () => pick(util)
741
+ },
742
+ util
743
+ );
744
+ })
745
+ ] }, family))
746
+ ]
747
+ }
748
+ )
749
+ ]
750
+ }
751
+ ) : null
752
+ ] })
753
+ ] });
754
+ }
755
+
756
+ // src/PropertyPanelShell.tsx
757
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
758
+ function assignShellRef(shellRef, el) {
759
+ shellRef.current = el;
760
+ }
761
+ function SelectRow({
762
+ label,
763
+ value,
764
+ onChange,
765
+ options
766
+ }) {
767
+ return /* @__PURE__ */ jsxs3("label", { className: "grid grid-cols-[minmax(0,6.5rem)_1fr] items-center gap-x-2 gap-y-1", children: [
768
+ /* @__PURE__ */ jsx3("span", { className: "text-xs text-slate-500", children: label }),
769
+ /* @__PURE__ */ jsx3(
770
+ "select",
771
+ {
772
+ value,
773
+ onChange: (e) => onChange(e.target.value),
774
+ className: "rounded border border-slate-600 bg-slate-950 px-2 py-1 text-xs text-slate-100",
775
+ children: options.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: o.label }, o.value || "__none"))
776
+ }
777
+ )
778
+ ] });
779
+ }
780
+ function PropertyPanelShell({
781
+ selectedId,
782
+ resolvedFile,
783
+ resolvedLine,
784
+ indexIdCount,
785
+ selectError,
786
+ channelReady,
787
+ previewSummary,
788
+ previewError,
789
+ lastPatchError,
790
+ stagedVersion,
791
+ previewValidatedFingerprint,
792
+ previewValidatedOps,
793
+ structuralPreviewActive,
794
+ undoStackDepth,
795
+ previewBusy,
796
+ onStagedPatchFingerprint,
797
+ onRequestPreview,
798
+ onRequestApply,
799
+ onRequestUndo,
800
+ onCancelPreview,
801
+ shellRef,
802
+ panelCollapsed,
803
+ panelPosition,
804
+ onPanelCollapsedChange,
805
+ onPanelPositionChange,
806
+ indexEntries,
807
+ onSelectIndexedId,
808
+ onRequestStructuralPreview
809
+ }) {
810
+ const internalShellRef = useRef3(null);
811
+ const [missing, setMissing] = useState4(false);
812
+ const [draftText, setDraftText] = useState4("");
813
+ const [baselineText, setBaselineText] = useState4("");
814
+ const [baselinePicks, setBaselinePicks] = useState4(EMPTY_ALPHA_PICKS);
815
+ const [picks, setPicks] = useState4(EMPTY_ALPHA_PICKS);
816
+ const selectedIdRef = useRef3(selectedId);
817
+ selectedIdRef.current = selectedId;
818
+ const setShellElement = useCallback2(
819
+ (el) => {
820
+ internalShellRef.current = el;
821
+ assignShellRef(shellRef, el);
822
+ },
823
+ [shellRef]
824
+ );
825
+ const [livePanelPosition, setLivePanelPosition] = useState4(panelPosition);
826
+ const { dragging: panelDragging, onHeaderPointerDown } = useChromeDrag({
827
+ shellRef: internalShellRef,
828
+ enabled: !panelCollapsed,
829
+ position: livePanelPosition,
830
+ setPosition: (next) => {
831
+ if (next) {
832
+ setLivePanelPosition(next);
833
+ }
834
+ },
835
+ onDragEnd: onPanelPositionChange
836
+ });
837
+ useEffect4(() => {
838
+ if (!panelDragging) {
839
+ setLivePanelPosition(panelPosition);
840
+ }
841
+ }, [panelPosition, panelDragging]);
842
+ const displayPanelPosition = livePanelPosition;
843
+ const tabOnRight = displayPanelPosition != null && typeof window !== "undefined" && displayPanelPosition.x > window.innerWidth / 2;
844
+ useEffect4(() => {
845
+ if (!selectedId) {
846
+ setMissing(false);
847
+ return;
848
+ }
849
+ const el = document.querySelector(`[data-nuvio-id="${escapeAttrSelector(selectedId)}"]`);
850
+ setMissing(!(el instanceof HTMLElement));
851
+ }, [selectedId]);
852
+ const syncFromSelectedElement = useCallback2(() => {
853
+ const id = selectedIdRef.current;
854
+ if (!id) {
855
+ return;
856
+ }
857
+ const el = document.querySelector(`[data-nuvio-id="${escapeAttrSelector(id)}"]`);
858
+ if (!(el instanceof HTMLElement)) {
859
+ return;
860
+ }
861
+ const text = (el.textContent ?? "").trim();
862
+ const fromClass = readAlphaPicksFromElement(el);
863
+ setBaselineText(text);
864
+ setDraftText(text);
865
+ setBaselinePicks(fromClass);
866
+ setPicks(fromClass);
867
+ }, []);
868
+ useEffect4(() => {
869
+ if (!selectedId) {
870
+ setDraftText("");
871
+ setBaselineText("");
872
+ setBaselinePicks(EMPTY_ALPHA_PICKS);
873
+ setPicks(EMPTY_ALPHA_PICKS);
874
+ return;
875
+ }
876
+ syncFromSelectedElement();
877
+ }, [selectedId, syncFromSelectedElement]);
878
+ useEffect4(() => {
879
+ if (stagedVersion === 0) {
880
+ return;
881
+ }
882
+ const id = selectedIdRef.current;
883
+ if (!id) {
884
+ return;
885
+ }
886
+ const t = window.setTimeout(syncFromSelectedElement, 280);
887
+ return () => window.clearTimeout(t);
888
+ }, [stagedVersion, syncFromSelectedElement]);
889
+ const stagedOps = useMemo(
890
+ () => buildAlphaPatchOps(baselineText, draftText, baselinePicks, picks),
891
+ [baselineText, draftText, baselinePicks, picks]
892
+ );
893
+ const stagedOpsFingerprint = useMemo(() => JSON.stringify(stagedOps), [stagedOps]);
894
+ useEffect4(() => {
895
+ if (!selectedId) {
896
+ return;
897
+ }
898
+ onStagedPatchFingerprint(stagedOpsFingerprint);
899
+ }, [selectedId, stagedOpsFingerprint, onStagedPatchFingerprint]);
900
+ const hasStagedOps = draftText !== baselineText || alphaPicksDiffer(picks, baselinePicks);
901
+ const selectionResolved = Boolean(resolvedFile);
902
+ const patchBlockedReason = indexIdCount === 0 ? "Source index has 0 ids \u2014 the dev server cannot map data-nuvio-id to files. Run pnpm dev from the repo root (builds packages), then hard-refresh. Check the terminal for [Nuvio] index warnings." : selectedId && !selectionResolved ? selectError ?? "Server did not confirm this id (no source file). Patches stay disabled until selection succeeds." : null;
903
+ const previewApplyMismatch = hasStagedOps && selectionResolved && channelReady && indexIdCount > 0 && (!previewSummary || previewError != null || previewValidatedFingerprint !== stagedOpsFingerprint);
904
+ const patchActionsDisabled = !channelReady || !hasStagedOps || indexIdCount === 0 || !selectionResolved;
905
+ const structuralActionsDisabled = !channelReady || indexIdCount === 0 || !selectedId || !selectionResolved || missing;
906
+ const applyReady = channelReady && indexIdCount > 0 && selectionResolved && previewValidatedOps != null && previewValidatedOps.length > 0 && previewValidatedFingerprint != null && !previewError && !previewBusy;
907
+ const applyDisabled = !applyReady;
908
+ const [siblingMove, setSiblingMove] = useState4(() => ({
909
+ canMoveUp: false,
910
+ canMoveDown: false,
911
+ peerCount: 0
912
+ }));
913
+ useLayoutEffect(() => {
914
+ if (!selectedId || missing) {
915
+ setSiblingMove({ canMoveUp: false, canMoveDown: false, peerCount: 0 });
916
+ return;
917
+ }
918
+ setSiblingMove(getIndexedSiblingMoveAvailability(selectedId));
919
+ }, [selectedId, missing, stagedVersion]);
920
+ const structuralPreviewMessage = structuralPreviewActive ? formatPatchUserMessage(previewError) : null;
921
+ const structuralPreviewOk = structuralPreviewActive && !previewError && previewSummary ? previewSummary : null;
922
+ const fontSizeOpts = [
923
+ { value: "", label: "\u2014" },
924
+ { value: "text-sm", label: "text-sm" },
925
+ { value: "text-base", label: "text-base" },
926
+ { value: "text-lg", label: "text-lg" },
927
+ { value: "text-xl", label: "text-xl" },
928
+ { value: "text-2xl", label: "text-2xl" }
929
+ ];
930
+ const fontWeightOpts = [
931
+ { value: "", label: "\u2014" },
932
+ { value: "font-medium", label: "font-medium" },
933
+ { value: "font-semibold", label: "font-semibold" },
934
+ { value: "font-bold", label: "font-bold" }
935
+ ];
936
+ const roundedOpts = [
937
+ { value: "", label: "\u2014" },
938
+ { value: "rounded-md", label: "rounded-md" },
939
+ { value: "rounded-lg", label: "rounded-lg" },
940
+ { value: "rounded-xl", label: "rounded-xl" },
941
+ { value: "rounded-full", label: "rounded-full" }
942
+ ];
943
+ const padOpts = [
944
+ { value: "", label: "\u2014" },
945
+ { value: "p-2", label: "p-2" },
946
+ { value: "p-4", label: "p-4" },
947
+ { value: "p-6", label: "p-6" },
948
+ { value: "px-4 py-2", label: "px-4 py-2" }
949
+ ];
950
+ const marginOpts = [
951
+ { value: "", label: "\u2014" },
952
+ { value: "m-2", label: "m-2" },
953
+ { value: "m-4", label: "m-4" },
954
+ { value: "mx-auto", label: "mx-auto" },
955
+ { value: "mt-4", label: "mt-4" },
956
+ { value: "mb-4", label: "mb-4" }
957
+ ];
958
+ const textAlignOpts = [
959
+ { value: "", label: "\u2014" },
960
+ { value: "text-left", label: "text-left" },
961
+ { value: "text-center", label: "text-center" },
962
+ { value: "text-right", label: "text-right" },
963
+ { value: "text-justify", label: "text-justify" }
964
+ ];
965
+ const gapOpts = [
966
+ { value: "", label: "\u2014" },
967
+ { value: "gap-1", label: "gap-1" },
968
+ { value: "gap-2", label: "gap-2" },
969
+ { value: "gap-4", label: "gap-4" },
970
+ { value: "gap-6", label: "gap-6" },
971
+ { value: "gap-8", label: "gap-8" }
972
+ ];
973
+ const widthOpts = [
974
+ { value: "", label: "\u2014" },
975
+ { value: "w-auto", label: "w-auto" },
976
+ { value: "w-full", label: "w-full" },
977
+ { value: "w-1/2", label: "w-1/2" },
978
+ { value: "w-1/3", label: "w-1/3" },
979
+ { value: "w-2/3", label: "w-2/3" },
980
+ { value: "w-1/4", label: "w-1/4" },
981
+ { value: "w-3/4", label: "w-3/4" }
982
+ ];
983
+ const maxWidthOpts = [
984
+ { value: "", label: "\u2014" },
985
+ { value: "max-w-sm", label: "max-w-sm" },
986
+ { value: "max-w-md", label: "max-w-md" },
987
+ { value: "max-w-lg", label: "max-w-lg" },
988
+ { value: "max-w-xl", label: "max-w-xl" },
989
+ { value: "max-w-2xl", label: "max-w-2xl" },
990
+ { value: "max-w-4xl", label: "max-w-4xl" },
991
+ { value: "max-w-prose", label: "max-w-prose" },
992
+ { value: "max-w-full", label: "max-w-full" }
993
+ ];
994
+ const heightOpts = [
995
+ { value: "", label: "\u2014" },
996
+ { value: "h-auto", label: "h-auto" },
997
+ { value: "h-full", label: "h-full" },
998
+ { value: "h-8", label: "h-8" },
999
+ { value: "h-12", label: "h-12" },
1000
+ { value: "h-16", label: "h-16" },
1001
+ { value: "h-24", label: "h-24" },
1002
+ { value: "h-screen", label: "h-screen" }
1003
+ ];
1004
+ const minHeightOpts = [
1005
+ { value: "", label: "\u2014" },
1006
+ { value: "min-h-0", label: "min-h-0" },
1007
+ { value: "min-h-full", label: "min-h-full" },
1008
+ { value: "min-h-screen", label: "min-h-screen" },
1009
+ { value: "min-h-16", label: "min-h-16" },
1010
+ { value: "min-h-24", label: "min-h-24" }
1011
+ ];
1012
+ const opacityOpts = [
1013
+ { value: "", label: "\u2014" },
1014
+ { value: "opacity-0", label: "opacity-0" },
1015
+ { value: "opacity-25", label: "opacity-25" },
1016
+ { value: "opacity-50", label: "opacity-50" },
1017
+ { value: "opacity-75", label: "opacity-75" },
1018
+ { value: "opacity-100", label: "opacity-100" }
1019
+ ];
1020
+ const shadowOpts = [
1021
+ { value: "", label: "\u2014" },
1022
+ { value: "shadow-none", label: "shadow-none" },
1023
+ { value: "shadow-sm", label: "shadow-sm" },
1024
+ { value: "shadow", label: "shadow" },
1025
+ { value: "shadow-md", label: "shadow-md" },
1026
+ { value: "shadow-lg", label: "shadow-lg" },
1027
+ { value: "shadow-xl", label: "shadow-xl" }
1028
+ ];
1029
+ if (panelCollapsed) {
1030
+ return /* @__PURE__ */ jsxs3(
1031
+ "button",
1032
+ {
1033
+ type: "button",
1034
+ ref: (el) => setShellElement(el),
1035
+ className: `pointer-events-auto fixed z-[9998] rounded-2xl px-2 py-3 text-xs font-semibold text-slate-100 ${NUVO_GLASS_FRAME} ${tabOnRight ? "right-4 top-1/2 -translate-y-1/2" : "left-4 top-1/2 -translate-y-1/2"}`,
1036
+ style: {
1037
+ ...NUVO_GLASS_SURFACE_STYLE,
1038
+ ...displayPanelPosition ? {
1039
+ left: tabOnRight ? void 0 : displayPanelPosition.x,
1040
+ right: tabOnRight ? window.innerWidth - displayPanelPosition.x - 40 : void 0,
1041
+ top: displayPanelPosition.y,
1042
+ transform: "none"
1043
+ } : {}
1044
+ },
1045
+ title: "Expand Editor panel",
1046
+ onClick: () => onPanelCollapsedChange(false),
1047
+ children: [
1048
+ /* @__PURE__ */ jsx3("span", { className: tabOnRight ? "" : "inline-block -scale-x-100", "aria-hidden": "true", children: "\u203A" }),
1049
+ /* @__PURE__ */ jsx3("span", { className: "sr-only", children: "Expand Editor" })
1050
+ ]
1051
+ }
1052
+ );
1053
+ }
1054
+ const docked = displayPanelPosition === null;
1055
+ const panelStyle = docked ? void 0 : {
1056
+ left: displayPanelPosition.x,
1057
+ top: displayPanelPosition.y,
1058
+ maxHeight: "min(calc(100vh - 2rem), 720px)",
1059
+ height: "min(calc(100vh - 2rem), 720px)"
1060
+ };
1061
+ return /* @__PURE__ */ jsxs3(
1062
+ "aside",
1063
+ {
1064
+ ref: setShellElement,
1065
+ style: { ...NUVO_GLASS_SURFACE_STYLE, ...panelStyle },
1066
+ className: `pointer-events-auto fixed z-[9998] flex w-[min(100vw-2rem,20rem)] flex-col overflow-hidden rounded-2xl text-sm text-slate-100 ${NUVO_GLASS_FRAME} ${docked ? "left-4 top-4 max-h-[calc(100vh-2rem)]" : ""} ${panelDragging ? "select-none" : ""}`,
1067
+ children: [
1068
+ /* @__PURE__ */ jsxs3(
1069
+ "header",
1070
+ {
1071
+ className: `flex shrink-0 items-center gap-2 px-3 py-2 ${NUVO_GLASS_HEADER} ${panelDragging ? "cursor-grabbing" : "cursor-grab"}`,
1072
+ onPointerDown: onHeaderPointerDown,
1073
+ children: [
1074
+ /* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 font-semibold", children: "Editor" }),
1075
+ /* @__PURE__ */ jsx3(
1076
+ "button",
1077
+ {
1078
+ type: "button",
1079
+ className: "shrink-0 rounded px-1.5 py-0.5 text-xs text-slate-400 hover:bg-slate-800 hover:text-slate-200",
1080
+ title: "Collapse panel",
1081
+ "aria-label": "Collapse Editor panel",
1082
+ onPointerDown: (e) => e.stopPropagation(),
1083
+ onClick: () => onPanelCollapsedChange(true),
1084
+ children: "\u2212"
1085
+ }
1086
+ )
1087
+ ]
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1 space-y-4 overflow-y-auto px-3 py-3", children: [
1091
+ selectedId ? /* @__PURE__ */ jsxs3("p", { className: "text-xs text-slate-400", children: [
1092
+ /* @__PURE__ */ jsx3("span", { className: "font-mono text-sky-300/95", children: selectedId }),
1093
+ resolvedFile ? /* @__PURE__ */ jsxs3("span", { className: "text-slate-500", children: [
1094
+ " ",
1095
+ "\xB7",
1096
+ " ",
1097
+ /* @__PURE__ */ jsxs3("span", { className: "font-mono text-slate-300", children: [
1098
+ resolvedFile,
1099
+ resolvedLine != null ? `:${resolvedLine}` : ""
1100
+ ] })
1101
+ ] }) : null
1102
+ ] }) : /* @__PURE__ */ jsx3("p", { className: "text-xs text-slate-500", children: "Select an element on the page." }),
1103
+ indexIdCount === 0 ? /* @__PURE__ */ jsx3("p", { className: "text-xs text-amber-200/90", children: "Index empty \u2014 restart dev server." }) : null,
1104
+ selectedId && !resolvedFile && selectError ? /* @__PURE__ */ jsx3("p", { className: "text-xs text-red-300/95", children: selectError }) : null,
1105
+ selectedId && missing ? /* @__PURE__ */ jsxs3("p", { className: "text-xs text-amber-300/90", children: [
1106
+ "No matching ",
1107
+ /* @__PURE__ */ jsx3("span", { className: "font-mono", children: "data-nuvio-id" }),
1108
+ " node in the document."
1109
+ ] }) : null,
1110
+ selectedId && !missing ? /* @__PURE__ */ jsxs3("section", { className: `space-y-2 p-2 ${NUVO_GLASS_SECTION}`, children: [
1111
+ /* @__PURE__ */ jsx3("h3", { className: "text-xs font-medium text-slate-400", children: "Structure" }),
1112
+ previewBusy && structuralPreviewActive ? /* @__PURE__ */ jsx3("p", { className: "text-[11px] text-sky-200/90", children: "Updating layout\u2026" }) : null,
1113
+ structuralPreviewMessage ? /* @__PURE__ */ jsx3("p", { className: "rounded border border-red-800/70 bg-red-950/50 px-2 py-1.5 text-xs text-red-200", children: structuralPreviewMessage }) : null,
1114
+ structuralPreviewOk ? /* @__PURE__ */ jsx3("p", { className: "rounded border border-emerald-700/50 bg-emerald-950/25 px-2 py-1.5 font-mono text-[11px] text-emerald-100/95", children: structuralPreviewOk }) : null,
1115
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-2", children: [
1116
+ /* @__PURE__ */ jsx3(
1117
+ "button",
1118
+ {
1119
+ type: "button",
1120
+ disabled: structuralActionsDisabled || !siblingMove.canMoveUp,
1121
+ title: siblingMove.canMoveUp ? "Move earlier in source / left in row" : "Already first in this row",
1122
+ className: "rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1123
+ onClick: () => onRequestStructuralPreview(buildMoveSiblingOp("up")),
1124
+ children: "Move up"
1125
+ }
1126
+ ),
1127
+ /* @__PURE__ */ jsx3(
1128
+ "button",
1129
+ {
1130
+ type: "button",
1131
+ disabled: structuralActionsDisabled || !siblingMove.canMoveDown,
1132
+ title: siblingMove.canMoveDown ? "Move later in source / right in row" : "Already last in this row",
1133
+ className: "rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1134
+ onClick: () => onRequestStructuralPreview(buildMoveSiblingOp("down")),
1135
+ children: "Move down"
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ jsx3(
1139
+ "button",
1140
+ {
1141
+ type: "button",
1142
+ disabled: structuralActionsDisabled,
1143
+ className: "rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1144
+ onClick: () => onRequestStructuralPreview(buildHideOp()),
1145
+ children: "Hide"
1146
+ }
1147
+ ),
1148
+ /* @__PURE__ */ jsx3(
1149
+ "button",
1150
+ {
1151
+ type: "button",
1152
+ disabled: structuralActionsDisabled,
1153
+ className: "rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1154
+ onClick: () => onRequestStructuralPreview(buildShowOp()),
1155
+ children: "Show"
1156
+ }
1157
+ ),
1158
+ /* @__PURE__ */ jsx3(
1159
+ "button",
1160
+ {
1161
+ type: "button",
1162
+ disabled: structuralActionsDisabled,
1163
+ className: "rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1164
+ onClick: () => onRequestStructuralPreview(buildDuplicateOp()),
1165
+ children: "Duplicate"
1166
+ }
1167
+ )
1168
+ ] })
1169
+ ] }) : null,
1170
+ selectedId && !missing ? /* @__PURE__ */ jsxs3("section", { className: `space-y-2 p-2 ${NUVO_GLASS_SECTION}`, children: [
1171
+ /* @__PURE__ */ jsx3("h3", { className: "text-xs font-medium text-slate-400", children: "Style" }),
1172
+ previewBusy ? /* @__PURE__ */ jsx3("p", { className: "rounded border border-sky-800/50 bg-sky-950/40 px-2 py-1.5 text-[11px] text-sky-100/95", children: "Validating patch with the dev server\u2026" }) : null,
1173
+ lastPatchError ? /* @__PURE__ */ jsx3("p", { className: "rounded border border-red-800/70 bg-red-950/50 px-2 py-1.5 text-xs text-red-200", children: lastPatchError }) : null,
1174
+ previewError && !structuralPreviewActive ? /* @__PURE__ */ jsx3("p", { className: "rounded border border-red-800/70 bg-red-950/50 px-2 py-1.5 text-xs text-red-200", children: formatPatchUserMessage(previewError) }) : null,
1175
+ patchBlockedReason ? /* @__PURE__ */ jsx3("p", { className: "rounded border border-amber-800/60 bg-amber-950/40 px-2 py-1.5 text-xs text-amber-100/95", children: patchBlockedReason }) : null,
1176
+ hasStagedOps && !patchBlockedReason && previewApplyMismatch ? /* @__PURE__ */ jsxs3("p", { className: "rounded border border-slate-600/80 bg-slate-900/60 px-2 py-1.5 text-[11px] leading-snug text-slate-300", children: [
1177
+ "Run ",
1178
+ /* @__PURE__ */ jsx3("span", { className: "font-medium text-slate-200", children: "Validate" }),
1179
+ " after each edit so the summary matches what you apply."
1180
+ ] }) : null,
1181
+ /* @__PURE__ */ jsxs3("label", { className: "block space-y-1", children: [
1182
+ /* @__PURE__ */ jsx3("span", { className: "text-xs text-slate-500", children: "Text" }),
1183
+ /* @__PURE__ */ jsx3(
1184
+ "textarea",
1185
+ {
1186
+ value: draftText,
1187
+ onChange: (e) => setDraftText(e.target.value),
1188
+ rows: 2,
1189
+ className: "w-full resize-y rounded border border-slate-600 bg-slate-950 px-2 py-1 font-mono text-xs text-slate-100"
1190
+ }
1191
+ )
1192
+ ] }),
1193
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-2 pt-1", children: [
1194
+ /* @__PURE__ */ jsx3(
1195
+ SelectRow,
1196
+ {
1197
+ label: "Font size",
1198
+ value: picks.fontSize,
1199
+ onChange: (v) => setPicks((p) => ({ ...p, fontSize: v })),
1200
+ options: fontSizeOpts
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ jsx3(
1204
+ SelectRow,
1205
+ {
1206
+ label: "Weight",
1207
+ value: picks.fontWeight,
1208
+ onChange: (v) => setPicks((p) => ({ ...p, fontWeight: v })),
1209
+ options: fontWeightOpts
1210
+ }
1211
+ ),
1212
+ /* @__PURE__ */ jsx3(
1213
+ ColorPickerRow,
1214
+ {
1215
+ label: "Text color",
1216
+ value: picks.textColor,
1217
+ onChange: (v) => setPicks((p) => ({ ...p, textColor: v })),
1218
+ options: TEXT_COLOR_OPTIONS,
1219
+ utilityPrefix: "text"
1220
+ }
1221
+ ),
1222
+ /* @__PURE__ */ jsx3(
1223
+ ColorPickerRow,
1224
+ {
1225
+ label: "Background",
1226
+ value: picks.bgColor,
1227
+ onChange: (v) => setPicks((p) => ({ ...p, bgColor: v })),
1228
+ options: BACKGROUND_COLOR_OPTIONS,
1229
+ utilityPrefix: "bg"
1230
+ }
1231
+ ),
1232
+ /* @__PURE__ */ jsx3(
1233
+ SelectRow,
1234
+ {
1235
+ label: "Radius",
1236
+ value: picks.rounded,
1237
+ onChange: (v) => setPicks((p) => ({ ...p, rounded: v })),
1238
+ options: roundedOpts
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsx3(
1242
+ SelectRow,
1243
+ {
1244
+ label: "Padding",
1245
+ value: picks.padding,
1246
+ onChange: (v) => setPicks((p) => ({ ...p, padding: v })),
1247
+ options: padOpts
1248
+ }
1249
+ ),
1250
+ /* @__PURE__ */ jsx3(
1251
+ SelectRow,
1252
+ {
1253
+ label: "Margin",
1254
+ value: picks.margin,
1255
+ onChange: (v) => setPicks((p) => ({ ...p, margin: v })),
1256
+ options: marginOpts
1257
+ }
1258
+ ),
1259
+ /* @__PURE__ */ jsx3("p", { className: "pt-1 text-[10px] font-medium text-slate-500", children: "Layout & effects" }),
1260
+ /* @__PURE__ */ jsx3(
1261
+ SelectRow,
1262
+ {
1263
+ label: "Text align",
1264
+ value: picks.textAlign,
1265
+ onChange: (v) => setPicks((p) => ({ ...p, textAlign: v })),
1266
+ options: textAlignOpts
1267
+ }
1268
+ ),
1269
+ /* @__PURE__ */ jsx3(
1270
+ SelectRow,
1271
+ {
1272
+ label: "Gap",
1273
+ value: picks.gap,
1274
+ onChange: (v) => setPicks((p) => ({ ...p, gap: v })),
1275
+ options: gapOpts
1276
+ }
1277
+ ),
1278
+ /* @__PURE__ */ jsx3(
1279
+ SelectRow,
1280
+ {
1281
+ label: "Width",
1282
+ value: picks.width,
1283
+ onChange: (v) => setPicks((p) => ({ ...p, width: v })),
1284
+ options: widthOpts
1285
+ }
1286
+ ),
1287
+ /* @__PURE__ */ jsx3(
1288
+ SelectRow,
1289
+ {
1290
+ label: "Max width",
1291
+ value: picks.maxWidth,
1292
+ onChange: (v) => setPicks((p) => ({ ...p, maxWidth: v })),
1293
+ options: maxWidthOpts
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsx3(
1297
+ SelectRow,
1298
+ {
1299
+ label: "Height",
1300
+ value: picks.height,
1301
+ onChange: (v) => setPicks((p) => ({ ...p, height: v })),
1302
+ options: heightOpts
1303
+ }
1304
+ ),
1305
+ /* @__PURE__ */ jsx3(
1306
+ SelectRow,
1307
+ {
1308
+ label: "Min height",
1309
+ value: picks.minHeight,
1310
+ onChange: (v) => setPicks((p) => ({ ...p, minHeight: v })),
1311
+ options: minHeightOpts
1312
+ }
1313
+ ),
1314
+ /* @__PURE__ */ jsx3(
1315
+ SelectRow,
1316
+ {
1317
+ label: "Opacity",
1318
+ value: picks.opacity,
1319
+ onChange: (v) => setPicks((p) => ({ ...p, opacity: v })),
1320
+ options: opacityOpts
1321
+ }
1322
+ ),
1323
+ /* @__PURE__ */ jsx3(
1324
+ SelectRow,
1325
+ {
1326
+ label: "Shadow",
1327
+ value: picks.shadow,
1328
+ onChange: (v) => setPicks((p) => ({ ...p, shadow: v })),
1329
+ options: shadowOpts
1330
+ }
1331
+ )
1332
+ ] }),
1333
+ previewSummary && !structuralPreviewActive ? /* @__PURE__ */ jsxs3("div", { className: "rounded border border-emerald-700/50 bg-emerald-950/25 p-2 ring-1 ring-emerald-500/20", children: [
1334
+ /* @__PURE__ */ jsx3("p", { className: "text-[10px] font-medium uppercase tracking-wide text-emerald-400/90", children: "Validated change" }),
1335
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 font-mono text-[11px] leading-snug text-emerald-100/95", children: previewSummary })
1336
+ ] }) : null,
1337
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap gap-2 pt-2", children: [
1338
+ /* @__PURE__ */ jsx3(
1339
+ "button",
1340
+ {
1341
+ type: "button",
1342
+ disabled: patchActionsDisabled,
1343
+ className: "rounded bg-slate-700 px-2 py-1 text-xs font-medium text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1344
+ onClick: () => onRequestPreview(stagedOps),
1345
+ children: "Validate"
1346
+ }
1347
+ ),
1348
+ /* @__PURE__ */ jsx3(
1349
+ "button",
1350
+ {
1351
+ type: "button",
1352
+ disabled: applyDisabled,
1353
+ className: "rounded bg-sky-700 px-2 py-1 text-xs font-medium text-white enabled:hover:bg-sky-600 disabled:opacity-40",
1354
+ onClick: () => {
1355
+ if (previewValidatedOps?.length) {
1356
+ onRequestApply([...previewValidatedOps]);
1357
+ }
1358
+ },
1359
+ children: "Apply"
1360
+ }
1361
+ ),
1362
+ /* @__PURE__ */ jsx3(
1363
+ "button",
1364
+ {
1365
+ type: "button",
1366
+ disabled: !channelReady || undoStackDepth <= 0,
1367
+ className: "rounded bg-slate-700 px-2 py-1 text-xs font-medium text-slate-100 enabled:hover:bg-slate-600 disabled:opacity-40",
1368
+ onClick: () => onRequestUndo(),
1369
+ children: "Undo last"
1370
+ }
1371
+ ),
1372
+ /* @__PURE__ */ jsx3(
1373
+ "button",
1374
+ {
1375
+ type: "button",
1376
+ className: "rounded border border-slate-600 px-2 py-1 text-xs text-slate-300 hover:bg-slate-800",
1377
+ onClick: () => onCancelPreview(),
1378
+ children: "Cancel"
1379
+ }
1380
+ )
1381
+ ] })
1382
+ ] }) : null,
1383
+ /* @__PURE__ */ jsx3(
1384
+ ComponentTree,
1385
+ {
1386
+ entries: indexEntries,
1387
+ selectedId,
1388
+ onSelectId: onSelectIndexedId
1389
+ }
1390
+ )
1391
+ ] })
1392
+ ]
1393
+ }
1394
+ );
1395
+ }
1396
+
1397
+ // src/NuvioDevShell.tsx
1398
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1399
+ function shortDisplayPath(absPath) {
1400
+ const norm = absPath.replace(/\\/g, "/");
1401
+ const idx = norm.lastIndexOf("/");
1402
+ if (idx === -1) {
1403
+ return absPath;
1404
+ }
1405
+ const parent = norm.slice(0, idx).split("/").pop() ?? "";
1406
+ const base = norm.slice(idx + 1);
1407
+ return parent ? `${parent}/${base}` : base;
1408
+ }
1409
+ function safeCloseWebSocket(socket) {
1410
+ if (socket.readyState === WebSocket.CLOSING || socket.readyState === WebSocket.CLOSED) {
1411
+ return;
1412
+ }
1413
+ if (socket.readyState === WebSocket.CONNECTING) {
1414
+ socket.addEventListener(
1415
+ "open",
1416
+ () => {
1417
+ socket.close(1e3, "nuvio-dispose");
1418
+ },
1419
+ { once: true }
1420
+ );
1421
+ return;
1422
+ }
1423
+ socket.close(1e3, "nuvio-dispose");
1424
+ }
1425
+ function wsPayloadToString(data) {
1426
+ if (typeof data === "string") {
1427
+ return data;
1428
+ }
1429
+ if (data instanceof ArrayBuffer) {
1430
+ return new TextDecoder("utf-8").decode(data);
1431
+ }
1432
+ if (ArrayBuffer.isView(data)) {
1433
+ const v = data;
1434
+ return new TextDecoder("utf-8").decode(
1435
+ new Uint8Array(v.buffer, v.byteOffset, v.byteLength)
1436
+ );
1437
+ }
1438
+ if (data instanceof Blob) {
1439
+ return null;
1440
+ }
1441
+ return null;
1442
+ }
1443
+ function assignRef(ref, el) {
1444
+ ref.current = el;
1445
+ }
1446
+ function NuvioDevShellInner() {
1447
+ const panelRef = useRef4(null);
1448
+ const chipRef = useRef4(null);
1449
+ const chromeRootRefs = useMemo2(
1450
+ () => [panelRef, chipRef],
1451
+ []
1452
+ );
1453
+ const wsRef = useRef4(null);
1454
+ const connectGenRef = useRef4(0);
1455
+ const [chromeLayout, setChromeLayout] = useState5(loadOverlayChromePersist);
1456
+ const [chipPos, setChipPos] = useState5(
1457
+ () => cornerAnchorPosition(loadOverlayChromePersist().chip.corner, 220, 120)
1458
+ );
1459
+ const patchChrome = useCallback3(
1460
+ (patch) => {
1461
+ setChromeLayout((prev) => {
1462
+ const next = {
1463
+ panel: patch.panel ? { ...prev.panel, ...patch.panel } : prev.panel,
1464
+ chip: patch.chip ? { ...prev.chip, ...patch.chip } : prev.chip
1465
+ };
1466
+ saveOverlayChromePersist(next);
1467
+ return next;
1468
+ });
1469
+ },
1470
+ []
1471
+ );
1472
+ const onPanelCollapsedChange = useCallback3(
1473
+ (collapsed) => {
1474
+ patchChrome({ panel: { collapsed } });
1475
+ },
1476
+ [patchChrome]
1477
+ );
1478
+ const onPanelPositionChange = useCallback3(
1479
+ (position) => {
1480
+ patchChrome({ panel: { position } });
1481
+ },
1482
+ [patchChrome]
1483
+ );
1484
+ const onChipCollapsedChange = useCallback3(
1485
+ (collapsed) => {
1486
+ patchChrome({ chip: { collapsed } });
1487
+ },
1488
+ [patchChrome]
1489
+ );
1490
+ const onChipCornerChange = useCallback3(
1491
+ (corner) => {
1492
+ patchChrome({ chip: { corner } });
1493
+ },
1494
+ [patchChrome]
1495
+ );
1496
+ const anchorChipToCorner = useCallback3(
1497
+ (corner) => {
1498
+ const el = chipRef.current;
1499
+ if (!el) {
1500
+ return;
1501
+ }
1502
+ setChipPos(cornerAnchorPosition(corner, el.offsetWidth, el.offsetHeight));
1503
+ },
1504
+ []
1505
+ );
1506
+ const commitChipDrag = useCallback3(
1507
+ (pos) => {
1508
+ const el = chipRef.current;
1509
+ if (!el) {
1510
+ return;
1511
+ }
1512
+ const centerX = pos.x + el.offsetWidth / 2;
1513
+ const centerY = pos.y + el.offsetHeight / 2;
1514
+ const corner = snapToNearestCorner(centerX, centerY);
1515
+ onChipCornerChange(corner);
1516
+ setChipPos(cornerAnchorPosition(corner, el.offsetWidth, el.offsetHeight));
1517
+ },
1518
+ [onChipCornerChange]
1519
+ );
1520
+ const { dragging: chipDragging, onHeaderPointerDown: onChipHeaderPointerDown } = useChromeDrag({
1521
+ shellRef: chipRef,
1522
+ enabled: true,
1523
+ position: chipPos,
1524
+ setPosition: (next) => {
1525
+ if (next) {
1526
+ setChipPos(next);
1527
+ }
1528
+ },
1529
+ onDragEnd: commitChipDrag
1530
+ });
1531
+ useLayoutEffect2(() => {
1532
+ if (chipDragging) {
1533
+ return;
1534
+ }
1535
+ anchorChipToCorner(chromeLayout.chip.corner);
1536
+ }, [
1537
+ anchorChipToCorner,
1538
+ chipDragging,
1539
+ chromeLayout.chip.collapsed,
1540
+ chromeLayout.chip.corner
1541
+ ]);
1542
+ useEffect5(() => {
1543
+ const onResize = () => {
1544
+ if (!chipDragging) {
1545
+ anchorChipToCorner(chromeLayout.chip.corner);
1546
+ }
1547
+ };
1548
+ window.addEventListener("resize", onResize);
1549
+ return () => window.removeEventListener("resize", onResize);
1550
+ }, [anchorChipToCorner, chipDragging, chromeLayout.chip.corner]);
1551
+ const [editMode, setEditMode] = useState5(false);
1552
+ const [channel, setChannel] = useState5("idle");
1553
+ const [knownIds, setKnownIds] = useState5(/* @__PURE__ */ new Set());
1554
+ const [indexEntries, setIndexEntries] = useState5([]);
1555
+ const [duplicateErrors, setDuplicateErrors] = useState5([]);
1556
+ const [selectedId, setSelectedId] = useState5(null);
1557
+ const [resolvedFile, setResolvedFile] = useState5(void 0);
1558
+ const [resolvedLine, setResolvedLine] = useState5(void 0);
1559
+ const [selectError, setSelectError] = useState5(null);
1560
+ const [previewSummary, setPreviewSummary] = useState5(null);
1561
+ const [previewError, setPreviewError] = useState5(null);
1562
+ const [lastPatchError, setLastPatchError] = useState5(null);
1563
+ const [stagedVersion, setStagedVersion] = useState5(0);
1564
+ const [previewValidatedFingerprint, setPreviewValidatedFingerprint] = useState5(
1565
+ null
1566
+ );
1567
+ const [previewValidatedOps, setPreviewValidatedOps] = useState5(null);
1568
+ const [undoStackDepth, setUndoStackDepth] = useState5(0);
1569
+ const [previewBusy, setPreviewBusy] = useState5(false);
1570
+ const patchPendingMapRef = useRef4(/* @__PURE__ */ new Map());
1571
+ const previewTimeoutRef = useRef4(null);
1572
+ const undoPendingRef = useRef4(null);
1573
+ const resolvedFileRef = useRef4(void 0);
1574
+ const selectedIdRef = useRef4(null);
1575
+ const lastIndexEntriesRef = useRef4([]);
1576
+ const lastStagedOpsFpRef = useRef4(null);
1577
+ const autoApplyStructuralRef = useRef4(false);
1578
+ const [structuralPreviewActive, setStructuralPreviewActive] = useState5(false);
1579
+ useEffect5(() => {
1580
+ resolvedFileRef.current = resolvedFile;
1581
+ }, [resolvedFile]);
1582
+ useEffect5(() => {
1583
+ selectedIdRef.current = selectedId;
1584
+ }, [selectedId]);
1585
+ const toggleEditMode = useCallback3(() => {
1586
+ setEditMode((prev) => {
1587
+ if (prev) {
1588
+ clearNuvioOutlines();
1589
+ setSelectedId(null);
1590
+ }
1591
+ return !prev;
1592
+ });
1593
+ }, []);
1594
+ const sendPatchMessage = useCallback3((ops, dryRun) => {
1595
+ const ws = wsRef.current;
1596
+ const id = selectedIdRef.current;
1597
+ if (!ws || ws.readyState !== WebSocket.OPEN || !id) {
1598
+ if (dryRun) {
1599
+ setPreviewBusy(false);
1600
+ setPreviewError(
1601
+ !ws || ws.readyState !== WebSocket.OPEN ? "Dev channel is not connected \u2014 wait for \u201Cconnected\u201D in the chip, then try Validate again." : "Nothing is selected \u2014 click an element on the page first."
1602
+ );
1603
+ } else {
1604
+ setLastPatchError(
1605
+ !ws || ws.readyState !== WebSocket.OPEN ? "Dev channel is not connected." : "Nothing is selected."
1606
+ );
1607
+ }
1608
+ return;
1609
+ }
1610
+ const requestId = `patch-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`;
1611
+ const opsFingerprint = JSON.stringify(ops);
1612
+ patchPendingMapRef.current.set(requestId, {
1613
+ kind: dryRun ? "preview" : "apply",
1614
+ opsFingerprint,
1615
+ ops
1616
+ });
1617
+ if (dryRun) {
1618
+ setPreviewBusy(true);
1619
+ if (previewTimeoutRef.current) {
1620
+ clearTimeout(previewTimeoutRef.current);
1621
+ }
1622
+ previewTimeoutRef.current = setTimeout(() => {
1623
+ previewTimeoutRef.current = null;
1624
+ if (!patchPendingMapRef.current.has(requestId)) {
1625
+ return;
1626
+ }
1627
+ patchPendingMapRef.current.delete(requestId);
1628
+ setPreviewBusy(false);
1629
+ setPreviewError("No validation response from the dev server (timed out). Check the terminal for [Nuvio] errors.");
1630
+ }, 15e3);
1631
+ }
1632
+ ws.send(
1633
+ JSON.stringify({
1634
+ type: "patchApply",
1635
+ protocolVersion: PROTOCOL_VERSION,
1636
+ requestId,
1637
+ id,
1638
+ ops,
1639
+ ...dryRun ? { dryRun: true } : {}
1640
+ })
1641
+ );
1642
+ }, []);
1643
+ const onRequestPreview = useCallback3(
1644
+ (ops) => {
1645
+ setStructuralPreviewActive(false);
1646
+ autoApplyStructuralRef.current = false;
1647
+ setPreviewValidatedFingerprint(null);
1648
+ setPreviewValidatedOps(null);
1649
+ setPreviewSummary(null);
1650
+ setPreviewError(null);
1651
+ setLastPatchError(null);
1652
+ sendPatchMessage(ops, true);
1653
+ },
1654
+ [sendPatchMessage]
1655
+ );
1656
+ const onRequestStructuralPreview = useCallback3(
1657
+ (ops) => {
1658
+ lastStagedOpsFpRef.current = null;
1659
+ setStructuralPreviewActive(true);
1660
+ autoApplyStructuralRef.current = isStructuralOnlyOps(ops);
1661
+ setPreviewValidatedFingerprint(null);
1662
+ setPreviewValidatedOps(null);
1663
+ setPreviewSummary(null);
1664
+ setPreviewError(null);
1665
+ setLastPatchError(null);
1666
+ sendPatchMessage(ops, true);
1667
+ },
1668
+ [sendPatchMessage]
1669
+ );
1670
+ const onRequestApply = useCallback3(
1671
+ (ops) => {
1672
+ const fp = JSON.stringify(ops);
1673
+ if (fp !== previewValidatedFingerprint) {
1674
+ setLastPatchError(
1675
+ "Run Validate first \u2014 staged edits changed since the last successful validation."
1676
+ );
1677
+ return;
1678
+ }
1679
+ setLastPatchError(null);
1680
+ sendPatchMessage(ops, false);
1681
+ },
1682
+ [previewValidatedFingerprint, sendPatchMessage]
1683
+ );
1684
+ const onRequestUndo = useCallback3(() => {
1685
+ const ws = wsRef.current;
1686
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
1687
+ return;
1688
+ }
1689
+ const requestId = `undo-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`;
1690
+ undoPendingRef.current = requestId;
1691
+ ws.send(
1692
+ JSON.stringify({
1693
+ type: "patchUndo",
1694
+ protocolVersion: PROTOCOL_VERSION,
1695
+ requestId
1696
+ })
1697
+ );
1698
+ }, []);
1699
+ const onCancelPreview = useCallback3(() => {
1700
+ patchPendingMapRef.current.clear();
1701
+ if (previewTimeoutRef.current) {
1702
+ clearTimeout(previewTimeoutRef.current);
1703
+ previewTimeoutRef.current = null;
1704
+ }
1705
+ setPreviewBusy(false);
1706
+ setPreviewSummary(null);
1707
+ setPreviewError(null);
1708
+ setLastPatchError(null);
1709
+ setPreviewValidatedFingerprint(null);
1710
+ setPreviewValidatedOps(null);
1711
+ }, []);
1712
+ const onStagedPatchFingerprint = useCallback3((fp) => {
1713
+ if (lastStagedOpsFpRef.current === fp) {
1714
+ return;
1715
+ }
1716
+ lastStagedOpsFpRef.current = fp;
1717
+ patchPendingMapRef.current.clear();
1718
+ if (previewTimeoutRef.current) {
1719
+ clearTimeout(previewTimeoutRef.current);
1720
+ previewTimeoutRef.current = null;
1721
+ }
1722
+ setPreviewBusy(false);
1723
+ setPreviewSummary(null);
1724
+ setPreviewError(null);
1725
+ setPreviewValidatedFingerprint(null);
1726
+ setPreviewValidatedOps(null);
1727
+ }, []);
1728
+ const sendSelect = useCallback3((id) => {
1729
+ const ws = wsRef.current;
1730
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
1731
+ return;
1732
+ }
1733
+ ws.send(
1734
+ JSON.stringify({
1735
+ type: "select",
1736
+ protocolVersion: PROTOCOL_VERSION,
1737
+ requestId: `select-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`,
1738
+ id
1739
+ })
1740
+ );
1741
+ }, []);
1742
+ const onSelectId = useCallback3(
1743
+ (id) => {
1744
+ if (!id) {
1745
+ return;
1746
+ }
1747
+ lastStagedOpsFpRef.current = null;
1748
+ patchPendingMapRef.current.clear();
1749
+ if (previewTimeoutRef.current) {
1750
+ clearTimeout(previewTimeoutRef.current);
1751
+ previewTimeoutRef.current = null;
1752
+ }
1753
+ setPreviewBusy(false);
1754
+ setPreviewSummary(null);
1755
+ setPreviewError(null);
1756
+ setPreviewValidatedFingerprint(null);
1757
+ setPreviewValidatedOps(null);
1758
+ setLastPatchError(null);
1759
+ setSelectedId(id);
1760
+ setSelectError(null);
1761
+ const hit = lastIndexEntriesRef.current.find((e) => e.id === id);
1762
+ if (hit) {
1763
+ setResolvedFile(shortDisplayPath(hit.file));
1764
+ setResolvedLine(hit.line);
1765
+ } else {
1766
+ setResolvedFile(void 0);
1767
+ setResolvedLine(void 0);
1768
+ }
1769
+ sendSelect(id);
1770
+ },
1771
+ [sendSelect]
1772
+ );
1773
+ useEffect5(() => {
1774
+ let ws = null;
1775
+ let cancelled = false;
1776
+ let retryMs = 400;
1777
+ let reconnectTimer;
1778
+ const connect = () => {
1779
+ if (cancelled) {
1780
+ return;
1781
+ }
1782
+ const myGen = ++connectGenRef.current;
1783
+ setChannel("connecting");
1784
+ const proto = location.protocol === "https:" ? "wss:" : "ws:";
1785
+ ws = new WebSocket(`${proto}//${location.host}${NUVIO_WS_PATH}`);
1786
+ wsRef.current = ws;
1787
+ ws.addEventListener("open", () => {
1788
+ if (cancelled || connectGenRef.current !== myGen) {
1789
+ safeCloseWebSocket(ws);
1790
+ return;
1791
+ }
1792
+ retryMs = 400;
1793
+ setChannel("ready");
1794
+ patchPendingMapRef.current.clear();
1795
+ if (previewTimeoutRef.current) {
1796
+ clearTimeout(previewTimeoutRef.current);
1797
+ previewTimeoutRef.current = null;
1798
+ }
1799
+ setPreviewBusy(false);
1800
+ undoPendingRef.current = null;
1801
+ setUndoStackDepth(0);
1802
+ ws?.send(
1803
+ JSON.stringify({
1804
+ type: "ping",
1805
+ protocolVersion: PROTOCOL_VERSION,
1806
+ requestId: "overlay-mount"
1807
+ })
1808
+ );
1809
+ const sid = selectedIdRef.current;
1810
+ if (sid) {
1811
+ ws?.send(
1812
+ JSON.stringify({
1813
+ type: "select",
1814
+ protocolVersion: PROTOCOL_VERSION,
1815
+ requestId: `select-${globalThis.crypto?.randomUUID?.() ?? Date.now()}`,
1816
+ id: sid
1817
+ })
1818
+ );
1819
+ }
1820
+ });
1821
+ ws.addEventListener("message", (ev) => {
1822
+ const raw = wsPayloadToString(ev.data);
1823
+ const parsePayload = (text) => {
1824
+ const msg = parseServerMessage(text);
1825
+ if (!msg) {
1826
+ return;
1827
+ }
1828
+ if (msg.type === "indexReady") {
1829
+ lastIndexEntriesRef.current = msg.entries;
1830
+ setIndexEntries(msg.entries);
1831
+ setKnownIds(new Set(msg.entries.map((e) => e.id)));
1832
+ setDuplicateErrors(msg.duplicateErrors);
1833
+ const sid = selectedIdRef.current;
1834
+ if (sid) {
1835
+ const hit = msg.entries.find((e) => e.id === sid);
1836
+ if (hit) {
1837
+ setResolvedFile(shortDisplayPath(hit.file));
1838
+ setResolvedLine(hit.line);
1839
+ setSelectError(null);
1840
+ }
1841
+ }
1842
+ if (sid && msg.entries.some((e) => e.id === sid)) {
1843
+ queueMicrotask(() => {
1844
+ sendSelect(sid);
1845
+ });
1846
+ }
1847
+ return;
1848
+ }
1849
+ if (msg.type === "selectAck") {
1850
+ if (msg.ok && msg.file != null && msg.line != null) {
1851
+ setResolvedFile(shortDisplayPath(msg.file));
1852
+ setResolvedLine(msg.line);
1853
+ setSelectError(null);
1854
+ } else {
1855
+ const hit = lastIndexEntriesRef.current.find((e) => e.id === msg.id);
1856
+ if (hit) {
1857
+ setResolvedFile(shortDisplayPath(hit.file));
1858
+ setResolvedLine(hit.line);
1859
+ setSelectError(null);
1860
+ } else {
1861
+ setResolvedFile(void 0);
1862
+ setResolvedLine(void 0);
1863
+ setSelectError(msg.errorMessage ?? msg.errorCode ?? "Selection failed");
1864
+ }
1865
+ }
1866
+ return;
1867
+ }
1868
+ if (msg.type === "patchAck") {
1869
+ const pending = patchPendingMapRef.current.get(msg.requestId);
1870
+ if (!pending) {
1871
+ return;
1872
+ }
1873
+ patchPendingMapRef.current.delete(msg.requestId);
1874
+ const savedFp = pending.opsFingerprint;
1875
+ if (pending.kind === "preview") {
1876
+ if (previewTimeoutRef.current) {
1877
+ clearTimeout(previewTimeoutRef.current);
1878
+ previewTimeoutRef.current = null;
1879
+ }
1880
+ setPreviewBusy(false);
1881
+ if (msg.ok) {
1882
+ const summary = msg.diffSummary?.trim() || "Validation OK \u2014 server did not return a change summary line.";
1883
+ setPreviewSummary(summary);
1884
+ setPreviewError(null);
1885
+ setPreviewValidatedFingerprint(savedFp);
1886
+ setPreviewValidatedOps(pending.ops);
1887
+ if (autoApplyStructuralRef.current && isStructuralOnlyOps(pending.ops)) {
1888
+ autoApplyStructuralRef.current = false;
1889
+ sendPatchMessage(pending.ops, false);
1890
+ }
1891
+ } else {
1892
+ autoApplyStructuralRef.current = false;
1893
+ setPreviewSummary(null);
1894
+ setPreviewValidatedFingerprint(null);
1895
+ setPreviewValidatedOps(null);
1896
+ setPreviewError(msg.errorMessage ?? msg.errorCode ?? "Validation failed");
1897
+ }
1898
+ return;
1899
+ }
1900
+ if (pending.kind === "apply") {
1901
+ if (msg.ok) {
1902
+ setStructuralPreviewActive(false);
1903
+ setLastPatchError(null);
1904
+ setPreviewSummary(null);
1905
+ setPreviewError(null);
1906
+ setPreviewValidatedFingerprint(null);
1907
+ setPreviewValidatedOps(null);
1908
+ setStagedVersion((v) => v + 1);
1909
+ if (msg.undoStackDepth !== void 0) {
1910
+ setUndoStackDepth(msg.undoStackDepth);
1911
+ }
1912
+ } else {
1913
+ setLastPatchError(msg.errorMessage ?? msg.errorCode ?? "Apply failed");
1914
+ }
1915
+ return;
1916
+ }
1917
+ }
1918
+ if (msg.type === "patchUndoAck") {
1919
+ const rid = undoPendingRef.current;
1920
+ if (!rid || rid !== msg.requestId) {
1921
+ return;
1922
+ }
1923
+ undoPendingRef.current = null;
1924
+ if (msg.ok) {
1925
+ if (msg.undoStackDepth !== void 0) {
1926
+ setUndoStackDepth(msg.undoStackDepth);
1927
+ }
1928
+ setStagedVersion((v) => v + 1);
1929
+ setLastPatchError(null);
1930
+ } else {
1931
+ setLastPatchError(msg.errorMessage ?? msg.errorCode ?? "Undo failed");
1932
+ }
1933
+ return;
1934
+ }
1935
+ if (cancelled || connectGenRef.current !== myGen) {
1936
+ return;
1937
+ }
1938
+ if (msg.type === "error") {
1939
+ setChannel("error");
1940
+ }
1941
+ };
1942
+ if (raw !== null) {
1943
+ parsePayload(raw);
1944
+ return;
1945
+ }
1946
+ if (ev.data instanceof Blob) {
1947
+ void ev.data.text().then((text) => {
1948
+ parsePayload(text);
1949
+ });
1950
+ return;
1951
+ }
1952
+ parsePayload(String(ev.data));
1953
+ });
1954
+ ws.addEventListener("error", () => {
1955
+ if (cancelled || connectGenRef.current !== myGen) {
1956
+ return;
1957
+ }
1958
+ setChannel("error");
1959
+ });
1960
+ ws.addEventListener("close", () => {
1961
+ const wasActiveSocket = wsRef.current === ws;
1962
+ if (wasActiveSocket) {
1963
+ wsRef.current = null;
1964
+ }
1965
+ if (cancelled || connectGenRef.current !== myGen) {
1966
+ return;
1967
+ }
1968
+ if (!wasActiveSocket) {
1969
+ return;
1970
+ }
1971
+ const hadPending = patchPendingMapRef.current.size > 0;
1972
+ if (hadPending) {
1973
+ patchPendingMapRef.current.clear();
1974
+ if (previewTimeoutRef.current) {
1975
+ clearTimeout(previewTimeoutRef.current);
1976
+ previewTimeoutRef.current = null;
1977
+ }
1978
+ setPreviewBusy(false);
1979
+ setPreviewError("Dev channel closed before validation finished.");
1980
+ }
1981
+ setChannel("idle");
1982
+ reconnectTimer = setTimeout(() => {
1983
+ retryMs = Math.min(retryMs * 2, 1e4);
1984
+ connect();
1985
+ }, retryMs);
1986
+ });
1987
+ };
1988
+ connect();
1989
+ return () => {
1990
+ cancelled = true;
1991
+ connectGenRef.current += 1;
1992
+ clearTimeout(reconnectTimer);
1993
+ if (ws) {
1994
+ safeCloseWebSocket(ws);
1995
+ }
1996
+ wsRef.current = null;
1997
+ };
1998
+ }, [sendSelect]);
1999
+ const channelLabel = channel === "ready" ? "connected" : channel;
2000
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
2001
+ editMode ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2002
+ /* @__PURE__ */ jsx4(
2003
+ InteractionLayer,
2004
+ {
2005
+ enabled: editMode,
2006
+ chromeRootRefs,
2007
+ knownIds,
2008
+ selectedId,
2009
+ onSelectId
2010
+ }
2011
+ ),
2012
+ /* @__PURE__ */ jsx4(
2013
+ PropertyPanelShell,
2014
+ {
2015
+ shellRef: panelRef,
2016
+ panelCollapsed: chromeLayout.panel.collapsed,
2017
+ panelPosition: chromeLayout.panel.position,
2018
+ onPanelCollapsedChange,
2019
+ onPanelPositionChange,
2020
+ indexEntries,
2021
+ onSelectIndexedId: onSelectId,
2022
+ onRequestStructuralPreview,
2023
+ selectedId,
2024
+ resolvedFile,
2025
+ resolvedLine,
2026
+ indexIdCount: knownIds.size,
2027
+ selectError,
2028
+ channelReady: channel === "ready",
2029
+ previewSummary,
2030
+ previewError,
2031
+ lastPatchError,
2032
+ stagedVersion,
2033
+ previewValidatedFingerprint,
2034
+ previewValidatedOps,
2035
+ structuralPreviewActive,
2036
+ undoStackDepth,
2037
+ previewBusy,
2038
+ onStagedPatchFingerprint,
2039
+ onRequestPreview,
2040
+ onRequestApply,
2041
+ onRequestUndo,
2042
+ onCancelPreview
2043
+ }
2044
+ )
2045
+ ] }) : null,
2046
+ /* @__PURE__ */ jsxs4(
2047
+ "div",
2048
+ {
2049
+ ref: (el) => assignRef(chipRef, el),
2050
+ style: {
2051
+ ...NUVO_GLASS_SURFACE_STYLE,
2052
+ ...chipPos ? { left: chipPos.x, top: chipPos.y, right: "auto", bottom: "auto" } : {}
2053
+ },
2054
+ className: `pointer-events-auto fixed z-[9999] flex flex-col gap-2 rounded-2xl text-left text-sm text-slate-100 ${NUVO_GLASS_CHIP} ${chromeLayout.chip.collapsed ? "min-w-0 max-w-[10rem] p-2" : "min-w-[200px] max-w-sm p-3"} ${chipDragging ? "select-none" : ""}`,
2055
+ children: [
2056
+ /* @__PURE__ */ jsxs4(
2057
+ "div",
2058
+ {
2059
+ className: `flex items-center gap-2 ${chipDragging ? "cursor-grabbing" : "cursor-grab"}`,
2060
+ onPointerDown: onChipHeaderPointerDown,
2061
+ children: [
2062
+ /* @__PURE__ */ jsx4("span", { className: "shrink-0 font-semibold tracking-tight", children: "Nuvio" }),
2063
+ /* @__PURE__ */ jsx4("span", { className: "flex-1", "aria-hidden": "true" }),
2064
+ /* @__PURE__ */ jsx4(
2065
+ "button",
2066
+ {
2067
+ type: "button",
2068
+ className: "shrink-0 rounded px-1.5 py-0.5 text-xs text-slate-400 hover:bg-slate-800 hover:text-slate-200",
2069
+ title: chromeLayout.chip.collapsed ? "Expand chip" : "Collapse chip",
2070
+ "aria-label": chromeLayout.chip.collapsed ? "Expand Nuvio chip" : "Collapse Nuvio chip",
2071
+ onPointerDown: (e) => e.stopPropagation(),
2072
+ onClick: () => onChipCollapsedChange(!chromeLayout.chip.collapsed),
2073
+ children: chromeLayout.chip.collapsed ? "+" : "\u2212"
2074
+ }
2075
+ ),
2076
+ /* @__PURE__ */ jsx4(
2077
+ "button",
2078
+ {
2079
+ type: "button",
2080
+ className: `shrink-0 rounded px-2 py-1 text-xs font-medium ${editMode ? "bg-sky-600 text-white" : "bg-slate-700 text-slate-200 hover:bg-slate-600"}`,
2081
+ onPointerDown: (e) => e.stopPropagation(),
2082
+ onClick: (e) => {
2083
+ e.stopPropagation();
2084
+ toggleEditMode();
2085
+ },
2086
+ children: editMode ? "Editing" : "Edit"
2087
+ }
2088
+ )
2089
+ ]
2090
+ }
2091
+ ),
2092
+ !chromeLayout.chip.collapsed ? /* @__PURE__ */ jsx4(Fragment, { children: /* @__PURE__ */ jsxs4("div", { className: "space-y-1 text-xs text-slate-300/90", children: [
2093
+ /* @__PURE__ */ jsxs4("p", { children: [
2094
+ /* @__PURE__ */ jsx4("span", { className: "text-slate-500", children: "Channel " }),
2095
+ /* @__PURE__ */ jsx4("span", { className: channel === "ready" ? "text-emerald-300" : "text-slate-200", children: channelLabel }),
2096
+ /* @__PURE__ */ jsx4("span", { className: "text-slate-600", children: " \xB7 " }),
2097
+ /* @__PURE__ */ jsxs4("span", { className: "text-slate-500", children: [
2098
+ knownIds.size,
2099
+ " ids"
2100
+ ] })
2101
+ ] }),
2102
+ knownIds.size === 0 ? /* @__PURE__ */ jsx4("p", { className: "text-[11px] text-amber-200/90", children: "Restart dev server if index stays empty." }) : null,
2103
+ duplicateErrors.length > 0 ? /* @__PURE__ */ jsxs4("p", { className: "text-amber-300/90", children: [
2104
+ "Duplicate ids: ",
2105
+ duplicateErrors.map((d) => d.id).join(", ")
2106
+ ] }) : null,
2107
+ selectError ? /* @__PURE__ */ jsx4("p", { className: "text-red-300/95", children: selectError }) : null
2108
+ ] }) }) : /* @__PURE__ */ jsxs4("p", { className: "text-[11px] text-slate-500", children: [
2109
+ channelLabel,
2110
+ editMode ? " \xB7 edit on" : ""
2111
+ ] })
2112
+ ]
2113
+ }
2114
+ )
2115
+ ] });
2116
+ }
2117
+
2118
+ // src/index.tsx
2119
+ import { jsx as jsx5 } from "react/jsx-runtime";
2120
+ function nuvioDevEnabled() {
2121
+ const env = import.meta.env;
2122
+ return env?.DEV === true;
2123
+ }
2124
+ function NuvioDevShell() {
2125
+ if (!nuvioDevEnabled()) {
2126
+ return null;
2127
+ }
2128
+ return /* @__PURE__ */ jsx5(NuvioDevShellInner, {});
2129
+ }
2130
+ export {
2131
+ NuvioDevShell
2132
+ };