@tangle-network/ui 1.0.0 → 3.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [8152d92]
8
+ - @tangle-network/brand@0.4.0
9
+
10
+ ## 2.1.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 12f5565: Add `<RedactedDocument>` viewer (`@tangle-network/ui/redaction`): renders a server-produced redacted document with masked, click-to-reveal spans. The client holds only `{ id, kind }` per span; revealing one round-trips through an `onReveal` callback so authorization and the audit trail stay server-side (pairs with `@tangle-network/agent-app/redact`'s `buildRedactedDocument` / `revealSpan`).
15
+
16
+ ## 2.0.0
17
+
18
+ ### Major Changes
19
+
20
+ - 4d7cc77: Drop `editor` re-exports from `@tangle-network/ui` root barrel. The `@tangle-network/ui/editor` subpath is unchanged.
21
+
22
+ **Rationale:** the editor surface drags `@tiptap/*`, `yjs`, and `@hocuspocus/provider` type chains into the package root's `.d.ts`. These are specialized collaboration tooling, not generic UI primitives — they should not appear in a consumer's default import.
23
+
24
+ **Migration:**
25
+
26
+ ```ts
27
+ // before
28
+ import {
29
+ TiptapEditor,
30
+ EditorToolbar,
31
+ DocumentEditorPane,
32
+ } from "@tangle-network/ui";
33
+
34
+ // after
35
+ import {
36
+ TiptapEditor,
37
+ EditorToolbar,
38
+ DocumentEditorPane,
39
+ } from "@tangle-network/ui/editor";
40
+ ```
41
+
42
+ `sed` recipe:
43
+
44
+ ```bash
45
+ grep -rl '"@tangle-network/ui"' src/ \
46
+ | xargs sed -i '' -E '/Tiptap|Editor|Collaborat|useYjs|useAwareness|DocumentEditor|ConnectionState/s|"@tangle-network/ui"|"@tangle-network/ui/editor"|g'
47
+ ```
48
+
49
+ **Known residual:** `dist/index.d.ts` still emits type-only side-effect imports for `@hocuspocus/provider` and `yjs`. These come from `FileArtifactPaneEditorOptions` in `files/file-artifact-pane.tsx`, which types its optional collaboration config against `DocumentEditorMode`/`DocumentEditorBackend`/`DocumentEditorPaneCollaborationConfig` from `editor/`. The actual editor symbols (`TiptapEditor`, `EditorProvider`, etc.) and `@tiptap/*` types are no longer at the root. A follow-up PR can either extract the editor option types out of `editor/` or drop `./files` from the root barrel; both exceed this PR's scope.
50
+
51
+ ## 1.0.1
52
+
53
+ ### Patch Changes
54
+
55
+ - 0db7afc: Expose `ThemeToggle` and `useTheme` from `@tangle-network/ui/primitives`. The component and hook were bulk-imported in `1.0.0` but never wired into `primitives/index.ts`, leaving them inaccessible to consumers.
56
+
3
57
  ## 1.0.0
4
58
 
5
59
  ### Minor Changes
@@ -1175,6 +1175,91 @@ function SidebarDropZone({
1175
1175
  );
1176
1176
  }
1177
1177
 
1178
+ // src/primitives/theme-toggle.tsx
1179
+ import { useCallback as useCallback4, useEffect as useEffect2, useState as useState5 } from "react";
1180
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1181
+ function getSystemTheme() {
1182
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1183
+ }
1184
+ function applyTheme(theme) {
1185
+ const resolved = theme === "system" ? getSystemTheme() : theme;
1186
+ document.documentElement.classList.toggle("dark", resolved === "dark");
1187
+ }
1188
+ function useTheme() {
1189
+ const [theme, setThemeState] = useState5(() => {
1190
+ if (typeof window === "undefined") return "system";
1191
+ return localStorage.getItem("theme") ?? "system";
1192
+ });
1193
+ const setTheme = useCallback4((next) => {
1194
+ setThemeState(next);
1195
+ if (next === "system") {
1196
+ localStorage.removeItem("theme");
1197
+ } else {
1198
+ localStorage.setItem("theme", next);
1199
+ }
1200
+ applyTheme(next);
1201
+ }, []);
1202
+ useEffect2(() => {
1203
+ applyTheme(theme);
1204
+ if (theme !== "system") return;
1205
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
1206
+ const handler = () => applyTheme("system");
1207
+ mq.addEventListener("change", handler);
1208
+ return () => mq.removeEventListener("change", handler);
1209
+ }, [theme]);
1210
+ return { theme, setTheme };
1211
+ }
1212
+ var iconClass = "h-4 w-4";
1213
+ function SunIcon() {
1214
+ return /* @__PURE__ */ jsxs14(
1215
+ "svg",
1216
+ {
1217
+ xmlns: "http://www.w3.org/2000/svg",
1218
+ viewBox: "0 0 24 24",
1219
+ fill: "none",
1220
+ stroke: "currentColor",
1221
+ strokeWidth: 2,
1222
+ strokeLinecap: "round",
1223
+ strokeLinejoin: "round",
1224
+ className: iconClass,
1225
+ children: [
1226
+ /* @__PURE__ */ jsx16("circle", { cx: 12, cy: 12, r: 5 }),
1227
+ /* @__PURE__ */ jsx16("path", { d: "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" })
1228
+ ]
1229
+ }
1230
+ );
1231
+ }
1232
+ function MoonIcon() {
1233
+ return /* @__PURE__ */ jsx16(
1234
+ "svg",
1235
+ {
1236
+ xmlns: "http://www.w3.org/2000/svg",
1237
+ viewBox: "0 0 24 24",
1238
+ fill: "none",
1239
+ stroke: "currentColor",
1240
+ strokeWidth: 2,
1241
+ strokeLinecap: "round",
1242
+ strokeLinejoin: "round",
1243
+ className: iconClass,
1244
+ children: /* @__PURE__ */ jsx16("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
1245
+ }
1246
+ );
1247
+ }
1248
+ function ThemeToggle() {
1249
+ const { theme, setTheme } = useTheme();
1250
+ const resolved = theme === "system" ? getSystemTheme() : theme;
1251
+ return /* @__PURE__ */ jsx16(
1252
+ "button",
1253
+ {
1254
+ type: "button",
1255
+ onClick: () => setTheme(resolved === "dark" ? "light" : "dark"),
1256
+ className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors",
1257
+ "aria-label": `Switch to ${resolved === "dark" ? "light" : "dark"} mode`,
1258
+ children: resolved === "dark" ? /* @__PURE__ */ jsx16(SunIcon, {}) : /* @__PURE__ */ jsx16(MoonIcon, {})
1259
+ }
1260
+ );
1261
+ }
1262
+
1178
1263
  export {
1179
1264
  Dialog,
1180
1265
  DialogTrigger,
@@ -1217,6 +1302,8 @@ export {
1217
1302
  DropZone,
1218
1303
  UploadProgress,
1219
1304
  SidebarDropZone,
1305
+ useTheme,
1306
+ ThemeToggle,
1220
1307
  Logo,
1221
1308
  TangleKnot
1222
1309
  };
package/dist/editor.js CHANGED
@@ -1,4 +1,3 @@
1
- import "./chunk-Q7EIIWTC.js";
2
1
  import {
3
2
  CollaboratorsList,
4
3
  DocumentEditorPane,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { B as Button, a as ButtonProps, b as buttonVariants } from './button-CMQuQEW_.js';
2
- export { Avatar, AvatarFallback, AvatarImage, Badge, BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EmptyStateProps, InlineCode, InlineCodeProps, Input, InputProps, Label, Progress, SegmentedControl, SegmentedControlOption, SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, StatCardProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, TextareaProps, Toast, ToastContainer, ToastProvider, UploadFile, UploadProgress, UploadProgressProps, badgeVariants, useToast } from './primitives.js';
2
+ export { Avatar, AvatarFallback, AvatarImage, Badge, BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EmptyStateProps, InlineCode, InlineCodeProps, Input, InputProps, Label, Progress, SegmentedControl, SegmentedControlOption, SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, StatCardProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, TextareaProps, ThemeToggle, Toast, ToastContainer, ToastProvider, UploadFile, UploadProgress, UploadProgressProps, badgeVariants, useTheme, useToast } from './primitives.js';
3
3
  export { Logo, LogoProps, TangleKnot } from '@tangle-network/brand';
4
4
  export { A as ArtifactPane, a as ArtifactPaneProps } from './artifact-pane-DvJyPWV4.js';
5
5
  export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatInput, ChatInputProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, PendingFile, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from './chat.js';
@@ -7,12 +7,10 @@ export { ExpandedToolDetail, ExpandedToolDetailProps, InlineThinkingItem, Inline
7
7
  export { F as FeedSegment, T as ToolCallData, a as ToolCallFeed, b as ToolCallFeedProps, c as ToolCallGroup, d as ToolCallGroupProps, e as ToolCallStatus, f as ToolCallStep, g as ToolCallStepProps, h as ToolCallType, p as parseToolEvent } from './tool-call-feed-Bs3MyQMT.js';
8
8
  export { OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifactRendererProps, OpenUIBadgeNode, OpenUICardNode, OpenUICodeNode, OpenUIComponentNode, OpenUIGridNode, OpenUIHeadingNode, OpenUIKeyValueNode, OpenUIMarkdownNode, OpenUIPrimitive, OpenUISeparatorNode, OpenUIStackNode, OpenUIStatNode, OpenUITableNode, OpenUITextNode } from './openui.js';
9
9
  export { FileArtifactPane, FileArtifactPaneProps, FileNode, FilePreview, FilePreviewProps, FileTabData, FileTabs, FileTabsProps, FileTree, FileTreeProps, FileTreeVisibilityOptions, RichFileTree, RichFileTreeGitEntry, RichFileTreeGitStatus, RichFileTreeProps, RichFileTreeThemeVars, filterFileTree } from './files.js';
10
- export { C as Collaborator, a as ConnectionState, D as DocumentEditorBackend, b as DocumentEditorMode, c as DocumentEditorPane, d as DocumentEditorPaneCollaborationConfig, e as DocumentEditorPaneProps, E as EditorContextValue, f as EditorProvider, g as EditorProviderProps, h as EditorTokenRefreshResult, i as EditorUser, u as useEditorContext } from './document-editor-pane-DyDEX_Zm.js';
11
- export { CollaboratorsList, EditorToolbar, TiptapEditor, TiptapEditorProps, useAwareness, useCollaboratorPresence, useCollaborators, useDocumentChanges, useEditorConnection, useYjsState } from './editor.js';
12
10
  export { Markdown, MarkdownProps } from './markdown.js';
13
11
  export { C as CodeBlock, a as CodeBlockProps, b as CopyButton } from './code-block-DjXf8eOG.js';
14
12
  export { AuthHeader, AuthHeaderProps, GitHubLoginButton, GitHubLoginButtonProps, LoginLayout, LoginLayoutProps, SessionUser, UserMenu, UserMenuProps } from './auth.js';
15
- export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, useAutoScroll, useDropdownMenu, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from './sdk-hooks.js';
13
+ export { AgentStreamEvent, AppendUserMessageOptions, ApplySdkEventOptions, AutomationStreamEvent, BeginAssistantMessageOptions, BotStreamEvent, CompleteAssistantMessageOptions, ConnectionState, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, TaskStreamEvent, TerminalStreamEvent, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, useAutoScroll, useDropdownMenu, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useToolCallStream } from './sdk-hooks.js';
16
14
  export { AuthUser, UseAuthOptions, UseAuthResult, createAuthFetcher, useApiKey, useAuth, useLiveTime } from './hooks.js';
17
15
  export { A as ActiveProjectActivity, a as ActiveSessionActivityOptions, b as ActiveSessionConnectionOptions, c as ActiveSessionConnectionState, d as ActiveSessionReconnectState, e as ActiveSessionRecord, f as ActiveSessionStatus, g as ActiveSessionTransportMode, h as ActiveSessionsState, R as RegisterActiveSessionOptions, S as SessionProjectKey, i as activeSessionsAtom, j as bumpActiveSessionActivity, k as getActiveSession, l as getAllActiveSessions, m as getAllProjectActivity, n as getSessionsByActivity, o as getSessionsForNavbar, p as getSessionsForProject, q as getTotalRunningSessionCount, r as hasBackgroundRunningSessions, s as registerActiveSession, t as resetActiveSessions, u as setActiveSessionAttention, v as setActiveSessionConnection, w as setActiveSessionError, x as setActiveSessionRunning, y as setForegroundActiveSession, z as unregisterActiveSession, B as updateActiveSessionMeta, C as useActiveSession, D as useActiveSessions, E as useActiveSessionsState, F as useHasBackgroundRunningSessions, G as useNavbarSessions, H as useProjectActivity, I as useProjectSessions, J as useSessionsByActivity, K as useTotalRunningSessions } from './active-sessions-store-CeOmXgv5.js';
18
16
  export { addMessage, addParts, clearChat, isStreamingAtom, messagesAtom, partMapAtom, updatePart } from './stores.js';
@@ -23,8 +21,8 @@ export { F as FinalTextPart, G as GroupedMessage, M as MessageRun, a as MessageU
23
21
  export { C as CustomToolRenderer, D as DisplayVariant, T as ToolDisplayMetadata } from './tool-display-z4JcDmMQ.js';
24
22
  export { TOOL_CATEGORY_ICONS, cn, copyText, formatBytes, formatDuration, formatUptime, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText } from './utils.js';
25
23
  export { CommandPreview, CommandPreviewProps, DiffPreview, DiffPreviewProps, GlobResultsPreview, GlobResultsPreviewProps, GrepResultsPreview, GrepResultsPreviewProps, QuestionPreview, QuestionPreviewProps, WebSearchPreview, WebSearchPreviewProps, WriteFilePreview, WriteFilePreviewProps } from './tool-previews.js';
24
+ import { ReactNode } from 'react';
26
25
  import 'class-variance-authority/types';
27
- import 'react';
28
26
  import 'react/jsx-runtime';
29
27
  import '@radix-ui/react-dialog';
30
28
  import 'class-variance-authority';
@@ -36,8 +34,49 @@ import '@radix-ui/react-progress';
36
34
  import '@radix-ui/react-switch';
37
35
  import '@radix-ui/react-label';
38
36
  import '@pierre/trees';
37
+ import './document-editor-pane-DyDEX_Zm.js';
39
38
  import '@hocuspocus/provider';
40
39
  import 'yjs';
41
- import '@tiptap/react';
42
40
  import 'nanostores';
43
41
  import 'clsx';
42
+
43
+ /**
44
+ * Viewer for a server-produced redacted document. Renders text inline and each
45
+ * redacted span as a masked chip; clicking a chip asks the server to reveal that
46
+ * one span. The original plaintext is NEVER in the document the client holds —
47
+ * the chip carries only an id + kind; `onReveal` round-trips to the server, where
48
+ * `@tangle-network/agent-app/redact`'s `revealSpan` runs the authorization check
49
+ * and writes the audit trail. So authz + audit are server-truth; this is display.
50
+ *
51
+ * Structural types (no `@tangle-network/agent-app` dependency) — the viewer needs
52
+ * only `{ id, kind }` per span; the cipher stays server-side.
53
+ */
54
+ type RedactedDocSegment = {
55
+ type: "text";
56
+ text: string;
57
+ } | {
58
+ type: "redacted";
59
+ id: string;
60
+ kind: string;
61
+ };
62
+ interface RedactedDocumentData {
63
+ segments: RedactedDocSegment[];
64
+ }
65
+ interface RevealResult {
66
+ ok: boolean;
67
+ value?: string;
68
+ /** e.g. `forbidden` | `not_found` when `ok` is false. */
69
+ reason?: string;
70
+ }
71
+ interface RedactedDocumentProps {
72
+ document: RedactedDocumentData;
73
+ /** Reveal one span by id. Wire to a server route that calls agent-app's
74
+ * `revealSpan` (authz + audit happen there). Resolves with the original. */
75
+ onReveal: (spanId: string) => Promise<RevealResult>;
76
+ /** Display label for a redaction kind (default: the kind, upper-cased). */
77
+ labelForKind?: (kind: string) => string;
78
+ className?: string;
79
+ }
80
+ declare function RedactedDocument({ document, onReveal, labelForKind, className, }: RedactedDocumentProps): ReactNode;
81
+
82
+ export { type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, type RevealResult };
package/dist/index.js CHANGED
@@ -103,11 +103,13 @@ import {
103
103
  TerminalInput,
104
104
  TerminalLine,
105
105
  Textarea,
106
+ ThemeToggle,
106
107
  ToastContainer,
107
108
  ToastProvider,
108
109
  UploadProgress,
110
+ useTheme,
109
111
  useToast
110
- } from "./chunk-LISXUB4D.js";
112
+ } from "./chunk-XY2FP763.js";
111
113
  import {
112
114
  Avatar,
113
115
  AvatarFallback,
@@ -214,21 +216,6 @@ import {
214
216
  RichFileTree,
215
217
  filterFileTree
216
218
  } from "./chunk-HJKCSXCH.js";
217
- import "./chunk-Q7EIIWTC.js";
218
- import {
219
- CollaboratorsList,
220
- DocumentEditorPane,
221
- EditorProvider,
222
- EditorToolbar,
223
- TiptapEditor,
224
- useAwareness,
225
- useCollaboratorPresence,
226
- useCollaborators,
227
- useDocumentChanges,
228
- useEditorConnection,
229
- useEditorContext,
230
- useYjsState
231
- } from "./chunk-EEE55AVS.js";
232
219
  import {
233
220
  Tabs,
234
221
  TabsContent,
@@ -249,6 +236,103 @@ import {
249
236
  import {
250
237
  cn
251
238
  } from "./chunk-RQHJBTEU.js";
239
+
240
+ // src/redaction/redacted-document.tsx
241
+ import { useCallback, useState } from "react";
242
+ import { Eye, EyeOff, Loader2, ShieldAlert } from "lucide-react";
243
+ import { jsx, jsxs } from "react/jsx-runtime";
244
+ var defaultLabel = (kind) => kind.replace(/[-_]/g, " ").toUpperCase();
245
+ function RedactedChip({
246
+ kind,
247
+ label,
248
+ onReveal
249
+ }) {
250
+ const [state, setState] = useState({ status: "masked" });
251
+ const reveal = useCallback(async () => {
252
+ setState({ status: "loading" });
253
+ try {
254
+ const r = await onReveal();
255
+ setState(
256
+ r.ok && r.value !== void 0 ? { status: "revealed", value: r.value } : { status: "denied", reason: r.reason }
257
+ );
258
+ } catch {
259
+ setState({ status: "denied", reason: "error" });
260
+ }
261
+ }, [onReveal]);
262
+ if (state.status === "revealed") {
263
+ return /* @__PURE__ */ jsxs(
264
+ "button",
265
+ {
266
+ type: "button",
267
+ onClick: () => setState({ status: "masked" }),
268
+ title: "Revealed \u2014 click to hide",
269
+ "aria-label": `${label}: revealed, click to hide`,
270
+ className: cn(
271
+ "inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium",
272
+ "bg-[color-mix(in_oklch,var(--color-warning,orange)_18%,transparent)] text-foreground ring-1 ring-warning/40"
273
+ ),
274
+ children: [
275
+ state.value,
276
+ /* @__PURE__ */ jsx(EyeOff, { className: "size-3 opacity-60" })
277
+ ]
278
+ }
279
+ );
280
+ }
281
+ if (state.status === "denied") {
282
+ return /* @__PURE__ */ jsxs(
283
+ "span",
284
+ {
285
+ title: `Restricted${state.reason ? ` (${state.reason})` : ""}`,
286
+ "aria-label": `${label}: restricted`,
287
+ className: "inline-flex items-center gap-1 rounded-[var(--radius-sm)] bg-muted px-1 text-muted-foreground",
288
+ children: [
289
+ /* @__PURE__ */ jsx(ShieldAlert, { className: "size-3" }),
290
+ " ",
291
+ label
292
+ ]
293
+ }
294
+ );
295
+ }
296
+ return /* @__PURE__ */ jsxs(
297
+ "button",
298
+ {
299
+ type: "button",
300
+ disabled: state.status === "loading",
301
+ onClick: reveal,
302
+ title: `${label} \u2014 click to reveal`,
303
+ "aria-label": `${label} redacted, click to reveal`,
304
+ className: cn(
305
+ "inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium tracking-wide",
306
+ "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors",
307
+ "cursor-pointer select-none"
308
+ ),
309
+ children: [
310
+ state.status === "loading" ? /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx(Eye, { className: "size-3 opacity-60" }),
311
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2588\u2588\u2588" }),
312
+ " ",
313
+ label
314
+ ]
315
+ }
316
+ );
317
+ }
318
+ function RedactedDocument({
319
+ document,
320
+ onReveal,
321
+ labelForKind = defaultLabel,
322
+ className
323
+ }) {
324
+ return /* @__PURE__ */ jsx("div", { className: cn("whitespace-pre-wrap break-words leading-relaxed", className), children: document.segments.map(
325
+ (seg, i) => seg.type === "text" ? /* @__PURE__ */ jsx("span", { children: seg.text }, i) : /* @__PURE__ */ jsx(
326
+ RedactedChip,
327
+ {
328
+ kind: seg.kind,
329
+ label: labelForKind(seg.kind),
330
+ onReveal: () => onReveal(seg.id)
331
+ },
332
+ seg.id
333
+ )
334
+ ) });
335
+ }
252
336
  export {
253
337
  AgentTimeline,
254
338
  ArtifactPane,
@@ -268,7 +352,6 @@ export {
268
352
  ChatInput,
269
353
  ChatMessage,
270
354
  CodeBlock,
271
- CollaboratorsList,
272
355
  CommandPreview,
273
356
  CopyButton,
274
357
  Dialog,
@@ -282,7 +365,6 @@ export {
282
365
  DialogTitle,
283
366
  DialogTrigger,
284
367
  DiffPreview,
285
- DocumentEditorPane,
286
368
  DropZone,
287
369
  DropdownMenu,
288
370
  DropdownMenuCheckboxItem,
@@ -299,8 +381,6 @@ export {
299
381
  DropdownMenuSubContent,
300
382
  DropdownMenuSubTrigger,
301
383
  DropdownMenuTrigger,
302
- EditorProvider,
303
- EditorToolbar,
304
384
  EmptyState,
305
385
  ExpandedToolDetail,
306
386
  FileArtifactPane,
@@ -324,6 +404,7 @@ export {
324
404
  Progress,
325
405
  QuestionPreview,
326
406
  RealtimeSessionRegistry,
407
+ RedactedDocument,
327
408
  RichFileTree,
328
409
  RunGroup,
329
410
  SegmentedControl,
@@ -362,8 +443,8 @@ export {
362
443
  TerminalInput,
363
444
  TerminalLine,
364
445
  Textarea,
446
+ ThemeToggle,
365
447
  ThinkingIndicator,
366
- TiptapEditor,
367
448
  ToastContainer,
368
449
  ToastProvider,
369
450
  ToolCallFeed,
@@ -421,13 +502,7 @@ export {
421
502
  useApiKey,
422
503
  useAuth,
423
504
  useAutoScroll,
424
- useAwareness,
425
- useCollaboratorPresence,
426
- useCollaborators,
427
- useDocumentChanges,
428
505
  useDropdownMenu,
429
- useEditorConnection,
430
- useEditorContext,
431
506
  useHasBackgroundRunningSessions,
432
507
  useLiveTime,
433
508
  useNavbarSessions,
@@ -439,8 +514,8 @@ export {
439
514
  useSSEStream,
440
515
  useSdkSession,
441
516
  useSessionsByActivity,
517
+ useTheme,
442
518
  useToast,
443
519
  useToolCallStream,
444
- useTotalRunningSessions,
445
- useYjsState
520
+ useTotalRunningSessions
446
521
  };
package/dist/nav.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { LinkProps, NavLinkProps } from 'react-router';
3
+ export { LinkProps, NavLinkProps } from 'react-router';
4
+
5
+ /**
6
+ * Navigation primitives that make agent-app products feel snappy by default.
7
+ *
8
+ * The dominant source of the "1–2s when I click through pages" latency in the
9
+ * fleet is NOT slow queries (route loaders' D1 indexes already cover their
10
+ * filters) — it is that every click is a COLD loader round-trip the user waits
11
+ * on, because bare `<Link>`s do no prefetching. React Router can fire the
12
+ * target route's loader on hover/focus (`prefetch="intent"`), overlapping the
13
+ * round-trip with the user's mouse travel so the transition feels instant.
14
+ *
15
+ * These wrappers default `prefetch="intent"` so a product gets that behaviour
16
+ * by importing the shared `<Link>` instead of remembering the flag on every
17
+ * nav element. The default is overridable — a caller that passes `prefetch`
18
+ * wins (the spread is applied after the default).
19
+ */
20
+ /** `react-router` `<Link>` with `prefetch="intent"` on by default. */
21
+ declare function Link({ prefetch, ...props }: LinkProps): react_jsx_runtime.JSX.Element;
22
+ /** `react-router` `<NavLink>` with `prefetch="intent"` on by default. */
23
+ declare function NavLink({ prefetch, ...props }: NavLinkProps): react_jsx_runtime.JSX.Element;
24
+
25
+ export { Link, NavLink };
package/dist/nav.js ADDED
@@ -0,0 +1,16 @@
1
+ // src/nav/index.tsx
2
+ import {
3
+ Link as RRLink,
4
+ NavLink as RRNavLink
5
+ } from "react-router";
6
+ import { jsx } from "react/jsx-runtime";
7
+ function Link({ prefetch = "intent", ...props }) {
8
+ return /* @__PURE__ */ jsx(RRLink, { prefetch, ...props });
9
+ }
10
+ function NavLink({ prefetch = "intent", ...props }) {
11
+ return /* @__PURE__ */ jsx(RRNavLink, { prefetch, ...props });
12
+ }
13
+ export {
14
+ Link,
15
+ NavLink
16
+ };
@@ -329,4 +329,11 @@ interface InlineCodeProps extends React.HTMLAttributes<HTMLElement> {
329
329
  }
330
330
  declare function InlineCode({ className, children, ...props }: InlineCodeProps): react_jsx_runtime.JSX.Element;
331
331
 
332
- export { Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Progress, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, type StatCardProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, type TextareaProps, type Toast, ToastContainer, ToastProvider, type UploadFile, UploadProgress, type UploadProgressProps, badgeVariants, useToast };
332
+ type Theme = "light" | "dark" | "system";
333
+ declare function useTheme(): {
334
+ theme: Theme;
335
+ setTheme: (next: Theme) => void;
336
+ };
337
+ declare function ThemeToggle(): react_jsx_runtime.JSX.Element;
338
+
339
+ export { Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, InlineCode, type InlineCodeProps, Input, type InputProps, Label, Progress, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, StatCard, type StatCardProps, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, Textarea, type TextareaProps, ThemeToggle, type Toast, ToastContainer, ToastProvider, type UploadFile, UploadProgress, type UploadProgressProps, badgeVariants, useTheme, useToast };
@@ -38,11 +38,13 @@ import {
38
38
  TerminalInput,
39
39
  TerminalLine,
40
40
  Textarea,
41
+ ThemeToggle,
41
42
  ToastContainer,
42
43
  ToastProvider,
43
44
  UploadProgress,
45
+ useTheme,
44
46
  useToast
45
- } from "./chunk-LISXUB4D.js";
47
+ } from "./chunk-XY2FP763.js";
46
48
  import {
47
49
  Avatar,
48
50
  AvatarFallback,
@@ -182,10 +184,12 @@ export {
182
184
  TerminalInput,
183
185
  TerminalLine,
184
186
  Textarea,
187
+ ThemeToggle,
185
188
  ToastContainer,
186
189
  ToastProvider,
187
190
  UploadProgress,
188
191
  badgeVariants,
189
192
  buttonVariants,
193
+ useTheme,
190
194
  useToast
191
195
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "1.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -96,6 +96,11 @@
96
96
  "types": "./dist/tool-previews.d.ts",
97
97
  "import": "./dist/tool-previews.js",
98
98
  "default": "./dist/tool-previews.js"
99
+ },
100
+ "./nav": {
101
+ "types": "./dist/nav.d.ts",
102
+ "import": "./dist/nav.js",
103
+ "default": "./dist/nav.js"
99
104
  }
100
105
  },
101
106
  "dependencies": {
@@ -126,7 +131,8 @@
126
131
  "peerDependencies": {
127
132
  "react": "^18 || ^19",
128
133
  "react-dom": "^18 || ^19",
129
- "@tangle-network/brand": "^0.3.0"
134
+ "react-router": "^7",
135
+ "@tangle-network/brand": "^0.4.0"
130
136
  },
131
137
  "peerDependenciesMeta": {
132
138
  "@nanostores/react": {
@@ -158,6 +164,9 @@
158
164
  },
159
165
  "yjs": {
160
166
  "optional": true
167
+ },
168
+ "react-router": {
169
+ "optional": true
161
170
  }
162
171
  },
163
172
  "devDependencies": {
@@ -177,7 +186,8 @@
177
186
  "@storybook/react": "^8.6.18",
178
187
  "tsup": "^8.3.5",
179
188
  "typescript": "^5.6.0",
180
- "yjs": "^13.6.0"
189
+ "yjs": "^13.6.0",
190
+ "react-router": "^7"
181
191
  },
182
192
  "keywords": [
183
193
  "tangle",
package/src/index.ts CHANGED
@@ -3,8 +3,6 @@ export * from "./chat";
3
3
  export * from "./run";
4
4
  export * from "./openui";
5
5
  export * from "./files";
6
- export { type ConnectionState } from "./editor";
7
- export * from "./editor";
8
6
  export * from "./markdown";
9
7
  export * from "./auth";
10
8
  export * from "./hooks";
@@ -12,3 +10,4 @@ export * from "./stores";
12
10
  export * from "./types";
13
11
  export * from "./utils";
14
12
  export * from "./tool-previews";
13
+ export * from "./redaction";
@@ -0,0 +1,34 @@
1
+ import {
2
+ Link as RRLink,
3
+ NavLink as RRNavLink,
4
+ type LinkProps,
5
+ type NavLinkProps,
6
+ } from "react-router";
7
+
8
+ /**
9
+ * Navigation primitives that make agent-app products feel snappy by default.
10
+ *
11
+ * The dominant source of the "1–2s when I click through pages" latency in the
12
+ * fleet is NOT slow queries (route loaders' D1 indexes already cover their
13
+ * filters) — it is that every click is a COLD loader round-trip the user waits
14
+ * on, because bare `<Link>`s do no prefetching. React Router can fire the
15
+ * target route's loader on hover/focus (`prefetch="intent"`), overlapping the
16
+ * round-trip with the user's mouse travel so the transition feels instant.
17
+ *
18
+ * These wrappers default `prefetch="intent"` so a product gets that behaviour
19
+ * by importing the shared `<Link>` instead of remembering the flag on every
20
+ * nav element. The default is overridable — a caller that passes `prefetch`
21
+ * wins (the spread is applied after the default).
22
+ */
23
+
24
+ /** `react-router` `<Link>` with `prefetch="intent"` on by default. */
25
+ export function Link({ prefetch = "intent", ...props }: LinkProps) {
26
+ return <RRLink prefetch={prefetch} {...props} />;
27
+ }
28
+
29
+ /** `react-router` `<NavLink>` with `prefetch="intent"` on by default. */
30
+ export function NavLink({ prefetch = "intent", ...props }: NavLinkProps) {
31
+ return <RRNavLink prefetch={prefetch} {...props} />;
32
+ }
33
+
34
+ export type { LinkProps, NavLinkProps } from "react-router";
@@ -116,3 +116,5 @@ export type { ArtifactPaneProps } from "./artifact-pane";
116
116
 
117
117
  export { CodeBlock, CopyButton, InlineCode } from "./code-block";
118
118
  export type { CodeBlockProps, InlineCodeProps } from "./code-block";
119
+
120
+ export { ThemeToggle, useTheme } from "./theme-toggle";
@@ -0,0 +1,7 @@
1
+ export {
2
+ RedactedDocument,
3
+ type RedactedDocumentProps,
4
+ type RedactedDocumentData,
5
+ type RedactedDocSegment,
6
+ type RevealResult,
7
+ } from "./redacted-document";
@@ -0,0 +1,150 @@
1
+ import { useCallback, useState, type ReactNode } from "react";
2
+ import { Eye, EyeOff, Loader2, ShieldAlert } from "lucide-react";
3
+ import { cn } from "../lib/utils";
4
+
5
+ /**
6
+ * Viewer for a server-produced redacted document. Renders text inline and each
7
+ * redacted span as a masked chip; clicking a chip asks the server to reveal that
8
+ * one span. The original plaintext is NEVER in the document the client holds —
9
+ * the chip carries only an id + kind; `onReveal` round-trips to the server, where
10
+ * `@tangle-network/agent-app/redact`'s `revealSpan` runs the authorization check
11
+ * and writes the audit trail. So authz + audit are server-truth; this is display.
12
+ *
13
+ * Structural types (no `@tangle-network/agent-app` dependency) — the viewer needs
14
+ * only `{ id, kind }` per span; the cipher stays server-side.
15
+ */
16
+
17
+ export type RedactedDocSegment =
18
+ | { type: "text"; text: string }
19
+ | { type: "redacted"; id: string; kind: string };
20
+
21
+ export interface RedactedDocumentData {
22
+ segments: RedactedDocSegment[];
23
+ }
24
+
25
+ export interface RevealResult {
26
+ ok: boolean;
27
+ value?: string;
28
+ /** e.g. `forbidden` | `not_found` when `ok` is false. */
29
+ reason?: string;
30
+ }
31
+
32
+ export interface RedactedDocumentProps {
33
+ document: RedactedDocumentData;
34
+ /** Reveal one span by id. Wire to a server route that calls agent-app's
35
+ * `revealSpan` (authz + audit happen there). Resolves with the original. */
36
+ onReveal: (spanId: string) => Promise<RevealResult>;
37
+ /** Display label for a redaction kind (default: the kind, upper-cased). */
38
+ labelForKind?: (kind: string) => string;
39
+ className?: string;
40
+ }
41
+
42
+ type ChipState =
43
+ | { status: "masked" }
44
+ | { status: "loading" }
45
+ | { status: "revealed"; value: string }
46
+ | { status: "denied"; reason?: string };
47
+
48
+ const defaultLabel = (kind: string) => kind.replace(/[-_]/g, " ").toUpperCase();
49
+
50
+ function RedactedChip({
51
+ kind,
52
+ label,
53
+ onReveal,
54
+ }: {
55
+ kind: string;
56
+ label: string;
57
+ onReveal: () => Promise<RevealResult>;
58
+ }) {
59
+ const [state, setState] = useState<ChipState>({ status: "masked" });
60
+
61
+ const reveal = useCallback(async () => {
62
+ setState({ status: "loading" });
63
+ try {
64
+ const r = await onReveal();
65
+ setState(
66
+ r.ok && r.value !== undefined
67
+ ? { status: "revealed", value: r.value }
68
+ : { status: "denied", reason: r.reason },
69
+ );
70
+ } catch {
71
+ setState({ status: "denied", reason: "error" });
72
+ }
73
+ }, [onReveal]);
74
+
75
+ if (state.status === "revealed") {
76
+ return (
77
+ <button
78
+ type="button"
79
+ onClick={() => setState({ status: "masked" })}
80
+ title="Revealed — click to hide"
81
+ aria-label={`${label}: revealed, click to hide`}
82
+ className={cn(
83
+ "inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium",
84
+ "bg-[color-mix(in_oklch,var(--color-warning,orange)_18%,transparent)] text-foreground ring-1 ring-warning/40",
85
+ )}
86
+ >
87
+ {state.value}
88
+ <EyeOff className="size-3 opacity-60" />
89
+ </button>
90
+ );
91
+ }
92
+
93
+ if (state.status === "denied") {
94
+ return (
95
+ <span
96
+ title={`Restricted${state.reason ? ` (${state.reason})` : ""}`}
97
+ aria-label={`${label}: restricted`}
98
+ className="inline-flex items-center gap-1 rounded-[var(--radius-sm)] bg-muted px-1 text-muted-foreground"
99
+ >
100
+ <ShieldAlert className="size-3" /> {label}
101
+ </span>
102
+ );
103
+ }
104
+
105
+ return (
106
+ <button
107
+ type="button"
108
+ disabled={state.status === "loading"}
109
+ onClick={reveal}
110
+ title={`${label} — click to reveal`}
111
+ aria-label={`${label} redacted, click to reveal`}
112
+ className={cn(
113
+ "inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium tracking-wide",
114
+ "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors",
115
+ "cursor-pointer select-none",
116
+ )}
117
+ >
118
+ {state.status === "loading" ? (
119
+ <Loader2 className="size-3 animate-spin" />
120
+ ) : (
121
+ <Eye className="size-3 opacity-60" />
122
+ )}
123
+ <span aria-hidden>{"███"}</span> {label}
124
+ </button>
125
+ );
126
+ }
127
+
128
+ export function RedactedDocument({
129
+ document,
130
+ onReveal,
131
+ labelForKind = defaultLabel,
132
+ className,
133
+ }: RedactedDocumentProps): ReactNode {
134
+ return (
135
+ <div className={cn("whitespace-pre-wrap break-words leading-relaxed", className)}>
136
+ {document.segments.map((seg, i) =>
137
+ seg.type === "text" ? (
138
+ <span key={i}>{seg.text}</span>
139
+ ) : (
140
+ <RedactedChip
141
+ key={seg.id}
142
+ kind={seg.kind}
143
+ label={labelForKind(seg.kind)}
144
+ onReveal={() => onReveal(seg.id)}
145
+ />
146
+ ),
147
+ )}
148
+ </div>
149
+ );
150
+ }
File without changes