@tangle-network/sandbox-ui 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -516,6 +516,10 @@ function XIcon({ className }) {
516
516
  /* @__PURE__ */ jsx7("path", { d: "m6 6 12 12" })
517
517
  ] });
518
518
  }
519
+ function formatNotifDate(raw) {
520
+ const d = new Date(raw);
521
+ return Number.isNaN(d.getTime()) ? raw : d.toLocaleString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
522
+ }
519
523
  function DefaultLink2({
520
524
  href,
521
525
  to,
@@ -546,10 +550,30 @@ function DashboardLayoutInner({
546
550
  LinkComponent = DefaultLink2,
547
551
  footer,
548
552
  railFooter,
549
- profileMenuItems
553
+ profileMenuItems,
554
+ notifications: notifData
550
555
  }) {
551
556
  const Link = LinkComponent;
552
557
  const [mobileMenuOpen, setMobileMenuOpen] = React3.useState(false);
558
+ const [notificationsOpen, setNotificationsOpen] = React3.useState(false);
559
+ const notifRef = React3.useRef(null);
560
+ React3.useEffect(() => {
561
+ if (!notificationsOpen) return;
562
+ const handler = (e) => {
563
+ if (notifRef.current && !notifRef.current.contains(e.target)) {
564
+ setNotificationsOpen(false);
565
+ }
566
+ };
567
+ const keyHandler = (e) => {
568
+ if (e.key === "Escape") setNotificationsOpen(false);
569
+ };
570
+ document.addEventListener("mousedown", handler);
571
+ document.addEventListener("keydown", keyHandler);
572
+ return () => {
573
+ document.removeEventListener("mousedown", handler);
574
+ document.removeEventListener("keydown", keyHandler);
575
+ };
576
+ }, [notificationsOpen]);
553
577
  const { contentMargin, hidden, mode, hasPanels, panelOpen } = useSidebar();
554
578
  const modeSet = React3.useMemo(() => new Set(modeItems), [modeItems]);
555
579
  const sidebarUser = user ? { email: user.email, name: user.name, tier: user.tier, avatarUrl: user.avatarUrl } : void 0;
@@ -640,7 +664,61 @@ function DashboardLayoutInner({
640
664
  ]
641
665
  }
642
666
  ),
643
- /* @__PURE__ */ jsx7("button", { type: "button", className: "text-muted-foreground hover:text-foreground transition-colors p-2 rounded-lg hover:bg-muted/50", children: /* @__PURE__ */ jsx7(Bell, { className: "h-4 w-4" }) })
667
+ /* @__PURE__ */ jsxs6("div", { className: "relative", ref: notifRef, children: [
668
+ /* @__PURE__ */ jsxs6(
669
+ "button",
670
+ {
671
+ type: "button",
672
+ className: "relative text-muted-foreground hover:text-foreground transition-colors p-2 rounded-lg hover:bg-muted/50",
673
+ onClick: () => setNotificationsOpen(!notificationsOpen),
674
+ "aria-label": "Notifications",
675
+ "aria-expanded": notificationsOpen,
676
+ children: [
677
+ /* @__PURE__ */ jsx7(Bell, { className: "h-4 w-4" }),
678
+ (notifData?.unreadCount ?? 0) > 0 && /* @__PURE__ */ jsx7("span", { className: "absolute top-1 right-1 h-2 w-2 rounded-full bg-destructive" })
679
+ ]
680
+ }
681
+ ),
682
+ notificationsOpen && /* @__PURE__ */ jsxs6("div", { className: "absolute right-0 top-full mt-2 w-80 rounded-lg border border-border bg-card shadow-lg z-50", children: [
683
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
684
+ /* @__PURE__ */ jsx7("p", { className: "font-bold text-foreground text-sm", children: "Notifications" }),
685
+ (notifData?.unreadCount ?? 0) > 0 && notifData?.onMarkAllRead && /* @__PURE__ */ jsx7(
686
+ "button",
687
+ {
688
+ type: "button",
689
+ onClick: () => {
690
+ notifData.onMarkAllRead?.();
691
+ },
692
+ className: "text-primary text-xs font-medium hover:underline",
693
+ children: "Mark all read"
694
+ }
695
+ )
696
+ ] }),
697
+ !notifData?.items || notifData.items.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center justify-center px-4 py-8 text-center", children: [
698
+ /* @__PURE__ */ jsx7(Bell, { className: "h-8 w-8 text-muted-foreground/40 mb-2" }),
699
+ /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground text-sm", children: "No notifications yet" }),
700
+ /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground/60 text-xs mt-1", children: "We'll notify you about important updates" })
701
+ ] }) : /* @__PURE__ */ jsx7("div", { className: "max-h-80 overflow-y-auto", children: notifData.items.map((n) => /* @__PURE__ */ jsxs6(
702
+ "button",
703
+ {
704
+ type: "button",
705
+ className: cn(
706
+ "w-full text-left px-4 py-3 border-b border-border last:border-0 transition-colors",
707
+ n.read ? "cursor-default" : "bg-primary/5 hover:bg-muted/50"
708
+ ),
709
+ onClick: () => {
710
+ if (!n.read) notifData.onMarkRead?.(n.id);
711
+ },
712
+ children: [
713
+ /* @__PURE__ */ jsx7("p", { className: cn("text-sm", !n.read ? "font-semibold text-foreground" : "text-muted-foreground"), children: n.title }),
714
+ /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground mt-0.5 line-clamp-2", children: n.message }),
715
+ /* @__PURE__ */ jsx7("p", { className: "text-[10px] text-muted-foreground/50 mt-1", children: formatNotifDate(n.createdAt) })
716
+ ]
717
+ },
718
+ n.id
719
+ )) })
720
+ ] })
721
+ ] })
644
722
  ] }),
645
723
  /* @__PURE__ */ jsx7(
646
724
  "button",
@@ -1,4 +1,4 @@
1
- export { B as Backend, a as BackendSelector, b as BackendSelectorProps, C as ClusterStatusBar, c as ClusterStatusBarProps, d as ClusterStatusItem, e as CreditBalance, f as CreditBalanceProps, D as DashboardLayout, g as DashboardLayoutProps, ag as DashboardProfile, h as DashboardUser, I as Invoice, i as InvoiceTable, j as InvoiceTableProps, N as NavItem, k as NewSandboxCard, l as NewSandboxCardProps, m as PlanCardData, n as PlanCards, o as PlanCardsProps, ah as PlanFeature, p as ProductVariant, q as ProfileAvatar, r as ProfileAvatarProps, s as ProfileComparison, t as ProfileComparisonProps, u as ProfileSelector, v as ProfileSelectorProps, R as RailButton, w as RailButtonProps, x as RailModeButton, y as RailModeButtonProps, z as RailSeparator, A as RailSeparatorProps, E as ResourceMeter, F as ResourceMeterProps, S as SIDEBAR_PANEL_WIDTH, G as SIDEBAR_RAIL_WIDTH, H as SIDEBAR_TOTAL_WIDTH, J as SandboxCard, K as SandboxCardData, L as SandboxCardProps, M as SandboxStatus, O as SandboxTable, Q as SandboxTableProps, T as Sidebar, U as SidebarContent, V as SidebarContentProps, W as SidebarPanel, X as SidebarPanelContent, Y as SidebarPanelContentProps, Z as SidebarPanelHeader, _ as SidebarPanelHeaderProps, $ as SidebarPanelProps, a0 as SidebarProps, a1 as SidebarProvider, a2 as SidebarProviderProps, a3 as SidebarRail, a4 as SidebarRailFooter, a5 as SidebarRailFooterProps, a6 as SidebarRailHeader, a7 as SidebarRailHeaderProps, a8 as SidebarRailNav, a9 as SidebarRailNavProps, aa as SidebarRailProps, ab as SidebarUser, ai as Variant, ad as VariantList, ae as VariantListProps, aj as VariantOutcome, ak as VariantStatus, af as useSidebar } from './variant-list-CsS6ydgm.js';
1
+ export { B as Backend, a as BackendSelector, b as BackendSelectorProps, C as ClusterStatusBar, c as ClusterStatusBarProps, d as ClusterStatusItem, e as CreditBalance, f as CreditBalanceProps, D as DashboardLayout, g as DashboardLayoutProps, ag as DashboardProfile, h as DashboardUser, I as Invoice, i as InvoiceTable, j as InvoiceTableProps, N as NavItem, k as NewSandboxCard, l as NewSandboxCardProps, m as PlanCardData, n as PlanCards, o as PlanCardsProps, ah as PlanFeature, p as ProductVariant, q as ProfileAvatar, r as ProfileAvatarProps, s as ProfileComparison, t as ProfileComparisonProps, u as ProfileSelector, v as ProfileSelectorProps, R as RailButton, w as RailButtonProps, x as RailModeButton, y as RailModeButtonProps, z as RailSeparator, A as RailSeparatorProps, E as ResourceMeter, F as ResourceMeterProps, S as SIDEBAR_PANEL_WIDTH, G as SIDEBAR_RAIL_WIDTH, H as SIDEBAR_TOTAL_WIDTH, J as SandboxCard, K as SandboxCardData, L as SandboxCardProps, M as SandboxStatus, O as SandboxTable, Q as SandboxTableProps, T as Sidebar, U as SidebarContent, V as SidebarContentProps, W as SidebarPanel, X as SidebarPanelContent, Y as SidebarPanelContentProps, Z as SidebarPanelHeader, _ as SidebarPanelHeaderProps, $ as SidebarPanelProps, a0 as SidebarProps, a1 as SidebarProvider, a2 as SidebarProviderProps, a3 as SidebarRail, a4 as SidebarRailFooter, a5 as SidebarRailFooterProps, a6 as SidebarRailHeader, a7 as SidebarRailHeaderProps, a8 as SidebarRailNav, a9 as SidebarRailNavProps, aa as SidebarRailProps, ab as SidebarUser, ai as Variant, ad as VariantList, ae as VariantListProps, aj as VariantOutcome, ak as VariantStatus, af as useSidebar } from './variant-list-BNwUOSgz.js';
2
2
  export { a as BillingBalance, c as BillingDashboard, d as BillingDashboardProps, B as BillingSubscription, b as BillingUsage, e as PricingPage, f as PricingPageProps, P as PricingTier, g as UsageChart, h as UsageChartProps, U as UsageDataPoint } from './usage-chart-SSiOgeQI.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  export { a as TemplateCard, T as TemplateCardData, b as TemplateCardProps } from './template-card-BAtvcAkU.js';
@@ -128,10 +128,11 @@ interface SnapshotListProps {
128
128
  snapshots: SnapshotInfo[];
129
129
  onCreate: (tags?: string[]) => void;
130
130
  onRestore: (snapshotId: string) => void;
131
+ onSaveAsTemplate?: (snapshotId: string) => void;
131
132
  loading?: boolean;
132
133
  className?: string;
133
134
  }
134
- declare function SnapshotList({ snapshots, onCreate, onRestore, loading, className }: SnapshotListProps): react_jsx_runtime.JSX.Element;
135
+ declare function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loading, className }: SnapshotListProps): react_jsx_runtime.JSX.Element;
135
136
 
136
137
  interface PromoBannerProps {
137
138
  title: string;
package/dist/dashboard.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  SidebarRailNav,
35
35
  VariantList,
36
36
  useSidebar
37
- } from "./chunk-7YWFOGKQ.js";
37
+ } from "./chunk-2XCOGNZP.js";
38
38
  import {
39
39
  BillingDashboard,
40
40
  PricingPage,
@@ -769,7 +769,7 @@ function formatDate(dateStr) {
769
769
  if (Number.isNaN(d.getTime())) return dateStr;
770
770
  return d.toLocaleDateString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
771
771
  }
772
- function SnapshotList({ snapshots, onCreate, onRestore, loading = false, className }) {
772
+ function SnapshotList({ snapshots, onCreate, onRestore, onSaveAsTemplate, loading = false, className }) {
773
773
  const [showCreate, setShowCreate] = React6.useState(false);
774
774
  const [tags, setTags] = React6.useState("");
775
775
  const handleCreate = () => {
@@ -853,19 +853,31 @@ function SnapshotList({ snapshots, onCreate, onRestore, loading = false, classNa
853
853
  formatBytes(s.sizeBytes)
854
854
  ] }) }),
855
855
  /* @__PURE__ */ jsx8("td", { className: "px-4 py-3", children: s.tags?.length ? /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-1 flex-wrap", children: s.tags.map((tag) => /* @__PURE__ */ jsx8("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground border border-border", children: tag }, tag)) }) : /* @__PURE__ */ jsx8("span", { className: "text-xs text-muted-foreground", children: "-" }) }),
856
- /* @__PURE__ */ jsx8("td", { className: "px-4 py-3 text-right", children: /* @__PURE__ */ jsxs8(
857
- "button",
858
- {
859
- type: "button",
860
- onClick: () => onRestore(s.id),
861
- className: "inline-flex items-center gap-1.5 rounded-md bg-muted px-2.5 py-1 text-xs font-medium text-foreground hover:bg-muted/80 transition-colors border border-border",
862
- title: "Restore to new sandbox",
863
- children: [
864
- /* @__PURE__ */ jsx8(RotateCcw, { className: "h-3 w-3" }),
865
- "Restore"
866
- ]
867
- }
868
- ) })
856
+ /* @__PURE__ */ jsx8("td", { className: "px-4 py-3 text-right", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-end gap-2", children: [
857
+ /* @__PURE__ */ jsxs8(
858
+ "button",
859
+ {
860
+ type: "button",
861
+ onClick: () => onRestore(s.id),
862
+ className: "inline-flex items-center gap-1.5 rounded-md bg-muted px-2.5 py-1 text-xs font-medium text-foreground hover:bg-muted/80 transition-colors border border-border",
863
+ title: "Restore to new sandbox",
864
+ children: [
865
+ /* @__PURE__ */ jsx8(RotateCcw, { className: "h-3 w-3" }),
866
+ "Restore"
867
+ ]
868
+ }
869
+ ),
870
+ onSaveAsTemplate && /* @__PURE__ */ jsx8(
871
+ "button",
872
+ {
873
+ type: "button",
874
+ onClick: () => onSaveAsTemplate(s.id),
875
+ className: "inline-flex items-center gap-1.5 rounded-md bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary hover:bg-primary/20 transition-colors border border-primary/20",
876
+ title: "Save as reusable template",
877
+ children: "Save as Template"
878
+ }
879
+ )
880
+ ] }) })
869
881
  ] }, s.id)) })
870
882
  ] }) }) : /* @__PURE__ */ jsxs8("div", { className: "rounded-lg border border-border bg-muted/20 p-6 text-center", children: [
871
883
  /* @__PURE__ */ jsx8(Camera, { className: "mx-auto h-8 w-8 text-muted-foreground mb-2" }),
package/dist/globals.css CHANGED
@@ -720,6 +720,9 @@
720
720
  .top-0\.5 {
721
721
  top: calc(var(--spacing) * 0.5);
722
722
  }
723
+ .top-1 {
724
+ top: calc(var(--spacing) * 1);
725
+ }
723
726
  .top-1\/2 {
724
727
  top: calc(1 / 2 * 100%);
725
728
  }
@@ -735,12 +738,18 @@
735
738
  .top-\[50\%\] {
736
739
  top: 50%;
737
740
  }
741
+ .top-full {
742
+ top: 100%;
743
+ }
738
744
  .-right-1 {
739
745
  right: calc(var(--spacing) * -1);
740
746
  }
741
747
  .right-0 {
742
748
  right: calc(var(--spacing) * 0);
743
749
  }
750
+ .right-1 {
751
+ right: calc(var(--spacing) * 1);
752
+ }
744
753
  .right-2 {
745
754
  right: calc(var(--spacing) * 2);
746
755
  }
@@ -2479,6 +2488,9 @@
2479
2488
  .py-5 {
2480
2489
  padding-block: calc(var(--spacing) * 5);
2481
2490
  }
2491
+ .py-8 {
2492
+ padding-block: calc(var(--spacing) * 8);
2493
+ }
2482
2494
  .py-12 {
2483
2495
  padding-block: calc(var(--spacing) * 12);
2484
2496
  }
@@ -3400,6 +3412,12 @@
3400
3412
  }
3401
3413
  }
3402
3414
  }
3415
+ .last\:border-0 {
3416
+ &:last-child {
3417
+ border-style: var(--tw-border-style);
3418
+ border-width: 0px;
3419
+ }
3420
+ }
3403
3421
  .focus-within\:border-\[var\(--border-accent-hover\)\] {
3404
3422
  &:focus-within {
3405
3423
  border-color: var(--border-accent-hover);
package/dist/index.d.ts CHANGED
@@ -15,7 +15,7 @@ import { b as ToolPart } from './parts-CyGkM6Fp.js';
15
15
  export { R as ReasoningPart, S as SessionMessage, a as SessionPart, T as TextPart, c as ToolState, d as ToolStatus, e as ToolTime } from './parts-CyGkM6Fp.js';
16
16
  export { F as FileNode, a as FileTabData, b as FileTabs, c as FileTabsProps, d as FileTree, e as FileTreeProps, f as FileTreeVisibilityOptions, g as filterFileTree } from './file-tabs-BLfxfmAH.js';
17
17
  export { FileArtifactPane, FileArtifactPaneProps, FilePreview, FilePreviewProps } from './files.js';
18
- export { B as Backend, a as BackendSelector, b as BackendSelectorProps, C as ClusterStatusBar, c as ClusterStatusBarProps, d as ClusterStatusItem, e as CreditBalance, f as CreditBalanceProps, D as DashboardLayout, g as DashboardLayoutProps, h as DashboardUser, I as Invoice, i as InvoiceTable, j as InvoiceTableProps, N as NavItem, k as NewSandboxCard, l as NewSandboxCardProps, P as PanelConfig, m as PlanCardData, n as PlanCards, o as PlanCardsProps, p as ProductVariant, q as ProfileAvatar, r as ProfileAvatarProps, s as ProfileComparison, t as ProfileComparisonProps, u as ProfileSelector, v as ProfileSelectorProps, R as RailButton, w as RailButtonProps, x as RailModeButton, y as RailModeButtonProps, z as RailSeparator, A as RailSeparatorProps, E as ResourceMeter, F as ResourceMeterProps, S as SIDEBAR_PANEL_WIDTH, G as SIDEBAR_RAIL_WIDTH, H as SIDEBAR_TOTAL_WIDTH, J as SandboxCard, K as SandboxCardData, L as SandboxCardProps, M as SandboxStatus, O as SandboxTable, Q as SandboxTableProps, T as Sidebar, U as SidebarContent, V as SidebarContentProps, W as SidebarPanel, X as SidebarPanelContent, Y as SidebarPanelContentProps, Z as SidebarPanelHeader, _ as SidebarPanelHeaderProps, $ as SidebarPanelProps, a0 as SidebarProps, a1 as SidebarProvider, a2 as SidebarProviderProps, a3 as SidebarRail, a4 as SidebarRailFooter, a5 as SidebarRailFooterProps, a6 as SidebarRailHeader, a7 as SidebarRailHeaderProps, a8 as SidebarRailNav, a9 as SidebarRailNavProps, aa as SidebarRailProps, ab as SidebarUser, ac as TopNavLink, ad as VariantList, ae as VariantListProps, af as useSidebar } from './variant-list-CsS6ydgm.js';
18
+ export { B as Backend, a as BackendSelector, b as BackendSelectorProps, C as ClusterStatusBar, c as ClusterStatusBarProps, d as ClusterStatusItem, e as CreditBalance, f as CreditBalanceProps, D as DashboardLayout, g as DashboardLayoutProps, h as DashboardUser, I as Invoice, i as InvoiceTable, j as InvoiceTableProps, N as NavItem, k as NewSandboxCard, l as NewSandboxCardProps, P as PanelConfig, m as PlanCardData, n as PlanCards, o as PlanCardsProps, p as ProductVariant, q as ProfileAvatar, r as ProfileAvatarProps, s as ProfileComparison, t as ProfileComparisonProps, u as ProfileSelector, v as ProfileSelectorProps, R as RailButton, w as RailButtonProps, x as RailModeButton, y as RailModeButtonProps, z as RailSeparator, A as RailSeparatorProps, E as ResourceMeter, F as ResourceMeterProps, S as SIDEBAR_PANEL_WIDTH, G as SIDEBAR_RAIL_WIDTH, H as SIDEBAR_TOTAL_WIDTH, J as SandboxCard, K as SandboxCardData, L as SandboxCardProps, M as SandboxStatus, O as SandboxTable, Q as SandboxTableProps, T as Sidebar, U as SidebarContent, V as SidebarContentProps, W as SidebarPanel, X as SidebarPanelContent, Y as SidebarPanelContentProps, Z as SidebarPanelHeader, _ as SidebarPanelHeaderProps, $ as SidebarPanelProps, a0 as SidebarProps, a1 as SidebarProvider, a2 as SidebarProviderProps, a3 as SidebarRail, a4 as SidebarRailFooter, a5 as SidebarRailFooterProps, a6 as SidebarRailHeader, a7 as SidebarRailHeaderProps, a8 as SidebarRailNav, a9 as SidebarRailNavProps, aa as SidebarRailProps, ab as SidebarUser, ac as TopNavLink, ad as VariantList, ae as VariantListProps, af as useSidebar } from './variant-list-BNwUOSgz.js';
19
19
  export { c as BillingDashboard, d as BillingDashboardProps, e as PricingCards, f as PricingPageProps, g as UsageChart, h as UsageChartProps, U as UsageDataPoint } from './usage-chart-SSiOgeQI.js';
20
20
  export { AuthHeader, GitHubLoginButton, LoginLayout, LoginLayoutProps, UserMenu } from './auth.js';
21
21
  export { CodeBlock, CodeBlock as CodeBlockDisplay, CopyButton, Markdown, MarkdownProps } from './markdown.js';
package/dist/index.js CHANGED
@@ -209,7 +209,7 @@ import {
209
209
  SidebarRailNav,
210
210
  VariantList,
211
211
  useSidebar
212
- } from "./chunk-7YWFOGKQ.js";
212
+ } from "./chunk-2XCOGNZP.js";
213
213
  import {
214
214
  BillingDashboard,
215
215
  PricingPage,
package/dist/pages.d.ts CHANGED
@@ -34,6 +34,11 @@ interface EnvironmentEntry {
34
34
  description?: string;
35
35
  version?: string;
36
36
  }
37
+ interface ResourceLimits {
38
+ cpuMax?: number;
39
+ ramMaxGB?: number;
40
+ storageMaxGB?: number;
41
+ }
37
42
  interface ProvisioningWizardProps {
38
43
  environments?: EnvironmentOption[];
39
44
  onLoadEnvironments?: () => Promise<EnvironmentEntry[]>;
@@ -49,6 +54,8 @@ interface ProvisioningWizardProps {
49
54
  skipToReview?: boolean;
50
55
  /** Load user's startup scripts for the advanced options selector */
51
56
  onLoadStartupScripts?: () => Promise<StartupScriptEntry[]>;
57
+ /** Plan-based resource limits — caps the slider maximums */
58
+ resourceLimits?: ResourceLimits;
52
59
  }
53
60
  interface StartupScriptEntry {
54
61
  id: string;
@@ -75,7 +82,7 @@ interface ProvisioningConfig {
75
82
  startupScriptIds?: string[];
76
83
  }
77
84
  declare function resolveEnvironment(env: EnvironmentEntry): EnvironmentOption;
78
- declare function ProvisioningWizard({ environments: environmentsProp, onLoadEnvironments, onSubmit, onBack, className, variant, defaultEnvironment, defaultConfig, skipToReview, onLoadStartupScripts, }: ProvisioningWizardProps): react_jsx_runtime.JSX.Element;
85
+ declare function ProvisioningWizard({ environments: environmentsProp, onLoadEnvironments, onSubmit, onBack, className, variant, defaultEnvironment, defaultConfig, skipToReview, onLoadStartupScripts, resourceLimits, }: ProvisioningWizardProps): react_jsx_runtime.JSX.Element;
79
86
 
80
87
  type ProductVariant = "sandbox";
81
88
  interface StandalonePricingPageProps {
@@ -227,4 +234,4 @@ type TemplateCategory = "blockchain" | "ai-ml" | "frontend" | "infrastructure" |
227
234
  type TemplatePreset = Omit<ProvisioningConfig, "name" | "gitUrl" | "envVars" | "driver" | "startupScriptIds">;
228
235
  declare function getPresetForTemplate(id: string): TemplatePreset;
229
236
 
230
- export { BillingPage, type BillingPageData, type BillingPageProps, type EnvironmentEntry, type EnvironmentOption, PricingTier, type ProductVariant$1 as ProductVariant, type Profile, type ProfileFormData, type ProfileMetrics, ProfilesPage, type ProfilesPageProps, type ProvisioningConfig, ProvisioningWizard, type ProvisioningWizardProps, type ScriptType, type Secret, type SecretsApiClient, SecretsPage, type SecretsPageProps, StandalonePricingPage, type StandalonePricingPageProps, type StartupScript, type StartupScriptEntry, type StartupScriptFormData, type StartupScriptsApiClient, StartupScriptsPage, type StartupScriptsPageProps, type TemplateCategory, type TemplatePreset, TemplatesPage, type TemplatesPageProps, getPresetForTemplate, resolveEnvironment };
237
+ export { BillingPage, type BillingPageData, type BillingPageProps, type EnvironmentEntry, type EnvironmentOption, PricingTier, type ProductVariant$1 as ProductVariant, type Profile, type ProfileFormData, type ProfileMetrics, ProfilesPage, type ProfilesPageProps, type ProvisioningConfig, ProvisioningWizard, type ProvisioningWizardProps, type ResourceLimits, type ScriptType, type Secret, type SecretsApiClient, SecretsPage, type SecretsPageProps, StandalonePricingPage, type StandalonePricingPageProps, type StartupScript, type StartupScriptEntry, type StartupScriptFormData, type StartupScriptsApiClient, StartupScriptsPage, type StartupScriptsPageProps, type TemplateCategory, type TemplatePreset, TemplatesPage, type TemplatesPageProps, getPresetForTemplate, resolveEnvironment };
package/dist/pages.js CHANGED
@@ -310,6 +310,16 @@ var STACK_DISPLAY = {
310
310
  rust: { name: "Rust", abbr: "Rs", color: "orange", textClass: "text-[var(--surface-orange-text)]" }
311
311
  };
312
312
  function resolveEnvironment(env) {
313
+ if (env.id.startsWith("template:")) {
314
+ const templateName = env.description?.replace(/^Template:\s*/, "") ?? "Custom Template";
315
+ return {
316
+ id: env.id,
317
+ name: templateName,
318
+ description: env.description ?? "User template from snapshot",
319
+ icon: /* @__PURE__ */ jsx2("span", { className: "text-[var(--surface-success-text)] text-2xl font-bold", children: "T" }),
320
+ color: "green"
321
+ };
322
+ }
313
323
  const display = STACK_DISPLAY[env.id];
314
324
  const name = display?.name ?? (env.id.length > 0 ? env.id.charAt(0).toUpperCase() + env.id.slice(1).replace(/-/g, " ") : "Unknown");
315
325
  const abbr = display?.abbr ?? (env.id.length > 0 ? env.id[0].toUpperCase() : "?");
@@ -334,8 +344,8 @@ var RAM_MIN = 2;
334
344
  var RAM_MAX = 32;
335
345
  var STORAGE_MIN = 20;
336
346
  var STORAGE_MAX = 512;
337
- function calcCost(cpu, ram) {
338
- const cost = cpu * 0.045 + ram * 5e-3;
347
+ function calcCost(cpu, ram, storage) {
348
+ const cost = cpu * 0.045 + ram * 5e-3 + storage * 11e-4;
339
349
  return cost.toFixed(2);
340
350
  }
341
351
  function ProvisioningWizard({
@@ -348,8 +358,12 @@ function ProvisioningWizard({
348
358
  defaultEnvironment,
349
359
  defaultConfig,
350
360
  skipToReview,
351
- onLoadStartupScripts
361
+ onLoadStartupScripts,
362
+ resourceLimits
352
363
  }) {
364
+ const cpuMax = Math.max(CPU_MIN, Math.min(resourceLimits?.cpuMax ?? CPU_MAX, CPU_MAX));
365
+ const ramMax = Math.max(RAM_MIN, Math.min(resourceLimits?.ramMaxGB ?? RAM_MAX, RAM_MAX));
366
+ const storageMax = Math.max(STORAGE_MIN, Math.min(resourceLimits?.storageMaxGB ?? STORAGE_MAX, STORAGE_MAX));
353
367
  const dc = defaultConfig;
354
368
  const [envList, setEnvList] = React2.useState(environmentsProp ?? defaultEnvironments);
355
369
  const onLoadEnvironmentsRef = React2.useRef(onLoadEnvironments);
@@ -377,9 +391,14 @@ function ProvisioningWizard({
377
391
  setSelectedEnv(effectiveDefault);
378
392
  }
379
393
  }, [envList, effectiveDefault]);
380
- const [cpuCores, setCpuCores] = React2.useState(dc?.cpuCores ?? 4);
381
- const [ramGB, setRamGB] = React2.useState(dc?.ramGB ?? 16);
382
- const [storageGB, setStorageGB] = React2.useState(dc?.storageGB ?? 128);
394
+ const [cpuCores, setCpuCores] = React2.useState(Math.min(dc?.cpuCores ?? 4, cpuMax));
395
+ const [ramGB, setRamGB] = React2.useState(Math.min(dc?.ramGB ?? 16, ramMax));
396
+ const [storageGB, setStorageGB] = React2.useState(Math.min(dc?.storageGB ?? 128, storageMax));
397
+ React2.useEffect(() => {
398
+ setCpuCores((prev) => Math.min(prev, cpuMax));
399
+ setRamGB((prev) => Math.min(prev, ramMax));
400
+ setStorageGB((prev) => Math.min(prev, storageMax));
401
+ }, [cpuMax, ramMax, storageMax]);
383
402
  const [modelTier, setModelTier] = React2.useState(dc?.modelTier ?? "claude-sonnet");
384
403
  const [systemPrompt, setSystemPrompt] = React2.useState(dc?.systemPrompt ?? "");
385
404
  const [name, setName] = React2.useState(dc?.name ?? "");
@@ -389,6 +408,7 @@ function ProvisioningWizard({
389
408
  const [bare, setBare] = React2.useState(dc?.bare ?? false);
390
409
  const [startupScriptIds, setStartupScriptIds] = React2.useState(dc?.startupScriptIds ?? []);
391
410
  const [availableScripts, setAvailableScripts] = React2.useState([]);
411
+ const [activePreset, setActivePreset] = React2.useState(null);
392
412
  const [showAdvanced, setShowAdvanced] = React2.useState(false);
393
413
  const [loadError, setLoadError] = React2.useState(null);
394
414
  const onLoadStartupScriptsRef = React2.useRef(onLoadStartupScripts);
@@ -423,12 +443,18 @@ function ProvisioningWizard({
423
443
  setIsDeploying(false);
424
444
  }
425
445
  };
426
- const applyPreset = (cpu, ram, storage) => {
427
- setCpuCores(cpu);
428
- setRamGB(ram);
429
- setStorageGB(storage);
446
+ const applyPreset = (name2, cpu, ram, storage) => {
447
+ setCpuCores(Math.min(cpu, cpuMax));
448
+ setRamGB(Math.min(ram, ramMax));
449
+ setStorageGB(Math.min(storage, storageMax));
450
+ setActivePreset(name2);
430
451
  };
431
- const hourCost = calcCost(cpuCores, ramGB);
452
+ const presets = [
453
+ { name: "Lightweight", cpu: Math.min(2, cpuMax), ram: Math.min(4, ramMax), storage: Math.min(50, storageMax) },
454
+ { name: "Standard", cpu: Math.min(4, cpuMax), ram: Math.min(16, ramMax), storage: Math.min(128, storageMax) },
455
+ { name: "Performance", cpu: Math.min(8, cpuMax), ram: Math.min(32, ramMax), storage: Math.min(256, storageMax) }
456
+ ];
457
+ const hourCost = calcCost(cpuCores, ramGB, storageGB);
432
458
  return /* @__PURE__ */ jsxs2("div", { className: cn("max-w-6xl mx-auto flex flex-col", className), children: [
433
459
  /* @__PURE__ */ jsxs2("div", { className: "mb-6 flex items-center gap-4 shrink-0", children: [
434
460
  onBack && /* @__PURE__ */ jsx2("button", { type: "button", onClick: onBack, className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-border hover:bg-muted/50 transition-colors text-foreground", children: /* @__PURE__ */ jsx2(ArrowLeft, { className: "h-5 w-5" }) }),
@@ -463,9 +489,9 @@ function ProvisioningWizard({
463
489
  onClick: () => {
464
490
  setCurrentStep(1);
465
491
  setSelectedEnv(environments[0]?.id ?? "");
466
- setCpuCores(4);
467
- setRamGB(16);
468
- setStorageGB(128);
492
+ setCpuCores(Math.min(4, cpuMax));
493
+ setRamGB(Math.min(16, ramMax));
494
+ setStorageGB(Math.min(128, storageMax));
469
495
  setModelTier("claude-sonnet");
470
496
  setSystemPrompt("");
471
497
  setName("");
@@ -474,6 +500,7 @@ function ProvisioningWizard({
474
500
  setDriver("docker");
475
501
  setBare(false);
476
502
  setStartupScriptIds([]);
503
+ setActivePreset(null);
477
504
  },
478
505
  className: "text-xs font-bold text-primary hover:text-primary/70 transition-colors",
479
506
  children: "Start from scratch"
@@ -522,25 +549,25 @@ function ProvisioningWizard({
522
549
  ] }),
523
550
  /* @__PURE__ */ jsxs2("div", { className: "mb-6", children: [
524
551
  /* @__PURE__ */ jsx2("label", { className: "block font-label text-xs font-bold uppercase tracking-widest text-muted-foreground mb-3", children: "Compute Presets" }),
525
- /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-3 gap-3", children: [
526
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(2, 4, 50), className: cn("p-3 rounded-[14px] transition-all duration-200 text-center group border", cpuCores === 2 && ramGB === 4 && storageGB === 50 ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"), children: [
527
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", cpuCores === 2 && ramGB === 4 && storageGB === 50 ? "text-primary" : "text-foreground"), children: "Lightweight" }),
528
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: "2C / 4G / 50G" })
529
- ] }),
530
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(4, 16, 128), className: cn("p-3 rounded-[14px] transition-all duration-200 text-center group border", cpuCores === 4 && ramGB === 16 && storageGB === 128 ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"), children: [
531
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", cpuCores === 4 && ramGB === 16 && storageGB === 128 ? "text-primary" : "text-foreground"), children: "Standard" }),
532
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: "4C / 16G / 128G" })
533
- ] }),
534
- /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(8, 32, 256), className: cn("p-3 rounded-[14px] transition-all duration-200 text-center group border", cpuCores === 8 && ramGB === 32 && storageGB === 256 ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"), children: [
535
- /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", cpuCores === 8 && ramGB === 32 && storageGB === 256 ? "text-primary" : "text-foreground"), children: "Performance" }),
536
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: "8C / 32G / 256G" })
537
- ] })
538
- ] })
552
+ /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-3 gap-3", children: presets.map((p) => {
553
+ const active = activePreset === p.name;
554
+ return /* @__PURE__ */ jsxs2("button", { type: "button", onClick: () => applyPreset(p.name, p.cpu, p.ram, p.storage), className: cn("p-3 rounded-[14px] transition-all duration-200 text-center group border", active ? "bg-primary/5 border-primary ring-1 ring-primary/20 shadow-sm" : "bg-card border-border hover:border-primary/30 hover:shadow-sm active:scale-[0.97]"), children: [
555
+ /* @__PURE__ */ jsx2("div", { className: cn("font-bold text-sm transition-colors duration-200", active ? "text-primary" : "text-foreground"), children: p.name }),
556
+ /* @__PURE__ */ jsxs2("div", { className: "text-xs text-muted-foreground mt-0.5 font-mono", children: [
557
+ p.cpu,
558
+ "C / ",
559
+ p.ram,
560
+ "G / ",
561
+ p.storage,
562
+ "G"
563
+ ] })
564
+ ] }, p.name);
565
+ }) })
539
566
  ] }),
540
567
  /* @__PURE__ */ jsx2("div", { className: "space-y-6", children: [
541
- { label: "Compute Cores (CPU)", value: cpuCores, setter: setCpuCores, min: CPU_MIN, max: CPU_MAX, step: 0.5, unit: "vCPUs" },
542
- { label: "Memory (RAM)", value: ramGB, setter: setRamGB, min: RAM_MIN, max: RAM_MAX, step: 1, unit: "GB" },
543
- { label: "Ephemeral Storage", value: storageGB, setter: setStorageGB, min: STORAGE_MIN, max: STORAGE_MAX, step: 8, unit: "GB" }
568
+ { label: "Compute Cores (CPU)", value: cpuCores, setter: setCpuCores, min: CPU_MIN, max: cpuMax, step: 0.5, unit: "vCPUs" },
569
+ { label: "Memory (RAM)", value: ramGB, setter: setRamGB, min: RAM_MIN, max: ramMax, step: 1, unit: "GB" },
570
+ { label: "Ephemeral Storage", value: storageGB, setter: setStorageGB, min: STORAGE_MIN, max: storageMax, step: 8, unit: "GB" }
544
571
  ].map(({ label, value, setter, min, max, step: s, unit }) => /* @__PURE__ */ jsxs2("div", { children: [
545
572
  /* @__PURE__ */ jsxs2("div", { className: "flex justify-between items-end border-b border-border pb-1.5 mb-2", children: [
546
573
  /* @__PURE__ */ jsx2("label", { className: "font-label text-xs font-bold uppercase tracking-widest text-muted-foreground", children: label }),
@@ -558,7 +585,10 @@ function ProvisioningWizard({
558
585
  max,
559
586
  step: s,
560
587
  value,
561
- onChange: (e) => setter(+e.target.value),
588
+ onChange: (e) => {
589
+ setter(+e.target.value);
590
+ setActivePreset(null);
591
+ },
562
592
  className: "w-full h-2 rounded-full appearance-none cursor-pointer accent-primary [&::-webkit-slider-runnable-track]:bg-border [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-2 [&::-moz-range-track]:bg-border [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-2 [&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:-mt-[6px] [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-primary-foreground [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110"
563
593
  }
564
594
  ),
@@ -829,7 +859,16 @@ function ProvisioningWizard({
829
859
  /* @__PURE__ */ jsx2("span", { children: "MEMORY" }),
830
860
  /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
831
861
  "$",
832
- (ramGB * 5e-3).toFixed(2)
862
+ (ramGB * 5e-3).toFixed(2),
863
+ "/h"
864
+ ] })
865
+ ] }),
866
+ /* @__PURE__ */ jsxs2("div", { className: "flex justify-between text-xs font-mono tracking-widest text-muted-foreground", children: [
867
+ /* @__PURE__ */ jsx2("span", { children: "STORAGE" }),
868
+ /* @__PURE__ */ jsxs2("span", { className: "text-foreground/80", children: [
869
+ "$",
870
+ (storageGB * 11e-4).toFixed(2),
871
+ "/h"
833
872
  ] })
834
873
  ] })
835
874
  ] })
@@ -902,16 +941,16 @@ async function fetchTiersFromApi(apiBasePath) {
902
941
  }
903
942
  var FAQ = [
904
943
  {
905
- q: "What are credits?",
906
- a: "Credits are used to pay for AI model usage. Different models consume different amounts of credits based on their complexity and the length of your requests."
944
+ q: "What is included usage?",
945
+ a: "Each plan includes a monthly USD usage balance. Compute and AI model usage are billed per second from this balance. If you exceed your included amount, overage is billed at the same rates with no penalty."
907
946
  },
908
947
  {
909
948
  q: "Can I change plans later?",
910
949
  a: "Yes. You can upgrade or downgrade your plan at any time. When you upgrade, you are charged the prorated difference. When you downgrade, the change takes effect at the end of your billing cycle."
911
950
  },
912
951
  {
913
- q: "Do unused credits roll over?",
914
- a: "Monthly credits do not roll over to the next month. However, purchased credit packs never expire and can be used at any time."
952
+ q: "Does unused balance roll over?",
953
+ a: "Monthly included usage does not roll over to the next month. Your balance resets at the start of each billing cycle."
915
954
  }
916
955
  ];
917
956
  function StandalonePricingPage({
package/dist/styles.css CHANGED
@@ -720,6 +720,9 @@
720
720
  .top-0\.5 {
721
721
  top: calc(var(--spacing) * 0.5);
722
722
  }
723
+ .top-1 {
724
+ top: calc(var(--spacing) * 1);
725
+ }
723
726
  .top-1\/2 {
724
727
  top: calc(1 / 2 * 100%);
725
728
  }
@@ -735,12 +738,18 @@
735
738
  .top-\[50\%\] {
736
739
  top: 50%;
737
740
  }
741
+ .top-full {
742
+ top: 100%;
743
+ }
738
744
  .-right-1 {
739
745
  right: calc(var(--spacing) * -1);
740
746
  }
741
747
  .right-0 {
742
748
  right: calc(var(--spacing) * 0);
743
749
  }
750
+ .right-1 {
751
+ right: calc(var(--spacing) * 1);
752
+ }
744
753
  .right-2 {
745
754
  right: calc(var(--spacing) * 2);
746
755
  }
@@ -2479,6 +2488,9 @@
2479
2488
  .py-5 {
2480
2489
  padding-block: calc(var(--spacing) * 5);
2481
2490
  }
2491
+ .py-8 {
2492
+ padding-block: calc(var(--spacing) * 8);
2493
+ }
2482
2494
  .py-12 {
2483
2495
  padding-block: calc(var(--spacing) * 12);
2484
2496
  }
@@ -3400,6 +3412,12 @@
3400
3412
  }
3401
3413
  }
3402
3414
  }
3415
+ .last\:border-0 {
3416
+ &:last-child {
3417
+ border-style: var(--tw-border-style);
3418
+ border-width: 0px;
3419
+ }
3420
+ }
3403
3421
  .focus-within\:border-\[var\(--border-accent-hover\)\] {
3404
3422
  &:focus-within {
3405
3423
  border-color: var(--border-accent-hover);
@@ -235,6 +235,19 @@ interface DashboardLayoutProps {
235
235
  railFooter?: React.ReactNode;
236
236
  /** Extra dropdown items in the profile menu */
237
237
  profileMenuItems?: React.ReactNode;
238
+ /** Notification data for the bell dropdown */
239
+ notifications?: {
240
+ items: {
241
+ id: string;
242
+ title: string;
243
+ message: string;
244
+ read: boolean;
245
+ createdAt: string;
246
+ }[];
247
+ unreadCount: number;
248
+ onMarkRead?: (id: string) => void;
249
+ onMarkAllRead?: () => void;
250
+ };
238
251
  }
239
252
  declare function DashboardLayout({ defaultPanelOpen, defaultMode, ...props }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
240
253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",