@tangle-network/sandbox-ui 0.137.1 → 0.138.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 (92) hide show
  1. package/dist/{WorkflowGraph-BcuJ0kgt.js → WorkflowGraph-DXmu60At.js} +4 -7
  2. package/dist/assets.d.ts +0 -18
  3. package/dist/assets.js +9 -17
  4. package/dist/{auth-CWe17Hly.js → auth-BDpoZ_2f.js} +1 -2
  5. package/dist/auth.d.ts +1 -1
  6. package/dist/auth.js +1 -2
  7. package/dist/{chat-_aHUfqtH.js → chat-BYTVLZQW.js} +9 -15
  8. package/dist/chat.d.ts +1 -1
  9. package/dist/chat.js +1 -2
  10. package/dist/{connections-C5DxYxvz.js → connections-DymjzHe_.js} +1 -2
  11. package/dist/connections.d.ts +1 -1
  12. package/dist/connections.js +1 -2
  13. package/dist/connectors.d.ts +0 -10
  14. package/dist/connectors.js +5 -9
  15. package/dist/{dashboard-SFakZEtu.js → dashboard-ClKA3opr.js} +31 -55
  16. package/dist/dashboard.d.ts +3 -3
  17. package/dist/dashboard.js +3 -3
  18. package/dist/diff-view-A0O0Z_SR.js +287 -0
  19. package/dist/diff-view-DDHWtR6X.d.ts +21 -0
  20. package/dist/editor.js +1 -2
  21. package/dist/{embedded-app-view-CYb9hevq.js → embedded-app-view-DXsoPale.js} +5 -8
  22. package/dist/{embedded-app-view-B_vMTtJn.d.ts → embedded-app-view-Ta5GI4hx.d.ts} +0 -4
  23. package/dist/files.js +1 -2
  24. package/dist/globals.css +43 -0
  25. package/dist/hooks.d.ts +3 -7
  26. package/dist/hooks.js +4 -6
  27. package/dist/{index-DsU8ts9f.d.ts → index-BAEvGqrn.d.ts} +0 -4
  28. package/dist/{index-DvmZLMT1.d.ts → index-BbfWVtZq.d.ts} +0 -2
  29. package/dist/index-C5h5T0nb.d.ts +91 -0
  30. package/dist/{index-ByzdHyte.d.ts → index-CvtEhIpk.d.ts} +25 -52
  31. package/dist/{index-ClRHbsoX.d.ts → index-DLX35YAa.d.ts} +0 -2
  32. package/dist/{index-AVDzT8Jw.d.ts → index-DRDtTgb0.d.ts} +2 -54
  33. package/dist/{index-C61CTrhx.d.ts → index-F42BOxUM.d.ts} +15 -37
  34. package/dist/{index-QBCEflyf.d.ts → index-ucJHuuVZ.d.ts} +2 -8
  35. package/dist/{index-DGil6iqm.d.ts → index-y2OPUSoY.d.ts} +0 -4
  36. package/dist/index.d.ts +17 -16
  37. package/dist/index.js +24 -20
  38. package/dist/integrations.d.ts +0 -14
  39. package/dist/integrations.js +6 -11
  40. package/dist/markdown.js +1 -2
  41. package/dist/{model-brand-B57CV8Lg.d.ts → model-brand-ZwVS49Fq.d.ts} +0 -2
  42. package/dist/{model-brand-BSr9on8C.js → model-brand-bTcMjQtB.js} +34 -67
  43. package/dist/{motion-D4njYpys.js → motion-Bn7ESHrj.js} +1 -2
  44. package/dist/{node-output-fuk0NV0V.js → node-output-udnwhShN.js} +9 -16
  45. package/dist/openui.js +1 -2
  46. package/dist/{page-layout-2IRtfx51.js → page-layout-CuOpvZys.js} +5 -9
  47. package/dist/pages.d.ts +2 -14
  48. package/dist/pages.js +9 -15
  49. package/dist/{primitives-CLOVnjWz.js → primitives-CiwDp4-0.js} +4 -7
  50. package/dist/primitives.d.ts +1 -1
  51. package/dist/primitives.js +1 -2
  52. package/dist/{provider-logo-BGCziCbM.js → provider-logo-CPKRPHcS.js} +1 -2
  53. package/dist/{rolldown-runtime-BBjsoOtd.js → rolldown-runtime-782esTHO.js} +1 -2
  54. package/dist/run-CliTxkfw.js +397 -0
  55. package/dist/run.d.ts +2 -2
  56. package/dist/run.js +1 -2
  57. package/dist/sdk-hooks.d.ts +1 -1
  58. package/dist/sdk-hooks.js +1 -1
  59. package/dist/{shell-header-CTh3oV_e.js → shell-header-CwqDzC4I.js} +2 -3
  60. package/dist/{sidecar-DgH_vQ6m.d.ts → sidecar-CNP4zXop.d.ts} +0 -2
  61. package/dist/stores.d.ts +0 -2
  62. package/dist/stores.js +1 -2
  63. package/dist/styles.css +43 -0
  64. package/dist/{terminal-X6r5pjgv.js → terminal-nUCznZRj.js} +3 -5
  65. package/dist/terminal.d.ts +1 -3
  66. package/dist/terminal.js +1 -2
  67. package/dist/{tree-data-DtoCNNBJ.js → tree-data-YXtcK6Ld.js} +2 -4
  68. package/dist/types.d.ts +1 -1
  69. package/dist/{usage-chart-BjM8ShvX.d.ts → usage-chart-BFpdkqNq.d.ts} +0 -6
  70. package/dist/{use-pty-session-B5Q_i9RI.js → use-pty-session-BHVQlSs-.js} +1 -2
  71. package/dist/{use-pty-session-DbAYO5uH.d.ts → use-pty-session-DSgtQyYX.d.ts} +0 -2
  72. package/dist/{use-sidecar-auth-CCTJcrq7.d.ts → use-sidecar-auth-CPevSSEj.d.ts} +0 -4
  73. package/dist/{use-sidecar-auth-DkdcufOz.js → use-sidecar-auth-DpG0aWQ3.js} +3 -6
  74. package/dist/utils-BER_bdgE.js +1 -0
  75. package/dist/utils.js +1 -2
  76. package/dist/vault-tree.d.ts +1 -1
  77. package/dist/vault-tree.js +1 -2
  78. package/dist/workbench/diff.d.ts +2 -0
  79. package/dist/workbench/diff.js +1 -0
  80. package/dist/workbench/embedded-app.d.ts +1 -1
  81. package/dist/workbench/embedded-app.js +1 -2
  82. package/dist/workbench-B3JL1qbt.js +892 -0
  83. package/dist/workbench.d.ts +4 -3
  84. package/dist/workbench.js +4 -3
  85. package/dist/workflows.d.ts +1 -13
  86. package/dist/workflows.js +4 -5
  87. package/dist/{workspace-cjk0axyT.js → workspace-DRhi6S3o.js} +325 -141
  88. package/dist/workspace.d.ts +1 -1
  89. package/dist/workspace.js +2 -2
  90. package/package.json +36 -28
  91. package/dist/utils-DzWbktWG.js +0 -2
  92. package/dist/workbench-DPZ6LXqf.js +0 -8136
@@ -0,0 +1,892 @@
1
+ import { t as cn$1 } from "./utils-BER_bdgE.js";
2
+ import { a as PortsList } from "./dashboard-ClKA3opr.js";
3
+ import { n as TerminalView } from "./terminal-nUCznZRj.js";
4
+ import { RichFileTree } from "./files.js";
5
+ import { a as CodeSurface, n as buildUnifiedPatch, r as computeDiffStats, t as DiffView } from "./diff-view-A0O0Z_SR.js";
6
+ import { n as PreviewView } from "./embedded-app-view-DXsoPale.js";
7
+ import { Button, Skeleton, Textarea } from "@tangle-network/ui/primitives";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import * as React$1 from "react";
10
+ import { Check, ChevronRight, Code2, Copy, FileCode2, GitBranch, GitCompare, Globe, Network, RefreshCw, SquareTerminal, WrapText } from "lucide-react";
11
+ import { focusRing, focusRingInset } from "@tangle-network/ui/utils";
12
+ // src/workbench/pill-tabs.tsx
13
+ /**
14
+ * Sliding-pill segmented control. Reimplemented from blueprint-agent's
15
+ * framer-motion `PillTabs` with a measured CSS transform instead — no motion
16
+ * dependency. A `ResizeObserver` re-measures on container resize and font
17
+ * load so the indicator tracks the active tab exactly. Token-swapped onto the
18
+ * sandbox-ui surface ladder.
19
+ */
20
+ function PillTabs({ items, value, onChange, className, "aria-label": ariaLabel, idPrefix, panelId }) {
21
+ const containerRef = React$1.useRef(null);
22
+ const tabRefs = React$1.useRef(/* @__PURE__ */ new Map());
23
+ const [indicator, setIndicator] = React$1.useState(null);
24
+ const measure = React$1.useCallback(() => {
25
+ const container = containerRef.current;
26
+ const active = tabRefs.current.get(value);
27
+ if (!container || !active) return;
28
+ const c = container.getBoundingClientRect();
29
+ const a = active.getBoundingClientRect();
30
+ setIndicator({
31
+ left: a.left - c.left,
32
+ width: a.width
33
+ });
34
+ }, [value]);
35
+ React$1.useLayoutEffect(() => {
36
+ measure();
37
+ }, [measure, items]);
38
+ React$1.useEffect(() => {
39
+ const container = containerRef.current;
40
+ if (!container) return;
41
+ const ro = new ResizeObserver(() => measure());
42
+ ro.observe(container);
43
+ const raf = requestAnimationFrame(measure);
44
+ return () => {
45
+ ro.disconnect();
46
+ cancelAnimationFrame(raf);
47
+ };
48
+ }, [measure]);
49
+ return /* @__PURE__ */ jsxs("div", {
50
+ ref: containerRef,
51
+ role: "tablist",
52
+ "aria-label": ariaLabel,
53
+ className: cn$1("relative inline-flex items-center gap-0.5 rounded-full border border-[var(--md3-outline-variant)] bg-surface-container-low p-1", className),
54
+ children: [indicator && /* @__PURE__ */ jsx("span", {
55
+ "aria-hidden": true,
56
+ className: "absolute top-1 bottom-1 rounded-full bg-surface-container-high shadow-[var(--shadow-card)] ring-1 ring-[var(--md3-outline-variant)] transition-[transform,width] duration-200 ease-out",
57
+ style: {
58
+ transform: `translateX(${indicator.left}px)`,
59
+ width: indicator.width,
60
+ left: 0
61
+ }
62
+ }), items.map((item) => {
63
+ const selected = item.value === value;
64
+ return /* @__PURE__ */ jsxs("button", {
65
+ ref: (el) => {
66
+ if (el) tabRefs.current.set(item.value, el);
67
+ else tabRefs.current.delete(item.value);
68
+ },
69
+ type: "button",
70
+ role: "tab",
71
+ id: idPrefix ? `${idPrefix}-${item.value}` : void 0,
72
+ "aria-controls": panelId,
73
+ "aria-selected": selected,
74
+ tabIndex: selected ? 0 : -1,
75
+ onClick: () => onChange(item.value),
76
+ onKeyDown: (event) => {
77
+ const index = items.findIndex((candidate) => candidate.value === item.value);
78
+ let nextIndex;
79
+ switch (event.key) {
80
+ case "ArrowRight":
81
+ nextIndex = (index + 1) % items.length;
82
+ break;
83
+ case "ArrowLeft":
84
+ nextIndex = (index - 1 + items.length) % items.length;
85
+ break;
86
+ case "Home":
87
+ nextIndex = 0;
88
+ break;
89
+ case "End":
90
+ nextIndex = items.length - 1;
91
+ break;
92
+ default: return;
93
+ }
94
+ event.preventDefault();
95
+ const next = items[nextIndex];
96
+ onChange(next.value);
97
+ const nextTab = tabRefs.current.get(next.value);
98
+ const container = containerRef.current;
99
+ if (!nextTab || !container) return;
100
+ nextTab.focus({ preventScroll: true });
101
+ const padding = getComputedStyle(container);
102
+ const target = nextTab.getBoundingClientRect();
103
+ const targetLeft = target.left - (Number.parseFloat(padding.paddingLeft) || 0);
104
+ const targetRight = target.right + (Number.parseFloat(padding.paddingRight) || 0);
105
+ for (let scrollport = container; scrollport; scrollport = scrollport.parentElement) {
106
+ if (![
107
+ "auto",
108
+ "scroll",
109
+ "hidden"
110
+ ].includes(getComputedStyle(scrollport).overflowX)) continue;
111
+ const left = scrollport.getBoundingClientRect().left + scrollport.clientLeft;
112
+ const right = left + scrollport.clientWidth;
113
+ scrollport.scrollLeft += targetLeft < left ? targetLeft - left : targetRight > right ? targetRight - right : 0;
114
+ break;
115
+ }
116
+ },
117
+ className: cn$1("relative z-10 inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors", focusRing, selected ? "text-foreground" : "text-muted-foreground hover:text-foreground"),
118
+ children: [item.icon && /* @__PURE__ */ jsx("span", {
119
+ className: "flex h-3.5 w-3.5 items-center justify-center",
120
+ children: item.icon
121
+ }), item.label]
122
+ }, item.value);
123
+ })]
124
+ });
125
+ }
126
+ // src/workbench/panel-header.tsx
127
+ const GIT_STATUS_META = {
128
+ added: {
129
+ label: "Added",
130
+ className: "bg-[var(--surface-success-bg)] text-[var(--surface-success-text)]"
131
+ },
132
+ modified: {
133
+ label: "Modified",
134
+ className: "bg-[var(--surface-warning-bg)] text-[var(--surface-warning-text)]"
135
+ },
136
+ deleted: {
137
+ label: "Deleted",
138
+ className: "bg-[var(--surface-danger-bg)] text-[var(--surface-danger-text)]"
139
+ },
140
+ renamed: {
141
+ label: "Renamed",
142
+ className: "bg-[var(--surface-info-bg)] text-[var(--surface-info-text)]"
143
+ },
144
+ untracked: {
145
+ label: "Untracked",
146
+ className: "bg-surface-container-high text-muted-foreground"
147
+ },
148
+ ignored: {
149
+ label: "Ignored",
150
+ className: "bg-surface-container-high text-muted-foreground"
151
+ }
152
+ };
153
+ function GitStatusBadge({ status }) {
154
+ const meta = GIT_STATUS_META[status];
155
+ return /* @__PURE__ */ jsx("span", {
156
+ className: cn$1("inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider", meta.className),
157
+ children: meta.label
158
+ });
159
+ }
160
+ /** `+added −removed` line counts in mono; the same pair the Changes list prints per file. */
161
+ function DiffStatsBadge({ stats, className }) {
162
+ return /* @__PURE__ */ jsxs("span", {
163
+ className: cn$1("inline-flex items-center gap-1.5 font-mono text-xs", className),
164
+ children: [/* @__PURE__ */ jsxs("span", {
165
+ className: "text-[var(--code-success,#10B981)]",
166
+ children: ["+", stats.added]
167
+ }), /* @__PURE__ */ jsxs("span", {
168
+ className: "text-[var(--code-error,#FF4D6D)]",
169
+ children: ["-", stats.removed]
170
+ })]
171
+ });
172
+ }
173
+ /**
174
+ * Thin bordered chrome bar above the artifact body — filename, git-status
175
+ * badge, optional diff counts, and a copy action. Ported in spirit from
176
+ * blueprint-agent's `PanelHeader` (min-h, hairline bottom border, dense
177
+ * horizontal rhythm), retoned to the sandbox-ui surface ladder.
178
+ */
179
+ function PanelHeader({ filename, gitStatus, stats, copyText, children, className }) {
180
+ const [copied, setCopied] = React$1.useState(false);
181
+ const timer = React$1.useRef(null);
182
+ React$1.useEffect(() => () => {
183
+ if (timer.current) clearTimeout(timer.current);
184
+ }, []);
185
+ const handleCopy = React$1.useCallback(async () => {
186
+ if (copyText == null) return;
187
+ try {
188
+ await navigator.clipboard.writeText(copyText);
189
+ setCopied(true);
190
+ if (timer.current) clearTimeout(timer.current);
191
+ timer.current = setTimeout(() => setCopied(false), 2e3);
192
+ } catch (err) {
193
+ console.warn("Clipboard write failed:", err);
194
+ }
195
+ }, [copyText]);
196
+ return /* @__PURE__ */ jsxs("div", {
197
+ className: cn$1("flex min-h-[38px] items-center gap-2 border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-3 py-1", className),
198
+ children: [
199
+ /* @__PURE__ */ jsx("span", {
200
+ className: "truncate font-mono text-xs text-foreground",
201
+ children: filename
202
+ }),
203
+ gitStatus && /* @__PURE__ */ jsx(GitStatusBadge, { status: gitStatus }),
204
+ /* @__PURE__ */ jsxs("div", {
205
+ className: "ml-auto flex items-center gap-3",
206
+ children: [
207
+ stats && /* @__PURE__ */ jsx(DiffStatsBadge, { stats }),
208
+ children,
209
+ copyText != null && /* @__PURE__ */ jsxs("button", {
210
+ type: "button",
211
+ onClick: handleCopy,
212
+ "aria-label": "Copy to clipboard",
213
+ className: "inline-flex items-center gap-1.5 rounded-md border border-[var(--md3-outline-variant)] bg-surface-container px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
214
+ children: [copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-[var(--surface-success-text)]" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }), copied ? "Copied" : "Copy"]
215
+ })
216
+ ]
217
+ })
218
+ ]
219
+ });
220
+ }
221
+ // src/workbench/file-breadcrumb.tsx
222
+ /**
223
+ * Slim path breadcrumb for the code panel. Reimplemented (not ported) from
224
+ * blueprint-agent's `FileBreadcrumb` — that one nests a `FileTree` dropdown per
225
+ * segment and couples to the workbench store + framer-motion. Here the rail
226
+ * already owns navigation, so this is a read-only trail: caret-separated parent
227
+ * segments with a file-icon leaf.
228
+ */
229
+ function FileBreadcrumb({ path, className }) {
230
+ const segments = path.split("/").filter(Boolean);
231
+ const leaf = segments[segments.length - 1] ?? path;
232
+ const parents = segments.slice(0, -1);
233
+ return /* @__PURE__ */ jsxs("div", {
234
+ className: cn$1("flex min-h-[30px] items-center gap-1 overflow-hidden border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest px-3 text-xs text-muted-foreground", className),
235
+ children: [
236
+ /* @__PURE__ */ jsx(FileCode2, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
237
+ parents.map((seg, i) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsx("span", {
238
+ className: "truncate font-mono",
239
+ children: seg
240
+ }), /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 shrink-0 opacity-50" })] }, `${seg}-${i}`)),
241
+ /* @__PURE__ */ jsx("span", {
242
+ className: "truncate font-mono text-foreground",
243
+ children: leaf
244
+ })
245
+ ]
246
+ });
247
+ }
248
+ // src/workbench/code-view.tsx
249
+ /**
250
+ * Code surface: a fixed-width `RichFileTree` rail (with git-status
251
+ * decorations) beside a breadcrumb-topped, syntax-highlighted read-only file
252
+ * view. The rail owns navigation; selecting a path fans out to `onFileSelect`
253
+ * so the host can swap the active artifact.
254
+ */
255
+ function CodeView({ path, filename, content, language, paths, gitStatus, onFileSelect, showTree = true, defaultWrap = false, themeType, className }) {
256
+ const [narrowPane, setNarrowPane] = React$1.useState("code");
257
+ const [wrap, setWrap] = React$1.useState(defaultWrap);
258
+ const [copied, setCopied] = React$1.useState(false);
259
+ const [line, setLine] = React$1.useState();
260
+ const lineInput = React$1.useRef(null);
261
+ const lineCount = React$1.useMemo(() => content.split("\n").length, [content]);
262
+ React$1.useEffect(() => {
263
+ setLine(void 0);
264
+ }, [path]);
265
+ React$1.useEffect(() => {
266
+ if (!copied) return;
267
+ const timer = setTimeout(() => setCopied(false), 1500);
268
+ return () => clearTimeout(timer);
269
+ }, [copied]);
270
+ const copy = () => {
271
+ navigator.clipboard?.writeText(content).then(() => setCopied(true));
272
+ };
273
+ const goToLine = (event) => {
274
+ event.preventDefault();
275
+ const value = Math.round(Number(lineInput.current?.value));
276
+ if (Number.isFinite(value) && value >= 1) setLine(Math.min(value, lineCount));
277
+ };
278
+ const treePaths = React$1.useMemo(() => {
279
+ const list = paths && paths.length > 0 ? paths : [path];
280
+ return list.includes(path) ? list : [...list, path];
281
+ }, [paths, path]);
282
+ return /* @__PURE__ */ jsx("div", {
283
+ className: cn$1("sandbox-code-view h-full min-h-0 w-full", className),
284
+ children: /* @__PURE__ */ jsxs("div", {
285
+ className: "sandbox-code-view-layout flex h-full min-h-0 w-full",
286
+ children: [
287
+ showTree && /* @__PURE__ */ jsxs("div", {
288
+ className: "sandbox-code-view-mobile-toolbar",
289
+ role: "group",
290
+ "aria-label": "Code view pane",
291
+ children: [/* @__PURE__ */ jsx("button", {
292
+ type: "button",
293
+ "aria-pressed": narrowPane === "files",
294
+ onClick: () => setNarrowPane("files"),
295
+ className: focusRing,
296
+ children: "Files"
297
+ }), /* @__PURE__ */ jsx("button", {
298
+ type: "button",
299
+ "aria-pressed": narrowPane === "code",
300
+ onClick: () => setNarrowPane("code"),
301
+ className: focusRing,
302
+ children: "Code"
303
+ })]
304
+ }),
305
+ showTree && /* @__PURE__ */ jsx("div", {
306
+ className: "sandbox-code-view-tree sandbox-tree-surface flex w-60 shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-lowest",
307
+ "data-mobile-open": narrowPane === "files",
308
+ children: /* @__PURE__ */ jsx(RichFileTree, {
309
+ paths: treePaths,
310
+ selectedPath: path,
311
+ onSelect: (next) => {
312
+ onFileSelect?.(next);
313
+ setNarrowPane("code");
314
+ },
315
+ gitStatus,
316
+ search: treePaths.length > 12,
317
+ height: "100%"
318
+ })
319
+ }),
320
+ /* @__PURE__ */ jsxs("div", {
321
+ className: "sandbox-code-view-editor flex min-w-0 flex-1 flex-col bg-surface-container",
322
+ "data-mobile-hidden": showTree && narrowPane === "files",
323
+ onKeyDown: (event) => {
324
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "g") {
325
+ event.preventDefault();
326
+ lineInput.current?.focus();
327
+ lineInput.current?.select();
328
+ }
329
+ },
330
+ children: [/* @__PURE__ */ jsxs("div", {
331
+ className: "flex shrink-0 items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest",
332
+ children: [/* @__PURE__ */ jsx(FileBreadcrumb, {
333
+ path,
334
+ className: "min-w-0 flex-1 border-b-0"
335
+ }), /* @__PURE__ */ jsxs("form", {
336
+ onSubmit: goToLine,
337
+ className: "flex shrink-0 items-center gap-0.5 pr-1",
338
+ children: [
339
+ /* @__PURE__ */ jsx("input", {
340
+ ref: lineInput,
341
+ type: "number",
342
+ min: 1,
343
+ max: lineCount,
344
+ inputMode: "numeric",
345
+ placeholder: "Line",
346
+ "aria-label": "Go to line",
347
+ "aria-keyshortcuts": "Control+g Meta+g",
348
+ className: cn$1("h-6 w-14 rounded border border-transparent bg-transparent px-1.5 font-mono text-xs text-foreground placeholder:text-muted-foreground hover:border-border", focusRing)
349
+ }),
350
+ /* @__PURE__ */ jsx(Button, {
351
+ type: "button",
352
+ variant: "ghost",
353
+ size: "sm",
354
+ "aria-pressed": wrap,
355
+ "aria-label": "Wrap lines",
356
+ title: "Wrap lines",
357
+ onClick: () => setWrap((value) => !value),
358
+ className: cn$1("h-6 w-6 p-0", wrap && "text-foreground bg-surface-container-high"),
359
+ children: /* @__PURE__ */ jsx(WrapText, { className: "h-3.5 w-3.5" })
360
+ }),
361
+ /* @__PURE__ */ jsx(Button, {
362
+ type: "button",
363
+ variant: "ghost",
364
+ size: "sm",
365
+ "aria-label": copied ? "Copied" : "Copy file",
366
+ title: copied ? "Copied" : "Copy file",
367
+ onClick: copy,
368
+ className: "h-6 w-6 p-0",
369
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
370
+ })
371
+ ]
372
+ })]
373
+ }), /* @__PURE__ */ jsx("div", {
374
+ className: "min-h-0 flex-1",
375
+ children: /* @__PURE__ */ jsx(CodeSurface, {
376
+ code: content,
377
+ filename,
378
+ language,
379
+ wrap,
380
+ line,
381
+ themeType
382
+ })
383
+ })]
384
+ })
385
+ ]
386
+ })
387
+ });
388
+ }
389
+ // src/workbench/sandbox-artifact-pane.tsx
390
+ const VIEW_ORDER = [
391
+ "code",
392
+ "diff",
393
+ "preview",
394
+ "ports",
395
+ "terminal"
396
+ ];
397
+ const VIEW_META = {
398
+ code: {
399
+ label: "Code",
400
+ icon: /* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" })
401
+ },
402
+ diff: {
403
+ label: "Diff",
404
+ icon: /* @__PURE__ */ jsx(GitCompare, { className: "h-3.5 w-3.5" })
405
+ },
406
+ preview: {
407
+ label: "Preview",
408
+ icon: /* @__PURE__ */ jsx(Globe, { className: "h-3.5 w-3.5" })
409
+ },
410
+ ports: {
411
+ label: "Ports",
412
+ icon: /* @__PURE__ */ jsx(Network, { className: "h-3.5 w-3.5" })
413
+ },
414
+ terminal: {
415
+ label: "Terminal",
416
+ icon: /* @__PURE__ */ jsx(SquareTerminal, { className: "h-3.5 w-3.5" })
417
+ }
418
+ };
419
+ const noop = () => {};
420
+ /**
421
+ * Blueprint-grade artifact workbench. Composes the sandbox-ui substrate
422
+ * (`RichFileTree`, `PortsList`, `TerminalView`, `@pierre/diffs`) behind a
423
+ * single pill-tab view switcher: Code · Diff · Preview · Ports · Terminal.
424
+ * Tabs appear only when their data is present (Diff requires a differing
425
+ * baseline; Preview a url; Ports a port list; Terminal a live binding), so the
426
+ * chrome never shows a dead surface.
427
+ */
428
+ function SandboxArtifactPane({ artifact, paths, gitStatus, showTree, ports, previewUrl, terminal, activeView, defaultView, onViewChange, onFileSelect, onExposePort, onRemovePort, className, style }) {
429
+ const hasDiff = artifact.baselineContent != null && artifact.baselineContent !== artifact.content;
430
+ const hasPreview = previewUrl != null && previewUrl !== "";
431
+ const hasPorts = ports != null;
432
+ const hasTerminal = terminal != null;
433
+ const available = React$1.useMemo(() => {
434
+ return VIEW_ORDER.filter((v) => {
435
+ if (v === "diff") return hasDiff;
436
+ if (v === "preview") return hasPreview;
437
+ if (v === "ports") return hasPorts;
438
+ if (v === "terminal") return hasTerminal;
439
+ return true;
440
+ });
441
+ }, [
442
+ hasDiff,
443
+ hasPreview,
444
+ hasPorts,
445
+ hasTerminal
446
+ ]);
447
+ const [internalView, setInternalView] = React$1.useState(() => defaultView && (defaultView !== "diff" || hasDiff) ? defaultView : "code");
448
+ const tabIdPrefix = React$1.useId();
449
+ const panelId = `${tabIdPrefix}-panel`;
450
+ const controlled = activeView != null;
451
+ const requestedView = controlled ? activeView : internalView;
452
+ const view = available.includes(requestedView) ? requestedView : available[0];
453
+ const selectView = React$1.useCallback((next) => {
454
+ if (!controlled) setInternalView(next);
455
+ onViewChange?.(next);
456
+ }, [controlled, onViewChange]);
457
+ const tabs = React$1.useMemo(() => available.map((v) => ({
458
+ value: v,
459
+ label: VIEW_META[v].label,
460
+ icon: VIEW_META[v].icon
461
+ })), [available]);
462
+ const activeGitStatus = React$1.useMemo(() => gitStatus?.find((e) => e.path === artifact.path)?.status, [gitStatus, artifact.path]);
463
+ const stats = React$1.useMemo(() => hasDiff ? computeDiffStats(artifact.baselineContent ?? "", artifact.content) : void 0, [
464
+ hasDiff,
465
+ artifact.baselineContent,
466
+ artifact.content
467
+ ]);
468
+ const copyText = React$1.useMemo(() => {
469
+ if (view === "diff" && hasDiff) return buildUnifiedPatch(artifact.filename, artifact.baselineContent ?? "", artifact.content);
470
+ if (view === "code" || view === "diff") return artifact.content;
471
+ }, [
472
+ view,
473
+ hasDiff,
474
+ artifact.filename,
475
+ artifact.baselineContent,
476
+ artifact.content
477
+ ]);
478
+ return /* @__PURE__ */ jsxs("div", {
479
+ className: cn$1("flex h-full min-h-0 flex-col overflow-hidden rounded-xl border border-[var(--md3-outline-variant)] bg-surface-container shadow-[var(--shadow-card)]", className),
480
+ style,
481
+ children: [
482
+ /* @__PURE__ */ jsx(PanelHeader, {
483
+ filename: artifact.filename,
484
+ gitStatus: activeGitStatus,
485
+ stats: view === "diff" ? stats : void 0,
486
+ copyText
487
+ }),
488
+ /* @__PURE__ */ jsx("div", {
489
+ className: "flex min-w-0 items-center overflow-x-auto border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest px-3 py-1.5",
490
+ children: /* @__PURE__ */ jsx(PillTabs, {
491
+ items: tabs,
492
+ value: view,
493
+ onChange: selectView,
494
+ "aria-label": "Artifact view",
495
+ idPrefix: tabIdPrefix,
496
+ panelId
497
+ })
498
+ }),
499
+ /* @__PURE__ */ jsxs("div", {
500
+ id: panelId,
501
+ role: "tabpanel",
502
+ "aria-labelledby": `${tabIdPrefix}-${view}`,
503
+ tabIndex: 0,
504
+ className: `min-h-0 flex-1 ${focusRingInset}`,
505
+ children: [
506
+ view === "code" && /* @__PURE__ */ jsx(CodeView, {
507
+ path: artifact.path,
508
+ filename: artifact.filename,
509
+ content: artifact.content,
510
+ language: artifact.language,
511
+ paths,
512
+ gitStatus,
513
+ showTree,
514
+ onFileSelect
515
+ }),
516
+ view === "diff" && hasDiff && /* @__PURE__ */ jsx(DiffView, {
517
+ filename: artifact.filename,
518
+ baseline: artifact.baselineContent ?? "",
519
+ current: artifact.content
520
+ }),
521
+ view === "preview" && hasPreview && /* @__PURE__ */ jsx(PreviewView, { url: previewUrl }),
522
+ view === "ports" && hasPorts && /* @__PURE__ */ jsx("div", {
523
+ className: "h-full overflow-auto bg-surface-container p-4",
524
+ children: /* @__PURE__ */ jsx(PortsList, {
525
+ ports,
526
+ onExposePort: onExposePort ?? noop,
527
+ onRemovePort
528
+ })
529
+ }),
530
+ view === "terminal" && hasTerminal && /* @__PURE__ */ jsx("div", {
531
+ className: "h-full bg-surface-container-lowest",
532
+ children: /* @__PURE__ */ jsx(TerminalView, {
533
+ apiUrl: terminal.apiUrl,
534
+ token: terminal.token,
535
+ connectionId: terminal.connectionId,
536
+ title: terminal.title,
537
+ subtitle: terminal.subtitle
538
+ })
539
+ })
540
+ ]
541
+ })
542
+ ]
543
+ });
544
+ }
545
+ // src/workbench/changes-pane.tsx
546
+ const BORDER = "border-[var(--md3-outline-variant)]";
547
+ function splitPath(path) {
548
+ const at = path.lastIndexOf("/");
549
+ if (at < 0) return {
550
+ dir: "",
551
+ base: path
552
+ };
553
+ return {
554
+ dir: path.slice(0, at + 1),
555
+ base: path.slice(at + 1)
556
+ };
557
+ }
558
+ /**
559
+ * The contents a diff needs, or `null` while the consumer is still resolving
560
+ * them. An added or untracked file has no baseline, a deleted file has no
561
+ * working copy; each diffs against the empty string once its one side is
562
+ * present. Every other status waits for both sides so a half-resolved
563
+ * modification is never drawn as a wholesale rewrite.
564
+ */
565
+ function resolveContents(file) {
566
+ const { status, baseline, current } = file;
567
+ if (status === "added" || status === "untracked") return current == null ? null : {
568
+ baseline: baseline ?? "",
569
+ current
570
+ };
571
+ if (status === "deleted") return baseline == null ? null : {
572
+ baseline,
573
+ current: current ?? ""
574
+ };
575
+ return baseline == null || current == null ? null : {
576
+ baseline,
577
+ current
578
+ };
579
+ }
580
+ function errorText(err) {
581
+ if (err instanceof Error && err.message) return err.message;
582
+ return String(err);
583
+ }
584
+ const SKELETON_ROWS = [
585
+ 0,
586
+ 1,
587
+ 2,
588
+ 3
589
+ ];
590
+ /**
591
+ * The sandbox's git working tree as a review surface: every changed file with
592
+ * its line counts, the selected file's word-level diff beneath, and a commit
593
+ * box in the footer. Data in, callbacks out — the pane never fetches; the
594
+ * consumer feeds it the sidecar's `/git/status` + `/git/diff` and resolves
595
+ * `baseline` / `current` for the file it is told was selected.
596
+ *
597
+ * The list is capped at 40% of the pane and scrolls, so the diff keeps its
598
+ * room however many files changed. The row's own chrome is compact: the
599
+ * workspace shell already gives its right pane the 56px `WorkspacePaneHeader`,
600
+ * so this header nests beneath it at `PanelHeader` rhythm rather than adding a
601
+ * second tall bar.
602
+ */
603
+ function ChangesPane({ branch, ahead = 0, behind = 0, files, selectedPath, onSelectFile, loading = false, error, onRefresh, onCommit, onPush, busy, onOpenFile, className }) {
604
+ const idPrefix = React$1.useId();
605
+ const listRef = React$1.useRef(null);
606
+ const rowRefs = React$1.useRef(/* @__PURE__ */ new Map());
607
+ const [message, setMessage] = React$1.useState("");
608
+ const [actionError, setActionError] = React$1.useState(null);
609
+ const [pending, setPending] = React$1.useState(null);
610
+ const inFlight = busy ?? pending;
611
+ const totals = React$1.useMemo(() => {
612
+ let added = 0;
613
+ let removed = 0;
614
+ for (const f of files) {
615
+ added += f.additions;
616
+ removed += f.deletions;
617
+ }
618
+ return {
619
+ added,
620
+ removed
621
+ };
622
+ }, [files]);
623
+ const selectedIndex = selectedPath == null ? -1 : files.findIndex((f) => f.path === selectedPath);
624
+ const selected = selectedIndex >= 0 ? files[selectedIndex] : void 0;
625
+ React$1.useEffect(() => {
626
+ const list = listRef.current;
627
+ if (!list || selected == null) return;
628
+ if (!list.contains(document.activeElement)) return;
629
+ rowRefs.current.get(selected.path)?.focus();
630
+ }, [selected]);
631
+ const handleListKeyDown = React$1.useCallback((event) => {
632
+ if (files.length === 0 || !onSelectFile) return;
633
+ const last = files.length - 1;
634
+ let next = null;
635
+ switch (event.key) {
636
+ case "ArrowDown":
637
+ next = selectedIndex < 0 ? 0 : Math.min(selectedIndex + 1, last);
638
+ break;
639
+ case "ArrowUp":
640
+ next = selectedIndex < 0 ? last : Math.max(selectedIndex - 1, 0);
641
+ break;
642
+ case "Home":
643
+ next = 0;
644
+ break;
645
+ case "End":
646
+ next = last;
647
+ break;
648
+ case "Enter":
649
+ case " ":
650
+ next = selectedIndex < 0 ? 0 : selectedIndex;
651
+ break;
652
+ default: return;
653
+ }
654
+ event.preventDefault();
655
+ const file = files[next];
656
+ if (file) onSelectFile(file.path);
657
+ }, [
658
+ files,
659
+ onSelectFile,
660
+ selectedIndex
661
+ ]);
662
+ const runCommit = React$1.useCallback(async () => {
663
+ const text = message.trim();
664
+ if (!onCommit || !text || inFlight) return;
665
+ setActionError(null);
666
+ setPending("commit");
667
+ try {
668
+ await onCommit(text);
669
+ setMessage("");
670
+ } catch (err) {
671
+ setActionError(errorText(err));
672
+ } finally {
673
+ setPending(null);
674
+ }
675
+ }, [
676
+ message,
677
+ onCommit,
678
+ inFlight
679
+ ]);
680
+ const runPush = React$1.useCallback(async () => {
681
+ if (!onPush || inFlight) return;
682
+ setActionError(null);
683
+ setPending("push");
684
+ try {
685
+ await onPush();
686
+ } catch (err) {
687
+ setActionError(errorText(err));
688
+ } finally {
689
+ setPending(null);
690
+ }
691
+ }, [onPush, inFlight]);
692
+ const handleMessageKeyDown = React$1.useCallback((event) => {
693
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
694
+ event.preventDefault();
695
+ runCommit();
696
+ }
697
+ }, [runCommit]);
698
+ const canCommit = files.length > 0 && message.trim().length > 0 && inFlight == null;
699
+ const canPush = ahead > 0 && inFlight == null;
700
+ const contents = React$1.useMemo(() => selected ? resolveContents(selected) : null, [selected]);
701
+ const stats = React$1.useMemo(() => contents ? computeDiffStats(contents.baseline, contents.current) : void 0, [contents]);
702
+ const patch = React$1.useMemo(() => selected && contents ? buildUnifiedPatch(selected.path, contents.baseline, contents.current) : void 0, [selected, contents]);
703
+ const showList = !loading && files.length > 0;
704
+ const showEmpty = !loading && files.length === 0 && !error;
705
+ return /* @__PURE__ */ jsxs("div", {
706
+ className: cn$1("flex h-full min-h-0 flex-col text-foreground", className),
707
+ children: [
708
+ /* @__PURE__ */ jsxs("div", {
709
+ className: cn$1("flex min-h-[38px] shrink-0 items-center gap-2 border-b bg-surface-container-low px-3 py-1", BORDER),
710
+ children: [
711
+ /* @__PURE__ */ jsx(GitBranch, {
712
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
713
+ "aria-hidden": true
714
+ }),
715
+ loading && branch == null ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24 rounded-full" }) : branch == null ? /* @__PURE__ */ jsx("span", {
716
+ className: "text-xs text-muted-foreground",
717
+ children: "no commits yet"
718
+ }) : /* @__PURE__ */ jsx("span", {
719
+ className: "truncate rounded-full bg-surface-container-high px-2 py-0.5 font-mono text-xs text-foreground",
720
+ children: branch
721
+ }),
722
+ (ahead > 0 || behind > 0) && /* @__PURE__ */ jsxs("span", {
723
+ className: "flex items-center gap-1.5 font-mono text-xs text-muted-foreground",
724
+ children: [ahead > 0 && /* @__PURE__ */ jsxs("span", {
725
+ "aria-label": `${ahead} ahead`,
726
+ children: ["↑", ahead]
727
+ }), behind > 0 && /* @__PURE__ */ jsxs("span", {
728
+ "aria-label": `${behind} behind`,
729
+ children: ["↓", behind]
730
+ })]
731
+ }),
732
+ /* @__PURE__ */ jsxs("div", {
733
+ className: "ml-auto flex items-center gap-3",
734
+ children: [files.length > 0 && /* @__PURE__ */ jsx(DiffStatsBadge, { stats: totals }), onRefresh && /* @__PURE__ */ jsx("button", {
735
+ type: "button",
736
+ onClick: onRefresh,
737
+ "aria-label": "Refresh",
738
+ className: `rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground ${focusRing}`,
739
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: cn$1("h-3.5 w-3.5", loading && "animate-spin") })
740
+ })]
741
+ })
742
+ ]
743
+ }),
744
+ /* @__PURE__ */ jsxs("div", {
745
+ className: cn$1("flex max-h-[40%] shrink-0 flex-col border-b", BORDER),
746
+ "aria-busy": loading || void 0,
747
+ children: [
748
+ error && /* @__PURE__ */ jsx("p", {
749
+ role: "alert",
750
+ className: "px-3 py-2 text-xs text-[var(--surface-danger-text)]",
751
+ children: error
752
+ }),
753
+ loading && /* @__PURE__ */ jsxs("div", {
754
+ className: "space-y-1 px-3 py-2",
755
+ "data-testid": "changes-skeleton",
756
+ children: [/* @__PURE__ */ jsx("span", {
757
+ className: "sr-only",
758
+ children: "Loading changes…"
759
+ }), SKELETON_ROWS.map((row) => /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-full" }, row))]
760
+ }),
761
+ showEmpty && /* @__PURE__ */ jsx("p", {
762
+ className: "px-3 py-6 text-center text-xs text-muted-foreground",
763
+ children: "No changes in the working tree."
764
+ }),
765
+ showList && /* @__PURE__ */ jsx("ul", {
766
+ ref: listRef,
767
+ role: "listbox",
768
+ "aria-label": "Changed files",
769
+ "aria-activedescendant": selected ? `${idPrefix}-${selectedIndex}` : void 0,
770
+ onKeyDown: handleListKeyDown,
771
+ className: "min-h-0 overflow-y-auto py-1 focus:outline-none",
772
+ children: files.map((file, index) => {
773
+ const isSelected = index === selectedIndex;
774
+ const { dir, base } = splitPath(file.path);
775
+ const tabbable = isSelected || selectedIndex < 0 && index === 0;
776
+ return /* @__PURE__ */ jsxs("li", {
777
+ id: `${idPrefix}-${index}`,
778
+ ref: (el) => {
779
+ if (el) rowRefs.current.set(file.path, el);
780
+ else rowRefs.current.delete(file.path);
781
+ },
782
+ role: "option",
783
+ "aria-selected": isSelected,
784
+ tabIndex: tabbable ? 0 : -1,
785
+ onClick: () => onSelectFile?.(file.path),
786
+ className: cn$1("flex cursor-pointer items-center gap-2 px-3 py-1.5 text-xs transition-colors", focusRingInset, isSelected ? "bg-surface-container-high" : "hover:bg-surface-container"),
787
+ children: [
788
+ /* @__PURE__ */ jsxs("span", {
789
+ className: "min-w-0 flex-1 truncate font-mono",
790
+ title: file.path,
791
+ children: [dir && /* @__PURE__ */ jsx("span", {
792
+ className: "text-muted-foreground",
793
+ children: dir
794
+ }), /* @__PURE__ */ jsx("span", {
795
+ className: "text-foreground",
796
+ children: base
797
+ })]
798
+ }),
799
+ /* @__PURE__ */ jsx(GitStatusBadge, { status: file.status }),
800
+ /* @__PURE__ */ jsx(DiffStatsBadge, {
801
+ stats: {
802
+ added: file.additions,
803
+ removed: file.deletions
804
+ },
805
+ className: "w-[5.5rem] justify-end tabular-nums"
806
+ })
807
+ ]
808
+ }, file.path);
809
+ })
810
+ })
811
+ ]
812
+ }),
813
+ /* @__PURE__ */ jsx("div", {
814
+ className: "flex min-h-0 flex-1 flex-col",
815
+ children: selected ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PanelHeader, {
816
+ filename: selected.path,
817
+ gitStatus: selected.status,
818
+ stats,
819
+ copyText: patch,
820
+ children: onOpenFile && /* @__PURE__ */ jsx(Button, {
821
+ type: "button",
822
+ variant: "outline",
823
+ size: "sm",
824
+ className: "h-7 px-2",
825
+ onClick: () => onOpenFile(selected.path),
826
+ children: "Open"
827
+ })
828
+ }), contents ? /* @__PURE__ */ jsx(DiffView, {
829
+ filename: selected.path,
830
+ baseline: contents.baseline,
831
+ current: contents.current,
832
+ showFileHeader: false,
833
+ className: "flex-1"
834
+ }) : selected.loadError ? /* @__PURE__ */ jsx("p", {
835
+ className: "px-3 py-3 text-xs text-destructive",
836
+ role: "alert",
837
+ children: selected.loadError
838
+ }) : /* @__PURE__ */ jsx("p", {
839
+ className: "px-3 py-3 text-xs text-muted-foreground",
840
+ role: "status",
841
+ children: "Loading diff…"
842
+ })] }) : showList && /* @__PURE__ */ jsx("p", {
843
+ className: "px-3 py-3 text-xs text-muted-foreground",
844
+ children: "Select a file to read its diff."
845
+ })
846
+ }),
847
+ (onCommit || onPush) && /* @__PURE__ */ jsxs("div", {
848
+ className: cn$1("shrink-0 space-y-2 border-t bg-surface-container-low p-3", BORDER),
849
+ children: [
850
+ onCommit && /* @__PURE__ */ jsx(Textarea, {
851
+ "aria-label": "Commit message",
852
+ placeholder: "Commit message",
853
+ rows: 2,
854
+ value: message,
855
+ onChange: (event) => setMessage(event.target.value),
856
+ onKeyDown: handleMessageKeyDown,
857
+ disabled: inFlight != null,
858
+ className: "min-h-[3.5rem] resize-none px-3 py-2 text-xs"
859
+ }),
860
+ /* @__PURE__ */ jsxs("div", {
861
+ className: "flex items-center gap-2",
862
+ children: [onCommit && /* @__PURE__ */ jsx(Button, {
863
+ type: "button",
864
+ size: "sm",
865
+ disabled: !canCommit,
866
+ loading: inFlight === "commit",
867
+ onClick: () => void runCommit(),
868
+ children: "Commit"
869
+ }), onPush && /* @__PURE__ */ jsxs(Button, {
870
+ type: "button",
871
+ size: "sm",
872
+ variant: "secondary",
873
+ disabled: !canPush,
874
+ loading: inFlight === "push",
875
+ onClick: () => void runPush(),
876
+ children: ["Push", ahead > 0 && /* @__PURE__ */ jsxs("span", {
877
+ className: "font-mono text-muted-foreground",
878
+ children: ["↑", ahead]
879
+ })]
880
+ })]
881
+ }),
882
+ actionError && /* @__PURE__ */ jsx("p", {
883
+ role: "alert",
884
+ className: "text-xs text-[var(--surface-danger-text)]",
885
+ children: actionError
886
+ })
887
+ ]
888
+ })
889
+ ]
890
+ });
891
+ }
892
+ export { DiffStatsBadge as a, PillTabs as c, FileBreadcrumb as i, SandboxArtifactPane as n, GitStatusBadge as o, CodeView as r, PanelHeader as s, ChangesPane as t };