@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 +54 -0
- package/dist/{chunk-LISXUB4D.js → chunk-XY2FP763.js} +87 -0
- package/dist/editor.js +0 -1
- package/dist/index.d.ts +45 -6
- package/dist/index.js +104 -29
- package/dist/nav.d.ts +25 -0
- package/dist/nav.js +16 -0
- package/dist/primitives.d.ts +8 -1
- package/dist/primitives.js +5 -1
- package/package.json +13 -3
- package/src/index.ts +1 -2
- package/src/nav/index.tsx +34 -0
- package/src/primitives/index.ts +2 -0
- package/src/redaction/index.ts +7 -0
- package/src/redaction/redacted-document.tsx +150 -0
- package/dist/chunk-Q7EIIWTC.js +0 -0
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
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-
|
|
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
|
+
};
|
package/dist/primitives.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/primitives.js
CHANGED
|
@@ -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-
|
|
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": "
|
|
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
|
-
"
|
|
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";
|
package/src/primitives/index.ts
CHANGED
|
@@ -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,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
|
+
}
|
package/dist/chunk-Q7EIIWTC.js
DELETED
|
File without changes
|