@tangle-network/ui 11.11.1 → 11.11.3
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 +14 -0
- package/README.md +9 -1
- package/dist/{chat-D4jAfEmj.js → chat-BUJhNpx2.js} +11 -6
- package/dist/chat.js +1 -1
- package/dist/{document-editor-pane-BcoABlhn.d.ts → document-editor-pane-DHM8DDhb.d.ts} +4 -1
- package/dist/{document-editor-pane-BxlPjaTP.js → document-editor-pane-LyanNypc.js} +6 -14
- package/dist/editor.d.ts +1 -1
- package/dist/editor.js +1 -1
- package/dist/{files-Bug33qCl.js → files-DgPc3vI2.js} +16 -7
- package/dist/files.d.ts +1 -1
- package/dist/files.js +1 -1
- package/dist/{index-DhYmSaLF.d.ts → index-CyGSLGVF.d.ts} +6 -17
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -5
- package/dist/{openui-artifact-renderer-DrU1AdVk.js → openui-artifact-renderer-C3bmqfjX.js} +15 -20
- package/dist/openui-schema.d.ts +3 -0
- package/dist/openui-schema.js +2 -0
- package/dist/openui.d.ts +2 -1
- package/dist/openui.js +3 -2
- package/dist/{run-group-BACLbCFl.js → run-group-CxNskmHd.js} +1 -1
- package/dist/run.js +1 -1
- package/dist/schema-BFKgCGH-.d.ts +228 -0
- package/dist/schema-BpW9rSSG.js +9754 -0
- package/package.json +9 -2
- package/src/chat/agent-timeline.stories.tsx +15 -0
- package/src/chat/agent-timeline.tsx +10 -5
- package/src/editor/document-editor-pane.tsx +5 -1
- package/src/files/file-artifact-pane.test.tsx +41 -0
- package/src/files/file-artifact-pane.tsx +3 -0
- package/src/files/file-preview.tsx +16 -3
- package/src/openui/index.ts +10 -0
- package/src/openui/openui-artifact-renderer.stories.tsx +36 -0
- package/src/openui/openui-artifact-renderer.tsx +19 -27
- package/src/openui/schema-compiled.ts +3 -0
- package/src/openui/schema-data.ts +95 -0
- package/src/openui/schema.test.tsx +74 -0
- package/src/openui/schema.ts +136 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/ui",
|
|
3
|
-
"version": "11.11.
|
|
3
|
+
"version": "11.11.3",
|
|
4
4
|
"description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"import": "./dist/openui.js",
|
|
48
48
|
"default": "./dist/openui.js"
|
|
49
49
|
},
|
|
50
|
+
"./openui-schema": {
|
|
51
|
+
"types": "./dist/openui-schema.d.ts",
|
|
52
|
+
"import": "./dist/openui-schema.js",
|
|
53
|
+
"default": "./dist/openui-schema.js"
|
|
54
|
+
},
|
|
50
55
|
"./files": {
|
|
51
56
|
"types": "./dist/files.d.ts",
|
|
52
57
|
"import": "./dist/files.js",
|
|
@@ -191,6 +196,7 @@
|
|
|
191
196
|
"@types/react": "^19.3.0",
|
|
192
197
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
193
198
|
"@types/turndown": "^5.0.6",
|
|
199
|
+
"ajv": "8.17.1",
|
|
194
200
|
"nanostores": "^1.5.4",
|
|
195
201
|
"react": "^19.3.0",
|
|
196
202
|
"react-dom": "^19.3.0",
|
|
@@ -207,7 +213,8 @@
|
|
|
207
213
|
"design-system"
|
|
208
214
|
],
|
|
209
215
|
"scripts": {
|
|
210
|
-
"build": "tsdown && node ../../scripts/validate-dist.mjs",
|
|
216
|
+
"build": "node scripts/generate-openui-validators.mjs --check && tsdown && node ../../scripts/validate-dist.mjs && node scripts/probe-openui-worker.mjs",
|
|
217
|
+
"gen:openui-schema": "node scripts/generate-openui-validators.mjs",
|
|
211
218
|
"dev": "tsdown --watch"
|
|
212
219
|
}
|
|
213
220
|
}
|
|
@@ -450,6 +450,21 @@ export const StatusTones: Story = {
|
|
|
450
450
|
},
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
export const ErrorArtifact: Story = {
|
|
454
|
+
args: {
|
|
455
|
+
items: [
|
|
456
|
+
{
|
|
457
|
+
id: 'artifact-error',
|
|
458
|
+
kind: 'artifact',
|
|
459
|
+
title: 'Build output',
|
|
460
|
+
description: 'The compiler stopped before writing the bundle.',
|
|
461
|
+
meta: 'Exit code 1',
|
|
462
|
+
tone: 'error',
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
|
|
453
468
|
// ---------------------------------------------------------------------------
|
|
454
469
|
// Full transcript through ChatContainer — the real consumer path. Tool titles
|
|
455
470
|
// and descriptions come from `getToolDisplayMetadata`, so this story shows the
|
|
@@ -101,30 +101,35 @@ export interface AgentTimelineProps {
|
|
|
101
101
|
collapseAfter?: number;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const TONE_STYLES: Record<AgentTimelineTone, { card: string; text: string; icon: typeof Info }> = {
|
|
104
|
+
const TONE_STYLES: Record<AgentTimelineTone, { card: string; text: string; supportingText: string; icon: typeof Info }> = {
|
|
105
105
|
default: {
|
|
106
106
|
card: "border-border bg-card",
|
|
107
107
|
text: "text-foreground",
|
|
108
|
+
supportingText: "text-muted-foreground",
|
|
108
109
|
icon: CircleDot,
|
|
109
110
|
},
|
|
110
111
|
info: {
|
|
111
112
|
card: "border-[var(--surface-info-border)] bg-[var(--surface-info-bg)]",
|
|
112
113
|
text: "text-[var(--surface-info-text)]",
|
|
114
|
+
supportingText: "text-[var(--surface-info-text)]",
|
|
113
115
|
icon: Info,
|
|
114
116
|
},
|
|
115
117
|
success: {
|
|
116
118
|
card: "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]",
|
|
117
119
|
text: "text-[var(--surface-success-text)]",
|
|
120
|
+
supportingText: "text-[var(--surface-success-text)]",
|
|
118
121
|
icon: CheckCircle2,
|
|
119
122
|
},
|
|
120
123
|
warning: {
|
|
121
124
|
card: "border-[var(--surface-warning-border)] bg-[var(--surface-warning-bg)]",
|
|
122
125
|
text: "text-[var(--surface-warning-text)]",
|
|
126
|
+
supportingText: "text-[var(--surface-warning-text)]",
|
|
123
127
|
icon: AlertTriangle,
|
|
124
128
|
},
|
|
125
129
|
error: {
|
|
126
130
|
card: "border-[var(--surface-danger-border)] bg-[var(--surface-danger-bg)]",
|
|
127
131
|
text: "text-[var(--surface-danger-text)]",
|
|
132
|
+
supportingText: "text-[var(--surface-danger-text)]",
|
|
128
133
|
icon: AlertTriangle,
|
|
129
134
|
},
|
|
130
135
|
};
|
|
@@ -191,7 +196,7 @@ function StatusCard({ item }: { item: AgentTimelineStatusItem }) {
|
|
|
191
196
|
<div className="min-w-0">
|
|
192
197
|
<div className={cn("text-sm font-medium", tone.text)}>{item.label}</div>
|
|
193
198
|
{item.detail && (
|
|
194
|
-
<div className="mt-0.5 text-sm
|
|
199
|
+
<div className={cn("mt-0.5 text-sm", tone.supportingText)}>{item.detail}</div>
|
|
195
200
|
)}
|
|
196
201
|
</div>
|
|
197
202
|
</div>
|
|
@@ -208,12 +213,12 @@ function ArtifactCard({ item }: { item: AgentTimelineArtifactItem }) {
|
|
|
208
213
|
{item.icon ?? <FileText className="h-4 w-4" />}
|
|
209
214
|
</div>
|
|
210
215
|
<div className="min-w-0 flex-1">
|
|
211
|
-
<div className="text-sm font-medium text
|
|
216
|
+
<div className={cn("text-sm font-medium", tone.text)}>{item.title}</div>
|
|
212
217
|
{item.description && (
|
|
213
|
-
<div className="mt-1 text-sm
|
|
218
|
+
<div className={cn("mt-1 text-sm", tone.supportingText)}>{item.description}</div>
|
|
214
219
|
)}
|
|
215
220
|
{item.meta && (
|
|
216
|
-
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs
|
|
221
|
+
<div className={cn("mt-2 flex flex-wrap items-center gap-2 text-xs", tone.supportingText)}>
|
|
217
222
|
{item.meta}
|
|
218
223
|
</div>
|
|
219
224
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { type ReactNode, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import type { UrlTransform } from "react-markdown";
|
|
4
5
|
import { PencilLine, Save, Users, Wifi, WifiOff } from "lucide-react";
|
|
5
6
|
import { Markdown } from "../markdown/markdown";
|
|
6
7
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/tabs";
|
|
@@ -36,6 +37,8 @@ export interface DocumentEditorPaneProps
|
|
|
36
37
|
tabs?: ArtifactPaneProps["tabs"];
|
|
37
38
|
toolbar?: ReactNode;
|
|
38
39
|
markdown?: string;
|
|
40
|
+
/** Transform Markdown links and images through the shared URL policy. */
|
|
41
|
+
urlTransform?: UrlTransform;
|
|
39
42
|
mode?: DocumentEditorMode;
|
|
40
43
|
defaultMode?: DocumentEditorMode;
|
|
41
44
|
onModeChange?: (mode: DocumentEditorMode) => void;
|
|
@@ -188,6 +191,7 @@ export function DocumentEditorPane({
|
|
|
188
191
|
tabs,
|
|
189
192
|
toolbar,
|
|
190
193
|
markdown = "",
|
|
194
|
+
urlTransform,
|
|
191
195
|
mode,
|
|
192
196
|
defaultMode = "preview",
|
|
193
197
|
onModeChange,
|
|
@@ -300,7 +304,7 @@ export function DocumentEditorPane({
|
|
|
300
304
|
previewClassName,
|
|
301
305
|
)}
|
|
302
306
|
>
|
|
303
|
-
<Markdown>{draft}</Markdown>
|
|
307
|
+
<Markdown urlTransform={urlTransform}>{draft}</Markdown>
|
|
304
308
|
</div>
|
|
305
309
|
);
|
|
306
310
|
|
|
@@ -113,6 +113,47 @@ describe("FileArtifactPane previews", () => {
|
|
|
113
113
|
expect(container.querySelector("strong")).toHaveTextContent("bold");
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
it("forwards Markdown URL transforms to file and rich editor previews", async () => {
|
|
117
|
+
const urlTransform = vi.fn((url: string, key: string) => (
|
|
118
|
+
key === "href" && url === "sibling.md"
|
|
119
|
+
? "/app/ws-1/vault?file=docs%2Fsibling.md"
|
|
120
|
+
: url
|
|
121
|
+
));
|
|
122
|
+
const readOnly = render(
|
|
123
|
+
<FileArtifactPane
|
|
124
|
+
filename="README.md"
|
|
125
|
+
content={"[Sibling](sibling.md)"}
|
|
126
|
+
urlTransform={urlTransform}
|
|
127
|
+
/>,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(screen.getByRole("link", { name: "Sibling" })).toHaveAttribute(
|
|
131
|
+
"href",
|
|
132
|
+
"/app/ws-1/vault?file=docs%2Fsibling.md",
|
|
133
|
+
);
|
|
134
|
+
readOnly.unmount();
|
|
135
|
+
|
|
136
|
+
render(
|
|
137
|
+
<FileArtifactPane
|
|
138
|
+
path="docs/README.md"
|
|
139
|
+
filename="README.md"
|
|
140
|
+
content={"[Sibling](sibling.md)"}
|
|
141
|
+
urlTransform={urlTransform}
|
|
142
|
+
editor={{ enabled: true, defaultMode: "preview" }}
|
|
143
|
+
/>,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(await screen.findByRole("link", { name: "Sibling" })).toHaveAttribute(
|
|
147
|
+
"href",
|
|
148
|
+
"/app/ws-1/vault?file=docs%2Fsibling.md",
|
|
149
|
+
);
|
|
150
|
+
expect(urlTransform).toHaveBeenCalledWith(
|
|
151
|
+
"sibling.md",
|
|
152
|
+
"href",
|
|
153
|
+
expect.objectContaining({ tagName: "a" }),
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
116
157
|
it("renders a binary file with no download link as one muted sentence", () => {
|
|
117
158
|
const onDownload = vi.fn();
|
|
118
159
|
const withDownload = render(
|
|
@@ -64,6 +64,7 @@ export function FileArtifactPane({
|
|
|
64
64
|
size,
|
|
65
65
|
onClose,
|
|
66
66
|
onDownload,
|
|
67
|
+
urlTransform,
|
|
67
68
|
path,
|
|
68
69
|
tabs = [],
|
|
69
70
|
activeTabId,
|
|
@@ -154,6 +155,7 @@ export function FileArtifactPane({
|
|
|
154
155
|
headerClassName={headerClassName}
|
|
155
156
|
hideTitleBlock={hideTitleBlock}
|
|
156
157
|
markdown={content ?? ""}
|
|
158
|
+
urlTransform={urlTransform}
|
|
157
159
|
mode={editor.mode}
|
|
158
160
|
defaultMode={editor.defaultMode}
|
|
159
161
|
onModeChange={editor.onModeChange}
|
|
@@ -193,6 +195,7 @@ export function FileArtifactPane({
|
|
|
193
195
|
blobUrl={blobUrl}
|
|
194
196
|
mimeType={mimeType}
|
|
195
197
|
size={size}
|
|
198
|
+
urlTransform={urlTransform}
|
|
196
199
|
onDownload={onDownload}
|
|
197
200
|
hideHeader={true}
|
|
198
201
|
/>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
X,
|
|
23
23
|
type LucideIcon,
|
|
24
24
|
} from "lucide-react";
|
|
25
|
+
import type { UrlTransform } from "react-markdown";
|
|
25
26
|
import { focusRingInset } from "../lib/focus";
|
|
26
27
|
import { cn } from "../lib/utils";
|
|
27
28
|
import { Button } from "../primitives/button";
|
|
@@ -44,6 +45,8 @@ export interface FilePreviewProps {
|
|
|
44
45
|
mimeType?: string;
|
|
45
46
|
/** Size in bytes; download cards show it when known. */
|
|
46
47
|
size?: number;
|
|
48
|
+
/** Transform Markdown links and images through the shared URL policy. */
|
|
49
|
+
urlTransform?: UrlTransform;
|
|
47
50
|
onClose?: () => void;
|
|
48
51
|
onDownload?: () => void;
|
|
49
52
|
hideHeader?: boolean;
|
|
@@ -301,10 +304,16 @@ function TextPreview({ content }: { content: string }) {
|
|
|
301
304
|
);
|
|
302
305
|
}
|
|
303
306
|
|
|
304
|
-
function MarkdownPreview({
|
|
307
|
+
function MarkdownPreview({
|
|
308
|
+
content,
|
|
309
|
+
urlTransform,
|
|
310
|
+
}: {
|
|
311
|
+
content: string;
|
|
312
|
+
urlTransform?: UrlTransform;
|
|
313
|
+
}) {
|
|
305
314
|
return (
|
|
306
315
|
<div className="rounded-[var(--radius-md)] border border-border bg-background p-5">
|
|
307
|
-
<Markdown>{content}</Markdown>
|
|
316
|
+
<Markdown urlTransform={urlTransform}>{content}</Markdown>
|
|
308
317
|
</div>
|
|
309
318
|
);
|
|
310
319
|
}
|
|
@@ -358,6 +367,7 @@ function PreviewBody({
|
|
|
358
367
|
content,
|
|
359
368
|
blobUrl,
|
|
360
369
|
size,
|
|
370
|
+
urlTransform,
|
|
361
371
|
onDownload,
|
|
362
372
|
}: {
|
|
363
373
|
kind: FilePreviewKind;
|
|
@@ -365,6 +375,7 @@ function PreviewBody({
|
|
|
365
375
|
content?: string;
|
|
366
376
|
blobUrl?: string;
|
|
367
377
|
size?: number;
|
|
378
|
+
urlTransform?: UrlTransform;
|
|
368
379
|
onDownload?: () => void;
|
|
369
380
|
}) {
|
|
370
381
|
const hasText = typeof content === "string";
|
|
@@ -404,7 +415,7 @@ function PreviewBody({
|
|
|
404
415
|
if (hasText) return <CodePreview content={content} filename={filename} kind={kind} />;
|
|
405
416
|
return card(NO_INLINE_CONTENT);
|
|
406
417
|
case "markdown":
|
|
407
|
-
if (hasText) return <MarkdownPreview content={content} />;
|
|
418
|
+
if (hasText) return <MarkdownPreview content={content} urlTransform={urlTransform} />;
|
|
408
419
|
return card(NO_INLINE_CONTENT);
|
|
409
420
|
case "text":
|
|
410
421
|
if (hasText) return <TextPreview content={content} />;
|
|
@@ -423,6 +434,7 @@ export function FilePreview({
|
|
|
423
434
|
blobUrl,
|
|
424
435
|
mimeType,
|
|
425
436
|
size,
|
|
437
|
+
urlTransform,
|
|
426
438
|
onClose,
|
|
427
439
|
onDownload,
|
|
428
440
|
hideHeader = false,
|
|
@@ -473,6 +485,7 @@ export function FilePreview({
|
|
|
473
485
|
content={content}
|
|
474
486
|
blobUrl={blobUrl}
|
|
475
487
|
size={size}
|
|
488
|
+
urlTransform={urlTransform}
|
|
476
489
|
onDownload={onDownload}
|
|
477
490
|
/>
|
|
478
491
|
</div>
|
package/src/openui/index.ts
CHANGED
|
@@ -18,3 +18,13 @@ export {
|
|
|
18
18
|
type OpenUIGridNode,
|
|
19
19
|
type OpenUICardNode,
|
|
20
20
|
} from "./openui-artifact-renderer";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
OPENUI_NODE_TYPES,
|
|
24
|
+
OPENUI_NODE_JSON_SCHEMA,
|
|
25
|
+
findUnsupportedOpenUINode,
|
|
26
|
+
validateOpenUIJsonNode,
|
|
27
|
+
validateOpenUIJsonArtifact,
|
|
28
|
+
type OpenUISchemaIssue,
|
|
29
|
+
type OpenUISchemaValidation,
|
|
30
|
+
} from "./schema";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { OpenUIArtifactRenderer } from "./openui-artifact-renderer";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof OpenUIArtifactRenderer> = {
|
|
5
|
+
title: "OpenUI/ArtifactRenderer",
|
|
6
|
+
component: OpenUIArtifactRenderer,
|
|
7
|
+
decorators: [(Story) => <div className="mx-auto max-w-2xl p-6"><Story /></div>],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof OpenUIArtifactRenderer>;
|
|
12
|
+
|
|
13
|
+
export const NestedCard: Story = {
|
|
14
|
+
args: {
|
|
15
|
+
schema: {
|
|
16
|
+
type: "card",
|
|
17
|
+
title: "Release proof",
|
|
18
|
+
children: [
|
|
19
|
+
{ type: "heading", text: "Result" },
|
|
20
|
+
{ type: "stack", children: [
|
|
21
|
+
{ type: "stat", label: "Checks", value: "3" },
|
|
22
|
+
{ type: "text", text: "The saved result remains visible after reload." },
|
|
23
|
+
] },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const UnsupportedSavedNode: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
schema: {
|
|
32
|
+
type: "card",
|
|
33
|
+
children: [{ type: "section", children: [{ type: "text", text: "This content must not disappear silently." }] }],
|
|
34
|
+
} as never,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../primitives/table";
|
|
14
14
|
import { CodeBlock } from "../primitives/code-block";
|
|
15
15
|
import { Markdown } from "../markdown/markdown";
|
|
16
|
+
import { findUnsupportedOpenUINode, type OPENUI_NODE_TYPES } from "./schema";
|
|
16
17
|
|
|
17
18
|
export type OpenUIPrimitive = string | number | boolean | null | undefined;
|
|
18
19
|
|
|
@@ -148,21 +149,14 @@ export interface OpenUIArtifactRendererProps {
|
|
|
148
149
|
className?: string;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
"table",
|
|
160
|
-
"actions",
|
|
161
|
-
"separator",
|
|
162
|
-
"stack",
|
|
163
|
-
"grid",
|
|
164
|
-
"card",
|
|
165
|
-
]);
|
|
152
|
+
type RendererNodeTypesMatchSchema =
|
|
153
|
+
Exclude<OpenUIComponentNode["type"], (typeof OPENUI_NODE_TYPES)[number]> extends never
|
|
154
|
+
? Exclude<(typeof OPENUI_NODE_TYPES)[number], OpenUIComponentNode["type"]> extends never
|
|
155
|
+
? true
|
|
156
|
+
: never
|
|
157
|
+
: never;
|
|
158
|
+
const rendererNodeTypesMatchSchema: RendererNodeTypesMatchSchema = true;
|
|
159
|
+
void rendererNodeTypesMatchSchema;
|
|
166
160
|
|
|
167
161
|
const GAP_STYLES = {
|
|
168
162
|
sm: "gap-2",
|
|
@@ -200,16 +194,6 @@ function asArray<T>(value: unknown): T[] {
|
|
|
200
194
|
return Array.isArray(value) ? (value as T[]) : [];
|
|
201
195
|
}
|
|
202
196
|
|
|
203
|
-
function isOpenUIComponentNode(value: unknown): value is OpenUIComponentNode {
|
|
204
|
-
return (
|
|
205
|
-
typeof value === "object" &&
|
|
206
|
-
value !== null &&
|
|
207
|
-
"type" in value &&
|
|
208
|
-
typeof (value as { type?: unknown }).type === "string" &&
|
|
209
|
-
NODE_TYPES.has((value as { type: string }).type)
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
197
|
function renderActions(actions: OpenUIAction[], onAction?: (action: OpenUIAction) => void) {
|
|
214
198
|
if (actions.length === 0) return null;
|
|
215
199
|
|
|
@@ -394,7 +378,7 @@ function renderNode(node: OpenUIComponentNode, onAction?: (action: OpenUIAction)
|
|
|
394
378
|
column.align === "right" && "text-right tabular-nums",
|
|
395
379
|
)}
|
|
396
380
|
>
|
|
397
|
-
{formatValue(row[column.key])}
|
|
381
|
+
{formatValue(Object.prototype.hasOwnProperty.call(row, column.key) ? row[column.key] : undefined)}
|
|
398
382
|
</TableCell>
|
|
399
383
|
))}
|
|
400
384
|
</TableRow>
|
|
@@ -510,7 +494,15 @@ export function OpenUIArtifactRenderer({
|
|
|
510
494
|
onAction,
|
|
511
495
|
className,
|
|
512
496
|
}: OpenUIArtifactRendererProps) {
|
|
513
|
-
const
|
|
497
|
+
const issue = findUnsupportedOpenUINode(schema);
|
|
498
|
+
if (issue) {
|
|
499
|
+
return (
|
|
500
|
+
<div role="alert" className={cn("rounded-[var(--radius-xl)] border border-border bg-card p-5 text-sm text-foreground", className)}>
|
|
501
|
+
Cannot display this saved view. {issue.message} Path: {issue.path}.
|
|
502
|
+
</div>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
const nodes = Array.isArray(schema) ? schema : [schema];
|
|
514
506
|
|
|
515
507
|
if (nodes.length === 0) {
|
|
516
508
|
return (
|