@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 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
- import { ReactNode } from 'react';
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 };
@@ -0,0 +1,7 @@
1
+ import {
2
+ RedactedDocument
3
+ } from "./chunk-HGML45QH.js";
4
+ import "./chunk-RQHJBTEU.js";
5
+ export {
6
+ RedactedDocument
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.3.0",
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.4.0"
149
+ "@tangle-network/brand": "^1.5.0"
145
150
  },
146
151
  "peerDependenciesMeta": {
147
152
  "@nanostores/react": {
@@ -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: string;
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). */