@decido/shell 4.0.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/CenterComposite-RPEGBKQU.mjs +1697 -0
  2. package/dist/DebugPanel-KEKDMHDN.mjs +14 -0
  3. package/dist/MorphShell-FKDBB7E5.mjs +14 -0
  4. package/dist/PlaygroundAppSidebar-ALYJJGAO.mjs +9 -0
  5. package/dist/PlaygroundChat-MI2KXMK6.mjs +15 -0
  6. package/dist/PlaygroundTerminal-5AV4BJAI.mjs +7 -0
  7. package/dist/PluginSandbox-WMNAUQOJ.mjs +188 -0
  8. package/dist/ReactFlowEditor-RTF2652X.mjs +3574 -0
  9. package/dist/ReactFlowEditor-ZW5MCN5Y.css +561 -0
  10. package/dist/TimelineEditor-N4HRMHTB.mjs +226 -0
  11. package/dist/WidgetSlotPanel-KJI4CHHD.mjs +11 -0
  12. package/dist/chunk-2YMI4N5I.mjs +2004 -0
  13. package/dist/chunk-3BZX7LF2.mjs +139 -0
  14. package/dist/chunk-3P4P3M54.mjs +136 -0
  15. package/dist/chunk-F3OTFHNO.mjs +40 -0
  16. package/dist/chunk-IMHORBTL.mjs +48 -0
  17. package/dist/chunk-JF5QSJYT.mjs +295 -0
  18. package/dist/chunk-LWMMFTJC.mjs +382 -0
  19. package/dist/chunk-MSVEFEXE.mjs +179 -0
  20. package/dist/chunk-OCHGY2MN.mjs +1662 -0
  21. package/dist/chunk-PMYAM764.mjs +813 -0
  22. package/dist/chunk-Q64KZXPK.mjs +43 -0
  23. package/dist/chunk-QHQW2HMU.mjs +155 -0
  24. package/dist/chunk-RWZ4BOIN.mjs +385 -0
  25. package/dist/chunk-UHT6FIYF.mjs +195 -0
  26. package/dist/chunk-UJCSKKID.mjs +30 -0
  27. package/dist/chunk-V3CYNPGL.mjs +8758 -0
  28. package/dist/chunk-VBPGEFNM.mjs +2381 -0
  29. package/dist/chunk-XMSU6UWD.mjs +158 -0
  30. package/dist/chunk-ZCCCBHE6.mjs +55 -0
  31. package/dist/index.css +561 -0
  32. package/dist/index.js +65130 -0
  33. package/dist/index.mjs +40248 -0
  34. package/dist/useIntentLens-LEQCAXCK.mjs +13 -0
  35. package/dist/useSuggestionsStore-4L2AIZ2D.mjs +7 -0
  36. package/dist/wasm-QFXGEYGP.mjs +81 -0
  37. package/package.json +27 -22
  38. package/src/index.ts +0 -97
@@ -0,0 +1,813 @@
1
+ import {
2
+ useLayoutStore
3
+ } from "./chunk-MSVEFEXE.mjs";
4
+
5
+ // src/components/widgets/WidgetSlotPanel.tsx
6
+ import React2 from "react";
7
+
8
+ // src/components/widgets/WidgetRenderer.tsx
9
+ import React from "react";
10
+ import { RefreshCw, X, AlertTriangle, Wifi, WifiOff } from "lucide-react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ var WidgetDataStore = class {
13
+ data = {};
14
+ listeners = /* @__PURE__ */ new Set();
15
+ /** Set data and notify subscribers */
16
+ set(path, value) {
17
+ this.data[path] = value;
18
+ this.listeners.forEach((fn) => fn());
19
+ }
20
+ /** Resolve a dotted path: 'sentinel.metrics.uptime' → value */
21
+ resolve(path) {
22
+ const parts = path.split(".");
23
+ let current = this.data;
24
+ for (const part of parts) {
25
+ if (current == null) return void 0;
26
+ current = current[part];
27
+ }
28
+ return current;
29
+ }
30
+ /** Bulk set */
31
+ merge(obj) {
32
+ Object.assign(this.data, obj);
33
+ this.listeners.forEach((fn) => fn());
34
+ }
35
+ subscribe(fn) {
36
+ this.listeners.add(fn);
37
+ return () => this.listeners.delete(fn);
38
+ }
39
+ };
40
+ var widgetDataStore = new WidgetDataStore();
41
+ widgetDataStore.merge({
42
+ sentinel: {
43
+ metrics: { eventsPerMin: 1247, avgLatency: 23, uptime: 99.7, errorCount: 2 },
44
+ events: [
45
+ { time: "10:23:01", type: "pageload", path: "/dashboard", duration: 142 },
46
+ { time: "10:22:58", type: "api", path: "/api/nodes", duration: 89 },
47
+ { time: "10:22:45", type: "click", path: "sidebar", duration: 3 },
48
+ { time: "10:22:30", type: "session", path: "user:admin", duration: 0 }
49
+ ],
50
+ errors: [
51
+ { message: "TypeError: Cannot read properties of undefined", source: "IssueList.tsx:11:39", ago: "3 min", severity: "error" },
52
+ { message: "Warning: defaultProps deprecated", source: "recharts.js:28626", ago: "15 min", severity: "warning" }
53
+ ],
54
+ sessions: { today: 3, recording: true }
55
+ }
56
+ });
57
+ var PluginBridge = class {
58
+ iframe = null;
59
+ handlers = /* @__PURE__ */ new Map();
60
+ themeObserver = null;
61
+ attach(iframe) {
62
+ this.iframe = iframe;
63
+ window.addEventListener("message", this.onMessage);
64
+ this.themeObserver = new MutationObserver(() => {
65
+ const isDark = document.documentElement.classList.contains("dark");
66
+ this.emit("theme:changed", isDark ? "dark" : "light");
67
+ });
68
+ this.themeObserver.observe(document.documentElement, {
69
+ attributes: true,
70
+ attributeFilter: ["class"]
71
+ });
72
+ }
73
+ detach() {
74
+ window.removeEventListener("message", this.onMessage);
75
+ this.themeObserver?.disconnect();
76
+ this.themeObserver = null;
77
+ this.iframe = null;
78
+ }
79
+ /** Register a handler for plugin requests */
80
+ onRequest(action, handler) {
81
+ this.handlers.set(action, handler);
82
+ }
83
+ /** Send an event into the iframe */
84
+ emit(topic, payload) {
85
+ this.iframe?.contentWindow?.postMessage({
86
+ type: "DECIDO_HOST_EVENT",
87
+ topic,
88
+ payload
89
+ }, "*");
90
+ }
91
+ onMessage = (event) => {
92
+ if (!this.iframe || event.source !== this.iframe.contentWindow) return;
93
+ const msg = event.data;
94
+ if (msg?.type === "DECIDO_PLUGIN_REQUEST" && msg.action) {
95
+ const handler = this.handlers.get(msg.action);
96
+ if (handler) {
97
+ try {
98
+ const result = handler(msg.payload);
99
+ if (result instanceof Promise) {
100
+ result.then((r) => this.respond(msg.requestId, r)).catch((e) => this.respond(msg.requestId, { error: String(e) }));
101
+ } else {
102
+ this.respond(msg.requestId, result);
103
+ }
104
+ } catch (e) {
105
+ console.error("[Bridge] Handler error:", e);
106
+ this.respond(msg.requestId, { error: String(e) });
107
+ }
108
+ } else {
109
+ console.warn(`[Bridge] No handler for action: ${msg.action}`);
110
+ }
111
+ }
112
+ };
113
+ respond(requestId, payload) {
114
+ if (!requestId) return;
115
+ this.iframe?.contentWindow?.postMessage({
116
+ type: "DECIDO_PLUGIN_RESPONSE",
117
+ requestId,
118
+ payload
119
+ }, "*");
120
+ }
121
+ };
122
+ var WidgetErrorBoundary = class extends React.Component {
123
+ state = { hasError: false };
124
+ static getDerivedStateFromError(error) {
125
+ return { hasError: true, error };
126
+ }
127
+ componentDidCatch(error, info) {
128
+ console.error(`[Widget:${this.props.widgetId}] Crash:`, error, info);
129
+ }
130
+ render() {
131
+ if (this.state.hasError) {
132
+ return /* @__PURE__ */ jsxs("div", { className: "w-full h-full flex flex-col items-center justify-center bg-surface-primary gap-3 p-6", children: [
133
+ /* @__PURE__ */ jsx("div", { className: "w-14 h-14 rounded-2xl bg-red-500/10 border border-red-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx(AlertTriangle, { className: "w-7 h-7 text-red-400" }) }),
134
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-text-primary", children: "Widget Crashed" }),
135
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-muted text-center max-w-[280px] font-mono bg-surface-glass px-3 py-2 rounded-lg", children: this.state.error?.message || "Unknown error" }),
136
+ /* @__PURE__ */ jsx(
137
+ "button",
138
+ {
139
+ onClick: () => {
140
+ this.setState({ hasError: false, error: void 0 });
141
+ this.props.onReset?.();
142
+ },
143
+ className: "px-4 py-1.5 rounded-lg text-xs font-medium bg-surface-glass hover:bg-surface-elevated border border-border-subtle transition-colors",
144
+ children: "Retry"
145
+ }
146
+ )
147
+ ] });
148
+ }
149
+ return this.props.children;
150
+ }
151
+ };
152
+ function WidgetRenderer({ widget, onClose, className = "" }) {
153
+ return /* @__PURE__ */ jsx(WidgetErrorBoundary, { widgetId: widget.id, children: widget.url ? /* @__PURE__ */ jsx(IframeWidget, { widget, onClose, className }) : widget.schema ? /* @__PURE__ */ jsx(SDUIWidget, { widget, onClose, className }) : widget.scriptUrl ? /* @__PURE__ */ jsx(HeadlessWidget, { widget, onClose, className }) : widget.component ? /* @__PURE__ */ jsx(NativeComponentWidget, { widget, onClose, className }) : /* @__PURE__ */ jsx("div", { className: `w-full h-full flex items-center justify-center text-text-muted ${className}`, children: /* @__PURE__ */ jsxs("p", { className: "text-xs", children: [
154
+ 'Widget "',
155
+ widget.title,
156
+ '" has no schema, URL, scriptUrl, or component'
157
+ ] }) }) });
158
+ }
159
+ function NativeComponentWidget({ widget, onClose, className = "" }) {
160
+ const Component = widget.component;
161
+ return /* @__PURE__ */ jsxs("div", { className: `w-full h-full flex flex-col bg-surface-primary ${className} min-w-0 min-h-0`, children: [
162
+ /* @__PURE__ */ jsx(WidgetToolbar, { label: "OPTION D \xB7 NATIVE", color: "emerald", title: widget.title, onClose, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-1.5 py-0.5 rounded bg-emerald-500/10 border border-emerald-500/20", children: /* @__PURE__ */ jsx("span", { className: "text-[9px] text-emerald-400 font-mono", children: "REACT" }) }) }),
163
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 min-w-0 overflow-y-auto overflow-x-auto bg-surface-primary rr-block relative", "data-rr-block": true, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(LoadingOverlay, { title: widget.title, color: "emerald" }), children: /* @__PURE__ */ jsx(Component, {}) }) })
164
+ ] });
165
+ }
166
+ function HeadlessWidget({ widget, onClose, className = "" }) {
167
+ const [status, setStatus] = React.useState("idle");
168
+ const [runCount, setRunCount] = React.useState(0);
169
+ const [lastError, setLastError] = React.useState(null);
170
+ const sandboxRef = React.useRef(null);
171
+ React.useEffect(() => {
172
+ return () => {
173
+ if (sandboxRef.current) {
174
+ sandboxRef.current.destroy();
175
+ sandboxRef.current = null;
176
+ }
177
+ };
178
+ }, []);
179
+ const handleStart = React.useCallback(async () => {
180
+ try {
181
+ setStatus("running");
182
+ setLastError(null);
183
+ const scriptUrl = widget.scriptUrl || "";
184
+ if (scriptUrl.startsWith("wasm://")) {
185
+ const parts = scriptUrl.replace("wasm://", "").split("/");
186
+ const pluginName = parts[0];
187
+ const fnName = parts[1] || "run";
188
+ const { runWasmPlugin } = await import("./wasm-QFXGEYGP.mjs");
189
+ const result = await runWasmPlugin(pluginName, fnName);
190
+ console.log(`[WASM] Plugin "${pluginName}.${fnName}" result:`, result);
191
+ setRunCount((c) => c + 1);
192
+ setStatus("stopped");
193
+ return;
194
+ }
195
+ const { PluginSandbox } = await import("./PluginSandbox-WMNAUQOJ.mjs");
196
+ const sandbox = new PluginSandbox({
197
+ pluginId: widget.pluginId,
198
+ pluginUrl: scriptUrl,
199
+ tenantId: "default",
200
+ permissions: widget.permissions || []
201
+ });
202
+ sandbox.onRpcRequest = async (action, payload) => {
203
+ return new Promise((resolve, reject) => {
204
+ const reqId = `headless-${Date.now()}-${Math.random().toString(36).slice(2)}`;
205
+ const handler = (e) => {
206
+ if (e.data?.reqId === reqId) {
207
+ window.removeEventListener("message", handler);
208
+ if (e.data.error) reject(new Error(e.data.error));
209
+ else resolve(e.data.result);
210
+ }
211
+ };
212
+ window.addEventListener("message", handler);
213
+ window.postMessage({
214
+ type: "DECIDO_RPC",
215
+ reqId,
216
+ action,
217
+ pluginId: widget.pluginId,
218
+ payload
219
+ }, window.location.origin);
220
+ });
221
+ };
222
+ sandbox.onError = (error) => {
223
+ setLastError(error);
224
+ setStatus("error");
225
+ };
226
+ sandboxRef.current = sandbox;
227
+ await sandbox.start();
228
+ setRunCount((c) => c + 1);
229
+ } catch (err) {
230
+ setStatus("error");
231
+ setLastError(typeof err === "string" ? err : err?.message || String(err));
232
+ console.error("[HeadlessWidget] Error:", err);
233
+ }
234
+ }, [widget]);
235
+ const handleStop = React.useCallback(() => {
236
+ if (sandboxRef.current) {
237
+ sandboxRef.current.destroy();
238
+ sandboxRef.current = null;
239
+ }
240
+ setStatus("stopped");
241
+ }, []);
242
+ const statusColors = {
243
+ idle: "bg-gray-500/20 text-gray-400",
244
+ running: "bg-emerald-500/20 text-emerald-400",
245
+ stopped: "bg-amber-500/20 text-amber-400",
246
+ error: "bg-red-500/20 text-red-400"
247
+ };
248
+ return /* @__PURE__ */ jsxs("div", { className: `flex flex-col h-full ${className}`, children: [
249
+ /* @__PURE__ */ jsx(WidgetToolbar, { title: widget.title, label: "WORKER", color: "violet", onClose, children: /* @__PURE__ */ jsx("div", { className: `flex items-center gap-1 px-1.5 py-0.5 rounded ${statusColors[status]}`, children: /* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono uppercase", children: status }) }) }),
250
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 p-4", children: [
251
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
252
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-muted mb-1", children: "Worker Sandbox Plugin" }),
253
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-text-disabled font-mono", children: widget.scriptUrl })
254
+ ] }),
255
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
256
+ /* @__PURE__ */ jsx(
257
+ "button",
258
+ {
259
+ onClick: handleStart,
260
+ disabled: status === "running",
261
+ className: "px-3 py-1.5 text-xs rounded bg-emerald-500/20 text-emerald-400 hover:bg-emerald-500/30 disabled:opacity-50 transition-colors",
262
+ children: "\u25B6 Start"
263
+ }
264
+ ),
265
+ /* @__PURE__ */ jsx(
266
+ "button",
267
+ {
268
+ onClick: handleStop,
269
+ disabled: status !== "running",
270
+ className: "px-3 py-1.5 text-xs rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 disabled:opacity-50 transition-colors",
271
+ children: "\u25A0 Stop"
272
+ }
273
+ )
274
+ ] }),
275
+ runCount > 0 && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-text-disabled", children: [
276
+ "Runs: ",
277
+ runCount
278
+ ] }),
279
+ lastError && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-red-400 max-w-[200px] text-center", children: lastError })
280
+ ] })
281
+ ] });
282
+ }
283
+ function IframeWidget({ widget, onClose, className = "" }) {
284
+ const iframeRef = React.useRef(null);
285
+ const bridgeRef = React.useRef(new PluginBridge());
286
+ const [isLoading, setIsLoading] = React.useState(true);
287
+ const [hasError, setHasError] = React.useState(false);
288
+ const [isOnline, setIsOnline] = React.useState(true);
289
+ React.useEffect(() => {
290
+ if (!widget.url) return;
291
+ const controller = new AbortController();
292
+ fetch(widget.url, { mode: "no-cors", signal: controller.signal }).then(() => setIsOnline(true)).catch(() => setIsOnline(false));
293
+ return () => controller.abort();
294
+ }, [widget.url]);
295
+ React.useEffect(() => {
296
+ const bridge = bridgeRef.current;
297
+ bridge.onRequest("getTheme", () => {
298
+ return document.documentElement.classList.contains("dark") ? "dark" : "light";
299
+ });
300
+ bridge.onRequest("getData", (p) => {
301
+ return widgetDataStore.resolve(p.path);
302
+ });
303
+ bridge.onRequest("navigate", (p) => {
304
+ console.log("[Bridge] Plugin requested navigation:", p.route);
305
+ });
306
+ bridge.onRequest("requestAction", (p) => {
307
+ console.log("[Bridge] Action from plugin:", p.command);
308
+ window.dispatchEvent(new CustomEvent("sdui:action", {
309
+ detail: { command: p.command, payload: p.payload }
310
+ }));
311
+ return { dispatched: true };
312
+ });
313
+ bridge.onRequest("getWidgets", () => {
314
+ const store = window.__pluginWidgetStore;
315
+ if (!store) return { widgets: [] };
316
+ return { widgets: store.getVisible().map((w) => ({ id: w.id, title: w.title, pluginId: w.pluginId })) };
317
+ });
318
+ bridge.onRequest("openExternal", (p) => {
319
+ window.open(p.url, "_blank", "noopener,noreferrer");
320
+ return { opened: true };
321
+ });
322
+ return () => bridge.detach();
323
+ }, []);
324
+ React.useEffect(() => {
325
+ setIsLoading(true);
326
+ setHasError(false);
327
+ }, [widget.url]);
328
+ const handleIframeLoad = () => {
329
+ setIsLoading(false);
330
+ setHasError(false);
331
+ if (iframeRef.current) {
332
+ bridgeRef.current.attach(iframeRef.current);
333
+ }
334
+ };
335
+ const handleRefresh = () => {
336
+ if (iframeRef.current) {
337
+ setIsLoading(true);
338
+ setHasError(false);
339
+ iframeRef.current.src = iframeRef.current.src;
340
+ }
341
+ };
342
+ if (!isOnline) {
343
+ return /* @__PURE__ */ jsxs("div", { className: `w-full h-full flex flex-col bg-surface-primary ${className}`, children: [
344
+ /* @__PURE__ */ jsx(WidgetToolbar, { label: "OPTION B \xB7 IFRAME", color: "blue", title: widget.title, onClose, onRefresh: () => setIsOnline(true) }),
345
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3", children: [
346
+ /* @__PURE__ */ jsx("div", { className: "w-14 h-14 rounded-2xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsx(WifiOff, { className: "w-7 h-7 text-amber-400" }) }),
347
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-text-primary", children: "Plugin Offline" }),
348
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-text-muted", children: [
349
+ widget.url,
350
+ " no responde"
351
+ ] }),
352
+ /* @__PURE__ */ jsx("button", { onClick: () => {
353
+ setIsOnline(true);
354
+ }, className: "px-3 py-1.5 rounded-lg text-xs font-medium bg-surface-glass hover:bg-surface-elevated transition-colors", children: "Reintentar" })
355
+ ] })
356
+ ] });
357
+ }
358
+ return /* @__PURE__ */ jsxs("div", { className: `w-full h-full flex flex-col bg-surface-primary ${className}`, children: [
359
+ /* @__PURE__ */ jsx(WidgetToolbar, { label: "OPTION B \xB7 IFRAME", color: "blue", title: widget.title, onClose, onRefresh: handleRefresh, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-500/10 border border-blue-500/20", children: [
360
+ /* @__PURE__ */ jsx(Wifi, { className: "w-2.5 h-2.5 text-blue-400" }),
361
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-blue-400 font-mono", children: "BRIDGE" })
362
+ ] }) }),
363
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 relative rr-block", "data-rr-block": true, children: [
364
+ isLoading && /* @__PURE__ */ jsx(LoadingOverlay, { title: widget.title, color: "blue" }),
365
+ hasError && /* @__PURE__ */ jsx(ErrorOverlay, { url: widget.url || "", onRetry: handleRefresh }),
366
+ /* @__PURE__ */ jsx(
367
+ "iframe",
368
+ {
369
+ ref: iframeRef,
370
+ src: widget.url,
371
+ className: "w-full h-full border-0 rr-block",
372
+ onLoad: handleIframeLoad,
373
+ onError: () => {
374
+ setIsLoading(false);
375
+ setHasError(true);
376
+ },
377
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms allow-modals",
378
+ "data-rr-block": true,
379
+ title: widget.title
380
+ }
381
+ )
382
+ ] })
383
+ ] });
384
+ }
385
+ function SDUIWidget({ widget, onClose, className = "" }) {
386
+ const [, forceRender] = React.useReducer((x) => x + 1, 0);
387
+ React.useEffect(() => widgetDataStore.subscribe(forceRender), []);
388
+ return /* @__PURE__ */ jsxs("div", { className: `w-full h-full flex flex-col bg-surface-primary overflow-hidden ${className}`, children: [
389
+ /* @__PURE__ */ jsx(WidgetToolbar, { label: "OPTION A \xB7 SDUI", color: "emerald", title: widget.title, onClose, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-1.5 py-0.5 rounded bg-emerald-500/10 border border-emerald-500/20", children: /* @__PURE__ */ jsx("span", { className: "text-[9px] text-emerald-400 font-mono", children: "REACTIVE" }) }) }),
390
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: widget.schema && /* @__PURE__ */ jsx(SDUINode, { node: widget.schema }) })
391
+ ] });
392
+ }
393
+ function WidgetToolbar({ label, color, title, onClose, onRefresh, children }) {
394
+ const colorClasses = {
395
+ blue: "text-blue-400 bg-blue-400",
396
+ emerald: "text-emerald-400 bg-emerald-400"
397
+ };
398
+ const textColor = colorClasses[color]?.split(" ")[0] || "text-cyan-400";
399
+ const dotColor = colorClasses[color]?.split(" ")[1] || "bg-cyan-400";
400
+ return /* @__PURE__ */ jsxs("div", { className: "flex-none flex items-center justify-between px-3 py-1.5 bg-surface-secondary/80 backdrop-blur-sm border-b border-border-subtle", children: [
401
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
402
+ /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-full ${dotColor} animate-pulse` }),
403
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] font-bold ${textColor} tracking-wider uppercase`, children: label }),
404
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-text-primary", children: title }),
405
+ children
406
+ ] }),
407
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
408
+ onRefresh && /* @__PURE__ */ jsx("button", { onClick: onRefresh, className: "p-1 rounded text-text-muted hover:text-text-primary hover:bg-surface-glass transition-colors", title: "Refresh", children: /* @__PURE__ */ jsx(RefreshCw, { className: "w-3 h-3" }) }),
409
+ onClose && /* @__PURE__ */ jsx("button", { onClick: onClose, className: "p-1 rounded text-text-muted hover:text-red-400 hover:bg-red-500/10 transition-colors", title: "Close", children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" }) })
410
+ ] })
411
+ ] });
412
+ }
413
+ function LoadingOverlay({ title, color }) {
414
+ const borderColor = color === "blue" ? "border-blue-400" : "border-emerald-400";
415
+ const borderFade = color === "blue" ? "border-blue-500/20" : "border-emerald-500/20";
416
+ return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-surface-primary z-10", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
417
+ /* @__PURE__ */ jsx("div", { className: `w-6 h-6 border-2 ${borderFade} rounded-full relative`, children: /* @__PURE__ */ jsx("div", { className: `absolute inset-0 w-6 h-6 border-2 border-transparent border-t-current ${borderColor} rounded-full animate-spin` }) }),
418
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-text-muted", children: [
419
+ "Loading ",
420
+ title,
421
+ "..."
422
+ ] })
423
+ ] }) });
424
+ }
425
+ function ErrorOverlay({ url, onRetry }) {
426
+ return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-surface-primary z-10", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
427
+ /* @__PURE__ */ jsx(X, { className: "w-8 h-8 text-red-400 mx-auto" }),
428
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-text-muted", children: [
429
+ "Failed to load ",
430
+ url
431
+ ] }),
432
+ /* @__PURE__ */ jsx("button", { onClick: onRetry, className: "text-[10px] px-2 py-1 rounded bg-surface-glass hover:bg-surface-elevated transition-colors", children: "Retry" })
433
+ ] }) });
434
+ }
435
+ var SAFE_TAGS = /* @__PURE__ */ new Set([
436
+ "div",
437
+ "span",
438
+ "p",
439
+ "h1",
440
+ "h2",
441
+ "h3",
442
+ "h4",
443
+ "h5",
444
+ "h6",
445
+ "button",
446
+ "a",
447
+ "img",
448
+ "ul",
449
+ "ol",
450
+ "li",
451
+ "table",
452
+ "thead",
453
+ "tbody",
454
+ "tr",
455
+ "th",
456
+ "td",
457
+ "form",
458
+ "input",
459
+ "textarea",
460
+ "select",
461
+ "option",
462
+ "label",
463
+ "section",
464
+ "article",
465
+ "nav",
466
+ "header",
467
+ "footer",
468
+ "main",
469
+ "aside"
470
+ ]);
471
+ var RICH_COMPONENTS = {
472
+ "MetricCard": MetricCard,
473
+ "ProgressBar": ProgressBar,
474
+ "StatusBadge": StatusBadge,
475
+ "EventList": EventList,
476
+ "ErrorList": ErrorList,
477
+ "Sparkline": Sparkline
478
+ };
479
+ function SDUINode({ node }) {
480
+ if (typeof node === "string") return /* @__PURE__ */ jsx(Fragment, { children: node });
481
+ if (!node || !node.type) return null;
482
+ if (node.conditional) {
483
+ const val = widgetDataStore.resolve(node.conditional);
484
+ if (!val) return null;
485
+ }
486
+ let resolvedProps = { ...node.props };
487
+ if (node.dataSource) {
488
+ const val = widgetDataStore.resolve(node.dataSource);
489
+ if (val !== void 0) {
490
+ if (typeof val === "string" || typeof val === "number") {
491
+ return React.createElement(node.type, resolvedProps, String(val));
492
+ }
493
+ resolvedProps = { ...resolvedProps, data: val };
494
+ }
495
+ }
496
+ if (node.onAction) {
497
+ const action = node.onAction;
498
+ resolvedProps.onClick = () => {
499
+ console.log(`[SDUI Action] ${action.command}`, action.payload);
500
+ window.dispatchEvent(new CustomEvent("sdui:action", {
501
+ detail: { command: action.command, payload: action.payload }
502
+ }));
503
+ };
504
+ resolvedProps.style = { ...resolvedProps.style, cursor: "pointer" };
505
+ }
506
+ const RichComponent = RICH_COMPONENTS[node.type];
507
+ if (RichComponent) {
508
+ return /* @__PURE__ */ jsx(RichComponent, { ...resolvedProps });
509
+ }
510
+ if (!SAFE_TAGS.has(node.type)) {
511
+ return /* @__PURE__ */ jsxs("div", { className: "p-2 border border-amber-500/20 rounded bg-amber-500/5 text-xs text-amber-400", children: [
512
+ "Unknown: ",
513
+ node.type
514
+ ] });
515
+ }
516
+ const children = node.children?.map((child, i) => /* @__PURE__ */ jsx(SDUINode, { node: child }, i));
517
+ return React.createElement(node.type, resolvedProps, children);
518
+ }
519
+ function MetricCard({ label, value, unit, color = "cyan", trend }) {
520
+ const colors = {
521
+ cyan: "bg-cyan-900/30 border-cyan-500/20 text-cyan-400",
522
+ emerald: "bg-emerald-900/30 border-emerald-500/20 text-emerald-400",
523
+ amber: "bg-amber-900/30 border-amber-500/20 text-amber-400",
524
+ red: "bg-red-900/30 border-red-500/20 text-red-400",
525
+ blue: "bg-blue-900/30 border-blue-500/20 text-blue-400"
526
+ };
527
+ const trendIcons = { up: "\u2191", down: "\u2193", stable: "\u2192" };
528
+ const trendColors = { up: "text-emerald-400", down: "text-red-400", stable: "text-zinc-500" };
529
+ return /* @__PURE__ */ jsxs("div", { className: `p-3 rounded-lg border text-center ${colors[color] || colors.cyan}`, children: [
530
+ /* @__PURE__ */ jsxs("div", { className: "text-2xl font-bold flex items-center justify-center gap-1", children: [
531
+ value,
532
+ unit && /* @__PURE__ */ jsx("span", { className: "text-sm opacity-60", children: unit }),
533
+ trend && /* @__PURE__ */ jsx("span", { className: `text-sm ${trendColors[trend]}`, children: trendIcons[trend] })
534
+ ] }),
535
+ /* @__PURE__ */ jsx("div", { className: "text-[10px] text-zinc-400 uppercase tracking-wider mt-1", children: label })
536
+ ] });
537
+ }
538
+ function ProgressBar({ value, max = 100, label, color = "cyan" }) {
539
+ const pct = Math.min(100, value / max * 100);
540
+ const barColors = {
541
+ cyan: "bg-cyan-500",
542
+ emerald: "bg-emerald-500",
543
+ amber: "bg-amber-500",
544
+ red: "bg-red-500"
545
+ };
546
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
547
+ label && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
548
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: label }),
549
+ /* @__PURE__ */ jsxs("span", { className: "text-zinc-300 font-mono", children: [
550
+ pct.toFixed(0),
551
+ "%"
552
+ ] })
553
+ ] }),
554
+ /* @__PURE__ */ jsx("div", { className: "h-2 rounded-full bg-zinc-800 overflow-hidden", children: /* @__PURE__ */ jsx(
555
+ "div",
556
+ {
557
+ className: `h-full rounded-full transition-all duration-700 ${barColors[color] || barColors.cyan}`,
558
+ style: { width: `${pct}%` }
559
+ }
560
+ ) })
561
+ ] });
562
+ }
563
+ function StatusBadge({ status, label }) {
564
+ const styles = {
565
+ online: "bg-emerald-500/10 border-emerald-500/20 text-emerald-400",
566
+ offline: "bg-zinc-500/10 border-zinc-500/20 text-zinc-400",
567
+ warning: "bg-amber-500/10 border-amber-500/20 text-amber-400",
568
+ error: "bg-red-500/10 border-red-500/20 text-red-400"
569
+ };
570
+ const dots = {
571
+ online: "bg-emerald-400",
572
+ offline: "bg-zinc-500",
573
+ warning: "bg-amber-400",
574
+ error: "bg-red-400"
575
+ };
576
+ return /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full border text-[10px] font-semibold uppercase tracking-wider ${styles[status]}`, children: [
577
+ /* @__PURE__ */ jsx("span", { className: `w-1.5 h-1.5 rounded-full ${dots[status]} ${status === "online" ? "animate-pulse" : ""}` }),
578
+ label || status
579
+ ] });
580
+ }
581
+ function EventList({ data, maxItems = 6 }) {
582
+ const events = Array.isArray(data) ? data.slice(0, maxItems) : [];
583
+ if (events.length === 0) {
584
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-zinc-500 italic", children: "No events" });
585
+ }
586
+ const typeColors = {
587
+ pageload: "text-cyan-400",
588
+ api: "text-blue-400",
589
+ click: "text-emerald-400",
590
+ session: "text-purple-400",
591
+ error: "text-red-400"
592
+ };
593
+ return /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: events.map((ev, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs py-1.5 px-2 rounded hover:bg-white/[0.02] transition-colors", children: [
594
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-600 font-mono text-[10px] w-16 shrink-0", children: ev.time }),
595
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] font-bold uppercase tracking-wider w-16 shrink-0 ${typeColors[ev.type] || "text-zinc-500"}`, children: ev.type }),
596
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-400 truncate", children: ev.path }),
597
+ ev.duration > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-auto text-zinc-600 font-mono text-[10px] shrink-0", children: [
598
+ ev.duration,
599
+ "ms"
600
+ ] })
601
+ ] }, i)) });
602
+ }
603
+ function ErrorList({ data }) {
604
+ const errors = Array.isArray(data) ? data : [];
605
+ if (errors.length === 0) {
606
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 bg-emerald-900/10 rounded-lg border border-emerald-500/10", children: [
607
+ /* @__PURE__ */ jsx("span", { className: "text-emerald-400 text-sm", children: "\u2713" }),
608
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-emerald-400", children: "No errors detected" })
609
+ ] });
610
+ }
611
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: errors.map((err, i) => {
612
+ const isError = err.severity === "error";
613
+ return /* @__PURE__ */ jsxs("div", { className: `p-3 rounded-lg border space-y-1 ${isError ? "bg-red-900/10 border-red-500/20" : "bg-amber-900/10 border-amber-500/20"}`, children: [
614
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
615
+ /* @__PURE__ */ jsx("span", { className: `text-sm font-medium ${isError ? "text-red-400" : "text-amber-400"}`, children: err.message }),
616
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-zinc-500", children: err.ago })
617
+ ] }),
618
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-zinc-500 font-mono", children: err.source })
619
+ ] }, i);
620
+ }) });
621
+ }
622
+ function Sparkline({ data, color = "cyan", height = 32 }) {
623
+ const values = Array.isArray(data) ? data : [30, 45, 28, 60, 42, 75, 55, 90, 65, 80];
624
+ const max = Math.max(...values);
625
+ const min = Math.min(...values);
626
+ const range = max - min || 1;
627
+ const w = 120;
628
+ const points = values.map((v, i) => {
629
+ const x = i / (values.length - 1) * w;
630
+ const y = height - (v - min) / range * (height - 4) - 2;
631
+ return `${x},${y}`;
632
+ }).join(" ");
633
+ const strokeColors = {
634
+ cyan: "#22d3ee",
635
+ emerald: "#34d399",
636
+ amber: "#fbbf24",
637
+ red: "#f87171",
638
+ blue: "#60a5fa"
639
+ };
640
+ return /* @__PURE__ */ jsx("svg", { width: w, height, className: "inline-block align-middle", children: /* @__PURE__ */ jsx(
641
+ "polyline",
642
+ {
643
+ points,
644
+ fill: "none",
645
+ stroke: strokeColors[color] || strokeColors.cyan,
646
+ strokeWidth: "1.5",
647
+ strokeLinecap: "round",
648
+ strokeLinejoin: "round"
649
+ }
650
+ ) });
651
+ }
652
+
653
+ // src/components/widgets/WidgetSlotPanel.tsx
654
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
655
+ var PERSISTENCE_KEY = "decido:activeWidgets";
656
+ var WidgetStore = class {
657
+ widgets = /* @__PURE__ */ new Map();
658
+ listeners = /* @__PURE__ */ new Set();
659
+ _activeId = null;
660
+ persistTimer = null;
661
+ register(widget) {
662
+ this.widgets.set(widget.id, { ...widget, visible: widget.visible ?? true });
663
+ this.notify();
664
+ this.persist();
665
+ }
666
+ remove(id) {
667
+ this.widgets.delete(id);
668
+ if (this._activeId === id) this._activeId = null;
669
+ this.notify();
670
+ this.persist();
671
+ }
672
+ clear() {
673
+ this.widgets.clear();
674
+ this._activeId = null;
675
+ this.notify();
676
+ this.persist();
677
+ }
678
+ setActive(id) {
679
+ this._activeId = id;
680
+ this.notify();
681
+ this.persist();
682
+ }
683
+ get activeId() {
684
+ return this._activeId;
685
+ }
686
+ get size() {
687
+ return this.widgets.size;
688
+ }
689
+ getVisible() {
690
+ return Array.from(this.widgets.values()).filter((w) => w.visible !== false).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
691
+ }
692
+ /** Restore from localStorage (call on mount with schema resolver) */
693
+ restoreFromPersistence(schemaResolver) {
694
+ try {
695
+ const raw = localStorage.getItem(PERSISTENCE_KEY);
696
+ if (!raw) return false;
697
+ const snapshot = JSON.parse(raw);
698
+ if (!snapshot?.widgets?.length) return false;
699
+ for (const pw of snapshot.widgets) {
700
+ const schema = schemaResolver(pw.id);
701
+ this.widgets.set(pw.id, {
702
+ ...pw,
703
+ visible: true,
704
+ schema
705
+ });
706
+ }
707
+ this._activeId = snapshot.activeId;
708
+ this.notify();
709
+ console.log(`[WidgetStore] Restored ${snapshot.widgets.length} widgets from persistence`);
710
+ return true;
711
+ } catch (e) {
712
+ console.warn("[WidgetStore] Restore failed:", e);
713
+ return false;
714
+ }
715
+ }
716
+ subscribe(fn) {
717
+ this.listeners.add(fn);
718
+ return () => this.listeners.delete(fn);
719
+ }
720
+ notify() {
721
+ this.listeners.forEach((fn) => fn());
722
+ }
723
+ /** Debounced write to localStorage (excludes schema which can't be serialized) */
724
+ persist() {
725
+ if (this.persistTimer) clearTimeout(this.persistTimer);
726
+ this.persistTimer = setTimeout(() => {
727
+ try {
728
+ const snapshot = {
729
+ widgets: Array.from(this.widgets.values()).map((w) => ({
730
+ id: w.id,
731
+ pluginId: w.pluginId,
732
+ title: w.title,
733
+ icon: w.icon,
734
+ slot: w.slot,
735
+ url: w.url,
736
+ order: w.order
737
+ })),
738
+ activeId: this._activeId
739
+ };
740
+ localStorage.setItem(PERSISTENCE_KEY, JSON.stringify(snapshot));
741
+ } catch {
742
+ }
743
+ }, 300);
744
+ }
745
+ };
746
+ var pluginWidgetStore = new WidgetStore();
747
+ window.__pluginWidgetStore = pluginWidgetStore;
748
+ function useWidgetStore() {
749
+ const [, forceUpdate] = React2.useReducer((x) => x + 1, 0);
750
+ React2.useEffect(() => pluginWidgetStore.subscribe(forceUpdate), []);
751
+ return {
752
+ widgets: pluginWidgetStore.getVisible(),
753
+ activeId: pluginWidgetStore.activeId
754
+ };
755
+ }
756
+ function WidgetSlotPanel() {
757
+ const { widgets, activeId } = useWidgetStore();
758
+ const activeWidget = activeId ? widgets.find((w) => w.id === activeId) || widgets[0] : widgets[0];
759
+ const handleClose = React2.useCallback((widgetId) => {
760
+ pluginWidgetStore.remove(widgetId);
761
+ if (pluginWidgetStore.size === 0) {
762
+ useLayoutStore.getState().setSlot("R3", null);
763
+ }
764
+ }, []);
765
+ const handleCloseAll = React2.useCallback(() => {
766
+ pluginWidgetStore.clear();
767
+ useLayoutStore.getState().setSlot("R3", null);
768
+ }, []);
769
+ if (widgets.length === 0) {
770
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full h-full flex flex-col items-center justify-center text-text-muted gap-3 bg-surface-primary", children: [
771
+ /* @__PURE__ */ jsx2("div", { className: "w-12 h-12 rounded-xl bg-cyan-500/5 border border-cyan-500/10 flex items-center justify-center", children: /* @__PURE__ */ jsx2("span", { className: "text-xl", children: "\u{1F9E9}" }) }),
772
+ /* @__PURE__ */ jsx2("p", { className: "text-sm font-medium", children: "No widgets activos" }),
773
+ /* @__PURE__ */ jsx2("p", { className: "text-xs opacity-60 max-w-[200px] text-center", children: "Selecciona un plugin del sidebar para ver widgets aqu\xED" })
774
+ ] });
775
+ }
776
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full h-full flex flex-col bg-surface-primary overflow-hidden", children: [
777
+ /* @__PURE__ */ jsxs2("div", { className: "flex-none flex items-center gap-1 px-2 py-1 bg-surface-secondary border-b border-border-subtle overflow-x-auto", children: [
778
+ widgets.map((w) => /* @__PURE__ */ jsxs2(
779
+ "button",
780
+ {
781
+ onClick: () => pluginWidgetStore.setActive(w.id),
782
+ className: `px-2.5 py-1 rounded-md text-[10px] font-medium transition-all whitespace-nowrap ${activeWidget?.id === w.id ? "bg-surface-glass text-text-primary border border-border-default" : "text-text-muted hover:text-text-secondary hover:bg-surface-glass/50"}`,
783
+ children: [
784
+ w.icon && /* @__PURE__ */ jsx2("span", { className: "mr-1", children: w.icon }),
785
+ w.title
786
+ ]
787
+ },
788
+ w.id
789
+ )),
790
+ widgets.length > 1 && /* @__PURE__ */ jsx2(
791
+ "button",
792
+ {
793
+ onClick: handleCloseAll,
794
+ className: "ml-auto px-2 py-1 rounded-md text-[10px] text-text-muted hover:text-red-400 hover:bg-red-500/10 transition-all",
795
+ title: "Close all widgets",
796
+ children: "\u2715 All"
797
+ }
798
+ )
799
+ ] }),
800
+ activeWidget && /* @__PURE__ */ jsx2("div", { className: "flex-1 min-h-0 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx2(
801
+ WidgetRenderer,
802
+ {
803
+ widget: activeWidget,
804
+ onClose: () => handleClose(activeWidget.id)
805
+ }
806
+ ) })
807
+ ] });
808
+ }
809
+
810
+ export {
811
+ pluginWidgetStore,
812
+ WidgetSlotPanel
813
+ };