@tangle-network/ui 1.0.1 → 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 +48 -0
- package/dist/editor.js +0 -1
- package/dist/index.d.ts +44 -5
- package/dist/index.js +99 -28
- package/dist/nav.d.ts +25 -0
- package/dist/nav.js +16 -0
- package/package.json +13 -3
- package/src/index.ts +1 -2
- package/src/nav/index.tsx +34 -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,53 @@
|
|
|
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
|
+
|
|
3
51
|
## 1.0.1
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/dist/editor.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -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
|
@@ -216,21 +216,6 @@ import {
|
|
|
216
216
|
RichFileTree,
|
|
217
217
|
filterFileTree
|
|
218
218
|
} from "./chunk-HJKCSXCH.js";
|
|
219
|
-
import "./chunk-Q7EIIWTC.js";
|
|
220
|
-
import {
|
|
221
|
-
CollaboratorsList,
|
|
222
|
-
DocumentEditorPane,
|
|
223
|
-
EditorProvider,
|
|
224
|
-
EditorToolbar,
|
|
225
|
-
TiptapEditor,
|
|
226
|
-
useAwareness,
|
|
227
|
-
useCollaboratorPresence,
|
|
228
|
-
useCollaborators,
|
|
229
|
-
useDocumentChanges,
|
|
230
|
-
useEditorConnection,
|
|
231
|
-
useEditorContext,
|
|
232
|
-
useYjsState
|
|
233
|
-
} from "./chunk-EEE55AVS.js";
|
|
234
219
|
import {
|
|
235
220
|
Tabs,
|
|
236
221
|
TabsContent,
|
|
@@ -251,6 +236,103 @@ import {
|
|
|
251
236
|
import {
|
|
252
237
|
cn
|
|
253
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
|
+
}
|
|
254
336
|
export {
|
|
255
337
|
AgentTimeline,
|
|
256
338
|
ArtifactPane,
|
|
@@ -270,7 +352,6 @@ export {
|
|
|
270
352
|
ChatInput,
|
|
271
353
|
ChatMessage,
|
|
272
354
|
CodeBlock,
|
|
273
|
-
CollaboratorsList,
|
|
274
355
|
CommandPreview,
|
|
275
356
|
CopyButton,
|
|
276
357
|
Dialog,
|
|
@@ -284,7 +365,6 @@ export {
|
|
|
284
365
|
DialogTitle,
|
|
285
366
|
DialogTrigger,
|
|
286
367
|
DiffPreview,
|
|
287
|
-
DocumentEditorPane,
|
|
288
368
|
DropZone,
|
|
289
369
|
DropdownMenu,
|
|
290
370
|
DropdownMenuCheckboxItem,
|
|
@@ -301,8 +381,6 @@ export {
|
|
|
301
381
|
DropdownMenuSubContent,
|
|
302
382
|
DropdownMenuSubTrigger,
|
|
303
383
|
DropdownMenuTrigger,
|
|
304
|
-
EditorProvider,
|
|
305
|
-
EditorToolbar,
|
|
306
384
|
EmptyState,
|
|
307
385
|
ExpandedToolDetail,
|
|
308
386
|
FileArtifactPane,
|
|
@@ -326,6 +404,7 @@ export {
|
|
|
326
404
|
Progress,
|
|
327
405
|
QuestionPreview,
|
|
328
406
|
RealtimeSessionRegistry,
|
|
407
|
+
RedactedDocument,
|
|
329
408
|
RichFileTree,
|
|
330
409
|
RunGroup,
|
|
331
410
|
SegmentedControl,
|
|
@@ -366,7 +445,6 @@ export {
|
|
|
366
445
|
Textarea,
|
|
367
446
|
ThemeToggle,
|
|
368
447
|
ThinkingIndicator,
|
|
369
|
-
TiptapEditor,
|
|
370
448
|
ToastContainer,
|
|
371
449
|
ToastProvider,
|
|
372
450
|
ToolCallFeed,
|
|
@@ -424,13 +502,7 @@ export {
|
|
|
424
502
|
useApiKey,
|
|
425
503
|
useAuth,
|
|
426
504
|
useAutoScroll,
|
|
427
|
-
useAwareness,
|
|
428
|
-
useCollaboratorPresence,
|
|
429
|
-
useCollaborators,
|
|
430
|
-
useDocumentChanges,
|
|
431
505
|
useDropdownMenu,
|
|
432
|
-
useEditorConnection,
|
|
433
|
-
useEditorContext,
|
|
434
506
|
useHasBackgroundRunningSessions,
|
|
435
507
|
useLiveTime,
|
|
436
508
|
useNavbarSessions,
|
|
@@ -445,6 +517,5 @@ export {
|
|
|
445
517
|
useTheme,
|
|
446
518
|
useToast,
|
|
447
519
|
useToolCallStream,
|
|
448
|
-
useTotalRunningSessions
|
|
449
|
-
useYjsState
|
|
520
|
+
useTotalRunningSessions
|
|
450
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/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";
|
|
@@ -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
|