@tangle-network/sandbox-ui 0.28.0 → 0.30.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.
@@ -813,10 +813,69 @@ function RuntimePane({
813
813
  }
814
814
 
815
815
  // src/workspace/session-sidebar.tsx
816
- import { useCallback, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
816
+ import { useCallback as useCallback2, useImperativeHandle, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
817
817
  import { ArrowLeft, FolderTree, MessageSquareText, Plus, Search as Search2, Settings, Sparkles } from "lucide-react";
818
818
  import { useNavbarSessions } from "@tangle-network/ui/stores";
819
+
820
+ // src/workspace/session-optimistic.ts
821
+ import { useCallback, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
822
+ var SESSION_CREATED_EVENT = "sandbox-thread-created";
823
+ function dispatchSessionCreated(detail, eventName = SESSION_CREATED_EVENT) {
824
+ if (typeof window === "undefined") return;
825
+ window.dispatchEvent(new CustomEvent(eventName, { detail }));
826
+ }
827
+ function toItem(detail) {
828
+ return { updatedAt: /* @__PURE__ */ new Date(), ...detail };
829
+ }
830
+ function useOptimisticSessionItems(items, options = {}) {
831
+ const { listenForCreate = true, eventName = SESSION_CREATED_EVENT } = options;
832
+ const [optimisticItems, setOptimisticItems] = useState3(items);
833
+ useEffect3(() => {
834
+ setOptimisticItems(items);
835
+ }, [items]);
836
+ const add = useCallback((detail) => {
837
+ const next = toItem(detail);
838
+ setOptimisticItems((prev) => [next, ...prev.filter((item) => item.id !== next.id)]);
839
+ }, []);
840
+ const update = useCallback((id, patch) => {
841
+ setOptimisticItems(
842
+ (prev) => prev.map((item) => item.id === id ? { ...item, ...patch } : item)
843
+ );
844
+ }, []);
845
+ const remove = useCallback((id) => {
846
+ setOptimisticItems((prev) => prev.filter((item) => item.id !== id));
847
+ }, []);
848
+ const reset = useCallback(() => setOptimisticItems(items), [items]);
849
+ const addRef = useRef3(add);
850
+ addRef.current = add;
851
+ useEffect3(() => {
852
+ if (!listenForCreate || typeof window === "undefined") return;
853
+ function onCreated(event) {
854
+ const detail = event.detail;
855
+ if (!detail?.id || !detail.title) return;
856
+ addRef.current(detail);
857
+ }
858
+ window.addEventListener(eventName, onCreated);
859
+ return () => window.removeEventListener(eventName, onCreated);
860
+ }, [listenForCreate, eventName]);
861
+ return {
862
+ items: optimisticItems,
863
+ controller: { add, update, remove, reset }
864
+ };
865
+ }
866
+
867
+ // src/workspace/session-sidebar.tsx
819
868
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
869
+ var NO_CONTROLLER = {
870
+ add: () => {
871
+ },
872
+ update: () => {
873
+ },
874
+ remove: () => {
875
+ },
876
+ reset: () => {
877
+ }
878
+ };
820
879
  function statusDot(status) {
821
880
  switch (status) {
822
881
  case "running":
@@ -888,24 +947,24 @@ function navigateToHref(href) {
888
947
  window.location.assign(href);
889
948
  }
890
949
  function useResizable(defaultWidth, min, max, onChange) {
891
- const [width, setWidth] = useState3(defaultWidth);
892
- const dragging = useRef3(false);
893
- const startX = useRef3(0);
894
- const startW = useRef3(0);
895
- const onPointerDown = useCallback((e) => {
950
+ const [width, setWidth] = useState4(defaultWidth);
951
+ const dragging = useRef4(false);
952
+ const startX = useRef4(0);
953
+ const startW = useRef4(0);
954
+ const onPointerDown = useCallback2((e) => {
896
955
  dragging.current = true;
897
956
  startX.current = e.clientX;
898
957
  startW.current = width;
899
958
  e.currentTarget.setPointerCapture(e.pointerId);
900
959
  }, [width]);
901
- const onPointerMove = useCallback((e) => {
960
+ const onPointerMove = useCallback2((e) => {
902
961
  if (!dragging.current) return;
903
962
  const delta = e.clientX - startX.current;
904
963
  const next = Math.min(max, Math.max(min, startW.current + delta));
905
964
  setWidth(next);
906
965
  onChange?.(next);
907
966
  }, [min, max, onChange]);
908
- const onPointerUp = useCallback(() => {
967
+ const onPointerUp = useCallback2(() => {
909
968
  dragging.current = false;
910
969
  }, []);
911
970
  return { width, onPointerDown, onPointerMove, onPointerUp };
@@ -933,18 +992,32 @@ function SessionSidebar({
933
992
  minWidth = 200,
934
993
  maxWidth = 400,
935
994
  onWidthChange,
936
- renderItemActions
995
+ renderItemActions,
996
+ optimistic = false,
997
+ optimisticRef
937
998
  }) {
938
- const [query, setQuery] = useState3("");
939
- const [activeFilterId, setActiveFilterId] = useState3(defaultFilterId ?? filters[0]?.id ?? "all");
999
+ const [query, setQuery] = useState4("");
1000
+ const [activeFilterId, setActiveFilterId] = useState4(defaultFilterId ?? filters[0]?.id ?? "all");
1001
+ const optimisticOptions = typeof optimistic === "object" ? optimistic : void 0;
1002
+ const optimisticState = useOptimisticSessionItems(
1003
+ items,
1004
+ optimistic === false ? { listenForCreate: false } : optimisticOptions
1005
+ );
1006
+ const optimisticEnabled = optimistic !== false;
1007
+ const effectiveItems = optimisticEnabled ? optimisticState.items : items;
1008
+ useImperativeHandle(
1009
+ optimisticRef,
1010
+ () => optimisticEnabled ? optimisticState.controller : NO_CONTROLLER,
1011
+ [optimisticEnabled, optimisticState.controller]
1012
+ );
940
1013
  const activeSessions = useNavbarSessions(projectId);
941
1014
  const sessionsById = useMemo3(
942
1015
  () => new Map(activeSessions.map((session) => [session.sessionId, session])),
943
1016
  [activeSessions]
944
1017
  );
945
1018
  const orderedItems = useMemo3(
946
- () => sortItems(items, sessionsById),
947
- [items, sessionsById]
1019
+ () => sortItems(effectiveItems, sessionsById),
1020
+ [effectiveItems, sessionsById]
948
1021
  );
949
1022
  const visibleItems = useMemo3(() => {
950
1023
  const normalizedQuery = query.trim().toLowerCase();
@@ -998,7 +1071,7 @@ function SessionSidebar({
998
1071
  ] })
999
1072
  ] }) }),
1000
1073
  (enableSearch && items.length > 0 || filters.length > 0) && /* @__PURE__ */ jsxs7("div", { className: "shrink-0 border-b border-border px-3 py-2.5", children: [
1001
- enableSearch && items.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
1074
+ enableSearch && effectiveItems.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
1002
1075
  /* @__PURE__ */ jsx7(Search2, { className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
1003
1076
  /* @__PURE__ */ jsx7(
1004
1077
  "input",
@@ -1136,6 +1209,20 @@ function SessionSidebar({
1136
1209
  );
1137
1210
  }
1138
1211
 
1212
+ // src/workspace/shell-revalidation.ts
1213
+ function createShellShouldRevalidate(options = {}) {
1214
+ const appPathPrefix = options.appPathPrefix ?? "/app";
1215
+ return function shouldRevalidate(args) {
1216
+ const { currentUrl, nextUrl, formMethod, defaultShouldRevalidate } = args;
1217
+ if (formMethod && formMethod !== "GET") return true;
1218
+ if (currentUrl.pathname !== nextUrl.pathname && currentUrl.pathname.startsWith(appPathPrefix) && nextUrl.pathname.startsWith(appPathPrefix)) {
1219
+ return false;
1220
+ }
1221
+ return defaultShouldRevalidate;
1222
+ };
1223
+ }
1224
+ var shellShouldRevalidate = createShellShouldRevalidate();
1225
+
1139
1226
  // src/workspace/session-activity-monitor.tsx
1140
1227
  import { Activity, AlertCircle as AlertCircle2, LoaderCircle, MessageSquareText as MessageSquareText2 } from "lucide-react";
1141
1228
  import {
@@ -1238,7 +1325,7 @@ function SessionActivityMonitor({
1238
1325
  }
1239
1326
 
1240
1327
  // src/workspace/sandbox-workbench.tsx
1241
- import { useEffect as useEffect3, useMemo as useMemo4, useState as useState4 } from "react";
1328
+ import { useEffect as useEffect4, useMemo as useMemo4, useState as useState5 } from "react";
1242
1329
  import {
1243
1330
  Bot,
1244
1331
  Boxes,
@@ -1438,10 +1525,10 @@ function SandboxWorkbench({
1438
1525
  emptyArtifactState,
1439
1526
  className
1440
1527
  }) {
1441
- const [uncontrolledArtifactId, setUncontrolledArtifactId] = useState4(
1528
+ const [uncontrolledArtifactId, setUncontrolledArtifactId] = useState5(
1442
1529
  activeArtifactId ?? artifacts[0]?.id
1443
1530
  );
1444
- useEffect3(() => {
1531
+ useEffect4(() => {
1445
1532
  if (activeArtifactId !== void 0) return;
1446
1533
  setUncontrolledArtifactId((current) => {
1447
1534
  if (artifacts.length === 0) return void 0;
@@ -1593,7 +1680,7 @@ function AgentWorkbench(props) {
1593
1680
  }
1594
1681
 
1595
1682
  // src/workspace/audit-results.tsx
1596
- import { useState as useState5 } from "react";
1683
+ import { useState as useState6 } from "react";
1597
1684
  import { CheckCircle as CheckCircle2, XCircle, ChevronRight, Shield } from "lucide-react";
1598
1685
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1599
1686
  function AuditResults({ forms, crossFormChecks = [], overallScore, className }) {
@@ -1624,7 +1711,7 @@ function AuditResults({ forms, crossFormChecks = [], overallScore, className })
1624
1711
  ] });
1625
1712
  }
1626
1713
  function FormAuditCard({ form }) {
1627
- const [expanded, setExpanded] = useState5(form.failed > 0);
1714
+ const [expanded, setExpanded] = useState6(form.failed > 0);
1628
1715
  const allPassed = form.failed === 0 && form.found;
1629
1716
  return /* @__PURE__ */ jsxs10("div", { className: "rounded-[var(--radius-md)] border border-border overflow-hidden", children: [
1630
1717
  /* @__PURE__ */ jsxs10(
@@ -1762,7 +1849,7 @@ function TaskBoard({
1762
1849
  }
1763
1850
 
1764
1851
  // src/workspace/calendar-view.tsx
1765
- import { useState as useState6, useMemo as useMemo6 } from "react";
1852
+ import { useState as useState7, useMemo as useMemo6 } from "react";
1766
1853
  import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1767
1854
  function toDateKey(d) {
1768
1855
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
@@ -1806,11 +1893,11 @@ function CalendarView({
1806
1893
  }) {
1807
1894
  const today = /* @__PURE__ */ new Date();
1808
1895
  const todayKey = toDateKey(today);
1809
- const [internalMonth, setInternalMonth] = useState6({
1896
+ const [internalMonth, setInternalMonth] = useState7({
1810
1897
  year: today.getFullYear(),
1811
1898
  month: today.getMonth()
1812
1899
  });
1813
- const [internalSelectedDay, setInternalSelectedDay] = useState6(
1900
+ const [internalSelectedDay, setInternalSelectedDay] = useState7(
1814
1901
  todayKey
1815
1902
  );
1816
1903
  const viewYear = controlledYear ?? internalMonth.year;
@@ -1983,7 +2070,7 @@ function CalendarView({
1983
2070
  }
1984
2071
 
1985
2072
  // src/workspace/approval-queue.tsx
1986
- import { useState as useState7, useMemo as useMemo7 } from "react";
2073
+ import { useState as useState8, useMemo as useMemo7 } from "react";
1987
2074
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1988
2075
  function ApprovalQueue({
1989
2076
  items,
@@ -1998,9 +2085,9 @@ function ApprovalQueue({
1998
2085
  emptyState,
1999
2086
  showResolved: controlledShowResolved
2000
2087
  }) {
2001
- const [rejectingId, setRejectingId] = useState7(null);
2002
- const [rejectReason, setRejectReason] = useState7("");
2003
- const [showResolved, setShowResolved] = useState7(controlledShowResolved ?? false);
2088
+ const [rejectingId, setRejectingId] = useState8(null);
2089
+ const [rejectReason, setRejectReason] = useState8("");
2090
+ const [showResolved, setShowResolved] = useState8(controlledShowResolved ?? false);
2004
2091
  const pending = useMemo7(
2005
2092
  () => items.filter((a) => a.status === "pending"),
2006
2093
  [items]
@@ -2209,7 +2296,12 @@ export {
2209
2296
  StatusBar,
2210
2297
  TerminalPanel,
2211
2298
  RuntimePane,
2299
+ SESSION_CREATED_EVENT,
2300
+ dispatchSessionCreated,
2301
+ useOptimisticSessionItems,
2212
2302
  SessionSidebar,
2303
+ createShellShouldRevalidate,
2304
+ shellShouldRevalidate,
2213
2305
  SessionActivityMonitor,
2214
2306
  SandboxWorkbench,
2215
2307
  AgentWorkbench,
@@ -24,11 +24,11 @@ import * as React from "react";
24
24
  import { jsx } from "react/jsx-runtime";
25
25
  var PANEL_OPEN_KEY = "sandbox-sidebar-panel-open";
26
26
  var SIDEBAR_MODE_KEY = "sandbox-sidebar-mode";
27
- var SIDEBAR_RAIL_WIDTH = 64;
28
- var SIDEBAR_PANEL_WIDTH = 260;
27
+ var SIDEBAR_RAIL_WIDTH = 56;
28
+ var SIDEBAR_PANEL_WIDTH = 268;
29
29
  var SIDEBAR_TOTAL_WIDTH = SIDEBAR_RAIL_WIDTH + SIDEBAR_PANEL_WIDTH;
30
30
  var SIDEBAR_MOBILE_WIDTH = 256;
31
- var SIDEBAR_RAIL_LABELED_WIDTH = 224;
31
+ var SIDEBAR_RAIL_LABELED_WIDTH = 248;
32
32
  var SidebarContext = React.createContext(null);
33
33
  function readStorage(key, fallback) {
34
34
  if (typeof window === "undefined") return fallback;
@@ -168,8 +168,8 @@ function RailThemeToggle({ className }) {
168
168
  "aria-label": "Toggle theme",
169
169
  title: isDark ? "Switch to light mode" : "Switch to dark mode",
170
170
  className: cn(
171
- "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors",
172
- "hover:bg-[var(--accent-surface-soft)] hover:text-[var(--accent-text)]",
171
+ "flex h-9 w-9 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors",
172
+ "hover:bg-[var(--accent-surface-soft)] hover:text-foreground",
173
173
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
174
174
  className
175
175
  ),
@@ -222,9 +222,9 @@ function RailSeparator({ className }) {
222
222
  }
223
223
  function RailButton({ icon: Icon, label, isActive, badge, onClick, className, showLabel, asChild, children }) {
224
224
  const classes = cn(
225
- "group relative flex shrink-0 items-center justify-center rounded-xl transition-all duration-200",
226
- showLabel ? "w-full justify-start px-3 h-11 gap-3" : "w-11 h-11 justify-center",
227
- "hover:bg-[var(--accent-surface-soft)] hover:text-[var(--accent-text)]",
225
+ "group relative flex shrink-0 items-center justify-center rounded-md transition-all duration-200",
226
+ showLabel ? "w-full justify-start px-2.5 h-9 gap-2.5" : "w-9 h-9 justify-center",
227
+ "hover:bg-[var(--accent-surface-soft)] hover:text-foreground",
228
228
  "active:scale-95",
229
229
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
230
230
  isActive && "bg-[var(--accent-surface-strong)] text-[var(--accent-text)]",
@@ -232,8 +232,8 @@ function RailButton({ icon: Icon, label, isActive, badge, onClick, className, sh
232
232
  className
233
233
  );
234
234
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
235
- /* @__PURE__ */ jsx2(Icon, { className: "h-5 w-5 shrink-0" }),
236
- showLabel && /* @__PURE__ */ jsx2("span", { className: "text-sm font-medium", children: label }),
235
+ /* @__PURE__ */ jsx2(Icon, { className: "h-[17px] w-[17px] shrink-0" }),
236
+ showLabel && /* @__PURE__ */ jsx2("span", { className: "text-[13.5px] font-medium", children: label }),
237
237
  badge !== void 0 && badge > 0 && /* @__PURE__ */ jsx2("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary text-[9px] font-bold text-[var(--md3-on-primary)] px-1 shadow-sm", children: badge > 99 ? "99+" : badge })
238
238
  ] });
239
239
  if (asChild && React2.isValidElement(children)) {
@@ -273,9 +273,9 @@ function RailFlyout({
273
273
  };
274
274
  }, [open]);
275
275
  const triggerClasses = cn(
276
- "group relative flex shrink-0 items-center rounded-xl transition-all duration-200",
277
- showLabel ? "w-full justify-start px-3 h-11 gap-3" : "w-11 h-11 justify-center",
278
- "hover:bg-[var(--accent-surface-soft)] hover:text-[var(--accent-text)]",
276
+ "group relative flex shrink-0 items-center rounded-md transition-all duration-200",
277
+ showLabel ? "w-full justify-start px-2.5 h-9 gap-2.5" : "w-9 h-9 justify-center",
278
+ "hover:bg-[var(--accent-surface-soft)] hover:text-foreground",
279
279
  "active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
280
280
  isActive || open ? "bg-[var(--accent-surface-strong)] text-[var(--accent-text)]" : "text-muted-foreground",
281
281
  className
@@ -317,7 +317,7 @@ function RailFlyout({
317
317
  if (el.closest("a,button")) setOpen(false);
318
318
  },
319
319
  className: cn(
320
- "absolute top-0 z-50 min-w-[12rem] rounded-xl border border-border bg-card p-1.5 shadow-lg",
320
+ "absolute top-0 z-50 min-w-[12rem] rounded-md border border-border bg-popover p-1.5 shadow-dropdown",
321
321
  showLabel ? "left-full ml-2" : "left-full ml-2"
322
322
  ),
323
323
  children: [
@@ -356,10 +356,10 @@ function SidebarPanel({ children, className }) {
356
356
  {
357
357
  className: cn(
358
358
  "transition-[opacity] duration-150 h-full overflow-hidden border-l border-border bg-card",
359
- panelOpen ? "w-[260px] opacity-100" : "w-0 opacity-0 pointer-events-none",
359
+ panelOpen ? "w-[268px] opacity-100" : "w-0 opacity-0 pointer-events-none",
360
360
  className
361
361
  ),
362
- children: /* @__PURE__ */ jsx2("div", { className: "flex flex-col h-full w-[260px]", children })
362
+ children: /* @__PURE__ */ jsx2("div", { className: "flex flex-col h-full w-[268px]", children })
363
363
  }
364
364
  );
365
365
  }
@@ -140,9 +140,9 @@ interface ProfileAvatarProps {
140
140
  }
141
141
  declare function ProfileAvatar({ user, isLoading, onLogout, onSettingsClick, settingsHref, children, className, showDetails, LinkComponent, }: ProfileAvatarProps): react_jsx_runtime.JSX.Element;
142
142
 
143
- /** Width constants (px) matching blueprint-agent layout */
144
- declare const SIDEBAR_RAIL_WIDTH = 64;
145
- declare const SIDEBAR_PANEL_WIDTH = 260;
143
+ /** Width constants (px) — Tangle Quiet density (ChatGPT-minimal) */
144
+ declare const SIDEBAR_RAIL_WIDTH = 56;
145
+ declare const SIDEBAR_PANEL_WIDTH = 268;
146
146
  declare const SIDEBAR_TOTAL_WIDTH: number;
147
147
  /**
148
148
  * Width of the mobile drawer when the rail is rendered with labels
package/dist/dashboard.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  canAdminSandbox,
49
49
  parseInsufficientBalance,
50
50
  useSidebar
51
- } from "./chunk-F2M5MN7K.js";
51
+ } from "./chunk-YPBFIETY.js";
52
52
  import {
53
53
  BillingDashboard,
54
54
  InfoPanel,