@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.
package/dist/tokens.css CHANGED
@@ -45,25 +45,25 @@
45
45
  --md3-inverse-on-surface: #000000;
46
46
  --md3-surface-tint: #818CF8;
47
47
 
48
- /* Semantic HSL bridge */
49
- --hsl-background: 244 28% 5%;
50
- --hsl-foreground: 240 18% 94%;
51
- --hsl-card: 246 30% 11%;
52
- --hsl-card-foreground: 240 18% 94%;
53
- --hsl-popover: 246 28% 14%;
54
- --hsl-popover-foreground: 240 18% 94%;
48
+ /* Semantic HSL bridge — flat neutral spine (Tangle Quiet) */
49
+ --hsl-background: 240 8% 5%; /* #0b0b0d */
50
+ --hsl-foreground: 240 6% 93%; /* #ececee */
51
+ --hsl-card: 240 5% 8%; /* #141416 surface */
52
+ --hsl-card-foreground: 240 6% 93%;
53
+ --hsl-popover: 240 4% 13%; /* #202023 elevated */
54
+ --hsl-popover-foreground: 240 6% 93%;
55
55
  --hsl-primary: 239 84% 67%;
56
56
  --hsl-primary-foreground: 0 0% 100%;
57
- --hsl-secondary: 248 24% 18%;
58
- --hsl-secondary-foreground: 240 16% 90%;
59
- --hsl-muted: 246 22% 15%;
60
- --hsl-muted-foreground: 240 14% 58%;
61
- --hsl-accent: 248 26% 20%;
62
- --hsl-accent-foreground: 240 16% 92%;
57
+ --hsl-secondary: 240 5% 11%; /* #1a1a1d surface-2 */
58
+ --hsl-secondary-foreground: 240 6% 93%;
59
+ --hsl-muted: 240 5% 11%; /* #1a1a1d — neutral hover well */
60
+ --hsl-muted-foreground: 240 4% 62%; /* #9a9aa2 text-muted */
61
+ --hsl-accent: 240 5% 11%; /* neutral hover (indigo lives in accent-surface-strong) */
62
+ --hsl-accent-foreground: 240 6% 93%;
63
63
  --hsl-destructive: 348 90% 60%;
64
64
  --hsl-destructive-foreground: 0 0% 100%;
65
- --hsl-border: 244 14% 22%;
66
- --hsl-input: 246 28% 14%;
65
+ --hsl-border: 240 3% 13%; /* hairline ≈ rgba(255,255,255,0.07) */
66
+ --hsl-input: 240 5% 11%;
67
67
  --hsl-ring: 239 84% 67%;
68
68
  --hsl-success: 160 84% 39%;
69
69
  --hsl-warning: 40 94% 56%;
@@ -94,21 +94,21 @@
94
94
  --error: var(--hsl-destructive);
95
95
  --info: var(--hsl-info);
96
96
 
97
- /* Sidebar tokens */
98
- --sidebar-background: 244 28% 5%;
99
- --sidebar-foreground: 240 18% 94%;
97
+ /* Sidebar tokens — flat surface + hairline */
98
+ --sidebar-background: 240 5% 8%; /* #141416 surface */
99
+ --sidebar-foreground: 240 6% 93%;
100
100
  --sidebar-primary: 239 84% 67%;
101
101
  --sidebar-primary-foreground: 0 0% 100%;
102
- --sidebar-accent: 248 26% 20%;
103
- --sidebar-accent-foreground: 240 16% 92%;
104
- --sidebar-border: 244 14% 17%;
102
+ --sidebar-accent: 240 5% 11%; /* neutral hover */
103
+ --sidebar-accent-foreground: 240 6% 93%;
104
+ --sidebar-border: 240 3% 13%; /* hairline */
105
105
  --sidebar-ring: 239 84% 67%;
106
106
 
107
- /* Depth scale — Tangle deep purple surfaces */
108
- --depth-1: #0C0B1D;
109
- --depth-2: #141328;
110
- --depth-3: #1D1B38;
111
- --depth-4: #262448;
107
+ /* Depth scale — flat neutral spine (Tangle Quiet) */
108
+ --depth-1: #0b0b0d;
109
+ --depth-2: #141416;
110
+ --depth-3: #1a1a1d;
111
+ --depth-4: #202023;
112
112
 
113
113
  /* Status colors */
114
114
  --status-running: #10B981;
@@ -152,36 +152,35 @@
152
152
  --glass-border-hover: var(--border-default);
153
153
  --glass-blur: 0px;
154
154
 
155
- /* Mesh gradient — Tangle brand glows */
156
- --mesh-teal: rgba(16, 185, 129, 0.05);
157
- --mesh-violet: rgba(99, 102, 241, 0.07);
158
- --mesh-blue: rgba(129, 140, 248, 0.04);
155
+ /* Mesh glows — quieted to off; chrome is flat in Tangle Quiet */
156
+ --mesh-teal: transparent;
157
+ --mesh-violet: transparent;
158
+ --mesh-blue: transparent;
159
159
 
160
- /* Brand gradients — Tangle */
160
+ /* Brand gradients — the indigo spark, kept ONLY for the logo mark + send button */
161
161
  --tangle-gradient: linear-gradient(135deg, #6366F1, #818CF8);
162
162
  --tangle-gradient-text: linear-gradient(135deg, #A5AAFC, #818CF8);
163
163
  --accent-gradient-strong: linear-gradient(135deg, #6366F1, #818CF8);
164
- --accent-surface-soft: rgba(99, 102, 241, 0.12);
165
- --accent-surface-strong: rgba(99, 102, 241, 0.20);
166
- --accent-text: #A5AAFC;
167
-
168
- /* Surfaces */
169
- --bg-root: radial-gradient(ellipse 70% 50% at 20% 0%, rgba(99, 102, 241, 0.08), transparent),
170
- radial-gradient(ellipse 50% 40% at 80% 10%, rgba(129, 140, 248, 0.06), transparent),
171
- linear-gradient(180deg, var(--depth-1), #0D0C1E 100%);
164
+ /* Accent wells — soft = hover neutral, strong = active/selected row (indigo) */
165
+ --accent-surface-soft: rgba(255, 255, 255, 0.04);
166
+ --accent-surface-strong: rgba(129, 140, 248, 0.14);
167
+ --accent-text: #b9bffb;
168
+
169
+ /* Surfaces — flat single-neutral (no radial mesh) */
170
+ --bg-root: #0b0b0d;
172
171
  --bg-dark: var(--depth-1);
173
- --bg-card: hsl(var(--hsl-card));
174
- --bg-elevated: hsl(246 28% 17%);
175
- --bg-section: hsl(246 28% 9%);
176
- --bg-input: hsl(var(--hsl-input));
177
- --bg-hover: rgba(99, 102, 241, 0.08);
178
- --bg-selection: rgba(99, 102, 241, 0.18);
179
-
180
- /* Text */
181
- --text-primary: hsl(var(--hsl-foreground));
182
- --text-secondary: hsl(240 16% 85%);
183
- --text-muted: #8688B0;
184
- --text-dim: #606294;
172
+ --bg-card: var(--depth-2);
173
+ --bg-elevated: var(--depth-4);
174
+ --bg-section: var(--depth-1);
175
+ --bg-input: var(--depth-3);
176
+ --bg-hover: rgba(255, 255, 255, 0.04);
177
+ --bg-selection: rgba(129, 140, 248, 0.14);
178
+
179
+ /* Text — neutral */
180
+ --text-primary: #ececee;
181
+ --text-secondary: #c8c8cd;
182
+ --text-muted: #9a9aa2;
183
+ --text-dim: #6b6b73;
185
184
 
186
185
  /* Brand accents — Tangle */
187
186
  --brand-primary: #6366F1;
@@ -195,12 +194,12 @@
195
194
  --brand-vibrant: #6366F1;
196
195
  --brand-emerald: #10B981;
197
196
 
198
- /* Borders */
199
- --border-subtle: rgba(100, 100, 148, 0.10);
200
- --border-default: rgba(100, 100, 148, 0.18);
201
- --border-hover: rgba(100, 100, 148, 0.28);
202
- --border-accent: rgba(99, 102, 241, 0.18);
203
- --border-accent-hover: rgba(99, 102, 241, 0.35);
197
+ /* Borders — neutral hairlines */
198
+ --border-subtle: rgba(255, 255, 255, 0.05);
199
+ --border-default: rgba(255, 255, 255, 0.07);
200
+ --border-hover: rgba(255, 255, 255, 0.12);
201
+ --border-accent: rgba(129, 140, 248, 0.20);
202
+ --border-accent-hover: rgba(129, 140, 248, 0.35);
204
203
 
205
204
  /* Buttons */
206
205
  --btn-primary-bg: #6366F1;
@@ -218,11 +217,11 @@
218
217
  --code-comment: #606294;
219
218
  --code-error: #FF4D6D;
220
219
 
221
- /* Shadows */
222
- --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04);
223
- --shadow-dropdown: 0 8px 32px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.06);
224
- --shadow-glow: 0 0 32px rgba(99, 102, 241, 0.18);
225
- --shadow-accent: 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(99, 102, 241, 0.2);
220
+ /* Shadows — flat, no inner ring, no indigo glow */
221
+ --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3);
222
+ --shadow-dropdown: 0 8px 30px rgba(0, 0, 0, 0.45);
223
+ --shadow-glow: none;
224
+ --shadow-accent: 0 1px 2px rgba(0, 0, 0, 0.3);
226
225
  --shadow-status-running: 0 0 6px rgba(16, 185, 129, 0.5);
227
226
  --shadow-status-creating: 0 0 6px rgba(99, 102, 241, 0.5);
228
227
  --shadow-status-error: 0 0 6px rgba(255, 77, 109, 0.5);
@@ -230,11 +229,12 @@
230
229
  /* Radii */
231
230
  --radius-sm: 6px;
232
231
  --radius-md: 8px;
233
- --radius-lg: 12px;
234
- --radius-xl: 16px;
232
+ --radius-lg: 10px;
233
+ --radius-xl: 12px; /* chrome stays tight (was 16px) */
235
234
  --radius-pill: 999px;
236
235
  --radius-full: 999px;
237
- --radius: 0.625rem;
236
+ --radius-composer: 26px; /* centered composer pill */
237
+ --radius: 0.5rem; /* 8px chrome default (was 10px) */
238
238
 
239
239
  /* Density */
240
240
  --control-height: 2.25rem;
@@ -249,8 +249,8 @@
249
249
  --font-size-lg: 1rem; /* 16px — emphasized text */
250
250
  --font-size-xl: 1.25rem; /* 20px — section headers */
251
251
  --line-height-tight: 1.3;
252
- --line-height-base: 1.75; /* matches leading-7 */
253
- --line-height-relaxed: 1.8;
252
+ --line-height-base: 1.6; /* tighter body rhythm (was 1.75) */
253
+ --line-height-relaxed: 1.75;
254
254
 
255
255
  /* Component spacing — override per-app for tighter/looser layouts */
256
256
  --avatar-size: 2rem; /* 32px — chat avatars */
@@ -284,9 +284,9 @@
284
284
  --transition-default: 0.16s ease;
285
285
  --transition-medium: 0.24s ease;
286
286
 
287
- /* Fonts */
288
- --font-sans: "Geist", "DM Sans", ui-sans-serif, system-ui, sans-serif;
289
- --font-display: "Outfit", "Geist", sans-serif;
287
+ /* Fonts — unified on Inter (dark + light) */
288
+ --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
289
+ --font-display: "Inter", "Geist", sans-serif;
290
290
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
291
291
  }
292
292
 
@@ -336,34 +336,34 @@
336
336
  --md3-inverse-on-surface: #f0f1f4;
337
337
  --md3-surface-tint: #6366F1;
338
338
 
339
- /* Semantic HSL bridge */
340
- --hsl-background: 225 20% 97%;
341
- --hsl-foreground: 228 20% 12%;
342
- --hsl-card: 0 0% 100%;
343
- --hsl-card-foreground: 228 20% 12%;
339
+ /* Semantic HSL bridge — flat neutral light (Tangle Quiet) */
340
+ --hsl-background: 0 0% 100%; /* #ffffff */
341
+ --hsl-foreground: 0 0% 5%; /* #0d0d0d */
342
+ --hsl-card: 240 7% 97%; /* #f7f7f8 surface */
343
+ --hsl-card-foreground: 0 0% 5%;
344
344
  --hsl-popover: 0 0% 100%;
345
- --hsl-popover-foreground: 228 20% 12%;
346
- --hsl-primary: 239 84% 67%;
345
+ --hsl-popover-foreground: 0 0% 5%;
346
+ --hsl-primary: 245 62% 57%; /* #5B4ED4 light accent */
347
347
  --hsl-primary-foreground: 0 0% 100%;
348
- --hsl-secondary: 242 22% 42%;
349
- --hsl-secondary-foreground: 0 0% 100%;
350
- --hsl-muted: 240 14% 89%;
351
- --hsl-muted-foreground: 240 10% 36%;
352
- --hsl-accent: 248 22% 86%;
353
- --hsl-accent-foreground: 248 38% 30%;
348
+ --hsl-secondary: 240 6% 93%; /* #ececee surface-2 */
349
+ --hsl-secondary-foreground: 0 0% 10%;
350
+ --hsl-muted: 240 6% 93%; /* #ececee neutral hover */
351
+ --hsl-muted-foreground: 240 5% 38%; /* #5d5d66 text-muted */
352
+ --hsl-accent: 240 6% 93%; /* neutral hover */
353
+ --hsl-accent-foreground: 0 0% 10%;
354
354
  --hsl-destructive: 0 72% 41%;
355
355
  --hsl-destructive-foreground: 0 0% 100%;
356
- --hsl-border: 244 14% 78%;
357
- --hsl-input: 244 12% 80%;
358
- --hsl-ring: 239 84% 67%;
356
+ --hsl-border: 240 6% 89%; /* hairline ≈ rgba(0,0,0,0.08) */
357
+ --hsl-input: 240 6% 89%;
358
+ --hsl-ring: 245 62% 57%;
359
359
  --hsl-success: 160 84% 30%;
360
360
  --hsl-warning: 41 96% 50%;
361
361
  --hsl-info: 212 80% 50%;
362
362
 
363
- --depth-1: #f4f4f9;
364
- --depth-2: #edecf4;
365
- --depth-3: #e5e4ee;
366
- --depth-4: #dcdbe8;
363
+ --depth-1: #ffffff;
364
+ --depth-2: #f7f7f8;
365
+ --depth-3: #ececee;
366
+ --depth-4: #e3e3e6;
367
367
 
368
368
  --status-running: #059669;
369
369
  --status-creating: #3E349E;
@@ -404,33 +404,34 @@
404
404
  --surface-neutral-border: var(--border-subtle);
405
405
  --surface-neutral-text: var(--text-muted);
406
406
 
407
- /* Sidebar — light */
408
- --sidebar-background: 232 18% 96%;
409
- --sidebar-foreground: 228 20% 12%;
410
- --sidebar-primary: 239 84% 67%;
407
+ /* Sidebar — light, flat surface + hairline */
408
+ --sidebar-background: 240 7% 97%; /* #f7f7f8 surface */
409
+ --sidebar-foreground: 0 0% 5%;
410
+ --sidebar-primary: 245 62% 57%; /* #5B4ED4 */
411
411
  --sidebar-primary-foreground: 0 0% 100%;
412
- --sidebar-accent: 240 18% 90%;
413
- --sidebar-accent-foreground: 240 38% 30%;
414
- --sidebar-border: 244 14% 78%;
415
- --sidebar-ring: 239 84% 67%;
416
-
417
- --mesh-teal: rgba(5, 150, 105, 0.04);
418
- --mesh-violet: rgba(99, 102, 241, 0.03);
419
- --mesh-blue: rgba(129, 140, 248, 0.03);
420
-
421
- --bg-root: #f4f4f9;
422
- --bg-dark: #edecf4;
423
- --bg-card: #ffffff;
424
- --bg-elevated: #e5e4ee;
425
- --bg-section: #edecf4;
426
- --bg-input: #edecf4;
427
- --bg-hover: rgba(99, 102, 241, 0.05);
428
- --bg-selection: rgba(99, 102, 241, 0.10);
429
-
430
- --text-primary: #191c24;
431
- --text-secondary: #3a3a50;
432
- --text-muted: #5E5E7A;
433
- --text-dim: #A0A0B8;
412
+ --sidebar-accent: 240 6% 93%; /* neutral hover */
413
+ --sidebar-accent-foreground: 0 0% 10%;
414
+ --sidebar-border: 240 6% 89%; /* hairline */
415
+ --sidebar-ring: 245 62% 57%;
416
+
417
+ --mesh-teal: transparent;
418
+ --mesh-violet: transparent;
419
+ --mesh-blue: transparent;
420
+
421
+ /* Surfaces — flat single-neutral light */
422
+ --bg-root: #ffffff;
423
+ --bg-dark: #f7f7f8;
424
+ --bg-card: #f7f7f8;
425
+ --bg-elevated: #ffffff;
426
+ --bg-section: #f7f7f8;
427
+ --bg-input: #ececee;
428
+ --bg-hover: rgba(0, 0, 0, 0.04);
429
+ --bg-selection: rgba(91, 78, 212, 0.10);
430
+
431
+ --text-primary: #0d0d0d;
432
+ --text-secondary: #2a2a30;
433
+ --text-muted: #5d5d66;
434
+ --text-dim: #8e8e98;
434
435
 
435
436
  --brand-primary: #6366F1;
436
437
  --brand-strong: #2E2A5E;
@@ -443,16 +444,16 @@
443
444
  --brand-vibrant: #6366F1;
444
445
  --brand-emerald: #059669;
445
446
 
446
- --border-subtle: rgba(110, 108, 148, 0.20);
447
- --border-default: rgba(110, 108, 148, 0.35);
448
- --border-hover: rgba(110, 108, 148, 0.50);
449
- --border-accent: rgba(99, 102, 241, 0.15);
450
- --border-accent-hover: rgba(99, 102, 241, 0.30);
447
+ --border-subtle: rgba(0, 0, 0, 0.06);
448
+ --border-default: rgba(0, 0, 0, 0.08);
449
+ --border-hover: rgba(0, 0, 0, 0.13);
450
+ --border-accent: rgba(91, 78, 212, 0.18);
451
+ --border-accent-hover: rgba(91, 78, 212, 0.35);
451
452
 
452
- --btn-primary-bg: #6366F1;
453
+ --btn-primary-bg: #5B4ED4;
453
454
  --btn-primary-hover: #4F46E5;
454
455
  --btn-primary-text: #ffffff;
455
- --btn-cta-bg: #6366F1;
456
+ --btn-cta-bg: #5B4ED4;
456
457
  --btn-cta-text: #ffffff;
457
458
 
458
459
  --code-keyword: #3E349E;
@@ -463,30 +464,33 @@
463
464
  --code-comment: #6B6E8A;
464
465
  --code-error: #ba1a1a;
465
466
 
466
- --shadow-card: 0 1px 3px rgba(25, 28, 36, 0.05), 0 0 0 1px rgba(200, 198, 218, 0.22);
467
- --shadow-dropdown: 0 8px 32px rgba(25, 28, 36, 0.10), 0 0 0 1px rgba(200, 198, 218, 0.28);
467
+ /* Shadows — flat, no inner ring, no glow */
468
+ --shadow-card: 0 1px 2px rgba(25, 28, 36, 0.06);
469
+ --shadow-dropdown: 0 8px 30px rgba(25, 28, 36, 0.10);
468
470
  --shadow-glow: none;
469
- --shadow-accent: 0 1px 3px rgba(25, 28, 36, 0.05), 0 0 0 1px rgba(99, 102, 241, 0.15);
470
-
471
- --tangle-gradient: linear-gradient(135deg, #6366F1, #818CF8);
472
- --tangle-gradient-text: linear-gradient(135deg, #4F46E5, #6366F1);
473
- --accent-gradient-strong: linear-gradient(135deg, #6366F1, #818CF8);
474
- --accent-surface-soft: rgba(99, 102, 241, 0.06);
475
- --accent-surface-strong: rgba(99, 102, 241, 0.12);
471
+ --shadow-accent: 0 1px 2px rgba(25, 28, 36, 0.06);
472
+
473
+ /* Indigo spark kept for logo + send; accent-fill is the active row well */
474
+ --tangle-gradient: linear-gradient(135deg, #4F46E5, #5B4ED4);
475
+ --tangle-gradient-text: linear-gradient(135deg, #4F46E5, #5B4ED4);
476
+ --accent-gradient-strong: linear-gradient(135deg, #4F46E5, #5B4ED4);
477
+ --accent-surface-soft: rgba(0, 0, 0, 0.04);
478
+ --accent-surface-strong: rgba(91, 78, 212, 0.10);
476
479
  --accent-text: #4F46E5;
477
480
 
478
- /* Typography — Manrope (headlines) + Inter (body/labels) */
479
- --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, sans-serif;
480
- --font-display: "Manrope", "Inter", sans-serif;
481
+ /* Typography — unified on Inter */
482
+ --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
483
+ --font-display: "Inter", "Geist", sans-serif;
481
484
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
482
485
 
483
- /* Tighter border radius for enterprise precision */
484
- --radius: 0.375rem;
485
- --radius-sm: 2px;
486
- --radius-md: 4px;
487
- --radius-lg: 6px;
488
- --radius-xl: 8px;
486
+ /* Radius — unified chrome scale (matches dark; 8px md) */
487
+ --radius: 0.5rem;
488
+ --radius-sm: 6px;
489
+ --radius-md: 8px;
490
+ --radius-lg: 10px;
491
+ --radius-xl: 12px;
489
492
  --radius-pill: 999px;
490
493
  --radius-full: 999px;
494
+ --radius-composer: 26px;
491
495
  }
492
496
 
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, Ref } from 'react';
3
3
  export { ArtifactPane, ArtifactPaneProps } from '@tangle-network/ui/primitives';
4
4
  import { FileNode, FileTreeVisibilityOptions, FileTabData } from '@tangle-network/ui/files';
5
5
  import { SessionProjectKey, ActiveSessionStatus, ActiveSessionRecord } from '@tangle-network/ui/stores';
@@ -160,6 +160,67 @@ interface RuntimePaneProps {
160
160
  */
161
161
  declare function RuntimePane({ title, subtitle, statusBanner, statusBar, content, terminal, audit, inspector, footer, className, }: RuntimePaneProps): react_jsx_runtime.JSX.Element;
162
162
 
163
+ /**
164
+ * Optimistic session/thread list: reflect a just-created / renamed / deleted
165
+ * item in the sidebar INSTANTLY, before the server loader round-trips.
166
+ *
167
+ * The shell loader is the source of truth and reconciles in the background
168
+ * (typically the app calls `useRevalidator().revalidate()` after its mutation
169
+ * fetch settles). Until that lands, a local mirror of the loader items carries
170
+ * the optimistic edit so a delete / rename / new-thread does not feel broken
171
+ * for the duration of a D1 round-trip.
172
+ *
173
+ * Two ways to drive it, usable together:
174
+ * 1. A window `CustomEvent` (default name {@link SESSION_CREATED_EVENT}) — the
175
+ * surface that creates a thread (often a different route from the sidebar's
176
+ * route) dispatches it; the sidebar prepends the item with no prop wiring.
177
+ * 2. The imperative {@link SessionOptimisticController} returned here — the
178
+ * sidebar's own action handlers (rename / delete / pin) call it directly.
179
+ */
180
+ /** Default window-event name for "a session/thread was just created". */
181
+ declare const SESSION_CREATED_EVENT = "sandbox-thread-created";
182
+ /**
183
+ * Payload of a {@link SESSION_CREATED_EVENT}. Only `id` + `title` are required;
184
+ * everything else mirrors {@link SessionSidebarItem}. `updatedAt` defaults to
185
+ * now so the new item sorts to the top.
186
+ */
187
+ interface SessionCreatedDetail extends Partial<Omit<SessionSidebarItem, "id" | "title">> {
188
+ id: string;
189
+ title: string;
190
+ }
191
+ /** Dispatch the shared "session created" event so any mounted sidebar updates. */
192
+ declare function dispatchSessionCreated(detail: SessionCreatedDetail, eventName?: string): void;
193
+ /** Imperative handle for driving optimistic edits from a mutation handler. */
194
+ interface SessionOptimisticController {
195
+ /** Prepend (or move to top) an item — call before the create fetch settles. */
196
+ add: (item: SessionCreatedDetail) => void;
197
+ /** Patch an existing item in place — e.g. a rename or category change. */
198
+ update: (id: string, patch: Partial<SessionSidebarItem>) => void;
199
+ /** Drop an item — call before the delete fetch settles. */
200
+ remove: (id: string) => void;
201
+ /** Discard all optimistic edits and snap back to the loader items. */
202
+ reset: () => void;
203
+ }
204
+ interface UseOptimisticSessionItemsOptions {
205
+ /**
206
+ * Listen for the shared window create-event and prepend its item. Set `false`
207
+ * to drive optimistic state purely through the imperative controller.
208
+ * Defaults to `true`.
209
+ */
210
+ listenForCreate?: boolean;
211
+ /** Override the window event name to listen for. */
212
+ eventName?: string;
213
+ }
214
+ /**
215
+ * Mirror `items` with optimistic add/update/remove on top. The mirror re-syncs
216
+ * whenever `items` changes (the loader is authoritative), so an optimistic edit
217
+ * is naturally superseded once the server result arrives.
218
+ */
219
+ declare function useOptimisticSessionItems(items: SessionSidebarItem[], options?: UseOptimisticSessionItemsOptions): {
220
+ items: SessionSidebarItem[];
221
+ controller: SessionOptimisticController;
222
+ };
223
+
163
224
  interface SessionSidebarItem {
164
225
  id: string;
165
226
  title: string;
@@ -218,8 +279,97 @@ interface SessionSidebarProps {
218
279
  session: ActiveSessionRecord | null;
219
280
  isActive: boolean;
220
281
  }) => ReactNode;
282
+ /**
283
+ * Opt into optimistic item updates: the sidebar keeps a local mirror of
284
+ * `items` so a just-created / renamed / deleted thread shows instantly,
285
+ * before the loader round-trips. Pass `true` for defaults, or an options
286
+ * object to tune the window create-event. Off by default — existing callers
287
+ * render `items` verbatim and are unaffected.
288
+ */
289
+ optimistic?: boolean | UseOptimisticSessionItemsOptions;
290
+ /**
291
+ * Ref to the imperative optimistic controller (`add` / `update` / `remove` /
292
+ * `reset`). Drive optimistic edits from the consumer's own rename/delete/pin
293
+ * handlers. Only populated when `optimistic` is enabled.
294
+ */
295
+ optimisticRef?: Ref<SessionOptimisticController | null>;
221
296
  }
222
- declare function SessionSidebar({ title, subtitle, projectId, items, currentItemId, createLabel, onCreate, onSelectItem, onSelectLink, links, className, emptyMessage, searchPlaceholder, enableSearch, activityMonitor, filters, defaultFilterId, resizable, defaultWidth, minWidth, maxWidth, onWidthChange, renderItemActions, }: SessionSidebarProps): react_jsx_runtime.JSX.Element;
297
+ declare function SessionSidebar({ title, subtitle, projectId, items, currentItemId, createLabel, onCreate, onSelectItem, onSelectLink, links, className, emptyMessage, searchPlaceholder, enableSearch, activityMonitor, filters, defaultFilterId, resizable, defaultWidth, minWidth, maxWidth, onWidthChange, renderItemActions, optimistic, optimisticRef, }: SessionSidebarProps): react_jsx_runtime.JSX.Element;
298
+
299
+ /**
300
+ * Snappy in-app navigation: a shared `shouldRevalidate` rule for the agent-app
301
+ * shell route(s) that own the workspace-list / sidebar loader.
302
+ *
303
+ * The shell loader (user + workspace list, sidebar state) runs on every
304
+ * navigation, but its data does not change just by moving between in-app pages.
305
+ * Refetching it on every internal GET nav adds a loader round-trip to each
306
+ * click — on Cloudflare Workers + D1 (HTTP-proxied) that is a visible stall.
307
+ *
308
+ * This helper encodes the rule that makes nav feel instant WITHOUT stranding
309
+ * freshly-mutated data:
310
+ *
311
+ * - Non-GET navigation (form submit / action) → always revalidate.
312
+ * - Internal app→app navigation that CHANGES the path → skip revalidation.
313
+ * - Imperative `useRevalidator().revalidate()` (which targets the SAME url,
314
+ * so `currentUrl.pathname === nextUrl.pathname`) → revalidate. Creating a
315
+ * project / thread typically goes through a raw `fetch`, so a same-url
316
+ * revalidate is the ONLY way the new row reaches the shell loader; the
317
+ * same-path carve-out below is what keeps that working. Suppressing it is
318
+ * the project-switcher regression — do not "simplify" it away.
319
+ * - Anything else (cross-path-prefix nav, entering/leaving the app) →
320
+ * defer to `defaultShouldRevalidate`.
321
+ *
322
+ * Framework-agnostic by design: sandbox-ui does not depend on react-router, so
323
+ * this takes a plain args object. The shape is a structural subset of
324
+ * react-router's `ShouldRevalidateFunctionArgs`, so an app can wire it as a
325
+ * route export with no adapter:
326
+ *
327
+ * ```ts
328
+ * // app/routes/app.tsx
329
+ * import { shellShouldRevalidate } from "@tangle-network/sandbox-ui/workspace";
330
+ * export const shouldRevalidate = shellShouldRevalidate;
331
+ * ```
332
+ *
333
+ * `shouldRevalidate` MUST be a per-app react-router route export — it cannot be
334
+ * exported FROM a library on the app's behalf — so the "out of the box" win is
335
+ * the one-line re-export above instead of every app re-deriving the rule (and
336
+ * re-introducing the same-url bug).
337
+ */
338
+ /**
339
+ * The subset of react-router's `ShouldRevalidateFunctionArgs` this rule reads.
340
+ * Kept structural so callers can pass the full react-router args object as-is.
341
+ */
342
+ interface ShellShouldRevalidateArgs {
343
+ currentUrl: URL;
344
+ nextUrl: URL;
345
+ formMethod?: string;
346
+ defaultShouldRevalidate: boolean;
347
+ }
348
+ interface ShellShouldRevalidateOptions {
349
+ /**
350
+ * Path prefix that marks "inside the app shell". Internal navigation between
351
+ * two paths under this prefix skips the shell-loader refetch. Defaults to
352
+ * `"/app"`.
353
+ */
354
+ appPathPrefix?: string;
355
+ }
356
+ /**
357
+ * Build a `shouldRevalidate` function for the shell route under `appPathPrefix`.
358
+ * Use this when the app mounts under a non-default prefix; otherwise prefer the
359
+ * ready-made {@link shellShouldRevalidate} export.
360
+ */
361
+ declare function createShellShouldRevalidate(options?: ShellShouldRevalidateOptions): (args: ShellShouldRevalidateArgs) => boolean;
362
+ /**
363
+ * Ready-made shell `shouldRevalidate` for apps mounted under `/app`. Wire it as
364
+ * a one-line route export:
365
+ *
366
+ * ```ts
367
+ * export const shouldRevalidate = shellShouldRevalidate;
368
+ * ```
369
+ *
370
+ * For a non-`/app` prefix, use {@link createShellShouldRevalidate}.
371
+ */
372
+ declare const shellShouldRevalidate: (args: ShellShouldRevalidateArgs) => boolean;
223
373
 
224
374
  interface SessionActivityMonitorProps {
225
375
  className?: string;
@@ -478,4 +628,4 @@ interface ApprovalQueueProps {
478
628
  */
479
629
  declare function ApprovalQueue({ items, className, onApprove, onReject, renderItemDetail, renderTypeBadge, renderStats, canResolve, header, emptyState, showResolved: controlledShowResolved, }: ApprovalQueueProps): react_jsx_runtime.JSX.Element;
480
630
 
481
- export { AgentWorkbench, type ApprovalConfidenceStat, type ApprovalItem, ApprovalQueue, type ApprovalQueueProps, type AuditCheck, AuditResults, type AuditResultsProps, type BannerType, type CalendarEvent, CalendarView, type CalendarViewProps, type ContextBadge, DirectoryPane, type DirectoryPaneProps, type FormAudit, RuntimePane, type RuntimePaneProps, SandboxWorkbench, type SandboxWorkbenchArtifact, type SandboxWorkbenchCustomArtifact, type SandboxWorkbenchFileArtifact, type SandboxWorkbenchLayoutOptions, type SandboxWorkbenchMarkdownArtifact, type SandboxWorkbenchOpenUIArtifact, type SandboxWorkbenchProps, type SandboxWorkbenchSessionProps, SessionActivityMonitor, type SessionActivityMonitorProps, SessionSidebar, type SessionSidebarBadge, type SessionSidebarFilter, type SessionSidebarItem, type SessionSidebarLink, type SessionSidebarProps, StatusBanner, type StatusBannerProps, StatusBar, type StatusBarProps, TaskBoard, type TaskBoardColumn, type TaskBoardItem, type TaskBoardProps, type TerminalLine, TerminalPanel, type TerminalProps, WorkspaceLayout, type WorkspaceLayoutProps };
631
+ export { AgentWorkbench, type ApprovalConfidenceStat, type ApprovalItem, ApprovalQueue, type ApprovalQueueProps, type AuditCheck, AuditResults, type AuditResultsProps, type BannerType, type CalendarEvent, CalendarView, type CalendarViewProps, type ContextBadge, DirectoryPane, type DirectoryPaneProps, type FormAudit, RuntimePane, type RuntimePaneProps, SESSION_CREATED_EVENT, SandboxWorkbench, type SandboxWorkbenchArtifact, type SandboxWorkbenchCustomArtifact, type SandboxWorkbenchFileArtifact, type SandboxWorkbenchLayoutOptions, type SandboxWorkbenchMarkdownArtifact, type SandboxWorkbenchOpenUIArtifact, type SandboxWorkbenchProps, type SandboxWorkbenchSessionProps, SessionActivityMonitor, type SessionActivityMonitorProps, type SessionCreatedDetail, type SessionOptimisticController, SessionSidebar, type SessionSidebarBadge, type SessionSidebarFilter, type SessionSidebarItem, type SessionSidebarLink, type SessionSidebarProps, type ShellShouldRevalidateArgs, type ShellShouldRevalidateOptions, StatusBanner, type StatusBannerProps, StatusBar, type StatusBarProps, TaskBoard, type TaskBoardColumn, type TaskBoardItem, type TaskBoardProps, type TerminalLine, TerminalPanel, type TerminalProps, type UseOptimisticSessionItemsOptions, WorkspaceLayout, type WorkspaceLayoutProps, createShellShouldRevalidate, dispatchSessionCreated, shellShouldRevalidate, useOptimisticSessionItems };
package/dist/workspace.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  CalendarView,
7
7
  DirectoryPane,
8
8
  RuntimePane,
9
+ SESSION_CREATED_EVENT,
9
10
  SandboxWorkbench,
10
11
  SessionActivityMonitor,
11
12
  SessionSidebar,
@@ -13,8 +14,12 @@ import {
13
14
  StatusBar,
14
15
  TaskBoard,
15
16
  TerminalPanel,
16
- WorkspaceLayout
17
- } from "./chunk-I3IVCGND.js";
17
+ WorkspaceLayout,
18
+ createShellShouldRevalidate,
19
+ dispatchSessionCreated,
20
+ shellShouldRevalidate,
21
+ useOptimisticSessionItems
22
+ } from "./chunk-AUFV7766.js";
18
23
  import "./chunk-EI44GEQ5.js";
19
24
  export {
20
25
  AgentWorkbench,
@@ -24,6 +29,7 @@ export {
24
29
  CalendarView,
25
30
  DirectoryPane,
26
31
  RuntimePane,
32
+ SESSION_CREATED_EVENT,
27
33
  SandboxWorkbench,
28
34
  SessionActivityMonitor,
29
35
  SessionSidebar,
@@ -31,5 +37,9 @@ export {
31
37
  StatusBar,
32
38
  TaskBoard,
33
39
  TerminalPanel,
34
- WorkspaceLayout
40
+ WorkspaceLayout,
41
+ createShellShouldRevalidate,
42
+ dispatchSessionCreated,
43
+ shellShouldRevalidate,
44
+ useOptimisticSessionItems
35
45
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.28.0",
3
+ "version": "0.30.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",
@@ -114,7 +114,7 @@
114
114
  "test:bridge": "pnpm build && vitest run src/__tests__/re-export-identity.test.ts"
115
115
  },
116
116
  "peerDependencies": {
117
- "@tangle-network/brand": "^0.3.0",
117
+ "@tangle-network/brand": "^0.4.0",
118
118
  "@tangle-network/ui": "^1.0.1",
119
119
  "react": "^18 || ^19",
120
120
  "react-dom": "^18 || ^19"
@@ -161,7 +161,7 @@
161
161
  "@storybook/react-vite": "^8.6.18",
162
162
  "@tailwindcss/postcss": "^4.2.2",
163
163
  "@tailwindcss/vite": "^4.2.2",
164
- "@tangle-network/brand": "^0.3.0",
164
+ "@tangle-network/brand": "^0.4.0",
165
165
  "@tangle-network/ui": "^1.0.1",
166
166
  "@tanstack/react-query": "^5.91.0",
167
167
  "@testing-library/dom": "^10.4.1",