@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.
Files changed (29) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/display/blocks/chart/ChartBlock.js +4 -5
  3. package/dist/display/blocks/csv/CsvBlock.js +2 -7
  4. package/dist/display/blocks/diff/DiffBlock.js +3 -7
  5. package/dist/display/blocks/generic/StructuredValueTabs.js +3 -6
  6. package/dist/display/blocks/json/JsonBlock.js +4 -8
  7. package/dist/display/blocks/svg/SvgBlock.js +4 -5
  8. package/dist/display/blocks/table/StreamingTableRenderer.js +12 -20
  9. package/dist/display/chat-markdown/BasicMarkdownContent.js +8 -0
  10. package/dist/display/chat-markdown/InlineCodeSnippet.js +3 -6
  11. package/dist/display/chat-markdown/block-registry/BlockRenderer.js +19 -2
  12. package/dist/display/tables/MarkdownTable.js +12 -18
  13. package/dist/display/tables/TableWithSeparatedControls.js +12 -18
  14. package/dist/host/RichContentHostGate.d.ts +10 -0
  15. package/dist/host/RichContentHostGate.js +16 -0
  16. package/dist/host/index.d.ts +2 -1
  17. package/dist/host/index.js +6 -0
  18. package/dist/host/registry.d.ts +10 -0
  19. package/dist/host/registry.js +30 -0
  20. package/dist/levels/RichContent.d.ts +1 -1
  21. package/dist/levels/RichContent.js +11 -1
  22. package/dist/levels/prose/inline-level-elements.js +1 -1
  23. package/dist/levels/prose/prose-inline-elements.js +1 -1
  24. package/dist/levels/server/RichContentServer.js +1 -1
  25. package/dist/markdown-core/MarkdownCore.d.ts +1 -1
  26. package/dist/markdown-core/MarkdownCore.js +11 -1
  27. package/dist/markdown-core/syntax/elements/DetailsElements.js +1 -1
  28. package/dist/rich-document/RichDocument.js +10 -0
  29. 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
- try {
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.map(
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
- try {
88
- if (!await copyText(content.trim())) return;
89
- setCopied(true);
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
- try {
50
- if (!await copyText(json)) return;
51
- setCopied(true);
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
- try {
492
- if (!await copyText(content)) return;
493
- setCopied(true);
494
- toast.success("Copied JSON to clipboard");
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
- try {
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
- const formattedTable = generateMarkdownTable();
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
- try {
273
- if (!await copyText(
274
- JSON.stringify(tableData.normalizedData, null, 2)
275
- )) return;
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
- try {
283
- if (content) {
284
- await copyText(content, "Markdown copied to clipboard");
285
- } else {
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
- try {
94
- if (!await copyText(code)) return;
95
- setCopied(true);
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
- if (isKindSlug(readEnvelope(block.metadata)?.root.kind)) return block;
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
- value: {},
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
- const formattedTable = generateMarkdownTable();
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
- try {
274
- if (!await copyText(
275
- JSON.stringify(internalTableData.normalizedData, null, 2)
276
- )) return;
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
- try {
284
- if (content) {
285
- await copyText(content, "Markdown copied to clipboard");
286
- } else {
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
- const formattedTable = generateMarkdownTable();
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
- try {
91
- if (!await copyText(
92
- JSON.stringify(debouncedTableData.normalizedData, null, 2)
93
- )) return;
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
- try {
101
- if (content) {
102
- await copyText(content, "Markdown copied to clipboard");
103
- } else {
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
+ };
@@ -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";
@@ -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
  };
@@ -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;
@@ -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({ source, level, isStreaming, className, depthCap, variant, links, headingAnchors, imagePolicy, gfmCell, onContentChange, applyLocalEdits, analysisData, messageId, conversationId, allowFullScreenEditor, hideCopyButton, }: RichContentProps): import("react").JSX.Element;
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({ streaming, children, ...rest }: MarkdownCoreProps): import("react").JSX.Element;
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.20",
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$).*'",