@tangle-network/sandbox-ui 0.113.7 → 0.115.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.
Files changed (107) hide show
  1. package/README.md +40 -0
  2. package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
  3. package/dist/assets.d.ts +214 -206
  4. package/dist/assets.js +1057 -908
  5. package/dist/auth-CWe17Hly.js +37 -0
  6. package/dist/auth.d.ts +2 -30
  7. package/dist/auth.js +2 -15
  8. package/dist/chat-BOvFAe6p.js +903 -0
  9. package/dist/chat.d.ts +2 -230
  10. package/dist/chat.js +2 -34
  11. package/dist/connectors.d.ts +52 -49
  12. package/dist/connectors.js +595 -643
  13. package/dist/dashboard-CZ3VgNPR.js +4969 -0
  14. package/dist/dashboard.d.ts +4 -1297
  15. package/dist/dashboard.js +4 -152
  16. package/dist/editor.d.ts +2 -1
  17. package/dist/editor.js +2 -31
  18. package/dist/files.d.ts +2 -6
  19. package/dist/files.js +2 -33
  20. package/dist/globals.css +75 -3
  21. package/dist/hooks.d.ts +146 -148
  22. package/dist/hooks.js +222 -55
  23. package/dist/index-CKRaj1pM.d.ts +776 -0
  24. package/dist/index-CXX5IpH2.d.ts +2 -0
  25. package/dist/index-CesPlOyx.d.ts +367 -0
  26. package/dist/index-DPePq7c8.d.ts +1340 -0
  27. package/dist/index-DiyLLZuJ.d.ts +230 -0
  28. package/dist/index-DvmZLMT1.d.ts +30 -0
  29. package/dist/index-PRM636Ex.d.ts +86 -0
  30. package/dist/index.d.ts +21 -33
  31. package/dist/index.js +21 -701
  32. package/dist/info-panel-C9cqDzOL.js +594 -0
  33. package/dist/integrations.d.ts +133 -141
  34. package/dist/integrations.js +491 -673
  35. package/dist/markdown.d.ts +2 -1
  36. package/dist/markdown.js +2 -13
  37. package/dist/model-I-NIxeM-.js +1250 -0
  38. package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
  39. package/dist/model-brand-hMyDTUqB.js +624 -0
  40. package/dist/motion-DkyBRJb9.js +37 -0
  41. package/dist/openui.d.ts +2 -1
  42. package/dist/openui.js +2 -7
  43. package/dist/pages.d.ts +307 -300
  44. package/dist/pages.js +4404 -4181
  45. package/dist/primitives-Dh1vG6J3.js +113 -0
  46. package/dist/primitives.d.ts +2 -74
  47. package/dist/primitives.js +2 -204
  48. package/dist/provider-logo-DjqmConF.js +311 -0
  49. package/dist/run.d.ts +2 -1
  50. package/dist/run.js +2 -23
  51. package/dist/sdk-hooks.d.ts +3 -4
  52. package/dist/sdk-hooks.js +3 -31
  53. package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
  54. package/dist/stores.d.ts +14 -15
  55. package/dist/stores.js +13 -81
  56. package/dist/styles.css +75 -3
  57. package/dist/template-card-L-4gmjx2.d.ts +90 -0
  58. package/dist/terminal-CN9ZgHRt.js +197 -0
  59. package/dist/terminal.d.ts +60 -60
  60. package/dist/terminal.js +2 -10
  61. package/dist/tokens.css +51 -0
  62. package/dist/types.d.ts +4 -11
  63. package/dist/types.js +0 -1
  64. package/dist/use-pty-session-C8cE0adT.js +669 -0
  65. package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
  66. package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
  67. package/dist/utils-DzWbktWG.js +2 -0
  68. package/dist/utils.d.ts +2 -1
  69. package/dist/utils.js +2 -37
  70. package/dist/workbench-ztU44Jt2.js +8455 -0
  71. package/dist/workbench.d.ts +2 -310
  72. package/dist/workbench.js +2 -44
  73. package/dist/workflows.d.ts +341 -339
  74. package/dist/workflows.js +74 -109
  75. package/dist/workspace-B6AtI6ld.js +2510 -0
  76. package/dist/workspace.d.ts +2 -761
  77. package/dist/workspace.js +2 -51
  78. package/package.json +46 -40
  79. package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
  80. package/dist/chunk-2BUPSB7O.js +0 -0
  81. package/dist/chunk-3KBNPQQF.js +0 -2697
  82. package/dist/chunk-3LIXVEEE.js +0 -279
  83. package/dist/chunk-72UF2H5H.js +0 -707
  84. package/dist/chunk-AI25G5MX.js +0 -944
  85. package/dist/chunk-ANSC5AFC.js +0 -314
  86. package/dist/chunk-AZ3AWMTM.js +0 -8
  87. package/dist/chunk-BGVEHENC.js +0 -10
  88. package/dist/chunk-CZ7CSFO4.js +0 -39
  89. package/dist/chunk-DYXXYGS4.js +0 -4700
  90. package/dist/chunk-EI44GEQ5.js +0 -6
  91. package/dist/chunk-IACRDXER.js +0 -219
  92. package/dist/chunk-IOB2PW5Z.js +0 -55
  93. package/dist/chunk-M4K7ZGR2.js +0 -24
  94. package/dist/chunk-MYZAW4RX.js +0 -665
  95. package/dist/chunk-OMUIPJQZ.js +0 -555
  96. package/dist/chunk-RQZRXKMR.js +0 -398
  97. package/dist/chunk-SOKKTB7W.js +0 -230
  98. package/dist/chunk-TADGS4Q7.js +0 -1010
  99. package/dist/chunk-UCQNOUN3.js +0 -7964
  100. package/dist/chunk-ULPLRBMF.js +0 -34
  101. package/dist/chunk-WID73FPH.js +0 -89
  102. package/dist/chunk-X3YXP7FG.js +0 -38
  103. package/dist/logo-DiAjq759.d.ts +0 -15
  104. package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
  105. package/dist/ports-list-CDIvTNnX.d.ts +0 -17
  106. package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
  107. package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
@@ -0,0 +1,2510 @@
1
+ import { t as cn$1 } from "./utils-DzWbktWG.js";
2
+ import { t as MOTION_CONTROL } from "./motion-DkyBRJb9.js";
3
+ import { ArtifactPane, ArtifactPane as ArtifactPane$1, Badge, EmptyState, Input } from "@tangle-network/ui/primitives";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
6
+ import { focusField, focusRing, focusRingInset } from "@tangle-network/ui/utils";
7
+ import { Markdown } from "@tangle-network/ui/markdown";
8
+ import { Activity, AlertCircle, AlertTriangle, ArrowLeft, Binary, Bot, Boxes, CheckCircle, ChevronDown, ChevronRight, ChevronUp, FileCode2, FileText, FolderTree, LayoutPanelTop, Loader2, LoaderCircle, MessageSquareText, PanelBottomClose, PanelBottomOpen, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Plus, RefreshCw, Search, Settings, Shield, ShieldCheck, Sparkles, Terminal, TerminalSquare, Upload, Wifi, X, XCircle, Zap } from "lucide-react";
9
+ import { FileArtifactPane, FileTree, filterFileTree } from "@tangle-network/ui/files";
10
+ import { useActiveSessions, useNavbarSessions, useProjectActivity, useTotalRunningSessions } from "@tangle-network/ui/stores";
11
+ import { ChatContainer } from "@tangle-network/ui/chat";
12
+ import { OpenUIArtifactRenderer } from "@tangle-network/ui/openui";
13
+ //#region src/workspace/workspace-pane-header.tsx
14
+ /**
15
+ * The shared 56px header row for top-level workspace panes and drawers.
16
+ * Bottom runtime panels intentionally use their own compact header.
17
+ */
18
+ function WorkspacePaneHeader({ className, ...props }) {
19
+ return /* @__PURE__ */ jsx("div", {
20
+ className: cn$1("flex h-14 shrink-0 items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-high px-3", className),
21
+ ...props
22
+ });
23
+ }
24
+ //#endregion
25
+ //#region src/workspace/workspace-layout.tsx
26
+ /**
27
+ * WorkspaceLayout — reusable sandbox shell with desktop resizable panels and
28
+ * mobile overlay drawers.
29
+ *
30
+ * Left: navigation / files / context
31
+ * Center: chat, timeline, or primary workspace
32
+ * Right: artifacts / previews / inspectors
33
+ * Bottom: optional runtime panel
34
+ */
35
+ const DESKTOP_BREAKPOINT = "(min-width: 1024px)";
36
+ function clamp(value, min, max) {
37
+ return Math.min(Math.max(value, min), max);
38
+ }
39
+ /**
40
+ * True while a keystroke belongs to a text field. `isContentEditable` is
41
+ * missing from jsdom, so the attribute is checked too — through `closest`,
42
+ * because the target inside an editable region is usually a descendant.
43
+ */
44
+ function isEditableTarget(target) {
45
+ if (!(target instanceof HTMLElement)) return false;
46
+ if (target.isContentEditable) return true;
47
+ const tag = target.tagName;
48
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
49
+ const editable = target.closest("[contenteditable]");
50
+ return editable !== null && editable.getAttribute("contenteditable") !== "false";
51
+ }
52
+ function readStoredLayout(key) {
53
+ if (typeof window === "undefined") return null;
54
+ try {
55
+ const raw = window.localStorage.getItem(key);
56
+ if (!raw) return null;
57
+ const parsed = JSON.parse(raw);
58
+ return parsed && typeof parsed === "object" ? parsed : null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ function useDesktopMediaQuery(query) {
64
+ const [matches, setMatches] = useState(() => {
65
+ if (typeof window === "undefined") return true;
66
+ return window.matchMedia(query).matches;
67
+ });
68
+ useEffect(() => {
69
+ if (typeof window === "undefined") return;
70
+ const media = window.matchMedia(query);
71
+ const handleChange = (event) => {
72
+ setMatches(event.matches);
73
+ };
74
+ setMatches(media.matches);
75
+ media.addEventListener("change", handleChange);
76
+ return () => media.removeEventListener("change", handleChange);
77
+ }, [query]);
78
+ return matches;
79
+ }
80
+ function ResizeHandle({ label, onDragStart, onStep, className }) {
81
+ const handlePointerDown = (event) => {
82
+ event.preventDefault();
83
+ onDragStart(event.clientX);
84
+ };
85
+ const handleKeyDown = (event) => {
86
+ if (event.key === "ArrowLeft") {
87
+ event.preventDefault();
88
+ onStep(-24);
89
+ }
90
+ if (event.key === "ArrowRight") {
91
+ event.preventDefault();
92
+ onStep(24);
93
+ }
94
+ };
95
+ return /* @__PURE__ */ jsxs("button", {
96
+ type: "button",
97
+ "aria-label": label,
98
+ role: "separator",
99
+ "aria-orientation": "vertical",
100
+ onPointerDown: handlePointerDown,
101
+ onKeyDown: handleKeyDown,
102
+ className: cn$1("relative hidden w-3 shrink-0 cursor-col-resize lg:flex", "items-stretch justify-center bg-transparent touch-none", focusRing, className),
103
+ children: [/* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-[var(--md3-outline-variant)] transition-colors" }), /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-1/2 w-[3px] -translate-x-1/2 rounded-full bg-transparent hover:bg-primary/30 focus-visible:bg-primary/40" })]
104
+ });
105
+ }
106
+ function HorizontalResizeHandle({ label, onDragStart, onStep, className }) {
107
+ const handlePointerDown = (event) => {
108
+ event.preventDefault();
109
+ onDragStart(event.clientY);
110
+ };
111
+ const handleKeyDown = (event) => {
112
+ if (event.key === "ArrowUp") {
113
+ event.preventDefault();
114
+ onStep(24);
115
+ }
116
+ if (event.key === "ArrowDown") {
117
+ event.preventDefault();
118
+ onStep(-24);
119
+ }
120
+ };
121
+ return /* @__PURE__ */ jsxs("button", {
122
+ type: "button",
123
+ "aria-label": label,
124
+ role: "separator",
125
+ "aria-orientation": "horizontal",
126
+ onPointerDown: handlePointerDown,
127
+ onKeyDown: handleKeyDown,
128
+ className: cn$1("relative hidden h-3 shrink-0 cursor-row-resize lg:flex", "items-center justify-center bg-transparent touch-none w-full", focusRing, className),
129
+ children: [/* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-[var(--md3-outline-variant)] transition-colors" }), /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 top-1/2 h-[3px] -translate-y-1/2 rounded-full bg-transparent hover:bg-primary/30 focus-visible:bg-primary/40" })]
130
+ });
131
+ }
132
+ function MobileDrawer({ side, title, header, onClose, children }) {
133
+ return /* @__PURE__ */ jsxs("div", {
134
+ className: "fixed inset-0 z-50 flex lg:hidden",
135
+ "aria-modal": "true",
136
+ role: "dialog",
137
+ "aria-label": title,
138
+ children: [/* @__PURE__ */ jsx("button", {
139
+ type: "button",
140
+ "aria-label": `Close ${title}`,
141
+ onClick: onClose,
142
+ className: "absolute inset-0 bg-black/80 backdrop-blur-sm"
143
+ }), /* @__PURE__ */ jsxs("aside", {
144
+ className: cn$1("relative flex h-full w-[min(88vw,24rem)] flex-col border-[var(--md3-outline-variant)] bg-surface-container-highest shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]", side === "left" ? "border-r" : "ml-auto border-l"),
145
+ children: [/* @__PURE__ */ jsxs(WorkspacePaneHeader, {
146
+ className: "justify-between gap-3",
147
+ children: [/* @__PURE__ */ jsx("div", {
148
+ className: "min-w-0 flex-1",
149
+ children: header ?? /* @__PURE__ */ jsx("span", {
150
+ className: "text-[13px] font-medium text-foreground",
151
+ children: title
152
+ })
153
+ }), /* @__PURE__ */ jsx("button", {
154
+ type: "button",
155
+ "aria-label": `Close ${title}`,
156
+ onClick: onClose,
157
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
158
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
159
+ })]
160
+ }), /* @__PURE__ */ jsx("div", {
161
+ className: "flex-1 overflow-auto",
162
+ children
163
+ })]
164
+ })]
165
+ });
166
+ }
167
+ function WorkspaceLayout({ left, leftHeader, center, centerHeader, centerFooter, right, rightHeader, bottom, bottomHeader, defaultLeftOpen = true, defaultRightOpen = false, defaultBottomOpen = false, defaultLeftWidth = 280, defaultRightWidth = 480, defaultBottomHeight = 224, minLeftWidth = 220, maxLeftWidth = 420, minRightWidth = 320, maxRightWidth = 720, minCenterWidth = 400, minBottomHeight = 100, maxBottomHeight = 500, persistenceKey, resizable = true, theme, density = "comfortable", leftLabel = "Left workspace panel", rightLabel = "Right workspace panel", bottomLabel = "Bottom runtime panel", leftOpen: leftOpenProp, onLeftOpenChange, rightOpen: rightOpenProp, onRightOpenChange, keyboardShortcuts = false, leftCollapsedControl, leftContentClassName, rightContentClassName, centerHeaderVisibility = "always", className }) {
168
+ const desktop = useDesktopMediaQuery(DESKTOP_BREAKPOINT);
169
+ const dragStateRef = useRef(null);
170
+ const storedLayout = useMemo(() => persistenceKey ? readStoredLayout(persistenceKey) : null, [persistenceKey]);
171
+ const [uncontrolledLeftOpen, setUncontrolledLeftOpen] = useState(storedLayout?.leftOpen ?? defaultLeftOpen);
172
+ const [uncontrolledRightOpen, setUncontrolledRightOpen] = useState(storedLayout?.rightOpen ?? defaultRightOpen);
173
+ const leftControlled = leftOpenProp !== void 0;
174
+ const rightControlled = rightOpenProp !== void 0;
175
+ const leftOpen = leftControlled ? leftOpenProp : uncontrolledLeftOpen;
176
+ const rightOpen = rightControlled ? rightOpenProp : uncontrolledRightOpen;
177
+ const setLeftOpen = useCallback((open) => {
178
+ if (!leftControlled) setUncontrolledLeftOpen(open);
179
+ onLeftOpenChange?.(open);
180
+ }, [leftControlled, onLeftOpenChange]);
181
+ const setRightOpen = useCallback((open) => {
182
+ if (!rightControlled) setUncontrolledRightOpen(open);
183
+ onRightOpenChange?.(open);
184
+ }, [rightControlled, onRightOpenChange]);
185
+ const hasLeft = Boolean(left);
186
+ const hasRight = Boolean(right);
187
+ const [bottomOpen, setBottomOpen] = useState(storedLayout?.bottomOpen ?? defaultBottomOpen);
188
+ const [leftWidth, setLeftWidth] = useState(clamp(storedLayout?.leftWidth ?? defaultLeftWidth, minLeftWidth, maxLeftWidth));
189
+ const [rightWidth, setRightWidth] = useState(clamp(storedLayout?.rightWidth ?? defaultRightWidth, minRightWidth, maxRightWidth));
190
+ const [bottomHeight, setBottomHeight] = useState(clamp(storedLayout?.bottomHeight ?? defaultBottomHeight, minBottomHeight, maxBottomHeight));
191
+ const showCenterHeader = centerHeaderVisibility === "auto" ? Boolean(centerHeader) || hasLeft && !leftOpen || hasRight && !rightOpen || Boolean(bottom) && !bottomOpen : Boolean(centerHeader || left || right || bottom);
192
+ useEffect(() => {
193
+ if (!persistenceKey || typeof window === "undefined") return;
194
+ const payload = {
195
+ leftOpen,
196
+ rightOpen,
197
+ bottomOpen,
198
+ leftWidth,
199
+ rightWidth,
200
+ bottomHeight
201
+ };
202
+ window.localStorage.setItem(persistenceKey, JSON.stringify(payload));
203
+ }, [
204
+ bottomHeight,
205
+ bottomOpen,
206
+ leftOpen,
207
+ leftWidth,
208
+ persistenceKey,
209
+ rightOpen,
210
+ rightWidth
211
+ ]);
212
+ useEffect(() => {
213
+ if (!keyboardShortcuts || typeof window === "undefined") return;
214
+ const handleKeyDown = (event) => {
215
+ if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
216
+ if (isEditableTarget(event.target)) return;
217
+ const key = event.key.toLowerCase();
218
+ if (key === "b" && hasLeft) {
219
+ event.preventDefault();
220
+ setLeftOpen(!leftOpen);
221
+ } else if (key === "e" && hasRight) {
222
+ event.preventDefault();
223
+ setRightOpen(!rightOpen);
224
+ }
225
+ };
226
+ window.addEventListener("keydown", handleKeyDown);
227
+ return () => window.removeEventListener("keydown", handleKeyDown);
228
+ }, [
229
+ hasLeft,
230
+ hasRight,
231
+ keyboardShortcuts,
232
+ leftOpen,
233
+ rightOpen,
234
+ setLeftOpen,
235
+ setRightOpen
236
+ ]);
237
+ useEffect(() => {
238
+ if (!desktop) return;
239
+ const handlePointerMove = (event) => {
240
+ const dragState = dragStateRef.current;
241
+ if (!dragState) return;
242
+ if (dragState.side === "bottom") {
243
+ const delta = dragState.pointerStartY - event.clientY;
244
+ setBottomHeight(clamp(dragState.heightStart + delta, minBottomHeight, maxBottomHeight));
245
+ } else if (dragState.side === "left") {
246
+ const delta = event.clientX - dragState.pointerStartX;
247
+ setLeftWidth(clamp(dragState.widthStart + delta, minLeftWidth, maxLeftWidth));
248
+ } else {
249
+ const delta = dragState.pointerStartX - event.clientX;
250
+ setRightWidth(clamp(dragState.widthStart + delta, minRightWidth, maxRightWidth));
251
+ }
252
+ };
253
+ const handlePointerUp = () => {
254
+ dragStateRef.current = null;
255
+ };
256
+ window.addEventListener("pointermove", handlePointerMove);
257
+ window.addEventListener("pointerup", handlePointerUp);
258
+ window.addEventListener("pointercancel", handlePointerUp);
259
+ return () => {
260
+ window.removeEventListener("pointermove", handlePointerMove);
261
+ window.removeEventListener("pointerup", handlePointerUp);
262
+ window.removeEventListener("pointercancel", handlePointerUp);
263
+ };
264
+ }, [
265
+ desktop,
266
+ maxBottomHeight,
267
+ maxLeftWidth,
268
+ maxRightWidth,
269
+ minBottomHeight,
270
+ minLeftWidth,
271
+ minRightWidth
272
+ ]);
273
+ const shellRef = useRef(null);
274
+ const [shellWidth, setShellWidth] = useState(null);
275
+ useEffect(() => {
276
+ const shell = shellRef.current;
277
+ if (!shell || typeof ResizeObserver === "undefined") return;
278
+ const observer = new ResizeObserver((entries) => {
279
+ const width = entries[0]?.contentRect.width;
280
+ if (typeof width === "number") setShellWidth(width);
281
+ });
282
+ observer.observe(shell);
283
+ return () => observer.disconnect();
284
+ }, []);
285
+ const { leftPx, rightPx } = useMemo(() => {
286
+ const leftShown = desktop && hasLeft && leftOpen;
287
+ const rightShown = desktop && hasRight && rightOpen;
288
+ let leftPx = leftWidth;
289
+ let rightPx = rightWidth;
290
+ if (shellWidth === null) return {
291
+ leftPx,
292
+ rightPx
293
+ };
294
+ const room = () => shellWidth - (leftShown ? leftPx : 0) - (rightShown ? rightPx : 0);
295
+ if (rightShown && room() < minCenterWidth) rightPx = Math.max(minRightWidth, rightPx - (minCenterWidth - room()));
296
+ if (leftShown && room() < minCenterWidth) leftPx = Math.max(minLeftWidth, leftPx - (minCenterWidth - room()));
297
+ return {
298
+ leftPx,
299
+ rightPx
300
+ };
301
+ }, [
302
+ desktop,
303
+ hasLeft,
304
+ hasRight,
305
+ leftOpen,
306
+ leftWidth,
307
+ minCenterWidth,
308
+ minLeftWidth,
309
+ minRightWidth,
310
+ rightOpen,
311
+ rightWidth,
312
+ shellWidth
313
+ ]);
314
+ const leftStyle = useMemo(() => ({ width: `${leftPx}px` }), [leftPx]);
315
+ const rightStyle = useMemo(() => ({ width: `${rightPx}px` }), [rightPx]);
316
+ const startResize = (side, pointerStartX) => {
317
+ dragStateRef.current = {
318
+ side,
319
+ pointerStartX,
320
+ pointerStartY: 0,
321
+ widthStart: side === "left" ? leftWidth : rightWidth,
322
+ heightStart: 0
323
+ };
324
+ };
325
+ const startBottomResize = (pointerStartY) => {
326
+ dragStateRef.current = {
327
+ side: "bottom",
328
+ pointerStartX: 0,
329
+ pointerStartY,
330
+ widthStart: 0,
331
+ heightStart: bottomHeight
332
+ };
333
+ };
334
+ const stepLeftWidth = (delta) => {
335
+ setLeftWidth((current) => clamp(current + delta, minLeftWidth, maxLeftWidth));
336
+ };
337
+ const stepRightWidth = (delta) => {
338
+ setRightWidth((current) => clamp(current + delta, minRightWidth, maxRightWidth));
339
+ };
340
+ const stepBottomHeight = (delta) => {
341
+ setBottomHeight((current) => clamp(current + delta, minBottomHeight, maxBottomHeight));
342
+ };
343
+ return /* @__PURE__ */ jsxs("div", {
344
+ ...theme ? {
345
+ "data-sandbox-ui": "true",
346
+ "data-sandbox-theme": theme
347
+ } : {},
348
+ "data-density": density,
349
+ className: cn$1("flex h-screen flex-col overflow-hidden bg-[var(--bg-root)] text-foreground font-sans", className),
350
+ children: [
351
+ /* @__PURE__ */ jsxs("div", {
352
+ ref: shellRef,
353
+ className: "flex min-h-0 flex-1",
354
+ children: [
355
+ desktop && left && leftOpen && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("aside", {
356
+ "aria-label": leftLabel,
357
+ style: leftStyle,
358
+ className: "hidden shrink-0 border-r border-[var(--md3-outline-variant)] bg-surface-container-low lg:flex lg:flex-col",
359
+ children: [leftHeader && /* @__PURE__ */ jsxs(WorkspacePaneHeader, {
360
+ className: "justify-between gap-2",
361
+ children: [/* @__PURE__ */ jsx("div", {
362
+ className: "min-w-0 flex-1",
363
+ children: leftHeader
364
+ }), /* @__PURE__ */ jsx("button", {
365
+ type: "button",
366
+ "aria-label": "Collapse left panel",
367
+ onClick: () => setLeftOpen(false),
368
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
369
+ children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
370
+ })]
371
+ }), /* @__PURE__ */ jsx("div", {
372
+ className: cn$1("min-h-0 flex-1 overflow-auto py-1", leftContentClassName),
373
+ children: left
374
+ })]
375
+ }), resizable && /* @__PURE__ */ jsx(ResizeHandle, {
376
+ label: "Resize left panel",
377
+ onDragStart: (clientX) => startResize("left", clientX),
378
+ onStep: stepLeftWidth
379
+ })] }),
380
+ /* @__PURE__ */ jsxs("main", {
381
+ className: "flex min-w-0 flex-1 flex-col",
382
+ children: [
383
+ showCenterHeader && /* @__PURE__ */ jsxs(WorkspacePaneHeader, {
384
+ className: "gap-2",
385
+ children: [
386
+ left && !leftOpen && (leftCollapsedControl ?? /* @__PURE__ */ jsx("button", {
387
+ type: "button",
388
+ "aria-label": "Open left panel",
389
+ onClick: () => setLeftOpen(true),
390
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
391
+ children: /* @__PURE__ */ jsx(PanelLeftOpen, { className: "h-4 w-4" })
392
+ })),
393
+ /* @__PURE__ */ jsx("div", {
394
+ className: "min-w-0 flex-1",
395
+ children: centerHeader
396
+ }),
397
+ bottom && !bottomOpen && /* @__PURE__ */ jsx("button", {
398
+ type: "button",
399
+ "aria-label": "Open bottom panel",
400
+ onClick: () => setBottomOpen(true),
401
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
402
+ children: /* @__PURE__ */ jsx(PanelBottomOpen, { className: "h-4 w-4" })
403
+ }),
404
+ right && !rightOpen && /* @__PURE__ */ jsx("button", {
405
+ type: "button",
406
+ "aria-label": "Open right panel",
407
+ onClick: () => setRightOpen(true),
408
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
409
+ children: /* @__PURE__ */ jsx(PanelRightOpen, { className: "h-4 w-4" })
410
+ })
411
+ ]
412
+ }),
413
+ /* @__PURE__ */ jsx("div", {
414
+ className: "min-h-0 flex-1 overflow-auto",
415
+ children: center
416
+ }),
417
+ bottom && bottomOpen && /* @__PURE__ */ jsxs(Fragment, { children: [resizable && /* @__PURE__ */ jsx(HorizontalResizeHandle, {
418
+ label: "Resize bottom panel",
419
+ onDragStart: startBottomResize,
420
+ onStep: stepBottomHeight
421
+ }), /* @__PURE__ */ jsx("section", {
422
+ "aria-label": bottomLabel,
423
+ className: "border-t border-[var(--md3-outline-variant)] bg-surface-container shrink-0",
424
+ style: { height: `${bottomHeight}px` },
425
+ children: /* @__PURE__ */ jsxs("div", {
426
+ className: "flex h-full flex-col",
427
+ children: [/* @__PURE__ */ jsxs("div", {
428
+ className: "flex items-center justify-between gap-2 border-b border-[var(--md3-outline-variant)] px-3 py-1.5 bg-surface-container-high shrink-0",
429
+ children: [/* @__PURE__ */ jsx("div", {
430
+ className: "min-w-0 flex-1",
431
+ children: bottomHeader ?? /* @__PURE__ */ jsx("span", {
432
+ className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground",
433
+ children: "Runtime"
434
+ })
435
+ }), /* @__PURE__ */ jsx("button", {
436
+ type: "button",
437
+ "aria-label": "Collapse bottom panel",
438
+ onClick: () => setBottomOpen(false),
439
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
440
+ children: /* @__PURE__ */ jsx(PanelBottomClose, { className: "h-4 w-4" })
441
+ })]
442
+ }), /* @__PURE__ */ jsx("div", {
443
+ className: "min-h-0 flex-1 overflow-auto",
444
+ children: bottom
445
+ })]
446
+ })
447
+ })] }),
448
+ centerFooter && /* @__PURE__ */ jsx("div", {
449
+ className: "shrink-0 border-t border-[var(--md3-outline-variant)] bg-surface-container-low",
450
+ children: centerFooter
451
+ })
452
+ ]
453
+ }),
454
+ desktop && right && rightOpen && /* @__PURE__ */ jsxs(Fragment, { children: [resizable && /* @__PURE__ */ jsx(ResizeHandle, {
455
+ label: "Resize right panel",
456
+ onDragStart: (clientX) => startResize("right", clientX),
457
+ onStep: stepRightWidth
458
+ }), /* @__PURE__ */ jsxs("aside", {
459
+ "aria-label": rightLabel,
460
+ style: rightStyle,
461
+ className: "hidden shrink-0 border-l border-[var(--md3-outline-variant)] bg-surface-container-low lg:flex lg:flex-col",
462
+ children: [/* @__PURE__ */ jsxs(WorkspacePaneHeader, {
463
+ className: "justify-between gap-2",
464
+ children: [/* @__PURE__ */ jsx("div", {
465
+ className: "min-w-0 flex-1",
466
+ children: rightHeader ?? /* @__PURE__ */ jsx("span", {
467
+ className: "text-[13px] font-medium text-foreground",
468
+ children: "Artifacts"
469
+ })
470
+ }), /* @__PURE__ */ jsx("button", {
471
+ type: "button",
472
+ "aria-label": "Collapse right panel",
473
+ onClick: () => setRightOpen(false),
474
+ className: `rounded-[2px] p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
475
+ children: /* @__PURE__ */ jsx(PanelRightClose, { className: "h-4 w-4" })
476
+ })]
477
+ }), /* @__PURE__ */ jsx("div", {
478
+ className: cn$1("min-h-0 flex-1 overflow-auto", rightContentClassName),
479
+ children: right
480
+ })]
481
+ })] })
482
+ ]
483
+ }),
484
+ !desktop && left && leftOpen && /* @__PURE__ */ jsx(MobileDrawer, {
485
+ side: "left",
486
+ title: leftLabel,
487
+ header: leftHeader,
488
+ onClose: () => setLeftOpen(false),
489
+ children: left
490
+ }),
491
+ !desktop && right && rightOpen && /* @__PURE__ */ jsx(MobileDrawer, {
492
+ side: "right",
493
+ title: rightLabel,
494
+ header: rightHeader,
495
+ onClose: () => setRightOpen(false),
496
+ children: right
497
+ })
498
+ ]
499
+ });
500
+ }
501
+ //#endregion
502
+ //#region src/workspace/directory-pane.tsx
503
+ function countNodes(node) {
504
+ if (node.type === "file") return {
505
+ files: 1,
506
+ directories: 0
507
+ };
508
+ return (node.children ?? []).reduce((totals, child) => {
509
+ const counts = countNodes(child);
510
+ return {
511
+ files: totals.files + counts.files,
512
+ directories: totals.directories + counts.directories
513
+ };
514
+ }, {
515
+ files: 0,
516
+ directories: 1
517
+ });
518
+ }
519
+ function filterTree(node, query) {
520
+ if (!query) return node;
521
+ const normalized = query.trim().toLowerCase();
522
+ const matches = node.name.toLowerCase().includes(normalized) || node.path.toLowerCase().includes(normalized);
523
+ if (node.type === "file") return matches ? node : null;
524
+ const filteredChildren = (node.children ?? []).map((child) => filterTree(child, normalized)).filter((child) => child !== null);
525
+ if (matches || filteredChildren.length > 0) return {
526
+ ...node,
527
+ children: filteredChildren
528
+ };
529
+ return null;
530
+ }
531
+ /**
532
+ * DirectoryPane — searchable file-system navigation surface for sandbox
533
+ * sessions.
534
+ */
535
+ function DirectoryPane({ root, selectedPath, onSelect, onRefresh, onUpload, title = "Directory", subtitle, defaultExpanded = true, searchPlaceholder = "Search files and folders…", visibility, className }) {
536
+ const [query, setQuery] = useState("");
537
+ const visibleRoot = useMemo(() => filterFileTree(root, visibility), [root, visibility]);
538
+ const filteredRoot = useMemo(() => visibleRoot ? filterTree(visibleRoot, query) : null, [query, visibleRoot]);
539
+ const counts = useMemo(() => visibleRoot ? countNodes(visibleRoot) : {
540
+ files: 0,
541
+ directories: 0
542
+ }, [visibleRoot]);
543
+ return /* @__PURE__ */ jsx(ArtifactPane, {
544
+ eyebrow: "Workspace",
545
+ title,
546
+ subtitle: subtitle ?? root.path,
547
+ className,
548
+ toolbar: /* @__PURE__ */ jsxs("div", {
549
+ className: "flex items-center gap-2",
550
+ children: [
551
+ /* @__PURE__ */ jsx("div", {
552
+ className: "flex-1",
553
+ children: /* @__PURE__ */ jsx(Input, {
554
+ value: query,
555
+ onChange: (event) => setQuery(event.target.value),
556
+ placeholder: searchPlaceholder,
557
+ className: "h-7 text-xs rounded-[2px]"
558
+ })
559
+ }),
560
+ onRefresh && /* @__PURE__ */ jsx("button", {
561
+ type: "button",
562
+ "aria-label": "Refresh directory",
563
+ onClick: onRefresh,
564
+ className: `rounded-[2px] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
565
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
566
+ }),
567
+ onUpload && /* @__PURE__ */ jsx("button", {
568
+ type: "button",
569
+ "aria-label": "Upload files",
570
+ onClick: onUpload,
571
+ className: `rounded-[2px] p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground ${focusRing}`,
572
+ children: /* @__PURE__ */ jsx(Upload, { className: "h-4 w-4" })
573
+ })
574
+ ]
575
+ }),
576
+ footer: /* @__PURE__ */ jsxs("div", {
577
+ className: "flex items-center justify-between text-xs text-muted-foreground",
578
+ children: [/* @__PURE__ */ jsxs("span", { children: [counts.files, " files"] }), /* @__PURE__ */ jsxs("span", { children: [Math.max(counts.directories - 1, 0), " folders"] })]
579
+ }),
580
+ emptyState: /* @__PURE__ */ jsx(EmptyState, {
581
+ icon: /* @__PURE__ */ jsx(Search, { className: "h-8 w-8" }),
582
+ title: "No matching files",
583
+ description: "Try a different search or clear the current filter."
584
+ }),
585
+ children: filteredRoot ? /* @__PURE__ */ jsx("div", {
586
+ className: "py-2",
587
+ children: /* @__PURE__ */ jsx(FileTree, {
588
+ root: filteredRoot,
589
+ selectedPath,
590
+ onSelect,
591
+ defaultExpanded,
592
+ visibility
593
+ })
594
+ }) : null
595
+ });
596
+ }
597
+ //#endregion
598
+ //#region src/workspace/status-banner.tsx
599
+ /**
600
+ * StatusBanner — full-width notification banner for connection/provisioning states.
601
+ */
602
+ const BANNER_STYLES = {
603
+ provisioning: {
604
+ bg: "bg-primary/5",
605
+ border: "border-primary/20",
606
+ text: "text-primary",
607
+ icon: Loader2
608
+ },
609
+ connecting: {
610
+ bg: "bg-[var(--code-number)]/5",
611
+ border: "border-[var(--code-number)]/20",
612
+ text: "text-[var(--code-number)]",
613
+ icon: Wifi
614
+ },
615
+ error: {
616
+ bg: "bg-[var(--code-error)]/5",
617
+ border: "border-[var(--code-error)]/20",
618
+ text: "text-[var(--code-error)]",
619
+ icon: AlertCircle
620
+ },
621
+ warning: {
622
+ bg: "bg-[var(--surface-warning-bg)]",
623
+ border: "border-[var(--surface-warning-border)]",
624
+ text: "text-[var(--surface-warning-text)]",
625
+ icon: AlertTriangle
626
+ },
627
+ success: {
628
+ bg: "bg-[var(--code-success)]/5",
629
+ border: "border-[var(--code-success)]/20",
630
+ text: "text-[var(--code-success)]",
631
+ icon: CheckCircle
632
+ },
633
+ info: {
634
+ bg: "bg-surface-container-high",
635
+ border: "border-[var(--md3-outline-variant)]",
636
+ text: "text-muted-foreground",
637
+ icon: AlertCircle
638
+ }
639
+ };
640
+ function StatusBanner({ type, message, detail, onDismiss, className }) {
641
+ const style = BANNER_STYLES[type];
642
+ const Icon = style.icon;
643
+ const isAnimated = type === "provisioning" || type === "connecting";
644
+ const role = type === "error" ? "alert" : "status";
645
+ return /* @__PURE__ */ jsxs("div", {
646
+ className: cn$1("flex items-center gap-2.5 px-4 py-2 border-b font-sans text-sm", style.bg, style.border, className),
647
+ children: [/* @__PURE__ */ jsxs("div", {
648
+ role,
649
+ className: "flex min-w-0 items-center gap-2.5",
650
+ children: [
651
+ /* @__PURE__ */ jsx(Icon, {
652
+ className: cn$1("h-4 w-4 shrink-0", style.text, isAnimated && "animate-spin"),
653
+ ...isAnimated ? { "data-motion": "essential" } : {}
654
+ }),
655
+ /* @__PURE__ */ jsx("span", {
656
+ className: "font-medium text-foreground",
657
+ children: message
658
+ }),
659
+ detail && /* @__PURE__ */ jsx("span", {
660
+ className: "font-mono text-xs text-muted-foreground tabular-nums",
661
+ children: detail
662
+ })
663
+ ]
664
+ }), onDismiss && /* @__PURE__ */ jsx("button", {
665
+ onClick: onDismiss,
666
+ className: "ml-auto font-sans text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
667
+ children: "Dismiss"
668
+ })]
669
+ });
670
+ }
671
+ //#endregion
672
+ //#region src/workspace/status-bar.tsx
673
+ /**
674
+ * StatusBar — compact bottom bar: connection state, file/artifact context
675
+ * badges, credits. The active model lives in the input box, not here.
676
+ */
677
+ const STATUS_LABELS = {
678
+ connected: {
679
+ label: "Connected",
680
+ color: "bg-[var(--code-success)]"
681
+ },
682
+ connecting: {
683
+ label: "Connecting…",
684
+ color: "bg-[var(--code-number)]"
685
+ },
686
+ disconnected: {
687
+ label: "Disconnected",
688
+ color: "bg-[var(--code-error)]"
689
+ },
690
+ provisioning: {
691
+ label: "Provisioning…",
692
+ color: "bg-[var(--code-number)]"
693
+ }
694
+ };
695
+ function StatusBar({ credits, contextBadges = [], onRemoveBadge, status = "connected", className }) {
696
+ const statusInfo = STATUS_LABELS[status];
697
+ return /* @__PURE__ */ jsxs("div", {
698
+ className: cn$1("flex flex-wrap items-center gap-3 px-3 py-1 border-t border-[var(--md3-outline-variant)] bg-surface-container-high font-sans text-[12px]", className),
699
+ children: [
700
+ /* @__PURE__ */ jsxs("span", {
701
+ className: "inline-flex items-center gap-1.5 text-muted-foreground",
702
+ children: [/* @__PURE__ */ jsx("span", { className: cn$1("w-1.5 h-1.5 rounded-full", statusInfo.color) }), statusInfo.label]
703
+ }),
704
+ contextBadges.map((badge) => /* @__PURE__ */ jsxs("span", {
705
+ className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-[2px] border border-[var(--md3-outline-variant)] text-foreground bg-[var(--border-accent)]/5",
706
+ children: [
707
+ /* @__PURE__ */ jsx(FileText, { className: "h-3 w-3" }),
708
+ badge.label,
709
+ badge.count !== void 0 && /* @__PURE__ */ jsx("span", {
710
+ className: "text-muted-foreground",
711
+ children: badge.count
712
+ }),
713
+ onRemoveBadge && /* @__PURE__ */ jsx("button", {
714
+ type: "button",
715
+ "aria-label": `Remove ${badge.label} from context`,
716
+ onClick: () => onRemoveBadge(badge.id),
717
+ className: "hover:text-foreground transition-colors",
718
+ children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
719
+ })
720
+ ]
721
+ }, badge.id)),
722
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
723
+ credits !== void 0 && /* @__PURE__ */ jsxs("span", {
724
+ className: "inline-flex items-center gap-1 text-muted-foreground",
725
+ children: [
726
+ /* @__PURE__ */ jsx(Zap, { className: "h-3 w-3 shrink-0" }),
727
+ /* @__PURE__ */ jsx("span", {
728
+ className: "font-mono tabular-nums",
729
+ children: credits.toLocaleString()
730
+ }),
731
+ /* @__PURE__ */ jsx("span", { children: "credits" })
732
+ ]
733
+ })
734
+ ]
735
+ });
736
+ }
737
+ //#endregion
738
+ //#region src/workspace/terminal-panel.tsx
739
+ /**
740
+ * Terminal — read-only terminal output panel.
741
+ *
742
+ * Shows agent's bash command output with basic ANSI color support.
743
+ * Collapsible, auto-scrolls to bottom.
744
+ */
745
+ const LINE_COLORS = {
746
+ command: "text-[var(--code-function)]",
747
+ stdout: "text-foreground",
748
+ stderr: "text-[var(--code-error)]",
749
+ system: "text-muted-foreground"
750
+ };
751
+ const LINE_PREFIXES = {
752
+ command: "$ ",
753
+ stdout: "",
754
+ stderr: "",
755
+ system: "# "
756
+ };
757
+ function TerminalPanel({ lines, title = "Terminal", isCollapsed = false, onToggle, onClose, maxHeight = 200, className }) {
758
+ const scrollRef = useRef(null);
759
+ const contentId = useId();
760
+ useEffect(() => {
761
+ if (!isCollapsed && scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
762
+ }, [lines, isCollapsed]);
763
+ return /* @__PURE__ */ jsxs("div", {
764
+ className: cn$1("border-t border-[var(--md3-outline-variant)] bg-surface-container", className),
765
+ children: [/* @__PURE__ */ jsxs("div", {
766
+ className: "flex items-center px-1",
767
+ children: [/* @__PURE__ */ jsxs("button", {
768
+ type: "button",
769
+ onClick: onToggle,
770
+ disabled: !onToggle,
771
+ "aria-expanded": !isCollapsed,
772
+ "aria-controls": contentId,
773
+ className: `flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-muted-foreground transition-colors hover:text-foreground disabled:cursor-default ${focusRing}`,
774
+ children: [
775
+ /* @__PURE__ */ jsx(Terminal, {
776
+ className: "h-3.5 w-3.5",
777
+ "aria-hidden": "true"
778
+ }),
779
+ /* @__PURE__ */ jsx("span", {
780
+ className: "font-medium",
781
+ children: title
782
+ }),
783
+ lines.length > 0 && /* @__PURE__ */ jsx("span", {
784
+ className: "rounded-full bg-surface-container-high px-1.5 py-0.5 text-[10px] tabular-nums",
785
+ children: lines.length
786
+ }),
787
+ /* @__PURE__ */ jsx("span", { className: "flex-1" }),
788
+ isCollapsed ? /* @__PURE__ */ jsx(ChevronUp, {
789
+ className: "h-3 w-3",
790
+ "aria-hidden": "true"
791
+ }) : /* @__PURE__ */ jsx(ChevronDown, {
792
+ className: "h-3 w-3",
793
+ "aria-hidden": "true"
794
+ })
795
+ ]
796
+ }), onClose && /* @__PURE__ */ jsx("button", {
797
+ type: "button",
798
+ "aria-label": `Close ${title}`,
799
+ onClick: onClose,
800
+ className: `rounded p-1.5 text-muted-foreground transition-colors hover:text-foreground ${focusRing}`,
801
+ children: /* @__PURE__ */ jsx(X, {
802
+ className: "h-3 w-3",
803
+ "aria-hidden": "true"
804
+ })
805
+ })]
806
+ }), /* @__PURE__ */ jsxs("div", {
807
+ id: contentId,
808
+ hidden: isCollapsed,
809
+ ref: scrollRef,
810
+ className: "overflow-auto px-3 pb-2 font-mono text-xs leading-[1.6] bg-surface-container-lowest",
811
+ style: { maxHeight },
812
+ children: [lines.map((line) => /* @__PURE__ */ jsxs("div", {
813
+ className: cn$1("whitespace-pre-wrap", LINE_COLORS[line.type]),
814
+ children: [/* @__PURE__ */ jsx("span", {
815
+ className: "text-muted-foreground select-none",
816
+ children: LINE_PREFIXES[line.type]
817
+ }), line.text]
818
+ }, line.id)), lines.length === 0 && /* @__PURE__ */ jsx("div", {
819
+ className: "text-muted-foreground py-2",
820
+ children: "No output yet"
821
+ })]
822
+ })]
823
+ });
824
+ }
825
+ //#endregion
826
+ //#region src/workspace/runtime-pane.tsx
827
+ /**
828
+ * RuntimePane — shared operating surface for sandbox session state, terminal
829
+ * output, audit results, and runtime metadata.
830
+ */
831
+ function RuntimePane({ title = "Runtime", subtitle = "Session state, execution output, and inspection surfaces", statusBanner, statusBar, content, terminal, audit, inspector, footer, className }) {
832
+ return /* @__PURE__ */ jsxs("section", {
833
+ className: cn$1("flex h-full min-h-0 flex-col overflow-hidden bg-surface-container text-foreground", className),
834
+ children: [
835
+ /* @__PURE__ */ jsxs("header", {
836
+ className: "border-b border-[var(--md3-outline-variant)] bg-surface-container-high bg-[linear-gradient(180deg,rgba(255,255,255,0.03),transparent)] px-4 py-3",
837
+ children: [
838
+ /* @__PURE__ */ jsx("div", {
839
+ className: "mb-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground",
840
+ children: "Sandbox"
841
+ }),
842
+ /* @__PURE__ */ jsx("div", {
843
+ className: "text-sm font-semibold text-foreground",
844
+ children: title
845
+ }),
846
+ /* @__PURE__ */ jsx("div", {
847
+ className: "mt-1 text-xs text-muted-foreground",
848
+ children: subtitle
849
+ })
850
+ ]
851
+ }),
852
+ statusBanner && /* @__PURE__ */ jsx(StatusBanner, { ...statusBanner }),
853
+ statusBar && /* @__PURE__ */ jsx(StatusBar, { ...statusBar }),
854
+ content ? /* @__PURE__ */ jsx("div", {
855
+ className: "min-h-0 flex-1 overflow-hidden",
856
+ children: content
857
+ }) : /* @__PURE__ */ jsxs("div", {
858
+ className: "grid min-h-0 flex-1 gap-px bg-[var(--md3-outline-variant)] lg:grid-cols-[minmax(0,1.35fr)_minmax(20rem,0.9fr)]",
859
+ children: [/* @__PURE__ */ jsx("div", {
860
+ className: "min-h-0 overflow-auto bg-surface-container",
861
+ children: terminal ? /* @__PURE__ */ jsx(TerminalPanel, {
862
+ ...terminal,
863
+ className: cn$1("border-t-0 bg-transparent", terminal.className)
864
+ }) : /* @__PURE__ */ jsx("div", {
865
+ className: "flex h-full items-center justify-center p-6 text-sm text-muted-foreground",
866
+ children: /* @__PURE__ */ jsxs("div", {
867
+ className: "flex items-center gap-2",
868
+ children: [/* @__PURE__ */ jsx(TerminalSquare, { className: "h-4 w-4" }), "No terminal activity yet"]
869
+ })
870
+ })
871
+ }), /* @__PURE__ */ jsxs("div", {
872
+ className: "grid min-h-0 gap-px bg-[var(--md3-outline-variant)]",
873
+ children: [/* @__PURE__ */ jsx("div", {
874
+ className: "min-h-0 overflow-auto bg-surface-container",
875
+ children: audit ? audit : /* @__PURE__ */ jsx("div", {
876
+ className: "flex h-full items-center justify-center p-6 text-sm text-muted-foreground",
877
+ children: /* @__PURE__ */ jsxs("div", {
878
+ className: "flex items-center gap-2",
879
+ children: [/* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4" }), "No audit data available"]
880
+ })
881
+ })
882
+ }), /* @__PURE__ */ jsx("div", {
883
+ className: "min-h-0 overflow-auto bg-surface-container",
884
+ children: inspector ? inspector : /* @__PURE__ */ jsx("div", {
885
+ className: "flex h-full items-center justify-center p-6 text-sm text-muted-foreground",
886
+ children: /* @__PURE__ */ jsxs("div", {
887
+ className: "flex items-center gap-2",
888
+ children: [/* @__PURE__ */ jsx(Binary, { className: "h-4 w-4" }), "No runtime inspector attached"]
889
+ })
890
+ })
891
+ })]
892
+ })]
893
+ }),
894
+ footer && /* @__PURE__ */ jsx("footer", {
895
+ className: "shrink-0 border-t border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2",
896
+ children: footer
897
+ })
898
+ ]
899
+ });
900
+ }
901
+ //#endregion
902
+ //#region src/lib/format-relative-age.ts
903
+ const MINUTE = 6e4;
904
+ const HOUR = 60 * MINUTE;
905
+ const DAY = 24 * HOUR;
906
+ const WEEK = 7 * DAY;
907
+ const MONTH = 30 * DAY;
908
+ const YEAR = 365 * DAY;
909
+ /**
910
+ * Compact age for a session rail: `now`, `5m`, `3h`, `2d`, `3w`, `4mo`, `1y`.
911
+ * Unlike `timeAgo` from `@tangle-network/ui/utils` it accepts any date input,
912
+ * rolls past hours into days and beyond, and carries no "ago" suffix, so it
913
+ * fits a "codex · 2h" meta line. A future or unparseable date reads `now` /
914
+ * empty rather than a negative count. `now` is injectable for deterministic tests.
915
+ */
916
+ function formatRelativeAge(date, now = Date.now()) {
917
+ const timestamp = date instanceof Date ? date.getTime() : new Date(date).getTime();
918
+ if (Number.isNaN(timestamp)) return "";
919
+ const elapsed = Math.max(0, now - timestamp);
920
+ if (elapsed < MINUTE) return "now";
921
+ if (elapsed < HOUR) return `${Math.floor(elapsed / MINUTE)}m`;
922
+ if (elapsed < DAY) return `${Math.floor(elapsed / HOUR)}h`;
923
+ if (elapsed < WEEK) return `${Math.floor(elapsed / DAY)}d`;
924
+ if (elapsed < MONTH) return `${Math.floor(elapsed / WEEK)}w`;
925
+ if (elapsed < YEAR) return `${Math.floor(elapsed / MONTH)}mo`;
926
+ return `${Math.floor(elapsed / YEAR)}y`;
927
+ }
928
+ //#endregion
929
+ //#region src/workspace/session-optimistic.ts
930
+ /**
931
+ * Optimistic session/thread list: reflect a just-created / renamed / deleted
932
+ * item in the sidebar INSTANTLY, before the server loader round-trips.
933
+ *
934
+ * The shell loader is the source of truth and reconciles in the background
935
+ * (typically the app calls `useRevalidator().revalidate()` after its mutation
936
+ * fetch settles). Until that lands, a local mirror of the loader items carries
937
+ * the optimistic edit so a delete / rename / new-thread does not feel broken
938
+ * for the duration of a D1 round-trip.
939
+ *
940
+ * Two ways to drive it, usable together:
941
+ * 1. A window `CustomEvent` (default name {@link SESSION_CREATED_EVENT}) — the
942
+ * surface that creates a thread (often a different route from the sidebar's
943
+ * route) dispatches it; the sidebar prepends the item with no prop wiring.
944
+ * 2. The imperative {@link SessionOptimisticController} returned here — the
945
+ * sidebar's own action handlers (rename / delete / pin) call it directly.
946
+ */
947
+ /** Default window-event name for "a session/thread was just created". */
948
+ const SESSION_CREATED_EVENT = "sandbox-thread-created";
949
+ /** Dispatch the shared "session created" event so any mounted sidebar updates. */
950
+ function dispatchSessionCreated(detail, eventName = SESSION_CREATED_EVENT) {
951
+ if (typeof window === "undefined") return;
952
+ window.dispatchEvent(new CustomEvent(eventName, { detail }));
953
+ }
954
+ function toItem(detail) {
955
+ return {
956
+ updatedAt: /* @__PURE__ */ new Date(),
957
+ ...detail
958
+ };
959
+ }
960
+ /**
961
+ * Mirror `items` with optimistic add/update/remove on top. The mirror re-syncs
962
+ * whenever `items` changes (the loader is authoritative), so an optimistic edit
963
+ * is naturally superseded once the server result arrives.
964
+ */
965
+ function useOptimisticSessionItems(items, options = {}) {
966
+ const { listenForCreate = true, eventName = SESSION_CREATED_EVENT } = options;
967
+ const [optimisticItems, setOptimisticItems] = useState(items);
968
+ useEffect(() => {
969
+ setOptimisticItems(items);
970
+ }, [items]);
971
+ const add = useCallback((detail) => {
972
+ const next = toItem(detail);
973
+ setOptimisticItems((prev) => [next, ...prev.filter((item) => item.id !== next.id)]);
974
+ }, []);
975
+ const update = useCallback((id, patch) => {
976
+ setOptimisticItems((prev) => prev.map((item) => item.id === id ? {
977
+ ...item,
978
+ ...patch
979
+ } : item));
980
+ }, []);
981
+ const remove = useCallback((id) => {
982
+ setOptimisticItems((prev) => prev.filter((item) => item.id !== id));
983
+ }, []);
984
+ const reset = useCallback(() => setOptimisticItems(items), [items]);
985
+ const addRef = useRef(add);
986
+ addRef.current = add;
987
+ useEffect(() => {
988
+ if (!listenForCreate || typeof window === "undefined") return;
989
+ function onCreated(event) {
990
+ const detail = event.detail;
991
+ if (!detail?.id || !detail.title) return;
992
+ addRef.current(detail);
993
+ }
994
+ window.addEventListener(eventName, onCreated);
995
+ return () => window.removeEventListener(eventName, onCreated);
996
+ }, [listenForCreate, eventName]);
997
+ return {
998
+ items: optimisticItems,
999
+ controller: {
1000
+ add,
1001
+ update,
1002
+ remove,
1003
+ reset
1004
+ }
1005
+ };
1006
+ }
1007
+ //#endregion
1008
+ //#region src/workspace/session-sidebar.tsx
1009
+ const NO_CONTROLLER = {
1010
+ add: () => {},
1011
+ update: () => {},
1012
+ remove: () => {},
1013
+ reset: () => {}
1014
+ };
1015
+ const STATUS_GROUPS = [
1016
+ {
1017
+ key: "attention",
1018
+ label: "Needs input"
1019
+ },
1020
+ {
1021
+ key: "failed",
1022
+ label: "Failed"
1023
+ },
1024
+ {
1025
+ key: "working",
1026
+ label: "Working"
1027
+ },
1028
+ {
1029
+ key: "done",
1030
+ label: "Done"
1031
+ }
1032
+ ];
1033
+ function groupKeyForStatus(status) {
1034
+ switch (status) {
1035
+ case "attention-needed": return "attention";
1036
+ case "error": return "failed";
1037
+ case "running": return "working";
1038
+ default: return "done";
1039
+ }
1040
+ }
1041
+ function statusDot(status) {
1042
+ switch (status) {
1043
+ case "running": return "bg-[var(--status-running)]";
1044
+ case "error": return "bg-[var(--surface-danger-text)]";
1045
+ case "attention-needed": return "bg-[var(--surface-warning-text)]";
1046
+ default: return "bg-muted-foreground/40";
1047
+ }
1048
+ }
1049
+ function iconForLink(icon) {
1050
+ switch (icon) {
1051
+ case "vault": return FolderTree;
1052
+ case "settings": return Settings;
1053
+ case "back": return ArrowLeft;
1054
+ default: return Sparkles;
1055
+ }
1056
+ }
1057
+ function toDate(value) {
1058
+ if (value === void 0) return null;
1059
+ const date = value instanceof Date ? value : new Date(value);
1060
+ return Number.isNaN(date.getTime()) ? null : date;
1061
+ }
1062
+ function sortItems(items, sessionStatusById) {
1063
+ return [...items].sort((left, right) => {
1064
+ if (Boolean(left.isPinned) !== Boolean(right.isPinned)) return left.isPinned ? -1 : 1;
1065
+ const leftSession = sessionStatusById.get(left.id);
1066
+ const rightSession = sessionStatusById.get(right.id);
1067
+ if (Boolean(leftSession) !== Boolean(rightSession)) return leftSession ? -1 : 1;
1068
+ if (leftSession && rightSession) {
1069
+ if (leftSession.isForeground !== rightSession.isForeground) return Number(rightSession.isForeground) - Number(leftSession.isForeground);
1070
+ const leftRunning = Number(leftSession.status === "running");
1071
+ const rightRunning = Number(rightSession.status === "running");
1072
+ if (leftRunning !== rightRunning) return rightRunning - leftRunning;
1073
+ if (leftSession.lastActivityAt !== rightSession.lastActivityAt) return rightSession.lastActivityAt - leftSession.lastActivityAt;
1074
+ }
1075
+ const leftUpdated = left.updatedAt ? new Date(left.updatedAt).getTime() : 0;
1076
+ return (right.updatedAt ? new Date(right.updatedAt).getTime() : 0) - leftUpdated;
1077
+ });
1078
+ }
1079
+ function badgeTone(tone = "neutral") {
1080
+ switch (tone) {
1081
+ case "accent": return "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary";
1082
+ case "success": return "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]";
1083
+ case "warning": return "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]";
1084
+ case "danger": return "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]";
1085
+ default: return "border-[var(--md3-outline-variant)] bg-surface-container-high text-muted-foreground";
1086
+ }
1087
+ }
1088
+ function navigateToHref(href) {
1089
+ if (!href || typeof window === "undefined") return;
1090
+ window.location.assign(href);
1091
+ }
1092
+ function useResizable(defaultWidth, min, max, onChange) {
1093
+ const [width, setWidth] = useState(defaultWidth);
1094
+ const dragging = useRef(false);
1095
+ const startX = useRef(0);
1096
+ const startW = useRef(0);
1097
+ return {
1098
+ width,
1099
+ onPointerDown: useCallback((e) => {
1100
+ dragging.current = true;
1101
+ startX.current = e.clientX;
1102
+ startW.current = width;
1103
+ e.currentTarget.setPointerCapture(e.pointerId);
1104
+ }, [width]),
1105
+ onPointerMove: useCallback((e) => {
1106
+ if (!dragging.current) return;
1107
+ const delta = e.clientX - startX.current;
1108
+ const next = Math.min(max, Math.max(min, startW.current + delta));
1109
+ setWidth(next);
1110
+ onChange?.(next);
1111
+ }, [
1112
+ min,
1113
+ max,
1114
+ onChange
1115
+ ]),
1116
+ onPointerUp: useCallback(() => {
1117
+ dragging.current = false;
1118
+ }, [])
1119
+ };
1120
+ }
1121
+ function SessionSidebar({ title, subtitle, projectId = null, items, currentItemId, createLabel = "New Session", onCreate, onSelectItem, onSelectLink, links = [], className, emptyMessage = "No sessions yet. Start a conversation.", searchPlaceholder = "Search sessions", enableSearch = true, activityMonitor, filters = [], defaultFilterId, resizable = false, defaultWidth = 256, minWidth = 200, maxWidth = 400, onWidthChange, renderItemActions, optimistic = false, optimisticRef, variant = "default", groupBy = "none", showUpdatedAt = false, onCollapse, fill = false }) {
1122
+ const quiet = variant === "quiet";
1123
+ const [query, setQuery] = useState("");
1124
+ const [activeFilterId, setActiveFilterId] = useState(defaultFilterId ?? filters[0]?.id ?? "all");
1125
+ const optimisticState = useOptimisticSessionItems(items, optimistic === false ? { listenForCreate: false } : typeof optimistic === "object" ? optimistic : void 0);
1126
+ const optimisticEnabled = optimistic !== false;
1127
+ const effectiveItems = optimisticEnabled ? optimisticState.items : items;
1128
+ useImperativeHandle(optimisticRef, () => optimisticEnabled ? optimisticState.controller : NO_CONTROLLER, [optimisticEnabled, optimisticState.controller]);
1129
+ const activeSessions = useNavbarSessions(projectId);
1130
+ const sessionsById = useMemo(() => new Map(activeSessions.map((session) => [session.sessionId, session])), [activeSessions]);
1131
+ const orderedItems = useMemo(() => sortItems(effectiveItems, sessionsById), [effectiveItems, sessionsById]);
1132
+ const visibleItems = useMemo(() => {
1133
+ const normalizedQuery = query.trim().toLowerCase();
1134
+ const selectedFilter = filters.find((filter) => filter.id === activeFilterId) ?? null;
1135
+ return orderedItems.filter((item) => {
1136
+ const session = sessionsById.get(item.id) ?? null;
1137
+ const isActive = currentItemId === item.id;
1138
+ if (selectedFilter && !selectedFilter.matches(item, {
1139
+ session,
1140
+ isActive
1141
+ })) return false;
1142
+ if (!normalizedQuery) return true;
1143
+ return `${item.title} ${item.subtitle ?? ""} ${item.category ?? ""} ${(item.badges ?? []).map((badge) => badge.label).join(" ")}`.toLowerCase().includes(normalizedQuery);
1144
+ });
1145
+ }, [
1146
+ activeFilterId,
1147
+ currentItemId,
1148
+ filters,
1149
+ orderedItems,
1150
+ query,
1151
+ sessionsById
1152
+ ]);
1153
+ const groups = useMemo(() => {
1154
+ if (groupBy !== "status") return [{
1155
+ key: "all",
1156
+ label: null,
1157
+ items: visibleItems
1158
+ }];
1159
+ return STATUS_GROUPS.map((group) => ({
1160
+ key: group.key,
1161
+ label: group.label,
1162
+ items: visibleItems.filter((item) => groupKeyForStatus(sessionsById.get(item.id)?.status ?? item.status) === group.key)
1163
+ })).filter((group) => group.items.length > 0);
1164
+ }, [
1165
+ groupBy,
1166
+ sessionsById,
1167
+ visibleItems
1168
+ ]);
1169
+ const showGroupLabels = groups.length > 1;
1170
+ const runningCount = activeSessions.filter((session) => session.status === "running").length;
1171
+ const filterCounts = useMemo(() => Object.fromEntries(filters.map((filter) => [filter.id, orderedItems.filter((item) => filter.matches(item, {
1172
+ session: sessionsById.get(item.id) ?? null,
1173
+ isActive: currentItemId === item.id
1174
+ })).length])), [
1175
+ currentItemId,
1176
+ filters,
1177
+ orderedItems,
1178
+ sessionsById
1179
+ ]);
1180
+ const resize = useResizable(defaultWidth, minWidth, maxWidth, onWidthChange);
1181
+ const fillsParent = fill && !resizable;
1182
+ const renderRow = (item, index) => {
1183
+ const session = sessionsById.get(item.id) ?? null;
1184
+ const isActive = currentItemId === item.id;
1185
+ const status = session?.status ?? item.status;
1186
+ const isWorking = status === "running";
1187
+ const visibleBadges = [...item.isPinned ? [{
1188
+ id: `${item.id}-pinned`,
1189
+ label: "Pinned",
1190
+ tone: "neutral"
1191
+ }] : [], ...item.badges ?? []].filter((badge) => !quiet || (badge.tone ?? "neutral") !== "neutral");
1192
+ const updatedAt = showUpdatedAt && item.meta === void 0 ? toDate(item.updatedAt) : null;
1193
+ const metaLine = item.meta ?? (updatedAt ? /* @__PURE__ */ jsx("time", {
1194
+ dateTime: updatedAt.toISOString(),
1195
+ title: updatedAt.toLocaleString(),
1196
+ children: formatRelativeAge(updatedAt)
1197
+ }) : null);
1198
+ return /* @__PURE__ */ jsx("li", {
1199
+ className: "agent-arrive",
1200
+ style: { "--stagger-index": index },
1201
+ children: /* @__PURE__ */ jsxs("div", {
1202
+ className: cn$1(quiet ? "group relative flex items-center gap-2 rounded-lg py-2 pl-2 pr-3 transition-colors" : "group relative flex items-center gap-2 rounded-[var(--radius-sm)] px-2.5 py-1.5 transition-colors", MOTION_CONTROL, quiet ? isActive ? "bg-surface-container-high" : "hover:bg-surface-container" : isActive ? "bg-accent font-medium text-foreground" : "text-muted-foreground hover:bg-accent/30"),
1203
+ children: [/* @__PURE__ */ jsxs("button", {
1204
+ type: "button",
1205
+ onClick: () => {
1206
+ if (onSelectItem) {
1207
+ onSelectItem(item);
1208
+ return;
1209
+ }
1210
+ navigateToHref(item.href);
1211
+ },
1212
+ "aria-current": isActive ? "page" : void 0,
1213
+ className: "min-w-0 flex flex-1 items-center gap-2 text-left",
1214
+ children: [item.icon ? /* @__PURE__ */ jsxs("span", {
1215
+ className: "relative flex h-5 w-4 shrink-0 items-center justify-center",
1216
+ "aria-hidden": "true",
1217
+ children: [item.icon, /* @__PURE__ */ jsx("span", { className: cn$1("absolute -bottom-0.5 -right-0.5 h-1.5 w-1.5 rounded-full", statusDot(status)) })]
1218
+ }) : /* @__PURE__ */ jsx("span", { className: cn$1("h-1.5 w-1.5 shrink-0 rounded-full", statusDot(status)) }), /* @__PURE__ */ jsxs("div", {
1219
+ className: "min-w-0 flex-1",
1220
+ children: [
1221
+ /* @__PURE__ */ jsx("div", {
1222
+ className: cn$1(quiet ? "truncate text-sm leading-5 text-foreground/75 group-hover:text-foreground" : "truncate text-sm font-medium", isActive && "text-foreground", isWorking && "agent-shimmer"),
1223
+ ...isWorking ? { "data-motion": "essential" } : {},
1224
+ children: item.title
1225
+ }),
1226
+ item.subtitle && /* @__PURE__ */ jsx("div", {
1227
+ className: "truncate text-xs leading-tight text-muted-foreground",
1228
+ children: item.subtitle
1229
+ }),
1230
+ metaLine && /* @__PURE__ */ jsx("div", {
1231
+ className: "truncate text-xs leading-tight text-muted-foreground",
1232
+ children: metaLine
1233
+ })
1234
+ ]
1235
+ })]
1236
+ }), /* @__PURE__ */ jsxs("div", {
1237
+ className: "flex shrink-0 items-center gap-1",
1238
+ children: [
1239
+ visibleBadges.length > 0 && visibleBadges.slice(0, 1).map((badge) => /* @__PURE__ */ jsx("span", {
1240
+ className: cn$1("rounded-full border px-1.5 py-px text-[8px] font-semibold uppercase", badgeTone(badge.tone)),
1241
+ children: badge.label
1242
+ }, badge.id)),
1243
+ session?.isForeground && /* @__PURE__ */ jsx("span", {
1244
+ className: "h-1.5 w-1.5 rounded-full bg-[var(--status-running)]",
1245
+ title: "Live"
1246
+ }),
1247
+ renderItemActions ? /* @__PURE__ */ jsx("div", {
1248
+ className: cn$1("opacity-0 transition-opacity group-hover:opacity-100", MOTION_CONTROL),
1249
+ onClick: (event) => event.stopPropagation(),
1250
+ children: renderItemActions(item, {
1251
+ session,
1252
+ isActive
1253
+ })
1254
+ }) : null
1255
+ ]
1256
+ })]
1257
+ })
1258
+ }, item.id);
1259
+ };
1260
+ let rowIndex = 0;
1261
+ return /* @__PURE__ */ jsxs("aside", {
1262
+ className: cn$1("relative flex shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-low", fillsParent && "h-full w-full", className),
1263
+ style: fillsParent ? void 0 : { width: resizable ? resize.width : defaultWidth },
1264
+ children: [
1265
+ quiet ? /* @__PURE__ */ jsxs("div", {
1266
+ className: "flex h-14 shrink-0 items-center justify-between gap-2 px-3",
1267
+ children: [
1268
+ /* @__PURE__ */ jsx("span", {
1269
+ className: "sr-only",
1270
+ children: title
1271
+ }),
1272
+ onCreate ? /* @__PURE__ */ jsxs("button", {
1273
+ type: "button",
1274
+ onClick: onCreate,
1275
+ className: cn$1("flex items-center gap-1.5 rounded-md px-1.5 py-1 text-sm text-muted-foreground transition-colors hover:text-foreground", MOTION_CONTROL),
1276
+ children: [/* @__PURE__ */ jsx(Plus, {
1277
+ className: "h-4 w-4",
1278
+ "aria-hidden": "true"
1279
+ }), /* @__PURE__ */ jsx("span", { children: createLabel })]
1280
+ }) : /* @__PURE__ */ jsx("span", {}),
1281
+ onCollapse && /* @__PURE__ */ jsx("button", {
1282
+ type: "button",
1283
+ onClick: onCollapse,
1284
+ "aria-label": "Hide chats",
1285
+ className: cn$1("flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-surface-container hover:text-foreground", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
1286
+ children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
1287
+ })
1288
+ ]
1289
+ }) : /* @__PURE__ */ jsx(WorkspacePaneHeader, { children: /* @__PURE__ */ jsxs("div", {
1290
+ className: "flex w-full items-center justify-between gap-2",
1291
+ children: [/* @__PURE__ */ jsxs("div", {
1292
+ className: "flex min-w-0 items-center gap-2",
1293
+ children: [/* @__PURE__ */ jsx("div", {
1294
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary",
1295
+ children: /* @__PURE__ */ jsx(MessageSquareText, { className: "h-3.5 w-3.5" })
1296
+ }), /* @__PURE__ */ jsxs("div", {
1297
+ className: "min-w-0",
1298
+ children: [/* @__PURE__ */ jsx("div", {
1299
+ className: "truncate text-sm font-semibold text-foreground",
1300
+ children: title
1301
+ }), subtitle && /* @__PURE__ */ jsx("div", {
1302
+ className: "truncate text-xs text-muted-foreground",
1303
+ children: subtitle
1304
+ })]
1305
+ })]
1306
+ }), /* @__PURE__ */ jsxs("div", {
1307
+ className: "flex shrink-0 items-center gap-1.5",
1308
+ children: [runningCount > 0 && /* @__PURE__ */ jsx("span", {
1309
+ className: "rounded-full border border-[var(--border-accent)] bg-[var(--accent-surface-soft)] px-1.5 py-px text-xs font-medium text-primary",
1310
+ children: runningCount
1311
+ }), onCreate && /* @__PURE__ */ jsx("button", {
1312
+ type: "button",
1313
+ onClick: onCreate,
1314
+ title: createLabel,
1315
+ className: cn$1("flex h-7 w-7 items-center justify-center rounded-[var(--radius-sm)] border border-[var(--md3-outline-variant)] text-muted-foreground transition-colors hover:bg-[var(--accent-surface-soft)] hover:text-foreground", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
1316
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
1317
+ })]
1318
+ })]
1319
+ }) }),
1320
+ (enableSearch && items.length > 0 || filters.length > 0) && /* @__PURE__ */ jsxs("div", {
1321
+ className: cn$1("shrink-0", quiet ? "px-3 pb-1" : "border-b border-[var(--md3-outline-variant)] px-3 py-2.5"),
1322
+ children: [enableSearch && effectiveItems.length > 0 && /* @__PURE__ */ jsxs("div", {
1323
+ className: "relative",
1324
+ children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ jsx("input", {
1325
+ value: query,
1326
+ onChange: (event) => setQuery(event.target.value),
1327
+ placeholder: searchPlaceholder,
1328
+ "aria-label": searchPlaceholder,
1329
+ className: cn$1(quiet ? `h-8 w-full rounded-md border-0 bg-transparent pl-7 pr-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus-visible:bg-surface-container ${focusRing}` : `h-8 w-full rounded-[var(--radius-sm)] border bg-surface-container-lowest pl-7 pr-2 text-sm text-foreground placeholder:text-muted-foreground ${focusField}`, "duration-[var(--duration-fast)] ease-[var(--ease-standard)]")
1330
+ })]
1331
+ }), filters.length > 0 && /* @__PURE__ */ jsx("div", {
1332
+ className: cn$1("flex flex-wrap gap-1", enableSearch && items.length > 0 && "mt-2"),
1333
+ children: filters.map((filter) => {
1334
+ const isSelected = activeFilterId === filter.id;
1335
+ return /* @__PURE__ */ jsxs("button", {
1336
+ type: "button",
1337
+ onClick: () => setActiveFilterId(filter.id),
1338
+ className: cn$1("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]", isSelected ? "border-[var(--border-accent)] bg-[var(--accent-surface-soft)] text-primary" : "border-[var(--md3-outline-variant)] text-muted-foreground hover:text-foreground"),
1339
+ children: [/* @__PURE__ */ jsx("span", { children: filter.label }), /* @__PURE__ */ jsx("span", {
1340
+ className: "text-[11px] opacity-60",
1341
+ children: filterCounts[filter.id] ?? 0
1342
+ })]
1343
+ }, filter.id);
1344
+ })
1345
+ })]
1346
+ }),
1347
+ /* @__PURE__ */ jsx("nav", {
1348
+ "aria-label": "Sessions",
1349
+ className: "flex-1 overflow-y-auto px-1.5 py-1.5",
1350
+ children: visibleItems.length === 0 ? /* @__PURE__ */ jsxs("div", {
1351
+ className: "flex flex-col items-center justify-center gap-2 px-4 py-10 text-center text-sm text-muted-foreground",
1352
+ children: [/* @__PURE__ */ jsx(MessageSquareText, {
1353
+ className: "h-5 w-5 opacity-50",
1354
+ "aria-hidden": "true"
1355
+ }), /* @__PURE__ */ jsx("p", { children: query.trim() ? `No sessions match "${query.trim()}".` : emptyMessage })]
1356
+ }) : showGroupLabels ? groups.map((group, groupIndex) => /* @__PURE__ */ jsxs("section", {
1357
+ "aria-label": group.label ?? void 0,
1358
+ children: [/* @__PURE__ */ jsx("div", {
1359
+ className: cn$1("px-2 pb-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", groupIndex === 0 ? "pt-1" : "pt-3"),
1360
+ children: group.label
1361
+ }), /* @__PURE__ */ jsx("ul", {
1362
+ className: "space-y-px",
1363
+ children: group.items.map((item) => renderRow(item, rowIndex++))
1364
+ })]
1365
+ }, group.key)) : /* @__PURE__ */ jsx("ul", {
1366
+ className: "space-y-px",
1367
+ children: visibleItems.map((item) => renderRow(item, rowIndex++))
1368
+ })
1369
+ }),
1370
+ activityMonitor && /* @__PURE__ */ jsx("div", {
1371
+ className: "border-t border-[var(--md3-outline-variant)] px-2 py-1.5",
1372
+ children: activityMonitor
1373
+ }),
1374
+ links.length > 0 && /* @__PURE__ */ jsx("nav", {
1375
+ "aria-label": "Workspace sections",
1376
+ className: "border-t border-[var(--md3-outline-variant)] px-1.5 py-1.5",
1377
+ children: /* @__PURE__ */ jsx("div", {
1378
+ className: "space-y-px",
1379
+ children: links.map((link) => {
1380
+ const Icon = iconForLink(link.icon);
1381
+ return /* @__PURE__ */ jsxs("button", {
1382
+ type: "button",
1383
+ onClick: () => {
1384
+ if (onSelectLink) {
1385
+ onSelectLink(link);
1386
+ return;
1387
+ }
1388
+ navigateToHref(link.href);
1389
+ },
1390
+ className: cn$1("flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-3 py-2 text-left text-sm text-muted-foreground transition-colors hover:bg-accent/30 hover:text-foreground", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
1391
+ children: [/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }), /* @__PURE__ */ jsx("span", {
1392
+ className: "truncate",
1393
+ children: link.label
1394
+ })]
1395
+ }, link.id);
1396
+ })
1397
+ })
1398
+ }),
1399
+ resizable && /* @__PURE__ */ jsx("div", {
1400
+ className: cn$1("absolute top-0 right-0 bottom-0 w-1 cursor-col-resize hover:bg-[var(--accent-surface-soft)] active:bg-[var(--border-accent)] transition-colors", "duration-[var(--duration-fast)] ease-[var(--ease-standard)]"),
1401
+ onPointerDown: resize.onPointerDown,
1402
+ onPointerMove: resize.onPointerMove,
1403
+ onPointerUp: resize.onPointerUp,
1404
+ title: "Drag to resize"
1405
+ })
1406
+ ]
1407
+ });
1408
+ }
1409
+ //#endregion
1410
+ //#region src/workspace/shell-revalidation.ts
1411
+ /**
1412
+ * Build a `shouldRevalidate` function for the shell route under `appPathPrefix`.
1413
+ * Use this when the app mounts under a non-default prefix; otherwise prefer the
1414
+ * ready-made {@link shellShouldRevalidate} export.
1415
+ */
1416
+ function createShellShouldRevalidate(options = {}) {
1417
+ const appPathPrefix = options.appPathPrefix ?? "/app";
1418
+ return function shouldRevalidate(args) {
1419
+ const { currentUrl, nextUrl, formMethod, defaultShouldRevalidate } = args;
1420
+ if (formMethod && formMethod !== "GET") return true;
1421
+ if (currentUrl.pathname !== nextUrl.pathname && currentUrl.pathname.startsWith(appPathPrefix) && nextUrl.pathname.startsWith(appPathPrefix)) return false;
1422
+ return defaultShouldRevalidate;
1423
+ };
1424
+ }
1425
+ /**
1426
+ * Ready-made shell `shouldRevalidate` for apps mounted under `/app`. Wire it as
1427
+ * a one-line route export:
1428
+ *
1429
+ * ```ts
1430
+ * export const shouldRevalidate = shellShouldRevalidate;
1431
+ * ```
1432
+ *
1433
+ * For a non-`/app` prefix, use {@link createShellShouldRevalidate}.
1434
+ */
1435
+ const shellShouldRevalidate = createShellShouldRevalidate();
1436
+ //#endregion
1437
+ //#region src/workspace/session-activity-monitor.tsx
1438
+ function SessionStatusDot({ session }) {
1439
+ if (session.status === "error") return /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 text-[var(--surface-danger-text)]" });
1440
+ if (session.status === "running") return /* @__PURE__ */ jsx(LoaderCircle, {
1441
+ className: "h-3 w-3 animate-spin text-primary",
1442
+ "data-motion": "essential"
1443
+ });
1444
+ if (session.status === "attention-needed") return /* @__PURE__ */ jsx(Activity, { className: "h-3 w-3 text-[var(--surface-warning-text)]" });
1445
+ return /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/40" });
1446
+ }
1447
+ function navigateToSession(session) {
1448
+ if (!session.href || typeof window === "undefined") return;
1449
+ window.location.assign(session.href);
1450
+ }
1451
+ function SessionActivityMonitor({ className, compact = false, sessionsById = {}, emptyMessage = "No active sessions", resolveProjectLabel, onSelectSession }) {
1452
+ const trackedSessions = useActiveSessions();
1453
+ const projectActivity = useProjectActivity();
1454
+ const totalRunning = useTotalRunningSessions();
1455
+ const sessionLookup = Object.keys(sessionsById).length > 0 ? sessionsById : Object.fromEntries(trackedSessions.map((session) => [session.sessionId, session]));
1456
+ if (projectActivity.length === 0) {
1457
+ if (compact) return null;
1458
+ return /* @__PURE__ */ jsx("div", {
1459
+ className: cn$1("rounded-[var(--radius-lg)] border border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2.5 text-xs text-muted-foreground", className),
1460
+ children: emptyMessage
1461
+ });
1462
+ }
1463
+ return /* @__PURE__ */ jsxs("div", {
1464
+ className: cn$1("space-y-1.5", className),
1465
+ children: [/* @__PURE__ */ jsxs("div", {
1466
+ className: "flex items-center justify-between",
1467
+ children: [/* @__PURE__ */ jsxs("span", {
1468
+ className: "flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-muted-foreground",
1469
+ children: [/* @__PURE__ */ jsx(Activity, { className: "h-3 w-3" }), "Active"]
1470
+ }), totalRunning > 0 && /* @__PURE__ */ jsx("span", {
1471
+ className: "rounded-full border border-primary/30 bg-[var(--accent-surface-soft)] px-1.5 py-px text-[10px] font-medium text-primary",
1472
+ children: totalRunning
1473
+ })]
1474
+ }), /* @__PURE__ */ jsx("div", {
1475
+ className: "space-y-1",
1476
+ children: projectActivity.map((project) => {
1477
+ const label = resolveProjectLabel?.(project.projectId, project.projectLabel) ?? project.projectLabel ?? String(project.projectId);
1478
+ return /* @__PURE__ */ jsxs("div", {
1479
+ className: "overflow-hidden rounded-[var(--radius-lg)] border border-[var(--md3-outline-variant)] bg-surface-container",
1480
+ children: [/* @__PURE__ */ jsxs("div", {
1481
+ className: "flex items-center justify-between gap-2 px-2.5 py-1.5",
1482
+ children: [/* @__PURE__ */ jsxs("div", {
1483
+ className: "min-w-0",
1484
+ children: [/* @__PURE__ */ jsx("div", {
1485
+ className: "truncate text-xs font-medium text-foreground",
1486
+ children: label
1487
+ }), /* @__PURE__ */ jsxs("div", {
1488
+ className: "text-[10px] text-muted-foreground",
1489
+ children: [
1490
+ project.activeSessionCount,
1491
+ " session",
1492
+ project.activeSessionCount === 1 ? "" : "s"
1493
+ ]
1494
+ })]
1495
+ }), project.runningSessionIds.length > 0 && /* @__PURE__ */ jsxs("span", {
1496
+ className: "shrink-0 text-[10px] text-muted-foreground",
1497
+ children: [project.runningSessionIds.length, " live"]
1498
+ })]
1499
+ }), !compact && project.runningSessionIds.length > 0 && /* @__PURE__ */ jsx("div", {
1500
+ className: "border-t border-[var(--md3-outline-variant)] bg-surface-container-high px-1 py-0.5",
1501
+ children: project.runningSessionIds.map((sessionId) => {
1502
+ const session = sessionLookup[sessionId];
1503
+ if (!session) return null;
1504
+ return /* @__PURE__ */ jsxs("button", {
1505
+ type: "button",
1506
+ onClick: () => {
1507
+ if (onSelectSession) {
1508
+ onSelectSession(session);
1509
+ return;
1510
+ }
1511
+ navigateToSession(session);
1512
+ },
1513
+ className: "flex w-full items-center gap-2 rounded-[var(--radius-sm)] px-2 py-1 text-left transition-colors hover:bg-[var(--accent-surface-soft)]",
1514
+ children: [
1515
+ /* @__PURE__ */ jsx(SessionStatusDot, { session }),
1516
+ /* @__PURE__ */ jsx("span", {
1517
+ className: "min-w-0 truncate text-xs text-muted-foreground",
1518
+ children: session.title ?? "Untitled"
1519
+ }),
1520
+ /* @__PURE__ */ jsx(MessageSquareText, { className: "ml-auto h-3 w-3 shrink-0 text-muted-foreground" })
1521
+ ]
1522
+ }, sessionId);
1523
+ })
1524
+ })]
1525
+ }, String(project.projectId));
1526
+ })
1527
+ })]
1528
+ });
1529
+ }
1530
+ //#endregion
1531
+ //#region src/workspace/sandbox-workbench.tsx
1532
+ function getArtifactTabIcon(kind) {
1533
+ switch (kind) {
1534
+ case "file": return FileCode2;
1535
+ case "markdown": return FileText;
1536
+ case "openui": return LayoutPanelTop;
1537
+ case "custom": return Boxes;
1538
+ }
1539
+ }
1540
+ function artifactTabLabel(artifact) {
1541
+ if (typeof artifact.title === "string") return artifact.title;
1542
+ if (artifact.kind === "file") return artifact.filename;
1543
+ return "Artifact";
1544
+ }
1545
+ function sortPinnedFirst(artifacts) {
1546
+ if (!artifacts.some((artifact) => artifact.pinned)) return artifacts;
1547
+ return [...artifacts.filter((artifact) => artifact.pinned), ...artifacts.filter((artifact) => !artifact.pinned)];
1548
+ }
1549
+ function ArtifactTabs({ artifacts, activeArtifactId, onSelect, onClose, tabIdPrefix, panelId }) {
1550
+ const tabRefs = useRef(/* @__PURE__ */ new Map());
1551
+ if (artifacts.length === 0) return null;
1552
+ return /* @__PURE__ */ jsx("div", {
1553
+ role: "tablist",
1554
+ "aria-label": "Artifacts",
1555
+ className: "flex items-center overflow-x-auto border-b border-[var(--md3-outline-variant)] bg-surface-container-high",
1556
+ children: artifacts.map((artifact) => {
1557
+ const Icon = artifact.icon ?? getArtifactTabIcon(artifact.kind);
1558
+ const isActive = artifact.id === activeArtifactId;
1559
+ return /* @__PURE__ */ jsxs("div", {
1560
+ className: cn$1("group flex shrink-0 items-center border-r border-[var(--md3-outline-variant)] bg-surface-container relative", isActive ? "text-foreground after:absolute after:top-0 after:left-0 after:right-0 after:h-[2px] after:bg-primary" : "text-muted-foreground hover:bg-surface-container-high cursor-pointer"),
1561
+ children: [/* @__PURE__ */ jsxs("button", {
1562
+ type: "button",
1563
+ ref: (element) => {
1564
+ if (element) tabRefs.current.set(artifact.id, element);
1565
+ else tabRefs.current.delete(artifact.id);
1566
+ },
1567
+ id: `${tabIdPrefix}-${artifact.id}`,
1568
+ role: "tab",
1569
+ "aria-controls": panelId,
1570
+ "aria-selected": isActive,
1571
+ tabIndex: isActive ? 0 : -1,
1572
+ onClick: () => onSelect(artifact.id),
1573
+ onKeyDown: (event) => {
1574
+ const index = artifacts.findIndex((entry) => entry.id === artifact.id);
1575
+ let nextIndex;
1576
+ switch (event.key) {
1577
+ case "ArrowRight":
1578
+ nextIndex = (index + 1) % artifacts.length;
1579
+ break;
1580
+ case "ArrowLeft":
1581
+ nextIndex = (index - 1 + artifacts.length) % artifacts.length;
1582
+ break;
1583
+ case "Home":
1584
+ nextIndex = 0;
1585
+ break;
1586
+ case "End":
1587
+ nextIndex = artifacts.length - 1;
1588
+ break;
1589
+ default: return;
1590
+ }
1591
+ event.preventDefault();
1592
+ const next = artifacts[nextIndex];
1593
+ onSelect(next.id);
1594
+ tabRefs.current.get(next.id)?.focus();
1595
+ },
1596
+ className: `flex min-w-0 items-center gap-2 px-3 py-2 text-[12px] uppercase tracking-wider font-medium transition-colors hover:text-foreground ${focusRingInset}`,
1597
+ children: [/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", {
1598
+ className: "max-w-[14rem] truncate",
1599
+ children: artifactTabLabel(artifact)
1600
+ })]
1601
+ }), onClose && !artifact.pinned && /* @__PURE__ */ jsx("button", {
1602
+ type: "button",
1603
+ "aria-label": `Close ${artifactTabLabel(artifact)}`,
1604
+ tabIndex: isActive ? 0 : -1,
1605
+ onClick: () => onClose(artifact.id),
1606
+ className: `mr-1 rounded-[2px] p-1 opacity-0 transition-opacity hover:bg-accent hover:text-foreground focus-visible:opacity-100 group-hover:opacity-100 ${focusRing}`,
1607
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
1608
+ })]
1609
+ }, artifact.id);
1610
+ })
1611
+ });
1612
+ }
1613
+ function renderArtifact(artifact) {
1614
+ switch (artifact.kind) {
1615
+ case "file": return /* @__PURE__ */ jsx(FileArtifactPane, {
1616
+ path: artifact.path,
1617
+ filename: artifact.filename,
1618
+ content: artifact.content,
1619
+ blobUrl: artifact.blobUrl,
1620
+ mimeType: artifact.mimeType,
1621
+ onDownload: artifact.onDownload,
1622
+ tabs: artifact.tabs,
1623
+ activeTabId: artifact.activeTabId,
1624
+ onTabSelect: artifact.onTabSelect,
1625
+ onTabClose: artifact.onTabClose,
1626
+ eyebrow: artifact.eyebrow,
1627
+ meta: artifact.meta,
1628
+ toolbar: artifact.toolbar,
1629
+ footer: artifact.footer
1630
+ });
1631
+ case "markdown": return /* @__PURE__ */ jsx(ArtifactPane, {
1632
+ eyebrow: artifact.eyebrow ?? "Document",
1633
+ title: artifact.title,
1634
+ subtitle: artifact.subtitle,
1635
+ meta: artifact.meta,
1636
+ headerActions: artifact.headerActions,
1637
+ toolbar: artifact.toolbar,
1638
+ footer: artifact.footer,
1639
+ children: /* @__PURE__ */ jsx("div", {
1640
+ className: "p-5",
1641
+ children: /* @__PURE__ */ jsx(Markdown, {
1642
+ className: "prose-sm max-w-none",
1643
+ children: artifact.content
1644
+ })
1645
+ })
1646
+ });
1647
+ case "openui": return /* @__PURE__ */ jsx(ArtifactPane, {
1648
+ eyebrow: artifact.eyebrow ?? "Structured Artifact",
1649
+ title: artifact.title,
1650
+ subtitle: artifact.subtitle,
1651
+ meta: artifact.meta,
1652
+ headerActions: artifact.headerActions,
1653
+ toolbar: artifact.toolbar,
1654
+ footer: artifact.footer,
1655
+ children: /* @__PURE__ */ jsx(OpenUIArtifactRenderer, {
1656
+ schema: artifact.schema,
1657
+ onAction: artifact.onAction
1658
+ })
1659
+ });
1660
+ case "custom": return /* @__PURE__ */ jsx(ArtifactPane, {
1661
+ eyebrow: artifact.eyebrow ?? "Artifact",
1662
+ title: artifact.title,
1663
+ subtitle: artifact.subtitle,
1664
+ meta: artifact.meta,
1665
+ headerActions: artifact.headerActions,
1666
+ toolbar: artifact.toolbar,
1667
+ footer: artifact.footer,
1668
+ children: artifact.content
1669
+ });
1670
+ }
1671
+ }
1672
+ function regionHeader(sections, fallback) {
1673
+ if (sections.length !== 1) return fallback;
1674
+ switch (sections[0]?.key) {
1675
+ case "rail": return;
1676
+ case "directory": return /* @__PURE__ */ jsxs("div", {
1677
+ className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
1678
+ children: [/* @__PURE__ */ jsx(FolderTree, { className: "h-3.5 w-3.5" }), "Directory"]
1679
+ });
1680
+ case "artifacts": return /* @__PURE__ */ jsxs("div", {
1681
+ className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
1682
+ children: [/* @__PURE__ */ jsx(LayoutPanelTop, { className: "h-3.5 w-3.5" }), "Artifacts"]
1683
+ });
1684
+ case "runtime": return /* @__PURE__ */ jsxs("div", {
1685
+ className: "flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
1686
+ children: [/* @__PURE__ */ jsx(Bot, { className: "h-3.5 w-3.5" }), "Runtime"]
1687
+ });
1688
+ default: return fallback;
1689
+ }
1690
+ }
1691
+ function renderRegion(sections, region) {
1692
+ if (sections.length === 0) return void 0;
1693
+ if (sections.length === 1) return sections[0]?.content;
1694
+ return /* @__PURE__ */ jsx("div", {
1695
+ className: cn$1("flex min-h-0 h-full gap-3 p-3", region === "bottom" ? "flex-col" : "flex-col"),
1696
+ children: sections.map((section) => /* @__PURE__ */ jsx("div", {
1697
+ className: "min-h-0 flex-1 overflow-hidden",
1698
+ children: section.content
1699
+ }, section.key))
1700
+ });
1701
+ }
1702
+ /**
1703
+ * SandboxWorkbench — high-level composition that turns sandbox-ui primitives
1704
+ * into a complete session surface: directory, agent timeline, artifacts, and
1705
+ * runtime in one reusable shell.
1706
+ */
1707
+ function SandboxWorkbench({ title = "Sandbox session", subtitle, status, centerHeader: centerHeaderProp, composer, rail, directory, session, artifacts: artifactsProp = [], activeArtifactId, onArtifactChange, onArtifactClose, runtime, layout, emptyArtifactState, className }) {
1708
+ const artifacts = useMemo(() => sortPinnedFirst(artifactsProp), [artifactsProp]);
1709
+ const artifactTabsId = useId();
1710
+ const artifactPanelId = `${artifactTabsId}-panel`;
1711
+ const [uncontrolledArtifactId, setUncontrolledArtifactId] = useState(activeArtifactId ?? artifacts[0]?.id);
1712
+ useEffect(() => {
1713
+ if (activeArtifactId !== void 0) return;
1714
+ setUncontrolledArtifactId((current) => {
1715
+ if (artifacts.length === 0) return void 0;
1716
+ if (current && artifacts.some((artifact) => artifact.id === current)) return current;
1717
+ return artifacts[0]?.id;
1718
+ });
1719
+ }, [activeArtifactId, artifacts]);
1720
+ const resolvedArtifactId = activeArtifactId ?? uncontrolledArtifactId;
1721
+ const activeArtifact = useMemo(() => artifacts.find((artifact) => artifact.id === resolvedArtifactId), [artifacts, resolvedArtifactId]);
1722
+ const handleArtifactChange = (artifactId) => {
1723
+ if (activeArtifactId === void 0) setUncontrolledArtifactId(artifactId);
1724
+ onArtifactChange?.(artifactId);
1725
+ };
1726
+ const brandedHeader = /* @__PURE__ */ jsxs("div", {
1727
+ className: "flex min-w-0 items-start justify-between gap-4 rounded-[var(--radius-xl)] border border-[var(--md3-outline-variant)] bg-surface-container px-4 py-3.5",
1728
+ children: [/* @__PURE__ */ jsxs("div", {
1729
+ className: "min-w-0",
1730
+ children: [
1731
+ /* @__PURE__ */ jsx("div", {
1732
+ className: "text-[10px] font-semibold uppercase tracking-[0.18em] text-primary",
1733
+ children: "Tangle Sandbox"
1734
+ }),
1735
+ /* @__PURE__ */ jsx("div", {
1736
+ className: "truncate text-[17px] font-semibold tracking-[0.01em] text-foreground",
1737
+ children: title
1738
+ }),
1739
+ subtitle && /* @__PURE__ */ jsx("div", {
1740
+ className: "truncate text-sm text-muted-foreground",
1741
+ children: subtitle
1742
+ })
1743
+ ]
1744
+ }), /* @__PURE__ */ jsxs("div", {
1745
+ className: "flex shrink-0 flex-wrap items-center justify-end gap-2",
1746
+ children: [status, artifacts.length > 0 && /* @__PURE__ */ jsxs(Badge, {
1747
+ variant: "outline",
1748
+ children: [artifacts.length, " artifacts"]
1749
+ })]
1750
+ })]
1751
+ });
1752
+ const quiet = centerHeaderProp === null;
1753
+ const centerHeader = centerHeaderProp === void 0 ? brandedHeader : centerHeaderProp ?? void 0;
1754
+ const { composerControls, ...chatSession } = session;
1755
+ const transcript = /* @__PURE__ */ jsxs("div", {
1756
+ className: cn$1("flex h-full min-h-0 flex-col", quiet && "bg-surface"),
1757
+ children: [
1758
+ /* @__PURE__ */ jsx(ChatContainer, {
1759
+ ...chatSession,
1760
+ className: "min-h-0 flex-1",
1761
+ presentation: session.presentation ?? "timeline"
1762
+ }),
1763
+ composerControls && /* @__PURE__ */ jsx("div", {
1764
+ className: "shrink-0 border-t border-[var(--md3-outline-variant)] bg-surface-container-high px-3 py-2",
1765
+ children: composerControls
1766
+ }),
1767
+ composer && /* @__PURE__ */ jsx("div", {
1768
+ className: "shrink-0",
1769
+ children: /* @__PURE__ */ jsx("div", {
1770
+ className: "mx-auto w-full max-w-3xl px-3 pb-3",
1771
+ children: composer
1772
+ })
1773
+ })
1774
+ ]
1775
+ });
1776
+ const center = quiet ? transcript : /* @__PURE__ */ jsx(ArtifactPane, {
1777
+ eyebrow: session.eyebrow ?? "Agent Session",
1778
+ title: session.title ?? "Execution timeline",
1779
+ subtitle: session.subtitle,
1780
+ meta: session.meta,
1781
+ headerActions: session.headerActions,
1782
+ className: "h-full",
1783
+ contentClassName: "bg-surface-container",
1784
+ children: transcript
1785
+ });
1786
+ const artifactPanel = artifacts.length > 0 || emptyArtifactState ? /* @__PURE__ */ jsxs("section", {
1787
+ className: "flex h-full min-h-0 flex-col bg-surface-container",
1788
+ children: [/* @__PURE__ */ jsx(ArtifactTabs, {
1789
+ artifacts,
1790
+ activeArtifactId: resolvedArtifactId,
1791
+ onSelect: handleArtifactChange,
1792
+ onClose: onArtifactClose,
1793
+ tabIdPrefix: artifactTabsId,
1794
+ panelId: artifactPanelId
1795
+ }), /* @__PURE__ */ jsx("div", {
1796
+ id: artifactPanelId,
1797
+ role: "tabpanel",
1798
+ "aria-labelledby": activeArtifact ? `${artifactTabsId}-${activeArtifact.id}` : void 0,
1799
+ "aria-label": activeArtifact ? void 0 : "Artifact content",
1800
+ tabIndex: 0,
1801
+ className: `min-h-0 flex-1 overflow-auto bg-surface-container ${focusRingInset}`,
1802
+ children: activeArtifact ? renderArtifact(activeArtifact) : /* @__PURE__ */ jsx("div", {
1803
+ className: "flex h-full items-center justify-center p-6",
1804
+ children: emptyArtifactState ?? /* @__PURE__ */ jsx(EmptyState, {
1805
+ icon: /* @__PURE__ */ jsx(Boxes, { className: "h-8 w-8" }),
1806
+ title: "No artifact selected",
1807
+ description: "Select a generated artifact, file preview, or OpenUI panel to inspect it here."
1808
+ })
1809
+ })
1810
+ })]
1811
+ }) : null;
1812
+ const directoryPlacement = layout?.directoryPlacement ?? (directory ? "left" : "hidden");
1813
+ const artifactPlacement = layout?.artifactPlacement ?? (artifactPanel ? "right" : "hidden");
1814
+ const runtimePlacement = layout?.runtimePlacement ?? (runtime ? "bottom" : "hidden");
1815
+ const regionSections = {
1816
+ left: [],
1817
+ right: [],
1818
+ bottom: []
1819
+ };
1820
+ if (rail) regionSections.left.push({
1821
+ key: "rail",
1822
+ content: rail
1823
+ });
1824
+ if (directory && directoryPlacement !== "hidden") regionSections[directoryPlacement].push({
1825
+ key: "directory",
1826
+ content: /* @__PURE__ */ jsx(DirectoryPane, {
1827
+ ...directory,
1828
+ className: "h-full"
1829
+ })
1830
+ });
1831
+ if (artifactPanel && artifactPlacement !== "hidden") regionSections[artifactPlacement].push({
1832
+ key: "artifacts",
1833
+ content: artifactPanel
1834
+ });
1835
+ if (runtime && runtimePlacement !== "hidden") regionSections[runtimePlacement].push({
1836
+ key: "runtime",
1837
+ content: /* @__PURE__ */ jsx(RuntimePane, {
1838
+ ...runtime,
1839
+ className: "h-full"
1840
+ })
1841
+ });
1842
+ const left = renderRegion(regionSections.left, "left");
1843
+ const right = renderRegion(regionSections.right, "right");
1844
+ const bottom = renderRegion(regionSections.bottom, "bottom");
1845
+ const railOnly = regionSections.left.length === 1 && regionSections.left[0]?.key === "rail";
1846
+ const genericPanelsHeader = /* @__PURE__ */ jsx("span", {
1847
+ className: "text-xs font-semibold uppercase tracking-[0.12em] text-muted-foreground",
1848
+ children: "Workspace Panels"
1849
+ });
1850
+ return /* @__PURE__ */ jsx(WorkspaceLayout, {
1851
+ left,
1852
+ leftHeader: left ? regionHeader(regionSections.left, genericPanelsHeader) : void 0,
1853
+ center,
1854
+ centerHeader,
1855
+ right,
1856
+ rightHeader: right ? regionHeader(regionSections.right, genericPanelsHeader) : void 0,
1857
+ bottom,
1858
+ bottomHeader: bottom ? regionHeader(regionSections.bottom, genericPanelsHeader) : void 0,
1859
+ theme: layout?.theme,
1860
+ density: layout?.density ?? "comfortable",
1861
+ persistenceKey: layout?.persistenceKey,
1862
+ defaultLeftOpen: layout?.defaultLeftOpen ?? Boolean(left),
1863
+ defaultRightOpen: layout?.defaultRightOpen ?? Boolean(right),
1864
+ defaultBottomOpen: layout?.defaultBottomOpen ?? Boolean(bottom),
1865
+ defaultLeftWidth: layout?.defaultLeftWidth,
1866
+ defaultRightWidth: layout?.defaultRightWidth,
1867
+ minLeftWidth: layout?.minLeftWidth,
1868
+ maxLeftWidth: layout?.maxLeftWidth,
1869
+ minRightWidth: layout?.minRightWidth,
1870
+ maxRightWidth: layout?.maxRightWidth,
1871
+ resizable: layout?.resizable,
1872
+ leftOpen: layout?.leftOpen,
1873
+ onLeftOpenChange: layout?.onLeftOpenChange,
1874
+ rightOpen: layout?.rightOpen,
1875
+ onRightOpenChange: layout?.onRightOpenChange,
1876
+ keyboardShortcuts: layout?.keyboardShortcuts,
1877
+ leftCollapsedControl: layout?.leftCollapsedControl,
1878
+ leftContentClassName: railOnly ? "py-0" : void 0,
1879
+ centerHeaderVisibility: quiet ? "auto" : "always",
1880
+ className: cn$1("p-3 lg:p-4", className)
1881
+ });
1882
+ }
1883
+ function AgentWorkbench(props) {
1884
+ return /* @__PURE__ */ jsx(SandboxWorkbench, {
1885
+ ...props,
1886
+ session: {
1887
+ ...props.session,
1888
+ eyebrow: props.session.eyebrow ?? "Agent Session",
1889
+ title: props.session.title ?? /* @__PURE__ */ jsxs("span", {
1890
+ className: "inline-flex items-center gap-2",
1891
+ children: [/* @__PURE__ */ jsx(Bot, { className: "h-4 w-4 text-primary" }), "Execution timeline"]
1892
+ })
1893
+ }
1894
+ });
1895
+ }
1896
+ //#endregion
1897
+ //#region src/workspace/audit-results.tsx
1898
+ /**
1899
+ * AuditResults — structured pass/fail display for form audits.
1900
+ */
1901
+ function AuditResults({ forms, crossFormChecks = [], overallScore, className }) {
1902
+ const totalPassed = forms.reduce((s, f) => s + f.passed, 0);
1903
+ const totalChecks = forms.reduce((s, f) => s + f.passed + f.failed, 0);
1904
+ return /* @__PURE__ */ jsxs("div", {
1905
+ className: cn$1("space-y-3 p-3", className),
1906
+ children: [
1907
+ /* @__PURE__ */ jsxs("div", {
1908
+ className: "flex items-center gap-3 px-3 py-2 rounded-[var(--radius-md)] bg-surface-container-high border border-[var(--md3-outline-variant)]",
1909
+ children: [/* @__PURE__ */ jsx(Shield, { className: cn$1("h-5 w-5", totalChecks === totalPassed ? "text-[var(--code-success)]" : "text-[var(--code-number)]") }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
1910
+ className: "text-sm font-semibold text-foreground",
1911
+ children: [
1912
+ totalPassed,
1913
+ "/",
1914
+ totalChecks,
1915
+ " checks passed"
1916
+ ]
1917
+ }), overallScore !== void 0 && /* @__PURE__ */ jsxs("div", {
1918
+ className: "text-xs text-muted-foreground",
1919
+ children: [
1920
+ "Score: ",
1921
+ overallScore,
1922
+ "/100"
1923
+ ]
1924
+ })] })]
1925
+ }),
1926
+ forms.map((form) => /* @__PURE__ */ jsx(FormAuditCard, { form }, form.formId)),
1927
+ crossFormChecks.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
1928
+ className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1",
1929
+ children: "Cross-Form Checks"
1930
+ }), crossFormChecks.map((check, i) => /* @__PURE__ */ jsx(CheckRow, { check }, i))] })
1931
+ ]
1932
+ });
1933
+ }
1934
+ function FormAuditCard({ form }) {
1935
+ const [expanded, setExpanded] = useState(form.failed > 0);
1936
+ const allPassed = form.failed === 0 && form.found;
1937
+ return /* @__PURE__ */ jsxs("div", {
1938
+ className: "rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-high overflow-hidden",
1939
+ children: [/* @__PURE__ */ jsxs("button", {
1940
+ onClick: () => setExpanded(!expanded),
1941
+ className: "flex items-center gap-2 w-full px-3 py-2 text-left hover:bg-accent transition-colors",
1942
+ children: [
1943
+ allPassed ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-[var(--code-success)] shrink-0" }) : !form.found ? /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4 text-[var(--code-error)] shrink-0" }) : /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4 text-[var(--code-number)] shrink-0" }),
1944
+ /* @__PURE__ */ jsx("span", {
1945
+ className: "text-sm font-medium text-foreground flex-1",
1946
+ children: form.formName || form.formId
1947
+ }),
1948
+ /* @__PURE__ */ jsxs("span", {
1949
+ className: cn$1("text-xs tabular-nums", allPassed ? "text-[var(--code-success)]" : "text-muted-foreground"),
1950
+ children: [
1951
+ form.passed,
1952
+ "/",
1953
+ form.passed + form.failed
1954
+ ]
1955
+ }),
1956
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn$1("h-3 w-3 text-muted-foreground transition-transform", expanded && "rotate-90") })
1957
+ ]
1958
+ }), expanded && /* @__PURE__ */ jsx("div", {
1959
+ className: "border-t border-[var(--md3-outline-variant)] px-3 py-1.5 space-y-0.5",
1960
+ children: form.checks.map((check, i) => /* @__PURE__ */ jsx(CheckRow, { check }, i))
1961
+ })]
1962
+ });
1963
+ }
1964
+ function CheckRow({ check }) {
1965
+ return /* @__PURE__ */ jsxs("div", {
1966
+ className: "flex items-center gap-2 py-1 text-xs",
1967
+ children: [
1968
+ check.passed ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-3 w-3 text-[var(--code-success)] shrink-0" }) : /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3 text-[var(--code-error)] shrink-0" }),
1969
+ /* @__PURE__ */ jsx("span", {
1970
+ className: "text-foreground flex-1 truncate",
1971
+ children: check.label
1972
+ }),
1973
+ /* @__PURE__ */ jsx("span", {
1974
+ className: "text-muted-foreground tabular-nums shrink-0",
1975
+ children: check.passed ? String(check.actual ?? check.expected) : `${check.actual ?? "missing"} ≠ ${check.expected}`
1976
+ })
1977
+ ]
1978
+ });
1979
+ }
1980
+ //#endregion
1981
+ //#region src/workspace/task-board.tsx
1982
+ const INTERACTIVE_SELECTOR = "button, a, input, textarea, select, [role=\"button\"], [role=\"link\"]";
1983
+ function TaskBoard({ items, columns, className, onMoveItem, onClickItem, renderItemMeta, renderColumnAction, renderBadge, columnEmptyState, header, renderColumnBody, renderItemWrapper }) {
1984
+ const grouped = useMemo(() => {
1985
+ const map = /* @__PURE__ */ new Map();
1986
+ for (const col of columns) map.set(col.id, []);
1987
+ for (const item of items) {
1988
+ const list = map.get(item.status);
1989
+ if (list) list.push(item);
1990
+ else map.get(columns[columns.length - 1]?.id)?.push(item);
1991
+ }
1992
+ return map;
1993
+ }, [items, columns]);
1994
+ return /* @__PURE__ */ jsxs("div", {
1995
+ className: cn$1("flex flex-1 flex-col overflow-hidden", className),
1996
+ children: [header, /* @__PURE__ */ jsx("div", {
1997
+ className: "flex flex-1 gap-3 overflow-x-auto p-4",
1998
+ children: columns.map((col) => {
1999
+ const colItems = grouped.get(col.id) ?? [];
2000
+ const itemCards = /* @__PURE__ */ jsxs(Fragment, { children: [colItems.length === 0 && columnEmptyState, colItems.map((item, index) => {
2001
+ const card = /* @__PURE__ */ jsxs("div", {
2002
+ role: onClickItem ? "button" : void 0,
2003
+ tabIndex: onClickItem ? 0 : void 0,
2004
+ onClick: (event) => {
2005
+ if (!onClickItem) return;
2006
+ const interactive = event.target.closest(INTERACTIVE_SELECTOR);
2007
+ if (interactive && interactive !== event.currentTarget) return;
2008
+ onClickItem(item);
2009
+ },
2010
+ onKeyDown: (event) => {
2011
+ if (!onClickItem || event.currentTarget !== event.target) return;
2012
+ if (event.key !== "Enter" && event.key !== " ") return;
2013
+ if (event.repeat) return;
2014
+ event.preventDefault();
2015
+ onClickItem(item);
2016
+ },
2017
+ className: "group w-full rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container-high p-3 text-left transition-colors hover:border-accent/50",
2018
+ children: [
2019
+ /* @__PURE__ */ jsx("p", {
2020
+ className: "text-sm font-medium text-foreground",
2021
+ children: item.title
2022
+ }),
2023
+ item.description && /* @__PURE__ */ jsx("p", {
2024
+ className: "mt-1 text-xs text-muted-foreground line-clamp-2",
2025
+ children: item.description
2026
+ }),
2027
+ (item.priority || item.tags?.length) && /* @__PURE__ */ jsxs("div", {
2028
+ className: "mt-2 flex flex-wrap gap-1.5",
2029
+ children: [item.priority && (renderBadge ? renderBadge(item.priority, "priority") : /* @__PURE__ */ jsx("span", {
2030
+ className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
2031
+ children: item.priority
2032
+ })), item.tags?.map((tag) => renderBadge ? /* @__PURE__ */ jsx("span", { children: renderBadge(tag, "tag") }, tag) : /* @__PURE__ */ jsx("span", {
2033
+ className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] text-muted-foreground",
2034
+ children: tag
2035
+ }, tag))]
2036
+ }),
2037
+ renderItemMeta?.(item)
2038
+ ]
2039
+ }, item.id);
2040
+ return renderItemWrapper ? /* @__PURE__ */ jsx("span", { children: renderItemWrapper(item, index, card) }, item.id) : card;
2041
+ })] });
2042
+ const columnBody = /* @__PURE__ */ jsx("div", {
2043
+ className: "flex flex-1 flex-col gap-2 overflow-y-auto px-2 pb-2 min-h-[80px]",
2044
+ children: renderColumnBody ? renderColumnBody(col.id, itemCards, colItems) : itemCards
2045
+ });
2046
+ return /* @__PURE__ */ jsxs("div", {
2047
+ className: cn$1("flex w-72 shrink-0 flex-col rounded-xl border border-[var(--md3-outline-variant)] bg-surface-container border-t-2", col.accent ?? "border-t-muted-foreground/30"),
2048
+ children: [/* @__PURE__ */ jsxs("div", {
2049
+ className: "flex items-center justify-between px-4 py-3",
2050
+ children: [/* @__PURE__ */ jsxs("div", {
2051
+ className: "flex items-center gap-2",
2052
+ children: [/* @__PURE__ */ jsx("h3", {
2053
+ className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground",
2054
+ children: col.label
2055
+ }), /* @__PURE__ */ jsx("span", {
2056
+ className: "inline-flex h-5 min-w-[20px] items-center justify-center rounded-full border border-[var(--md3-outline-variant)] px-1.5 text-[10px] font-medium text-muted-foreground",
2057
+ children: colItems.length
2058
+ })]
2059
+ }), renderColumnAction?.(col)]
2060
+ }), columnBody]
2061
+ }, col.id);
2062
+ })
2063
+ })]
2064
+ });
2065
+ }
2066
+ //#endregion
2067
+ //#region src/workspace/calendar-view.tsx
2068
+ function toDateKey(d) {
2069
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2070
+ }
2071
+ function getMonthDays(year, month) {
2072
+ const startDay = new Date(year, month, 1).getDay();
2073
+ const days = [];
2074
+ for (let i = startDay - 1; i >= 0; i--) {
2075
+ const d = new Date(year, month, -i);
2076
+ days.push({
2077
+ date: d,
2078
+ key: toDateKey(d),
2079
+ inMonth: false
2080
+ });
2081
+ }
2082
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
2083
+ for (let i = 1; i <= daysInMonth; i++) {
2084
+ const d = new Date(year, month, i);
2085
+ days.push({
2086
+ date: d,
2087
+ key: toDateKey(d),
2088
+ inMonth: true
2089
+ });
2090
+ }
2091
+ const remaining = 7 - days.length % 7;
2092
+ if (remaining < 7) for (let i = 1; i <= remaining; i++) {
2093
+ const d = new Date(year, month + 1, i);
2094
+ days.push({
2095
+ date: d,
2096
+ key: toDateKey(d),
2097
+ inMonth: false
2098
+ });
2099
+ }
2100
+ return days;
2101
+ }
2102
+ /**
2103
+ * CalendarView — month grid calendar with event chips and optional day detail panel.
2104
+ *
2105
+ * Stateless by default (controlled month/selectedDay). Falls back to internal
2106
+ * state when uncontrolled. All rendering is customizable via render props.
2107
+ */
2108
+ function CalendarView({ events, className, month: controlledMonth, year: controlledYear, onMonthChange, selectedDay: controlledSelectedDay, onSelectDay, onDoubleClickDay, renderEventChip, renderDayDetail, headerLeft, headerRight, showDayPanel = true }) {
2109
+ const today = /* @__PURE__ */ new Date();
2110
+ const todayKey = toDateKey(today);
2111
+ const [internalMonth, setInternalMonth] = useState({
2112
+ year: today.getFullYear(),
2113
+ month: today.getMonth()
2114
+ });
2115
+ const [internalSelectedDay, setInternalSelectedDay] = useState(todayKey);
2116
+ const viewYear = controlledYear ?? internalMonth.year;
2117
+ const viewMonth = controlledMonth ?? internalMonth.month;
2118
+ const selectedDay = controlledSelectedDay ?? internalSelectedDay;
2119
+ function setMonth(y, m) {
2120
+ if (onMonthChange) onMonthChange(y, m);
2121
+ else setInternalMonth({
2122
+ year: y,
2123
+ month: m
2124
+ });
2125
+ }
2126
+ function selectDay(key) {
2127
+ if (onSelectDay) onSelectDay(key);
2128
+ else setInternalSelectedDay(key);
2129
+ }
2130
+ function prevMonth() {
2131
+ const m = viewMonth === 0 ? 11 : viewMonth - 1;
2132
+ setMonth(viewMonth === 0 ? viewYear - 1 : viewYear, m);
2133
+ }
2134
+ function nextMonth() {
2135
+ const m = viewMonth === 11 ? 0 : viewMonth + 1;
2136
+ setMonth(viewMonth === 11 ? viewYear + 1 : viewYear, m);
2137
+ }
2138
+ const eventsByDate = useMemo(() => {
2139
+ const map = {};
2140
+ for (const evt of events) {
2141
+ const d = typeof evt.startAt === "string" ? evt.startAt.slice(0, 10) : toDateKey(evt.startAt);
2142
+ if (!map[d]) map[d] = [];
2143
+ map[d].push(evt);
2144
+ }
2145
+ return map;
2146
+ }, [events]);
2147
+ const monthDays = useMemo(() => getMonthDays(viewYear, viewMonth), [viewYear, viewMonth]);
2148
+ const monthLabel = new Date(viewYear, viewMonth).toLocaleString("en-US", {
2149
+ month: "long",
2150
+ year: "numeric"
2151
+ });
2152
+ const selectedDayEvents = selectedDay ? eventsByDate[selectedDay] ?? [] : [];
2153
+ return /* @__PURE__ */ jsxs("div", {
2154
+ className: cn$1("flex flex-1 min-h-0 overflow-hidden", className),
2155
+ children: [/* @__PURE__ */ jsxs("div", {
2156
+ className: "flex-1 flex flex-col overflow-hidden border-r border-[var(--md3-outline-variant)]",
2157
+ children: [
2158
+ /* @__PURE__ */ jsxs("div", {
2159
+ className: "flex items-center justify-between border-b border-[var(--md3-outline-variant)] px-4 py-2.5 shrink-0",
2160
+ children: [/* @__PURE__ */ jsxs("div", {
2161
+ className: "flex items-center gap-2",
2162
+ children: [
2163
+ headerLeft,
2164
+ /* @__PURE__ */ jsx("button", {
2165
+ type: "button",
2166
+ onClick: prevMonth,
2167
+ className: "h-7 w-7 rounded-md hover:bg-surface-container-high flex items-center justify-center text-muted-foreground",
2168
+ children: /* @__PURE__ */ jsx("svg", {
2169
+ width: "16",
2170
+ height: "16",
2171
+ viewBox: "0 0 24 24",
2172
+ fill: "none",
2173
+ stroke: "currentColor",
2174
+ strokeWidth: "2",
2175
+ children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" })
2176
+ })
2177
+ }),
2178
+ /* @__PURE__ */ jsx("span", {
2179
+ className: "text-sm font-semibold text-foreground min-w-[160px] text-center",
2180
+ children: monthLabel
2181
+ }),
2182
+ /* @__PURE__ */ jsx("button", {
2183
+ type: "button",
2184
+ onClick: nextMonth,
2185
+ className: "h-7 w-7 rounded-md hover:bg-surface-container-high flex items-center justify-center text-muted-foreground",
2186
+ children: /* @__PURE__ */ jsx("svg", {
2187
+ width: "16",
2188
+ height: "16",
2189
+ viewBox: "0 0 24 24",
2190
+ fill: "none",
2191
+ stroke: "currentColor",
2192
+ strokeWidth: "2",
2193
+ children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
2194
+ })
2195
+ }),
2196
+ /* @__PURE__ */ jsx("button", {
2197
+ type: "button",
2198
+ onClick: () => {
2199
+ setMonth(today.getFullYear(), today.getMonth());
2200
+ selectDay(todayKey);
2201
+ },
2202
+ className: "h-7 px-2 rounded-md border border-[var(--md3-outline-variant)] text-xs font-medium text-muted-foreground hover:text-foreground",
2203
+ children: "Today"
2204
+ })
2205
+ ]
2206
+ }), headerRight]
2207
+ }),
2208
+ /* @__PURE__ */ jsx("div", {
2209
+ className: "grid grid-cols-7 border-b border-[var(--md3-outline-variant)] shrink-0",
2210
+ children: [
2211
+ "Sun",
2212
+ "Mon",
2213
+ "Tue",
2214
+ "Wed",
2215
+ "Thu",
2216
+ "Fri",
2217
+ "Sat"
2218
+ ].map((d) => /* @__PURE__ */ jsx("div", {
2219
+ className: "px-2 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground text-center",
2220
+ children: d
2221
+ }, d))
2222
+ }),
2223
+ /* @__PURE__ */ jsx("div", {
2224
+ className: "flex-1 grid grid-cols-7 auto-rows-fr overflow-y-auto",
2225
+ children: monthDays.map(({ date, key, inMonth }) => {
2226
+ const dayEvents = eventsByDate[key] ?? [];
2227
+ const isToday = key === todayKey;
2228
+ const isSelected = key === selectedDay;
2229
+ return /* @__PURE__ */ jsxs("button", {
2230
+ type: "button",
2231
+ onClick: () => selectDay(key),
2232
+ onDoubleClick: () => onDoubleClickDay?.(key),
2233
+ className: cn$1("flex flex-col items-start p-1.5 border-b border-r border-[var(--md3-outline-variant)] text-left transition-colors min-h-[72px]", !inMonth && "bg-muted/30", isSelected && "bg-primary/5 ring-1 ring-inset ring-primary/20", !isSelected && "hover:bg-surface-container-high"),
2234
+ children: [/* @__PURE__ */ jsx("span", {
2235
+ className: cn$1("text-xs font-medium w-6 h-6 flex items-center justify-center rounded-full mb-0.5", isToday && "bg-primary text-primary-foreground", !isToday && !inMonth && "text-muted-foreground/50", !isToday && inMonth && "text-foreground"),
2236
+ children: date.getDate()
2237
+ }), /* @__PURE__ */ jsxs("div", {
2238
+ className: "flex flex-col gap-0.5 w-full min-w-0",
2239
+ children: [dayEvents.slice(0, 3).map((evt) => renderEventChip ? /* @__PURE__ */ jsx("div", {
2240
+ className: "w-full min-w-0",
2241
+ children: renderEventChip(evt)
2242
+ }, evt.id) : /* @__PURE__ */ jsx("div", {
2243
+ className: "w-full truncate text-[9px] px-1 py-0.5 rounded bg-primary/10 text-primary",
2244
+ title: evt.title,
2245
+ children: evt.title
2246
+ }, evt.id)), dayEvents.length > 3 && /* @__PURE__ */ jsxs("span", {
2247
+ className: "text-[9px] text-muted-foreground px-1",
2248
+ children: [
2249
+ "+",
2250
+ dayEvents.length - 3,
2251
+ " more"
2252
+ ]
2253
+ })]
2254
+ })]
2255
+ }, key);
2256
+ })
2257
+ })
2258
+ ]
2259
+ }), showDayPanel && /* @__PURE__ */ jsx("div", {
2260
+ className: "w-80 shrink-0 flex flex-col overflow-hidden",
2261
+ children: renderDayDetail ? renderDayDetail(selectedDay ?? todayKey, selectedDayEvents) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2262
+ className: "px-4 py-3 border-b border-[var(--md3-outline-variant)] shrink-0",
2263
+ children: [/* @__PURE__ */ jsx("h3", {
2264
+ className: "text-sm font-semibold text-foreground",
2265
+ children: selectedDay === todayKey ? "Today" : selectedDay ? (/* @__PURE__ */ new Date(selectedDay + "T00:00:00")).toLocaleDateString("en-US", {
2266
+ weekday: "long",
2267
+ month: "long",
2268
+ day: "numeric"
2269
+ }) : "Select a day"
2270
+ }), /* @__PURE__ */ jsxs("p", {
2271
+ className: "text-xs text-muted-foreground",
2272
+ children: [
2273
+ selectedDayEvents.length,
2274
+ " event",
2275
+ selectedDayEvents.length !== 1 ? "s" : ""
2276
+ ]
2277
+ })]
2278
+ }), /* @__PURE__ */ jsxs("div", {
2279
+ className: "flex-1 overflow-y-auto p-2 space-y-2",
2280
+ children: [selectedDayEvents.length === 0 && /* @__PURE__ */ jsx("div", {
2281
+ className: "px-2 py-8 text-center",
2282
+ children: /* @__PURE__ */ jsx("p", {
2283
+ className: "text-xs text-muted-foreground",
2284
+ children: "No events this day"
2285
+ })
2286
+ }), selectedDayEvents.map((evt) => /* @__PURE__ */ jsxs("div", {
2287
+ className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-3",
2288
+ children: [/* @__PURE__ */ jsx("p", {
2289
+ className: "text-sm font-medium text-foreground",
2290
+ children: evt.title
2291
+ }), evt.type && /* @__PURE__ */ jsx("span", {
2292
+ className: "mt-1 inline-block rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] text-muted-foreground",
2293
+ children: evt.type
2294
+ })]
2295
+ }, evt.id))]
2296
+ })] })
2297
+ })]
2298
+ });
2299
+ }
2300
+ //#endregion
2301
+ //#region src/workspace/approval-queue.tsx
2302
+ /**
2303
+ * ApprovalQueue — review queue for agent-proposed actions.
2304
+ *
2305
+ * Shows pending items with approve/reject controls, confidence stats per type,
2306
+ * and a collapsible resolved history. Designed for the agent approval feedback
2307
+ * loop: agent proposes → user approves/rejects → rejection reasons feed back.
2308
+ */
2309
+ function ApprovalQueue({ items, className, onApprove, onReject, renderItemDetail, renderTypeBadge, renderStats, canResolve = true, header, emptyState, showResolved: controlledShowResolved }) {
2310
+ const [rejectingId, setRejectingId] = useState(null);
2311
+ const [rejectReason, setRejectReason] = useState("");
2312
+ const [showResolved, setShowResolved] = useState(controlledShowResolved ?? false);
2313
+ const pending = useMemo(() => items.filter((a) => a.status === "pending"), [items]);
2314
+ const resolved = useMemo(() => items.filter((a) => a.status !== "pending"), [items]);
2315
+ const stats = useMemo(() => {
2316
+ const map = {};
2317
+ for (const item of items) {
2318
+ if (!map[item.type]) map[item.type] = {
2319
+ type: item.type,
2320
+ approved: 0,
2321
+ rejected: 0,
2322
+ total: 0,
2323
+ rate: 0
2324
+ };
2325
+ map[item.type].total++;
2326
+ if (item.status === "approved" || item.status === "executed") map[item.type].approved++;
2327
+ if (item.status === "rejected") map[item.type].rejected++;
2328
+ }
2329
+ for (const s of Object.values(map)) s.rate = s.total > 0 ? Math.round(s.approved / s.total * 100) : 0;
2330
+ return Object.values(map);
2331
+ }, [items]);
2332
+ if (items.length === 0) return /* @__PURE__ */ jsx("div", {
2333
+ className: cn$1("flex-1 flex items-center justify-center", className),
2334
+ children: emptyState ?? /* @__PURE__ */ jsx("div", {
2335
+ className: "text-center py-20",
2336
+ children: /* @__PURE__ */ jsx("p", {
2337
+ className: "text-sm text-muted-foreground",
2338
+ children: "No proposals yet"
2339
+ })
2340
+ })
2341
+ });
2342
+ return /* @__PURE__ */ jsx("div", {
2343
+ className: cn$1("flex-1 overflow-y-auto", className),
2344
+ children: /* @__PURE__ */ jsxs("div", {
2345
+ className: "max-w-3xl mx-auto p-6",
2346
+ children: [
2347
+ header,
2348
+ stats.length > 0 && /* @__PURE__ */ jsx("div", {
2349
+ className: "flex gap-3 mb-6 flex-wrap",
2350
+ children: renderStats ? renderStats(stats) : stats.map((s) => /* @__PURE__ */ jsxs("div", {
2351
+ className: "flex-1 min-w-[120px] rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-3",
2352
+ children: [
2353
+ /* @__PURE__ */ jsx("div", {
2354
+ className: "flex items-center gap-2 mb-1",
2355
+ children: renderTypeBadge ? renderTypeBadge(s.type) : /* @__PURE__ */ jsx("span", {
2356
+ className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
2357
+ children: s.type
2358
+ })
2359
+ }),
2360
+ /* @__PURE__ */ jsx("div", {
2361
+ className: "flex items-baseline gap-1",
2362
+ children: /* @__PURE__ */ jsxs("span", {
2363
+ className: "text-xl font-semibold text-foreground",
2364
+ children: [s.rate, "%"]
2365
+ })
2366
+ }),
2367
+ /* @__PURE__ */ jsxs("p", {
2368
+ className: "text-[10px] text-muted-foreground",
2369
+ children: [
2370
+ s.approved,
2371
+ "/",
2372
+ s.total,
2373
+ " approved"
2374
+ ]
2375
+ })
2376
+ ]
2377
+ }, s.type))
2378
+ }),
2379
+ pending.length > 0 && /* @__PURE__ */ jsxs("section", {
2380
+ className: "mb-8",
2381
+ children: [/* @__PURE__ */ jsxs("h3", {
2382
+ className: "text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3",
2383
+ children: [
2384
+ "Pending (",
2385
+ pending.length,
2386
+ ")"
2387
+ ]
2388
+ }), /* @__PURE__ */ jsx("div", {
2389
+ className: "space-y-3",
2390
+ children: pending.map((item) => /* @__PURE__ */ jsxs("div", {
2391
+ className: "rounded-lg border border-primary/20 bg-surface-container p-4",
2392
+ children: [/* @__PURE__ */ jsxs("div", {
2393
+ className: "flex items-start gap-3",
2394
+ children: [/* @__PURE__ */ jsxs("div", {
2395
+ className: "flex-1 min-w-0",
2396
+ children: [
2397
+ /* @__PURE__ */ jsxs("div", {
2398
+ className: "flex items-center gap-2 mb-1",
2399
+ children: [/* @__PURE__ */ jsx("h4", {
2400
+ className: "text-sm font-medium text-foreground",
2401
+ children: item.title
2402
+ }), renderTypeBadge ? renderTypeBadge(item.type) : /* @__PURE__ */ jsx("span", {
2403
+ className: "rounded-full border border-[var(--md3-outline-variant)] px-2 py-0.5 text-[10px] text-muted-foreground",
2404
+ children: item.type
2405
+ })]
2406
+ }),
2407
+ item.description && /* @__PURE__ */ jsx("p", {
2408
+ className: "text-xs text-muted-foreground line-clamp-2",
2409
+ children: item.description
2410
+ }),
2411
+ renderItemDetail?.(item)
2412
+ ]
2413
+ }), canResolve && /* @__PURE__ */ jsxs("div", {
2414
+ className: "flex gap-1.5 shrink-0",
2415
+ children: [/* @__PURE__ */ jsx("button", {
2416
+ type: "button",
2417
+ onClick: () => onApprove?.(item),
2418
+ className: "h-8 px-3 rounded-md border border-[var(--md3-outline-variant)] text-xs font-medium text-emerald-500 hover:bg-emerald-500/10 hover:border-emerald-500/30 transition-colors",
2419
+ children: "Approve"
2420
+ }), /* @__PURE__ */ jsx("button", {
2421
+ type: "button",
2422
+ onClick: () => {
2423
+ setRejectingId(rejectingId === item.id ? null : item.id);
2424
+ setRejectReason("");
2425
+ },
2426
+ className: "h-8 px-3 rounded-md border border-[var(--md3-outline-variant)] text-xs font-medium text-destructive hover:bg-destructive/10 hover:border-destructive/30 transition-colors",
2427
+ children: "Reject"
2428
+ })]
2429
+ })]
2430
+ }), rejectingId === item.id && /* @__PURE__ */ jsxs("div", {
2431
+ className: "mt-3 border-t border-[var(--md3-outline-variant)] pt-3",
2432
+ children: [/* @__PURE__ */ jsx("textarea", {
2433
+ placeholder: "Why are you rejecting this?",
2434
+ value: rejectReason,
2435
+ onChange: (e) => setRejectReason(e.target.value),
2436
+ className: `w-full rounded-md border bg-surface-container-low px-3 py-2 text-sm placeholder:text-muted-foreground mb-2 ${focusField}`,
2437
+ rows: 2
2438
+ }), /* @__PURE__ */ jsxs("div", {
2439
+ className: "flex gap-2 justify-end",
2440
+ children: [/* @__PURE__ */ jsx("button", {
2441
+ type: "button",
2442
+ onClick: () => {
2443
+ setRejectingId(null);
2444
+ setRejectReason("");
2445
+ },
2446
+ className: "h-7 px-3 rounded-md text-xs text-muted-foreground hover:text-foreground",
2447
+ children: "Cancel"
2448
+ }), /* @__PURE__ */ jsx("button", {
2449
+ type: "button",
2450
+ onClick: () => {
2451
+ onReject?.(item, rejectReason.trim() || void 0);
2452
+ setRejectingId(null);
2453
+ setRejectReason("");
2454
+ },
2455
+ className: "h-7 px-3 rounded-md bg-destructive text-destructive-foreground text-xs font-medium",
2456
+ children: "Submit Rejection"
2457
+ })]
2458
+ })]
2459
+ })]
2460
+ }, item.id))
2461
+ })]
2462
+ }),
2463
+ resolved.length > 0 && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsxs("button", {
2464
+ type: "button",
2465
+ onClick: () => setShowResolved(!showResolved),
2466
+ className: "flex items-center gap-2 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3 hover:text-foreground transition-colors",
2467
+ children: [
2468
+ /* @__PURE__ */ jsx("svg", {
2469
+ width: "12",
2470
+ height: "12",
2471
+ viewBox: "0 0 24 24",
2472
+ fill: "none",
2473
+ stroke: "currentColor",
2474
+ strokeWidth: "2",
2475
+ className: cn$1("transition-transform", showResolved && "rotate-180"),
2476
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
2477
+ }),
2478
+ "Resolved (",
2479
+ resolved.length,
2480
+ ")"
2481
+ ]
2482
+ }), showResolved && /* @__PURE__ */ jsx("div", {
2483
+ className: "space-y-2",
2484
+ children: resolved.map((item) => /* @__PURE__ */ jsxs("div", {
2485
+ className: "rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container p-4 opacity-60",
2486
+ children: [/* @__PURE__ */ jsxs("div", {
2487
+ className: "flex items-center gap-2 mb-1",
2488
+ children: [
2489
+ /* @__PURE__ */ jsx("h4", {
2490
+ className: "text-sm font-medium text-foreground",
2491
+ children: item.title
2492
+ }),
2493
+ renderTypeBadge?.(item.type),
2494
+ /* @__PURE__ */ jsx("span", {
2495
+ className: cn$1("rounded-full px-2 py-0.5 text-[10px] font-medium", item.status === "approved" && "bg-emerald-500/10 text-emerald-500", item.status === "rejected" && "bg-destructive/10 text-destructive", item.status === "executed" && "bg-primary/10 text-primary"),
2496
+ children: item.status
2497
+ })
2498
+ ]
2499
+ }), item.meta?.rejectionReason != null && /* @__PURE__ */ jsxs("p", {
2500
+ className: "mt-1 text-xs text-destructive",
2501
+ children: ["Reason: ", String(item.meta.rejectionReason)]
2502
+ })]
2503
+ }, item.id))
2504
+ })] })
2505
+ ]
2506
+ })
2507
+ });
2508
+ }
2509
+ //#endregion
2510
+ export { WorkspacePaneHeader as S, TerminalPanel as _, AuditResults as a, DirectoryPane as b, SessionActivityMonitor as c, SessionSidebar as d, SESSION_CREATED_EVENT as f, RuntimePane as g, formatRelativeAge as h, TaskBoard as i, createShellShouldRevalidate as l, useOptimisticSessionItems as m, ApprovalQueue as n, AgentWorkbench as o, dispatchSessionCreated as p, CalendarView as r, SandboxWorkbench as s, ArtifactPane$1 as t, shellShouldRevalidate as u, StatusBar as v, WorkspaceLayout as x, StatusBanner as y };