@tangle-network/sandbox-ui 2.0.20 → 2.0.22

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 (40) hide show
  1. package/README.md +18 -1
  2. package/dist/{record-Cwyo2oHb.js → AgentTree-KO1TJXBq.js} +203 -313
  3. package/dist/VirtualList-DLh2JE0F.js +205 -0
  4. package/dist/{WorkflowGraph-CADReIDl.js → WorkflowGraph-B8cRtVaw.js} +2 -2
  5. package/dist/event-stream-DNEbvx4s.d.ts +2 -0
  6. package/dist/globals.css +5910 -240
  7. package/dist/hooks.d.ts +2 -2
  8. package/dist/hooks.js +2 -2
  9. package/dist/{index-ByNsP4VC.d.ts → index-BoKSR0s9.d.ts} +62 -4
  10. package/dist/{index-BYkigpIM.d.ts → index-gXcuwPOX.d.ts} +13 -7
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +8 -6
  13. package/dist/record-BGcR48YY.js +47 -0
  14. package/dist/record.d.ts +66 -0
  15. package/dist/record.js +2329 -0
  16. package/dist/report-viewer.js +554 -0
  17. package/dist/run/live.d.ts +1 -1
  18. package/dist/run/live.js +26 -28
  19. package/dist/run/record.d.ts +2 -2
  20. package/dist/run/record.js +4 -1
  21. package/dist/run.d.ts +4 -4
  22. package/dist/run.js +5 -3
  23. package/dist/runtime-viewer/{AgentGraphCanvas-B74GN1o6-DC3EH_ss.js → AgentGraphCanvas-DDvaU9Rg-DlNZxJ20.js} +187 -168
  24. package/dist/runtime-viewer/{latest-events-iiWwpIck-BTK0vSlR.js → latest-events-CabhXynX-Dp_vs0Px.js} +705 -691
  25. package/dist/runtime-viewer/sandbox-ui-CUj4Q1P_.css +2 -0
  26. package/dist/styles.css +5911 -241
  27. package/dist/tailwind.css +5677 -5
  28. package/dist/tokens.css +7 -1
  29. package/dist/{trace-workspace-C8dHRBDV.js → trace-workspace-DkakKRLV.js} +64 -78
  30. package/dist/{tree-data-C8CNt4WE.js → tree-data-B_IXAIgg.js} +171 -121
  31. package/dist/use-follow-tail-Ckx5ZSV2.js +1 -0
  32. package/dist/vault-tree.js +1 -1
  33. package/dist/workflows.js +1 -1
  34. package/dist/{workspace-CzikYt_y.js → workspace--GvnZNpH.js} +1 -1
  35. package/dist/{workspace-files-pane-DB9Nvwh4.js → workspace-files-pane-C0PeY6rY.js} +12 -12
  36. package/dist/workspace-viewer.js +9693 -9462
  37. package/dist/workspace.js +2 -2
  38. package/package.json +27 -13
  39. package/dist/agent-stream-CBZ2fXJ2.d.ts +0 -66
  40. package/dist/runtime-viewer/sandbox-ui-z5RGk327.css +0 -2
package/dist/tokens.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /* palette:begin — generated by scripts/gen-ladders.mjs; edit palette.mjs or palette-template.css. */
2
- /* Palette layout only. Colour inputs live in palette.mjs; gen-ladders.mjs writes the public stylesheets. */
2
+ /* Theme roles derive from palette.mjs; fixed categorical chart pairs live here. gen-ladders.mjs writes the public stylesheets. */
3
3
  :root,
4
4
  [data-sandbox-ui],
5
5
  [data-sandbox-theme="vault"],
@@ -524,6 +524,12 @@
524
524
  [data-sandbox-theme],
525
525
  [data-theme] {
526
526
  --chart-ink: var(--text-primary); --chart-ink-muted: var(--text-dim);
527
+ --chart-series-1: var(--chart-accent); --chart-series-2: light-dark(#2a7fa8, #6cc0e8);
528
+ --chart-series-3: light-dark(#7b4fc0, #b99af0); --chart-series-4: light-dark(#1f7f88, #5fc2c9);
529
+ --chart-series-5: light-dark(#a8418f, #e58ccf); --chart-series-6: light-dark(#5a6ad8, #8fa4ff);
530
+ --chart-series-7: light-dark(#45507a, #c0c4dc); --chart-series-8: var(--text-muted);
531
+ --chart-category-1: light-dark(#2a78d6, #3987e5); --chart-category-2: light-dark(#eb6834, #d95926);
532
+ --chart-category-3: light-dark(#1baf7a, #199e70); --chart-category-other: var(--text-muted);
527
533
  --chart-muted: var(--md3-outline); --chart-grid: var(--md3-surface-container-high);
528
534
  --chart-rule: var(--md3-outline-variant); --chart-shade: var(--bg-hover);
529
535
  --chart-accent: var(--brand-cool); --chart-tier-c: hsl(246.1017 55.1402% 41.9608%);
@@ -2,14 +2,16 @@ import { t as cn } from "./utils-BER_bdgE.js";
2
2
  import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-BQlarCIo.js";
3
3
  import { t as ProviderIcon } from "./provider-logo-CPKRPHcS.js";
4
4
  import { t as AgentGraph$1 } from "./AgentGraph-BGWx9hhS.js";
5
+ import { t as useFollowTail } from "./use-follow-tail-Ckx5ZSV2.js";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
- import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
7
+ import { useMemo, useReducer, useRef, useState } from "react";
7
8
  import { X } from "lucide-react";
8
9
  import { activityTiming, ambientStyle, syncAmbient } from "@tangle-network/charts/react/agent-graph/presentation";
9
10
  import { AgentTimeline } from "@tangle-network/ui/chat";
10
11
  import { InlineThinkingItem, runStatus } from "@tangle-network/ui/run";
11
12
  import { FlowWaterfall, toolGlyph } from "@tangle-network/charts/react";
12
13
  import { LatestEvents } from "@tangle-network/charts/react/agent-graph";
14
+ import { useEventStream } from "@tangle-network/ui/hooks";
13
15
  // src/run/trace-waterfall.tsx
14
16
  /** Agent and model spans show their lab/host mark; tool spans show their provider's mark. Unknown keeps the kind glyph. */
15
17
  function traceSpanMark(span) {
@@ -205,59 +207,61 @@ function useAgentStream(url, openEventSource) {
205
207
  lastSeq: 0,
206
208
  alive: false
207
209
  });
208
- useEffect(() => {
209
- if (!url || !openEventSource && typeof EventSource === "undefined") return;
210
- dispatch({ type: "reset" });
211
- let lastSeq = 0;
212
- let closed = false;
213
- let source;
214
- let retry;
215
- const open = () => {
216
- const target = new URL(url, globalThis.location?.href);
217
- if (lastSeq > 0) target.searchParams.set("after", String(lastSeq));
218
- source = openEventSource ? openEventSource(target.toString(), (live) => {
219
- if (!closed && !live) dispatch({ type: "unreachable" });
220
- }) : new EventSource(target.toString());
221
- let opened = false;
222
- if (!openEventSource) source.onopen = () => {
223
- opened = true;
224
- };
225
- source.addEventListener("rows", (message) => {
226
- const data = JSON.parse(message.data);
227
- lastSeq = Math.max(lastSeq, data.lastSeq ?? 0, ...data.events.map((row) => row.seq));
228
- dispatch({
229
- type: "rows",
230
- generation: data.generation,
231
- rows: data.events,
232
- alive: data.alive
233
- });
234
- });
235
- source.addEventListener("reset", (message) => {
236
- lastSeq = 0;
237
- dispatch({
238
- type: "reset",
239
- generation: JSON.parse(message.data).generation
240
- });
241
- });
242
- source.addEventListener("end", () => {
243
- closed = true;
244
- source?.close();
245
- dispatch({ type: "end" });
246
- });
247
- if (!openEventSource) source.onerror = () => {
248
- source?.close();
249
- if (closed) return;
250
- if (!opened) dispatch({ type: "unreachable" });
251
- retry = setTimeout(open, 1e3);
252
- };
253
- };
254
- open();
255
- return () => {
256
- closed = true;
257
- clearTimeout(retry);
258
- source?.close();
259
- };
260
- }, [url, openEventSource]);
210
+ const cursor = useRef({ lastSeq: 0 });
211
+ useEventStream(url, {
212
+ opener: openEventSource,
213
+ after: () => cursor.current.lastSeq,
214
+ onStart: () => {
215
+ cursor.current = { lastSeq: 0 };
216
+ dispatch({ type: "reset" });
217
+ },
218
+ onLiveChange: (live) => {
219
+ if (!live) dispatch({ type: "unreachable" });
220
+ },
221
+ events: {
222
+ rows: (message) => {
223
+ try {
224
+ const data = JSON.parse(message.data);
225
+ if (!Array.isArray(data.events)) throw new Error("Invalid agent rows stream");
226
+ if (data.generation !== void 0 && data.generation !== cursor.current.generation) cursor.current = {
227
+ lastSeq: 0,
228
+ generation: data.generation
229
+ };
230
+ cursor.current.lastSeq = Math.max(cursor.current.lastSeq, data.lastSeq ?? 0, ...data.events.map((row) => row.seq));
231
+ dispatch({
232
+ type: "rows",
233
+ generation: data.generation,
234
+ rows: data.events,
235
+ alive: data.alive
236
+ });
237
+ } catch (cause) {
238
+ dispatch({
239
+ type: "error",
240
+ message: String(cause)
241
+ });
242
+ }
243
+ },
244
+ reset: (message) => {
245
+ try {
246
+ const { generation } = JSON.parse(message.data);
247
+ cursor.current = {
248
+ lastSeq: 0,
249
+ generation
250
+ };
251
+ dispatch({
252
+ type: "reset",
253
+ generation
254
+ });
255
+ } catch (cause) {
256
+ dispatch({
257
+ type: "error",
258
+ message: String(cause)
259
+ });
260
+ }
261
+ },
262
+ end: () => dispatch({ type: "end" })
263
+ }
264
+ });
261
265
  return state;
262
266
  }
263
267
  // src/run/trace-workspace.tsx
@@ -438,18 +442,10 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
438
442
  const live = stream.alive && !stream.error && cursor === null;
439
443
  const items = useMemo(() => timelineItems(shown, live), [shown, live]);
440
444
  const scroller = useRef(null);
441
- const pinned = useRef(true);
442
- const [unseen, setUnseen] = useState(false);
443
- useLayoutEffect(() => {
444
- const element = scroller.current;
445
- if (!element) return;
446
- if (pinned.current) element.scrollTop = element.scrollHeight;
447
- else setUnseen(true);
448
- }, [items.length, items[items.length - 1]]);
449
- useEffect(() => {
450
- pinned.current = true;
451
- setUnseen(false);
452
- }, [selected]);
445
+ const { unseen, jump } = useFollowTail(scroller, {
446
+ identity: showConversation ? selected : void 0,
447
+ content: items
448
+ });
453
449
  return /* @__PURE__ */ jsxs("div", {
454
450
  className: cn("sandbox-trace-workspace flex min-h-0 flex-col gap-3", className),
455
451
  children: [
@@ -580,11 +576,6 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
580
576
  }),
581
577
  /* @__PURE__ */ jsx("div", {
582
578
  ref: scroller,
583
- onScroll: (event) => {
584
- const element = event.currentTarget;
585
- pinned.current = element.scrollHeight - element.scrollTop - element.clientHeight < 48;
586
- if (pinned.current) setUnseen(false);
587
- },
588
579
  "data-primary-content": "",
589
580
  className: "min-h-0 flex-1 overflow-y-auto px-3 py-2",
590
581
  children: streamUrl && (cursor === null || origin !== void 0) ? /* @__PURE__ */ jsx(AgentTimeline, {
@@ -598,12 +589,7 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
598
589
  }),
599
590
  unseen && /* @__PURE__ */ jsx("button", {
600
591
  type: "button",
601
- onClick: () => {
602
- const element = scroller.current;
603
- if (element) element.scrollTop = element.scrollHeight;
604
- pinned.current = true;
605
- setUnseen(false);
606
- },
592
+ onClick: () => jump(),
607
593
  className: "absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-border bg-background px-3 py-1 text-xs shadow-sm",
608
594
  children: "New output"
609
595
  })
@@ -612,4 +598,4 @@ function TraceWorkspace({ trace, streamUrl, openEventSource, events, onTimeChang
612
598
  ]
613
599
  });
614
600
  }
615
- export { TraceWaterfall as a, useAgentStream as i, LatestEvents as n, traceSpanMark as o, foldAgentRows as r, TraceWorkspace as t };
601
+ export { useEventStream as a, useAgentStream as i, LatestEvents as n, TraceWaterfall as o, foldAgentRows as r, traceSpanMark as s, TraceWorkspace as t };
@@ -1,4 +1,5 @@
1
1
  import { t as revealInScroller } from "./reveal-in-scroller-CzKwuqCG.js";
2
+ import { t as VirtualList } from "./VirtualList-DLh2JE0F.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
5
  import { BookOpen, Braces, ChevronRight, File, FileCode, FileCog, FileImage, FileLock, FileSpreadsheet, FileTerminal, FileText, Folder, FolderOpen, GitBranch, Package, Scale } from "lucide-react";
@@ -10,8 +11,8 @@ import { BookOpen, Braces, ChevronRight, File, FileCode, FileCog, FileImage, Fil
10
11
  * trees given the same key share one remembered expansion.
11
12
  *
12
13
  * Rendering is lazy: only the children of expanded folders are walked or
13
- * mounted, and a folder with more than `CHILD_PAGE` children renders them a page
14
- * at a time, so a vault with thousands of files costs only what is on screen.
14
+ * mounted; folders retain their child paging, and collections over 100 visible
15
+ * rows use the shared viewport list. Keyboard navigation reveals offscreen rows.
15
16
  *
16
17
  * The tree follows the WAI-ARIA tree pattern with a single tab stop: arrow keys
17
18
  * move between rows, Right/Left expand, collapse or move to a child or parent,
@@ -294,7 +295,10 @@ function VaultTree({ root, selectedPath, activeFolder, onSelect, onFolderToggle,
294
295
  const [focusedPath, setFocusedPath] = useState(null);
295
296
  const rowRefs = useRef(/* @__PURE__ */ new Map());
296
297
  const pendingFocusRef = useRef(null);
297
- const pendingRevealRef = useRef(null);
298
+ const treeRef = useRef(null), scroller = useRef(null);
299
+ const [target, setTarget] = useState();
300
+ const previousPaths = useRef(null);
301
+ const revealed = useRef(void 0);
298
302
  const index = useMemo(() => {
299
303
  const nodes = /* @__PURE__ */ new Map();
300
304
  const parents = /* @__PURE__ */ new Map();
@@ -376,35 +380,48 @@ function VaultTree({ root, selectedPath, activeFolder, onSelect, onFolderToggle,
376
380
  onFolderToggle
377
381
  ]);
378
382
  useEffect(() => {
379
- if (!selectedPath || !index.nodes.has(selectedPath)) return;
383
+ if (!selectedPath || !index.nodes.has(selectedPath)) {
384
+ revealed.current = void 0;
385
+ return;
386
+ }
387
+ if (revealed.current?.path === selectedPath && revealed.current.scope === storageKey) return;
388
+ revealed.current = {
389
+ scope: storageKey,
390
+ path: selectedPath
391
+ };
380
392
  const ancestors = [];
381
393
  for (let parent = index.parents.get(selectedPath) ?? null; parent; parent = index.parents.get(parent) ?? null) ancestors.push(parent);
382
- pendingRevealRef.current = selectedPath;
394
+ setTarget(selectedPath);
395
+ setPageLimits((current) => {
396
+ const next = new Map(current);
397
+ for (const path of [selectedPath, ...ancestors]) {
398
+ const parent = index.parents.get(path) ?? null;
399
+ if (parent) next.set(parent, Math.max(next.get(parent) ?? 200, (index.children.get(parent)?.findIndex((n) => n.path === path) ?? -1) + 1));
400
+ }
401
+ return next;
402
+ });
383
403
  if (ancestors.some((path) => !expandedPaths.has(path))) updateExpanded((next) => {
384
404
  for (const path of ancestors) next.add(path);
385
405
  });
386
- }, [selectedPath, index]);
387
- useEffect(() => {
388
- const reveal = pendingRevealRef.current;
389
- if (reveal && rowRefs.current.has(reveal)) {
390
- pendingRevealRef.current = null;
391
- const row = rowRefs.current.get(reveal);
392
- if (row) revealInScroller(row);
393
- }
394
- const focus = pendingFocusRef.current;
395
- if (focus && rowRefs.current.has(focus)) {
396
- pendingFocusRef.current = null;
397
- rowRefs.current.get(focus)?.focus();
398
- }
399
- }, [rows]);
406
+ }, [
407
+ selectedPath,
408
+ index,
409
+ storageKey
410
+ ]);
400
411
  const visiblePaths = useMemo(() => new Set(nodeRows.map((row) => row.node.path)), [nodeRows]);
401
412
  const tabStop = focusedPath && visiblePaths.has(focusedPath) && focusedPath || selectedPath && visiblePaths.has(selectedPath) && selectedPath || nodeRows[0]?.node.path || null;
402
413
  const focusRow = useCallback((path) => {
403
414
  if (!path) return;
404
415
  setFocusedPath(path);
405
416
  const element = rowRefs.current.get(path);
406
- if (element) element.focus();
407
- else pendingFocusRef.current = path;
417
+ if (element) {
418
+ setTarget(void 0);
419
+ element.focus({ preventScroll: true });
420
+ revealInScroller(element);
421
+ } else {
422
+ pendingFocusRef.current = path;
423
+ setTarget(path);
424
+ }
408
425
  }, []);
409
426
  const activate = useCallback((node) => {
410
427
  setFocusedPath(node.path);
@@ -424,19 +441,14 @@ function VaultTree({ root, selectedPath, activeFolder, onSelect, onFolderToggle,
424
441
  const dir = node.type === "directory";
425
442
  const open = dir && isOpen(node.path);
426
443
  let handled = true;
427
- switch (event.key) {
428
- case "ArrowDown":
429
- focusRow(nodeRows[at + 1]?.node.path);
430
- break;
431
- case "ArrowUp":
432
- focusRow(nodeRows[at - 1]?.node.path);
433
- break;
434
- case "Home":
435
- focusRow(nodeRows[0]?.node.path);
436
- break;
437
- case "End":
438
- focusRow(nodeRows[nodeRows.length - 1]?.node.path);
439
- break;
444
+ const moves = {
445
+ ArrowDown: nodeRows[at + 1]?.node.path,
446
+ ArrowUp: nodeRows[at - 1]?.node.path,
447
+ Home: nodeRows[0]?.node.path,
448
+ End: nodeRows[nodeRows.length - 1]?.node.path
449
+ };
450
+ if (event.key in moves) focusRow(moves[event.key]);
451
+ else switch (event.key) {
440
452
  case "ArrowRight":
441
453
  if (dir && !open) setFolderOpen(node.path, true);
442
454
  else if (open) {
@@ -480,97 +492,135 @@ function VaultTree({ root, selectedPath, activeFolder, onSelect, onFolderToggle,
480
492
  return out;
481
493
  }, [status]);
482
494
  const openParent = selectedPath ? index.parents.get(selectedPath) ?? null : null;
483
- return /* @__PURE__ */ jsx("div", {
484
- role: "tree",
485
- "aria-label": label,
486
- "data-vault-tree-rows": true,
487
- className: `flex flex-col gap-px ${className ?? ""}`,
488
- children: rows.map((row) => {
489
- if (row.kind === "more") return /* @__PURE__ */ jsxs("button", {
490
- type: "button",
491
- onClick: () => setPageLimits((current) => {
495
+ useEffect(() => {
496
+ previousPaths.current = new Set(index.nodes.keys());
497
+ }, [index]);
498
+ const keyOf = (row) => row.kind === "node" ? row.node.path : `more:${row.parent}`;
499
+ const render = (row) => {
500
+ if (row.kind === "more") return /* @__PURE__ */ jsxs("button", {
501
+ type: "button",
502
+ onClick: () => {
503
+ focusRow(index.children.get(row.parent)?.[(index.children.get(row.parent)?.length ?? 0) - row.hidden]?.path);
504
+ setPageLimits((current) => {
492
505
  const next = new Map(current);
493
506
  next.set(row.parent, Math.max(200, current.get(row.parent) ?? 0) + 200);
494
507
  return next;
508
+ });
509
+ },
510
+ style: { paddingInlineStart: row.depth * INDENT + 28 },
511
+ className: "flex min-h-8 w-full items-center rounded-md pr-2 text-left text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
512
+ children: [
513
+ "Show ",
514
+ Math.min(row.hidden, 200),
515
+ " more of ",
516
+ row.hidden
517
+ ]
518
+ }, `more:${row.parent}`);
519
+ const { node, depth } = row;
520
+ const dir = node.type === "directory";
521
+ const open = dir && isOpen(node.path);
522
+ const selected = !dir && node.path === selectedPath;
523
+ const active = dir && node.path === activeFolder;
524
+ const kind = dir ? {
525
+ Icon: open ? FolderOpen : Folder,
526
+ ink: "text-muted-foreground"
527
+ } : fileKind(node.name);
528
+ const Icon = kind.Icon;
529
+ const change = dir ? open ? void 0 : changedFolders.get(node.path) : status?.[node.path];
530
+ const ancestors = [];
531
+ for (let at = row.parent; at; at = index.parents.get(at) ?? null) ancestors.unshift(at);
532
+ return /* @__PURE__ */ jsxs("div", {
533
+ ref: (element) => {
534
+ if (element) {
535
+ rowRefs.current.set(node.path, element);
536
+ if (previousPaths.current && !previousPaths.current.has(node.path) && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
537
+ previousPaths.current.add(node.path);
538
+ element.classList.add("agent-arrive");
539
+ Promise.allSettled(element.getAnimations().map((animation) => animation.finished)).then(() => element.classList.remove("agent-arrive"));
540
+ }
541
+ if (pendingFocusRef.current === node.path) {
542
+ pendingFocusRef.current = null;
543
+ element.focus({ preventScroll: true });
544
+ }
545
+ if (target === node.path && rows.length <= 100) revealInScroller(element);
546
+ } else rowRefs.current.delete(node.path);
547
+ },
548
+ role: "treeitem",
549
+ "aria-level": depth + 1,
550
+ "aria-posinset": row.posInSet,
551
+ "aria-setsize": row.setSize,
552
+ "aria-expanded": dir ? open : void 0,
553
+ "aria-selected": dir ? void 0 : selected,
554
+ "aria-current": active ? "true" : void 0,
555
+ tabIndex: node.path === tabStop ? 0 : -1,
556
+ title: node.path,
557
+ "data-vault-tree-item": node.type,
558
+ "data-path": node.path,
559
+ "data-selected": selected ? "true" : void 0,
560
+ onClick: () => activate(node),
561
+ onFocus: () => setFocusedPath(node.path),
562
+ onKeyDown: (event) => onKeyDown(event, node),
563
+ style: { paddingInlineStart: depth * INDENT + 4 },
564
+ className: [
565
+ "group relative flex min-h-8 w-full cursor-pointer select-none items-center gap-1.5 rounded-md pr-2 text-sm transition-colors [@media(pointer:coarse)]:min-h-10",
566
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
567
+ selected ? "bg-[var(--ix-sel-bg)] font-medium text-[var(--accent-text)]" : active ? "bg-muted font-medium text-foreground" : "text-foreground hover:bg-[var(--ix-hover-bg)]"
568
+ ].join(" "),
569
+ children: [
570
+ ancestors.map((folder, level) => /* @__PURE__ */ jsx("span", {
571
+ "aria-hidden": "true",
572
+ className: `pointer-events-none absolute inset-y-0 w-px ${folder === openParent ? "bg-[var(--border-strong)]" : "bg-border"}`,
573
+ style: { left: level * INDENT + 12 }
574
+ }, folder)),
575
+ /* @__PURE__ */ jsx("span", {
576
+ "aria-hidden": "true",
577
+ className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground",
578
+ children: dir && /* @__PURE__ */ jsx(ChevronRight, { className: `size-3.5 transition-transform duration-fast ${open ? "rotate-90" : ""}` })
579
+ }),
580
+ /* @__PURE__ */ jsx(Icon, {
581
+ "aria-hidden": "true",
582
+ className: `size-4 shrink-0 ${kind.ink}`
495
583
  }),
496
- style: { paddingInlineStart: row.depth * INDENT + 28 },
497
- className: "flex min-h-8 w-full items-center rounded-md pr-2 text-left text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
498
- children: [
499
- "Show ",
500
- Math.min(row.hidden, 200),
501
- " more of ",
502
- row.hidden
503
- ]
504
- }, `more:${row.parent}`);
505
- const { node, depth } = row;
506
- const dir = node.type === "directory";
507
- const open = dir && isOpen(node.path);
508
- const selected = !dir && node.path === selectedPath;
509
- const active = dir && node.path === activeFolder;
510
- const kind = dir ? {
511
- Icon: open ? FolderOpen : Folder,
512
- ink: "text-muted-foreground"
513
- } : fileKind(node.name);
514
- const Icon = kind.Icon;
515
- const change = dir ? open ? void 0 : changedFolders.get(node.path) : status?.[node.path];
516
- const ancestors = [];
517
- for (let at = row.parent; at; at = index.parents.get(at) ?? null) ancestors.unshift(at);
518
- return /* @__PURE__ */ jsxs("div", {
519
- ref: (element) => {
520
- if (element) rowRefs.current.set(node.path, element);
521
- else rowRefs.current.delete(node.path);
522
- },
523
- role: "treeitem",
524
- "aria-level": depth + 1,
525
- "aria-posinset": row.posInSet,
526
- "aria-setsize": row.setSize,
527
- "aria-expanded": dir ? open : void 0,
528
- "aria-selected": dir ? void 0 : selected,
529
- "aria-current": active ? "true" : void 0,
530
- tabIndex: node.path === tabStop ? 0 : -1,
531
- title: node.path,
532
- "data-vault-tree-item": node.type,
533
- "data-path": node.path,
534
- "data-selected": selected ? "true" : void 0,
535
- onClick: () => activate(node),
536
- onFocus: () => setFocusedPath(node.path),
537
- onKeyDown: (event) => onKeyDown(event, node),
538
- style: { paddingInlineStart: depth * INDENT + 4 },
539
- className: [
540
- "group relative flex min-h-8 w-full cursor-pointer select-none items-center gap-1.5 rounded-md pr-2 text-sm transition-colors [@media(pointer:coarse)]:min-h-10",
541
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
542
- selected ? "bg-[var(--ix-sel-bg)] font-medium text-[var(--accent-text)]" : active ? "bg-muted font-medium text-foreground" : "text-foreground hover:bg-[var(--ix-hover-bg)]"
543
- ].join(" "),
544
- children: [
545
- ancestors.map((folder, level) => /* @__PURE__ */ jsx("span", {
546
- "aria-hidden": "true",
547
- className: `pointer-events-none absolute inset-y-0 w-px ${folder === openParent ? "bg-[var(--border-strong)]" : "bg-border"}`,
548
- style: { left: level * INDENT + 12 }
549
- }, folder)),
550
- /* @__PURE__ */ jsx("span", {
551
- "aria-hidden": "true",
552
- className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground",
553
- children: dir && /* @__PURE__ */ jsx(ChevronRight, { className: `size-3.5 transition-transform duration-fast ${open ? "rotate-90" : ""}` })
554
- }),
555
- /* @__PURE__ */ jsx(Icon, {
556
- "aria-hidden": "true",
557
- className: `size-4 shrink-0 ${kind.ink}`
558
- }),
559
- /* @__PURE__ */ jsx("span", {
560
- className: "min-w-0 flex-1 truncate",
561
- children: node.name
562
- }),
563
- change && (dir ? /* @__PURE__ */ jsx("span", {
564
- "aria-label": `holds ${change === "added" ? "added" : "changed"} files`,
565
- className: `size-1.5 shrink-0 rounded-full ${change === "added" ? "bg-[var(--surface-success-text)]" : "bg-[var(--surface-warning-text)]"}`
566
- }) : /* @__PURE__ */ jsx("span", {
567
- "aria-label": change,
568
- className: `w-3.5 shrink-0 text-center font-mono text-xs font-semibold ${CHANGE[change].ink}`,
569
- children: CHANGE[change].letter
570
- }))
571
- ]
572
- }, node.path);
573
- })
584
+ /* @__PURE__ */ jsx("span", {
585
+ className: "min-w-0 flex-1 truncate",
586
+ children: node.name
587
+ }),
588
+ change && (dir ? /* @__PURE__ */ jsx("span", {
589
+ "aria-label": `holds ${change === "added" ? "added" : "changed"} files`,
590
+ className: `size-1.5 shrink-0 rounded-full ${change === "added" ? "bg-[var(--surface-success-text)]" : "bg-[var(--surface-warning-text)]"}`
591
+ }) : /* @__PURE__ */ jsx("span", {
592
+ "aria-label": change,
593
+ className: `w-3.5 shrink-0 text-center font-mono text-sm font-semibold ${CHANGE[change].ink}`,
594
+ children: CHANGE[change].letter
595
+ }))
596
+ ]
597
+ }, node.path);
598
+ };
599
+ return /* @__PURE__ */ jsx("div", {
600
+ ref: (element) => {
601
+ treeRef.current = element;
602
+ if (!element) return;
603
+ let parent = element?.parentElement ?? null;
604
+ while (parent && !/auto|scroll/.test(getComputedStyle(parent).overflowY)) parent = parent.parentElement;
605
+ scroller.current = parent ?? document.scrollingElement;
606
+ },
607
+ role: "tree",
608
+ "aria-label": label,
609
+ "data-vault-tree-rows": true,
610
+ className,
611
+ children: /* @__PURE__ */ jsx(VirtualList, {
612
+ items: rows,
613
+ keyOf,
614
+ render,
615
+ scroller,
616
+ origin: treeRef,
617
+ estimate: 33,
618
+ overscan: 320,
619
+ target,
620
+ targetAlign: "nearest",
621
+ virtualize: rows.length > 100,
622
+ keepMounted: tabStop ?? void 0
623
+ }, storageKey)
574
624
  });
575
625
  }
576
626
  // src/vault-tree/tree-data.ts
@@ -0,0 +1 @@
1
+ export { useFollowTail as t } from "@tangle-network/ui/hooks";
@@ -1 +1 @@
1
- export { r as VAULT_TREE_CHILD_PAGE, i as VaultTree, t as fileTreeFromPaths, n as filterFileNodes } from "./tree-data-C8CNt4WE.js";
1
+ export { r as VAULT_TREE_CHILD_PAGE, i as VaultTree, t as fileTreeFromPaths, n as filterFileNodes } from "./tree-data-B_IXAIgg.js";
package/dist/workflows.js CHANGED
@@ -21,7 +21,7 @@ import { GraphErrorBoundary, retryImport } from "@tangle-network/charts/react/ag
21
21
  * via a Vite `vite:preloadError` reload guard.
22
22
  */
23
23
  function lazyWorkflowGraph() {
24
- return lazy(() => retryImport(() => import("./WorkflowGraph-CADReIDl.js").then((m) => ({ default: m.WorkflowGraph }))));
24
+ return lazy(() => retryImport(() => import("./WorkflowGraph-B8cRtVaw.js").then((m) => ({ default: m.WorkflowGraph }))));
25
25
  }
26
26
  /** Graceful degradation when the graph can't render: the workflow is still
27
27
  * fully legible as YAML, with a retry for a recoverable load failure. */
@@ -1,7 +1,7 @@
1
1
  import { t as cn$1 } from "./utils-BER_bdgE.js";
2
2
  import { r as ShellHeader } from "./shell-header-CwqDzC4I.js";
3
3
  import { t as MOTION_CONTROL } from "./motion-2Ine0j_s.js";
4
- import "./workspace-files-pane-DB9Nvwh4.js";
4
+ import "./workspace-files-pane-C0PeY6rY.js";
5
5
  import { ArtifactPane, ArtifactPane as ArtifactPane$1, EmptyState, Input, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@tangle-network/ui/primitives";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Markdown } from "@tangle-network/ui/markdown";