@tangle-network/sandbox-ui 0.16.1 → 0.16.2

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.
@@ -666,8 +666,34 @@ function NewSandboxCard({ onClick, className }) {
666
666
  }
667
667
 
668
668
  // src/dashboard/sandbox-table.tsx
669
- import { Terminal as Terminal2, Code2 as Code22, Key, Trash2 as Trash22, RefreshCw, ChevronLeft, ChevronRight, Users as Users2, User, Play as Play2 } from "lucide-react";
670
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
669
+ import * as React3 from "react";
670
+ import {
671
+ Activity as Activity2,
672
+ BarChart2 as BarChart22,
673
+ ChevronLeft,
674
+ ChevronRight,
675
+ Clock as Clock2,
676
+ Code2 as Code22,
677
+ Copy as Copy2,
678
+ ExternalLink,
679
+ Key,
680
+ MoreVertical as MoreVertical2,
681
+ Play as Play2,
682
+ PowerOff as PowerOff2,
683
+ RefreshCw,
684
+ Terminal as Terminal2,
685
+ Trash2 as Trash22,
686
+ User,
687
+ Users as Users2
688
+ } from "lucide-react";
689
+ import {
690
+ DropdownMenu as DropdownMenu3,
691
+ DropdownMenuContent as DropdownMenuContent3,
692
+ DropdownMenuItem as DropdownMenuItem3,
693
+ DropdownMenuSeparator as DropdownMenuSeparator3,
694
+ DropdownMenuTrigger as DropdownMenuTrigger3
695
+ } from "@tangle-network/ui/primitives";
696
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
671
697
  var statusColors = {
672
698
  running: { dot: "bg-[var(--code-success)] animate-pulse", text: "text-[var(--code-success)]", bar: "bg-[var(--code-success)]" },
673
699
  hibernating: { dot: "bg-muted-foreground", text: "text-muted-foreground", bar: "bg-muted-foreground" },
@@ -705,6 +731,11 @@ function SandboxTable({
705
731
  onWake,
706
732
  onMore,
707
733
  onDelete,
734
+ onStop,
735
+ onKeepAlive,
736
+ onUsage,
737
+ onHealth,
738
+ onFork,
708
739
  className
709
740
  }) {
710
741
  const totalCount = total ?? sandboxes.length;
@@ -812,7 +843,7 @@ function SandboxTable({
812
843
  /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: 0 })
813
844
  ] }) : null }),
814
845
  /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 text-right", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1", children: [
815
- isActive && /* @__PURE__ */ jsxs6(Fragment4, { children: [
846
+ isActive && /* @__PURE__ */ jsxs6(Fragment5, { children: [
816
847
  /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
817
848
  stopRowClick(e);
818
849
  onOpenIDE?.(sb.id);
@@ -842,10 +873,80 @@ function SandboxTable({
842
873
  ]
843
874
  }
844
875
  ),
845
- onMore && /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
846
- stopRowClick(e);
847
- onMore(sb.id);
848
- }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
876
+ (() => {
877
+ const runItem = (handler) => (e) => {
878
+ e.stopPropagation();
879
+ handler(sb.id);
880
+ };
881
+ const overflowSections = [];
882
+ if (isActive) {
883
+ const lifecycle = [];
884
+ if (onStop) lifecycle.push(
885
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onStop), children: [
886
+ /* @__PURE__ */ jsx7(PowerOff2, { className: "mr-2 h-4 w-4" }),
887
+ " Stop Sandbox"
888
+ ] }, "stop")
889
+ );
890
+ if (onKeepAlive) lifecycle.push(
891
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onKeepAlive), children: [
892
+ /* @__PURE__ */ jsx7(Clock2, { className: "mr-2 h-4 w-4" }),
893
+ " Keep Alive"
894
+ ] }, "keep-alive")
895
+ );
896
+ if (lifecycle.length) overflowSections.push(lifecycle);
897
+ const observability = [];
898
+ if (onUsage) observability.push(
899
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onUsage), children: [
900
+ /* @__PURE__ */ jsx7(BarChart22, { className: "mr-2 h-4 w-4" }),
901
+ " View Usage"
902
+ ] }, "usage")
903
+ );
904
+ if (onHealth) observability.push(
905
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onHealth), children: [
906
+ /* @__PURE__ */ jsx7(Activity2, { className: "mr-2 h-4 w-4" }),
907
+ " Health Check"
908
+ ] }, "health")
909
+ );
910
+ if (observability.length) overflowSections.push(observability);
911
+ if (onFork) overflowSections.push([
912
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onFork), children: [
913
+ /* @__PURE__ */ jsx7(Copy2, { className: "mr-2 h-4 w-4" }),
914
+ " Fork Sandbox"
915
+ ] }, "fork")
916
+ ]);
917
+ } else if (isResumable(sb.status)) {
918
+ if (onFork) overflowSections.push([
919
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onFork), children: [
920
+ /* @__PURE__ */ jsx7(Copy2, { className: "mr-2 h-4 w-4" }),
921
+ " Fork Sandbox"
922
+ ] }, "fork")
923
+ ]);
924
+ }
925
+ if (onMore) overflowSections.push([
926
+ /* @__PURE__ */ jsxs6(DropdownMenuItem3, { onClick: runItem(onMore), children: [
927
+ /* @__PURE__ */ jsx7(ExternalLink, { className: "mr-2 h-4 w-4" }),
928
+ " View Details"
929
+ ] }, "view-details")
930
+ ]);
931
+ if (overflowSections.length === 0) return null;
932
+ return /* @__PURE__ */ jsxs6(DropdownMenu3, { children: [
933
+ /* @__PURE__ */ jsx7(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx7(
934
+ "button",
935
+ {
936
+ type: "button",
937
+ onClick: stopRowClick,
938
+ className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90 outline-none",
939
+ "aria-label": "More actions",
940
+ title: "More actions",
941
+ children: /* @__PURE__ */ jsx7(MoreVertical2, { className: "h-4 w-4" })
942
+ }
943
+ ) }),
944
+ /* @__PURE__ */ jsx7(DropdownMenuContent3, { align: "end", className: "min-w-[180px]", children: overflowSections.map((section, sectionIdx) => /* @__PURE__ */ jsxs6(React3.Fragment, { children: [
945
+ sectionIdx > 0 && /* @__PURE__ */ jsx7(DropdownMenuSeparator3, {}),
946
+ section
947
+ ] }, sectionIdx)) })
948
+ ] });
949
+ })(),
849
950
  onDelete && canAdminSandbox(sb) && /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
850
951
  stopRowClick(e);
851
952
  onDelete(sb.id);
@@ -1115,9 +1216,9 @@ function HarnessPicker({
1115
1216
  }
1116
1217
 
1117
1218
  // src/dashboard/dashboard-layout.tsx
1118
- import * as React3 from "react";
1219
+ import * as React4 from "react";
1119
1220
  import { Plus as Plus2, Bell } from "lucide-react";
1120
- import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1221
+ import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1121
1222
  function SettingsIconSmall({ className }) {
1122
1223
  return /* @__PURE__ */ jsxs10("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
1123
1224
  /* @__PURE__ */ jsx12("title", { children: "Settings" }),
@@ -1178,10 +1279,10 @@ function DashboardLayoutInner({
1178
1279
  notifications: notifData
1179
1280
  }) {
1180
1281
  const Link = LinkComponent;
1181
- const [mobileMenuOpen, setMobileMenuOpen] = React3.useState(false);
1182
- const [notificationsOpen, setNotificationsOpen] = React3.useState(false);
1183
- const notifRef = React3.useRef(null);
1184
- React3.useEffect(() => {
1282
+ const [mobileMenuOpen, setMobileMenuOpen] = React4.useState(false);
1283
+ const [notificationsOpen, setNotificationsOpen] = React4.useState(false);
1284
+ const notifRef = React4.useRef(null);
1285
+ React4.useEffect(() => {
1185
1286
  if (!notificationsOpen) return;
1186
1287
  const handler = (e) => {
1187
1288
  if (notifRef.current && !notifRef.current.contains(e.target)) {
@@ -1199,21 +1300,21 @@ function DashboardLayoutInner({
1199
1300
  };
1200
1301
  }, [notificationsOpen]);
1201
1302
  const { contentMargin, hidden, mode, hasPanels, panelOpen } = useSidebar();
1202
- const modeSet = React3.useMemo(() => new Set(modeItems), [modeItems]);
1203
- const sidebarUser = React3.useMemo(
1303
+ const modeSet = React4.useMemo(() => new Set(modeItems), [modeItems]);
1304
+ const sidebarUser = React4.useMemo(
1204
1305
  () => user ? { email: user.email, name: user.name, tier: user.tier, avatarUrl: user.avatarUrl } : void 0,
1205
1306
  [user?.email, user?.name, user?.tier, user?.avatarUrl]
1206
1307
  );
1207
1308
  const activePanel = panels.find((p) => p.mode === mode);
1208
- const buildSidebarContent = React3.useCallback(
1209
- (showLabels) => /* @__PURE__ */ jsxs10(Fragment6, { children: [
1309
+ const buildSidebarContent = React4.useCallback(
1310
+ (showLabels) => /* @__PURE__ */ jsxs10(Fragment7, { children: [
1210
1311
  /* @__PURE__ */ jsxs10(SidebarRail, { wide: showLabels, children: [
1211
1312
  /* @__PURE__ */ jsx12(SidebarRailHeader, { children: /* @__PURE__ */ jsx12(Link, { href: "/", to: "/", className: "p-1 rounded-md transition-colors hover:bg-muted/50", children: /* @__PURE__ */ jsx12(Logo, { variant, size: "sm", iconOnly: true }) }) }),
1212
1313
  /* @__PURE__ */ jsx12(SidebarRailNav, { children: navItems.map((item, i) => {
1213
1314
  const isMode = modeSet.has(item.id);
1214
1315
  const prevIsMode = i > 0 && modeSet.has(navItems[i - 1].id);
1215
1316
  const showSep = i > 0 && isMode && !prevIsMode;
1216
- return /* @__PURE__ */ jsxs10(React3.Fragment, { children: [
1317
+ return /* @__PURE__ */ jsxs10(React4.Fragment, { children: [
1217
1318
  showSep && /* @__PURE__ */ jsx12(RailSeparator, {}),
1218
1319
  isMode ? /* @__PURE__ */ jsx12(
1219
1320
  RailModeButton,
@@ -1278,8 +1379,8 @@ function DashboardLayoutInner({
1278
1379
  mode
1279
1380
  ]
1280
1381
  );
1281
- const sidebarContent = React3.useMemo(() => buildSidebarContent(false), [buildSidebarContent]);
1282
- const mobileSidebarContent = React3.useMemo(() => buildSidebarContent(true), [buildSidebarContent]);
1382
+ const sidebarContent = React4.useMemo(() => buildSidebarContent(false), [buildSidebarContent]);
1383
+ const mobileSidebarContent = React4.useMemo(() => buildSidebarContent(true), [buildSidebarContent]);
1283
1384
  return /* @__PURE__ */ jsxs10("div", { className: cn("min-h-screen bg-background text-foreground", className), children: [
1284
1385
  /* @__PURE__ */ jsxs10(
1285
1386
  "nav",
@@ -1417,14 +1518,14 @@ import { Check as Check2, ChevronDown as ChevronDown2, Plus as Plus3, Settings }
1417
1518
  import { Button } from "@tangle-network/ui/primitives";
1418
1519
  import { Badge } from "@tangle-network/ui/primitives";
1419
1520
  import {
1420
- DropdownMenu as DropdownMenu3,
1421
- DropdownMenuContent as DropdownMenuContent3,
1422
- DropdownMenuItem as DropdownMenuItem3,
1521
+ DropdownMenu as DropdownMenu4,
1522
+ DropdownMenuContent as DropdownMenuContent4,
1523
+ DropdownMenuItem as DropdownMenuItem4,
1423
1524
  DropdownMenuLabel as DropdownMenuLabel2,
1424
- DropdownMenuSeparator as DropdownMenuSeparator3,
1425
- DropdownMenuTrigger as DropdownMenuTrigger3
1525
+ DropdownMenuSeparator as DropdownMenuSeparator4,
1526
+ DropdownMenuTrigger as DropdownMenuTrigger4
1426
1527
  } from "@tangle-network/ui/primitives";
1427
- import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1528
+ import { Fragment as Fragment8, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1428
1529
  function ProfileSelector({
1429
1530
  profiles,
1430
1531
  selectedId,
@@ -1441,14 +1542,14 @@ function ProfileSelector({
1441
1542
  const customProfiles = profiles.filter((p) => !p.is_builtin);
1442
1543
  return /* @__PURE__ */ jsxs11("div", { className, children: [
1443
1544
  label && /* @__PURE__ */ jsx13("label", { className: "mb-2 block font-medium text-sm", children: label }),
1444
- /* @__PURE__ */ jsxs11(DropdownMenu3, { children: [
1445
- /* @__PURE__ */ jsx13(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsxs11(Button, { variant: "outline", className: "w-full justify-between", children: [
1545
+ /* @__PURE__ */ jsxs11(DropdownMenu4, { children: [
1546
+ /* @__PURE__ */ jsx13(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsxs11(Button, { variant: "outline", className: "w-full justify-between", children: [
1446
1547
  /* @__PURE__ */ jsx13("span", { className: "truncate", children: selected ? selected.name : placeholder }),
1447
1548
  /* @__PURE__ */ jsx13(ChevronDown2, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })
1448
1549
  ] }) }),
1449
- /* @__PURE__ */ jsxs11(DropdownMenuContent3, { className: "w-[300px]", align: "start", children: [
1550
+ /* @__PURE__ */ jsxs11(DropdownMenuContent4, { className: "w-[300px]", align: "start", children: [
1450
1551
  /* @__PURE__ */ jsxs11(
1451
- DropdownMenuItem3,
1552
+ DropdownMenuItem4,
1452
1553
  {
1453
1554
  onClick: () => onSelect(null),
1454
1555
  className: "flex items-center justify-between",
@@ -1458,11 +1559,11 @@ function ProfileSelector({
1458
1559
  ]
1459
1560
  }
1460
1561
  ),
1461
- builtinProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment7, { children: [
1462
- /* @__PURE__ */ jsx13(DropdownMenuSeparator3, {}),
1562
+ builtinProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment8, { children: [
1563
+ /* @__PURE__ */ jsx13(DropdownMenuSeparator4, {}),
1463
1564
  /* @__PURE__ */ jsx13(DropdownMenuLabel2, { children: "Built-in Profiles" }),
1464
1565
  builtinProfiles.map((profile) => /* @__PURE__ */ jsxs11(
1465
- DropdownMenuItem3,
1566
+ DropdownMenuItem4,
1466
1567
  {
1467
1568
  onClick: () => onSelect(profile),
1468
1569
  className: "flex flex-col items-start gap-1",
@@ -1483,11 +1584,11 @@ function ProfileSelector({
1483
1584
  profile.id
1484
1585
  ))
1485
1586
  ] }),
1486
- customProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment7, { children: [
1487
- /* @__PURE__ */ jsx13(DropdownMenuSeparator3, {}),
1587
+ customProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment8, { children: [
1588
+ /* @__PURE__ */ jsx13(DropdownMenuSeparator4, {}),
1488
1589
  /* @__PURE__ */ jsx13(DropdownMenuLabel2, { children: "Custom Profiles" }),
1489
1590
  customProfiles.map((profile) => /* @__PURE__ */ jsxs11(
1490
- DropdownMenuItem3,
1591
+ DropdownMenuItem4,
1491
1592
  {
1492
1593
  onClick: () => onSelect(profile),
1493
1594
  className: "flex flex-col items-start gap-1",
@@ -1520,10 +1621,10 @@ function ProfileSelector({
1520
1621
  profile.id
1521
1622
  ))
1522
1623
  ] }),
1523
- (onCreateClick || onManageClick) && /* @__PURE__ */ jsxs11(Fragment7, { children: [
1524
- /* @__PURE__ */ jsx13(DropdownMenuSeparator3, {}),
1624
+ (onCreateClick || onManageClick) && /* @__PURE__ */ jsxs11(Fragment8, { children: [
1625
+ /* @__PURE__ */ jsx13(DropdownMenuSeparator4, {}),
1525
1626
  onCreateClick && /* @__PURE__ */ jsxs11(
1526
- DropdownMenuItem3,
1627
+ DropdownMenuItem4,
1527
1628
  {
1528
1629
  onClick: onCreateClick,
1529
1630
  className: "text-[var(--surface-info-text)]",
@@ -1534,7 +1635,7 @@ function ProfileSelector({
1534
1635
  }
1535
1636
  ),
1536
1637
  onManageClick && /* @__PURE__ */ jsxs11(
1537
- DropdownMenuItem3,
1638
+ DropdownMenuItem4,
1538
1639
  {
1539
1640
  onClick: onManageClick,
1540
1641
  className: "text-muted-foreground",
@@ -1618,8 +1719,8 @@ function ProfileComparison({
1618
1719
  import {
1619
1720
  Check as Check3,
1620
1721
  CheckCircle2,
1621
- Clock as Clock2,
1622
- ExternalLink,
1722
+ Clock as Clock3,
1723
+ ExternalLink as ExternalLink2,
1623
1724
  Loader2,
1624
1725
  Timer,
1625
1726
  X,
@@ -1627,10 +1728,10 @@ import {
1627
1728
  } from "lucide-react";
1628
1729
  import { Button as Button2 } from "@tangle-network/ui/primitives";
1629
1730
  import { Badge as Badge2 } from "@tangle-network/ui/primitives";
1630
- import { Fragment as Fragment8, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1731
+ import { Fragment as Fragment9, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1631
1732
  var statusConfig = {
1632
1733
  pending: {
1633
- icon: Clock2,
1734
+ icon: Clock3,
1634
1735
  color: "text-[var(--surface-warning-text)]",
1635
1736
  bg: "bg-[var(--surface-warning-bg)]",
1636
1737
  border: "border-[var(--surface-warning-border)]",
@@ -1750,7 +1851,7 @@ function VariantList({
1750
1851
  children: outcomeConfig[variant.outcome].label
1751
1852
  }
1752
1853
  ),
1753
- variant.status === "completed" && variant.outcome === "pending_review" && onAccept && onReject && /* @__PURE__ */ jsxs12(Fragment8, { children: [
1854
+ variant.status === "completed" && variant.outcome === "pending_review" && onAccept && onReject && /* @__PURE__ */ jsxs12(Fragment9, { children: [
1754
1855
  /* @__PURE__ */ jsxs12(
1755
1856
  Button2,
1756
1857
  {
@@ -1796,7 +1897,7 @@ function VariantList({
1796
1897
  e.stopPropagation();
1797
1898
  window.open(variant.detailsUrl, "_blank");
1798
1899
  },
1799
- children: /* @__PURE__ */ jsx14(ExternalLink, { className: "h-3.5 w-3.5" })
1900
+ children: /* @__PURE__ */ jsx14(ExternalLink2, { className: "h-3.5 w-3.5" })
1800
1901
  }
1801
1902
  )
1802
1903
  ] })
@@ -1922,7 +2023,7 @@ function SystemLogsViewer({ apiUrl, token, className }) {
1922
2023
  }
1923
2024
 
1924
2025
  // src/dashboard/usage-summary.tsx
1925
- import { Clock as Clock3, Layers, MessageSquare, DollarSign } from "lucide-react";
2026
+ import { Clock as Clock4, Layers, MessageSquare, DollarSign } from "lucide-react";
1926
2027
  import { StatCard } from "@tangle-network/ui/primitives";
1927
2028
  import { Skeleton as Skeleton2 } from "@tangle-network/ui/primitives";
1928
2029
  import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
@@ -1938,7 +2039,7 @@ function UsageSummary({ data, loading = false, className }) {
1938
2039
  title: "Compute Hours",
1939
2040
  value: data.computeHours.toFixed(1),
1940
2041
  subtitle: "This billing period",
1941
- icon: /* @__PURE__ */ jsx16(Clock3, { className: "h-5 w-5" })
2042
+ icon: /* @__PURE__ */ jsx16(Clock4, { className: "h-5 w-5" })
1942
2043
  }
1943
2044
  ),
1944
2045
  /* @__PURE__ */ jsx16(
@@ -2045,14 +2146,14 @@ function GitPanel({ status, log, loading = false, onRefresh, className }) {
2045
2146
  }
2046
2147
 
2047
2148
  // src/dashboard/ports-list.tsx
2048
- import * as React5 from "react";
2049
- import { Copy as Copy2, Check as Check4, Globe, Plus as Plus4, Trash2 as Trash23 } from "lucide-react";
2149
+ import * as React6 from "react";
2150
+ import { Copy as Copy3, Check as Check4, Globe, Plus as Plus4, Trash2 as Trash23 } from "lucide-react";
2050
2151
  import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2051
2152
  function PortsList({ ports, onExposePort, onRemovePort, isExposing = false, className }) {
2052
- const [newPort, setNewPort] = React5.useState("");
2053
- const [copiedPort, setCopiedPort] = React5.useState(null);
2054
- const copyTimerRef = React5.useRef(null);
2055
- React5.useEffect(() => {
2153
+ const [newPort, setNewPort] = React6.useState("");
2154
+ const [copiedPort, setCopiedPort] = React6.useState(null);
2155
+ const copyTimerRef = React6.useRef(null);
2156
+ React6.useEffect(() => {
2056
2157
  return () => {
2057
2158
  if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
2058
2159
  };
@@ -2092,7 +2193,7 @@ function PortsList({ ports, onExposePort, onRemovePort, isExposing = false, clas
2092
2193
  className: "flex items-center gap-2 font-mono text-xs text-primary hover:underline cursor-pointer group",
2093
2194
  children: [
2094
2195
  /* @__PURE__ */ jsx18("span", { className: "truncate max-w-[300px]", children: p.url }),
2095
- copiedPort === p.port ? /* @__PURE__ */ jsx18(Check4, { className: "h-3 w-3 text-[var(--surface-success-text)] shrink-0" }) : /* @__PURE__ */ jsx18(Copy2, { className: "h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" })
2196
+ copiedPort === p.port ? /* @__PURE__ */ jsx18(Check4, { className: "h-3 w-3 text-[var(--surface-success-text)] shrink-0" }) : /* @__PURE__ */ jsx18(Copy3, { className: "h-3 w-3 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" })
2096
2197
  ]
2097
2198
  }
2098
2199
  ) }),
@@ -2146,8 +2247,8 @@ function PortsList({ ports, onExposePort, onRemovePort, isExposing = false, clas
2146
2247
  }
2147
2248
 
2148
2249
  // src/dashboard/process-list.tsx
2149
- import * as React6 from "react";
2150
- import { Activity as Activity2, Plus as Plus5, Skull, Terminal as Terminal4 } from "lucide-react";
2250
+ import * as React7 from "react";
2251
+ import { Activity as Activity3, Plus as Plus5, Skull, Terminal as Terminal4 } from "lucide-react";
2151
2252
  import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2152
2253
  function formatUptime(startedAt) {
2153
2254
  if (!startedAt) return "-";
@@ -2158,7 +2259,7 @@ function formatUptime(startedAt) {
2158
2259
  return `${Math.floor(ms / 36e5)}h ${Math.floor(ms % 36e5 / 6e4)}m`;
2159
2260
  }
2160
2261
  function ProcessList({ processes, onSpawn, onKill, loading = false, className }) {
2161
- const [newCommand, setNewCommand] = React6.useState("");
2262
+ const [newCommand, setNewCommand] = React7.useState("");
2162
2263
  const handleSpawn = () => {
2163
2264
  const cmd = newCommand.trim();
2164
2265
  if (cmd) {
@@ -2168,7 +2269,7 @@ function ProcessList({ processes, onSpawn, onKill, loading = false, className })
2168
2269
  };
2169
2270
  return /* @__PURE__ */ jsxs17("div", { className: cn("space-y-4", className), children: [
2170
2271
  loading ? /* @__PURE__ */ jsxs17("div", { className: "rounded-lg border border-border bg-muted/20 p-6 text-center", children: [
2171
- /* @__PURE__ */ jsx19(Activity2, { className: "mx-auto h-6 w-6 text-muted-foreground animate-spin mb-2" }),
2272
+ /* @__PURE__ */ jsx19(Activity3, { className: "mx-auto h-6 w-6 text-muted-foreground animate-spin mb-2" }),
2172
2273
  /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Loading processes..." })
2173
2274
  ] }) : processes.length > 0 ? /* @__PURE__ */ jsx19("div", { className: "rounded-lg border border-border overflow-hidden", children: /* @__PURE__ */ jsxs17("table", { className: "w-full text-sm", children: [
2174
2275
  /* @__PURE__ */ jsx19("thead", { className: "bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsxs17("tr", { children: [
@@ -2231,11 +2332,11 @@ function ProcessList({ processes, onSpawn, onKill, loading = false, className })
2231
2332
  }
2232
2333
 
2233
2334
  // src/dashboard/network-config.tsx
2234
- import * as React7 from "react";
2335
+ import * as React8 from "react";
2235
2336
  import { Network as Network2, Plus as Plus6, Trash2 as Trash24, ShieldAlert } from "lucide-react";
2236
2337
  import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2237
2338
  function NetworkConfig({ config, onUpdate, loading = false, className }) {
2238
- const [newCidr, setNewCidr] = React7.useState("");
2339
+ const [newCidr, setNewCidr] = React8.useState("");
2239
2340
  const isValidCidr = (value) => {
2240
2341
  const match = value.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/);
2241
2342
  if (!match) return false;
@@ -2339,9 +2440,9 @@ function NetworkConfig({ config, onUpdate, loading = false, className }) {
2339
2440
  }
2340
2441
 
2341
2442
  // src/dashboard/backend-config.tsx
2342
- import * as React8 from "react";
2443
+ import * as React9 from "react";
2343
2444
  import { Bot, Plus as Plus7, RefreshCw as RefreshCw2, Trash2 as Trash25, Server, Wrench } from "lucide-react";
2344
- import { Fragment as Fragment9, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
2445
+ import { Fragment as Fragment10, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
2345
2446
  function BackendConfig({
2346
2447
  status,
2347
2448
  mcpServers,
@@ -2351,10 +2452,10 @@ function BackendConfig({
2351
2452
  loading = false,
2352
2453
  className
2353
2454
  }) {
2354
- const [showAddMcp, setShowAddMcp] = React8.useState(false);
2355
- const [mcpName, setMcpName] = React8.useState("");
2356
- const [mcpCommand, setMcpCommand] = React8.useState("");
2357
- const [mcpArgs, setMcpArgs] = React8.useState("");
2455
+ const [showAddMcp, setShowAddMcp] = React9.useState(false);
2456
+ const [mcpName, setMcpName] = React9.useState("");
2457
+ const [mcpCommand, setMcpCommand] = React9.useState("");
2458
+ const [mcpArgs, setMcpArgs] = React9.useState("");
2358
2459
  const handleAddMcp = () => {
2359
2460
  const name = mcpName.trim();
2360
2461
  const command = mcpCommand.trim();
@@ -2401,7 +2502,7 @@ function BackendConfig({
2401
2502
  ), children: status.running ? "Running" : "Stopped" }) }),
2402
2503
  /* @__PURE__ */ jsx21("dt", { className: "text-muted-foreground", children: "Model" }),
2403
2504
  /* @__PURE__ */ jsx21("dd", { className: "font-mono text-xs", children: status.model ?? "Default" }),
2404
- status.provider && /* @__PURE__ */ jsxs19(Fragment9, { children: [
2505
+ status.provider && /* @__PURE__ */ jsxs19(Fragment10, { children: [
2405
2506
  /* @__PURE__ */ jsx21("dt", { className: "text-muted-foreground", children: "Provider" }),
2406
2507
  /* @__PURE__ */ jsx21("dd", { className: "font-mono text-xs", children: status.provider })
2407
2508
  ] })
@@ -2512,8 +2613,8 @@ function BackendConfig({
2512
2613
  }
2513
2614
 
2514
2615
  // src/dashboard/snapshot-list.tsx
2515
- import * as React9 from "react";
2516
- import { Camera, Clock as Clock4, HardDrive, Plus as Plus8, RotateCcw } from "lucide-react";
2616
+ import * as React10 from "react";
2617
+ import { Camera, Clock as Clock5, HardDrive, Plus as Plus8, RotateCcw } from "lucide-react";
2517
2618
  import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2518
2619
  function formatBytes(bytes) {
2519
2620
  if (bytes == null || bytes < 0) return "-";
@@ -2529,8 +2630,8 @@ function formatDate(dateStr) {
2529
2630
  return d.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
2530
2631
  }
2531
2632
  function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loading = false, className }) {
2532
- const [showCreate, setShowCreate] = React9.useState(false);
2533
- const [tags, setTags] = React9.useState("");
2633
+ const [showCreate, setShowCreate] = React10.useState(false);
2634
+ const [tags, setTags] = React10.useState("");
2534
2635
  const handleCreate = () => {
2535
2636
  const tagList = tags.trim() ? tags.trim().split(",").map((t) => t.trim()).filter(Boolean) : void 0;
2536
2637
  onCreate(tagList);
@@ -2604,7 +2705,7 @@ function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loadin
2604
2705
  /* @__PURE__ */ jsx22("tbody", { className: "divide-y divide-border", children: snapshots.map((s) => /* @__PURE__ */ jsxs20("tr", { children: [
2605
2706
  /* @__PURE__ */ jsx22("td", { className: "px-4 py-3 font-mono text-xs text-foreground", children: s.id.slice(0, 12) }),
2606
2707
  /* @__PURE__ */ jsx22("td", { className: "px-4 py-3 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs20("span", { className: "inline-flex items-center gap-1.5", children: [
2607
- /* @__PURE__ */ jsx22(Clock4, { className: "h-3 w-3" }),
2708
+ /* @__PURE__ */ jsx22(Clock5, { className: "h-3 w-3" }),
2608
2709
  formatDate(s.createdAt)
2609
2710
  ] }) }),
2610
2711
  /* @__PURE__ */ jsx22("td", { className: "px-4 py-3 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs20("span", { className: "inline-flex items-center gap-1.5", children: [
@@ -2647,7 +2748,7 @@ function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loadin
2647
2748
  }
2648
2749
 
2649
2750
  // src/dashboard/promo-banner.tsx
2650
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
2751
+ import { Fragment as Fragment11, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
2651
2752
  function PromoBanner({
2652
2753
  title,
2653
2754
  description,
@@ -2662,7 +2763,7 @@ function PromoBanner({
2662
2763
  "mt-6 inline-flex items-center gap-2 rounded-md border border-white/20 bg-[var(--btn-primary-bg)] px-4 py-2 text-sm font-medium text-[var(--btn-primary-text)] transition-colors",
2663
2764
  disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-[var(--btn-primary-hover)]"
2664
2765
  );
2665
- const buttonContent = /* @__PURE__ */ jsxs21(Fragment10, { children: [
2766
+ const buttonContent = /* @__PURE__ */ jsxs21(Fragment11, { children: [
2666
2767
  buttonLabel,
2667
2768
  /* @__PURE__ */ jsxs21("svg", { "aria-hidden": "true", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-4 w-4", children: [
2668
2769
  /* @__PURE__ */ jsx23("path", { d: "M5 12h14" }),
@@ -249,10 +249,16 @@ interface SandboxTableProps {
249
249
  */
250
250
  onWake?: (id: string) => void;
251
251
  onMore?: (id: string) => void;
252
+ /** Fired on the user's first click; the caller owns the confirmation step. */
252
253
  onDelete?: (id: string) => void;
254
+ onStop?: (id: string) => void;
255
+ onKeepAlive?: (id: string) => void;
256
+ onUsage?: (id: string) => void;
257
+ onHealth?: (id: string) => void;
258
+ onFork?: (id: string) => void;
253
259
  className?: string;
254
260
  }
255
- declare function SandboxTable({ sandboxes, page, pageSize, total, onPageChange, onOpenIDE, onOpenTerminal, onSSH, onResume, onWake, onMore, onDelete, className, }: SandboxTableProps): react_jsx_runtime.JSX.Element;
261
+ declare function SandboxTable({ sandboxes, page, pageSize, total, onPageChange, onOpenIDE, onOpenTerminal, onSSH, onResume, onWake, onMore, onDelete, onStop, onKeepAlive, onUsage, onHealth, onFork, className, }: SandboxTableProps): react_jsx_runtime.JSX.Element;
256
262
 
257
263
  interface Invoice {
258
264
  id: string;
package/dist/dashboard.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  VariantList,
44
44
  canAdminSandbox,
45
45
  useSidebar
46
- } from "./chunk-X3UATIZH.js";
46
+ } from "./chunk-5I363RL7.js";
47
47
  import {
48
48
  BillingDashboard,
49
49
  InfoPanel,
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ import {
174
174
  VariantList,
175
175
  canAdminSandbox,
176
176
  useSidebar
177
- } from "./chunk-X3UATIZH.js";
177
+ } from "./chunk-5I363RL7.js";
178
178
  import {
179
179
  BillingDashboard,
180
180
  InfoPanel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
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",