@tangle-network/sandbox-ui 0.23.3 → 0.24.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/chat.d.ts CHANGED
@@ -5,7 +5,16 @@ import * as React from 'react';
5
5
  import { M as ModelInfo } from './model-picker-DUfMTQo5.js';
6
6
  import { e as HarnessType } from './harness-picker-ppDe7ap-.js';
7
7
 
8
- type ReasoningLevel = "auto" | "low" | "medium" | "high";
8
+ /**
9
+ * Thinking-effort ladder — the superset of the per-harness reasoning scales so
10
+ * one control covers every backend. Consumers map each level onto what the
11
+ * chosen harness/model supports (and degrade unsupported ones):
12
+ * - OpenAI / Codex reasoning_effort: minimal · low · medium · high · xhigh
13
+ * - Anthropic (Claude) extended thinking: … · high · max
14
+ * - Claude Code: … · high · max · ultracode
15
+ * - generic: low · medium · high
16
+ */
17
+ type ReasoningLevel = "auto" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultracode";
9
18
  interface ReasoningLevelOption {
10
19
  value: ReasoningLevel;
11
20
  label: string;
package/dist/chat.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  modelProvider,
17
17
  snapHarnessToModel,
18
18
  snapModelToHarness
19
- } from "./chunk-H5PBSY62.js";
19
+ } from "./chunk-KANKBACI.js";
20
20
  import "./chunk-HXIYUQN2.js";
21
21
  import "./chunk-JDMX4HHN.js";
22
22
  import "./chunk-EI44GEQ5.js";
@@ -3,10 +3,11 @@ import { useState, useEffect, useRef, useCallback } from "react";
3
3
  function createEmptyBatch() {
4
4
  return { data: "", waiters: [] };
5
5
  }
6
- var WS_OPEN_TIMEOUT_MS = 1500;
6
+ var WS_OPEN_TIMEOUT_MS = 1e4;
7
7
  function toWsUrl(apiUrl, sessionId) {
8
8
  try {
9
- const url = new URL(`${apiUrl}/terminals/${sessionId}/ws`);
9
+ const base = typeof window !== "undefined" ? window.location.href : void 0;
10
+ const url = new URL(`${apiUrl}/terminals/${sessionId}/ws`, base);
10
11
  if (url.protocol === "https:") url.protocol = "wss:";
11
12
  else if (url.protocol === "http:") url.protocol = "ws:";
12
13
  else return null;
@@ -26,25 +27,32 @@ function toBearerSubprotocol(token) {
26
27
  }
27
28
  return `${BEARER_SUBPROTOCOL_PREFIX}${encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
28
29
  }
29
- var stdinEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
30
- function encodeStdin(text) {
31
- if (!stdinEncoder) {
32
- throw new Error(
33
- "TextEncoder is unavailable; WebSocket transport cannot encode stdin"
34
- );
30
+ var DEFAULT_TERMINAL_COLS = 80;
31
+ var DEFAULT_TERMINAL_ROWS = 24;
32
+ function createTerminalConnectionId() {
33
+ const cryptoApi = globalThis.crypto;
34
+ if (cryptoApi && typeof cryptoApi.randomUUID === "function") {
35
+ return `terminal-${cryptoApi.randomUUID()}`;
35
36
  }
36
- return stdinEncoder.encode(text);
37
+ return `terminal-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
37
38
  }
38
- function usePtySession({ apiUrl, token, onData }) {
39
+ function usePtySession({ apiUrl, token, onData, connectionId: providedConnectionId }) {
39
40
  const [isConnected, setIsConnected] = useState(false);
40
41
  const [error, setError] = useState(null);
41
42
  const sessionIdRef = useRef(null);
43
+ const connectGenRef = useRef(0);
42
44
  const abortRef = useRef(null);
43
45
  const retryTimerRef = useRef(void 0);
44
46
  const retryCountRef = useRef(0);
45
47
  const mountedRef = useRef(true);
46
48
  const onDataRef = useRef(onData);
49
+ const tokenRef = useRef(token);
47
50
  const connectStreamRef = useRef(null);
51
+ const reconnectRef = useRef(null);
52
+ const shouldDeleteSessionRef = useRef(false);
53
+ const transportReadyRef = useRef(false);
54
+ const colsRef = useRef(DEFAULT_TERMINAL_COLS);
55
+ const rowsRef = useRef(DEFAULT_TERMINAL_ROWS);
48
56
  const wsRef = useRef(null);
49
57
  const pendingWsRef = useRef(null);
50
58
  const pendingBatchRef = useRef(createEmptyBatch());
@@ -96,22 +104,27 @@ function usePtySession({ apiUrl, token, onData }) {
96
104
  inputAbortRef.current.abort();
97
105
  inputAbortRef.current = null;
98
106
  }
107
+ transportReadyRef.current = false;
99
108
  if (sessionIdRef.current) {
100
109
  const sid = sessionIdRef.current;
110
+ const shouldDeleteSession = shouldDeleteSessionRef.current;
101
111
  sessionIdRef.current = null;
102
- fetch(`${apiUrl}/terminals/${sid}`, {
103
- method: "DELETE",
104
- headers: { Authorization: `Bearer ${token}` },
105
- credentials: "include"
106
- }).catch(() => {
107
- });
112
+ shouldDeleteSessionRef.current = false;
113
+ if (shouldDeleteSession) {
114
+ fetch(`${apiUrl}/terminals/${sid}`, {
115
+ method: "DELETE",
116
+ headers: { Authorization: `Bearer ${tokenRef.current}` },
117
+ credentials: "include"
118
+ }).catch(() => {
119
+ });
120
+ }
108
121
  }
109
122
  rejectPendingInput("Terminal session is not connected");
110
123
  setIsConnected(false);
111
- }, [apiUrl, token, abortStream, closeWs, rejectPendingInput]);
112
- const connectWs = useCallback((sessionId) => {
124
+ }, [apiUrl, abortStream, closeWs, rejectPendingInput]);
125
+ const connectWs = useCallback((sessionId, options = {}) => {
113
126
  return new Promise((resolve) => {
114
- if (typeof WebSocket === "undefined" || stdinEncoder === null) {
127
+ if (typeof WebSocket === "undefined") {
115
128
  resolve(false);
116
129
  return;
117
130
  }
@@ -120,7 +133,7 @@ function usePtySession({ apiUrl, token, onData }) {
120
133
  resolve(false);
121
134
  return;
122
135
  }
123
- const subprotocol = toBearerSubprotocol(token);
136
+ const subprotocol = toBearerSubprotocol(tokenRef.current);
124
137
  if (!subprotocol) {
125
138
  resolve(false);
126
139
  return;
@@ -161,7 +174,24 @@ function usePtySession({ apiUrl, token, onData }) {
161
174
  return;
162
175
  }
163
176
  pendingWsRef.current = null;
177
+ if (options.initOnOpen) {
178
+ try {
179
+ ws.send(JSON.stringify({
180
+ type: "init",
181
+ cols: colsRef.current,
182
+ rows: rowsRef.current
183
+ }));
184
+ } catch {
185
+ try {
186
+ ws.close();
187
+ } catch {
188
+ }
189
+ settle(false);
190
+ return;
191
+ }
192
+ }
164
193
  wsRef.current = ws;
194
+ transportReadyRef.current = true;
165
195
  setIsConnected(true);
166
196
  setError(null);
167
197
  retryCountRef.current = 0;
@@ -184,6 +214,24 @@ function usePtySession({ apiUrl, token, onData }) {
184
214
  });
185
215
  return;
186
216
  }
217
+ if (typeof data === "string") {
218
+ try {
219
+ const event = JSON.parse(text);
220
+ if (event?.type === "ready") {
221
+ return;
222
+ }
223
+ if (event?.type === "error") {
224
+ const message = typeof event.message === "string" ? event.message : "Terminal WebSocket error";
225
+ setError(message);
226
+ return;
227
+ }
228
+ if (event?.type === "exit") {
229
+ setIsConnected(false);
230
+ return;
231
+ }
232
+ } catch {
233
+ }
234
+ }
187
235
  if (text) onDataRef.current(text);
188
236
  };
189
237
  ws.onerror = () => {
@@ -195,6 +243,7 @@ function usePtySession({ apiUrl, token, onData }) {
195
243
  const wasActive = wsRef.current === ws;
196
244
  if (wasActive) {
197
245
  wsRef.current = null;
246
+ transportReadyRef.current = false;
198
247
  }
199
248
  if (!opened) {
200
249
  settle(false);
@@ -205,13 +254,17 @@ function usePtySession({ apiUrl, token, onData }) {
205
254
  if (sessionIdRef.current) {
206
255
  retryTimerRef.current = setTimeout(() => {
207
256
  if (mountedRef.current && sessionIdRef.current) {
208
- connectStreamRef.current?.(sessionIdRef.current);
257
+ if (shouldDeleteSessionRef.current) {
258
+ connectStreamRef.current?.(sessionIdRef.current);
259
+ } else {
260
+ reconnectRef.current?.();
261
+ }
209
262
  }
210
263
  }, 1e3);
211
264
  }
212
265
  };
213
266
  });
214
- }, [apiUrl, token]);
267
+ }, [apiUrl]);
215
268
  const connectStream = useCallback(async (sessionId) => {
216
269
  abortStream();
217
270
  setError(null);
@@ -219,7 +272,7 @@ function usePtySession({ apiUrl, token, onData }) {
219
272
  const controller = new AbortController();
220
273
  abortRef.current = controller;
221
274
  const streamRes = await fetch(`${apiUrl}/terminals/${sessionId}/stream`, {
222
- headers: { Authorization: `Bearer ${token}` },
275
+ headers: { Authorization: `Bearer ${tokenRef.current}` },
223
276
  credentials: "include",
224
277
  signal: controller.signal
225
278
  });
@@ -229,9 +282,11 @@ function usePtySession({ apiUrl, token, onData }) {
229
282
  throw err;
230
283
  }
231
284
  if (mountedRef.current) {
285
+ transportReadyRef.current = true;
232
286
  setIsConnected(true);
233
287
  setError(null);
234
288
  retryCountRef.current = 0;
289
+ ensureDrainRunningRef.current?.();
235
290
  }
236
291
  const reader = streamRes.body.getReader();
237
292
  const decoder = new TextDecoder();
@@ -266,6 +321,7 @@ function usePtySession({ apiUrl, token, onData }) {
266
321
  }
267
322
  }
268
323
  if (mountedRef.current) {
324
+ transportReadyRef.current = false;
269
325
  setIsConnected(false);
270
326
  retryTimerRef.current = setTimeout(() => {
271
327
  if (mountedRef.current && sessionIdRef.current) {
@@ -278,6 +334,7 @@ function usePtySession({ apiUrl, token, onData }) {
278
334
  if (mountedRef.current) {
279
335
  const message = err instanceof Error ? err.message : "Stream connection failed";
280
336
  setError(message);
337
+ transportReadyRef.current = false;
281
338
  setIsConnected(false);
282
339
  const httpStatus = err.httpStatus;
283
340
  const is4xx = httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500;
@@ -293,18 +350,38 @@ function usePtySession({ apiUrl, token, onData }) {
293
350
  }
294
351
  }
295
352
  }
296
- }, [apiUrl, token, abortStream]);
353
+ }, [apiUrl, abortStream]);
297
354
  onDataRef.current = onData;
355
+ tokenRef.current = token;
298
356
  connectStreamRef.current = connectStream;
299
357
  const connect = useCallback(async () => {
300
358
  cleanup();
359
+ const myGen = ++connectGenRef.current;
301
360
  retryCountRef.current = 0;
302
361
  setError(null);
303
362
  try {
363
+ const connectionId = providedConnectionId ?? createTerminalConnectionId();
364
+ if (!mountedRef.current) return;
365
+ sessionIdRef.current = connectionId;
366
+ shouldDeleteSessionRef.current = false;
367
+ transportReadyRef.current = false;
368
+ inputAbortRef.current = new AbortController();
369
+ const directWsOk = await connectWs(connectionId, { initOnOpen: true });
370
+ if (!mountedRef.current || connectGenRef.current !== myGen) return;
371
+ ensureDrainRunningRef.current?.();
372
+ if (directWsOk) {
373
+ return;
374
+ }
375
+ sessionIdRef.current = null;
376
+ transportReadyRef.current = false;
377
+ if (inputAbortRef.current) {
378
+ inputAbortRef.current.abort();
379
+ inputAbortRef.current = null;
380
+ }
304
381
  const res = await fetch(`${apiUrl}/terminals`, {
305
382
  method: "POST",
306
383
  headers: {
307
- Authorization: `Bearer ${token}`,
384
+ Authorization: `Bearer ${tokenRef.current}`,
308
385
  "Content-Type": "application/json"
309
386
  },
310
387
  credentials: "include"
@@ -317,9 +394,11 @@ function usePtySession({ apiUrl, token, onData }) {
317
394
  if (!sessionId) throw new Error("No sessionId in response");
318
395
  if (!mountedRef.current) return;
319
396
  sessionIdRef.current = sessionId;
397
+ shouldDeleteSessionRef.current = true;
398
+ transportReadyRef.current = false;
320
399
  inputAbortRef.current = new AbortController();
321
- const wsOk = await connectWs(sessionId);
322
- if (!mountedRef.current || sessionIdRef.current !== sessionId) return;
400
+ const wsOk = await connectWs(sessionId, { initOnOpen: true });
401
+ if (!mountedRef.current || connectGenRef.current !== myGen) return;
323
402
  ensureDrainRunningRef.current?.();
324
403
  if (wsOk) {
325
404
  return;
@@ -330,11 +409,15 @@ function usePtySession({ apiUrl, token, onData }) {
330
409
  if (mountedRef.current) {
331
410
  const message = err instanceof Error ? err.message : "Terminal connection failed";
332
411
  setError(message);
412
+ transportReadyRef.current = false;
333
413
  setIsConnected(false);
334
414
  }
335
415
  }
336
- }, [apiUrl, token, cleanup, connectWs, connectStream]);
416
+ }, [apiUrl, providedConnectionId, cleanup, connectWs, connectStream]);
417
+ reconnectRef.current = connect;
337
418
  const resizeTerminal = useCallback(async (cols, rows) => {
419
+ if (cols > 0) colsRef.current = cols;
420
+ if (rows > 0) rowsRef.current = rows;
338
421
  const sid = sessionIdRef.current;
339
422
  if (!sid || cols <= 0 || rows <= 0) return;
340
423
  const ws = wsRef.current;
@@ -350,7 +433,7 @@ function usePtySession({ apiUrl, token, onData }) {
350
433
  const res = await fetch(`${apiUrl}/terminals/${sid}`, {
351
434
  method: "PATCH",
352
435
  headers: {
353
- Authorization: `Bearer ${token}`,
436
+ Authorization: `Bearer ${tokenRef.current}`,
354
437
  "Content-Type": "application/json"
355
438
  },
356
439
  credentials: "include",
@@ -362,11 +445,11 @@ function usePtySession({ apiUrl, token, onData }) {
362
445
  } catch (err) {
363
446
  console.error("Failed to resize terminal", err);
364
447
  }
365
- }, [apiUrl, token]);
448
+ }, [apiUrl]);
366
449
  const drainInputQueue = useCallback(async () => {
367
450
  while (pendingBatchRef.current.data.length > 0) {
368
451
  const sid = sessionIdRef.current;
369
- if (!sid) {
452
+ if (!sid || !transportReadyRef.current) {
370
453
  return;
371
454
  }
372
455
  const batch = pendingBatchRef.current;
@@ -374,7 +457,7 @@ function usePtySession({ apiUrl, token, onData }) {
374
457
  const ws = wsRef.current;
375
458
  if (ws && ws.readyState === WebSocket.OPEN) {
376
459
  try {
377
- ws.send(encodeStdin(batch.data));
460
+ ws.send(JSON.stringify({ type: "input", data: batch.data }));
378
461
  for (const w of batch.waiters) w.resolve();
379
462
  } catch (err) {
380
463
  for (const w of batch.waiters) w.reject(err);
@@ -385,7 +468,7 @@ function usePtySession({ apiUrl, token, onData }) {
385
468
  const res = await fetch(`${apiUrl}/terminals/${sid}/input`, {
386
469
  method: "POST",
387
470
  headers: {
388
- Authorization: `Bearer ${token}`,
471
+ Authorization: `Bearer ${tokenRef.current}`,
389
472
  "Content-Type": "application/json"
390
473
  },
391
474
  credentials: "include",
@@ -404,11 +487,11 @@ function usePtySession({ apiUrl, token, onData }) {
404
487
  for (const w of batch.waiters) w.reject(rejection);
405
488
  }
406
489
  }
407
- }, [apiUrl, token]);
490
+ }, [apiUrl]);
408
491
  const ensureDrainRunning = useCallback(() => {
409
492
  if (drainPromiseRef.current) return;
410
493
  const run = () => drainInputQueue().finally(() => {
411
- if (pendingBatchRef.current.data.length > 0 && sessionIdRef.current) {
494
+ if (pendingBatchRef.current.data.length > 0 && sessionIdRef.current && transportReadyRef.current) {
412
495
  drainPromiseRef.current = run();
413
496
  } else {
414
497
  drainPromiseRef.current = null;
@@ -25,30 +25,36 @@ import {
25
25
  import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
26
26
  import { Brain, ChevronDown, Sparkles } from "lucide-react";
27
27
  import { jsx, jsxs } from "react/jsx-runtime";
28
- var INTENSITY_BARS = {
29
- low: 1,
30
- medium: 2,
31
- high: 3
32
- };
28
+ var REASONING_LADDER = [
29
+ "minimal",
30
+ "low",
31
+ "medium",
32
+ "high",
33
+ "xhigh",
34
+ "max",
35
+ "ultracode"
36
+ ];
33
37
  function ReasoningGlyph({ level }) {
34
38
  if (level === "auto") {
35
39
  return /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-muted-foreground" });
36
40
  }
37
- const filled = INTENSITY_BARS[level];
41
+ const BARS = 4;
42
+ const rank = REASONING_LADDER.indexOf(level) + 1;
43
+ const filled = Math.max(1, Math.ceil(rank / REASONING_LADDER.length * BARS));
38
44
  return /* @__PURE__ */ jsx(
39
45
  "span",
40
46
  {
41
47
  "aria-hidden": true,
42
48
  className: "inline-flex h-3.5 items-end gap-px",
43
- style: { width: 14 },
44
- children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
49
+ style: { width: 16 },
50
+ children: Array.from({ length: BARS }).map((_, i) => /* @__PURE__ */ jsx(
45
51
  "span",
46
52
  {
47
53
  className: cn(
48
54
  "w-1 rounded-[1px]",
49
55
  i < filled ? "bg-foreground" : "bg-border"
50
56
  ),
51
- style: { height: `${40 + i * 30}%` }
57
+ style: { height: `${35 + i * 22}%` }
52
58
  },
53
59
  i
54
60
  ))
@@ -57,9 +63,13 @@ function ReasoningGlyph({ level }) {
57
63
  }
58
64
  var DEFAULT_REASONING_LEVEL_OPTIONS = [
59
65
  { value: "auto", label: "Auto", description: "Let the agent pick the right depth." },
66
+ { value: "minimal", label: "Minimal", description: "Almost no deliberation \u2014 fastest, cheapest." },
60
67
  { value: "low", label: "Low", description: "Fast, direct answers." },
61
68
  { value: "medium", label: "Medium", description: "Inspect context before acting." },
62
- { value: "high", label: "High", description: "Deeper planning and edge-case checks." }
69
+ { value: "high", label: "High", description: "Deeper planning and edge-case checks." },
70
+ { value: "xhigh", label: "Extra High", description: "Extended reasoning for hard problems (Codex/OpenAI)." },
71
+ { value: "max", label: "Max", description: "Maximum extended thinking budget (Claude)." },
72
+ { value: "ultracode", label: "Ultracode", description: "Exhaustive multi-pass reasoning (Claude Code)." }
63
73
  ];
64
74
  function ReasoningLevelPicker({
65
75
  value,
package/dist/hooks.d.ts CHANGED
@@ -13,6 +13,14 @@ interface UsePtySessionOptions {
13
13
  token: string;
14
14
  /** Called with raw PTY output (may contain ANSI escape codes). */
15
15
  onData: (data: string) => void;
16
+ /**
17
+ * Stable id identifying this terminal connection to the sidecar.
18
+ * Reused on every connect/reconnect so the sidecar restores the same
19
+ * PTY session (within its reconnect window) instead of spawning a
20
+ * fresh shell. When omitted, a random id is generated per connect —
21
+ * so the session does not survive a remount.
22
+ */
23
+ connectionId?: string;
16
24
  }
17
25
  interface UsePtySessionReturn {
18
26
  /** Whether the underlying transport is connected and receiving data. */
@@ -30,24 +38,26 @@ interface UsePtySessionReturn {
30
38
  * Manages a PTY session against the sidecar terminal API.
31
39
  *
32
40
  * Transport:
33
- * 1. POST /terminals creates the session.
34
- * 2. The hook tries WebSocket: GET /terminals/:id/ws (Upgrade).
35
- * - Server → client: TEXT frames carrying raw PTY output.
36
- * - Client → server: BINARY frames carrying stdin (UTF-8); TEXT
37
- * frames carrying JSON control messages (`{"type":"resize",...}`).
38
- * 3. If the WS does not reach OPEN within WS_OPEN_TIMEOUT_MS, or it
39
- * errors before opening, the hook falls back to SSE+POST:
41
+ * 1. Try the current sidecar WebSocket contract directly:
42
+ * GET /terminals/:id/ws, then send `{"type":"init",...}`.
43
+ * - Server → client: BINARY frames carrying PTY output; TEXT frames
44
+ * carrying lifecycle/control messages.
45
+ * - Client → server: TEXT frames carrying JSON input/resize messages.
46
+ * 2. If direct WS does not reach OPEN within WS_OPEN_TIMEOUT_MS, or it
47
+ * errors before opening, fall back to the older terminal contract:
48
+ * - POST /terminals creates the session.
49
+ * - GET /terminals/:id/ws tries the older WS transport.
40
50
  * - GET /terminals/:id/stream (SSE for output)
41
51
  * - POST /terminals/:id/input (one batched POST at a time)
42
52
  * - PATCH /terminals/:id (resize)
43
- * 4. DELETE /terminals/:id closes the session (both transports).
53
+ * 3. DELETE /terminals/:id closes sessions created by the older REST API.
44
54
  *
45
55
  * The WS path eliminates the per-keystroke HTTP round-trip that
46
56
  * dominates typing latency through edge proxies; the HTTP+SSE path is
47
57
  * preserved as a fallback so the hook keeps working against
48
58
  * deployments that have not yet shipped the WS endpoint.
49
59
  */
50
- declare function usePtySession({ apiUrl, token, onData }: UsePtySessionOptions): UsePtySessionReturn;
60
+ declare function usePtySession({ apiUrl, token, onData, connectionId: providedConnectionId }: UsePtySessionOptions): UsePtySessionReturn;
51
61
 
52
62
  /**
53
63
  * Sandbox-level telemetry collected by the sidecar from cgroup v2
package/dist/hooks.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-SOKKTB7W.js";
21
21
  import {
22
22
  usePtySession
23
- } from "./chunk-AG7QDC2Q.js";
23
+ } from "./chunk-77WVVJA4.js";
24
24
  import {
25
25
  useSessionStream,
26
26
  useSidecarAuth
package/dist/index.js CHANGED
@@ -81,7 +81,7 @@ import {
81
81
  } from "./chunk-SOKKTB7W.js";
82
82
  import {
83
83
  usePtySession
84
- } from "./chunk-AG7QDC2Q.js";
84
+ } from "./chunk-77WVVJA4.js";
85
85
  import {
86
86
  useSessionStream,
87
87
  useSidecarAuth
@@ -104,7 +104,7 @@ import {
104
104
  modelProvider,
105
105
  snapHarnessToModel,
106
106
  snapModelToHarness
107
- } from "./chunk-H5PBSY62.js";
107
+ } from "./chunk-KANKBACI.js";
108
108
  import {
109
109
  ExpandedToolDetail,
110
110
  InlineThinkingItem,
package/dist/pages.d.ts CHANGED
@@ -1,8 +1,62 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { c as BillingSubscription, B as BillingBalance, d as BillingUsage, j as UsageDataPoint, f as PricingTier, g as TemplateCardData } from './template-card-UhV3pmRC.js';
3
2
  import * as React from 'react';
3
+ import { CSSProperties, ReactNode } from 'react';
4
+ import { c as BillingSubscription, B as BillingBalance, d as BillingUsage, j as UsageDataPoint, f as PricingTier, g as TemplateCardData } from './template-card-UhV3pmRC.js';
4
5
  export { M as ModelInfo } from './model-picker-DUfMTQo5.js';
5
6
 
7
+ /**
8
+ * Shared, self-contained sign-in / sign-up page for every Tangle vertical app.
9
+ *
10
+ * Ships its OWN palette (scoped CSS custom properties on the card) so it renders
11
+ * identically in every app regardless of that app's theme tokens — the bug that
12
+ * made per-app hand-rolled logins drift (e.g. a Tangle button rendering
13
+ * dark-on-dark when an app didn't load the expected token sheet). Everything is
14
+ * overridable via props for per-app customization (`accent`, `providers`, copy,
15
+ * `className`/`style`).
16
+ *
17
+ * Each app's login route becomes a thin wrapper:
18
+ * export default () => <AuthPage product="Legal" tangleAuthUrl="/auth/tangle/start" />
19
+ */
20
+ type SocialProvider = "github" | "google";
21
+ interface AuthPageProps {
22
+ /** Suffix after the Tangle wordmark in the lockup, e.g. "Legal", "Tax". */
23
+ product?: string;
24
+ /** Sub-headline under the logo lockup. */
25
+ tagline?: string;
26
+ /** "signin" (default) or "signup" — flips copy + the email submit action. */
27
+ mode?: "signin" | "signup";
28
+ /**
29
+ * Endpoint that starts the Tangle cross-site SSO flow. The app's server
30
+ * 302-redirects this to the platform authorize URL. Default `/auth/tangle/start`.
31
+ */
32
+ tangleAuthUrl?: string;
33
+ /** Social providers to surface; default `["github", "google"]`. Empty = none. */
34
+ providers?: SocialProvider[];
35
+ /** Build the social sign-in href. Default better-auth social endpoint. */
36
+ socialHref?: (provider: SocialProvider) => string;
37
+ /**
38
+ * Email/password handler. Return an error message to show, or null on success
39
+ * (the caller handles navigation). If omitted, the email form is hidden —
40
+ * SSO/social only.
41
+ */
42
+ onEmailSubmit?: (email: string, password: string) => Promise<string | null>;
43
+ /** Footer link target for the opposite mode (signup from signin, vice versa). */
44
+ altHref?: string;
45
+ /** Primary (Tangle) button background. Default Tangle ink `#0f172a`. */
46
+ accent?: string;
47
+ /** Primary button hover background. Default `#1e293b`. */
48
+ accentHover?: string;
49
+ /** Optional brand-mark size in the lockup. Default "lg". */
50
+ logoSize?: "sm" | "md" | "lg" | "xl";
51
+ /** Escape hatch: extra class on the card. */
52
+ className?: string;
53
+ /** Escape hatch: inline style merged onto the card. */
54
+ style?: CSSProperties;
55
+ /** Optional node rendered above the footer (legal copy, SSO notice, etc.). */
56
+ children?: ReactNode;
57
+ }
58
+ declare function AuthPage({ product, tagline, mode, tangleAuthUrl, providers, socialHref, onEmailSubmit, altHref, accent, accentHover, logoSize, className, style, children, }: AuthPageProps): react_jsx_runtime.JSX.Element;
59
+
6
60
  type ProductVariant$1 = "sandbox";
7
61
  interface BillingPageData {
8
62
  subscription: BillingSubscription | null;
@@ -289,4 +343,4 @@ type TemplateCategory = "blockchain" | "ai-ml" | "frontend" | "infrastructure" |
289
343
  type TemplatePreset = Omit<ProvisioningConfig, "name" | "gitUrl" | "envVars" | "driver" | "startupScriptIds">;
290
344
  declare function getPresetForTemplate(id: string): TemplatePreset;
291
345
 
292
- export { BillingPage, type BillingPageData, type BillingPageProps, type EnvironmentEntry, type EnvironmentOption, type PlanTierInfo, type PricingRates, 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, type SshAccessConfig, type SshKeyOption, StandalonePricingPage, type StandalonePricingPageProps, type StartupScript, type StartupScriptEntry, type StartupScriptFormData, type StartupScriptsApiClient, StartupScriptsPage, type StartupScriptsPageProps, type TemplateCategory, type TemplatePreset, TemplatesPage, type TemplatesPageProps, getPresetForTemplate, resolveEnvironment };
346
+ export { AuthPage, type AuthPageProps, BillingPage, type BillingPageData, type BillingPageProps, type EnvironmentEntry, type EnvironmentOption, type PlanTierInfo, type PricingRates, 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, type SocialProvider, type SshAccessConfig, type SshKeyOption, StandalonePricingPage, type StandalonePricingPageProps, type StartupScript, type StartupScriptEntry, type StartupScriptFormData, type StartupScriptsApiClient, StartupScriptsPage, type StartupScriptsPageProps, type TemplateCategory, type TemplatePreset, TemplatesPage, type TemplatesPageProps, getPresetForTemplate, resolveEnvironment };