@tangle-network/sandbox-ui 0.16.0 → 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 } 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" },
@@ -677,6 +703,9 @@ var statusColors = {
677
703
  failed: { dot: "bg-[var(--code-error)]", text: "text-[var(--code-error)]", bar: "bg-[var(--code-error)]" },
678
704
  archived: { dot: "bg-border", text: "text-muted-foreground", bar: "bg-border" }
679
705
  };
706
+ function isResumable(status) {
707
+ return status !== "running" && status !== "provisioning" && status !== "creating";
708
+ }
680
709
  function MiniMeter({ label, percent, className }) {
681
710
  return /* @__PURE__ */ jsxs6("div", { className: cn("space-y-1", className), children: [
682
711
  /* @__PURE__ */ jsxs6("div", { className: "flex justify-between text-[10px] font-mono text-muted-foreground", children: [
@@ -698,14 +727,33 @@ function SandboxTable({
698
727
  onOpenIDE,
699
728
  onOpenTerminal,
700
729
  onSSH,
730
+ onResume,
701
731
  onWake,
702
732
  onMore,
703
733
  onDelete,
734
+ onStop,
735
+ onKeepAlive,
736
+ onUsage,
737
+ onHealth,
738
+ onFork,
704
739
  className
705
740
  }) {
706
741
  const totalCount = total ?? sandboxes.length;
707
742
  const totalPages = Math.ceil(totalCount / pageSize);
708
743
  const hasTeamSandboxes = sandboxes.some((sb) => sb.team !== void 0);
744
+ const resolveResumeHandler = (status) => {
745
+ if (onResume) return onResume;
746
+ if (status === "hibernating") return onWake;
747
+ return void 0;
748
+ };
749
+ const resolveRowClick = (sb) => {
750
+ if (sb.status === "running") {
751
+ return onOpenIDE ? () => onOpenIDE(sb.id) : void 0;
752
+ }
753
+ if (!isResumable(sb.status)) return void 0;
754
+ const handler = resolveResumeHandler(sb.status);
755
+ return handler ? () => handler(sb.id) : void 0;
756
+ };
709
757
  return /* @__PURE__ */ jsxs6("div", { className: cn("w-full", className), children: [
710
758
  /* @__PURE__ */ jsx7("div", { className: "w-full bg-card rounded-2xl overflow-hidden border border-border", children: /* @__PURE__ */ jsx7("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs6("table", { className: "w-full text-left border-collapse", children: [
711
759
  /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs6("tr", { className: "bg-background border-b border-border", children: [
@@ -721,65 +769,194 @@ function SandboxTable({
721
769
  const isActive = sb.status === "running";
722
770
  const isHibernating = sb.status === "hibernating";
723
771
  const isProvisioning = sb.status === "provisioning";
724
- return /* @__PURE__ */ jsxs6("tr", { className: "hover:bg-muted/50 transition-colors group relative", children: [
725
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 whitespace-nowrap", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
726
- /* @__PURE__ */ jsx7("span", { className: cn("flex h-2.5 w-2.5 rounded-full", sc.dot) }),
727
- /* @__PURE__ */ jsx7("span", { className: cn("text-xs font-bold uppercase tracking-wide", sc.text), children: sb.status.charAt(0).toUpperCase() + sb.status.slice(1) })
728
- ] }) }),
729
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
730
- /* @__PURE__ */ jsx7("span", { className: "text-sm font-bold text-foreground group-hover:text-primary transition-colors", children: sb.name }),
731
- sb.nodeId && /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-mono text-muted-foreground", children: sb.nodeId })
732
- ] }) }),
733
- hasTeamSandboxes && /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: sb.team ? /* @__PURE__ */ jsxs6(
734
- "div",
735
- {
736
- className: "inline-flex items-center gap-1.5 rounded-full bg-[var(--accent-surface-soft)] px-2.5 py-1 text-[11px] font-semibold text-[var(--accent-text)]",
737
- title: `Shared with ${sb.team.name ?? "Team"} \xB7 ${sb.team.role}`,
738
- children: [
739
- /* @__PURE__ */ jsx7(Users2, { className: "h-3 w-3", "aria-hidden": "true" }),
740
- /* @__PURE__ */ jsx7("span", { children: sb.team.name ?? "Team" }),
741
- /* @__PURE__ */ jsxs6("span", { className: "font-normal text-muted-foreground", children: [
742
- "\xB7 ",
743
- sb.team.role
744
- ] })
745
- ]
746
- }
747
- ) : /* @__PURE__ */ jsxs6(
748
- "div",
772
+ const resumeHandler = isResumable(sb.status) ? resolveResumeHandler(sb.status) : void 0;
773
+ const onRowClick = resolveRowClick(sb);
774
+ const resumeLabel = isHibernating ? "Wake Up" : "Resume";
775
+ const stopRowClick = (e) => e.stopPropagation();
776
+ return (
777
+ // onClick is a sighted-user convenience only. We
778
+ // deliberately do NOT add role="button" / tabIndex /
779
+ // an onKeyDown handler to the row overriding a
780
+ // <tr>'s implicit row role with "button" collapses
781
+ // the per-cell announcements (Status, Environment,
782
+ // Resources…) that screen-reader users navigate
783
+ // through. Keyboard and assistive-tech users reach
784
+ // the same actions through the real <button>
785
+ // elements inside the actions cell (Resume, Open
786
+ // IDE, Delete, …), which keep their native
787
+ // semantics. Mouse users get the click-anywhere
788
+ // affordance; nobody loses access.
789
+ /* @__PURE__ */ jsxs6(
790
+ "tr",
749
791
  {
750
- className: "inline-flex items-center gap-1.5 rounded-full bg-muted px-2.5 py-1 text-[11px] font-medium text-muted-foreground",
751
- title: "Personal sandbox",
792
+ className: cn(
793
+ "group relative transition-colors",
794
+ onRowClick ? "cursor-pointer hover:bg-muted/50" : "hover:bg-muted/30"
795
+ ),
796
+ onClick: onRowClick,
752
797
  children: [
753
- /* @__PURE__ */ jsx7(User, { className: "h-3 w-3", "aria-hidden": "true" }),
754
- "Personal"
798
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 whitespace-nowrap", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
799
+ /* @__PURE__ */ jsx7("span", { className: cn("flex h-2.5 w-2.5 rounded-full", sc.dot) }),
800
+ /* @__PURE__ */ jsx7("span", { className: cn("text-xs font-bold uppercase tracking-wide", sc.text), children: sb.status.charAt(0).toUpperCase() + sb.status.slice(1) })
801
+ ] }) }),
802
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
803
+ /* @__PURE__ */ jsx7("span", { className: "text-sm font-bold text-foreground group-hover:text-primary transition-colors", children: sb.name }),
804
+ sb.nodeId && /* @__PURE__ */ jsx7("span", { className: "text-[10px] font-mono text-muted-foreground", children: sb.nodeId })
805
+ ] }) }),
806
+ hasTeamSandboxes && /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: sb.team ? /* @__PURE__ */ jsxs6(
807
+ "div",
808
+ {
809
+ className: "inline-flex items-center gap-1.5 rounded-full bg-[var(--accent-surface-soft)] px-2.5 py-1 text-[11px] font-semibold text-[var(--accent-text)]",
810
+ title: `Shared with ${sb.team.name ?? "Team"} \xB7 ${sb.team.role}`,
811
+ children: [
812
+ /* @__PURE__ */ jsx7(Users2, { className: "h-3 w-3", "aria-hidden": "true" }),
813
+ /* @__PURE__ */ jsx7("span", { children: sb.team.name ?? "Team" }),
814
+ /* @__PURE__ */ jsxs6("span", { className: "font-normal text-muted-foreground", children: [
815
+ "\xB7 ",
816
+ sb.team.role
817
+ ] })
818
+ ]
819
+ }
820
+ ) : /* @__PURE__ */ jsxs6(
821
+ "div",
822
+ {
823
+ className: "inline-flex items-center gap-1.5 rounded-full bg-muted px-2.5 py-1 text-[11px] font-medium text-muted-foreground",
824
+ title: "Personal sandbox",
825
+ children: [
826
+ /* @__PURE__ */ jsx7(User, { className: "h-3 w-3", "aria-hidden": "true" }),
827
+ "Personal"
828
+ ]
829
+ }
830
+ ) }),
831
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
832
+ sb.imageIcon && /* @__PURE__ */ jsx7("div", { className: "w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center", children: sb.imageIcon }),
833
+ sb.image && /* @__PURE__ */ jsx7("span", { className: "text-xs font-bold text-foreground", children: sb.image })
834
+ ] }) }),
835
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: isActive ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48", children: [
836
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: sb.cpuPercent ?? 0 }),
837
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: sb.ramTotal ? Math.round((sb.ramUsed ?? 0) / sb.ramTotal * 100) : 0 })
838
+ ] }) : isProvisioning ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-primary italic text-[10px] font-bold", children: [
839
+ /* @__PURE__ */ jsx7(RefreshCw, { className: "h-3.5 w-3.5 animate-spin" }),
840
+ sb.provisioningMessage ?? "Allocating nodes..."
841
+ ] }) : isHibernating ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48 opacity-30", children: [
842
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: 0 }),
843
+ /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: 0 })
844
+ ] }) : null }),
845
+ /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 text-right", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1", children: [
846
+ isActive && /* @__PURE__ */ jsxs6(Fragment5, { children: [
847
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
848
+ stopRowClick(e);
849
+ onOpenIDE?.(sb.id);
850
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Open IDE", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
851
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
852
+ stopRowClick(e);
853
+ onOpenTerminal?.(sb.id);
854
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Terminal", children: /* @__PURE__ */ jsx7(Terminal2, { className: "h-4 w-4" }) }),
855
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
856
+ stopRowClick(e);
857
+ onSSH?.(sb.id);
858
+ }, className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "SSH", children: /* @__PURE__ */ jsx7(Key, { className: "h-4 w-4" }) })
859
+ ] }),
860
+ resumeHandler && /* @__PURE__ */ jsxs6(
861
+ "button",
862
+ {
863
+ type: "button",
864
+ onClick: (e) => {
865
+ stopRowClick(e);
866
+ resumeHandler(sb.id);
867
+ },
868
+ className: "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-border text-primary text-[10px] font-bold uppercase tracking-wider hover:bg-[var(--accent-surface-soft)] active:scale-95 transition-all",
869
+ title: resumeLabel,
870
+ children: [
871
+ /* @__PURE__ */ jsx7(Play2, { className: "h-3 w-3" }),
872
+ resumeLabel
873
+ ]
874
+ }
875
+ ),
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
+ })(),
950
+ onDelete && canAdminSandbox(sb) && /* @__PURE__ */ jsx7("button", { type: "button", onClick: (e) => {
951
+ stopRowClick(e);
952
+ onDelete(sb.id);
953
+ }, className: "p-2 rounded-lg hover:bg-[var(--surface-danger-bg)] text-muted-foreground hover:text-[var(--surface-danger-text)] transition-all active:scale-90", title: "Delete", children: /* @__PURE__ */ jsx7(Trash22, { className: "h-4 w-4" }) })
954
+ ] }) })
755
955
  ]
756
- }
757
- ) }),
758
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
759
- sb.imageIcon && /* @__PURE__ */ jsx7("div", { className: "w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center", children: sb.imageIcon }),
760
- sb.image && /* @__PURE__ */ jsx7("span", { className: "text-xs font-bold text-foreground", children: sb.image })
761
- ] }) }),
762
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5", children: isActive ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48", children: [
763
- /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: sb.cpuPercent ?? 0 }),
764
- /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: sb.ramTotal ? Math.round((sb.ramUsed ?? 0) / sb.ramTotal * 100) : 0 })
765
- ] }) : isProvisioning ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-primary italic text-[10px] font-bold", children: [
766
- /* @__PURE__ */ jsx7(RefreshCw, { className: "h-3.5 w-3.5 animate-spin" }),
767
- sb.provisioningMessage ?? "Allocating nodes..."
768
- ] }) : isHibernating ? /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-48 opacity-30", children: [
769
- /* @__PURE__ */ jsx7(MiniMeter, { label: "CPU", percent: 0 }),
770
- /* @__PURE__ */ jsx7(MiniMeter, { label: "RAM", percent: 0 })
771
- ] }) : null }),
772
- /* @__PURE__ */ jsx7("td", { className: "px-6 py-5 text-right", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-1", children: [
773
- isActive && /* @__PURE__ */ jsxs6(Fragment4, { children: [
774
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onOpenIDE?.(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Open IDE", children: /* @__PURE__ */ jsx7(Code22, { className: "h-4 w-4" }) }),
775
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onOpenTerminal?.(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "Terminal", children: /* @__PURE__ */ jsx7(Terminal2, { className: "h-4 w-4" }) }),
776
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onSSH?.(sb.id), className: "p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-all active:scale-90", title: "SSH", children: /* @__PURE__ */ jsx7(Key, { className: "h-4 w-4" }) })
777
- ] }),
778
- isHibernating && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onWake?.(sb.id), className: "px-3 py-1.5 rounded-lg border border-border text-primary text-[10px] font-bold uppercase tracking-wider hover:bg-[var(--accent-surface-soft)] active:scale-95 transition-all", children: "Wake Up" }),
779
- onMore && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onMore(sb.id), 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" }) }),
780
- onDelete && canAdminSandbox(sb) && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => onDelete(sb.id), className: "p-2 rounded-lg hover:bg-[var(--surface-danger-bg)] text-muted-foreground hover:text-[var(--surface-danger-text)] transition-all active:scale-90", title: "Delete", children: /* @__PURE__ */ jsx7(Trash22, { className: "h-4 w-4" }) })
781
- ] }) })
782
- ] }, sb.id);
956
+ },
957
+ sb.id
958
+ )
959
+ );
783
960
  }) })
784
961
  ] }) }) }),
785
962
  totalPages > 1 && /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex flex-col md:flex-row justify-between items-center text-muted-foreground text-xs font-medium gap-4", children: [
@@ -1039,9 +1216,9 @@ function HarnessPicker({
1039
1216
  }
1040
1217
 
1041
1218
  // src/dashboard/dashboard-layout.tsx
1042
- import * as React3 from "react";
1219
+ import * as React4 from "react";
1043
1220
  import { Plus as Plus2, Bell } from "lucide-react";
1044
- 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";
1045
1222
  function SettingsIconSmall({ className }) {
1046
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: [
1047
1224
  /* @__PURE__ */ jsx12("title", { children: "Settings" }),
@@ -1102,10 +1279,10 @@ function DashboardLayoutInner({
1102
1279
  notifications: notifData
1103
1280
  }) {
1104
1281
  const Link = LinkComponent;
1105
- const [mobileMenuOpen, setMobileMenuOpen] = React3.useState(false);
1106
- const [notificationsOpen, setNotificationsOpen] = React3.useState(false);
1107
- const notifRef = React3.useRef(null);
1108
- 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(() => {
1109
1286
  if (!notificationsOpen) return;
1110
1287
  const handler = (e) => {
1111
1288
  if (notifRef.current && !notifRef.current.contains(e.target)) {
@@ -1123,21 +1300,21 @@ function DashboardLayoutInner({
1123
1300
  };
1124
1301
  }, [notificationsOpen]);
1125
1302
  const { contentMargin, hidden, mode, hasPanels, panelOpen } = useSidebar();
1126
- const modeSet = React3.useMemo(() => new Set(modeItems), [modeItems]);
1127
- const sidebarUser = React3.useMemo(
1303
+ const modeSet = React4.useMemo(() => new Set(modeItems), [modeItems]);
1304
+ const sidebarUser = React4.useMemo(
1128
1305
  () => user ? { email: user.email, name: user.name, tier: user.tier, avatarUrl: user.avatarUrl } : void 0,
1129
1306
  [user?.email, user?.name, user?.tier, user?.avatarUrl]
1130
1307
  );
1131
1308
  const activePanel = panels.find((p) => p.mode === mode);
1132
- const buildSidebarContent = React3.useCallback(
1133
- (showLabels) => /* @__PURE__ */ jsxs10(Fragment6, { children: [
1309
+ const buildSidebarContent = React4.useCallback(
1310
+ (showLabels) => /* @__PURE__ */ jsxs10(Fragment7, { children: [
1134
1311
  /* @__PURE__ */ jsxs10(SidebarRail, { wide: showLabels, children: [
1135
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 }) }) }),
1136
1313
  /* @__PURE__ */ jsx12(SidebarRailNav, { children: navItems.map((item, i) => {
1137
1314
  const isMode = modeSet.has(item.id);
1138
1315
  const prevIsMode = i > 0 && modeSet.has(navItems[i - 1].id);
1139
1316
  const showSep = i > 0 && isMode && !prevIsMode;
1140
- return /* @__PURE__ */ jsxs10(React3.Fragment, { children: [
1317
+ return /* @__PURE__ */ jsxs10(React4.Fragment, { children: [
1141
1318
  showSep && /* @__PURE__ */ jsx12(RailSeparator, {}),
1142
1319
  isMode ? /* @__PURE__ */ jsx12(
1143
1320
  RailModeButton,
@@ -1202,8 +1379,8 @@ function DashboardLayoutInner({
1202
1379
  mode
1203
1380
  ]
1204
1381
  );
1205
- const sidebarContent = React3.useMemo(() => buildSidebarContent(false), [buildSidebarContent]);
1206
- const mobileSidebarContent = React3.useMemo(() => buildSidebarContent(true), [buildSidebarContent]);
1382
+ const sidebarContent = React4.useMemo(() => buildSidebarContent(false), [buildSidebarContent]);
1383
+ const mobileSidebarContent = React4.useMemo(() => buildSidebarContent(true), [buildSidebarContent]);
1207
1384
  return /* @__PURE__ */ jsxs10("div", { className: cn("min-h-screen bg-background text-foreground", className), children: [
1208
1385
  /* @__PURE__ */ jsxs10(
1209
1386
  "nav",
@@ -1341,14 +1518,14 @@ import { Check as Check2, ChevronDown as ChevronDown2, Plus as Plus3, Settings }
1341
1518
  import { Button } from "@tangle-network/ui/primitives";
1342
1519
  import { Badge } from "@tangle-network/ui/primitives";
1343
1520
  import {
1344
- DropdownMenu as DropdownMenu3,
1345
- DropdownMenuContent as DropdownMenuContent3,
1346
- DropdownMenuItem as DropdownMenuItem3,
1521
+ DropdownMenu as DropdownMenu4,
1522
+ DropdownMenuContent as DropdownMenuContent4,
1523
+ DropdownMenuItem as DropdownMenuItem4,
1347
1524
  DropdownMenuLabel as DropdownMenuLabel2,
1348
- DropdownMenuSeparator as DropdownMenuSeparator3,
1349
- DropdownMenuTrigger as DropdownMenuTrigger3
1525
+ DropdownMenuSeparator as DropdownMenuSeparator4,
1526
+ DropdownMenuTrigger as DropdownMenuTrigger4
1350
1527
  } from "@tangle-network/ui/primitives";
1351
- 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";
1352
1529
  function ProfileSelector({
1353
1530
  profiles,
1354
1531
  selectedId,
@@ -1365,14 +1542,14 @@ function ProfileSelector({
1365
1542
  const customProfiles = profiles.filter((p) => !p.is_builtin);
1366
1543
  return /* @__PURE__ */ jsxs11("div", { className, children: [
1367
1544
  label && /* @__PURE__ */ jsx13("label", { className: "mb-2 block font-medium text-sm", children: label }),
1368
- /* @__PURE__ */ jsxs11(DropdownMenu3, { children: [
1369
- /* @__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: [
1370
1547
  /* @__PURE__ */ jsx13("span", { className: "truncate", children: selected ? selected.name : placeholder }),
1371
1548
  /* @__PURE__ */ jsx13(ChevronDown2, { className: "ml-2 h-4 w-4 shrink-0 opacity-50" })
1372
1549
  ] }) }),
1373
- /* @__PURE__ */ jsxs11(DropdownMenuContent3, { className: "w-[300px]", align: "start", children: [
1550
+ /* @__PURE__ */ jsxs11(DropdownMenuContent4, { className: "w-[300px]", align: "start", children: [
1374
1551
  /* @__PURE__ */ jsxs11(
1375
- DropdownMenuItem3,
1552
+ DropdownMenuItem4,
1376
1553
  {
1377
1554
  onClick: () => onSelect(null),
1378
1555
  className: "flex items-center justify-between",
@@ -1382,11 +1559,11 @@ function ProfileSelector({
1382
1559
  ]
1383
1560
  }
1384
1561
  ),
1385
- builtinProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment7, { children: [
1386
- /* @__PURE__ */ jsx13(DropdownMenuSeparator3, {}),
1562
+ builtinProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment8, { children: [
1563
+ /* @__PURE__ */ jsx13(DropdownMenuSeparator4, {}),
1387
1564
  /* @__PURE__ */ jsx13(DropdownMenuLabel2, { children: "Built-in Profiles" }),
1388
1565
  builtinProfiles.map((profile) => /* @__PURE__ */ jsxs11(
1389
- DropdownMenuItem3,
1566
+ DropdownMenuItem4,
1390
1567
  {
1391
1568
  onClick: () => onSelect(profile),
1392
1569
  className: "flex flex-col items-start gap-1",
@@ -1407,11 +1584,11 @@ function ProfileSelector({
1407
1584
  profile.id
1408
1585
  ))
1409
1586
  ] }),
1410
- customProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment7, { children: [
1411
- /* @__PURE__ */ jsx13(DropdownMenuSeparator3, {}),
1587
+ customProfiles.length > 0 && /* @__PURE__ */ jsxs11(Fragment8, { children: [
1588
+ /* @__PURE__ */ jsx13(DropdownMenuSeparator4, {}),
1412
1589
  /* @__PURE__ */ jsx13(DropdownMenuLabel2, { children: "Custom Profiles" }),
1413
1590
  customProfiles.map((profile) => /* @__PURE__ */ jsxs11(
1414
- DropdownMenuItem3,
1591
+ DropdownMenuItem4,
1415
1592
  {
1416
1593
  onClick: () => onSelect(profile),
1417
1594
  className: "flex flex-col items-start gap-1",
@@ -1444,10 +1621,10 @@ function ProfileSelector({
1444
1621
  profile.id
1445
1622
  ))
1446
1623
  ] }),
1447
- (onCreateClick || onManageClick) && /* @__PURE__ */ jsxs11(Fragment7, { children: [
1448
- /* @__PURE__ */ jsx13(DropdownMenuSeparator3, {}),
1624
+ (onCreateClick || onManageClick) && /* @__PURE__ */ jsxs11(Fragment8, { children: [
1625
+ /* @__PURE__ */ jsx13(DropdownMenuSeparator4, {}),
1449
1626
  onCreateClick && /* @__PURE__ */ jsxs11(
1450
- DropdownMenuItem3,
1627
+ DropdownMenuItem4,
1451
1628
  {
1452
1629
  onClick: onCreateClick,
1453
1630
  className: "text-[var(--surface-info-text)]",
@@ -1458,7 +1635,7 @@ function ProfileSelector({
1458
1635
  }
1459
1636
  ),
1460
1637
  onManageClick && /* @__PURE__ */ jsxs11(
1461
- DropdownMenuItem3,
1638
+ DropdownMenuItem4,
1462
1639
  {
1463
1640
  onClick: onManageClick,
1464
1641
  className: "text-muted-foreground",
@@ -1542,8 +1719,8 @@ function ProfileComparison({
1542
1719
  import {
1543
1720
  Check as Check3,
1544
1721
  CheckCircle2,
1545
- Clock as Clock2,
1546
- ExternalLink,
1722
+ Clock as Clock3,
1723
+ ExternalLink as ExternalLink2,
1547
1724
  Loader2,
1548
1725
  Timer,
1549
1726
  X,
@@ -1551,10 +1728,10 @@ import {
1551
1728
  } from "lucide-react";
1552
1729
  import { Button as Button2 } from "@tangle-network/ui/primitives";
1553
1730
  import { Badge as Badge2 } from "@tangle-network/ui/primitives";
1554
- 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";
1555
1732
  var statusConfig = {
1556
1733
  pending: {
1557
- icon: Clock2,
1734
+ icon: Clock3,
1558
1735
  color: "text-[var(--surface-warning-text)]",
1559
1736
  bg: "bg-[var(--surface-warning-bg)]",
1560
1737
  border: "border-[var(--surface-warning-border)]",
@@ -1674,7 +1851,7 @@ function VariantList({
1674
1851
  children: outcomeConfig[variant.outcome].label
1675
1852
  }
1676
1853
  ),
1677
- 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: [
1678
1855
  /* @__PURE__ */ jsxs12(
1679
1856
  Button2,
1680
1857
  {
@@ -1720,7 +1897,7 @@ function VariantList({
1720
1897
  e.stopPropagation();
1721
1898
  window.open(variant.detailsUrl, "_blank");
1722
1899
  },
1723
- children: /* @__PURE__ */ jsx14(ExternalLink, { className: "h-3.5 w-3.5" })
1900
+ children: /* @__PURE__ */ jsx14(ExternalLink2, { className: "h-3.5 w-3.5" })
1724
1901
  }
1725
1902
  )
1726
1903
  ] })
@@ -1846,7 +2023,7 @@ function SystemLogsViewer({ apiUrl, token, className }) {
1846
2023
  }
1847
2024
 
1848
2025
  // src/dashboard/usage-summary.tsx
1849
- import { Clock as Clock3, Layers, MessageSquare, DollarSign } from "lucide-react";
2026
+ import { Clock as Clock4, Layers, MessageSquare, DollarSign } from "lucide-react";
1850
2027
  import { StatCard } from "@tangle-network/ui/primitives";
1851
2028
  import { Skeleton as Skeleton2 } from "@tangle-network/ui/primitives";
1852
2029
  import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
@@ -1862,7 +2039,7 @@ function UsageSummary({ data, loading = false, className }) {
1862
2039
  title: "Compute Hours",
1863
2040
  value: data.computeHours.toFixed(1),
1864
2041
  subtitle: "This billing period",
1865
- icon: /* @__PURE__ */ jsx16(Clock3, { className: "h-5 w-5" })
2042
+ icon: /* @__PURE__ */ jsx16(Clock4, { className: "h-5 w-5" })
1866
2043
  }
1867
2044
  ),
1868
2045
  /* @__PURE__ */ jsx16(
@@ -1969,14 +2146,14 @@ function GitPanel({ status, log, loading = false, onRefresh, className }) {
1969
2146
  }
1970
2147
 
1971
2148
  // src/dashboard/ports-list.tsx
1972
- import * as React5 from "react";
1973
- 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";
1974
2151
  import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1975
2152
  function PortsList({ ports, onExposePort, onRemovePort, isExposing = false, className }) {
1976
- const [newPort, setNewPort] = React5.useState("");
1977
- const [copiedPort, setCopiedPort] = React5.useState(null);
1978
- const copyTimerRef = React5.useRef(null);
1979
- React5.useEffect(() => {
2153
+ const [newPort, setNewPort] = React6.useState("");
2154
+ const [copiedPort, setCopiedPort] = React6.useState(null);
2155
+ const copyTimerRef = React6.useRef(null);
2156
+ React6.useEffect(() => {
1980
2157
  return () => {
1981
2158
  if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
1982
2159
  };
@@ -2016,7 +2193,7 @@ function PortsList({ ports, onExposePort, onRemovePort, isExposing = false, clas
2016
2193
  className: "flex items-center gap-2 font-mono text-xs text-primary hover:underline cursor-pointer group",
2017
2194
  children: [
2018
2195
  /* @__PURE__ */ jsx18("span", { className: "truncate max-w-[300px]", children: p.url }),
2019
- 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" })
2020
2197
  ]
2021
2198
  }
2022
2199
  ) }),
@@ -2070,8 +2247,8 @@ function PortsList({ ports, onExposePort, onRemovePort, isExposing = false, clas
2070
2247
  }
2071
2248
 
2072
2249
  // src/dashboard/process-list.tsx
2073
- import * as React6 from "react";
2074
- 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";
2075
2252
  import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2076
2253
  function formatUptime(startedAt) {
2077
2254
  if (!startedAt) return "-";
@@ -2082,7 +2259,7 @@ function formatUptime(startedAt) {
2082
2259
  return `${Math.floor(ms / 36e5)}h ${Math.floor(ms % 36e5 / 6e4)}m`;
2083
2260
  }
2084
2261
  function ProcessList({ processes, onSpawn, onKill, loading = false, className }) {
2085
- const [newCommand, setNewCommand] = React6.useState("");
2262
+ const [newCommand, setNewCommand] = React7.useState("");
2086
2263
  const handleSpawn = () => {
2087
2264
  const cmd = newCommand.trim();
2088
2265
  if (cmd) {
@@ -2092,7 +2269,7 @@ function ProcessList({ processes, onSpawn, onKill, loading = false, className })
2092
2269
  };
2093
2270
  return /* @__PURE__ */ jsxs17("div", { className: cn("space-y-4", className), children: [
2094
2271
  loading ? /* @__PURE__ */ jsxs17("div", { className: "rounded-lg border border-border bg-muted/20 p-6 text-center", children: [
2095
- /* @__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" }),
2096
2273
  /* @__PURE__ */ jsx19("p", { className: "text-sm text-muted-foreground", children: "Loading processes..." })
2097
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: [
2098
2275
  /* @__PURE__ */ jsx19("thead", { className: "bg-muted/30 border-b border-border", children: /* @__PURE__ */ jsxs17("tr", { children: [
@@ -2155,11 +2332,11 @@ function ProcessList({ processes, onSpawn, onKill, loading = false, className })
2155
2332
  }
2156
2333
 
2157
2334
  // src/dashboard/network-config.tsx
2158
- import * as React7 from "react";
2335
+ import * as React8 from "react";
2159
2336
  import { Network as Network2, Plus as Plus6, Trash2 as Trash24, ShieldAlert } from "lucide-react";
2160
2337
  import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2161
2338
  function NetworkConfig({ config, onUpdate, loading = false, className }) {
2162
- const [newCidr, setNewCidr] = React7.useState("");
2339
+ const [newCidr, setNewCidr] = React8.useState("");
2163
2340
  const isValidCidr = (value) => {
2164
2341
  const match = value.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d{1,2})$/);
2165
2342
  if (!match) return false;
@@ -2263,9 +2440,9 @@ function NetworkConfig({ config, onUpdate, loading = false, className }) {
2263
2440
  }
2264
2441
 
2265
2442
  // src/dashboard/backend-config.tsx
2266
- import * as React8 from "react";
2443
+ import * as React9 from "react";
2267
2444
  import { Bot, Plus as Plus7, RefreshCw as RefreshCw2, Trash2 as Trash25, Server, Wrench } from "lucide-react";
2268
- 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";
2269
2446
  function BackendConfig({
2270
2447
  status,
2271
2448
  mcpServers,
@@ -2275,10 +2452,10 @@ function BackendConfig({
2275
2452
  loading = false,
2276
2453
  className
2277
2454
  }) {
2278
- const [showAddMcp, setShowAddMcp] = React8.useState(false);
2279
- const [mcpName, setMcpName] = React8.useState("");
2280
- const [mcpCommand, setMcpCommand] = React8.useState("");
2281
- 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("");
2282
2459
  const handleAddMcp = () => {
2283
2460
  const name = mcpName.trim();
2284
2461
  const command = mcpCommand.trim();
@@ -2325,7 +2502,7 @@ function BackendConfig({
2325
2502
  ), children: status.running ? "Running" : "Stopped" }) }),
2326
2503
  /* @__PURE__ */ jsx21("dt", { className: "text-muted-foreground", children: "Model" }),
2327
2504
  /* @__PURE__ */ jsx21("dd", { className: "font-mono text-xs", children: status.model ?? "Default" }),
2328
- status.provider && /* @__PURE__ */ jsxs19(Fragment9, { children: [
2505
+ status.provider && /* @__PURE__ */ jsxs19(Fragment10, { children: [
2329
2506
  /* @__PURE__ */ jsx21("dt", { className: "text-muted-foreground", children: "Provider" }),
2330
2507
  /* @__PURE__ */ jsx21("dd", { className: "font-mono text-xs", children: status.provider })
2331
2508
  ] })
@@ -2436,8 +2613,8 @@ function BackendConfig({
2436
2613
  }
2437
2614
 
2438
2615
  // src/dashboard/snapshot-list.tsx
2439
- import * as React9 from "react";
2440
- 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";
2441
2618
  import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2442
2619
  function formatBytes(bytes) {
2443
2620
  if (bytes == null || bytes < 0) return "-";
@@ -2453,8 +2630,8 @@ function formatDate(dateStr) {
2453
2630
  return d.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
2454
2631
  }
2455
2632
  function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loading = false, className }) {
2456
- const [showCreate, setShowCreate] = React9.useState(false);
2457
- const [tags, setTags] = React9.useState("");
2633
+ const [showCreate, setShowCreate] = React10.useState(false);
2634
+ const [tags, setTags] = React10.useState("");
2458
2635
  const handleCreate = () => {
2459
2636
  const tagList = tags.trim() ? tags.trim().split(",").map((t) => t.trim()).filter(Boolean) : void 0;
2460
2637
  onCreate(tagList);
@@ -2528,7 +2705,7 @@ function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loadin
2528
2705
  /* @__PURE__ */ jsx22("tbody", { className: "divide-y divide-border", children: snapshots.map((s) => /* @__PURE__ */ jsxs20("tr", { children: [
2529
2706
  /* @__PURE__ */ jsx22("td", { className: "px-4 py-3 font-mono text-xs text-foreground", children: s.id.slice(0, 12) }),
2530
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: [
2531
- /* @__PURE__ */ jsx22(Clock4, { className: "h-3 w-3" }),
2708
+ /* @__PURE__ */ jsx22(Clock5, { className: "h-3 w-3" }),
2532
2709
  formatDate(s.createdAt)
2533
2710
  ] }) }),
2534
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: [
@@ -2571,7 +2748,7 @@ function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loadin
2571
2748
  }
2572
2749
 
2573
2750
  // src/dashboard/promo-banner.tsx
2574
- 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";
2575
2752
  function PromoBanner({
2576
2753
  title,
2577
2754
  description,
@@ -2586,7 +2763,7 @@ function PromoBanner({
2586
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",
2587
2764
  disabled ? "opacity-50 cursor-not-allowed" : "hover:bg-[var(--btn-primary-hover)]"
2588
2765
  );
2589
- const buttonContent = /* @__PURE__ */ jsxs21(Fragment10, { children: [
2766
+ const buttonContent = /* @__PURE__ */ jsxs21(Fragment11, { children: [
2590
2767
  buttonLabel,
2591
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: [
2592
2769
  /* @__PURE__ */ jsx23("path", { d: "M5 12h14" }),
@@ -231,12 +231,34 @@ interface SandboxTableProps {
231
231
  onOpenIDE?: (id: string) => void;
232
232
  onOpenTerminal?: (id: string) => void;
233
233
  onSSH?: (id: string) => void;
234
+ /**
235
+ * Resume a stopped / failed / archived sandbox, or wake a hibernating
236
+ * one. Surfaces an explicit "Resume" (or "Wake Up" for `hibernating`)
237
+ * button in the actions cell and makes the row body clickable for any
238
+ * status this prop covers. Preferred over `onWake` for new code — the
239
+ * two are kept as separate props only so existing consumers that
240
+ * wired up `onWake` for hibernating continue to work.
241
+ */
242
+ onResume?: (id: string) => void;
243
+ /**
244
+ * @deprecated Use `onResume` instead. Retained for back-compat:
245
+ * when `onResume` is not provided but `onWake` is, hibernating rows
246
+ * will still surface a Wake action. New consumers should pass
247
+ * `onResume` so stopped / failed / archived rows also get a start
248
+ * affordance.
249
+ */
234
250
  onWake?: (id: string) => void;
235
251
  onMore?: (id: string) => void;
252
+ /** Fired on the user's first click; the caller owns the confirmation step. */
236
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;
237
259
  className?: string;
238
260
  }
239
- declare function SandboxTable({ sandboxes, page, pageSize, total, onPageChange, onOpenIDE, onOpenTerminal, onSSH, 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;
240
262
 
241
263
  interface Invoice {
242
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-HLZTKSGT.js";
46
+ } from "./chunk-5I363RL7.js";
47
47
  import {
48
48
  BillingDashboard,
49
49
  InfoPanel,
package/dist/globals.css CHANGED
@@ -232,6 +232,9 @@
232
232
  .pointer-events-none {
233
233
  pointer-events: none;
234
234
  }
235
+ .collapse {
236
+ visibility: collapse;
237
+ }
235
238
  .invisible {
236
239
  visibility: hidden;
237
240
  }
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ import {
174
174
  VariantList,
175
175
  canAdminSandbox,
176
176
  useSidebar
177
- } from "./chunk-HLZTKSGT.js";
177
+ } from "./chunk-5I363RL7.js";
178
178
  import {
179
179
  BillingDashboard,
180
180
  InfoPanel,
package/dist/styles.css CHANGED
@@ -232,6 +232,9 @@
232
232
  .pointer-events-none {
233
233
  pointer-events: none;
234
234
  }
235
+ .collapse {
236
+ visibility: collapse;
237
+ }
235
238
  .invisible {
236
239
  visibility: hidden;
237
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.16.0",
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",