@tangle-network/sandbox-ui 0.113.4 → 0.113.6

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.
@@ -18,6 +18,7 @@ import {
18
18
  } from "@tangle-network/agent-interface";
19
19
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
20
20
  import { Brain, ChevronDown, Sparkles } from "lucide-react";
21
+ import { focusRing } from "@tangle-network/ui/utils";
21
22
  import { jsx, jsxs } from "react/jsx-runtime";
22
23
  var REASONING_LADDER = reasoningLadder;
23
24
  function ReasoningGlyph({ level }) {
@@ -87,7 +88,8 @@ function ReasoningLevelPicker({
87
88
  className: cn(
88
89
  "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
89
90
  "text-xs font-medium text-foreground shadow-sm transition-colors",
90
- "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
91
+ "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high",
92
+ focusRing,
91
93
  "data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
92
94
  "disabled:cursor-not-allowed disabled:opacity-50",
93
95
  className,
@@ -224,6 +226,7 @@ var AnchoredPopover = React2.forwardRef(
224
226
  // src/chat/informative-lock.tsx
225
227
  import { Lock, Plus } from "lucide-react";
226
228
  import * as React3 from "react";
229
+ import { focusRing as focusRing2 } from "@tangle-network/ui/utils";
227
230
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
228
231
  function InformativeLock({
229
232
  children,
@@ -278,7 +281,8 @@ function InformativeLock({
278
281
  className: cn(
279
282
  "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5 opacity-60",
280
283
  "text-xs font-medium text-foreground shadow-sm transition-colors",
281
- "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
284
+ "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high",
285
+ focusRing2,
282
286
  "data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
283
287
  triggerClassName
284
288
  ),
@@ -319,7 +323,8 @@ function InformativeLock({
319
323
  className: cn(
320
324
  "mt-3 flex w-full items-center justify-center gap-1.5 rounded-md px-2.5 py-2 text-sm font-medium text-foreground",
321
325
  "bg-primary/10 ring-1 ring-inset ring-primary/25 transition-colors",
322
- "hover:bg-primary/15 focus:outline-none focus-visible:ring-1 focus-visible:ring-ring"
326
+ "hover:bg-primary/15",
327
+ focusRing2
323
328
  ),
324
329
  children: [
325
330
  /* @__PURE__ */ jsx3(Plus, { className: "h-4 w-4" }),
@@ -336,6 +341,7 @@ function InformativeLock({
336
341
  }
337
342
 
338
343
  // src/chat/agent-profile-picker.tsx
344
+ import { focusField, focusRing as focusRing3 } from "@tangle-network/ui/utils";
339
345
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
340
346
  function profileSubtitle(profile) {
341
347
  if (profile.description) return profile.description;
@@ -418,7 +424,8 @@ function AgentProfilePicker({
418
424
  className: cn(
419
425
  "inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
420
426
  "text-xs font-medium text-foreground shadow-sm transition-colors",
421
- "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
427
+ "hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high",
428
+ focusRing3,
422
429
  "data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
423
430
  "disabled:cursor-not-allowed disabled:opacity-60",
424
431
  triggerClassName
@@ -576,7 +583,7 @@ function ProfileForm({
576
583
  value: name,
577
584
  onChange: (event) => setName(event.target.value),
578
585
  placeholder: "Agent name (e.g. Tax Reviewer)",
579
- className: "w-full rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
586
+ className: `w-full rounded-lg border bg-surface-container px-3 py-2 text-sm ${focusField}`
580
587
  }
581
588
  ),
582
589
  capabilities.length > 0 && /* @__PURE__ */ jsxs3("div", { children: [
@@ -611,7 +618,7 @@ function ProfileForm({
611
618
  onChange: (event) => setInstructions(event.target.value),
612
619
  rows: 2,
613
620
  placeholder: "Instructions (optional) \u2014 how this agent should behave",
614
- className: "w-full resize-none rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-3 py-2 text-sm outline-none focus:border-primary"
621
+ className: `w-full resize-none rounded-lg border bg-surface-container px-3 py-2 text-sm ${focusField}`
615
622
  }
616
623
  ),
617
624
  /* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-2", children: [
@@ -650,6 +657,7 @@ import { modelProvider, snapHarnessToModel } from "@tangle-network/agent-interfa
650
657
  import * as React5 from "react";
651
658
  import { Sparkles as Sparkles2, Send, Square, X } from "lucide-react";
652
659
  import { ChatMessage } from "@tangle-network/ui/chat";
660
+ import { focusField as focusField2 } from "@tangle-network/ui/utils";
653
661
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
654
662
  function defaultScopeLabel(scope) {
655
663
  if (scope.label) return scope.label;
@@ -879,7 +887,7 @@ function ArtifactAgentDock({
879
887
  placeholder: `Ask about ${heading}\u2026`,
880
888
  rows: 2,
881
889
  disabled: !threadId,
882
- className: "min-h-[44px] flex-1 resize-none rounded-[20px] border border-[var(--md3-outline-variant)] bg-surface-dim px-3.5 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:outline-none focus:border-[var(--border-accent)] disabled:opacity-50"
890
+ className: `min-h-[44px] flex-1 resize-none rounded-[20px] border bg-surface-dim px-3.5 py-2 text-sm text-foreground placeholder:text-muted-foreground disabled:opacity-50 ${focusField2}`
883
891
  }
884
892
  ),
885
893
  sending ? /* @__PURE__ */ jsx5(
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  RichFileTree
3
- } from "./chunk-PZVUIUUW.js";
3
+ } from "./chunk-ULPLRBMF.js";
4
4
  import {
5
5
  PortsList
6
- } from "./chunk-LDAKIKTV.js";
6
+ } from "./chunk-DYXXYGS4.js";
7
7
  import {
8
8
  TerminalView
9
9
  } from "./chunk-IACRDXER.js";
@@ -215,16 +215,20 @@ var require_extends = __commonJS({
215
215
  // src/workbench/sandbox-artifact-pane.tsx
216
216
  import * as React9 from "react";
217
217
  import { Code2, GitCompare, Globe, Network, SquareTerminal } from "lucide-react";
218
+ import { focusRingInset } from "@tangle-network/ui/utils";
218
219
 
219
220
  // src/workbench/pill-tabs.tsx
220
221
  import * as React from "react";
222
+ import { focusRing } from "@tangle-network/ui/utils";
221
223
  import { jsx, jsxs } from "react/jsx-runtime";
222
224
  function PillTabs({
223
225
  items,
224
226
  value,
225
227
  onChange,
226
228
  className,
227
- "aria-label": ariaLabel
229
+ "aria-label": ariaLabel,
230
+ idPrefix,
231
+ panelId
228
232
  }) {
229
233
  const containerRef = React.useRef(null);
230
234
  const tabRefs = React.useRef(/* @__PURE__ */ new Map());
@@ -281,10 +285,38 @@ function PillTabs({
281
285
  },
282
286
  type: "button",
283
287
  role: "tab",
288
+ id: idPrefix ? `${idPrefix}-${item.value}` : void 0,
289
+ "aria-controls": panelId,
284
290
  "aria-selected": selected,
291
+ tabIndex: selected ? 0 : -1,
285
292
  onClick: () => onChange(item.value),
293
+ onKeyDown: (event) => {
294
+ const index = items.findIndex((candidate) => candidate.value === item.value);
295
+ let nextIndex;
296
+ switch (event.key) {
297
+ case "ArrowRight":
298
+ nextIndex = (index + 1) % items.length;
299
+ break;
300
+ case "ArrowLeft":
301
+ nextIndex = (index - 1 + items.length) % items.length;
302
+ break;
303
+ case "Home":
304
+ nextIndex = 0;
305
+ break;
306
+ case "End":
307
+ nextIndex = items.length - 1;
308
+ break;
309
+ default:
310
+ return;
311
+ }
312
+ event.preventDefault();
313
+ const next = items[nextIndex];
314
+ onChange(next.value);
315
+ tabRefs.current.get(next.value)?.focus();
316
+ },
286
317
  className: cn(
287
318
  "relative z-10 inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors",
319
+ focusRing,
288
320
  selected ? "text-foreground" : "text-muted-foreground hover:text-foreground"
289
321
  ),
290
322
  children: [
@@ -388,6 +420,7 @@ function PanelHeader({ filename, gitStatus, stats, copyText, children, className
388
420
 
389
421
  // src/workbench/code-view.tsx
390
422
  import * as React6 from "react";
423
+ import { focusRing as focusRing2 } from "@tangle-network/ui/utils";
391
424
 
392
425
  // node_modules/.pnpm/react-syntax-highlighter@16.1.1_react@19.2.8/node_modules/react-syntax-highlighter/dist/esm/highlight.js
393
426
  var import_objectWithoutProperties = __toESM(require_objectWithoutProperties());
@@ -7217,27 +7250,35 @@ function CodeView({
7217
7250
  showTree = true,
7218
7251
  className
7219
7252
  }) {
7253
+ const [narrowPane, setNarrowPane] = React6.useState("code");
7220
7254
  const treePaths = React6.useMemo(() => {
7221
7255
  const list = paths && paths.length > 0 ? paths : [path];
7222
7256
  return list.includes(path) ? list : [...list, path];
7223
7257
  }, [paths, path]);
7224
- return /* @__PURE__ */ jsxs4("div", { className: cn("flex h-full min-h-0 w-full", className), children: [
7225
- showTree && /* @__PURE__ */ jsx6("div", { className: "sandbox-tree-surface flex w-60 shrink-0 flex-col border-r border-[var(--md3-outline-variant)] bg-surface-container-lowest", children: /* @__PURE__ */ jsx6(
7258
+ return /* @__PURE__ */ jsx6("div", { className: cn("sandbox-code-view h-full min-h-0 w-full", className), children: /* @__PURE__ */ jsxs4("div", { className: "sandbox-code-view-layout flex h-full min-h-0 w-full", children: [
7259
+ showTree && /* @__PURE__ */ jsxs4("div", { className: "sandbox-code-view-mobile-toolbar", role: "group", "aria-label": "Code view pane", children: [
7260
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-pressed": narrowPane === "files", onClick: () => setNarrowPane("files"), className: focusRing2, children: "Files" }),
7261
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-pressed": narrowPane === "code", onClick: () => setNarrowPane("code"), className: focusRing2, children: "Code" })
7262
+ ] }),
7263
+ showTree && /* @__PURE__ */ jsx6("div", { 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", "data-mobile-open": narrowPane === "files", children: /* @__PURE__ */ jsx6(
7226
7264
  RichFileTree,
7227
7265
  {
7228
7266
  paths: treePaths,
7229
7267
  selectedPath: path,
7230
- onSelect: (next) => onFileSelect?.(next),
7268
+ onSelect: (next) => {
7269
+ onFileSelect?.(next);
7270
+ setNarrowPane("code");
7271
+ },
7231
7272
  gitStatus,
7232
7273
  search: treePaths.length > 12,
7233
7274
  height: "100%"
7234
7275
  }
7235
7276
  ) }),
7236
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-container", children: [
7277
+ /* @__PURE__ */ jsxs4("div", { className: "sandbox-code-view-editor flex min-w-0 flex-1 flex-col bg-surface-container", "data-mobile-hidden": showTree && narrowPane === "files", children: [
7237
7278
  /* @__PURE__ */ jsx6(FileBreadcrumb, { path }),
7238
7279
  /* @__PURE__ */ jsx6("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx6(CodeSurface, { code: content, filename, language }) })
7239
7280
  ] })
7240
- ] });
7281
+ ] }) });
7241
7282
  }
7242
7283
 
7243
7284
  // src/workbench/diff-view.tsx
@@ -7319,28 +7360,52 @@ function DiffView({ filename, baseline, current, showFileHeader = true, classNam
7319
7360
  // src/workbench/preview-view.tsx
7320
7361
  import * as React8 from "react";
7321
7362
  import { ExternalLink, RotateCw } from "lucide-react";
7363
+ import { focusField } from "@tangle-network/ui/utils";
7322
7364
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
7323
7365
  function PreviewView({ url, className }) {
7324
7366
  const [address, setAddress] = React8.useState(url);
7367
+ const [activeUrl, setActiveUrl] = React8.useState(url);
7325
7368
  const [iframeKey, setIframeKey] = React8.useState(0);
7326
7369
  const [loading, setLoading] = React8.useState(true);
7370
+ const [slow, setSlow] = React8.useState(false);
7371
+ const [addressError, setAddressError] = React8.useState(null);
7372
+ const errorId = React8.useId();
7327
7373
  React8.useEffect(() => {
7328
7374
  setAddress(url);
7375
+ setActiveUrl(url);
7329
7376
  setIframeKey((k) => k + 1);
7330
7377
  setLoading(true);
7378
+ setSlow(false);
7379
+ setAddressError(null);
7331
7380
  }, [url]);
7332
7381
  React8.useEffect(() => {
7333
- const t = setTimeout(() => setLoading(false), 1500);
7382
+ if (!loading) return;
7383
+ const t = setTimeout(() => setSlow(true), 8e3);
7334
7384
  return () => clearTimeout(t);
7335
- }, [iframeKey]);
7385
+ }, [iframeKey, loading]);
7336
7386
  const reload = React8.useCallback(() => {
7337
7387
  setIframeKey((k) => k + 1);
7338
7388
  setLoading(true);
7389
+ setSlow(false);
7339
7390
  }, []);
7340
7391
  const navigate = React8.useCallback(() => {
7392
+ let nextUrl;
7393
+ try {
7394
+ nextUrl = new URL(address, activeUrl);
7395
+ } catch {
7396
+ setAddressError("Enter a valid preview URL.");
7397
+ return;
7398
+ }
7399
+ if (nextUrl.protocol !== "http:" && nextUrl.protocol !== "https:") {
7400
+ setAddressError("Use an HTTP or HTTPS preview URL.");
7401
+ return;
7402
+ }
7403
+ setAddressError(null);
7404
+ setActiveUrl(nextUrl.toString());
7341
7405
  setIframeKey((k) => k + 1);
7342
7406
  setLoading(true);
7343
- }, []);
7407
+ setSlow(false);
7408
+ }, [activeUrl, address]);
7344
7409
  return /* @__PURE__ */ jsxs5("div", { className: cn("flex h-full min-h-0 flex-col bg-surface-container", className), children: [
7345
7410
  /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-2 py-1.5", children: [
7346
7411
  /* @__PURE__ */ jsx8(
@@ -7357,16 +7422,24 @@ function PreviewView({ url, className }) {
7357
7422
  "input",
7358
7423
  {
7359
7424
  value: address,
7360
- onChange: (e) => setAddress(e.target.value),
7361
- onKeyDown: (e) => e.key === "Enter" && navigate(),
7425
+ onChange: (e) => {
7426
+ setAddress(e.target.value);
7427
+ setAddressError(null);
7428
+ },
7429
+ onKeyDown: (e) => {
7430
+ if (e.key === "Enter") navigate();
7431
+ },
7432
+ "aria-label": "Preview address",
7433
+ "aria-invalid": addressError != null,
7434
+ "aria-describedby": addressError ? errorId : void 0,
7362
7435
  spellCheck: false,
7363
- className: "h-7 flex-1 rounded-md border border-[var(--md3-outline-variant)] bg-surface-container px-2.5 font-mono text-xs text-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring"
7436
+ className: `h-7 flex-1 rounded-md border bg-surface-container px-2.5 font-mono text-xs text-foreground ${focusField}`
7364
7437
  }
7365
7438
  ),
7366
7439
  /* @__PURE__ */ jsx8(
7367
7440
  "a",
7368
7441
  {
7369
- href: address,
7442
+ href: activeUrl,
7370
7443
  target: "_blank",
7371
7444
  rel: "noopener noreferrer",
7372
7445
  "aria-label": "Open preview in new tab",
@@ -7375,23 +7448,23 @@ function PreviewView({ url, className }) {
7375
7448
  }
7376
7449
  )
7377
7450
  ] }),
7451
+ addressError && /* @__PURE__ */ jsx8("p", { id: errorId, role: "alert", className: "px-3 py-1 text-xs text-destructive", children: addressError }),
7378
7452
  /* @__PURE__ */ jsxs5("div", { className: "relative min-h-0 flex-1 bg-surface-container-lowest", children: [
7379
7453
  /* @__PURE__ */ jsx8(
7380
7454
  "iframe",
7381
7455
  {
7382
- src: address,
7456
+ src: activeUrl,
7383
7457
  title: "Sandbox preview",
7384
7458
  className: "h-full w-full border-0 bg-white",
7385
7459
  sandbox: "allow-scripts allow-same-origin allow-forms allow-popups",
7386
- onLoad: () => setLoading(false)
7460
+ onLoad: () => {
7461
+ setLoading(false);
7462
+ setSlow(false);
7463
+ }
7387
7464
  },
7388
- `${iframeKey}-${address}`
7465
+ iframeKey
7389
7466
  ),
7390
- loading && /* @__PURE__ */ jsx8("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-surface-container-lowest", children: /* @__PURE__ */ jsxs5("p", { className: "text-xs text-muted-foreground", children: [
7391
- "Loading ",
7392
- address,
7393
- "\u2026"
7394
- ] }) })
7467
+ loading && /* @__PURE__ */ jsx8("div", { role: "status", className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-surface-container-lowest", children: /* @__PURE__ */ jsx8("p", { className: "px-4 text-center text-xs text-muted-foreground", children: slow ? "Preview is still loading. Check the address or open it in a new tab." : `Loading ${activeUrl}\u2026` }) })
7395
7468
  ] })
7396
7469
  ] });
7397
7470
  }
@@ -7441,6 +7514,8 @@ function SandboxArtifactPane({
7441
7514
  const [internalView, setInternalView] = React9.useState(
7442
7515
  () => defaultView && (defaultView !== "diff" || hasDiff) ? defaultView : "code"
7443
7516
  );
7517
+ const tabIdPrefix = React9.useId();
7518
+ const panelId = `${tabIdPrefix}-panel`;
7444
7519
  const controlled = activeView != null;
7445
7520
  const requestedView = controlled ? activeView : internalView;
7446
7521
  const view = available.includes(requestedView) ? requestedView : available[0];
@@ -7488,49 +7563,69 @@ function SandboxArtifactPane({
7488
7563
  copyText
7489
7564
  }
7490
7565
  ),
7491
- /* @__PURE__ */ jsx9("div", { className: "flex items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest px-3 py-1.5", children: /* @__PURE__ */ jsx9(PillTabs, { items: tabs, value: view, onChange: selectView, "aria-label": "Artifact view" }) }),
7492
- /* @__PURE__ */ jsxs6("div", { className: "min-h-0 flex-1", children: [
7493
- view === "code" && /* @__PURE__ */ jsx9(
7494
- CodeView,
7495
- {
7496
- path: artifact.path,
7497
- filename: artifact.filename,
7498
- content: artifact.content,
7499
- language: artifact.language,
7500
- paths,
7501
- gitStatus,
7502
- showTree,
7503
- onFileSelect
7504
- }
7505
- ),
7506
- view === "diff" && hasDiff && /* @__PURE__ */ jsx9(
7507
- DiffView,
7508
- {
7509
- filename: artifact.filename,
7510
- baseline: artifact.baselineContent ?? "",
7511
- current: artifact.content
7512
- }
7513
- ),
7514
- view === "preview" && hasPreview && /* @__PURE__ */ jsx9(PreviewView, { url: previewUrl }),
7515
- view === "ports" && hasPorts && /* @__PURE__ */ jsx9("div", { className: "h-full overflow-auto bg-surface-container p-4", children: /* @__PURE__ */ jsx9(
7516
- PortsList,
7517
- {
7518
- ports,
7519
- onExposePort: onExposePort ?? noop,
7520
- onRemovePort
7521
- }
7522
- ) }),
7523
- view === "terminal" && hasTerminal && /* @__PURE__ */ jsx9("div", { className: "h-full bg-surface-container-lowest", children: /* @__PURE__ */ jsx9(
7524
- TerminalView,
7525
- {
7526
- apiUrl: terminal.apiUrl,
7527
- token: terminal.token,
7528
- connectionId: terminal.connectionId,
7529
- title: terminal.title,
7530
- subtitle: terminal.subtitle
7531
- }
7532
- ) })
7533
- ] })
7566
+ /* @__PURE__ */ jsx9("div", { className: "flex items-center border-b border-[var(--md3-outline-variant)] bg-surface-container-lowest px-3 py-1.5", children: /* @__PURE__ */ jsx9(
7567
+ PillTabs,
7568
+ {
7569
+ items: tabs,
7570
+ value: view,
7571
+ onChange: selectView,
7572
+ "aria-label": "Artifact view",
7573
+ idPrefix: tabIdPrefix,
7574
+ panelId
7575
+ }
7576
+ ) }),
7577
+ /* @__PURE__ */ jsxs6(
7578
+ "div",
7579
+ {
7580
+ id: panelId,
7581
+ role: "tabpanel",
7582
+ "aria-labelledby": `${tabIdPrefix}-${view}`,
7583
+ tabIndex: 0,
7584
+ className: `min-h-0 flex-1 ${focusRingInset}`,
7585
+ children: [
7586
+ view === "code" && /* @__PURE__ */ jsx9(
7587
+ CodeView,
7588
+ {
7589
+ path: artifact.path,
7590
+ filename: artifact.filename,
7591
+ content: artifact.content,
7592
+ language: artifact.language,
7593
+ paths,
7594
+ gitStatus,
7595
+ showTree,
7596
+ onFileSelect
7597
+ }
7598
+ ),
7599
+ view === "diff" && hasDiff && /* @__PURE__ */ jsx9(
7600
+ DiffView,
7601
+ {
7602
+ filename: artifact.filename,
7603
+ baseline: artifact.baselineContent ?? "",
7604
+ current: artifact.content
7605
+ }
7606
+ ),
7607
+ view === "preview" && hasPreview && /* @__PURE__ */ jsx9(PreviewView, { url: previewUrl }),
7608
+ view === "ports" && hasPorts && /* @__PURE__ */ jsx9("div", { className: "h-full overflow-auto bg-surface-container p-4", children: /* @__PURE__ */ jsx9(
7609
+ PortsList,
7610
+ {
7611
+ ports,
7612
+ onExposePort: onExposePort ?? noop,
7613
+ onRemovePort
7614
+ }
7615
+ ) }),
7616
+ view === "terminal" && hasTerminal && /* @__PURE__ */ jsx9("div", { className: "h-full bg-surface-container-lowest", children: /* @__PURE__ */ jsx9(
7617
+ TerminalView,
7618
+ {
7619
+ apiUrl: terminal.apiUrl,
7620
+ token: terminal.token,
7621
+ connectionId: terminal.connectionId,
7622
+ title: terminal.title,
7623
+ subtitle: terminal.subtitle
7624
+ }
7625
+ ) })
7626
+ ]
7627
+ }
7628
+ )
7534
7629
  ]
7535
7630
  }
7536
7631
  );
@@ -7540,6 +7635,7 @@ function SandboxArtifactPane({
7540
7635
  import * as React10 from "react";
7541
7636
  import { GitBranch, RefreshCw } from "lucide-react";
7542
7637
  import { Button, Skeleton, Textarea } from "@tangle-network/ui/primitives";
7638
+ import { focusRing as focusRing3, focusRingInset as focusRingInset2 } from "@tangle-network/ui/utils";
7543
7639
  import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
7544
7640
  var BORDER = "border-[var(--md3-outline-variant)]";
7545
7641
  function splitPath(path) {
@@ -7710,7 +7806,7 @@ function ChangesPane({
7710
7806
  type: "button",
7711
7807
  onClick: onRefresh,
7712
7808
  "aria-label": "Refresh",
7713
- className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60",
7809
+ className: `rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground ${focusRing3}`,
7714
7810
  children: /* @__PURE__ */ jsx10(RefreshCw, { className: cn("h-3.5 w-3.5", loading && "animate-spin") })
7715
7811
  }
7716
7812
  )
@@ -7751,7 +7847,8 @@ function ChangesPane({
7751
7847
  tabIndex: tabbable ? 0 : -1,
7752
7848
  onClick: () => onSelectFile?.(file.path),
7753
7849
  className: cn(
7754
- "flex cursor-pointer items-center gap-2 px-3 py-1.5 text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary/60",
7850
+ "flex cursor-pointer items-center gap-2 px-3 py-1.5 text-xs transition-colors",
7851
+ focusRingInset2,
7755
7852
  isSelected ? "bg-surface-container-high" : "hover:bg-surface-container"
7756
7853
  ),
7757
7854
  children: [
@@ -1,5 +1,6 @@
1
1
  // src/files/index.ts
2
2
  import {
3
+ CSV_PREVIEW_ROW_LIMIT,
3
4
  FileArtifactPane,
4
5
  FilePreview,
5
6
  FileTabs,
@@ -10,10 +11,13 @@ import {
10
11
  filterFileTree,
11
12
  getCodeLanguage,
12
13
  getFormatLabel,
13
- getSyntaxLanguage
14
+ getSyntaxLanguage,
15
+ parseCsv,
16
+ resolveFilePreviewKind
14
17
  } from "@tangle-network/ui/files";
15
18
 
16
19
  export {
20
+ CSV_PREVIEW_ROW_LIMIT,
17
21
  FileArtifactPane,
18
22
  FilePreview,
19
23
  FileTabs,
@@ -24,5 +28,7 @@ export {
24
28
  filterFileTree,
25
29
  getCodeLanguage,
26
30
  getFormatLabel,
27
- getSyntaxLanguage
31
+ getSyntaxLanguage,
32
+ parseCsv,
33
+ resolveFilePreviewKind
28
34
  };
@@ -3,6 +3,11 @@ import {
3
3
  TOOL_CATEGORY_ICONS,
4
4
  cn,
5
5
  copyText,
6
+ focusField,
7
+ focusFieldInvalid,
8
+ focusFieldWithin,
9
+ focusRing,
10
+ focusRingInset,
6
11
  formatBytes,
7
12
  formatDuration,
8
13
  formatUptime,
@@ -17,6 +22,11 @@ export {
17
22
  TOOL_CATEGORY_ICONS,
18
23
  cn,
19
24
  copyText,
25
+ focusField,
26
+ focusFieldInvalid,
27
+ focusFieldWithin,
28
+ focusRing,
29
+ focusRingInset,
20
30
  formatBytes,
21
31
  formatDuration,
22
32
  formatUptime,
@@ -428,6 +428,7 @@ function ConnectorActionList({
428
428
  import { Button as Button2, EmptyState, Input as Input2 } from "@tangle-network/ui/primitives";
429
429
  import { Blocks, ChevronRight as ChevronRight2, Search as Search2 } from "lucide-react";
430
430
  import { useEffect as useEffect2, useMemo as useMemo2, useState as useState3 } from "react";
431
+ import { focusField } from "@tangle-network/ui/utils";
431
432
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
432
433
  function optionsFor(entries, pick) {
433
434
  const map = /* @__PURE__ */ new Map();
@@ -469,7 +470,7 @@ function FilterSelect({
469
470
  value,
470
471
  onChange: (e) => onChange(e.target.value),
471
472
  "aria-label": ariaLabel,
472
- className: "rounded-lg border border-border bg-card px-3 py-2 text-foreground text-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
473
+ className: `rounded-lg border bg-card px-3 py-2 text-foreground text-sm ${focusField}`,
473
474
  children: [
474
475
  /* @__PURE__ */ jsx3("option", { value: "", children: allLabel }),
475
476
  options.map((option) => /* @__PURE__ */ jsx3("option", { value: option.value, children: option.label }, option.value))
@@ -51,7 +51,14 @@ interface RailHeaderProps {
51
51
  brand?: React.ReactNode;
52
52
  /** Destination for the brand link. When set with `LinkComponent`, the brand is a link. */
53
53
  brandHref?: string;
54
- /** Optional middle content (e.g. a project/workspace switcher). Hidden when collapsed. */
54
+ /**
55
+ * Optional middle content (e.g. a project/workspace switcher).
56
+ *
57
+ * When `brand` is set, this is expanded-only — the collapsed rail has room
58
+ * for the mark alone. When `brand` is NOT set this content owns the rail top
59
+ * in BOTH states, so it is also rendered collapsed; read `useSidebar()` to
60
+ * swap in a compact mark there.
61
+ */
55
62
  children?: React.ReactNode;
56
63
  /** Whether the rail is collapsed (icon-only). */
57
64
  collapsed: boolean;
@@ -66,13 +73,6 @@ interface RailHeaderProps {
66
73
  LinkComponent?: React.ComponentType<any>;
67
74
  className?: string;
68
75
  }
69
- /**
70
- * The redesigned rail header. Expanded: brand (left) · optional middle · panel
71
- * toggle (right). Collapsed: only the brand mark, which morphs into the
72
- * expand button on hover (brand fades out, panel glyph fades in over the same
73
- * box) so the collapse affordance is discoverable without spending rail width.
74
- * Renders its own `h-14` bordered bar — drop it in at the top of {@link SidebarRail}.
75
- */
76
76
  declare function RailHeader({ brand, brandHref, children, collapsed, onToggle, collapsible, LinkComponent, className }: RailHeaderProps): React.JSX.Element;
77
77
  interface SidebarProps {
78
78
  children: React.ReactNode;
package/dist/dashboard.js CHANGED
@@ -60,7 +60,7 @@ import {
60
60
  chatCapableHarnesses,
61
61
  parseInsufficientBalance,
62
62
  useSidebar
63
- } from "./chunk-LDAKIKTV.js";
63
+ } from "./chunk-DYXXYGS4.js";
64
64
  import {
65
65
  BillingDashboard,
66
66
  InfoPanel,
@@ -69,7 +69,7 @@ import {
69
69
  UsageChart,
70
70
  formatPrice
71
71
  } from "./chunk-OMUIPJQZ.js";
72
- import "./chunk-2XPJ2XF7.js";
72
+ import "./chunk-ANSC5AFC.js";
73
73
  import {
74
74
  ModelBrandStack,
75
75
  canonicalModelId,
package/dist/files.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { FileArtifactPane, FileArtifactPaneProps, FileFormat, FileNode, FilePreview, FilePreviewProps, FileTabData, FileTabs, FileTabsProps, FileTree, FileTreeProps, FileTreeVisibilityOptions, RichFileTree, RichFileTreeGitEntry, RichFileTreeGitStatus, RichFileTreeProps, RichFileTreeThemeVars, detectFileFormat, fileExtension, filterFileTree, getCodeLanguage, getFormatLabel, getSyntaxLanguage } from '@tangle-network/ui/files';
1
+ export { CSV_PREVIEW_ROW_LIMIT, FileArtifactPane, FileArtifactPaneProps, FileFormat, FileNode, FilePreview, FilePreviewKind, FilePreviewProps, FileTabData, FileTabs, FileTabsProps, FileTree, FileTreeProps, FileTreeVisibilityOptions, RichFileTree, RichFileTreeGitEntry, RichFileTreeGitStatus, RichFileTreeProps, RichFileTreeThemeVars, detectFileFormat, fileExtension, filterFileTree, getCodeLanguage, getFormatLabel, getSyntaxLanguage, parseCsv, resolveFilePreviewKind } from '@tangle-network/ui/files';
2
2
  import '@tangle-network/ui/openui';
3
3
  import '@tangle-network/ui/run';
4
4
  import '@tangle-network/ui/utils';
package/dist/files.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CSV_PREVIEW_ROW_LIMIT,
2
3
  FileArtifactPane,
3
4
  FilePreview,
4
5
  FileTabs,
@@ -9,10 +10,13 @@ import {
9
10
  filterFileTree,
10
11
  getCodeLanguage,
11
12
  getFormatLabel,
12
- getSyntaxLanguage
13
- } from "./chunk-PZVUIUUW.js";
13
+ getSyntaxLanguage,
14
+ parseCsv,
15
+ resolveFilePreviewKind
16
+ } from "./chunk-ULPLRBMF.js";
14
17
  import "./chunk-CZ7CSFO4.js";
15
18
  export {
19
+ CSV_PREVIEW_ROW_LIMIT,
16
20
  FileArtifactPane,
17
21
  FilePreview,
18
22
  FileTabs,
@@ -23,5 +27,7 @@ export {
23
27
  filterFileTree,
24
28
  getCodeLanguage,
25
29
  getFormatLabel,
26
- getSyntaxLanguage
30
+ getSyntaxLanguage,
31
+ parseCsv,
32
+ resolveFilePreviewKind
27
33
  };