@immediately-run/sdk 0.29.0 → 0.31.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.
Files changed (83) hide show
  1. package/dist/boot.cjs +5 -1
  2. package/dist/boot.cjs.map +1 -1
  3. package/dist/boot.js +5 -1
  4. package/dist/boot.js.map +1 -1
  5. package/dist/components/Link.cjs +25 -0
  6. package/dist/components/Link.cjs.map +1 -1
  7. package/dist/components/Link.d.cts +7 -1
  8. package/dist/components/Link.d.ts +7 -1
  9. package/dist/components/Link.js +24 -0
  10. package/dist/components/Link.js.map +1 -1
  11. package/dist/components/ScrollAfterNavigation.cjs +66 -0
  12. package/dist/components/ScrollAfterNavigation.cjs.map +1 -0
  13. package/dist/components/ScrollAfterNavigation.d.cts +24 -0
  14. package/dist/components/ScrollAfterNavigation.d.ts +24 -0
  15. package/dist/components/ScrollAfterNavigation.js +41 -0
  16. package/dist/components/ScrollAfterNavigation.js.map +1 -0
  17. package/dist/components/WikiLink.cjs +10 -2
  18. package/dist/components/WikiLink.cjs.map +1 -1
  19. package/dist/components/WikiLink.d.cts +11 -3
  20. package/dist/components/WikiLink.d.ts +11 -3
  21. package/dist/components/WikiLink.js +10 -2
  22. package/dist/components/WikiLink.js.map +1 -1
  23. package/dist/index.cjs +3 -1
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +5 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/safeContent/SafeContent.cjs +54 -0
  30. package/dist/safeContent/SafeContent.cjs.map +1 -0
  31. package/dist/safeContent/SafeContent.d.cts +13 -0
  32. package/dist/safeContent/SafeContent.d.ts +13 -0
  33. package/dist/safeContent/SafeContent.js +30 -0
  34. package/dist/safeContent/SafeContent.js.map +1 -0
  35. package/dist/safeContent/index.cjs +43 -0
  36. package/dist/safeContent/index.cjs.map +1 -0
  37. package/dist/safeContent/index.d.cts +6 -0
  38. package/dist/safeContent/index.d.ts +6 -0
  39. package/dist/safeContent/index.js +14 -0
  40. package/dist/safeContent/index.js.map +1 -0
  41. package/dist/safeContent/parseSafeMdast.cjs +69 -0
  42. package/dist/safeContent/parseSafeMdast.cjs.map +1 -0
  43. package/dist/safeContent/parseSafeMdast.d.cts +31 -0
  44. package/dist/safeContent/parseSafeMdast.d.ts +31 -0
  45. package/dist/safeContent/parseSafeMdast.js +35 -0
  46. package/dist/safeContent/parseSafeMdast.js.map +1 -0
  47. package/dist/safeContent/renderMdast.cjs +138 -0
  48. package/dist/safeContent/renderMdast.cjs.map +1 -0
  49. package/dist/safeContent/renderMdast.d.cts +27 -0
  50. package/dist/safeContent/renderMdast.d.ts +27 -0
  51. package/dist/safeContent/renderMdast.js +114 -0
  52. package/dist/safeContent/renderMdast.js.map +1 -0
  53. package/dist/safeContent/sanitizeUrl.cjs +39 -0
  54. package/dist/safeContent/sanitizeUrl.cjs.map +1 -0
  55. package/dist/safeContent/sanitizeUrl.d.cts +9 -0
  56. package/dist/safeContent/sanitizeUrl.d.ts +9 -0
  57. package/dist/safeContent/sanitizeUrl.js +15 -0
  58. package/dist/safeContent/sanitizeUrl.js.map +1 -0
  59. package/dist/safeContent/wikilink.cjs +63 -0
  60. package/dist/safeContent/wikilink.cjs.map +1 -0
  61. package/dist/safeContent/wikilink.d.cts +21 -0
  62. package/dist/safeContent/wikilink.d.ts +21 -0
  63. package/dist/safeContent/wikilink.js +38 -0
  64. package/dist/safeContent/wikilink.js.map +1 -0
  65. package/dist/scrollToId.cjs +46 -0
  66. package/dist/scrollToId.cjs.map +1 -0
  67. package/dist/scrollToId.d.cts +12 -0
  68. package/dist/scrollToId.d.ts +12 -0
  69. package/dist/scrollToId.js +22 -0
  70. package/dist/scrollToId.js.map +1 -0
  71. package/dist/urlUtils.cjs +9 -1
  72. package/dist/urlUtils.cjs.map +1 -1
  73. package/dist/urlUtils.d.cts +10 -1
  74. package/dist/urlUtils.d.ts +10 -1
  75. package/dist/urlUtils.js +8 -1
  76. package/dist/urlUtils.js.map +1 -1
  77. package/dist/version.cjs +1 -1
  78. package/dist/version.cjs.map +1 -1
  79. package/dist/version.d.cts +1 -1
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/dist/version.js.map +1 -1
  83. package/package.json +9 -3
@@ -20,13 +20,21 @@ import { ReactNode } from 'react';
20
20
  * context nests with each `<Include>`, a relative target inside an included
21
21
  * fragment resolves against the **fragment**, not the top-level page in the URL.
22
22
  *
23
+ * A target may carry a `#fragment` (`[[FILE.mdx#sec-8-9]]`, `[[#sec-8-9]]`): the
24
+ * fragment is **split off** ({@link splitHash}, §13.5) and existence is resolved on
25
+ * the **fragment-stripped path**, so a section citation to an existing file resolves
26
+ * (not "broken"). The fragment then rides to navigation, where the scroll-after-nav
27
+ * effect ({@link useScrollAfterNavigation}) lands the reader on the section.
28
+ *
23
29
  * The resolved path is checked for **existence** against the live metadata store
24
- * (keyed by absolute `/app/…` paths) for the three states (§13.3):
25
- * - **self** — the resolved path is the current file: inert text, no link.
30
+ * (keyed by absolute `/app/…` paths) for the states (§13.3, §13.5):
31
+ * - **anchor** — a fragment with no path (`[[#sec-8-9]]`), or a fragment whose path is
32
+ * the current file: a same-page {@link Link} that scrolls in place, no route change.
33
+ * - **self** — the resolved path is the current file (no fragment): inert text, no link.
26
34
  * - **broken** — no file at the resolved path (and the store has loaded): rendered
27
35
  * as marked text, **not** a throw.
28
36
  * - **resolved** — routed through {@link Link} (in-app navigation for a same-app
29
- * href, a plain `<a>` otherwise).
37
+ * href, a plain `<a>` otherwise), carrying any `#fragment`.
30
38
  *
31
39
  * The check is **optimistic until the metadata store loads** (an empty store never
32
40
  * flashes "broken"), and a relative target with no ambient render context (MDX
@@ -3,6 +3,7 @@ import { use } from "react";
3
3
  import { Link } from "./Link";
4
4
  import { RenderExportedComponentContext } from "./Include";
5
5
  import { TinkerableContext } from "../TinkerableContext";
6
+ import { splitHash } from "../urlUtils";
6
7
  const labelFromTarget = (target) => {
7
8
  const base = target.split(/[\\/]/).pop() ?? target;
8
9
  return base.replace(/\.mdx?$/i, "") || target;
@@ -32,15 +33,22 @@ const WikiLink = ({
32
33
  const renderContext = use(RenderExportedComponentContext);
33
34
  const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;
34
35
  const rawTarget = target ?? "";
35
- const text = children ?? label ?? (rawTarget ? labelFromTarget(rawTarget) : "");
36
+ const [pathPart, frag] = splitHash(rawTarget);
37
+ const text = children ?? label ?? (rawTarget ? labelFromTarget(pathPart || frag || rawTarget) : "");
36
38
  if (!rawTarget) {
37
39
  return /* @__PURE__ */ jsx("span", { className: "ir-wikilink", ...rest, children: text });
38
40
  }
39
- const resolved = resolveWikiTarget(rawTarget, currentFile);
41
+ if (pathPart === "" && frag) {
42
+ return /* @__PURE__ */ jsx(Link, { href: `#${frag}`, className: "ir-wikilink", "data-state": "anchor", ...rest, children: text });
43
+ }
44
+ const resolved = resolveWikiTarget(pathPart, currentFile);
40
45
  const files = filesMetadata ?? {};
41
46
  const loaded = Object.keys(files).length > 0;
42
47
  if (resolved !== void 0) {
43
48
  if (currentFile && resolved === currentFile) {
49
+ if (frag) {
50
+ return /* @__PURE__ */ jsx(Link, { href: `#${frag}`, className: "ir-wikilink", "data-state": "anchor", ...rest, children: text });
51
+ }
44
52
  return /* @__PURE__ */ jsx("span", { className: "ir-wikilink ir-wikilink-self", "data-state": "self", ...rest, children: text });
45
53
  }
46
54
  const exists = !loaded || resolved in files;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/WikiLink.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Link } from './Link';\nimport { RenderExportedComponentContext } from './Include';\nimport { TinkerableContext } from '../TinkerableContext';\n\n/** Derive a human label from a target path: basename without the extension. */\nconst labelFromTarget = (target: string): string => {\n const base = target.split(/[\\\\/]/).pop() ?? target;\n return base.replace(/\\.mdx?$/i, '') || target;\n};\n\n/** Collapse `.`/`..`/empty segments into a clean absolute path. */\nconst normalize = (path: string): string => {\n const out: string[] = [];\n for (const seg of path.split('/')) {\n if (seg === '' || seg === '.') continue;\n if (seg === '..') out.pop();\n else out.push(seg);\n }\n return '/' + out.join('/');\n};\n\n/**\n * Resolve a wiki-link target to an absolute sandbox path, or `undefined` when it\n * cannot be resolved (a relative target with no known current file). An\n * **absolute** target (`/…`) is taken verbatim; a **relative** target resolves\n * against the current file's directory. Pure path arithmetic (MARKDOWN_SYNTAX_SPEC\n * §13.2) — it never touches the filesystem or any other file.\n */\nconst resolveWikiTarget = (target: string, currentFile?: string): string | undefined => {\n if (target.startsWith('/')) return normalize(target);\n if (!currentFile) return undefined;\n const dir = currentFile.slice(0, currentFile.lastIndexOf('/'));\n return normalize(`${dir}/${target}`);\n};\n\n/**\n * Default MDX `WikiLink` component — the render target for the `[[target]]` /\n * `[[label|target]]` wiki-link syntax. The transpiler remark plugin (R3-153)\n * compiles that syntax to `<WikiLink target=\"…\" label=\"…\">`, carrying the raw\n * target/label verbatim; **resolution lives here** (MARKDOWN_SYNTAX_SPEC §13.2).\n *\n * Registered in {@link DEFAULT_MDX_COMPONENTS} so wiki-links render even in a\n * plain-markdown repo (§11.2 phantom defaults). Targets are **paths only** —\n * relative (resolved against the current file's directory) or absolute — with\n * **no implicit search path** (§13.3, a deliberate departure from Obsidian).\n *\n * The **current file** — the one the link is *authored in* — is read from the\n * ambient `<Include>` render context. Every MDX file renders through `<Include>`\n * (`FileRouter` renders even the top-level file that way), and Include publishes\n * the rendered module's `EvaluationContext` to its subtree via\n * {@link RenderExportedComponentContext}; the nearest one's\n * `evaluation.module.filepath` is this file's own `/app/…` path. Because that\n * context nests with each `<Include>`, a relative target inside an included\n * fragment resolves against the **fragment**, not the top-level page in the URL.\n *\n * The resolved path is checked for **existence** against the live metadata store\n * (keyed by absolute `/app/…` paths) for the three states (§13.3):\n * - **self** — the resolved path is the current file: inert text, no link.\n * - **broken** — no file at the resolved path (and the store has loaded): rendered\n * as marked text, **not** a throw.\n * - **resolved** — routed through {@link Link} (in-app navigation for a same-app\n * href, a plain `<a>` otherwise).\n *\n * The check is **optimistic until the metadata store loads** (an empty store never\n * flashes \"broken\"), and a relative target with no ambient render context (MDX\n * rendered outside `<Include>`) routes optimistically. Such an app overrides this\n * component (§11) for precise resolution.\n */\nexport const WikiLink = ({\n target,\n label,\n children,\n ...rest\n}: {\n target?: string;\n label?: ReactNode;\n children?: ReactNode;\n} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>): ReactNode => {\n const { filesMetadata } = use(TinkerableContext);\n const renderContext = use(RenderExportedComponentContext);\n const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;\n\n const rawTarget = target ?? '';\n const text = children ?? label ?? (rawTarget ? labelFromTarget(rawTarget) : '');\n\n // Defensive: the kernel never emits an empty target, but a hand-written\n // `<WikiLink>` might. Render inert text rather than a link to nowhere.\n if (!rawTarget) {\n return (\n <span className=\"ir-wikilink\" {...rest}>\n {text}\n </span>\n );\n }\n\n const resolved = resolveWikiTarget(rawTarget, currentFile);\n const files = filesMetadata ?? {};\n const loaded = Object.keys(files).length > 0;\n\n // `resolved === undefined` ⇒ a relative target with no known current file: route\n // it optimistically (can't check existence or self-ness generically).\n if (resolved !== undefined) {\n if (currentFile && resolved === currentFile) {\n return (\n <span className=\"ir-wikilink ir-wikilink-self\" data-state=\"self\" {...rest}>\n {text}\n </span>\n );\n }\n const exists = !loaded || resolved in files; // optimistic until loaded\n if (!exists) {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`No file at ${resolved}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n }\n return (\n <Link href={rawTarget} className=\"ir-wikilink\" data-state=\"resolved\" {...rest}>\n {text}\n </Link>\n );\n};\n"],"mappings":"AA0FM;AA1FN,SAAoB,WAAW;AAC/B,SAAS,YAAY;AACrB,SAAS,sCAAsC;AAC/C,SAAS,yBAAyB;AAGlC,MAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAM,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,KAAK;AAC5C,SAAO,KAAK,QAAQ,YAAY,EAAE,KAAK;AACzC;AAGA,MAAM,YAAY,CAAC,SAAyB;AAC1C,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AACjC,QAAI,QAAQ,MAAM,QAAQ,IAAK;AAC/B,QAAI,QAAQ,KAAM,KAAI,IAAI;AAAA,QACrB,KAAI,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,MAAM,IAAI,KAAK,GAAG;AAC3B;AASA,MAAM,oBAAoB,CAAC,QAAgB,gBAA6C;AACtF,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,UAAU,MAAM;AACnD,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,MAAM,YAAY,MAAM,GAAG,YAAY,YAAY,GAAG,CAAC;AAC7D,SAAO,UAAU,GAAG,GAAG,IAAI,MAAM,EAAE;AACrC;AAmCO,MAAM,WAAW,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAI+E;AAC7E,QAAM,EAAE,cAAc,IAAI,IAAI,iBAAiB;AAC/C,QAAM,gBAAgB,IAAI,8BAA8B;AACxD,QAAM,cAAc,eAAe,mBAAmB,YAAY,QAAQ;AAE1E,QAAM,YAAY,UAAU;AAC5B,QAAM,OAAO,YAAY,UAAU,YAAY,gBAAgB,SAAS,IAAI;AAI5E,MAAI,CAAC,WAAW;AACd,WACE,oBAAC,UAAK,WAAU,eAAe,GAAG,MAC/B,gBACH;AAAA,EAEJ;AAEA,QAAM,WAAW,kBAAkB,WAAW,WAAW;AACzD,QAAM,QAAQ,iBAAiB,CAAC;AAChC,QAAM,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS;AAI3C,MAAI,aAAa,QAAW;AAC1B,QAAI,eAAe,aAAa,aAAa;AAC3C,aACE,oBAAC,UAAK,WAAU,gCAA+B,cAAW,QAAQ,GAAG,MAClE,gBACH;AAAA,IAEJ;AACA,UAAM,SAAS,CAAC,UAAU,YAAY;AACtC,QAAI,CAAC,QAAQ;AACX,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,OAAO,cAAc,QAAQ;AAAA,UAC5B,GAAG;AAAA,UAEH;AAAA;AAAA,MACH;AAAA,IAEJ;AAAA,EACF;AACA,SACE,oBAAC,QAAK,MAAM,WAAW,WAAU,eAAc,cAAW,YAAY,GAAG,MACtE,gBACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/WikiLink.tsx"],"sourcesContent":["import { ReactNode, use } from 'react';\nimport { Link } from './Link';\nimport { RenderExportedComponentContext } from './Include';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { splitHash } from '../urlUtils';\n\n/** Derive a human label from a target path: basename without the extension. */\nconst labelFromTarget = (target: string): string => {\n const base = target.split(/[\\\\/]/).pop() ?? target;\n return base.replace(/\\.mdx?$/i, '') || target;\n};\n\n/** Collapse `.`/`..`/empty segments into a clean absolute path. */\nconst normalize = (path: string): string => {\n const out: string[] = [];\n for (const seg of path.split('/')) {\n if (seg === '' || seg === '.') continue;\n if (seg === '..') out.pop();\n else out.push(seg);\n }\n return '/' + out.join('/');\n};\n\n/**\n * Resolve a wiki-link target to an absolute sandbox path, or `undefined` when it\n * cannot be resolved (a relative target with no known current file). An\n * **absolute** target (`/…`) is taken verbatim; a **relative** target resolves\n * against the current file's directory. Pure path arithmetic (MARKDOWN_SYNTAX_SPEC\n * §13.2) — it never touches the filesystem or any other file.\n */\nconst resolveWikiTarget = (target: string, currentFile?: string): string | undefined => {\n if (target.startsWith('/')) return normalize(target);\n if (!currentFile) return undefined;\n const dir = currentFile.slice(0, currentFile.lastIndexOf('/'));\n return normalize(`${dir}/${target}`);\n};\n\n/**\n * Default MDX `WikiLink` component — the render target for the `[[target]]` /\n * `[[label|target]]` wiki-link syntax. The transpiler remark plugin (R3-153)\n * compiles that syntax to `<WikiLink target=\"…\" label=\"…\">`, carrying the raw\n * target/label verbatim; **resolution lives here** (MARKDOWN_SYNTAX_SPEC §13.2).\n *\n * Registered in {@link DEFAULT_MDX_COMPONENTS} so wiki-links render even in a\n * plain-markdown repo (§11.2 phantom defaults). Targets are **paths only** —\n * relative (resolved against the current file's directory) or absolute — with\n * **no implicit search path** (§13.3, a deliberate departure from Obsidian).\n *\n * The **current file** — the one the link is *authored in* — is read from the\n * ambient `<Include>` render context. Every MDX file renders through `<Include>`\n * (`FileRouter` renders even the top-level file that way), and Include publishes\n * the rendered module's `EvaluationContext` to its subtree via\n * {@link RenderExportedComponentContext}; the nearest one's\n * `evaluation.module.filepath` is this file's own `/app/…` path. Because that\n * context nests with each `<Include>`, a relative target inside an included\n * fragment resolves against the **fragment**, not the top-level page in the URL.\n *\n * A target may carry a `#fragment` (`[[FILE.mdx#sec-8-9]]`, `[[#sec-8-9]]`): the\n * fragment is **split off** ({@link splitHash}, §13.5) and existence is resolved on\n * the **fragment-stripped path**, so a section citation to an existing file resolves\n * (not \"broken\"). The fragment then rides to navigation, where the scroll-after-nav\n * effect ({@link useScrollAfterNavigation}) lands the reader on the section.\n *\n * The resolved path is checked for **existence** against the live metadata store\n * (keyed by absolute `/app/…` paths) for the states (§13.3, §13.5):\n * - **anchor** — a fragment with no path (`[[#sec-8-9]]`), or a fragment whose path is\n * the current file: a same-page {@link Link} that scrolls in place, no route change.\n * - **self** — the resolved path is the current file (no fragment): inert text, no link.\n * - **broken** — no file at the resolved path (and the store has loaded): rendered\n * as marked text, **not** a throw.\n * - **resolved** — routed through {@link Link} (in-app navigation for a same-app\n * href, a plain `<a>` otherwise), carrying any `#fragment`.\n *\n * The check is **optimistic until the metadata store loads** (an empty store never\n * flashes \"broken\"), and a relative target with no ambient render context (MDX\n * rendered outside `<Include>`) routes optimistically. Such an app overrides this\n * component (§11) for precise resolution.\n */\nexport const WikiLink = ({\n target,\n label,\n children,\n ...rest\n}: {\n target?: string;\n label?: ReactNode;\n children?: ReactNode;\n} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>): ReactNode => {\n const { filesMetadata } = use(TinkerableContext);\n const renderContext = use(RenderExportedComponentContext);\n const currentFile = renderContext?.evaluationContext?.evaluation?.module?.filepath;\n\n const rawTarget = target ?? '';\n const [pathPart, frag] = splitHash(rawTarget);\n const text = children ?? label ?? (rawTarget ? labelFromTarget(pathPart || frag || rawTarget) : '');\n\n // Defensive: the kernel never emits an empty target, but a hand-written\n // `<WikiLink>` might. Render inert text rather than a link to nowhere.\n if (!rawTarget) {\n return (\n <span className=\"ir-wikilink\" {...rest}>\n {text}\n </span>\n );\n }\n\n // Same-page anchor: a fragment with no path (`[[#sec-8-9]]`). Scroll within the\n // current file — no route change (§13.5). <Link> intercepts a bare `#`-href.\n if (pathPart === '' && frag) {\n return (\n <Link href={`#${frag}`} className=\"ir-wikilink\" data-state=\"anchor\" {...rest}>\n {text}\n </Link>\n );\n }\n\n const resolved = resolveWikiTarget(pathPart, currentFile);\n const files = filesMetadata ?? {};\n const loaded = Object.keys(files).length > 0;\n\n // `resolved === undefined` ⇒ a relative target with no known current file: route\n // it optimistically (can't check existence or self-ness generically).\n if (resolved !== undefined) {\n if (currentFile && resolved === currentFile) {\n // The target IS this file. With a fragment it is a same-page anchor to another\n // of this file's sections; without one it is an inert self-reference.\n if (frag) {\n return (\n <Link href={`#${frag}`} className=\"ir-wikilink\" data-state=\"anchor\" {...rest}>\n {text}\n </Link>\n );\n }\n return (\n <span className=\"ir-wikilink ir-wikilink-self\" data-state=\"self\" {...rest}>\n {text}\n </span>\n );\n }\n // Existence is checked on the FRAGMENT-STRIPPED path (optimistic until loaded).\n const exists = !loaded || resolved in files;\n if (!exists) {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`No file at ${resolved}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n }\n // Resolved cross-file target: route through <Link>, carrying the raw target so its\n // `#fragment` rides through navigation to the scroll-after-nav effect (§13.5).\n return (\n <Link href={rawTarget} className=\"ir-wikilink\" data-state=\"resolved\" {...rest}>\n {text}\n </Link>\n );\n};\n"],"mappings":"AAoGM;AApGN,SAAoB,WAAW;AAC/B,SAAS,YAAY;AACrB,SAAS,sCAAsC;AAC/C,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAG1B,MAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAM,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,KAAK;AAC5C,SAAO,KAAK,QAAQ,YAAY,EAAE,KAAK;AACzC;AAGA,MAAM,YAAY,CAAC,SAAyB;AAC1C,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,KAAK,MAAM,GAAG,GAAG;AACjC,QAAI,QAAQ,MAAM,QAAQ,IAAK;AAC/B,QAAI,QAAQ,KAAM,KAAI,IAAI;AAAA,QACrB,KAAI,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,MAAM,IAAI,KAAK,GAAG;AAC3B;AASA,MAAM,oBAAoB,CAAC,QAAgB,gBAA6C;AACtF,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,UAAU,MAAM;AACnD,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,MAAM,YAAY,MAAM,GAAG,YAAY,YAAY,GAAG,CAAC;AAC7D,SAAO,UAAU,GAAG,GAAG,IAAI,MAAM,EAAE;AACrC;AA2CO,MAAM,WAAW,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAI+E;AAC7E,QAAM,EAAE,cAAc,IAAI,IAAI,iBAAiB;AAC/C,QAAM,gBAAgB,IAAI,8BAA8B;AACxD,QAAM,cAAc,eAAe,mBAAmB,YAAY,QAAQ;AAE1E,QAAM,YAAY,UAAU;AAC5B,QAAM,CAAC,UAAU,IAAI,IAAI,UAAU,SAAS;AAC5C,QAAM,OAAO,YAAY,UAAU,YAAY,gBAAgB,YAAY,QAAQ,SAAS,IAAI;AAIhG,MAAI,CAAC,WAAW;AACd,WACE,oBAAC,UAAK,WAAU,eAAe,GAAG,MAC/B,gBACH;AAAA,EAEJ;AAIA,MAAI,aAAa,MAAM,MAAM;AAC3B,WACE,oBAAC,QAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,EAEJ;AAEA,QAAM,WAAW,kBAAkB,UAAU,WAAW;AACxD,QAAM,QAAQ,iBAAiB,CAAC;AAChC,QAAM,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS;AAI3C,MAAI,aAAa,QAAW;AAC1B,QAAI,eAAe,aAAa,aAAa;AAG3C,UAAI,MAAM;AACR,eACE,oBAAC,QAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,MAEJ;AACA,aACE,oBAAC,UAAK,WAAU,gCAA+B,cAAW,QAAQ,GAAG,MAClE,gBACH;AAAA,IAEJ;AAEA,UAAM,SAAS,CAAC,UAAU,YAAY;AACtC,QAAI,CAAC,QAAQ;AACX,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,cAAW;AAAA,UACX,OAAO,cAAc,QAAQ;AAAA,UAC5B,GAAG;AAAA,UAEH;AAAA;AAAA,MACH;AAAA,IAEJ;AAAA,EACF;AAGA,SACE,oBAAC,QAAK,MAAM,WAAW,WAAU,eAAc,cAAW,YAAY,GAAG,MACtE,gBACH;AAEJ;","names":[]}
package/dist/index.cjs CHANGED
@@ -49,6 +49,7 @@ __reExport(index_exports, require("./ready"), module.exports);
49
49
  __reExport(index_exports, require("./loading"), module.exports);
50
50
  __reExport(index_exports, require("./protocolStream"), module.exports);
51
51
  __reExport(index_exports, require("./sandboxTypes"), module.exports);
52
+ __reExport(index_exports, require("./safeContent"), module.exports);
52
53
  // Annotate the CommonJS export names for ESM import in node:
53
54
  0 && (module.exports = {
54
55
  ...require("./MDXProvider"),
@@ -84,6 +85,7 @@ __reExport(index_exports, require("./sandboxTypes"), module.exports);
84
85
  ...require("./ready"),
85
86
  ...require("./loading"),
86
87
  ...require("./protocolStream"),
87
- ...require("./sandboxTypes")
88
+ ...require("./sandboxTypes"),
89
+ ...require("./safeContent")
88
90
  });
89
91
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAIA,0BAAc,uCAJd;AAKA,0BAAc,oCALd;AAMA,0BAAc,gCANd;AAOA,0BAAc,oBAPd;AAQA,0BAAc,mBARd;AASA,0BAAc,oBATd;AAUA,0BAAc,4BAVd;AAWA,0BAAc,qBAXd;AAYA,0BAAc,yBAZd;AAaA,0BAAc,qBAbd;AAcA,0BAAc,qBAdd;AAeA,0BAAc,yBAfd;AAgBA,0BAAc,sBAhBd;AAiBA,0BAAc,kBAjBd;AAkBA,0BAAc,kBAlBd;AAmBA,0BAAc,uBAnBd;AAoBA,0BAAc,sBApBd;AAqBA,0BAAc,kBArBd;AAsBA,0BAAc,0BAtBd;AAuBA,0BAAc,kBAvBd;AAwBA,0BAAc,yBAxBd;AAyBA,0BAAc,iBAzBd;AA0BA,0BAAc,oBA1Bd;AA2BA,0BAAc,oBA3Bd;AA4BA,0BAAc,sBA5Bd;AA6BA,0BAAc,wBA7Bd;AA8BA,0BAAc,oBA9Bd;AA+BA,0BAAc,sBA/Bd;AAgCA,0BAAc,6BAhCd;AAiCA,0BAAc,2BAjCd;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAIA,0BAAc,uCAJd;AAKA,0BAAc,oCALd;AAMA,0BAAc,gCANd;AAOA,0BAAc,oBAPd;AAQA,0BAAc,mBARd;AASA,0BAAc,oBATd;AAUA,0BAAc,4BAVd;AAWA,0BAAc,qBAXd;AAYA,0BAAc,yBAZd;AAaA,0BAAc,qBAbd;AAcA,0BAAc,qBAdd;AAeA,0BAAc,yBAfd;AAgBA,0BAAc,sBAhBd;AAiBA,0BAAc,kBAjBd;AAkBA,0BAAc,kBAlBd;AAmBA,0BAAc,uBAnBd;AAoBA,0BAAc,sBApBd;AAqBA,0BAAc,kBArBd;AAsBA,0BAAc,0BAtBd;AAuBA,0BAAc,kBAvBd;AAwBA,0BAAc,yBAxBd;AAyBA,0BAAc,iBAzBd;AA0BA,0BAAc,oBA1Bd;AA2BA,0BAAc,oBA3Bd;AA4BA,0BAAc,sBA5Bd;AA6BA,0BAAc,wBA7Bd;AA8BA,0BAAc,oBA9Bd;AA+BA,0BAAc,sBA/Bd;AAgCA,0BAAc,6BAhCd;AAiCA,0BAAc,2BAjCd;AAkCA,0BAAc,0BAlCd;","names":[]}
package/dist/index.d.cts CHANGED
@@ -32,6 +32,11 @@ export { ReadyState, __resetReady, __setReadyDeps, getReadyState, onReady, repor
32
32
  export { LOADING_TIMINGS, LoadingRegion, Skeleton, SkeletonArchetype, SkeletonRow, Spinner } from './loading.cjs';
33
33
  export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStream } from './protocolStream.cjs';
34
34
  export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.cjs';
35
+ export { SafeContent, SafeContentProps } from './safeContent/SafeContent.cjs';
36
+ export { RenderMdastOptions, SafeContentComponents, renderMdast } from './safeContent/renderMdast.cjs';
37
+ export { SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.cjs';
38
+ export { sanitizeUrl } from './safeContent/sanitizeUrl.cjs';
39
+ export { WikiLinkToken, WikiPart, parseWikiInner, splitWikiLinks } from './safeContent/wikilink.cjs';
35
40
  export { Admonition, AdmonitionType } from './components/Admonition.cjs';
36
41
  export { HeadingAnchor } from './components/HeadingAnchor.cjs';
37
42
  export { ImmediatelyRunGlobal, getHostRuntime } from './hostRuntime.cjs';
package/dist/index.d.ts CHANGED
@@ -32,6 +32,11 @@ export { ReadyState, __resetReady, __setReadyDeps, getReadyState, onReady, repor
32
32
  export { LOADING_TIMINGS, LoadingRegion, Skeleton, SkeletonArchetype, SkeletonRow, Spinner } from './loading.js';
33
33
  export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStream } from './protocolStream.js';
34
34
  export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.js';
35
+ export { SafeContent, SafeContentProps } from './safeContent/SafeContent.js';
36
+ export { RenderMdastOptions, SafeContentComponents, renderMdast } from './safeContent/renderMdast.js';
37
+ export { SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.js';
38
+ export { sanitizeUrl } from './safeContent/sanitizeUrl.js';
39
+ export { WikiLinkToken, WikiPart, parseWikiInner, splitWikiLinks } from './safeContent/wikilink.js';
35
40
  export { Admonition, AdmonitionType } from './components/Admonition.js';
36
41
  export { HeadingAnchor } from './components/HeadingAnchor.js';
37
42
  export { ImmediatelyRunGlobal, getHostRuntime } from './hostRuntime.js';
package/dist/index.js CHANGED
@@ -32,4 +32,5 @@ export * from "./ready";
32
32
  export * from "./loading";
33
33
  export * from "./protocolStream";
34
34
  export * from "./sandboxTypes";
35
+ export * from "./safeContent";
35
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var SafeContent_exports = {};
20
+ __export(SafeContent_exports, {
21
+ SafeContent: () => SafeContent
22
+ });
23
+ module.exports = __toCommonJS(SafeContent_exports);
24
+ var import_react = require("react");
25
+ var import_parseSafeMdast = require("./parseSafeMdast");
26
+ var import_renderMdast = require("./renderMdast");
27
+ function SafeContent({ source, fallback = null, ...options }) {
28
+ const [tree, setTree] = (0, import_react.useState)(null);
29
+ const runId = (0, import_react.useRef)(0);
30
+ (0, import_react.useEffect)(() => {
31
+ const id = ++runId.current;
32
+ setTree(null);
33
+ let cancelled = false;
34
+ (0, import_parseSafeMdast.parseSafeMdast)(source).then((parsed) => {
35
+ if (!cancelled && runId.current === id) setTree(parsed);
36
+ }).catch(() => {
37
+ if (!cancelled && runId.current === id) setTree(null);
38
+ });
39
+ return () => {
40
+ cancelled = true;
41
+ };
42
+ }, [source]);
43
+ const rendered = (0, import_react.useMemo)(
44
+ () => tree ? (0, import_renderMdast.renderMdast)(tree, options) : null,
45
+ // eslint-disable-next-line react-hooks/exhaustive-deps
46
+ [tree, options.components, options.resolveWikiLink]
47
+ );
48
+ return tree ? rendered : fallback;
49
+ }
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ SafeContent
53
+ });
54
+ //# sourceMappingURL=SafeContent.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/SafeContent.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { parseSafeMdast } from './parseSafeMdast';\nimport { renderMdast, type RenderMdastOptions } from './renderMdast';\n\n// `<SafeContent>` — the host/SDK-owned safe renderer (TRUST_MODES_SPEC §5.1,\n// AGENT_AUTHORING §10). An interpreter app renders untrusted Markdown/MDX-syntax\n// content — a shared board's body, a multi-writer wiki entry — through THIS component\n// so **no author JavaScript ever executes** while `<Component/>` syntax still reaches\n// the app's own safe components. It is the mandatory terminal for the MDX-from-mount\n// gate: a shared board is a multi-writer (M3) source, and compiled/executable MDX\n// would run board-author code with the app's authority (the WHITEBOARD §8 blast\n// radius this gate bounds).\n//\n// The parse (no-acorn, ESM-only deps) is async, so this renders a small placeholder\n// until the tree is ready, then the pure synchronous `renderMdast`. Parsing is keyed\n// on the source string; a changed source re-parses, and a stale async result is\n// discarded (the ref guard) so a fast source swap can't paint the wrong content.\n\nexport interface SafeContentProps extends RenderMdastOptions {\n /** The untrusted Markdown/MDX-syntax source to render as data. */\n source: string;\n /** Rendered while the async parse is in flight (default: nothing). */\n fallback?: ReactNode;\n}\n\nexport function SafeContent({ source, fallback = null, ...options }: SafeContentProps): ReactNode {\n const [tree, setTree] = useState<Awaited<ReturnType<typeof parseSafeMdast>> | null>(null);\n const runId = useRef(0);\n\n useEffect(() => {\n const id = ++runId.current;\n setTree(null);\n let cancelled = false;\n parseSafeMdast(source)\n .then((parsed) => {\n // Discard a stale result (source changed / unmounted) so we never paint the\n // wrong tree — the render-as-data guarantee also needs the RIGHT data shown.\n if (!cancelled && runId.current === id) setTree(parsed);\n })\n .catch(() => {\n if (!cancelled && runId.current === id) setTree(null);\n });\n return () => {\n cancelled = true;\n };\n }, [source]);\n\n // `renderMdast` is pure; memoise on the tree + the option identities the caller\n // passes. (Callers should memoise `components`/`resolveWikiLink` themselves for a\n // stable identity; changing them re-renders, which is correct.)\n const rendered = useMemo(\n () => (tree ? renderMdast(tree, options) : null),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [tree, options.components, options.resolveWikiLink],\n );\n\n return tree ? rendered : fallback;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAqE;AACrE,4BAA+B;AAC/B,yBAAqD;AAuB9C,SAAS,YAAY,EAAE,QAAQ,WAAW,MAAM,GAAG,QAAQ,GAAgC;AAChG,QAAM,CAAC,MAAM,OAAO,QAAI,uBAA4D,IAAI;AACxF,QAAM,YAAQ,qBAAO,CAAC;AAEtB,8BAAU,MAAM;AACd,UAAM,KAAK,EAAE,MAAM;AACnB,YAAQ,IAAI;AACZ,QAAI,YAAY;AAChB,8CAAe,MAAM,EAClB,KAAK,CAAC,WAAW;AAGhB,UAAI,CAAC,aAAa,MAAM,YAAY,GAAI,SAAQ,MAAM;AAAA,IACxD,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,aAAa,MAAM,YAAY,GAAI,SAAQ,IAAI;AAAA,IACtD,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAKX,QAAM,eAAW;AAAA,IACf,MAAO,WAAO,gCAAY,MAAM,OAAO,IAAI;AAAA;AAAA,IAE3C,CAAC,MAAM,QAAQ,YAAY,QAAQ,eAAe;AAAA,EACpD;AAEA,SAAO,OAAO,WAAW;AAC3B;","names":[]}
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { RenderMdastOptions } from './renderMdast.cjs';
3
+ import './parseSafeMdast.cjs';
4
+
5
+ interface SafeContentProps extends RenderMdastOptions {
6
+ /** The untrusted Markdown/MDX-syntax source to render as data. */
7
+ source: string;
8
+ /** Rendered while the async parse is in flight (default: nothing). */
9
+ fallback?: ReactNode;
10
+ }
11
+ declare function SafeContent({ source, fallback, ...options }: SafeContentProps): ReactNode;
12
+
13
+ export { SafeContent, type SafeContentProps };
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { RenderMdastOptions } from './renderMdast.js';
3
+ import './parseSafeMdast.js';
4
+
5
+ interface SafeContentProps extends RenderMdastOptions {
6
+ /** The untrusted Markdown/MDX-syntax source to render as data. */
7
+ source: string;
8
+ /** Rendered while the async parse is in flight (default: nothing). */
9
+ fallback?: ReactNode;
10
+ }
11
+ declare function SafeContent({ source, fallback, ...options }: SafeContentProps): ReactNode;
12
+
13
+ export { SafeContent, type SafeContentProps };
@@ -0,0 +1,30 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { parseSafeMdast } from "./parseSafeMdast";
3
+ import { renderMdast } from "./renderMdast";
4
+ function SafeContent({ source, fallback = null, ...options }) {
5
+ const [tree, setTree] = useState(null);
6
+ const runId = useRef(0);
7
+ useEffect(() => {
8
+ const id = ++runId.current;
9
+ setTree(null);
10
+ let cancelled = false;
11
+ parseSafeMdast(source).then((parsed) => {
12
+ if (!cancelled && runId.current === id) setTree(parsed);
13
+ }).catch(() => {
14
+ if (!cancelled && runId.current === id) setTree(null);
15
+ });
16
+ return () => {
17
+ cancelled = true;
18
+ };
19
+ }, [source]);
20
+ const rendered = useMemo(
21
+ () => tree ? renderMdast(tree, options) : null,
22
+ // eslint-disable-next-line react-hooks/exhaustive-deps
23
+ [tree, options.components, options.resolveWikiLink]
24
+ );
25
+ return tree ? rendered : fallback;
26
+ }
27
+ export {
28
+ SafeContent
29
+ };
30
+ //# sourceMappingURL=SafeContent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/SafeContent.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';\nimport { parseSafeMdast } from './parseSafeMdast';\nimport { renderMdast, type RenderMdastOptions } from './renderMdast';\n\n// `<SafeContent>` — the host/SDK-owned safe renderer (TRUST_MODES_SPEC §5.1,\n// AGENT_AUTHORING §10). An interpreter app renders untrusted Markdown/MDX-syntax\n// content — a shared board's body, a multi-writer wiki entry — through THIS component\n// so **no author JavaScript ever executes** while `<Component/>` syntax still reaches\n// the app's own safe components. It is the mandatory terminal for the MDX-from-mount\n// gate: a shared board is a multi-writer (M3) source, and compiled/executable MDX\n// would run board-author code with the app's authority (the WHITEBOARD §8 blast\n// radius this gate bounds).\n//\n// The parse (no-acorn, ESM-only deps) is async, so this renders a small placeholder\n// until the tree is ready, then the pure synchronous `renderMdast`. Parsing is keyed\n// on the source string; a changed source re-parses, and a stale async result is\n// discarded (the ref guard) so a fast source swap can't paint the wrong content.\n\nexport interface SafeContentProps extends RenderMdastOptions {\n /** The untrusted Markdown/MDX-syntax source to render as data. */\n source: string;\n /** Rendered while the async parse is in flight (default: nothing). */\n fallback?: ReactNode;\n}\n\nexport function SafeContent({ source, fallback = null, ...options }: SafeContentProps): ReactNode {\n const [tree, setTree] = useState<Awaited<ReturnType<typeof parseSafeMdast>> | null>(null);\n const runId = useRef(0);\n\n useEffect(() => {\n const id = ++runId.current;\n setTree(null);\n let cancelled = false;\n parseSafeMdast(source)\n .then((parsed) => {\n // Discard a stale result (source changed / unmounted) so we never paint the\n // wrong tree — the render-as-data guarantee also needs the RIGHT data shown.\n if (!cancelled && runId.current === id) setTree(parsed);\n })\n .catch(() => {\n if (!cancelled && runId.current === id) setTree(null);\n });\n return () => {\n cancelled = true;\n };\n }, [source]);\n\n // `renderMdast` is pure; memoise on the tree + the option identities the caller\n // passes. (Callers should memoise `components`/`resolveWikiLink` themselves for a\n // stable identity; changing them re-renders, which is correct.)\n const rendered = useMemo(\n () => (tree ? renderMdast(tree, options) : null),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [tree, options.components, options.resolveWikiLink],\n );\n\n return tree ? rendered : fallback;\n}\n"],"mappings":"AAAA,SAAS,WAAW,SAAS,QAAQ,gBAAgC;AACrE,SAAS,sBAAsB;AAC/B,SAAS,mBAA4C;AAuB9C,SAAS,YAAY,EAAE,QAAQ,WAAW,MAAM,GAAG,QAAQ,GAAgC;AAChG,QAAM,CAAC,MAAM,OAAO,IAAI,SAA4D,IAAI;AACxF,QAAM,QAAQ,OAAO,CAAC;AAEtB,YAAU,MAAM;AACd,UAAM,KAAK,EAAE,MAAM;AACnB,YAAQ,IAAI;AACZ,QAAI,YAAY;AAChB,mBAAe,MAAM,EAClB,KAAK,CAAC,WAAW;AAGhB,UAAI,CAAC,aAAa,MAAM,YAAY,GAAI,SAAQ,MAAM;AAAA,IACxD,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,aAAa,MAAM,YAAY,GAAI,SAAQ,IAAI;AAAA,IACtD,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAKX,QAAM,WAAW;AAAA,IACf,MAAO,OAAO,YAAY,MAAM,OAAO,IAAI;AAAA;AAAA,IAE3C,CAAC,MAAM,QAAQ,YAAY,QAAQ,eAAe;AAAA,EACpD;AAEA,SAAO,OAAO,WAAW;AAC3B;","names":[]}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var safeContent_exports = {};
20
+ __export(safeContent_exports, {
21
+ SafeContent: () => import_SafeContent.SafeContent,
22
+ parseSafeMdast: () => import_parseSafeMdast.parseSafeMdast,
23
+ parseWikiInner: () => import_wikilink.parseWikiInner,
24
+ renderMdast: () => import_renderMdast.renderMdast,
25
+ sanitizeUrl: () => import_sanitizeUrl.sanitizeUrl,
26
+ splitWikiLinks: () => import_wikilink.splitWikiLinks
27
+ });
28
+ module.exports = __toCommonJS(safeContent_exports);
29
+ var import_SafeContent = require("./SafeContent");
30
+ var import_renderMdast = require("./renderMdast");
31
+ var import_parseSafeMdast = require("./parseSafeMdast");
32
+ var import_sanitizeUrl = require("./sanitizeUrl");
33
+ var import_wikilink = require("./wikilink");
34
+ // Annotate the CommonJS export names for ESM import in node:
35
+ 0 && (module.exports = {
36
+ SafeContent,
37
+ parseSafeMdast,
38
+ parseWikiInner,
39
+ renderMdast,
40
+ sanitizeUrl,
41
+ splitWikiLinks
42
+ });
43
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/index.ts"],"sourcesContent":["// Safe content rendering — non-executable MDX (TRUST_MODES_SPEC §5.1, AGENT_AUTHORING\n// §10). The host/SDK-owned renderer an INTERPRETER app uses to render untrusted\n// Markdown/MDX-syntax content as data, with **no evaluator anywhere in the pipeline**.\n// This is the mandatory terminal for the MDX-from-mount gate (§10 delta 1) — a shared\n// (multi-writer / M3) board or wiki entry MUST render through here, never compiled MDX.\n//\n// It is a **T2 tool, not a platform gate**: it stops content executing as code; it does\n// not certify anything to the host, and CO-4 data-fencing is still required for anything\n// that *reads* the content (the agent case).\n\nexport { SafeContent } from './SafeContent';\nexport type { SafeContentProps } from './SafeContent';\nexport { renderMdast } from './renderMdast';\nexport type {\n RenderMdastOptions,\n SafeContentComponents,\n} from './renderMdast';\nexport { parseSafeMdast } from './parseSafeMdast';\nexport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\nexport { sanitizeUrl } from './sanitizeUrl';\nexport { splitWikiLinks, parseWikiInner } from './wikilink';\nexport type { WikiLinkToken, WikiPart } from './wikilink';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,yBAA4B;AAE5B,yBAA4B;AAK5B,4BAA+B;AAE/B,yBAA4B;AAC5B,sBAA+C;","names":[]}
@@ -0,0 +1,6 @@
1
+ export { SafeContent, SafeContentProps } from './SafeContent.cjs';
2
+ export { RenderMdastOptions, SafeContentComponents, renderMdast } from './renderMdast.cjs';
3
+ export { SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './parseSafeMdast.cjs';
4
+ export { sanitizeUrl } from './sanitizeUrl.cjs';
5
+ export { WikiLinkToken, WikiPart, parseWikiInner, splitWikiLinks } from './wikilink.cjs';
6
+ import 'react';
@@ -0,0 +1,6 @@
1
+ export { SafeContent, SafeContentProps } from './SafeContent.js';
2
+ export { RenderMdastOptions, SafeContentComponents, renderMdast } from './renderMdast.js';
3
+ export { SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './parseSafeMdast.js';
4
+ export { sanitizeUrl } from './sanitizeUrl.js';
5
+ export { WikiLinkToken, WikiPart, parseWikiInner, splitWikiLinks } from './wikilink.js';
6
+ import 'react';
@@ -0,0 +1,14 @@
1
+ import { SafeContent } from "./SafeContent";
2
+ import { renderMdast } from "./renderMdast";
3
+ import { parseSafeMdast } from "./parseSafeMdast";
4
+ import { sanitizeUrl } from "./sanitizeUrl";
5
+ import { splitWikiLinks, parseWikiInner } from "./wikilink";
6
+ export {
7
+ SafeContent,
8
+ parseSafeMdast,
9
+ parseWikiInner,
10
+ renderMdast,
11
+ sanitizeUrl,
12
+ splitWikiLinks
13
+ };
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/index.ts"],"sourcesContent":["// Safe content rendering — non-executable MDX (TRUST_MODES_SPEC §5.1, AGENT_AUTHORING\n// §10). The host/SDK-owned renderer an INTERPRETER app uses to render untrusted\n// Markdown/MDX-syntax content as data, with **no evaluator anywhere in the pipeline**.\n// This is the mandatory terminal for the MDX-from-mount gate (§10 delta 1) — a shared\n// (multi-writer / M3) board or wiki entry MUST render through here, never compiled MDX.\n//\n// It is a **T2 tool, not a platform gate**: it stops content executing as code; it does\n// not certify anything to the host, and CO-4 data-fencing is still required for anything\n// that *reads* the content (the agent case).\n\nexport { SafeContent } from './SafeContent';\nexport type { SafeContentProps } from './SafeContent';\nexport { renderMdast } from './renderMdast';\nexport type {\n RenderMdastOptions,\n SafeContentComponents,\n} from './renderMdast';\nexport { parseSafeMdast } from './parseSafeMdast';\nexport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\nexport { sanitizeUrl } from './sanitizeUrl';\nexport { splitWikiLinks, parseWikiInner } from './wikilink';\nexport type { WikiLinkToken, WikiPart } from './wikilink';\n"],"mappings":"AAUA,SAAS,mBAAmB;AAE5B,SAAS,mBAAmB;AAK5B,SAAS,sBAAsB;AAE/B,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB,sBAAsB;","names":[]}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var parseSafeMdast_exports = {};
30
+ __export(parseSafeMdast_exports, {
31
+ parseSafeMdast: () => parseSafeMdast
32
+ });
33
+ module.exports = __toCommonJS(parseSafeMdast_exports);
34
+ let depsPromise = null;
35
+ function loadDeps() {
36
+ if (!depsPromise) {
37
+ depsPromise = Promise.all([
38
+ import("mdast-util-from-markdown"),
39
+ import("micromark-extension-mdx-jsx"),
40
+ import("mdast-util-mdx-jsx"),
41
+ import("micromark-extension-gfm"),
42
+ import("mdast-util-gfm")
43
+ ]).then(
44
+ ([fromMd, mdxJsxExt, mdxJsxMdast, gfmExt, gfmMdast]) => ({
45
+ fromMarkdown: fromMd.fromMarkdown,
46
+ mdxJsx: mdxJsxExt.mdxJsx,
47
+ mdxJsxFromMarkdown: mdxJsxMdast.mdxJsxFromMarkdown,
48
+ gfm: gfmExt.gfm,
49
+ gfmFromMarkdown: gfmMdast.gfmFromMarkdown
50
+ })
51
+ );
52
+ }
53
+ return depsPromise;
54
+ }
55
+ async function parseSafeMdast(source) {
56
+ const { fromMarkdown, mdxJsx, mdxJsxFromMarkdown, gfm, gfmFromMarkdown } = await loadDeps();
57
+ return fromMarkdown(source, {
58
+ // `mdxJsx()` WITHOUT an acorn option → JSX tags + literal attrs; expressions are
59
+ // raw strings, never estree. The mdx *expression* extension is intentionally
60
+ // absent, so `{…}` in body text stays literal. GFM for tables/task-lists.
61
+ extensions: [mdxJsx(), gfm()],
62
+ mdastExtensions: [mdxJsxFromMarkdown(), gfmFromMarkdown()]
63
+ });
64
+ }
65
+ // Annotate the CommonJS export names for ESM import in node:
66
+ 0 && (module.exports = {
67
+ parseSafeMdast
68
+ });
69
+ //# sourceMappingURL=parseSafeMdast.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/parseSafeMdast.ts"],"sourcesContent":["// The no-acorn parse pipeline for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// empirically verified 2026-07-07 against `@mdx-js/mdx` v3 / `micromark-extension-\n// mdx-jsx`). This is the load-bearing half of the fail-safe: `<Component/>` syntax\n// parses to an element node with **literal** attributes, and every expression\n// (`n={1+1}`, `f={fetch(\"/x\")}`, `{...spread}`) is captured as an **inert raw string\n// with no estree** — because we pass **NO acorn option** to `micromark-extension-mdx-\n// jsx`. There is therefore no evaluator anywhere in the pipeline (fail-safe), unlike\n// `@mdx-js/mdx`'s `compile({format:'mdx'})` which always wires acorn.\n//\n// The §5.1 residual guarantees are also here: the expression micromark extension is\n// **not** enabled (body `{…}` stays literal text), and **no `rehype-raw`** / raw-HTML\n// passthrough exists — raw HTML blocks arrive as inert `html` mdast nodes the renderer\n// prints as text, never injected.\n//\n// `micromark`/`mdast` are ESM-only. They are loaded with a memoised dynamic `import()`\n// (the transpiler's `compile.ts` pattern) so this module sits in the package's CJS\n// build too — a CJS consumer that never renders untrusted content never `require()`s\n// an ESM-only package, and one that does gets it via Node's ESM-from-CJS import. The\n// dynamic load changes only *when* the deps resolve, never the produced tree.\n\n// A minimal structural mdast shape — we deliberately avoid a type dependency on the\n// ESM-only `mdast`/`unist` packages (which would break the CJS build's type surface).\nexport interface SafeMdastNode {\n type: string;\n value?: string;\n name?: string | null;\n url?: string;\n alt?: string | null;\n title?: string | null;\n depth?: number;\n ordered?: boolean;\n lang?: string | null;\n children?: SafeMdastNode[];\n attributes?: SafeMdxAttribute[];\n [key: string]: unknown;\n}\n\nexport interface SafeMdxAttribute {\n type: 'mdxJsxAttribute' | 'mdxJsxExpressionAttribute';\n name?: string;\n // A literal attribute carries a string; an expression attribute carries an\n // `mdxJsxAttributeValueExpression` object (a raw string, NO estree) or a string.\n value?: string | { type: string; value?: string } | null;\n}\n\ntype Deps = {\n fromMarkdown: (\n value: string,\n options: { extensions: unknown[]; mdastExtensions: unknown[] },\n ) => SafeMdastNode;\n mdxJsx: () => unknown;\n mdxJsxFromMarkdown: () => unknown;\n gfm: () => unknown;\n gfmFromMarkdown: () => unknown;\n};\n\nlet depsPromise: Promise<Deps> | null = null;\n\nfunction loadDeps(): Promise<Deps> {\n if (!depsPromise) {\n depsPromise = Promise.all([\n import('mdast-util-from-markdown'),\n import('micromark-extension-mdx-jsx'),\n import('mdast-util-mdx-jsx'),\n import('micromark-extension-gfm'),\n import('mdast-util-gfm'),\n ]).then(\n ([fromMd, mdxJsxExt, mdxJsxMdast, gfmExt, gfmMdast]) =>\n ({\n fromMarkdown: (fromMd as unknown as { fromMarkdown: Deps['fromMarkdown'] }).fromMarkdown,\n mdxJsx: (mdxJsxExt as unknown as { mdxJsx: Deps['mdxJsx'] }).mdxJsx,\n mdxJsxFromMarkdown: (mdxJsxMdast as unknown as { mdxJsxFromMarkdown: Deps['mdxJsxFromMarkdown'] })\n .mdxJsxFromMarkdown,\n gfm: (gfmExt as unknown as { gfm: Deps['gfm'] }).gfm,\n gfmFromMarkdown: (gfmMdast as unknown as { gfmFromMarkdown: Deps['gfmFromMarkdown'] }).gfmFromMarkdown,\n }) as Deps,\n );\n }\n return depsPromise;\n}\n\n/**\n * Parse untrusted Markdown/MDX-syntax source to an mdast tree with **no evaluator in\n * the pipeline**: JSX-as-data (no acorn), GFM on, the expression extension OFF, no\n * raw-HTML re-parse. The returned tree is safe to hand to `renderMdast` — expression\n * attributes are inert strings and raw HTML is inert `html` nodes.\n */\nexport async function parseSafeMdast(source: string): Promise<SafeMdastNode> {\n const { fromMarkdown, mdxJsx, mdxJsxFromMarkdown, gfm, gfmFromMarkdown } = await loadDeps();\n return fromMarkdown(source, {\n // `mdxJsx()` WITHOUT an acorn option → JSX tags + literal attrs; expressions are\n // raw strings, never estree. The mdx *expression* extension is intentionally\n // absent, so `{…}` in body text stays literal. GFM for tables/task-lists.\n extensions: [mdxJsx(), gfm()],\n mdastExtensions: [mdxJsxFromMarkdown(), gfmFromMarkdown()],\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAwDA,IAAI,cAAoC;AAExC,SAAS,WAA0B;AACjC,MAAI,CAAC,aAAa;AAChB,kBAAc,QAAQ,IAAI;AAAA,MACxB,OAAO,0BAA0B;AAAA,MACjC,OAAO,6BAA6B;AAAA,MACpC,OAAO,oBAAoB;AAAA,MAC3B,OAAO,yBAAyB;AAAA,MAChC,OAAO,gBAAgB;AAAA,IACzB,CAAC,EAAE;AAAA,MACD,CAAC,CAAC,QAAQ,WAAW,aAAa,QAAQ,QAAQ,OAC/C;AAAA,QACC,cAAe,OAA6D;AAAA,QAC5E,QAAS,UAAoD;AAAA,QAC7D,oBAAqB,YAClB;AAAA,QACH,KAAM,OAA2C;AAAA,QACjD,iBAAkB,SAAqE;AAAA,MACzF;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;AAQA,eAAsB,eAAe,QAAwC;AAC3E,QAAM,EAAE,cAAc,QAAQ,oBAAoB,KAAK,gBAAgB,IAAI,MAAM,SAAS;AAC1F,SAAO,aAAa,QAAQ;AAAA;AAAA;AAAA;AAAA,IAI1B,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,IAC5B,iBAAiB,CAAC,mBAAmB,GAAG,gBAAgB,CAAC;AAAA,EAC3D,CAAC;AACH;","names":[]}
@@ -0,0 +1,31 @@
1
+ interface SafeMdastNode {
2
+ type: string;
3
+ value?: string;
4
+ name?: string | null;
5
+ url?: string;
6
+ alt?: string | null;
7
+ title?: string | null;
8
+ depth?: number;
9
+ ordered?: boolean;
10
+ lang?: string | null;
11
+ children?: SafeMdastNode[];
12
+ attributes?: SafeMdxAttribute[];
13
+ [key: string]: unknown;
14
+ }
15
+ interface SafeMdxAttribute {
16
+ type: 'mdxJsxAttribute' | 'mdxJsxExpressionAttribute';
17
+ name?: string;
18
+ value?: string | {
19
+ type: string;
20
+ value?: string;
21
+ } | null;
22
+ }
23
+ /**
24
+ * Parse untrusted Markdown/MDX-syntax source to an mdast tree with **no evaluator in
25
+ * the pipeline**: JSX-as-data (no acorn), GFM on, the expression extension OFF, no
26
+ * raw-HTML re-parse. The returned tree is safe to hand to `renderMdast` — expression
27
+ * attributes are inert strings and raw HTML is inert `html` nodes.
28
+ */
29
+ declare function parseSafeMdast(source: string): Promise<SafeMdastNode>;
30
+
31
+ export { type SafeMdastNode, type SafeMdxAttribute, parseSafeMdast };
@@ -0,0 +1,31 @@
1
+ interface SafeMdastNode {
2
+ type: string;
3
+ value?: string;
4
+ name?: string | null;
5
+ url?: string;
6
+ alt?: string | null;
7
+ title?: string | null;
8
+ depth?: number;
9
+ ordered?: boolean;
10
+ lang?: string | null;
11
+ children?: SafeMdastNode[];
12
+ attributes?: SafeMdxAttribute[];
13
+ [key: string]: unknown;
14
+ }
15
+ interface SafeMdxAttribute {
16
+ type: 'mdxJsxAttribute' | 'mdxJsxExpressionAttribute';
17
+ name?: string;
18
+ value?: string | {
19
+ type: string;
20
+ value?: string;
21
+ } | null;
22
+ }
23
+ /**
24
+ * Parse untrusted Markdown/MDX-syntax source to an mdast tree with **no evaluator in
25
+ * the pipeline**: JSX-as-data (no acorn), GFM on, the expression extension OFF, no
26
+ * raw-HTML re-parse. The returned tree is safe to hand to `renderMdast` — expression
27
+ * attributes are inert strings and raw HTML is inert `html` nodes.
28
+ */
29
+ declare function parseSafeMdast(source: string): Promise<SafeMdastNode>;
30
+
31
+ export { type SafeMdastNode, type SafeMdxAttribute, parseSafeMdast };
@@ -0,0 +1,35 @@
1
+ let depsPromise = null;
2
+ function loadDeps() {
3
+ if (!depsPromise) {
4
+ depsPromise = Promise.all([
5
+ import("mdast-util-from-markdown"),
6
+ import("micromark-extension-mdx-jsx"),
7
+ import("mdast-util-mdx-jsx"),
8
+ import("micromark-extension-gfm"),
9
+ import("mdast-util-gfm")
10
+ ]).then(
11
+ ([fromMd, mdxJsxExt, mdxJsxMdast, gfmExt, gfmMdast]) => ({
12
+ fromMarkdown: fromMd.fromMarkdown,
13
+ mdxJsx: mdxJsxExt.mdxJsx,
14
+ mdxJsxFromMarkdown: mdxJsxMdast.mdxJsxFromMarkdown,
15
+ gfm: gfmExt.gfm,
16
+ gfmFromMarkdown: gfmMdast.gfmFromMarkdown
17
+ })
18
+ );
19
+ }
20
+ return depsPromise;
21
+ }
22
+ async function parseSafeMdast(source) {
23
+ const { fromMarkdown, mdxJsx, mdxJsxFromMarkdown, gfm, gfmFromMarkdown } = await loadDeps();
24
+ return fromMarkdown(source, {
25
+ // `mdxJsx()` WITHOUT an acorn option → JSX tags + literal attrs; expressions are
26
+ // raw strings, never estree. The mdx *expression* extension is intentionally
27
+ // absent, so `{…}` in body text stays literal. GFM for tables/task-lists.
28
+ extensions: [mdxJsx(), gfm()],
29
+ mdastExtensions: [mdxJsxFromMarkdown(), gfmFromMarkdown()]
30
+ });
31
+ }
32
+ export {
33
+ parseSafeMdast
34
+ };
35
+ //# sourceMappingURL=parseSafeMdast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/parseSafeMdast.ts"],"sourcesContent":["// The no-acorn parse pipeline for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// empirically verified 2026-07-07 against `@mdx-js/mdx` v3 / `micromark-extension-\n// mdx-jsx`). This is the load-bearing half of the fail-safe: `<Component/>` syntax\n// parses to an element node with **literal** attributes, and every expression\n// (`n={1+1}`, `f={fetch(\"/x\")}`, `{...spread}`) is captured as an **inert raw string\n// with no estree** — because we pass **NO acorn option** to `micromark-extension-mdx-\n// jsx`. There is therefore no evaluator anywhere in the pipeline (fail-safe), unlike\n// `@mdx-js/mdx`'s `compile({format:'mdx'})` which always wires acorn.\n//\n// The §5.1 residual guarantees are also here: the expression micromark extension is\n// **not** enabled (body `{…}` stays literal text), and **no `rehype-raw`** / raw-HTML\n// passthrough exists — raw HTML blocks arrive as inert `html` mdast nodes the renderer\n// prints as text, never injected.\n//\n// `micromark`/`mdast` are ESM-only. They are loaded with a memoised dynamic `import()`\n// (the transpiler's `compile.ts` pattern) so this module sits in the package's CJS\n// build too — a CJS consumer that never renders untrusted content never `require()`s\n// an ESM-only package, and one that does gets it via Node's ESM-from-CJS import. The\n// dynamic load changes only *when* the deps resolve, never the produced tree.\n\n// A minimal structural mdast shape — we deliberately avoid a type dependency on the\n// ESM-only `mdast`/`unist` packages (which would break the CJS build's type surface).\nexport interface SafeMdastNode {\n type: string;\n value?: string;\n name?: string | null;\n url?: string;\n alt?: string | null;\n title?: string | null;\n depth?: number;\n ordered?: boolean;\n lang?: string | null;\n children?: SafeMdastNode[];\n attributes?: SafeMdxAttribute[];\n [key: string]: unknown;\n}\n\nexport interface SafeMdxAttribute {\n type: 'mdxJsxAttribute' | 'mdxJsxExpressionAttribute';\n name?: string;\n // A literal attribute carries a string; an expression attribute carries an\n // `mdxJsxAttributeValueExpression` object (a raw string, NO estree) or a string.\n value?: string | { type: string; value?: string } | null;\n}\n\ntype Deps = {\n fromMarkdown: (\n value: string,\n options: { extensions: unknown[]; mdastExtensions: unknown[] },\n ) => SafeMdastNode;\n mdxJsx: () => unknown;\n mdxJsxFromMarkdown: () => unknown;\n gfm: () => unknown;\n gfmFromMarkdown: () => unknown;\n};\n\nlet depsPromise: Promise<Deps> | null = null;\n\nfunction loadDeps(): Promise<Deps> {\n if (!depsPromise) {\n depsPromise = Promise.all([\n import('mdast-util-from-markdown'),\n import('micromark-extension-mdx-jsx'),\n import('mdast-util-mdx-jsx'),\n import('micromark-extension-gfm'),\n import('mdast-util-gfm'),\n ]).then(\n ([fromMd, mdxJsxExt, mdxJsxMdast, gfmExt, gfmMdast]) =>\n ({\n fromMarkdown: (fromMd as unknown as { fromMarkdown: Deps['fromMarkdown'] }).fromMarkdown,\n mdxJsx: (mdxJsxExt as unknown as { mdxJsx: Deps['mdxJsx'] }).mdxJsx,\n mdxJsxFromMarkdown: (mdxJsxMdast as unknown as { mdxJsxFromMarkdown: Deps['mdxJsxFromMarkdown'] })\n .mdxJsxFromMarkdown,\n gfm: (gfmExt as unknown as { gfm: Deps['gfm'] }).gfm,\n gfmFromMarkdown: (gfmMdast as unknown as { gfmFromMarkdown: Deps['gfmFromMarkdown'] }).gfmFromMarkdown,\n }) as Deps,\n );\n }\n return depsPromise;\n}\n\n/**\n * Parse untrusted Markdown/MDX-syntax source to an mdast tree with **no evaluator in\n * the pipeline**: JSX-as-data (no acorn), GFM on, the expression extension OFF, no\n * raw-HTML re-parse. The returned tree is safe to hand to `renderMdast` — expression\n * attributes are inert strings and raw HTML is inert `html` nodes.\n */\nexport async function parseSafeMdast(source: string): Promise<SafeMdastNode> {\n const { fromMarkdown, mdxJsx, mdxJsxFromMarkdown, gfm, gfmFromMarkdown } = await loadDeps();\n return fromMarkdown(source, {\n // `mdxJsx()` WITHOUT an acorn option → JSX tags + literal attrs; expressions are\n // raw strings, never estree. The mdx *expression* extension is intentionally\n // absent, so `{…}` in body text stays literal. GFM for tables/task-lists.\n extensions: [mdxJsx(), gfm()],\n mdastExtensions: [mdxJsxFromMarkdown(), gfmFromMarkdown()],\n });\n}\n"],"mappings":"AAwDA,IAAI,cAAoC;AAExC,SAAS,WAA0B;AACjC,MAAI,CAAC,aAAa;AAChB,kBAAc,QAAQ,IAAI;AAAA,MACxB,OAAO,0BAA0B;AAAA,MACjC,OAAO,6BAA6B;AAAA,MACpC,OAAO,oBAAoB;AAAA,MAC3B,OAAO,yBAAyB;AAAA,MAChC,OAAO,gBAAgB;AAAA,IACzB,CAAC,EAAE;AAAA,MACD,CAAC,CAAC,QAAQ,WAAW,aAAa,QAAQ,QAAQ,OAC/C;AAAA,QACC,cAAe,OAA6D;AAAA,QAC5E,QAAS,UAAoD;AAAA,QAC7D,oBAAqB,YAClB;AAAA,QACH,KAAM,OAA2C;AAAA,QACjD,iBAAkB,SAAqE;AAAA,MACzF;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;AAQA,eAAsB,eAAe,QAAwC;AAC3E,QAAM,EAAE,cAAc,QAAQ,oBAAoB,KAAK,gBAAgB,IAAI,MAAM,SAAS;AAC1F,SAAO,aAAa,QAAQ;AAAA;AAAA;AAAA;AAAA,IAI1B,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,IAC5B,iBAAiB,CAAC,mBAAmB,GAAG,gBAAgB,CAAC;AAAA,EAC3D,CAAC;AACH;","names":[]}