@ai-matrx/rich-content 0.2.20 → 0.2.25
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 +37 -0
- package/dist/display/blocks/chart/ChartBlock.js +4 -5
- package/dist/display/blocks/csv/CsvBlock.js +2 -7
- package/dist/display/blocks/diff/DiffBlock.js +3 -7
- package/dist/display/blocks/generic/StructuredValueTabs.js +3 -6
- package/dist/display/blocks/json/JsonBlock.js +4 -8
- package/dist/display/blocks/svg/SvgBlock.js +4 -5
- package/dist/display/blocks/table/StreamingTableRenderer.js +12 -20
- package/dist/display/chat-markdown/BasicMarkdownContent.js +8 -0
- package/dist/display/chat-markdown/InlineCodeSnippet.js +3 -6
- package/dist/display/chat-markdown/block-registry/BlockRenderer.js +19 -2
- package/dist/display/tables/MarkdownTable.js +12 -18
- package/dist/display/tables/TableWithSeparatedControls.js +12 -18
- package/dist/host/RichContentHostGate.d.ts +10 -0
- package/dist/host/RichContentHostGate.js +16 -0
- package/dist/host/index.d.ts +2 -1
- package/dist/host/index.js +6 -0
- package/dist/host/registry.d.ts +10 -0
- package/dist/host/registry.js +30 -0
- package/dist/levels/RichContent.d.ts +1 -1
- package/dist/levels/RichContent.js +11 -1
- package/dist/levels/prose/inline-level-elements.js +1 -1
- package/dist/levels/prose/prose-inline-elements.js +1 -1
- package/dist/levels/server/RichContentServer.js +1 -1
- package/dist/markdown-core/MarkdownCore.d.ts +1 -1
- package/dist/markdown-core/MarkdownCore.js +11 -1
- package/dist/markdown-core/syntax/elements/DetailsElements.js +1 -1
- package/dist/rich-document/RichDocument.js +10 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.25
|
|
4
|
+
|
|
5
|
+
- The server level no longer calls a client reference: `RichContentServer`, the inline/prose element maps and `DetailsElements` import `cn` from `@ai-matrx/design-system/cn` (server-safe) instead of the design-system root, whose `"use client"` stamp made `cn()` throw "Attempted to call cn() from the server" in every Server Component render (it failed matrx-frontend's production build at "Collecting page data").
|
|
6
|
+
- Guard: `pnpm check:server-boundary` (in `check:package`, with `--self-test`) walks everything the server roots reach without crossing a `"use client"` file and fails on any value imported from a client entry.
|
|
7
|
+
|
|
8
|
+
**Consumer action:** none — update to latest; server pages using `RichContentServer` render again.
|
|
9
|
+
|
|
10
|
+
## 0.2.24
|
|
11
|
+
|
|
12
|
+
- Publishes the lazy-host loader (0.2.21 failed its packed-consumer check on a doc-comment `import()` in `host/registry.d.ts`; 0.2.22 and 0.2.23 carry the same). No API change.
|
|
13
|
+
|
|
14
|
+
**Consumer action:** none (see 0.2.21 for `configureRichContentHostLoader`).
|
|
15
|
+
|
|
16
|
+
## 0.2.23
|
|
17
|
+
|
|
18
|
+
- A structured answer whose `"__kind"` is null (a strict-schema model filling the optional key) keeps its fields on the broken-structured floor. `settleBrokenKindRoute` set `value: {}`, so every such `expect: "json"` run drew "No result returned" over a complete answer.
|
|
19
|
+
- The CSV block writes an edited table back through THE one writer, `@ai-matrx/kit/delimited` (AP-2), unguarded on purpose (a lossless round-trip of the author's block).
|
|
20
|
+
|
|
21
|
+
**Consumer action:** none.
|
|
22
|
+
|
|
23
|
+
## 0.2.22
|
|
24
|
+
|
|
25
|
+
- A failed copy is no longer silent: the chart and SVG blocks show their specific failure toast from the kit copy's `false` branch (their `catch` could never run). Table copy builds the table inside the `try` and copies outside it, so a build error still reports and the copy reports through the hook.
|
|
26
|
+
- Dead `catch` blocks around the never-throwing kit copy removed from the diff, JSON, structured-value, inline-code and table blocks.
|
|
27
|
+
|
|
28
|
+
### Consumer action
|
|
29
|
+
|
|
30
|
+
- None required.
|
|
31
|
+
|
|
32
|
+
## 0.2.21
|
|
33
|
+
|
|
34
|
+
- Lazy host: `configureRichContentHostLoader(() => import("./host"))` (from `@ai-matrx/rich-content/host`) lets an app keep its whole host setup out of the first-load bundle. The load starts at once; `RichContent`, `MarkdownCore`, `RichDocument` and `BasicMarkdownContent` suspend on it behind `RichContentHostGate`, showing the source as plain text in the space the content will fill. A loader that rejects is reported (`console.error`) and the engine runs on its announced bare defaults. `RichContentHostGate` and `richContentHostReady` are exported for an app's own entry points.
|
|
35
|
+
|
|
36
|
+
### Consumer action
|
|
37
|
+
|
|
38
|
+
- Optional. Keep `configureRichContent(...)` where it is, or move it into a module and register `configureRichContentHostLoader(() => import("./that-module"))` once at the app root.
|
|
39
|
+
|
|
3
40
|
## 0.2.20
|
|
4
41
|
|
|
5
42
|
- Readable Markdown copy now uses Content IR's canonical fence opener and closer. Inline code made from four or more backticks remains prose, while closed, unclosed, and nested fenced-code bodies are copied without their outer fence.
|
|
@@ -37,13 +37,12 @@ const ChartBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
37
37
|
const presentation = useCanvasPresentation();
|
|
38
38
|
const paneLayout = chartPaneLayout(presentation, spec?.type ?? "");
|
|
39
39
|
const handleCopy = async () => {
|
|
40
|
-
|
|
41
|
-
if (!await copyText(source)) return;
|
|
42
|
-
setCopied(true);
|
|
43
|
-
setTimeout(() => setCopied(false), 1500);
|
|
44
|
-
} catch {
|
|
40
|
+
if (!await copyText(source)) {
|
|
45
41
|
toast.error("Couldn't copy the chart source");
|
|
42
|
+
return;
|
|
46
43
|
}
|
|
44
|
+
setCopied(true);
|
|
45
|
+
setTimeout(() => setCopied(false), 1500);
|
|
47
46
|
};
|
|
48
47
|
return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border bg-card", className), children: [
|
|
49
48
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toDelimited } from "@ai-matrx/kit/delimited";
|
|
4
5
|
import { useState, useEffect } from "react";
|
|
5
6
|
import { cn } from "@ai-matrx/design-system";
|
|
6
7
|
import {
|
|
@@ -53,13 +54,7 @@ function parseCsv(content, delimiter) {
|
|
|
53
54
|
return rows;
|
|
54
55
|
}
|
|
55
56
|
function rowsToDelimitedContent(rows, delimiter) {
|
|
56
|
-
return rows
|
|
57
|
-
(row) => row.map((cell) => {
|
|
58
|
-
const needsQuote = cell.includes(delimiter) || cell.includes('"') || cell.includes("\n") || cell.includes("\r");
|
|
59
|
-
const escaped = cell.replace(/"/g, '""');
|
|
60
|
-
return needsQuote ? `"${escaped}"` : escaped;
|
|
61
|
-
}).join(delimiter)
|
|
62
|
-
).join("\n");
|
|
57
|
+
return toDelimited(rows, { delimiter, spreadsheetSafe: false });
|
|
63
58
|
}
|
|
64
59
|
const CsvBlock = ({
|
|
65
60
|
content,
|
|
@@ -84,13 +84,9 @@ const DiffSpecBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
84
84
|
authoredExplicitly: spec?.splitExplicit ?? false
|
|
85
85
|
});
|
|
86
86
|
const handleCopy = async () => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setTimeout(() => setCopied(false), 1500);
|
|
91
|
-
} catch {
|
|
92
|
-
toast.error("Couldn't copy");
|
|
93
|
-
}
|
|
87
|
+
if (!await copyText(content.trim())) return;
|
|
88
|
+
setCopied(true);
|
|
89
|
+
setTimeout(() => setCopied(false), 1500);
|
|
94
90
|
};
|
|
95
91
|
return /* @__PURE__ */ jsxs("div", { className: cn("my-3 overflow-hidden rounded-lg border border-border bg-card", className), children: [
|
|
96
92
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-muted/50 px-3 py-1.5", children: [
|
|
@@ -46,12 +46,9 @@ function StructuredValueTabs({
|
|
|
46
46
|
const hasSlot = useHasTileActionsSlot();
|
|
47
47
|
const json = value !== void 0 && value !== null ? JSON.stringify(value, null, 2) : raw ?? "";
|
|
48
48
|
const copy = async () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
setTimeout(() => setCopied(false), 1500);
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
49
|
+
if (!await copyText(json)) return;
|
|
50
|
+
setCopied(true);
|
|
51
|
+
setTimeout(() => setCopied(false), 1500);
|
|
55
52
|
};
|
|
56
53
|
const jsonActive = tab === "json";
|
|
57
54
|
const errorsActive = tab === "errors";
|
|
@@ -488,14 +488,10 @@ const JsonViewHeader = ({
|
|
|
488
488
|
const kebabRef = useRef(null);
|
|
489
489
|
const [copied, setCopied] = useState(false);
|
|
490
490
|
const handleCopy = async () => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
setTimeout(() => setCopied(false), 1500);
|
|
496
|
-
} catch {
|
|
497
|
-
toast.error("Failed to copy");
|
|
498
|
-
}
|
|
491
|
+
if (!await copyText(content)) return;
|
|
492
|
+
setCopied(true);
|
|
493
|
+
toast.success("Copied JSON to clipboard");
|
|
494
|
+
setTimeout(() => setCopied(false), 1500);
|
|
499
495
|
};
|
|
500
496
|
const fullMenuItems = [
|
|
501
497
|
{
|
|
@@ -40,13 +40,12 @@ const SvgBlock = ({ content = "", isStreamActive = false, className }) => {
|
|
|
40
40
|
const [copied, setCopied] = useState(false);
|
|
41
41
|
const [fullscreen, setFullscreen] = useState(false);
|
|
42
42
|
const handleCopy = async () => {
|
|
43
|
-
|
|
44
|
-
if (!await copyText(svg)) return;
|
|
45
|
-
setCopied(true);
|
|
46
|
-
setTimeout(() => setCopied(false), 1500);
|
|
47
|
-
} catch {
|
|
43
|
+
if (!await copyText(svg)) {
|
|
48
44
|
toast.error("Couldn't copy the SVG");
|
|
45
|
+
return;
|
|
49
46
|
}
|
|
47
|
+
setCopied(true);
|
|
48
|
+
setTimeout(() => setCopied(false), 1500);
|
|
50
49
|
};
|
|
51
50
|
const handleDownload = () => {
|
|
52
51
|
try {
|
|
@@ -261,34 +261,26 @@ const StreamingTableRendererCore = ({
|
|
|
261
261
|
const showStreamingIndicator = isStreamActive && metadata?.hasPartialContent;
|
|
262
262
|
const generateMarkdownTable = () => rewriteTableSource(content, { headers, rows });
|
|
263
263
|
const copyTableToClipboard = async () => {
|
|
264
|
+
let formattedTable;
|
|
264
265
|
try {
|
|
265
|
-
|
|
266
|
-
await copyText(formattedTable, "Table copied to clipboard");
|
|
266
|
+
formattedTable = generateMarkdownTable();
|
|
267
267
|
} catch (err) {
|
|
268
268
|
toast.error(err instanceof Error ? err.message : "Failed to copy table");
|
|
269
|
+
return;
|
|
269
270
|
}
|
|
271
|
+
await copyText(formattedTable, "Table copied to clipboard");
|
|
270
272
|
};
|
|
271
273
|
const copyJsonToClipboard = async () => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
toast.success("JSON copied to clipboard");
|
|
277
|
-
} catch (err) {
|
|
278
|
-
toast.error(err instanceof Error ? err.message : "Failed to copy JSON");
|
|
279
|
-
}
|
|
274
|
+
if (!await copyText(
|
|
275
|
+
JSON.stringify(tableData.normalizedData, null, 2)
|
|
276
|
+
)) return;
|
|
277
|
+
toast.success("JSON copied to clipboard");
|
|
280
278
|
};
|
|
281
279
|
const copyMarkdownToClipboard = async () => {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
copyTableToClipboard();
|
|
287
|
-
}
|
|
288
|
-
} catch (err) {
|
|
289
|
-
toast.error(
|
|
290
|
-
err instanceof Error ? err.message : "Failed to copy markdown"
|
|
291
|
-
);
|
|
280
|
+
if (content) {
|
|
281
|
+
await copyText(content, "Markdown copied to clipboard");
|
|
282
|
+
} else {
|
|
283
|
+
await copyTableToClipboard();
|
|
292
284
|
}
|
|
293
285
|
};
|
|
294
286
|
const downloadCSV = () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { RichContentHostGate } from "../../host/RichContentHostGate.js";
|
|
3
4
|
import MarkdownCore from "../../markdown-core/MarkdownCore.js";
|
|
4
5
|
import { useState, useMemo, useEffect } from "react";
|
|
5
6
|
import {
|
|
@@ -25,6 +26,13 @@ import { withImagePolicy } from "../../levels/prose/remote-image-policy.js";
|
|
|
25
26
|
import { KindTextGate } from "./KindTextGate.js";
|
|
26
27
|
import { nonJsonKindsAsCode, spelledKindsAsOneLine } from "@ai-matrx/content-ir/surfaces";
|
|
27
28
|
const BasicMarkdownContent = (props) => /* @__PURE__ */ jsx(
|
|
29
|
+
RichContentHostGate,
|
|
30
|
+
{
|
|
31
|
+
fallback: /* @__PURE__ */ jsx("div", { "data-rich-content-host-loading": true, style: { whiteSpace: "pre-wrap" }, children: props.content }),
|
|
32
|
+
children: /* @__PURE__ */ jsx(BasicMarkdownContentKindGated, { ...props })
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
const BasicMarkdownContentKindGated = (props) => /* @__PURE__ */ jsx(
|
|
28
36
|
KindTextGate,
|
|
29
37
|
{
|
|
30
38
|
component: "BasicMarkdownContent",
|
|
@@ -90,12 +90,9 @@ const InlineCodeSnippet = ({
|
|
|
90
90
|
}
|
|
91
91
|
const langColor = language ? LANGUAGE_COLORS[language] : void 0;
|
|
92
92
|
const handleCopy = async () => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
93
|
+
if (!await copyText(code)) return;
|
|
94
|
+
setCopied(true);
|
|
95
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
99
96
|
};
|
|
100
97
|
return /* @__PURE__ */ jsxs(
|
|
101
98
|
"div",
|
|
@@ -143,7 +143,8 @@ function settleUnnamedKindRoute(block) {
|
|
|
143
143
|
if (!isJsonFenceLanguage(block.language) || !hasKindKey(content, { json5: isJson5Language(block.language) })) {
|
|
144
144
|
return block;
|
|
145
145
|
}
|
|
146
|
-
|
|
146
|
+
const parsed = readEnvelope(block.metadata);
|
|
147
|
+
if (isKindSlug(parsed?.root.kind)) return block;
|
|
147
148
|
const broken = {
|
|
148
149
|
v: IR_VERSION,
|
|
149
150
|
engine: "fe-kind-parser",
|
|
@@ -155,7 +156,11 @@ function settleUnnamedKindRoute(block) {
|
|
|
155
156
|
discriminator: { format: "json", key: "__kind" },
|
|
156
157
|
status: "error",
|
|
157
158
|
path: [],
|
|
158
|
-
|
|
159
|
+
// The fields still arrived: a structured answer whose `"__kind"` is
|
|
160
|
+
// null (a strict-schema model filling an optional key) is a complete
|
|
161
|
+
// answer and the floor draws it. An empty value here drew every such
|
|
162
|
+
// `expect: "json"` run as "No result returned" (Space Builder, 2026-10-07).
|
|
163
|
+
value: settledFields(parsed, content),
|
|
159
164
|
residue: {
|
|
160
165
|
extra: null,
|
|
161
166
|
optionalMissing: null,
|
|
@@ -174,6 +179,18 @@ function settleUnnamedKindRoute(block) {
|
|
|
174
179
|
metadata: { ...block.metadata ?? {}, [IR_ENVELOPE_KEY]: broken }
|
|
175
180
|
};
|
|
176
181
|
}
|
|
182
|
+
function settledFields(envelope, content) {
|
|
183
|
+
if (envelope?.root.status === "complete") {
|
|
184
|
+
const value = reconstructRegionValue(envelope);
|
|
185
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const value = JSON.parse(content);
|
|
189
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
190
|
+
} catch {
|
|
191
|
+
return {};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
177
194
|
function unparsedKindPendingEnvelope(block) {
|
|
178
195
|
if (!block.isStreamingBlock) return null;
|
|
179
196
|
const text = block.content ?? "";
|
|
@@ -262,32 +262,26 @@ const MarkdownTable = ({
|
|
|
262
262
|
return true;
|
|
263
263
|
};
|
|
264
264
|
const copyTableToClipboard = async () => {
|
|
265
|
+
let formattedTable;
|
|
265
266
|
try {
|
|
266
|
-
|
|
267
|
-
await copyText(formattedTable, "Table copied to clipboard");
|
|
267
|
+
formattedTable = generateMarkdownTable();
|
|
268
268
|
} catch (err) {
|
|
269
269
|
toast.error(err instanceof Error ? err.message : "Failed to copy table");
|
|
270
|
+
return;
|
|
270
271
|
}
|
|
272
|
+
await copyText(formattedTable, "Table copied to clipboard");
|
|
271
273
|
};
|
|
272
274
|
const copyJsonToClipboard = async () => {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
toast.success("JSON copied to clipboard");
|
|
278
|
-
} catch (err) {
|
|
279
|
-
toast.error(err instanceof Error ? err.message : "Failed to copy JSON");
|
|
280
|
-
}
|
|
275
|
+
if (!await copyText(
|
|
276
|
+
JSON.stringify(internalTableData.normalizedData, null, 2)
|
|
277
|
+
)) return;
|
|
278
|
+
toast.success("JSON copied to clipboard");
|
|
281
279
|
};
|
|
282
280
|
const copyMarkdownToClipboard = async () => {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
copyTableToClipboard();
|
|
288
|
-
}
|
|
289
|
-
} catch (err) {
|
|
290
|
-
toast.error(err instanceof Error ? err.message : "Failed to copy markdown");
|
|
281
|
+
if (content) {
|
|
282
|
+
await copyText(content, "Markdown copied to clipboard");
|
|
283
|
+
} else {
|
|
284
|
+
await copyTableToClipboard();
|
|
291
285
|
}
|
|
292
286
|
};
|
|
293
287
|
const downloadCSV = () => {
|
|
@@ -79,32 +79,26 @@ const TableControls = ({
|
|
|
79
79
|
return true;
|
|
80
80
|
}, [onContentChange, content, generateMarkdownTable, toast]);
|
|
81
81
|
const copyTableToClipboard = useCallback(async () => {
|
|
82
|
+
let formattedTable;
|
|
82
83
|
try {
|
|
83
|
-
|
|
84
|
-
await copyText(formattedTable, "Table copied to clipboard");
|
|
84
|
+
formattedTable = generateMarkdownTable();
|
|
85
85
|
} catch (err) {
|
|
86
86
|
toast.error(err instanceof Error ? err.message : "Failed to copy table");
|
|
87
|
+
return;
|
|
87
88
|
}
|
|
89
|
+
await copyText(formattedTable, "Table copied to clipboard");
|
|
88
90
|
}, [generateMarkdownTable, toast]);
|
|
89
91
|
const copyJsonToClipboard = useCallback(async () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
toast.success("JSON copied to clipboard");
|
|
95
|
-
} catch (err) {
|
|
96
|
-
toast.error(err instanceof Error ? err.message : "Failed to copy JSON");
|
|
97
|
-
}
|
|
92
|
+
if (!await copyText(
|
|
93
|
+
JSON.stringify(debouncedTableData.normalizedData, null, 2)
|
|
94
|
+
)) return;
|
|
95
|
+
toast.success("JSON copied to clipboard");
|
|
98
96
|
}, [debouncedTableData.normalizedData, toast]);
|
|
99
97
|
const copyMarkdownToClipboard = useCallback(async () => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
copyTableToClipboard();
|
|
105
|
-
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
toast.error(err instanceof Error ? err.message : "Failed to copy markdown");
|
|
98
|
+
if (content) {
|
|
99
|
+
await copyText(content, "Markdown copied to clipboard");
|
|
100
|
+
} else {
|
|
101
|
+
await copyTableToClipboard();
|
|
108
102
|
}
|
|
109
103
|
}, [content, copyTableToClipboard, toast]);
|
|
110
104
|
const downloadCSV = useCallback(() => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Suspends its children until the app's lazy host (`configureRichContentHostLoader`) has loaded.
|
|
4
|
+
* With no loader registered, or once it has settled, it renders the children directly. The
|
|
5
|
+
* `fallback` keeps the space the children fill — pass the plain text, never nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function RichContentHostGate({ children, fallback }: {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
fallback: ReactNode;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Suspense, use } from "react";
|
|
4
|
+
import { richContentHostPending } from "./registry.js";
|
|
5
|
+
function Wait({ children }) {
|
|
6
|
+
const pending = richContentHostPending();
|
|
7
|
+
if (pending) use(pending);
|
|
8
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
9
|
+
}
|
|
10
|
+
function RichContentHostGate({ children, fallback }) {
|
|
11
|
+
if (!richContentHostPending()) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
12
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback, children: /* @__PURE__ */ jsx(Wait, { children }) });
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
RichContentHostGate
|
|
16
|
+
};
|
package/dist/host/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
/** `@ai-matrx/rich-content/host` — configure the engine once at the app root. */
|
|
2
|
-
export { configureRichContent, resetRichContentHost, hostCapability, subscribeRichContentHost, richContentHostVersion, announceBareHost, BARE_HOST_ATTR, } from "./registry.js";
|
|
2
|
+
export { configureRichContent, resetRichContentHost, hostCapability, subscribeRichContentHost, richContentHostVersion, announceBareHost, configureRichContentHostLoader, richContentHostReady, BARE_HOST_ATTR, } from "./registry.js";
|
|
3
3
|
export type { RichContentHost, RichContentToast, RichContentErrorReport, RichContentWikilinks, RichContentPerson, RichContentSplitterEnvelopes, WikiResolution, } from "./types.js";
|
|
4
|
+
export { RichContentHostGate } from "./RichContentHostGate.js";
|
package/dist/host/index.js
CHANGED
|
@@ -5,14 +5,20 @@ import {
|
|
|
5
5
|
subscribeRichContentHost,
|
|
6
6
|
richContentHostVersion,
|
|
7
7
|
announceBareHost,
|
|
8
|
+
configureRichContentHostLoader,
|
|
9
|
+
richContentHostReady,
|
|
8
10
|
BARE_HOST_ATTR
|
|
9
11
|
} from "./registry.js";
|
|
12
|
+
import { RichContentHostGate } from "./RichContentHostGate.js";
|
|
10
13
|
export {
|
|
11
14
|
BARE_HOST_ATTR,
|
|
15
|
+
RichContentHostGate,
|
|
12
16
|
announceBareHost,
|
|
13
17
|
configureRichContent,
|
|
18
|
+
configureRichContentHostLoader,
|
|
14
19
|
hostCapability,
|
|
15
20
|
resetRichContentHost,
|
|
21
|
+
richContentHostReady,
|
|
16
22
|
richContentHostVersion,
|
|
17
23
|
subscribeRichContentHost
|
|
18
24
|
};
|
package/dist/host/registry.d.ts
CHANGED
|
@@ -27,3 +27,13 @@ export declare const BARE_HOST_ATTR: {
|
|
|
27
27
|
};
|
|
28
28
|
/** Run the host's `ensureBlockRegistrations` once (see RichContentHost). */
|
|
29
29
|
export declare function ensureHostBlockRegistrations(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Register the app's async host loader — a function that dynamically imports the app's host module,
|
|
32
|
+
* whose module calls `configureRichContent`. Idempotent: the first registration wins (a second graph copy of this
|
|
33
|
+
* module must not start a second load).
|
|
34
|
+
*/
|
|
35
|
+
export declare function configureRichContentHostLoader(load: () => Promise<unknown>): void;
|
|
36
|
+
/** True when no loader is pending (none registered, or it has settled). */
|
|
37
|
+
export declare function richContentHostReady(): boolean;
|
|
38
|
+
/** The pending host load, or null when ready. Entry points `use()` it. */
|
|
39
|
+
export declare function richContentHostPending(): Promise<void> | null;
|
package/dist/host/registry.js
CHANGED
|
@@ -53,13 +53,43 @@ function ensureHostBlockRegistrations() {
|
|
|
53
53
|
g[ENSURED_KEY] = true;
|
|
54
54
|
ensure();
|
|
55
55
|
}
|
|
56
|
+
const LOADER_KEY = /* @__PURE__ */ Symbol.for("ai-matrx.rich-content.host.loader");
|
|
57
|
+
function loaderState() {
|
|
58
|
+
return globalThis[LOADER_KEY];
|
|
59
|
+
}
|
|
60
|
+
function configureRichContentHostLoader(load) {
|
|
61
|
+
const g = globalThis;
|
|
62
|
+
if (g[LOADER_KEY]) return;
|
|
63
|
+
const state = { promise: void 0, settled: false };
|
|
64
|
+
state.promise = Promise.resolve().then(load).then(
|
|
65
|
+
() => {
|
|
66
|
+
state.settled = true;
|
|
67
|
+
},
|
|
68
|
+
(error) => {
|
|
69
|
+
state.settled = true;
|
|
70
|
+
console.error("[@ai-matrx/rich-content] The host loader failed; the engine runs on its bare defaults.", error);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
g[LOADER_KEY] = state;
|
|
74
|
+
}
|
|
75
|
+
function richContentHostReady() {
|
|
76
|
+
const s = loaderState();
|
|
77
|
+
return !s || s.settled;
|
|
78
|
+
}
|
|
79
|
+
function richContentHostPending() {
|
|
80
|
+
const s = loaderState();
|
|
81
|
+
return s && !s.settled ? s.promise : null;
|
|
82
|
+
}
|
|
56
83
|
export {
|
|
57
84
|
BARE_HOST_ATTR,
|
|
58
85
|
announceBareHost,
|
|
59
86
|
configureRichContent,
|
|
87
|
+
configureRichContentHostLoader,
|
|
60
88
|
ensureHostBlockRegistrations,
|
|
61
89
|
hostCapability,
|
|
62
90
|
resetRichContentHost,
|
|
91
|
+
richContentHostPending,
|
|
92
|
+
richContentHostReady,
|
|
63
93
|
richContentHostVersion,
|
|
64
94
|
subscribeRichContentHost
|
|
65
95
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { RichContentProps } from "./rich-content-types.js";
|
|
2
|
-
export declare function RichContent(
|
|
2
|
+
export declare function RichContent(props: RichContentProps): import("react").JSX.Element;
|
|
3
3
|
export default RichContent;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { clientLazy } from "../utils/client-lazy.js";
|
|
4
|
+
import { RichContentHostGate } from "../host/RichContentHostGate.js";
|
|
4
5
|
import { MarkdownStream } from "../host/app-bindings.js";
|
|
5
6
|
import { RichContentDepthProvider } from "./depth.js";
|
|
6
7
|
import { HeadingAnchorsProvider } from "../markdown-core/heading-anchors-context.js";
|
|
@@ -10,7 +11,16 @@ const RichContentStandardImpl = clientLazy(
|
|
|
10
11
|
() => import("./RichContentStandardImpl.js"),
|
|
11
12
|
{ loading: () => null }
|
|
12
13
|
);
|
|
13
|
-
function RichContent({
|
|
14
|
+
function RichContent(props) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
RichContentHostGate,
|
|
17
|
+
{
|
|
18
|
+
fallback: /* @__PURE__ */ jsx("div", { className: props.className, "data-rich-content-host-loading": true, style: { whiteSpace: "pre-wrap" }, children: props.source }),
|
|
19
|
+
children: /* @__PURE__ */ jsx(RichContentRouted, { ...props })
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function RichContentRouted({
|
|
14
24
|
source,
|
|
15
25
|
level,
|
|
16
26
|
isStreaming,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "@ai-matrx/design-system";
|
|
2
|
+
import { cn } from "@ai-matrx/design-system/cn";
|
|
3
3
|
import { isHeadingAnchorProps } from "../../markdown-core/heading-anchors-props.js";
|
|
4
4
|
import { THICK_HR_SENTINEL } from "./prose-prepare.js";
|
|
5
5
|
import { PROSE_INLINE_ELEMENTS } from "./prose-inline-elements.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "@ai-matrx/design-system";
|
|
2
|
+
import { cn } from "@ai-matrx/design-system/cn";
|
|
3
3
|
import { LinkComponent } from "../../display/blocks/links/LinkComponent.js";
|
|
4
4
|
import { useHeadingAnchors } from "../../markdown-core/heading-anchors-context.js";
|
|
5
5
|
import { isHeadingAnchorProps } from "../../markdown-core/heading-anchors-props.js";
|
|
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import "server-only";
|
|
3
3
|
import { guardMarkdownDelimiters } from "@ai-matrx/kit/delimiter-guard";
|
|
4
4
|
import MarkdownCoreServer from "../../markdown-core/MarkdownCoreServer.js";
|
|
5
|
-
import { cn } from "@ai-matrx/design-system";
|
|
5
|
+
import { cn } from "@ai-matrx/design-system/cn";
|
|
6
6
|
import { StaticStandard } from "../standard/static-standard.js";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_RICH_CONTENT_DEPTH_CAP
|
|
@@ -3,4 +3,4 @@ export type { MarkdownCoreProps, MarkdownPreset } from "./markdown-core-types.js
|
|
|
3
3
|
export declare function MarkdownCoreLoading({ error }: {
|
|
4
4
|
error?: Error | null;
|
|
5
5
|
}): import("react").JSX.Element | null;
|
|
6
|
-
export default function MarkdownCore(
|
|
6
|
+
export default function MarkdownCore(props: MarkdownCoreProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { clientLazy } from "../utils/client-lazy.js";
|
|
4
|
+
import { RichContentHostGate } from "../host/RichContentHostGate.js";
|
|
4
5
|
import { healStreamingMarkdown } from "./stream-heal.js";
|
|
5
6
|
import { useMarkdownStreaming } from "./streaming-context.js";
|
|
6
7
|
function MarkdownCoreLoading({ error }) {
|
|
@@ -17,7 +18,16 @@ function MarkdownCoreLoading({ error }) {
|
|
|
17
18
|
const MarkdownCoreLeaf = clientLazy(() => import("./MarkdownCoreImpl.js"), {
|
|
18
19
|
loading: MarkdownCoreLoading
|
|
19
20
|
});
|
|
20
|
-
function MarkdownCore({
|
|
21
|
+
function MarkdownCore(props) {
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
RichContentHostGate,
|
|
24
|
+
{
|
|
25
|
+
fallback: /* @__PURE__ */ jsx("span", { "data-rich-content-host-loading": true, style: { whiteSpace: "pre-wrap" }, children: props.children }),
|
|
26
|
+
children: /* @__PURE__ */ jsx(MarkdownCoreGated, { ...props })
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
function MarkdownCoreGated({
|
|
21
31
|
streaming,
|
|
22
32
|
children,
|
|
23
33
|
...rest
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cn } from "@ai-matrx/design-system";
|
|
2
|
+
import { cn } from "@ai-matrx/design-system/cn";
|
|
3
3
|
function DetailsElement({ children, className, open, id, node: _node, ...rest }) {
|
|
4
4
|
if (className?.includes("matrx-callout")) {
|
|
5
5
|
return /* @__PURE__ */ jsx("details", { ...rest, className, open, id, children });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from "@ai-matrx/design-system";
|
|
4
|
+
import { RichContentHostGate } from "../host/RichContentHostGate.js";
|
|
4
5
|
import { useActionSurfaceProvider } from "./runtime/useActionSurfaceProvider.js";
|
|
5
6
|
import { RegistryContextMenu } from "./RegistryContextMenu.js";
|
|
6
7
|
import { getSourceAdapter } from "./actions/sources/index.js";
|
|
@@ -22,6 +23,15 @@ import {
|
|
|
22
23
|
resolveSpecimenMode
|
|
23
24
|
} from "../display/specimen/SpecimenContext.js";
|
|
24
25
|
function RichDocument(props) {
|
|
26
|
+
return /* @__PURE__ */ jsx(
|
|
27
|
+
RichContentHostGate,
|
|
28
|
+
{
|
|
29
|
+
fallback: /* @__PURE__ */ jsx("div", { className: props.className, "data-rich-content-host-loading": true, style: { whiteSpace: "pre-wrap" }, children: props.content }),
|
|
30
|
+
children: /* @__PURE__ */ jsx(RichDocumentGated, { ...props })
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
function RichDocumentGated(props) {
|
|
25
35
|
const {
|
|
26
36
|
source,
|
|
27
37
|
actions: actionsProp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/rich-content",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "The AI Matrx formatted-text engine: the one markdown renderer, streaming markdown, the block splitter and renderer, the generic blocks, <RichContent> levels, the rich-document action registry and menus, copy, and the selection toolbar — as portable React.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -140,8 +140,8 @@
|
|
|
140
140
|
"@ai-matrx/diff": "latest",
|
|
141
141
|
"@ai-matrx/icons": "latest",
|
|
142
142
|
"@ai-matrx/kit": "latest",
|
|
143
|
-
"@ai-matrx/media": "latest",
|
|
144
143
|
"@ai-matrx/print": "latest",
|
|
144
|
+
"@ai-matrx/media": "latest",
|
|
145
145
|
"@ai-matrx/records": "latest",
|
|
146
146
|
"@ai-matrx/records-ui": "latest",
|
|
147
147
|
"@ai-matrx/tap-target": "latest"
|
|
@@ -178,8 +178,9 @@
|
|
|
178
178
|
},
|
|
179
179
|
"scripts": {
|
|
180
180
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsup && node ./scripts/copy-css.mjs && node ./scripts/tsc-own.mjs -p tsconfig.build.json && node ./scripts/finish-build.mjs",
|
|
181
|
-
"check:package": "pnpm build && publint && pnpm verify:modules-tarball && pnpm verify:tarball && pnpm check:host-boundary",
|
|
181
|
+
"check:package": "pnpm build && publint && pnpm verify:modules-tarball && pnpm verify:tarball && pnpm check:host-boundary && pnpm check:server-boundary",
|
|
182
182
|
"check:host-boundary": "node ./scripts/check-host-boundary.mjs && node ./scripts/check-host-boundary.mjs --self-test",
|
|
183
|
+
"check:server-boundary": "node ./scripts/check-server-boundary.mjs && node ./scripts/check-server-boundary.mjs --self-test",
|
|
183
184
|
"deps:write": "node ./scripts/finish-build.mjs --write-deps",
|
|
184
185
|
"typecheck": "node ./scripts/tsc-own.mjs -p tsconfig.json",
|
|
185
186
|
"test": "pnpm test:linear-time && jest -c jest.config.mjs --testPathPatterns='^(?!.*regex-linear-time\\.guard\\.test\\.ts$).*'",
|