@tangle-network/ui 11.3.0 → 11.4.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 +12 -0
- package/dist/chunk-HGML45QH.js +104 -0
- package/dist/index.d.ts +2 -42
- package/dist/index.js +3 -97
- package/dist/redaction.d.ts +42 -0
- package/dist/redaction.js +7 -0
- package/package.json +7 -2
- package/src/redaction/index.ts +12 -0
- package/src/run/run-row-shell.tsx +4 -1
- /package/src/primitives/{faint-text.test.tsx → faint-text-tokens.test.tsx} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tangle-network/ui
|
|
2
2
|
|
|
3
|
+
## 11.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ef1be75: Publish the redaction renderer on its own `./redaction` subpath.
|
|
8
|
+
|
|
9
|
+
`RedactedDocument` is the client half of agent-app's reversible redaction: agent-app builds a document of masked spans and reveals one at a time through `revealSpan`, where authorization and the audit record happen, and this component renders that document and calls back for a reveal. It reached the package only through the root entry, and no consumer imports the bare package — every app takes ui through a subpath — so the component was unreachable in practice.
|
|
10
|
+
|
|
11
|
+
The module gains a header stating what it pairs with, including why its segment type omits the `cipher` that agent-app's carries: the ciphertext stays on the server, so the type a browser holds cannot name it.
|
|
12
|
+
|
|
13
|
+
- b7c12be: `RunRowShell`'s `title` prop widens from `string` to `ReactNode` — purely additive (strings render unchanged). Lets a row carry a treated title (e.g. an active-state shimmer sweep) so consumers composing the canonical run-row grammar don't have to re-fork the shell for it.
|
|
14
|
+
|
|
3
15
|
## 11.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-RQHJBTEU.js";
|
|
4
|
+
|
|
5
|
+
// src/redaction/redacted-document.tsx
|
|
6
|
+
import { useCallback, useState } from "react";
|
|
7
|
+
import { Eye, EyeOff, Loader2, ShieldAlert } from "lucide-react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var defaultLabel = (kind) => kind.replace(/[-_]/g, " ").toUpperCase();
|
|
10
|
+
function RedactedChip({
|
|
11
|
+
kind,
|
|
12
|
+
label,
|
|
13
|
+
onReveal
|
|
14
|
+
}) {
|
|
15
|
+
const [state, setState] = useState({ status: "masked" });
|
|
16
|
+
const reveal = useCallback(async () => {
|
|
17
|
+
setState({ status: "loading" });
|
|
18
|
+
try {
|
|
19
|
+
const r = await onReveal();
|
|
20
|
+
setState(
|
|
21
|
+
r.ok && r.value !== void 0 ? { status: "revealed", value: r.value } : { status: "denied", reason: r.reason }
|
|
22
|
+
);
|
|
23
|
+
} catch {
|
|
24
|
+
setState({ status: "denied", reason: "error" });
|
|
25
|
+
}
|
|
26
|
+
}, [onReveal]);
|
|
27
|
+
if (state.status === "revealed") {
|
|
28
|
+
return /* @__PURE__ */ jsxs(
|
|
29
|
+
"button",
|
|
30
|
+
{
|
|
31
|
+
type: "button",
|
|
32
|
+
onClick: () => setState({ status: "masked" }),
|
|
33
|
+
title: "Revealed \u2014 click to hide",
|
|
34
|
+
"aria-label": `${label}: revealed, click to hide`,
|
|
35
|
+
className: cn(
|
|
36
|
+
"inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium",
|
|
37
|
+
"bg-[color-mix(in_oklch,var(--color-warning,orange)_18%,transparent)] text-foreground ring-1 ring-warning/40"
|
|
38
|
+
),
|
|
39
|
+
children: [
|
|
40
|
+
state.value,
|
|
41
|
+
/* @__PURE__ */ jsx(EyeOff, { className: "size-3 opacity-60" })
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (state.status === "denied") {
|
|
47
|
+
return /* @__PURE__ */ jsxs(
|
|
48
|
+
"span",
|
|
49
|
+
{
|
|
50
|
+
title: `Restricted${state.reason ? ` (${state.reason})` : ""}`,
|
|
51
|
+
"aria-label": `${label}: restricted`,
|
|
52
|
+
className: "inline-flex items-center gap-1 rounded-[var(--radius-sm)] bg-muted px-1 text-muted-foreground",
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsx(ShieldAlert, { className: "size-3" }),
|
|
55
|
+
" ",
|
|
56
|
+
label
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return /* @__PURE__ */ jsxs(
|
|
62
|
+
"button",
|
|
63
|
+
{
|
|
64
|
+
type: "button",
|
|
65
|
+
disabled: state.status === "loading",
|
|
66
|
+
onClick: reveal,
|
|
67
|
+
title: `${label} \u2014 click to reveal`,
|
|
68
|
+
"aria-label": `${label} redacted, click to reveal`,
|
|
69
|
+
className: cn(
|
|
70
|
+
"inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium tracking-wide",
|
|
71
|
+
"bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors",
|
|
72
|
+
"cursor-pointer select-none"
|
|
73
|
+
),
|
|
74
|
+
children: [
|
|
75
|
+
state.status === "loading" ? /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx(Eye, { className: "size-3 opacity-60" }),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2588\u2588\u2588" }),
|
|
77
|
+
" ",
|
|
78
|
+
label
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function RedactedDocument({
|
|
84
|
+
document,
|
|
85
|
+
onReveal,
|
|
86
|
+
labelForKind = defaultLabel,
|
|
87
|
+
className
|
|
88
|
+
}) {
|
|
89
|
+
return /* @__PURE__ */ jsx("div", { className: cn("whitespace-pre-wrap break-words leading-relaxed", className), children: document.segments.map(
|
|
90
|
+
(seg, i) => seg.type === "text" ? /* @__PURE__ */ jsx("span", { children: seg.text }, i) : /* @__PURE__ */ jsx(
|
|
91
|
+
RedactedChip,
|
|
92
|
+
{
|
|
93
|
+
kind: seg.kind,
|
|
94
|
+
label: labelForKind(seg.kind),
|
|
95
|
+
onReveal: () => onReveal(seg.id)
|
|
96
|
+
},
|
|
97
|
+
seg.id
|
|
98
|
+
)
|
|
99
|
+
) });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
RedactedDocument
|
|
104
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ export { F as FinalTextPart, G as GroupedMessage, M as MessageRun, a as MessageU
|
|
|
21
21
|
export { C as CustomToolRenderer, D as DisplayVariant, T as ToolDisplayMetadata } from './tool-display-z4JcDmMQ.js';
|
|
22
22
|
export { TOOL_CATEGORY_ICONS, cn, copyText, formatBytes, formatDuration, formatUptime, getToolCategory, getToolDisplayMetadata, getToolErrorText, timeAgo, truncateText } from './utils.js';
|
|
23
23
|
export { CommandPreview, CommandPreviewProps, DiffPreview, DiffPreviewProps, GlobResultsPreview, GlobResultsPreviewProps, GrepResultsPreview, GrepResultsPreviewProps, QuestionPreview, QuestionPreviewProps, WebSearchPreview, WebSearchPreviewProps, WriteFilePreview, WriteFilePreviewProps } from './tool-previews.js';
|
|
24
|
-
|
|
24
|
+
export { RedactedDocSegment, RedactedDocument, RedactedDocumentData, RedactedDocumentProps, RevealResult } from './redaction.js';
|
|
25
25
|
import 'class-variance-authority/types';
|
|
26
|
+
import 'react';
|
|
26
27
|
import 'react/jsx-runtime';
|
|
27
28
|
import '@radix-ui/react-dialog';
|
|
28
29
|
import 'class-variance-authority';
|
|
@@ -39,44 +40,3 @@ import '@hocuspocus/provider';
|
|
|
39
40
|
import 'yjs';
|
|
40
41
|
import 'nanostores';
|
|
41
42
|
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
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RedactedDocument
|
|
3
|
+
} from "./chunk-HGML45QH.js";
|
|
1
4
|
import {
|
|
2
5
|
AuthHeader,
|
|
3
6
|
GitHubLoginButton,
|
|
@@ -244,103 +247,6 @@ import {
|
|
|
244
247
|
import {
|
|
245
248
|
cn
|
|
246
249
|
} from "./chunk-RQHJBTEU.js";
|
|
247
|
-
|
|
248
|
-
// src/redaction/redacted-document.tsx
|
|
249
|
-
import { useCallback, useState } from "react";
|
|
250
|
-
import { Eye, EyeOff, Loader2, ShieldAlert } from "lucide-react";
|
|
251
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
252
|
-
var defaultLabel = (kind) => kind.replace(/[-_]/g, " ").toUpperCase();
|
|
253
|
-
function RedactedChip({
|
|
254
|
-
kind,
|
|
255
|
-
label,
|
|
256
|
-
onReveal
|
|
257
|
-
}) {
|
|
258
|
-
const [state, setState] = useState({ status: "masked" });
|
|
259
|
-
const reveal = useCallback(async () => {
|
|
260
|
-
setState({ status: "loading" });
|
|
261
|
-
try {
|
|
262
|
-
const r = await onReveal();
|
|
263
|
-
setState(
|
|
264
|
-
r.ok && r.value !== void 0 ? { status: "revealed", value: r.value } : { status: "denied", reason: r.reason }
|
|
265
|
-
);
|
|
266
|
-
} catch {
|
|
267
|
-
setState({ status: "denied", reason: "error" });
|
|
268
|
-
}
|
|
269
|
-
}, [onReveal]);
|
|
270
|
-
if (state.status === "revealed") {
|
|
271
|
-
return /* @__PURE__ */ jsxs(
|
|
272
|
-
"button",
|
|
273
|
-
{
|
|
274
|
-
type: "button",
|
|
275
|
-
onClick: () => setState({ status: "masked" }),
|
|
276
|
-
title: "Revealed \u2014 click to hide",
|
|
277
|
-
"aria-label": `${label}: revealed, click to hide`,
|
|
278
|
-
className: cn(
|
|
279
|
-
"inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium",
|
|
280
|
-
"bg-[color-mix(in_oklch,var(--color-warning,orange)_18%,transparent)] text-foreground ring-1 ring-warning/40"
|
|
281
|
-
),
|
|
282
|
-
children: [
|
|
283
|
-
state.value,
|
|
284
|
-
/* @__PURE__ */ jsx(EyeOff, { className: "size-3 opacity-60" })
|
|
285
|
-
]
|
|
286
|
-
}
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
if (state.status === "denied") {
|
|
290
|
-
return /* @__PURE__ */ jsxs(
|
|
291
|
-
"span",
|
|
292
|
-
{
|
|
293
|
-
title: `Restricted${state.reason ? ` (${state.reason})` : ""}`,
|
|
294
|
-
"aria-label": `${label}: restricted`,
|
|
295
|
-
className: "inline-flex items-center gap-1 rounded-[var(--radius-sm)] bg-muted px-1 text-muted-foreground",
|
|
296
|
-
children: [
|
|
297
|
-
/* @__PURE__ */ jsx(ShieldAlert, { className: "size-3" }),
|
|
298
|
-
" ",
|
|
299
|
-
label
|
|
300
|
-
]
|
|
301
|
-
}
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
return /* @__PURE__ */ jsxs(
|
|
305
|
-
"button",
|
|
306
|
-
{
|
|
307
|
-
type: "button",
|
|
308
|
-
disabled: state.status === "loading",
|
|
309
|
-
onClick: reveal,
|
|
310
|
-
title: `${label} \u2014 click to reveal`,
|
|
311
|
-
"aria-label": `${label} redacted, click to reveal`,
|
|
312
|
-
className: cn(
|
|
313
|
-
"inline-flex items-center gap-1 rounded-[var(--radius-sm)] px-1 font-medium tracking-wide",
|
|
314
|
-
"bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground transition-colors",
|
|
315
|
-
"cursor-pointer select-none"
|
|
316
|
-
),
|
|
317
|
-
children: [
|
|
318
|
-
state.status === "loading" ? /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx(Eye, { className: "size-3 opacity-60" }),
|
|
319
|
-
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2588\u2588\u2588" }),
|
|
320
|
-
" ",
|
|
321
|
-
label
|
|
322
|
-
]
|
|
323
|
-
}
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
function RedactedDocument({
|
|
327
|
-
document,
|
|
328
|
-
onReveal,
|
|
329
|
-
labelForKind = defaultLabel,
|
|
330
|
-
className
|
|
331
|
-
}) {
|
|
332
|
-
return /* @__PURE__ */ jsx("div", { className: cn("whitespace-pre-wrap break-words leading-relaxed", className), children: document.segments.map(
|
|
333
|
-
(seg, i) => seg.type === "text" ? /* @__PURE__ */ jsx("span", { children: seg.text }, i) : /* @__PURE__ */ jsx(
|
|
334
|
-
RedactedChip,
|
|
335
|
-
{
|
|
336
|
-
kind: seg.kind,
|
|
337
|
-
label: labelForKind(seg.kind),
|
|
338
|
-
onReveal: () => onReveal(seg.id)
|
|
339
|
-
},
|
|
340
|
-
seg.id
|
|
341
|
-
)
|
|
342
|
-
) });
|
|
343
|
-
}
|
|
344
250
|
export {
|
|
345
251
|
AgentTimeline,
|
|
346
252
|
ArtifactPane,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Viewer for a server-produced redacted document. Renders text inline and each
|
|
5
|
+
* redacted span as a masked chip; clicking a chip asks the server to reveal that
|
|
6
|
+
* one span. The original plaintext is NEVER in the document the client holds —
|
|
7
|
+
* the chip carries only an id + kind; `onReveal` round-trips to the server, where
|
|
8
|
+
* `@tangle-network/agent-app/redact`'s `revealSpan` runs the authorization check
|
|
9
|
+
* and writes the audit trail. So authz + audit are server-truth; this is display.
|
|
10
|
+
*
|
|
11
|
+
* Structural types (no `@tangle-network/agent-app` dependency) — the viewer needs
|
|
12
|
+
* only `{ id, kind }` per span; the cipher stays server-side.
|
|
13
|
+
*/
|
|
14
|
+
type RedactedDocSegment = {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "redacted";
|
|
19
|
+
id: string;
|
|
20
|
+
kind: string;
|
|
21
|
+
};
|
|
22
|
+
interface RedactedDocumentData {
|
|
23
|
+
segments: RedactedDocSegment[];
|
|
24
|
+
}
|
|
25
|
+
interface RevealResult {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
value?: string;
|
|
28
|
+
/** e.g. `forbidden` | `not_found` when `ok` is false. */
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
interface RedactedDocumentProps {
|
|
32
|
+
document: RedactedDocumentData;
|
|
33
|
+
/** Reveal one span by id. Wire to a server route that calls agent-app's
|
|
34
|
+
* `revealSpan` (authz + audit happen there). Resolves with the original. */
|
|
35
|
+
onReveal: (spanId: string) => Promise<RevealResult>;
|
|
36
|
+
/** Display label for a redaction kind (default: the kind, upper-cased). */
|
|
37
|
+
labelForKind?: (kind: string) => string;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function RedactedDocument({ document, onReveal, labelForKind, className, }: RedactedDocumentProps): ReactNode;
|
|
41
|
+
|
|
42
|
+
export { type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, type RevealResult };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.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",
|
|
@@ -101,6 +101,11 @@
|
|
|
101
101
|
"types": "./dist/nav.d.ts",
|
|
102
102
|
"import": "./dist/nav.js",
|
|
103
103
|
"default": "./dist/nav.js"
|
|
104
|
+
},
|
|
105
|
+
"./redaction": {
|
|
106
|
+
"types": "./dist/redaction.d.ts",
|
|
107
|
+
"import": "./dist/redaction.js",
|
|
108
|
+
"default": "./dist/redaction.js"
|
|
104
109
|
}
|
|
105
110
|
},
|
|
106
111
|
"dependencies": {
|
|
@@ -141,7 +146,7 @@
|
|
|
141
146
|
"react-dom": "^18 || ^19",
|
|
142
147
|
"react-router": "^7 || ^8",
|
|
143
148
|
"yjs": "^13.6.0",
|
|
144
|
-
"@tangle-network/brand": "^1.
|
|
149
|
+
"@tangle-network/brand": "^1.5.0"
|
|
145
150
|
},
|
|
146
151
|
"peerDependenciesMeta": {
|
|
147
152
|
"@nanostores/react": {
|
package/src/redaction/index.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of agent-app's reversible redaction.
|
|
3
|
+
*
|
|
4
|
+
* `agent-app/redact` builds a document of masked spans with each original kept
|
|
5
|
+
* encrypted, and reveals one span at a time through `revealSpan`, where the
|
|
6
|
+
* authorization and the audit record happen. This module renders that document
|
|
7
|
+
* and calls back for a reveal.
|
|
8
|
+
*
|
|
9
|
+
* The two segment types are deliberately not identical: agent-app's carries the
|
|
10
|
+
* `cipher` for a masked span, and this one does not. The ciphertext stays on the
|
|
11
|
+
* server, so the type a browser holds cannot name it.
|
|
12
|
+
*/
|
|
1
13
|
export {
|
|
2
14
|
RedactedDocument,
|
|
3
15
|
type RedactedDocumentProps,
|
|
@@ -32,7 +32,10 @@ export function RunRowStatusDot({ status }: { status: RunRowStatus }) {
|
|
|
32
32
|
export interface RunRowShellProps {
|
|
33
33
|
/** Semantic lead glyph — always visible; never replaced by status. */
|
|
34
34
|
icon: ReactNode;
|
|
35
|
-
title
|
|
35
|
+
/** Row title. A string in the common case; a node when the title carries its
|
|
36
|
+
* own treatment (e.g. an active-state shimmer sweep) — it renders inline in
|
|
37
|
+
* the shell's title slot either way. */
|
|
38
|
+
title: ReactNode;
|
|
36
39
|
/** Secondary inline text (tool path/command, or a reasoning preview). */
|
|
37
40
|
description?: string;
|
|
38
41
|
/** Render the description in mono (tools) vs prose (reasoning). */
|
|
File without changes
|