@tangle-network/sandbox-ui 0.74.0 → 0.75.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.
@@ -5,7 +5,7 @@ import {
5
5
  COMPACT_NODE_SIZE,
6
6
  buildWorkflowGraph,
7
7
  providerLabel
8
- } from "./chunk-VXGI2XUK.js";
8
+ } from "./chunk-FNE5QLU3.js";
9
9
  import "./chunk-U67V476Y.js";
10
10
 
11
11
  // src/workflows/WorkflowGraph.tsx
@@ -21,19 +21,7 @@ import {
21
21
  useNodesState
22
22
  } from "@xyflow/react";
23
23
  import "@xyflow/react/dist/style.css";
24
- import {
25
- Bell,
26
- Box,
27
- Cable,
28
- Circle,
29
- Clock,
30
- Maximize2,
31
- Minimize2,
32
- Repeat,
33
- Sparkles,
34
- Split,
35
- Webhook
36
- } from "lucide-react";
24
+ import { Maximize2, Minimize2 } from "lucide-react";
37
25
  import {
38
26
  createContext,
39
27
  useCallback,
@@ -125,7 +113,10 @@ function fmtCost(usd) {
125
113
  return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
126
114
  }
127
115
  function clampPreview(text, max = 200) {
128
- return text.length > max ? `${text.slice(0, max)}\u2026` : text;
116
+ if (text.length <= max) return text;
117
+ const lastCode = text.charCodeAt(max - 1);
118
+ const end = lastCode >= 55296 && lastCode <= 56319 ? max - 1 : max;
119
+ return `${text.slice(0, end)}\u2026`;
129
120
  }
130
121
  function fmtTokens(input, output) {
131
122
  const valid = (n) => n !== void 0 && Number.isFinite(n) && n >= 0 ? n : void 0;
@@ -135,23 +126,103 @@ function fmtTokens(input, output) {
135
126
  return `${i ?? 0}/${o ?? 0} tok`;
136
127
  }
137
128
 
138
- // src/workflows/WorkflowGraph.tsx
139
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
140
- function useColorMode() {
141
- const [mode, setMode] = useState(
142
- () => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
129
+ // src/workflows/node-output.tsx
130
+ import { jsx, jsxs } from "react/jsx-runtime";
131
+ var MAX_JSON_ENTRIES = 6;
132
+ function scalar(value) {
133
+ if (value === null) return "null";
134
+ if (typeof value === "string") return value;
135
+ if (typeof value === "number" || typeof value === "boolean") {
136
+ return String(value);
137
+ }
138
+ if (Array.isArray(value)) {
139
+ return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
140
+ }
141
+ if (typeof value === "object") return "{\u2026}";
142
+ return "";
143
+ }
144
+ function classifyOutput(preview) {
145
+ let trimmed = preview?.trim() ?? "";
146
+ trimmed = trimmed.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, "").replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "").trimEnd();
147
+ if (trimmed === "") return { kind: "empty" };
148
+ const structured = trimmed.startsWith("{") || trimmed.startsWith("[");
149
+ if (structured) {
150
+ try {
151
+ const parsed = JSON.parse(trimmed);
152
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
153
+ const all = Object.entries(parsed);
154
+ const entries = all.slice(0, MAX_JSON_ENTRIES).map(([k, v]) => [k, scalar(v)]);
155
+ if (entries.length > 0) {
156
+ return { kind: "json", entries, truncated: all.length > entries.length };
157
+ }
158
+ }
159
+ return { kind: "code", text: trimmed };
160
+ } catch {
161
+ return { kind: "code", text: trimmed };
162
+ }
163
+ }
164
+ return { kind: "text", text: trimmed };
165
+ }
166
+ var CLAMP_BY_ROWS = {
167
+ 1: "truncate",
168
+ 2: "line-clamp-2",
169
+ 3: "line-clamp-3"
170
+ };
171
+ function NodeOutputBody({
172
+ shape,
173
+ rows = 2,
174
+ tone = "default"
175
+ }) {
176
+ const clampClass = CLAMP_BY_ROWS[rows] ?? "line-clamp-2";
177
+ const codeWrap = rows === 1 ? "" : "whitespace-pre-wrap break-all";
178
+ if (shape.kind === "empty") return null;
179
+ if (shape.kind === "json") {
180
+ const overflow = shape.truncated || shape.entries.length > rows;
181
+ const visible = shape.entries.slice(0, overflow ? Math.max(0, rows - 1) : rows);
182
+ const isError = tone === "error";
183
+ const keyClass = isError ? "text-red-400/80" : "text-muted-foreground";
184
+ const valueClass = isError ? "text-red-400" : "text-foreground";
185
+ return /* @__PURE__ */ jsxs("dl", { className: "space-y-0.5 font-mono text-[10px] leading-tight", children: [
186
+ visible.map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
187
+ /* @__PURE__ */ jsx("dt", { className: `max-w-[45%] shrink-0 truncate ${keyClass}`, title: key, children: key }),
188
+ /* @__PURE__ */ jsx("dd", { className: `min-w-0 flex-1 truncate ${valueClass}`, title: value, children: value })
189
+ ] }, key)),
190
+ overflow && /* @__PURE__ */ jsx("div", { className: `text-[9px] ${keyClass}`, children: "\u2026" })
191
+ ] });
192
+ }
193
+ if (shape.kind === "code") {
194
+ return /* @__PURE__ */ jsx(
195
+ "pre",
196
+ {
197
+ className: `${clampClass} ${codeWrap} font-mono text-[10px] leading-tight ${tone === "error" ? "text-red-400" : "text-foreground/80"}`,
198
+ title: shape.text,
199
+ children: shape.text
200
+ }
201
+ );
202
+ }
203
+ return /* @__PURE__ */ jsx(
204
+ "p",
205
+ {
206
+ className: `${clampClass} text-[10px] leading-snug ${tone === "error" ? "text-red-400" : "text-muted-foreground"}`,
207
+ title: shape.text,
208
+ children: shape.text
209
+ }
143
210
  );
144
- useEffect(() => {
145
- const el = document.documentElement;
146
- const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
147
- update();
148
- const observer = new MutationObserver(update);
149
- observer.observe(el, { attributes: true, attributeFilter: ["class"] });
150
- return () => observer.disconnect();
151
- }, []);
152
- return mode;
153
211
  }
154
- var DirectionContext = createContext("LR");
212
+
213
+ // src/workflows/node-ui.tsx
214
+ import {
215
+ Bell,
216
+ Box,
217
+ Cable,
218
+ Circle,
219
+ Clock,
220
+ Repeat,
221
+ Sparkles,
222
+ Split,
223
+ Webhook
224
+ } from "lucide-react";
225
+ import { jsx as jsx2 } from "react/jsx-runtime";
155
226
  var TONE_ACCENT = {
156
227
  trigger: "hsl(var(--primary))",
157
228
  structural: "var(--surface-warning-text)",
@@ -167,7 +238,45 @@ var STATUS_COLOR = {
167
238
  succeeded: EDGE_DONE,
168
239
  failed: EDGE_FAIL
169
240
  };
170
- var DensityContext = createContext(false);
241
+ function edgeColor(status) {
242
+ switch (status) {
243
+ case "running":
244
+ return EDGE_RUNNING;
245
+ case "succeeded":
246
+ return EDGE_DONE;
247
+ case "failed":
248
+ return EDGE_FAIL;
249
+ default:
250
+ return EDGE_MUTED;
251
+ }
252
+ }
253
+ var STATUS_LABEL = {
254
+ queued: "Queued",
255
+ running: "Running",
256
+ succeeded: "Done",
257
+ failed: "Failed"
258
+ };
259
+ var STATUS_BADGE = {
260
+ queued: "bg-surface-container-high text-muted-foreground",
261
+ running: "bg-primary/15 text-primary",
262
+ succeeded: "bg-green-600/10 text-green-600",
263
+ failed: "bg-red-500/10 text-red-400"
264
+ };
265
+ function statusBorder(status) {
266
+ switch (status) {
267
+ case "running":
268
+ return "border-primary ring-1 ring-primary/40";
269
+ case "succeeded":
270
+ return "border-green-500";
271
+ case "failed":
272
+ return "border-red-500";
273
+ default:
274
+ return "opacity-70";
275
+ }
276
+ }
277
+ function progressFill(status) {
278
+ return status === "succeeded" || status === "failed" ? "100%" : status === "running" ? "58%" : "6%";
279
+ }
171
280
  var KIND_ICON = {
172
281
  schedule: Clock,
173
282
  provider_event: Webhook,
@@ -186,7 +295,7 @@ function NodeIcon({
186
295
  glyph
187
296
  }) {
188
297
  const Icon = kind && KIND_ICON[kind] || Circle;
189
- return /* @__PURE__ */ jsx(
298
+ return /* @__PURE__ */ jsx2(
190
299
  "span",
191
300
  {
192
301
  "aria-hidden": "true",
@@ -197,76 +306,64 @@ function NodeIcon({
197
306
  background: `color-mix(in srgb, ${accent} 16%, transparent)`,
198
307
  color: accent
199
308
  },
200
- children: /* @__PURE__ */ jsx(Icon, { size: glyph, strokeWidth: 2 })
309
+ children: /* @__PURE__ */ jsx2(Icon, { size: glyph, strokeWidth: 2 })
201
310
  }
202
311
  );
203
312
  }
204
- function ProgressStrip({
313
+ function MetaChip({
314
+ children,
315
+ title
316
+ }) {
317
+ return /* @__PURE__ */ jsx2(
318
+ "span",
319
+ {
320
+ title,
321
+ className: "inline-block max-w-[150px] truncate rounded bg-surface-container-high px-1.5 py-0.5 align-middle text-[10px] text-muted-foreground",
322
+ children
323
+ }
324
+ );
325
+ }
326
+
327
+ // src/workflows/WorkflowGraph.tsx
328
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
329
+ var MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
330
+ function useColorMode() {
331
+ const [mode, setMode] = useState(
332
+ () => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
333
+ );
334
+ useEffect(() => {
335
+ const el = document.documentElement;
336
+ const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
337
+ update();
338
+ const observer = new MutationObserver(update);
339
+ observer.observe(el, { attributes: true, attributeFilter: ["class"] });
340
+ return () => observer.disconnect();
341
+ }, []);
342
+ return mode;
343
+ }
344
+ var DirectionContext = createContext("LR");
345
+ var DensityContext = createContext(false);
346
+ function StatusFooter({
205
347
  status,
206
348
  rounds,
207
349
  elapsed
208
350
  }) {
209
- const color = STATUS_COLOR[status];
210
- const fill = status === "succeeded" || status === "failed" ? "100%" : status === "running" ? "58%" : "6%";
211
351
  const roundsLabel = rounds !== void 0 ? `${rounds} round${rounds === 1 ? "" : "s"}` : void 0;
212
- return /* @__PURE__ */ jsxs("div", { className: "mt-1.5", children: [
213
- /* @__PURE__ */ jsx(
352
+ return /* @__PURE__ */ jsxs2("div", { className: "mt-auto border-border/60 border-t", children: [
353
+ /* @__PURE__ */ jsx3("div", { className: "h-1 w-full overflow-hidden", style: { background: MUTED_TRACK }, children: /* @__PURE__ */ jsx3(
214
354
  "div",
215
355
  {
216
- className: "h-1 w-full overflow-hidden rounded-full",
217
- style: {
218
- background: "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)"
219
- },
220
- children: /* @__PURE__ */ jsx(
221
- "div",
222
- {
223
- className: `h-full rounded-full ${status === "running" ? "animate-pulse" : ""}`,
224
- style: { width: fill, background: color }
225
- }
226
- )
356
+ className: `h-full ${status === "running" ? "animate-pulse" : ""}`,
357
+ style: { width: progressFill(status), background: STATUS_COLOR[status] }
227
358
  }
228
- ),
229
- (roundsLabel || elapsed) && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center justify-between text-[9px] text-muted-foreground", children: [
230
- /* @__PURE__ */ jsx("span", { className: "truncate", children: roundsLabel ?? "" }),
231
- elapsed && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: elapsed })
359
+ ) }),
360
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3 py-1 text-[9px] text-muted-foreground", children: [
361
+ /* @__PURE__ */ jsx3("span", { className: "truncate", children: roundsLabel ?? "" }),
362
+ elapsed && /* @__PURE__ */ jsx3("span", { className: "shrink-0", children: elapsed })
232
363
  ] })
233
364
  ] });
234
365
  }
235
366
  var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
236
- var STATUS_LABEL = {
237
- queued: "Queued",
238
- running: "Running",
239
- succeeded: "Done",
240
- failed: "Failed"
241
- };
242
- var STATUS_BADGE = {
243
- queued: "bg-surface-container-high text-muted-foreground",
244
- running: "bg-primary/15 text-primary",
245
- succeeded: "bg-green-600/10 text-green-600",
246
- failed: "bg-red-500/10 text-red-400"
247
- };
248
- function statusBorder(status) {
249
- switch (status) {
250
- case "running":
251
- return "border-primary ring-1 ring-primary/40";
252
- case "succeeded":
253
- return "border-green-500";
254
- case "failed":
255
- return "border-red-500";
256
- default:
257
- return "opacity-70";
258
- }
259
- }
260
- function MetaChip({ children, title }) {
261
- return /* @__PURE__ */ jsx(
262
- "span",
263
- {
264
- title,
265
- className: "inline-block max-w-[150px] truncate rounded bg-surface-container-high px-1.5 py-0.5 align-middle text-[10px] text-muted-foreground",
266
- children
267
- }
268
- );
269
- }
270
367
  function WorkflowNode({ data }) {
271
368
  const d = data;
272
369
  const state = d.state;
@@ -281,49 +378,55 @@ function WorkflowNode({ data }) {
281
378
  const duration = fmtDuration(state?.durationMs);
282
379
  const cost = fmtCost(state?.costUsd);
283
380
  const tokens = isAgent ? fmtTokens(state?.inputTokens, state?.outputTokens) : void 0;
284
- const errorText = state?.error ? clampPreview(state.error) : void 0;
285
- const outputText = state?.outputPreview ? clampPreview(state.outputPreview) : void 0;
381
+ const runOutput = (() => {
382
+ const source = state?.status === "failed" && state.error ? { text: state.error, tone: "error", label: "Error" } : state && state.status !== "failed" && state.outputPreview ? { text: state.outputPreview, tone: "default", label: "Output" } : null;
383
+ if (!source) return null;
384
+ const trimmed = source.text.trim();
385
+ if (!trimmed) return null;
386
+ const shape = classifyOutput(clampPreview(trimmed));
387
+ return shape.kind === "empty" ? null : { shape, tone: source.tone, label: source.label };
388
+ })();
286
389
  const wrapperClass = `relative flex h-full w-full flex-col overflow-hidden rounded-lg border bg-card shadow-sm transition-colors ${state ? statusBorder(state.status) : ""}`;
287
390
  const wrapperStyle = state ? state.status === "running" ? { boxShadow: "0 0 22px -6px hsl(var(--primary))" } : void 0 : {
288
391
  borderColor: `color-mix(in srgb, ${accent} 42%, transparent)`,
289
392
  backgroundColor: `color-mix(in srgb, ${accent} 6%, hsl(var(--card)))`
290
393
  };
291
- const handles = /* @__PURE__ */ jsxs(Fragment, { children: [
292
- !d.isRoot && /* @__PURE__ */ jsx(Handle, { type: "target", position: targetPos, className: HANDLE_CLASS }),
293
- /* @__PURE__ */ jsx(Handle, { type: "source", position: sourcePos, className: HANDLE_CLASS })
394
+ const handles = /* @__PURE__ */ jsxs2(Fragment, { children: [
395
+ !d.isRoot && /* @__PURE__ */ jsx3(Handle, { type: "target", position: targetPos, className: HANDLE_CLASS }),
396
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: sourcePos, className: HANDLE_CLASS })
294
397
  ] });
295
398
  if (compact) {
296
- return /* @__PURE__ */ jsxs("div", { className: `${wrapperClass} justify-center px-2.5`, style: wrapperStyle, children: [
399
+ return /* @__PURE__ */ jsxs2("div", { className: `${wrapperClass} justify-center px-2.5`, style: wrapperStyle, children: [
297
400
  handles,
298
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
299
- /* @__PURE__ */ jsx(NodeIcon, { kind: d.kind, accent, box: 32, glyph: 16 }),
300
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
301
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
302
- /* @__PURE__ */ jsx("span", { className: "truncate font-medium text-[12px] text-foreground", children: d.title }),
303
- state ? /* @__PURE__ */ jsx(
401
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5", children: [
402
+ /* @__PURE__ */ jsx3(NodeIcon, { kind: d.kind, accent, box: 32, glyph: 16 }),
403
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
404
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
405
+ /* @__PURE__ */ jsx3("span", { className: "truncate font-medium text-[12px] text-foreground", children: d.title }),
406
+ state ? /* @__PURE__ */ jsx3(
304
407
  "span",
305
408
  {
306
409
  className: "ml-auto h-1.5 w-1.5 shrink-0 rounded-full",
307
410
  style: { backgroundColor: STATUS_COLOR[state.status] }
308
411
  }
309
- ) : d.badge && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 rounded bg-surface-container-high px-1 py-0.5 text-[9px] text-muted-foreground", children: d.badge })
412
+ ) : d.badge && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0 rounded bg-surface-container-high px-1 py-0.5 text-[9px] text-muted-foreground", children: d.badge })
310
413
  ] }),
311
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [
312
- /* @__PURE__ */ jsx("span", { className: "truncate", children: model ?? d.subtitle ?? d.kind }),
313
- cost && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0", children: cost }),
314
- duration && /* @__PURE__ */ jsxs("span", { className: "shrink-0", children: [
414
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [
415
+ /* @__PURE__ */ jsx3("span", { className: "truncate", children: model ?? d.subtitle ?? d.kind }),
416
+ cost && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0", children: cost }),
417
+ duration && /* @__PURE__ */ jsxs2("span", { className: "shrink-0", children: [
315
418
  "\xB7 ",
316
419
  duration
317
420
  ] })
318
421
  ] })
319
422
  ] })
320
423
  ] }),
321
- state?.status === "running" && /* @__PURE__ */ jsx(
424
+ state?.status === "running" && /* @__PURE__ */ jsx3(
322
425
  "div",
323
426
  {
324
427
  className: "mt-1.5 h-0.5 w-full overflow-hidden rounded-full",
325
- style: { background: "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)" },
326
- children: /* @__PURE__ */ jsx(
428
+ style: { background: MUTED_TRACK },
429
+ children: /* @__PURE__ */ jsx3(
327
430
  "div",
328
431
  {
329
432
  className: "h-full w-1/2 animate-pulse rounded-full",
@@ -334,83 +437,61 @@ function WorkflowNode({ data }) {
334
437
  )
335
438
  ] });
336
439
  }
337
- return /* @__PURE__ */ jsxs("div", { className: `${wrapperClass} px-3 py-2`, style: wrapperStyle, children: [
440
+ return /* @__PURE__ */ jsxs2("div", { className: wrapperClass, style: wrapperStyle, children: [
338
441
  handles,
339
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
340
- /* @__PURE__ */ jsx(NodeIcon, { kind: d.kind, accent, box: 24, glyph: 14 }),
341
- /* @__PURE__ */ jsx("span", { className: "truncate font-medium text-[12px] text-foreground", children: d.title }),
342
- state ? /* @__PURE__ */ jsx(
343
- "span",
442
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col px-3 pt-2 pb-2", children: [
443
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
444
+ /* @__PURE__ */ jsx3(NodeIcon, { kind: d.kind, accent, box: 24, glyph: 14 }),
445
+ /* @__PURE__ */ jsx3("span", { className: "truncate font-medium text-[12px] text-foreground", children: d.title }),
446
+ state ? /* @__PURE__ */ jsx3(
447
+ "span",
448
+ {
449
+ className: `ml-auto shrink-0 rounded px-1.5 py-0.5 text-[10px] ${STATUS_BADGE[state.status]}`,
450
+ children: STATUS_LABEL[state.status]
451
+ }
452
+ ) : d.badge && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0 rounded bg-surface-container-high px-1.5 py-0.5 text-[10px] text-muted-foreground", children: d.badge })
453
+ ] }),
454
+ d.subtitle && /* @__PURE__ */ jsx3(
455
+ "p",
344
456
  {
345
- className: `ml-auto shrink-0 rounded px-1.5 py-0.5 text-[10px] ${STATUS_BADGE[state.status]}`,
346
- children: STATUS_LABEL[state.status]
457
+ className: "mt-1 truncate text-[11px] text-muted-foreground",
458
+ title: d.subtitle,
459
+ children: d.subtitle
347
460
  }
348
- ) : d.badge && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 rounded bg-surface-container-high px-1.5 py-0.5 text-[10px] text-muted-foreground", children: d.badge })
349
- ] }),
350
- d.subtitle && /* @__PURE__ */ jsx(
351
- "p",
352
- {
353
- className: "mt-1 truncate text-[11px] text-muted-foreground",
354
- title: d.subtitle,
355
- children: d.subtitle
356
- }
357
- ),
358
- (model || cost || tokens) && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
359
- model && /* @__PURE__ */ jsx(MetaChip, { title: model, children: model }),
360
- cost && /* @__PURE__ */ jsx(MetaChip, { children: cost }),
361
- tokens && /* @__PURE__ */ jsx(MetaChip, { children: tokens })
461
+ ),
462
+ d.provider && /* @__PURE__ */ jsxs2("span", { className: "mt-1.5 inline-flex w-fit items-center gap-1 rounded bg-surface-container-high px-1.5 py-0.5 text-[10px] text-muted-foreground", children: [
463
+ /* @__PURE__ */ jsx3(
464
+ ProviderIcon,
465
+ {
466
+ id: d.provider,
467
+ displayName: providerLabel(d.provider),
468
+ size: 12,
469
+ className: "rounded-[2px]"
470
+ }
471
+ ),
472
+ providerLabel(d.provider)
473
+ ] }),
474
+ (model || cost || tokens) && /* @__PURE__ */ jsxs2("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
475
+ model && /* @__PURE__ */ jsx3(MetaChip, { title: model, children: model }),
476
+ cost && /* @__PURE__ */ jsx3(MetaChip, { children: cost }),
477
+ tokens && /* @__PURE__ */ jsx3(MetaChip, { children: tokens })
478
+ ] }),
479
+ runOutput && /* @__PURE__ */ jsxs2("div", { className: "mt-1.5 min-h-0", children: [
480
+ /* @__PURE__ */ jsx3("div", { className: "mb-0.5 font-medium text-[8px] text-muted-foreground/70 uppercase tracking-[0.08em]", children: runOutput.label }),
481
+ /* @__PURE__ */ jsx3(NodeOutputBody, { shape: runOutput.shape, tone: runOutput.tone, rows: 2 })
482
+ ] })
362
483
  ] }),
363
- state?.status === "failed" && errorText && /* @__PURE__ */ jsx(
364
- "p",
365
- {
366
- className: "mt-1.5 line-clamp-2 text-[10px] text-red-400",
367
- title: errorText,
368
- children: errorText
369
- }
370
- ),
371
- outputText && state?.status !== "failed" && /* @__PURE__ */ jsx(
372
- "p",
373
- {
374
- className: "mt-1.5 line-clamp-2 text-[10px] text-muted-foreground",
375
- title: outputText,
376
- children: outputText
377
- }
378
- ),
379
- state && d.tone !== "trigger" && /* @__PURE__ */ jsx(
380
- ProgressStrip,
484
+ state && d.tone !== "trigger" && /* @__PURE__ */ jsx3(
485
+ StatusFooter,
381
486
  {
382
487
  status: state.status,
383
488
  rounds: isAgent ? state.rounds : void 0,
384
489
  elapsed: duration
385
490
  }
386
- ),
387
- d.provider && /* @__PURE__ */ jsxs("span", { className: "mt-1.5 inline-flex w-fit items-center gap-1 rounded bg-surface-container-high px-1.5 py-0.5 text-[10px] text-muted-foreground", children: [
388
- /* @__PURE__ */ jsx(
389
- ProviderIcon,
390
- {
391
- id: d.provider,
392
- displayName: providerLabel(d.provider),
393
- size: 12,
394
- className: "rounded-[2px]"
395
- }
396
- ),
397
- providerLabel(d.provider)
398
- ] })
491
+ )
399
492
  ] });
400
493
  }
401
494
  var NODE_TYPES = { wfNode: WorkflowNode };
402
- function edgeColor(status) {
403
- switch (status) {
404
- case "running":
405
- return EDGE_RUNNING;
406
- case "succeeded":
407
- return EDGE_DONE;
408
- case "failed":
409
- return EDGE_FAIL;
410
- default:
411
- return EDGE_MUTED;
412
- }
413
- }
414
495
  function buildStyledEdges(base, nodeState) {
415
496
  return base.map((e) => {
416
497
  const status = nodeState?.[e.target]?.status;
@@ -486,7 +567,7 @@ function WorkflowGraph({
486
567
  [onNodeClick]
487
568
  );
488
569
  if (structural.error || structural.nodes.length === 0) {
489
- return /* @__PURE__ */ jsx(
570
+ return /* @__PURE__ */ jsx3(
490
571
  "div",
491
572
  {
492
573
  className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-muted-foreground text-xs ${className ?? ""}`,
@@ -494,7 +575,7 @@ function WorkflowGraph({
494
575
  }
495
576
  );
496
577
  }
497
- return /* @__PURE__ */ jsx(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx(DensityContext.Provider, { value: compact, children: /* @__PURE__ */ jsx("div", { className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs(
578
+ return /* @__PURE__ */ jsx3(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx3(DensityContext.Provider, { value: compact, children: /* @__PURE__ */ jsx3("div", { className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs2(
498
579
  ReactFlow,
499
580
  {
500
581
  nodes,
@@ -524,10 +605,10 @@ function WorkflowGraph({
524
605
  minZoom: 0.2,
525
606
  maxZoom: 1.5,
526
607
  children: [
527
- /* @__PURE__ */ jsx(Background, { gap: 18 }),
528
- !isPreview && /* @__PURE__ */ jsxs(Fragment, { children: [
529
- /* @__PURE__ */ jsx(Controls, { showInteractive: false, position: "bottom-right" }),
530
- /* @__PURE__ */ jsx(Panel, { position: "top-left", children: /* @__PURE__ */ jsxs(
608
+ /* @__PURE__ */ jsx3(Background, { gap: 18 }),
609
+ !isPreview && /* @__PURE__ */ jsxs2(Fragment, { children: [
610
+ /* @__PURE__ */ jsx3(Controls, { showInteractive: false, position: "bottom-right" }),
611
+ /* @__PURE__ */ jsx3(Panel, { position: "top-left", children: /* @__PURE__ */ jsxs2(
531
612
  "button",
532
613
  {
533
614
  type: "button",
@@ -536,7 +617,7 @@ function WorkflowGraph({
536
617
  title: userCompact ? "Expand nodes" : "Collapse nodes",
537
618
  className: "flex items-center gap-1 rounded-md border border-border bg-card/80 px-2 py-1 text-[11px] text-muted-foreground shadow-sm backdrop-blur transition hover:text-foreground",
538
619
  children: [
539
- userCompact ? /* @__PURE__ */ jsx(Maximize2, { size: 12 }) : /* @__PURE__ */ jsx(Minimize2, { size: 12 }),
620
+ userCompact ? /* @__PURE__ */ jsx3(Maximize2, { size: 12 }) : /* @__PURE__ */ jsx3(Minimize2, { size: 12 }),
540
621
  userCompact ? "Expand" : "Compact"
541
622
  ]
542
623
  }
@@ -18,19 +18,19 @@ import { parse as parseYaml } from "yaml";
18
18
  var NODE_W = 244;
19
19
  var RANK_SEP = 76;
20
20
  var CROSS_SEP = 22;
21
- var CARD_CHROME = 22;
22
- var HEADER_ROW = 30;
21
+ var CARD_CHROME = 18;
22
+ var HEADER_ROW = 28;
23
23
  var TEXT_ROW = 20;
24
24
  var META_ROW = 46;
25
- var PREVIEW_ROW = 36;
26
- var PROGRESS_ROW = 26;
25
+ var OUTPUT_ROW = 44;
26
+ var FOOTER_ROW = 26;
27
27
  var COMPACT_NODE_SIZE = { width: 240, height: 64 };
28
28
  function nodeHeight(data, withRunState) {
29
29
  let h = CARD_CHROME + HEADER_ROW;
30
30
  if (data.subtitle) h += TEXT_ROW;
31
- if (withRunState || data.model) h += META_ROW;
32
- if (withRunState) h += PREVIEW_ROW + PROGRESS_ROW;
33
31
  if (data.provider) h += TEXT_ROW;
32
+ if (withRunState || data.model) h += META_ROW;
33
+ if (withRunState) h += OUTPUT_ROW + FOOTER_ROW;
34
34
  return h;
35
35
  }
36
36
  function asRecord(v) {
package/dist/globals.css CHANGED
@@ -75,6 +75,7 @@
75
75
  --tracking-wider: 0.05em;
76
76
  --tracking-widest: 0.1em;
77
77
  --leading-tight: 1.25;
78
+ --leading-snug: 1.375;
78
79
  --leading-relaxed: 1.625;
79
80
  --radius-sm: 0.25rem;
80
81
  --radius-md: 0.375rem;
@@ -542,6 +543,9 @@
542
543
  .mt-6 {
543
544
  margin-top: calc(var(--spacing) * 6);
544
545
  }
546
+ .mt-auto {
547
+ margin-top: auto;
548
+ }
545
549
  .mr-1 {
546
550
  margin-right: calc(var(--spacing) * 1);
547
551
  }
@@ -626,6 +630,12 @@
626
630
  -webkit-box-orient: vertical;
627
631
  -webkit-line-clamp: 2;
628
632
  }
633
+ .line-clamp-3 {
634
+ overflow: hidden;
635
+ display: -webkit-box;
636
+ -webkit-box-orient: vertical;
637
+ -webkit-line-clamp: 3;
638
+ }
629
639
  .block {
630
640
  display: block;
631
641
  }
@@ -1186,6 +1196,9 @@
1186
1196
  .max-w-\[14rem\] {
1187
1197
  max-width: 14rem;
1188
1198
  }
1199
+ .max-w-\[45\%\] {
1200
+ max-width: 45%;
1201
+ }
1189
1202
  .max-w-\[75\%\] {
1190
1203
  max-width: 75%;
1191
1204
  }
@@ -1922,6 +1935,12 @@
1922
1935
  .border-border {
1923
1936
  border-color: var(--color-border);
1924
1937
  }
1938
+ .border-border\/60 {
1939
+ border-color: color-mix(in srgb, hsl(var(--border)) 60%, transparent);
1940
+ @supports (color: color-mix(in lab, red, red)) {
1941
+ border-color: color-mix(in oklab, var(--color-border) 60%, transparent);
1942
+ }
1943
+ }
1925
1944
  .border-destructive\/20 {
1926
1945
  border-color: color-mix(in srgb, hsl(var(--destructive)) 20%, transparent);
1927
1946
  @supports (color: color-mix(in lab, red, red)) {
@@ -2961,6 +2980,10 @@
2961
2980
  --tw-leading: var(--leading-relaxed);
2962
2981
  line-height: var(--leading-relaxed);
2963
2982
  }
2983
+ .leading-snug {
2984
+ --tw-leading: var(--leading-snug);
2985
+ line-height: var(--leading-snug);
2986
+ }
2964
2987
  .leading-tight {
2965
2988
  --tw-leading: var(--leading-tight);
2966
2989
  line-height: var(--leading-tight);
@@ -3286,6 +3309,12 @@
3286
3309
  .text-red-400 {
3287
3310
  color: var(--color-red-400);
3288
3311
  }
3312
+ .text-red-400\/80 {
3313
+ color: color-mix(in srgb, oklch(70.4% 0.191 22.216) 80%, transparent);
3314
+ @supports (color: color-mix(in lab, red, red)) {
3315
+ color: color-mix(in oklab, var(--color-red-400) 80%, transparent);
3316
+ }
3317
+ }
3289
3318
  .text-secondary-foreground {
3290
3319
  color: var(--color-secondary-foreground);
3291
3320
  }
package/dist/styles.css CHANGED
@@ -75,6 +75,7 @@
75
75
  --tracking-wider: 0.05em;
76
76
  --tracking-widest: 0.1em;
77
77
  --leading-tight: 1.25;
78
+ --leading-snug: 1.375;
78
79
  --leading-relaxed: 1.625;
79
80
  --radius-sm: 0.25rem;
80
81
  --radius-md: 0.375rem;
@@ -542,6 +543,9 @@
542
543
  .mt-6 {
543
544
  margin-top: calc(var(--spacing) * 6);
544
545
  }
546
+ .mt-auto {
547
+ margin-top: auto;
548
+ }
545
549
  .mr-1 {
546
550
  margin-right: calc(var(--spacing) * 1);
547
551
  }
@@ -626,6 +630,12 @@
626
630
  -webkit-box-orient: vertical;
627
631
  -webkit-line-clamp: 2;
628
632
  }
633
+ .line-clamp-3 {
634
+ overflow: hidden;
635
+ display: -webkit-box;
636
+ -webkit-box-orient: vertical;
637
+ -webkit-line-clamp: 3;
638
+ }
629
639
  .block {
630
640
  display: block;
631
641
  }
@@ -1186,6 +1196,9 @@
1186
1196
  .max-w-\[14rem\] {
1187
1197
  max-width: 14rem;
1188
1198
  }
1199
+ .max-w-\[45\%\] {
1200
+ max-width: 45%;
1201
+ }
1189
1202
  .max-w-\[75\%\] {
1190
1203
  max-width: 75%;
1191
1204
  }
@@ -1922,6 +1935,12 @@
1922
1935
  .border-border {
1923
1936
  border-color: var(--color-border);
1924
1937
  }
1938
+ .border-border\/60 {
1939
+ border-color: color-mix(in srgb, hsl(var(--border)) 60%, transparent);
1940
+ @supports (color: color-mix(in lab, red, red)) {
1941
+ border-color: color-mix(in oklab, var(--color-border) 60%, transparent);
1942
+ }
1943
+ }
1925
1944
  .border-destructive\/20 {
1926
1945
  border-color: color-mix(in srgb, hsl(var(--destructive)) 20%, transparent);
1927
1946
  @supports (color: color-mix(in lab, red, red)) {
@@ -2961,6 +2980,10 @@
2961
2980
  --tw-leading: var(--leading-relaxed);
2962
2981
  line-height: var(--leading-relaxed);
2963
2982
  }
2983
+ .leading-snug {
2984
+ --tw-leading: var(--leading-snug);
2985
+ line-height: var(--leading-snug);
2986
+ }
2964
2987
  .leading-tight {
2965
2988
  --tw-leading: var(--leading-tight);
2966
2989
  line-height: var(--leading-tight);
@@ -3286,6 +3309,12 @@
3286
3309
  .text-red-400 {
3287
3310
  color: var(--color-red-400);
3288
3311
  }
3312
+ .text-red-400\/80 {
3313
+ color: color-mix(in srgb, oklch(70.4% 0.191 22.216) 80%, transparent);
3314
+ @supports (color: color-mix(in lab, red, red)) {
3315
+ color: color-mix(in oklab, var(--color-red-400) 80%, transparent);
3316
+ }
3317
+ }
3289
3318
  .text-secondary-foreground {
3290
3319
  color: var(--color-secondary-foreground);
3291
3320
  }
package/dist/workflows.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildWorkflowGraph
3
- } from "./chunk-VXGI2XUK.js";
3
+ } from "./chunk-FNE5QLU3.js";
4
4
  import "./chunk-U67V476Y.js";
5
5
 
6
6
  // src/workflows/WorkflowGraphLazy.tsx
@@ -26,7 +26,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
26
26
  function lazyWorkflowGraph() {
27
27
  return lazy(
28
28
  () => retryImport(
29
- () => import("./WorkflowGraph-64XJBIW2.js").then((m) => ({ default: m.WorkflowGraph }))
29
+ () => import("./WorkflowGraph-RA7ZHN4I.js").then((m) => ({ default: m.WorkflowGraph }))
30
30
  )
31
31
  );
32
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.74.0",
3
+ "version": "0.75.0",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",