@immediately-run/sdk 0.45.3 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MDXProvider.cjs +1 -5
- package/dist/MDXProvider.cjs.map +1 -1
- package/dist/MDXProvider.js +1 -5
- package/dist/MDXProvider.js.map +1 -1
- package/dist/RoutingSpec.cjs.map +1 -1
- package/dist/TinkerableContext.cjs.map +1 -1
- package/dist/TinkerableContext.js.map +1 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/boot.cjs +2 -9
- package/dist/boot.cjs.map +1 -1
- package/dist/boot.d.cts +2 -2
- package/dist/boot.d.ts +2 -2
- package/dist/boot.js +2 -9
- package/dist/boot.js.map +1 -1
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/components/Admonition.cjs +4 -13
- package/dist/components/Admonition.cjs.map +1 -1
- package/dist/components/Admonition.js +4 -13
- package/dist/components/Admonition.js.map +1 -1
- package/dist/components/FileRouter.cjs +11 -3
- package/dist/components/FileRouter.cjs.map +1 -1
- package/dist/components/FileRouter.js +11 -3
- package/dist/components/FileRouter.js.map +1 -1
- package/dist/components/HeadingAnchor.cjs.map +1 -1
- package/dist/components/HeadingAnchor.js.map +1 -1
- package/dist/components/Link.cjs.map +1 -1
- package/dist/components/Link.js.map +1 -1
- package/dist/components/MainContent.cjs +11 -1
- package/dist/components/MainContent.cjs.map +1 -1
- package/dist/components/MainContent.js +11 -1
- package/dist/components/MainContent.js.map +1 -1
- package/dist/components/MountImage.cjs +1 -9
- package/dist/components/MountImage.cjs.map +1 -1
- package/dist/components/MountImage.js +1 -9
- package/dist/components/MountImage.js.map +1 -1
- package/dist/components/Routes.cjs +1 -4
- package/dist/components/Routes.cjs.map +1 -1
- package/dist/components/Routes.d.cts +1 -1
- package/dist/components/Routes.d.ts +1 -1
- package/dist/components/Routes.js +1 -4
- package/dist/components/Routes.js.map +1 -1
- package/dist/components/SafeInclude.cjs +1 -4
- package/dist/components/SafeInclude.cjs.map +1 -1
- package/dist/components/SafeInclude.js +1 -4
- package/dist/components/SafeInclude.js.map +1 -1
- package/dist/components/WikiLink.cjs +1 -10
- package/dist/components/WikiLink.cjs.map +1 -1
- package/dist/components/WikiLink.js +1 -10
- package/dist/components/WikiLink.js.map +1 -1
- package/dist/components/defaults.cjs.map +1 -1
- package/dist/components/defaults.d.cts +1 -1
- package/dist/components/defaults.d.ts +1 -1
- package/dist/components/defaults.js.map +1 -1
- package/dist/components/errors.cjs +3 -1
- package/dist/components/errors.cjs.map +1 -1
- package/dist/components/errors.js +3 -1
- package/dist/components/errors.js.map +1 -1
- package/dist/contextUtils.cjs.map +1 -1
- package/dist/contextUtils.js.map +1 -1
- package/dist/contribute.cjs.map +1 -1
- package/dist/contribute.js.map +1 -1
- package/dist/debug.cjs +2 -1
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.js +3 -8
- package/dist/debug.js.map +1 -1
- package/dist/diagnostics.cjs.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/editorContext.cjs.map +1 -1
- package/dist/editorContext.js.map +1 -1
- package/dist/fs.cjs.map +1 -1
- package/dist/fs.js.map +1 -1
- package/dist/hostAttention.cjs +57 -0
- package/dist/hostAttention.cjs.map +1 -0
- package/dist/hostAttention.d.cts +39 -0
- package/dist/hostAttention.d.ts +39 -0
- package/dist/hostAttention.js +31 -0
- package/dist/hostAttention.js.map +1 -0
- package/dist/hostTransport.cjs +55 -0
- package/dist/hostTransport.cjs.map +1 -0
- package/dist/hostTransport.d.cts +12 -0
- package/dist/hostTransport.d.ts +12 -0
- package/dist/hostTransport.js +30 -0
- package/dist/hostTransport.js.map +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/injectedBundler.cjs.map +1 -1
- package/dist/injectedBundler.js.map +1 -1
- package/dist/ipc.cjs +1 -4
- package/dist/ipc.cjs.map +1 -1
- package/dist/ipc.js +1 -4
- package/dist/ipc.js.map +1 -1
- package/dist/irMarkers.cjs.map +1 -1
- package/dist/irMarkers.d.cts +11 -11
- package/dist/irMarkers.d.ts +11 -11
- package/dist/irMarkers.js.map +1 -1
- package/dist/launch.cjs.map +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/linkSpace.cjs +11 -2
- package/dist/linkSpace.cjs.map +1 -1
- package/dist/linkSpace.d.cts +23 -0
- package/dist/linkSpace.d.ts +23 -0
- package/dist/linkSpace.js +11 -2
- package/dist/linkSpace.js.map +1 -1
- package/dist/llm.cjs +26 -8
- package/dist/llm.cjs.map +1 -1
- package/dist/llm.d.cts +75 -3
- package/dist/llm.d.ts +75 -3
- package/dist/llm.js +21 -7
- package/dist/llm.js.map +1 -1
- package/dist/loading.cjs +18 -14
- package/dist/loading.cjs.map +1 -1
- package/dist/loading.d.cts +2 -2
- package/dist/loading.d.ts +2 -2
- package/dist/loading.js +19 -19
- package/dist/loading.js.map +1 -1
- package/dist/markers.cjs.map +1 -1
- package/dist/markers.js.map +1 -1
- package/dist/metadataSource.cjs +1 -5
- package/dist/metadataSource.cjs.map +1 -1
- package/dist/metadataSource.d.cts +1 -1
- package/dist/metadataSource.d.ts +1 -1
- package/dist/metadataSource.js +1 -5
- package/dist/metadataSource.js.map +1 -1
- package/dist/moduleCache.cjs +4 -1
- package/dist/moduleCache.cjs.map +1 -1
- package/dist/moduleCache.d.cts +1 -1
- package/dist/moduleCache.d.ts +1 -1
- package/dist/moduleCache.js +4 -1
- package/dist/moduleCache.js.map +1 -1
- package/dist/mountMatch.cjs.map +1 -1
- package/dist/mountMatch.js.map +1 -1
- package/dist/mounts.cjs +6 -1
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +2 -2
- package/dist/mounts.d.ts +2 -2
- package/dist/mounts.js +6 -1
- package/dist/mounts.js.map +1 -1
- package/dist/netFetch.cjs +3 -5
- package/dist/netFetch.cjs.map +1 -1
- package/dist/netFetch.js +3 -5
- package/dist/netFetch.js.map +1 -1
- package/dist/onFsChange.cjs.map +1 -1
- package/dist/onFsChange.js.map +1 -1
- package/dist/pathUtils.cjs +9 -12
- package/dist/pathUtils.cjs.map +1 -1
- package/dist/pathUtils.js +9 -12
- package/dist/pathUtils.js.map +1 -1
- package/dist/protocolDeadline.cjs +205 -0
- package/dist/protocolDeadline.cjs.map +1 -0
- package/dist/protocolDeadline.d.cts +146 -0
- package/dist/protocolDeadline.d.ts +146 -0
- package/dist/protocolDeadline.js +168 -0
- package/dist/protocolDeadline.js.map +1 -0
- package/dist/protocolStream.cjs +49 -6
- package/dist/protocolStream.cjs.map +1 -1
- package/dist/protocolStream.d.cts +9 -2
- package/dist/protocolStream.d.ts +9 -2
- package/dist/protocolStream.js +56 -6
- package/dist/protocolStream.js.map +1 -1
- package/dist/pushChannel.cjs +13 -9
- package/dist/pushChannel.cjs.map +1 -1
- package/dist/pushChannel.js +12 -8
- package/dist/pushChannel.js.map +1 -1
- package/dist/ready.cjs.map +1 -1
- package/dist/ready.js.map +1 -1
- package/dist/routing.cjs +3 -1
- package/dist/routing.cjs.map +1 -1
- package/dist/routing.js +3 -1
- package/dist/routing.js.map +1 -1
- package/dist/safeContent/index.cjs.map +1 -1
- package/dist/safeContent/index.js.map +1 -1
- package/dist/safeContent/parseSafeMdast.cjs.map +1 -1
- package/dist/safeContent/parseSafeMdast.js.map +1 -1
- package/dist/safeContent/renderMdast.cjs.map +1 -1
- package/dist/safeContent/renderMdast.js.map +1 -1
- package/dist/sandboxTypes.cjs.map +1 -1
- package/dist/sandboxUtils.cjs +69 -24
- package/dist/sandboxUtils.cjs.map +1 -1
- package/dist/sandboxUtils.d.cts +37 -4
- package/dist/sandboxUtils.d.ts +37 -4
- package/dist/sandboxUtils.js +74 -22
- package/dist/sandboxUtils.js.map +1 -1
- package/dist/scrollToId.cjs.map +1 -1
- package/dist/scrollToId.js.map +1 -1
- package/dist/secrets.cjs.map +1 -1
- package/dist/secrets.js.map +1 -1
- package/dist/tasks.cjs +12 -2
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +12 -2
- package/dist/tasks.js.map +1 -1
- package/dist/testing.cjs +1 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +1 -3
- package/dist/testing.js.map +1 -1
- package/dist/theme.cjs.map +1 -1
- package/dist/theme.js.map +1 -1
- package/dist/urlUtils.cjs +7 -18
- package/dist/urlUtils.cjs.map +1 -1
- package/dist/urlUtils.js +7 -18
- package/dist/urlUtils.js.map +1 -1
- package/dist/vcs.cjs +1 -3
- package/dist/vcs.cjs.map +1 -1
- package/dist/vcs.js +1 -3
- package/dist/vcs.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +11 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/HeadingAnchor.tsx"],"sourcesContent":["import { ReactNode, use, useCallback } from 'react';\nimport { scrollToId } from '../scrollToId';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { constructOuterUrl } from '../urlUtils';\nimport { FragmentLink } from './Link';\n\n/**\n * Default MDX `HeadingAnchor` component — the render target for the autolink anchor\n * the transpiler's heading-slug plugin (R3-186, `MARKDOWN_SYNTAX_SPEC §15.4`)\n * prepends to every heading. That plugin sets the heading's own `id` (a text slug,\n * or a `sec-…` section id for a numbered heading, R3-211) and emits\n * `<HeadingAnchor id=\"<that id>\" />` as the heading's first child.\n *\n * It is registered in {@link DEFAULT_MDX_COMPONENTS} so a heading anchor renders\n * even in a plain-markdown repo that never calls `boot({ mdxComponents })` — the\n * MDX missing-reference guard never fires (§11.2 phantom defaults). The markup is\n * semantic and accessible — an `aria`-labelled `<a href=\"#id\">` permalink — but\n * **styling is intentionally minimal** (`§15.4` honesty note): an app supplies CSS\n * targeting the `ir-heading-anchor` class to reveal it on hover / add an icon, or\n * overrides this component via `boot({ mdxComponents: { HeadingAnchor } })` to\n * change the icon, position, or behaviour (e.g. copy-permalink-to-clipboard) of\n * every heading anchor in every content file at once (§15.4 late binding).\n *\n * `id` is the *heading's own* id, so the permalink `#id` is the same URL a reader\n * copies and (for a `sec-…` id) an agent computes from a `§`-citation with zero\n * lookup. A missing/empty `id` (defensive — the plugin always supplies one)\n * renders nothing rather than a dead `#` link.\n */\nexport const HeadingAnchor = ({\n id,\n ...rest\n}: {\n id?: string;\n} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>): ReactNode => {\n // ⚠ A BARE `#id` IS THE WRONG HREF INSIDE A SANDBOXED APP, and wrong in a way that only\n // shows up when someone copies the link. The app runs in an iframe whose document URL is\n // the SANDBOX's (`https://sandbox.immediately.run/index.html?href=…`), so the browser\n // resolves `#id` against THAT — \"copy link address\" on a heading yields a\n // sandbox-internal URL that means nothing to anyone else. Reported from a real wiki\n // (2026-08-14) as exactly that URL instead of the entry's.\n //\n // Both halves have to be right, and they pull in opposite directions:\n // • the HREF must be absolute in the HOST's space, so copy-link / middle-click /\n // open-in-new-tab all produce a URL that resolves for a reader;\n // • the CLICK must NOT navigate to it — that would be a full page load to the page you\n // are already on. It scrolls, leaving the sandbox URL the host owns untouched.\n //\n // `FragmentLink` is the SDK's existing home for the second half; it runs the caller's\n // `onClick` first and stands down if it preventDefaults, which is what lets the href be\n // absolute here while the behaviour stays a scroll.\n const ctx = use(TinkerableContext);\n const onAnchorClick = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Let a modified click through: cmd/ctrl/middle-click means \"open the permalink\",\n // and that is precisely the case the absolute href exists to serve.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;\n e.preventDefault();\n if (id) scrollToId(id);\n },\n [id]
|
|
1
|
+
{"version":3,"sources":["../../src/components/HeadingAnchor.tsx"],"sourcesContent":["import { ReactNode, use, useCallback } from 'react';\nimport { scrollToId } from '../scrollToId';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { constructOuterUrl } from '../urlUtils';\nimport { FragmentLink } from './Link';\n\n/**\n * Default MDX `HeadingAnchor` component — the render target for the autolink anchor\n * the transpiler's heading-slug plugin (R3-186, `MARKDOWN_SYNTAX_SPEC §15.4`)\n * prepends to every heading. That plugin sets the heading's own `id` (a text slug,\n * or a `sec-…` section id for a numbered heading, R3-211) and emits\n * `<HeadingAnchor id=\"<that id>\" />` as the heading's first child.\n *\n * It is registered in {@link DEFAULT_MDX_COMPONENTS} so a heading anchor renders\n * even in a plain-markdown repo that never calls `boot({ mdxComponents })` — the\n * MDX missing-reference guard never fires (§11.2 phantom defaults). The markup is\n * semantic and accessible — an `aria`-labelled `<a href=\"#id\">` permalink — but\n * **styling is intentionally minimal** (`§15.4` honesty note): an app supplies CSS\n * targeting the `ir-heading-anchor` class to reveal it on hover / add an icon, or\n * overrides this component via `boot({ mdxComponents: { HeadingAnchor } })` to\n * change the icon, position, or behaviour (e.g. copy-permalink-to-clipboard) of\n * every heading anchor in every content file at once (§15.4 late binding).\n *\n * `id` is the *heading's own* id, so the permalink `#id` is the same URL a reader\n * copies and (for a `sec-…` id) an agent computes from a `§`-citation with zero\n * lookup. A missing/empty `id` (defensive — the plugin always supplies one)\n * renders nothing rather than a dead `#` link.\n */\nexport const HeadingAnchor = ({\n id,\n ...rest\n}: {\n id?: string;\n} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>): ReactNode => {\n // ⚠ A BARE `#id` IS THE WRONG HREF INSIDE A SANDBOXED APP, and wrong in a way that only\n // shows up when someone copies the link. The app runs in an iframe whose document URL is\n // the SANDBOX's (`https://sandbox.immediately.run/index.html?href=…`), so the browser\n // resolves `#id` against THAT — \"copy link address\" on a heading yields a\n // sandbox-internal URL that means nothing to anyone else. Reported from a real wiki\n // (2026-08-14) as exactly that URL instead of the entry's.\n //\n // Both halves have to be right, and they pull in opposite directions:\n // • the HREF must be absolute in the HOST's space, so copy-link / middle-click /\n // open-in-new-tab all produce a URL that resolves for a reader;\n // • the CLICK must NOT navigate to it — that would be a full page load to the page you\n // are already on. It scrolls, leaving the sandbox URL the host owns untouched.\n //\n // `FragmentLink` is the SDK's existing home for the second half; it runs the caller's\n // `onClick` first and stands down if it preventDefaults, which is what lets the href be\n // absolute here while the behaviour stays a scroll.\n const ctx = use(TinkerableContext);\n const onAnchorClick = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Let a modified click through: cmd/ctrl/middle-click means \"open the permalink\",\n // and that is precisely the case the absolute href exists to serve.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;\n e.preventDefault();\n if (id) scrollToId(id);\n },\n [id],\n );\n if (!id) return null;\n // No context (a plain-markdown repo that never called `boot()`) → keep the old bare\n // fragment, which FragmentLink handles on its own.\n const href = ctx?.navigationState ? constructOuterUrl(ctx.outerHref, `#${id}`, ctx.navigationState) : `#${id}`;\n return (\n <FragmentLink\n className=\"ir-heading-anchor\"\n href={href}\n onClick={onAnchorClick}\n aria-label=\"Permalink to this heading\"\n {...rest}\n >\n <span aria-hidden=\"true\">#</span>\n </FragmentLink>\n );\n};\n"],"mappings":";AAyEM;AAzEN,SAAoB,KAAK,mBAAmB;AAC5C,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAwBtB,MAAM,gBAAgB,CAAC;AAAA,EAC5B;AAAA,EACA,GAAG;AACL,MAE+E;AAiB7E,QAAM,MAAM,IAAI,iBAAiB;AACjC,QAAM,gBAAgB;AAAA,IACpB,CAAC,MAA2C;AAG1C,UAAI,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAG;AACxE,QAAE,eAAe;AACjB,UAAI,GAAI,YAAW,EAAE;AAAA,IACvB;AAAA,IACA,CAAC,EAAE;AAAA,EACL;AACA,MAAI,CAAC,GAAI,QAAO;AAGhB,QAAM,OAAO,KAAK,kBAAkB,kBAAkB,IAAI,WAAW,IAAI,EAAE,IAAI,IAAI,eAAe,IAAI,IAAI,EAAE;AAC5G,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV;AAAA,MACA,SAAS;AAAA,MACT,cAAW;AAAA,MACV,GAAG;AAAA,MAEJ,8BAAC,UAAK,eAAY,QAAO,eAAC;AAAA;AAAA,EAC5B;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Link.tsx"],"sourcesContent":["import { ReactNode, use, useCallback } from 'react';\nimport { navigate } from '../routing';\nimport { scrollToId } from '../scrollToId';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { constructOuterUrl, isInternalHref } from '../urlUtils';\n\n/** A same-page anchor (`#frag`): scrolls the addressed section into view on click\n * **without a route change** (MARKDOWN_SYNTAX_SPEC §13.5). The default behavior of a\n * bare `#`-href is intercepted so the sandbox URL the host owns is never mutated\n * out from under it; a fragment that names nothing leaves the scroll position\n * untouched (a soft failure). */\nexport const FragmentLink = ({\n href,\n children,\n onClick,\n ...props\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const clickHandler = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n if (href && href.startsWith('#')) {\n e.preventDefault();\n scrollToId(href.slice(1));\n }\n },\n [href, onClick]
|
|
1
|
+
{"version":3,"sources":["../../src/components/Link.tsx"],"sourcesContent":["import { ReactNode, use, useCallback } from 'react';\nimport { navigate } from '../routing';\nimport { scrollToId } from '../scrollToId';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { constructOuterUrl, isInternalHref } from '../urlUtils';\n\n/** A same-page anchor (`#frag`): scrolls the addressed section into view on click\n * **without a route change** (MARKDOWN_SYNTAX_SPEC §13.5). The default behavior of a\n * bare `#`-href is intercepted so the sandbox URL the host owns is never mutated\n * out from under it; a fragment that names nothing leaves the scroll position\n * untouched (a soft failure). */\nexport const FragmentLink = ({\n href,\n children,\n onClick,\n ...props\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const clickHandler = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n if (href && href.startsWith('#')) {\n e.preventDefault();\n scrollToId(href.slice(1));\n }\n },\n [href, onClick],\n );\n return (\n <a href={href} onClick={clickHandler} {...props}>\n {children}\n </a>\n );\n};\n\n/** An `<a>` that performs in-sandbox navigation on click (prevents the default\n * full-page load and routes via {@link navigate}).\n *\n * A consumer-supplied `onClick` is COMPOSED with the router interception, never\n * substituted for it (the {@link FragmentLink} contract): it runs first, and\n * calling `preventDefault()` opts out of routing. Regression guard: `...props`\n * used to spread AFTER `onClick={clickHandler}`, so a consumer `onClick` (e.g. a\n * drawer's close-on-navigate) silently REPLACED the interception — the default\n * anchor action then navigated the sandboxed iframe itself to the host URL,\n * reloading the whole app (and framing the host inside its own sandbox).\n *\n * Modifier/middle clicks and explicit non-self `target`s keep the browser\n * default: the rendered href is a real host URL, so open-in-new-tab works. */\nexport const InternalLink = ({\n href,\n children,\n onClick,\n target,\n ...props\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const clickHandler = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n if (!href) return;\n // Open-in-new-tab gestures (and an explicit target) are the browser's.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;\n if (target && target !== '_self') return;\n e.preventDefault();\n navigate(href);\n },\n [href, onClick, target],\n );\n // Spread FIRST so no forwarded prop can clobber the interception or the href.\n return (\n <a {...props} href={href} target={target} onClick={clickHandler}>\n {children}\n </a>\n );\n};\n\n/** A link that routes same-app hrefs through the sandbox router (as an\n * {@link InternalLink}) and renders external hrefs as a plain `<a>`. */\nexport const Link = ({\n href,\n children,\n ...properties\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const { outerHref, navigationState } = use(TinkerableContext);\n // A pure same-page fragment (`#sec-8-9`) scrolls in place — no route change (§13.5).\n if (href && href.startsWith('#')) {\n return (\n <FragmentLink href={href} {...properties}>\n {children}\n </FragmentLink>\n );\n }\n if (href && isInternalHref(outerHref, href, navigationState)) {\n const targetHref = constructOuterUrl(outerHref, href, navigationState);\n return (\n <InternalLink href={targetHref} {...properties}>\n {children}\n </InternalLink>\n );\n } else {\n // create a regular link to external resource\n return <a {...{ href, ...properties }}>{children}</a>;\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BI;AA7BJ,mBAA4C;AAC5C,qBAAyB;AACzB,wBAA2B;AAC3B,+BAAkC;AAClC,sBAAkD;AAO3C,MAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4G;AAC1G,QAAM,mBAAe;AAAA,IACnB,CAAC,MAA2C;AAC1C,gBAAU,CAAC;AACX,UAAI,EAAE,iBAAkB;AACxB,UAAI,QAAQ,KAAK,WAAW,GAAG,GAAG;AAChC,UAAE,eAAe;AACjB,0CAAW,KAAK,MAAM,CAAC,CAAC;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,CAAC,MAAM,OAAO;AAAA,EAChB;AACA,SACE,4CAAC,OAAE,MAAY,SAAS,cAAe,GAAG,OACvC,UACH;AAEJ;AAeO,MAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4G;AAC1G,QAAM,mBAAe;AAAA,IACnB,CAAC,MAA2C;AAC1C,gBAAU,CAAC;AACX,UAAI,EAAE,iBAAkB;AACxB,UAAI,CAAC,KAAM;AAEX,UAAI,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAG;AACxE,UAAI,UAAU,WAAW,QAAS;AAClC,QAAE,eAAe;AACjB,mCAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,MAAM,SAAS,MAAM;AAAA,EACxB;AAEA,SACE,4CAAC,OAAG,GAAG,OAAO,MAAY,QAAgB,SAAS,cAChD,UACH;AAEJ;AAIO,MAAM,OAAO,CAAC;AAAA,EACnB;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4G;AAC1G,QAAM,EAAE,WAAW,gBAAgB,QAAI,kBAAI,0CAAiB;AAE5D,MAAI,QAAQ,KAAK,WAAW,GAAG,GAAG;AAChC,WACE,4CAAC,gBAAa,MAAa,GAAG,YAC3B,UACH;AAAA,EAEJ;AACA,MAAI,YAAQ,gCAAe,WAAW,MAAM,eAAe,GAAG;AAC5D,UAAM,iBAAa,mCAAkB,WAAW,MAAM,eAAe;AACrE,WACE,4CAAC,gBAAa,MAAM,YAAa,GAAG,YACjC,UACH;AAAA,EAEJ,OAAO;AAEL,WAAO,4CAAC,OAAG,GAAG,EAAE,MAAM,GAAG,WAAW,GAAI,UAAS;AAAA,EACnD;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Link.tsx"],"sourcesContent":["import { ReactNode, use, useCallback } from 'react';\nimport { navigate } from '../routing';\nimport { scrollToId } from '../scrollToId';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { constructOuterUrl, isInternalHref } from '../urlUtils';\n\n/** A same-page anchor (`#frag`): scrolls the addressed section into view on click\n * **without a route change** (MARKDOWN_SYNTAX_SPEC §13.5). The default behavior of a\n * bare `#`-href is intercepted so the sandbox URL the host owns is never mutated\n * out from under it; a fragment that names nothing leaves the scroll position\n * untouched (a soft failure). */\nexport const FragmentLink = ({\n href,\n children,\n onClick,\n ...props\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const clickHandler = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n if (href && href.startsWith('#')) {\n e.preventDefault();\n scrollToId(href.slice(1));\n }\n },\n [href, onClick]
|
|
1
|
+
{"version":3,"sources":["../../src/components/Link.tsx"],"sourcesContent":["import { ReactNode, use, useCallback } from 'react';\nimport { navigate } from '../routing';\nimport { scrollToId } from '../scrollToId';\nimport { TinkerableContext } from '../TinkerableContext';\nimport { constructOuterUrl, isInternalHref } from '../urlUtils';\n\n/** A same-page anchor (`#frag`): scrolls the addressed section into view on click\n * **without a route change** (MARKDOWN_SYNTAX_SPEC §13.5). The default behavior of a\n * bare `#`-href is intercepted so the sandbox URL the host owns is never mutated\n * out from under it; a fragment that names nothing leaves the scroll position\n * untouched (a soft failure). */\nexport const FragmentLink = ({\n href,\n children,\n onClick,\n ...props\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const clickHandler = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n if (href && href.startsWith('#')) {\n e.preventDefault();\n scrollToId(href.slice(1));\n }\n },\n [href, onClick],\n );\n return (\n <a href={href} onClick={clickHandler} {...props}>\n {children}\n </a>\n );\n};\n\n/** An `<a>` that performs in-sandbox navigation on click (prevents the default\n * full-page load and routes via {@link navigate}).\n *\n * A consumer-supplied `onClick` is COMPOSED with the router interception, never\n * substituted for it (the {@link FragmentLink} contract): it runs first, and\n * calling `preventDefault()` opts out of routing. Regression guard: `...props`\n * used to spread AFTER `onClick={clickHandler}`, so a consumer `onClick` (e.g. a\n * drawer's close-on-navigate) silently REPLACED the interception — the default\n * anchor action then navigated the sandboxed iframe itself to the host URL,\n * reloading the whole app (and framing the host inside its own sandbox).\n *\n * Modifier/middle clicks and explicit non-self `target`s keep the browser\n * default: the rendered href is a real host URL, so open-in-new-tab works. */\nexport const InternalLink = ({\n href,\n children,\n onClick,\n target,\n ...props\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const clickHandler = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>) => {\n onClick?.(e);\n if (e.defaultPrevented) return;\n if (!href) return;\n // Open-in-new-tab gestures (and an explicit target) are the browser's.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;\n if (target && target !== '_self') return;\n e.preventDefault();\n navigate(href);\n },\n [href, onClick, target],\n );\n // Spread FIRST so no forwarded prop can clobber the interception or the href.\n return (\n <a {...props} href={href} target={target} onClick={clickHandler}>\n {children}\n </a>\n );\n};\n\n/** A link that routes same-app hrefs through the sandbox router (as an\n * {@link InternalLink}) and renders external hrefs as a plain `<a>`. */\nexport const Link = ({\n href,\n children,\n ...properties\n}: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>): ReactNode => {\n const { outerHref, navigationState } = use(TinkerableContext);\n // A pure same-page fragment (`#sec-8-9`) scrolls in place — no route change (§13.5).\n if (href && href.startsWith('#')) {\n return (\n <FragmentLink href={href} {...properties}>\n {children}\n </FragmentLink>\n );\n }\n if (href && isInternalHref(outerHref, href, navigationState)) {\n const targetHref = constructOuterUrl(outerHref, href, navigationState);\n return (\n <InternalLink href={targetHref} {...properties}>\n {children}\n </InternalLink>\n );\n } else {\n // create a regular link to external resource\n return <a {...{ href, ...properties }}>{children}</a>;\n }\n};\n"],"mappings":";AA6BI;AA7BJ,SAAoB,KAAK,mBAAmB;AAC5C,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAClC,SAAS,mBAAmB,sBAAsB;AAO3C,MAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4G;AAC1G,QAAM,eAAe;AAAA,IACnB,CAAC,MAA2C;AAC1C,gBAAU,CAAC;AACX,UAAI,EAAE,iBAAkB;AACxB,UAAI,QAAQ,KAAK,WAAW,GAAG,GAAG;AAChC,UAAE,eAAe;AACjB,mBAAW,KAAK,MAAM,CAAC,CAAC;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,CAAC,MAAM,OAAO;AAAA,EAChB;AACA,SACE,oBAAC,OAAE,MAAY,SAAS,cAAe,GAAG,OACvC,UACH;AAEJ;AAeO,MAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4G;AAC1G,QAAM,eAAe;AAAA,IACnB,CAAC,MAA2C;AAC1C,gBAAU,CAAC;AACX,UAAI,EAAE,iBAAkB;AACxB,UAAI,CAAC,KAAM;AAEX,UAAI,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAG;AACxE,UAAI,UAAU,WAAW,QAAS;AAClC,QAAE,eAAe;AACjB,eAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,MAAM,SAAS,MAAM;AAAA,EACxB;AAEA,SACE,oBAAC,OAAG,GAAG,OAAO,MAAY,QAAgB,SAAS,cAChD,UACH;AAEJ;AAIO,MAAM,OAAO,CAAC;AAAA,EACnB;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA4G;AAC1G,QAAM,EAAE,WAAW,gBAAgB,IAAI,IAAI,iBAAiB;AAE5D,MAAI,QAAQ,KAAK,WAAW,GAAG,GAAG;AAChC,WACE,oBAAC,gBAAa,MAAa,GAAG,YAC3B,UACH;AAAA,EAEJ;AACA,MAAI,QAAQ,eAAe,WAAW,MAAM,eAAe,GAAG;AAC5D,UAAM,aAAa,kBAAkB,WAAW,MAAM,eAAe;AACrE,WACE,oBAAC,gBAAa,MAAM,YAAa,GAAG,YACjC,UACH;AAAA,EAEJ,OAAO;AAEL,WAAO,oBAAC,OAAG,GAAG,EAAE,MAAM,GAAG,WAAW,GAAI,UAAS;AAAA,EACnD;AACF;","names":[]}
|
|
@@ -29,7 +29,17 @@ var import_react_error_boundary = require("react-error-boundary");
|
|
|
29
29
|
var import_routing = require("../routing");
|
|
30
30
|
var import_urlUtils = require("../urlUtils");
|
|
31
31
|
var import_defaults = require("./defaults");
|
|
32
|
-
const candidates = [
|
|
32
|
+
const candidates = [
|
|
33
|
+
"/src/App.tsx",
|
|
34
|
+
"/src/App.ts",
|
|
35
|
+
"/src/App.js",
|
|
36
|
+
"/App.tsx",
|
|
37
|
+
"/App.ts",
|
|
38
|
+
"/App.js",
|
|
39
|
+
"/README.md",
|
|
40
|
+
"/README.mdx",
|
|
41
|
+
"/README.html"
|
|
42
|
+
];
|
|
33
43
|
const fileExists = async (path) => {
|
|
34
44
|
const bundler = module.evaluation.module.bundler;
|
|
35
45
|
const exists = await bundler.fs.isFile.async((0, import_urlUtils.underAppRoot)(path));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/MainContent.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\nimport { navigate, useTinkerableLink } from '../routing';\nimport { FILES_PREFIX, underAppRoot } from '../urlUtils';\n\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// Repo-relative candidate paths. These are kept repo-relative because the\n// returned path is reused below to build the redirect URL (which is anchored to\n// `/app` by the file router); only the filesystem existence check is resolved\n// under `APP_ROOT`, since `bundler.fs` is rooted at `/`.\nconst candidates = ['/src/App.tsx'
|
|
1
|
+
{"version":3,"sources":["../../src/components/MainContent.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\nimport { navigate, useTinkerableLink } from '../routing';\nimport { FILES_PREFIX, underAppRoot } from '../urlUtils';\n\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// Repo-relative candidate paths. These are kept repo-relative because the\n// returned path is reused below to build the redirect URL (which is anchored to\n// `/app` by the file router); only the filesystem existence check is resolved\n// under `APP_ROOT`, since `bundler.fs` is rooted at `/`.\nconst candidates = [\n '/src/App.tsx',\n '/src/App.ts',\n '/src/App.js',\n '/App.tsx',\n '/App.ts',\n '/App.js',\n '/README.md',\n '/README.mdx',\n '/README.html',\n];\n\nconst fileExists = async (path: string): Promise<[string, boolean]> => {\n // @ts-ignore\n const bundler = module.evaluation.module.bundler;\n const exists = await bundler.fs.isFile.async(underAppRoot(path));\n return [path, exists];\n};\n\nexport const MainContentRedirect = ({ filename }: { filename: string }) => {\n const url = useTinkerableLink(filename);\n navigate(url);\n return <>Redirecting to {filename}</>;\n};\n\nexport const MainContentInner = ({\n candidatesExistPromise,\n}: {\n candidatesExistPromise: Promise<[string, boolean][]>;\n}) => {\n const candidatesExist = use(candidatesExistPromise);\n const filename = candidatesExist.find(([_, exists]) => exists)?.[0];\n if (!filename) {\n // todo: show file list\n throw new Error(`No main content file present`);\n }\n return <MainContentRedirect filename={FILES_PREFIX + filename} />;\n};\n\nexport const MainContent = ({\n LoadingComponent = defaultLoadingComponent,\n ErrorComponent = defaultErrorComponent,\n}: {\n LoadingComponent?: typeof defaultLoadingComponent;\n ErrorComponent?: typeof defaultErrorComponent;\n} = {}) => {\n // TODO: when to invalidate?\n const candidatesExistPromise = useMemo(() => Promise.all(candidates.map(fileExists)), []);\n return (\n <ErrorBoundary fallbackRender={ErrorComponent}>\n <Suspense fallback={<LoadingComponent />}>\n <MainContentInner candidatesExistPromise={candidatesExistPromise} />\n </Suspense>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCS;AAjCT,mBAAuC;AACvC,kCAA8B;AAC9B,qBAA4C;AAC5C,sBAA2C;AAE3C,sBAA+D;AAM/D,MAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,aAAa,OAAO,SAA6C;AAErE,QAAM,UAAU,OAAO,WAAW,OAAO;AACzC,QAAM,SAAS,MAAM,QAAQ,GAAG,OAAO,UAAM,8BAAa,IAAI,CAAC;AAC/D,SAAO,CAAC,MAAM,MAAM;AACtB;AAEO,MAAM,sBAAsB,CAAC,EAAE,SAAS,MAA4B;AACzE,QAAM,UAAM,kCAAkB,QAAQ;AACtC,+BAAS,GAAG;AACZ,SAAO,4EAAE;AAAA;AAAA,IAAgB;AAAA,KAAS;AACpC;AAEO,MAAM,mBAAmB,CAAC;AAAA,EAC/B;AACF,MAEM;AACJ,QAAM,sBAAkB,kBAAI,sBAAsB;AAClD,QAAM,WAAW,gBAAgB,KAAK,CAAC,CAAC,GAAG,MAAM,MAAM,MAAM,IAAI,CAAC;AAClE,MAAI,CAAC,UAAU;AAEb,UAAM,IAAI,MAAM,8BAA8B;AAAA,EAChD;AACA,SAAO,4CAAC,uBAAoB,UAAU,+BAAe,UAAU;AACjE;AAEO,MAAM,cAAc,CAAC;AAAA,EAC1B,mBAAmB;AAAA,EACnB,iBAAiB;AACnB,IAGI,CAAC,MAAM;AAET,QAAM,6BAAyB,sBAAQ,MAAM,QAAQ,IAAI,WAAW,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;AACxF,SACE,4CAAC,6CAAc,gBAAgB,gBAC7B,sDAAC,yBAAS,UAAU,4CAAC,oBAAiB,GACpC,sDAAC,oBAAiB,wBAAgD,GACpE,GACF;AAEJ;","names":[]}
|
|
@@ -5,7 +5,17 @@ import { ErrorBoundary } from "react-error-boundary";
|
|
|
5
5
|
import { navigate, useTinkerableLink } from "../routing";
|
|
6
6
|
import { FILES_PREFIX, underAppRoot } from "../urlUtils";
|
|
7
7
|
import { defaultErrorComponent, defaultLoadingComponent } from "./defaults";
|
|
8
|
-
const candidates = [
|
|
8
|
+
const candidates = [
|
|
9
|
+
"/src/App.tsx",
|
|
10
|
+
"/src/App.ts",
|
|
11
|
+
"/src/App.js",
|
|
12
|
+
"/App.tsx",
|
|
13
|
+
"/App.ts",
|
|
14
|
+
"/App.js",
|
|
15
|
+
"/README.md",
|
|
16
|
+
"/README.mdx",
|
|
17
|
+
"/README.html"
|
|
18
|
+
];
|
|
9
19
|
const fileExists = async (path) => {
|
|
10
20
|
const bundler = module.evaluation.module.bundler;
|
|
11
21
|
const exists = await bundler.fs.isFile.async(underAppRoot(path));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/MainContent.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\nimport { navigate, useTinkerableLink } from '../routing';\nimport { FILES_PREFIX, underAppRoot } from '../urlUtils';\n\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// Repo-relative candidate paths. These are kept repo-relative because the\n// returned path is reused below to build the redirect URL (which is anchored to\n// `/app` by the file router); only the filesystem existence check is resolved\n// under `APP_ROOT`, since `bundler.fs` is rooted at `/`.\nconst candidates = ['/src/App.tsx'
|
|
1
|
+
{"version":3,"sources":["../../src/components/MainContent.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\nimport { navigate, useTinkerableLink } from '../routing';\nimport { FILES_PREFIX, underAppRoot } from '../urlUtils';\n\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// Repo-relative candidate paths. These are kept repo-relative because the\n// returned path is reused below to build the redirect URL (which is anchored to\n// `/app` by the file router); only the filesystem existence check is resolved\n// under `APP_ROOT`, since `bundler.fs` is rooted at `/`.\nconst candidates = [\n '/src/App.tsx',\n '/src/App.ts',\n '/src/App.js',\n '/App.tsx',\n '/App.ts',\n '/App.js',\n '/README.md',\n '/README.mdx',\n '/README.html',\n];\n\nconst fileExists = async (path: string): Promise<[string, boolean]> => {\n // @ts-ignore\n const bundler = module.evaluation.module.bundler;\n const exists = await bundler.fs.isFile.async(underAppRoot(path));\n return [path, exists];\n};\n\nexport const MainContentRedirect = ({ filename }: { filename: string }) => {\n const url = useTinkerableLink(filename);\n navigate(url);\n return <>Redirecting to {filename}</>;\n};\n\nexport const MainContentInner = ({\n candidatesExistPromise,\n}: {\n candidatesExistPromise: Promise<[string, boolean][]>;\n}) => {\n const candidatesExist = use(candidatesExistPromise);\n const filename = candidatesExist.find(([_, exists]) => exists)?.[0];\n if (!filename) {\n // todo: show file list\n throw new Error(`No main content file present`);\n }\n return <MainContentRedirect filename={FILES_PREFIX + filename} />;\n};\n\nexport const MainContent = ({\n LoadingComponent = defaultLoadingComponent,\n ErrorComponent = defaultErrorComponent,\n}: {\n LoadingComponent?: typeof defaultLoadingComponent;\n ErrorComponent?: typeof defaultErrorComponent;\n} = {}) => {\n // TODO: when to invalidate?\n const candidatesExistPromise = useMemo(() => Promise.all(candidates.map(fileExists)), []);\n return (\n <ErrorBoundary fallbackRender={ErrorComponent}>\n <Suspense fallback={<LoadingComponent />}>\n <MainContentInner candidatesExistPromise={candidatesExistPromise} />\n </Suspense>\n </ErrorBoundary>\n );\n};\n"],"mappings":";AAiCS,mBAcA,KAdA;AAjCT,SAAS,UAAU,KAAK,eAAe;AACvC,SAAS,qBAAqB;AAC9B,SAAS,UAAU,yBAAyB;AAC5C,SAAS,cAAc,oBAAoB;AAE3C,SAAS,uBAAuB,+BAA+B;AAM/D,MAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,aAAa,OAAO,SAA6C;AAErE,QAAM,UAAU,OAAO,WAAW,OAAO;AACzC,QAAM,SAAS,MAAM,QAAQ,GAAG,OAAO,MAAM,aAAa,IAAI,CAAC;AAC/D,SAAO,CAAC,MAAM,MAAM;AACtB;AAEO,MAAM,sBAAsB,CAAC,EAAE,SAAS,MAA4B;AACzE,QAAM,MAAM,kBAAkB,QAAQ;AACtC,WAAS,GAAG;AACZ,SAAO,iCAAE;AAAA;AAAA,IAAgB;AAAA,KAAS;AACpC;AAEO,MAAM,mBAAmB,CAAC;AAAA,EAC/B;AACF,MAEM;AACJ,QAAM,kBAAkB,IAAI,sBAAsB;AAClD,QAAM,WAAW,gBAAgB,KAAK,CAAC,CAAC,GAAG,MAAM,MAAM,MAAM,IAAI,CAAC;AAClE,MAAI,CAAC,UAAU;AAEb,UAAM,IAAI,MAAM,8BAA8B;AAAA,EAChD;AACA,SAAO,oBAAC,uBAAoB,UAAU,eAAe,UAAU;AACjE;AAEO,MAAM,cAAc,CAAC;AAAA,EAC1B,mBAAmB;AAAA,EACnB,iBAAiB;AACnB,IAGI,CAAC,MAAM;AAET,QAAM,yBAAyB,QAAQ,MAAM,QAAQ,IAAI,WAAW,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;AACxF,SACE,oBAAC,iBAAc,gBAAgB,gBAC7B,8BAAC,YAAS,UAAU,oBAAC,oBAAiB,GACpC,8BAAC,oBAAiB,wBAAgD,GACpE,GACF;AAEJ;","names":[]}
|
|
@@ -23,15 +23,7 @@ __export(MountImage_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(MountImage_exports);
|
|
24
24
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25
25
|
var import_hooks = require("../hooks");
|
|
26
|
-
function MountImage({
|
|
27
|
-
mount,
|
|
28
|
-
relPath,
|
|
29
|
-
type,
|
|
30
|
-
placeholder,
|
|
31
|
-
fallback,
|
|
32
|
-
alt = "",
|
|
33
|
-
...imgProps
|
|
34
|
-
}) {
|
|
26
|
+
function MountImage({ mount, relPath, type, placeholder, fallback, alt = "", ...imgProps }) {
|
|
35
27
|
const { url, loading, error } = (0, import_hooks.useObjectUrl)(mount, relPath, type ? { type } : void 0);
|
|
36
28
|
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: placeholder ?? null });
|
|
37
29
|
if (error || !url) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback ?? null });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/MountImage.tsx"],"sourcesContent":["import type { ImgHTMLAttributes, ReactNode } from 'react';\nimport { useObjectUrl } from '../hooks';\nimport type { SandboxMount } from '../mounts';\n\n/** Props for {@link MountImage}. Extends the native `<img>` attributes (minus `src`,\n * which the component derives from the mounted file). */\nexport interface MountImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {\n /** The mount whose file to display (`null`/`undefined` renders `loading`). */\n mount: SandboxMount | null | undefined;\n /** Mount-relative path to the image file (`null`/`undefined` renders `loading`). */\n relPath: string | null | undefined;\n /** Override the MIME type inferred from the extension (rarely needed). */\n type?: string;\n /** Rendered while the bytes load. Defaults to nothing. (Named `placeholder`, not\n * `loading`, so the native `<img loading=\"lazy\">` attribute still passes through.) */\n placeholder?: ReactNode;\n /** Rendered when the read fails or the file is missing. Defaults to nothing. */\n fallback?: ReactNode;\n}\n\n/**\n * Display an image file from a mount. Reads the bytes off the sandbox ZenFS, turns\n * them into an object URL, and renders an `<img>` — revoking the URL for you on\n * unmount / prop change (via {@link useObjectUrl}). This is the drop-in for the\n * pattern apps used to hand-roll: read bytes → `Blob` → `createObjectURL` → revoke.\n *\n * Any extra `<img>` attribute (`className`, `style`, `onClick`, `width`, …) passes\n * straight through. Provide `loading` / `fallback` for the pending and error states.\n *\n * ```tsx\n * <MountImage\n * mount={mount}\n * relPath=\"photos/cat.png\"\n * alt=\"cat\"\n * placeholder={<Spinner />}\n * fallback={<span>missing</span>}\n * />\n * ```\n */\nexport function MountImage({
|
|
1
|
+
{"version":3,"sources":["../../src/components/MountImage.tsx"],"sourcesContent":["import type { ImgHTMLAttributes, ReactNode } from 'react';\nimport { useObjectUrl } from '../hooks';\nimport type { SandboxMount } from '../mounts';\n\n/** Props for {@link MountImage}. Extends the native `<img>` attributes (minus `src`,\n * which the component derives from the mounted file). */\nexport interface MountImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {\n /** The mount whose file to display (`null`/`undefined` renders `loading`). */\n mount: SandboxMount | null | undefined;\n /** Mount-relative path to the image file (`null`/`undefined` renders `loading`). */\n relPath: string | null | undefined;\n /** Override the MIME type inferred from the extension (rarely needed). */\n type?: string;\n /** Rendered while the bytes load. Defaults to nothing. (Named `placeholder`, not\n * `loading`, so the native `<img loading=\"lazy\">` attribute still passes through.) */\n placeholder?: ReactNode;\n /** Rendered when the read fails or the file is missing. Defaults to nothing. */\n fallback?: ReactNode;\n}\n\n/**\n * Display an image file from a mount. Reads the bytes off the sandbox ZenFS, turns\n * them into an object URL, and renders an `<img>` — revoking the URL for you on\n * unmount / prop change (via {@link useObjectUrl}). This is the drop-in for the\n * pattern apps used to hand-roll: read bytes → `Blob` → `createObjectURL` → revoke.\n *\n * Any extra `<img>` attribute (`className`, `style`, `onClick`, `width`, …) passes\n * straight through. Provide `loading` / `fallback` for the pending and error states.\n *\n * ```tsx\n * <MountImage\n * mount={mount}\n * relPath=\"photos/cat.png\"\n * alt=\"cat\"\n * placeholder={<Spinner />}\n * fallback={<span>missing</span>}\n * />\n * ```\n */\nexport function MountImage({ mount, relPath, type, placeholder, fallback, alt = '', ...imgProps }: MountImageProps) {\n const { url, loading, error } = useObjectUrl(mount, relPath, type ? { type } : undefined);\n if (loading) return <>{placeholder ?? null}</>;\n if (error || !url) return <>{fallback ?? null}</>;\n return <img src={url} alt={alt} {...imgProps} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCsB;AAxCtB,mBAA6B;AAsCtB,SAAS,WAAW,EAAE,OAAO,SAAS,MAAM,aAAa,UAAU,MAAM,IAAI,GAAG,SAAS,GAAoB;AAClH,QAAM,EAAE,KAAK,SAAS,MAAM,QAAI,2BAAa,OAAO,SAAS,OAAO,EAAE,KAAK,IAAI,MAAS;AACxF,MAAI,QAAS,QAAO,2EAAG,yBAAe,MAAK;AAC3C,MAAI,SAAS,CAAC,IAAK,QAAO,2EAAG,sBAAY,MAAK;AAC9C,SAAO,4CAAC,SAAI,KAAK,KAAK,KAAW,GAAG,UAAU;AAChD;","names":[]}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import "../chunk-VHAA22YE.js";
|
|
2
2
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useObjectUrl } from "../hooks";
|
|
4
|
-
function MountImage({
|
|
5
|
-
mount,
|
|
6
|
-
relPath,
|
|
7
|
-
type,
|
|
8
|
-
placeholder,
|
|
9
|
-
fallback,
|
|
10
|
-
alt = "",
|
|
11
|
-
...imgProps
|
|
12
|
-
}) {
|
|
4
|
+
function MountImage({ mount, relPath, type, placeholder, fallback, alt = "", ...imgProps }) {
|
|
13
5
|
const { url, loading, error } = useObjectUrl(mount, relPath, type ? { type } : void 0);
|
|
14
6
|
if (loading) return /* @__PURE__ */ jsx(Fragment, { children: placeholder ?? null });
|
|
15
7
|
if (error || !url) return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? null });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/MountImage.tsx"],"sourcesContent":["import type { ImgHTMLAttributes, ReactNode } from 'react';\nimport { useObjectUrl } from '../hooks';\nimport type { SandboxMount } from '../mounts';\n\n/** Props for {@link MountImage}. Extends the native `<img>` attributes (minus `src`,\n * which the component derives from the mounted file). */\nexport interface MountImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {\n /** The mount whose file to display (`null`/`undefined` renders `loading`). */\n mount: SandboxMount | null | undefined;\n /** Mount-relative path to the image file (`null`/`undefined` renders `loading`). */\n relPath: string | null | undefined;\n /** Override the MIME type inferred from the extension (rarely needed). */\n type?: string;\n /** Rendered while the bytes load. Defaults to nothing. (Named `placeholder`, not\n * `loading`, so the native `<img loading=\"lazy\">` attribute still passes through.) */\n placeholder?: ReactNode;\n /** Rendered when the read fails or the file is missing. Defaults to nothing. */\n fallback?: ReactNode;\n}\n\n/**\n * Display an image file from a mount. Reads the bytes off the sandbox ZenFS, turns\n * them into an object URL, and renders an `<img>` — revoking the URL for you on\n * unmount / prop change (via {@link useObjectUrl}). This is the drop-in for the\n * pattern apps used to hand-roll: read bytes → `Blob` → `createObjectURL` → revoke.\n *\n * Any extra `<img>` attribute (`className`, `style`, `onClick`, `width`, …) passes\n * straight through. Provide `loading` / `fallback` for the pending and error states.\n *\n * ```tsx\n * <MountImage\n * mount={mount}\n * relPath=\"photos/cat.png\"\n * alt=\"cat\"\n * placeholder={<Spinner />}\n * fallback={<span>missing</span>}\n * />\n * ```\n */\nexport function MountImage({
|
|
1
|
+
{"version":3,"sources":["../../src/components/MountImage.tsx"],"sourcesContent":["import type { ImgHTMLAttributes, ReactNode } from 'react';\nimport { useObjectUrl } from '../hooks';\nimport type { SandboxMount } from '../mounts';\n\n/** Props for {@link MountImage}. Extends the native `<img>` attributes (minus `src`,\n * which the component derives from the mounted file). */\nexport interface MountImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {\n /** The mount whose file to display (`null`/`undefined` renders `loading`). */\n mount: SandboxMount | null | undefined;\n /** Mount-relative path to the image file (`null`/`undefined` renders `loading`). */\n relPath: string | null | undefined;\n /** Override the MIME type inferred from the extension (rarely needed). */\n type?: string;\n /** Rendered while the bytes load. Defaults to nothing. (Named `placeholder`, not\n * `loading`, so the native `<img loading=\"lazy\">` attribute still passes through.) */\n placeholder?: ReactNode;\n /** Rendered when the read fails or the file is missing. Defaults to nothing. */\n fallback?: ReactNode;\n}\n\n/**\n * Display an image file from a mount. Reads the bytes off the sandbox ZenFS, turns\n * them into an object URL, and renders an `<img>` — revoking the URL for you on\n * unmount / prop change (via {@link useObjectUrl}). This is the drop-in for the\n * pattern apps used to hand-roll: read bytes → `Blob` → `createObjectURL` → revoke.\n *\n * Any extra `<img>` attribute (`className`, `style`, `onClick`, `width`, …) passes\n * straight through. Provide `loading` / `fallback` for the pending and error states.\n *\n * ```tsx\n * <MountImage\n * mount={mount}\n * relPath=\"photos/cat.png\"\n * alt=\"cat\"\n * placeholder={<Spinner />}\n * fallback={<span>missing</span>}\n * />\n * ```\n */\nexport function MountImage({ mount, relPath, type, placeholder, fallback, alt = '', ...imgProps }: MountImageProps) {\n const { url, loading, error } = useObjectUrl(mount, relPath, type ? { type } : undefined);\n if (loading) return <>{placeholder ?? null}</>;\n if (error || !url) return <>{fallback ?? null}</>;\n return <img src={url} alt={alt} {...imgProps} />;\n}\n"],"mappings":";AAyCsB;AAxCtB,SAAS,oBAAoB;AAsCtB,SAAS,WAAW,EAAE,OAAO,SAAS,MAAM,aAAa,UAAU,MAAM,IAAI,GAAG,SAAS,GAAoB;AAClH,QAAM,EAAE,KAAK,SAAS,MAAM,IAAI,aAAa,OAAO,SAAS,OAAO,EAAE,KAAK,IAAI,MAAS;AACxF,MAAI,QAAS,QAAO,gCAAG,yBAAe,MAAK;AAC3C,MAAI,SAAS,CAAC,IAAK,QAAO,gCAAG,sBAAY,MAAK;AAC9C,SAAO,oBAAC,SAAI,KAAK,KAAK,KAAW,GAAG,UAAU;AAChD;","names":[]}
|
|
@@ -28,10 +28,7 @@ var import_TinkerableContext = require("../TinkerableContext");
|
|
|
28
28
|
var import_routeMatch = require("../routeMatch");
|
|
29
29
|
var import_routing = require("../routing");
|
|
30
30
|
const Route = (_props) => null;
|
|
31
|
-
const Routes = ({
|
|
32
|
-
children,
|
|
33
|
-
fallback = null
|
|
34
|
-
}) => {
|
|
31
|
+
const Routes = ({ children, fallback = null }) => {
|
|
35
32
|
const context = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
36
33
|
const { sandboxPath } = context.navigationState;
|
|
37
34
|
for (const child of import_react.Children.toArray(children)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Routes.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Children, isValidElement, use } from 'react';\n\nimport { TinkerableContext } from '../TinkerableContext';\nimport type { RouteComponent, RoutingRule } from '../RoutingSpec';\nimport { matchRoute } from '../routeMatch';\nimport { renderRoute } from '../routing';\n\n/** Props for a {@link Route} declared inside {@link Routes}. */\nexport type RouteProps = {\n /** A path template (`/posts/:slug`, `/files/*`, `/`) or a raw RegExp. */\n path: string | RegExp;\n name?: string;\n /** Element to render; reads params via `useRouteParams()`. */\n element?: ReactNode;\n /** Component to render; receives matched params as a prop. */\n component?: RouteComponent;\n};\n\n/**\n * Declares a route. Rendered as a child of `<Routes>`, where it is read for its\n * props — it renders nothing on its own. Mounting a `<Route>` registers it;\n * unmounting (or conditionally not rendering it) removes it, which is how routes\n * become dynamic without a mutable registry.\n */\nexport const Route = (_props: RouteProps): null => null;\n\n/**\n * Resolves the active `sandboxPath` against its mounted `<Route>` children,\n * first-match-wins in render order, and renders the winner (or `fallback`). The\n * match scopes a nested context so `useRouteParams()` / `useRoute()` inside the\n * rendered route see this match. Use instead of (or nested within) a `boot`-time\n * `routingSpec`.\n */\nexport const Routes = ({
|
|
1
|
+
{"version":3,"sources":["../../src/components/Routes.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Children, isValidElement, use } from 'react';\n\nimport { TinkerableContext } from '../TinkerableContext';\nimport type { RouteComponent, RoutingRule } from '../RoutingSpec';\nimport { matchRoute } from '../routeMatch';\nimport { renderRoute } from '../routing';\n\n/** Props for a {@link Route} declared inside {@link Routes}. */\nexport type RouteProps = {\n /** A path template (`/posts/:slug`, `/files/*`, `/`) or a raw RegExp. */\n path: string | RegExp;\n name?: string;\n /** Element to render; reads params via `useRouteParams()`. */\n element?: ReactNode;\n /** Component to render; receives matched params as a prop. */\n component?: RouteComponent;\n};\n\n/**\n * Declares a route. Rendered as a child of `<Routes>`, where it is read for its\n * props — it renders nothing on its own. Mounting a `<Route>` registers it;\n * unmounting (or conditionally not rendering it) removes it, which is how routes\n * become dynamic without a mutable registry.\n */\nexport const Route = (_props: RouteProps): null => null;\n\n/**\n * Resolves the active `sandboxPath` against its mounted `<Route>` children,\n * first-match-wins in render order, and renders the winner (or `fallback`). The\n * match scopes a nested context so `useRouteParams()` / `useRoute()` inside the\n * rendered route see this match. Use instead of (or nested within) a `boot`-time\n * `routingSpec`.\n */\nexport const Routes = ({ children, fallback = null }: { children?: ReactNode; fallback?: ReactNode }) => {\n const context = use(TinkerableContext);\n const { sandboxPath } = context.navigationState;\n\n for (const child of Children.toArray(children)) {\n if (!isValidElement(child) || child.type !== Route) {\n continue;\n }\n const { path, element, component, name } = child.props as RouteProps;\n const params = matchRoute(path, sandboxPath);\n if (params) {\n const routingRule: RoutingRule = { name, pattern: path, element, component };\n const scoped = {\n ...context,\n navigationState: { ...context.navigationState, routingRule, pathParameters: params },\n };\n return <TinkerableContext value={scoped}>{renderRoute(routingRule, params)}</TinkerableContext>;\n }\n }\n\n return <>{fallback}</>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkDa;AAjDb,mBAA8C;AAE9C,+BAAkC;AAElC,wBAA2B;AAC3B,qBAA4B;AAmBrB,MAAM,QAAQ,CAAC,WAA6B;AAS5C,MAAM,SAAS,CAAC,EAAE,UAAU,WAAW,KAAK,MAAsD;AACvG,QAAM,cAAU,kBAAI,0CAAiB;AACrC,QAAM,EAAE,YAAY,IAAI,QAAQ;AAEhC,aAAW,SAAS,sBAAS,QAAQ,QAAQ,GAAG;AAC9C,QAAI,KAAC,6BAAe,KAAK,KAAK,MAAM,SAAS,OAAO;AAClD;AAAA,IACF;AACA,UAAM,EAAE,MAAM,SAAS,WAAW,KAAK,IAAI,MAAM;AACjD,UAAM,aAAS,8BAAW,MAAM,WAAW;AAC3C,QAAI,QAAQ;AACV,YAAM,cAA2B,EAAE,MAAM,SAAS,MAAM,SAAS,UAAU;AAC3E,YAAM,SAAS;AAAA,QACb,GAAG;AAAA,QACH,iBAAiB,EAAE,GAAG,QAAQ,iBAAiB,aAAa,gBAAgB,OAAO;AAAA,MACrF;AACA,aAAO,4CAAC,8CAAkB,OAAO,QAAS,0CAAY,aAAa,MAAM,GAAE;AAAA,IAC7E;AAAA,EACF;AAEA,SAAO,2EAAG,oBAAS;AACrB;","names":[]}
|
|
@@ -26,7 +26,7 @@ declare const Route: (_props: RouteProps) => null;
|
|
|
26
26
|
* rendered route see this match. Use instead of (or nested within) a `boot`-time
|
|
27
27
|
* `routingSpec`.
|
|
28
28
|
*/
|
|
29
|
-
declare const Routes: ({ children, fallback
|
|
29
|
+
declare const Routes: ({ children, fallback }: {
|
|
30
30
|
children?: ReactNode;
|
|
31
31
|
fallback?: ReactNode;
|
|
32
32
|
}) => react.JSX.Element;
|
|
@@ -26,7 +26,7 @@ declare const Route: (_props: RouteProps) => null;
|
|
|
26
26
|
* rendered route see this match. Use instead of (or nested within) a `boot`-time
|
|
27
27
|
* `routingSpec`.
|
|
28
28
|
*/
|
|
29
|
-
declare const Routes: ({ children, fallback
|
|
29
|
+
declare const Routes: ({ children, fallback }: {
|
|
30
30
|
children?: ReactNode;
|
|
31
31
|
fallback?: ReactNode;
|
|
32
32
|
}) => react.JSX.Element;
|
|
@@ -5,10 +5,7 @@ import { TinkerableContext } from "../TinkerableContext";
|
|
|
5
5
|
import { matchRoute } from "../routeMatch";
|
|
6
6
|
import { renderRoute } from "../routing";
|
|
7
7
|
const Route = (_props) => null;
|
|
8
|
-
const Routes = ({
|
|
9
|
-
children,
|
|
10
|
-
fallback = null
|
|
11
|
-
}) => {
|
|
8
|
+
const Routes = ({ children, fallback = null }) => {
|
|
12
9
|
const context = use(TinkerableContext);
|
|
13
10
|
const { sandboxPath } = context.navigationState;
|
|
14
11
|
for (const child of Children.toArray(children)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Routes.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Children, isValidElement, use } from 'react';\n\nimport { TinkerableContext } from '../TinkerableContext';\nimport type { RouteComponent, RoutingRule } from '../RoutingSpec';\nimport { matchRoute } from '../routeMatch';\nimport { renderRoute } from '../routing';\n\n/** Props for a {@link Route} declared inside {@link Routes}. */\nexport type RouteProps = {\n /** A path template (`/posts/:slug`, `/files/*`, `/`) or a raw RegExp. */\n path: string | RegExp;\n name?: string;\n /** Element to render; reads params via `useRouteParams()`. */\n element?: ReactNode;\n /** Component to render; receives matched params as a prop. */\n component?: RouteComponent;\n};\n\n/**\n * Declares a route. Rendered as a child of `<Routes>`, where it is read for its\n * props — it renders nothing on its own. Mounting a `<Route>` registers it;\n * unmounting (or conditionally not rendering it) removes it, which is how routes\n * become dynamic without a mutable registry.\n */\nexport const Route = (_props: RouteProps): null => null;\n\n/**\n * Resolves the active `sandboxPath` against its mounted `<Route>` children,\n * first-match-wins in render order, and renders the winner (or `fallback`). The\n * match scopes a nested context so `useRouteParams()` / `useRoute()` inside the\n * rendered route see this match. Use instead of (or nested within) a `boot`-time\n * `routingSpec`.\n */\nexport const Routes = ({
|
|
1
|
+
{"version":3,"sources":["../../src/components/Routes.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Children, isValidElement, use } from 'react';\n\nimport { TinkerableContext } from '../TinkerableContext';\nimport type { RouteComponent, RoutingRule } from '../RoutingSpec';\nimport { matchRoute } from '../routeMatch';\nimport { renderRoute } from '../routing';\n\n/** Props for a {@link Route} declared inside {@link Routes}. */\nexport type RouteProps = {\n /** A path template (`/posts/:slug`, `/files/*`, `/`) or a raw RegExp. */\n path: string | RegExp;\n name?: string;\n /** Element to render; reads params via `useRouteParams()`. */\n element?: ReactNode;\n /** Component to render; receives matched params as a prop. */\n component?: RouteComponent;\n};\n\n/**\n * Declares a route. Rendered as a child of `<Routes>`, where it is read for its\n * props — it renders nothing on its own. Mounting a `<Route>` registers it;\n * unmounting (or conditionally not rendering it) removes it, which is how routes\n * become dynamic without a mutable registry.\n */\nexport const Route = (_props: RouteProps): null => null;\n\n/**\n * Resolves the active `sandboxPath` against its mounted `<Route>` children,\n * first-match-wins in render order, and renders the winner (or `fallback`). The\n * match scopes a nested context so `useRouteParams()` / `useRoute()` inside the\n * rendered route see this match. Use instead of (or nested within) a `boot`-time\n * `routingSpec`.\n */\nexport const Routes = ({ children, fallback = null }: { children?: ReactNode; fallback?: ReactNode }) => {\n const context = use(TinkerableContext);\n const { sandboxPath } = context.navigationState;\n\n for (const child of Children.toArray(children)) {\n if (!isValidElement(child) || child.type !== Route) {\n continue;\n }\n const { path, element, component, name } = child.props as RouteProps;\n const params = matchRoute(path, sandboxPath);\n if (params) {\n const routingRule: RoutingRule = { name, pattern: path, element, component };\n const scoped = {\n ...context,\n navigationState: { ...context.navigationState, routingRule, pathParameters: params },\n };\n return <TinkerableContext value={scoped}>{renderRoute(routingRule, params)}</TinkerableContext>;\n }\n }\n\n return <>{fallback}</>;\n};\n"],"mappings":";AAkDa,SAIJ,UAJI;AAjDb,SAAS,UAAU,gBAAgB,WAAW;AAE9C,SAAS,yBAAyB;AAElC,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAmBrB,MAAM,QAAQ,CAAC,WAA6B;AAS5C,MAAM,SAAS,CAAC,EAAE,UAAU,WAAW,KAAK,MAAsD;AACvG,QAAM,UAAU,IAAI,iBAAiB;AACrC,QAAM,EAAE,YAAY,IAAI,QAAQ;AAEhC,aAAW,SAAS,SAAS,QAAQ,QAAQ,GAAG;AAC9C,QAAI,CAAC,eAAe,KAAK,KAAK,MAAM,SAAS,OAAO;AAClD;AAAA,IACF;AACA,UAAM,EAAE,MAAM,SAAS,WAAW,KAAK,IAAI,MAAM;AACjD,UAAM,SAAS,WAAW,MAAM,WAAW;AAC3C,QAAI,QAAQ;AACV,YAAM,cAA2B,EAAE,MAAM,SAAS,MAAM,SAAS,UAAU;AAC3E,YAAM,SAAS;AAAA,QACb,GAAG;AAAA,QACH,iBAAiB,EAAE,GAAG,QAAQ,iBAAiB,aAAa,gBAAgB,OAAO;AAAA,MACrF;AACA,aAAO,oBAAC,qBAAkB,OAAO,QAAS,sBAAY,aAAa,MAAM,GAAE;AAAA,IAC7E;AAAA,EACF;AAEA,SAAO,gCAAG,oBAAS;AACrB;","names":[]}
|
|
@@ -62,10 +62,7 @@ function InterpretedBody({
|
|
|
62
62
|
components,
|
|
63
63
|
readSource
|
|
64
64
|
}) {
|
|
65
|
-
const sources = (0, import_react.useMemo)(
|
|
66
|
-
() => readSource ? (0, import_sourceCache.createSourceCache)(readSource) : defaultSources,
|
|
67
|
-
[readSource]
|
|
68
|
-
);
|
|
65
|
+
const sources = (0, import_react.useMemo)(() => readSource ? (0, import_sourceCache.createSourceCache)(readSource) : defaultSources, [readSource]);
|
|
69
66
|
const raw = (0, import_react.use)(sources.read(filename));
|
|
70
67
|
const provided = (0, import_MDXProvider.useMDXComponents)(components);
|
|
71
68
|
const body = stripFrontmatter(raw);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/SafeInclude.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\n\nimport { openAppFs, openFs } from '../fs';\nimport { getAppMountPath } from '../mounts';\nimport { useMDXComponents } from '../MDXProvider';\nimport { SafeContent } from '../safeContent/SafeContent';\nimport { createSourceCache, type SourceReader } from '../sourceCache';\nimport { RenderExportedComponentContext } from './includeContexts';\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// `<Include mode=\"interpreted\">` — the same tag, the NON-EXECUTABLE renderer. (R3-263)\n//\n// WHY THIS EXISTS. `<Include>` resolves a file through the module cache and *evaluates* it,\n// which is right for an app's own source and wrong for content. An interpreter app\n// (TRUST_MODES_SPEC §5) had no way to say \"include this file, but as data\": it could render\n// its own bodies through `<SafeContent>` while any file it pulled in with `<Include>` still\n// executed. Grove hit exactly that — a wiki declaring `render: safe` still ran author\n// JavaScript out of its `_layout.mdx`, because the layout chain went through `<Include>`.\n//\n// It is also what makes such a file renderable AT ALL when it does not live in the app: the\n// compiled path evaluates an app-source module, which a file resident in a content mount is\n// not. That is the `AGENT_AUTHORING §10` MDX-from-mount gate, which `TRUST_MODES §5.1` says\n// MUST terminate in the safe renderer and never in compiled MDX.\n//\n// The component map comes from `useMDXComponents` — the SAME provider map `boot({\n// mdxComponents })` establishes for the compiled path — so a document resolves the identical\n// vocabulary under either renderer and an app wires nothing extra to get it.\n\n/** Strip a leading YAML frontmatter block (and an optional BOM). The compiled path never\n * renders frontmatter — the MDX loader consumes it — so the safe path must not either, or\n * the same file gains a wall of raw YAML when the renderer changes. */\nexport function stripFrontmatter(source: string): string {\n return source.replace(/^\\uFEFF?---\\r?\\n[\\s\\S]*?\\r?\\n---[ \\t]*\\r?\\n?/, '');\n}\n\n/** `/app/content/x.mdx` → `content/x.mdx`. {@link openAppFs} is anchored at the app mount,\n * so an absolute module path has to be made mount-relative before it is read. A path that\n * is already relative is passed through untouched. */\nexport function appMountRelative(filename: string): string {\n const root = getAppMountPath().replace(/\\/+$/, '');\n const abs = filename.startsWith(root + '/') ? filename.slice(root.length) : filename;\n return abs.replace(/^\\/+/, '');\n}\n\n/** Is this an absolute path that lies OUTSIDE this app's own repo mount — i.e. a file\n * resident in a content mount rather than in the app? */\nexport function isForeignMountPath(filename: string): boolean {\n if (!filename.startsWith('/')) return false;\n const root = getAppMountPath().replace(/\\/+$/, '');\n return !filename.startsWith(`${root}/`) && filename !== root;\n}\n\n/**\n * Read an interpreted include's bytes.\n *\n * The reason this is not just `openAppFs()`: **the whole point of the interpreted include\n * is files the app did not author**, and the two consumers named in this file's header —\n * a dispatched wiki's `_layout.mdx`, a whiteboard object body — live in a MOUNT. An\n * app-anchored read cannot reach them, and it does not fail loudly either: the absolute\n * path is stripped to a relative one and looked up *inside the app*, so\n * `/task/<slot>/dir/_layout.mdx` becomes a miss at `task/<slot>/dir/_layout.mdx` in the\n * app's own tree. A silent wrong-file lookup, in the component whose job is rendering\n * other people's content.\n *\n * So: a path under the app mount reads exactly as before, and an absolute path outside it\n * — which could only ever have failed — reads from the unified sandbox namespace, where\n * every mount is addressable. This widens what resolves and changes nothing that resolved.\n */\nfunction readInterpretedSource(filename: string): Promise<string> {\n if (isForeignMountPath(filename)) {\n // Anchored at the namespace root: mounts are addressable by their absolute paths, and\n // this component has no way to know (and no need to know) which mount a path belongs\n // to. It confers nothing — the ZenFS port is already chroot/`ro`-enforced host-side.\n return openFs({ path: '/', type: 'mount' } as never).readFile(\n filename.replace(/^\\/+/, ''),\n 'utf8',\n ) as Promise<string>;\n }\n return openAppFs().readFile(appMountRelative(filename), 'utf8') as Promise<string>;\n}\n\n// One cache for every interpreted include in the app: a layout file is included on every\n// page, so a per-component cache would re-read it per navigation. See `../sourceCache` for\n// why a REJECTED read must not be memoised.\nconst defaultSources = createSourceCache(readInterpretedSource);\n\nfunction InterpretedBody({\n filename,\n components,\n readSource,\n}: {\n filename: string;\n components?: Record<string, unknown>;\n readSource?: SourceReader;\n}) {\n // A caller-supplied reader gets its own cache; the shared one is used otherwise. Memoised\n // on the reader identity so `use()` still sees a stable promise across renders.\n const sources = useMemo(
|
|
1
|
+
{"version":3,"sources":["../../src/components/SafeInclude.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\n\nimport { openAppFs, openFs } from '../fs';\nimport { getAppMountPath } from '../mounts';\nimport { useMDXComponents } from '../MDXProvider';\nimport { SafeContent } from '../safeContent/SafeContent';\nimport { createSourceCache, type SourceReader } from '../sourceCache';\nimport { RenderExportedComponentContext } from './includeContexts';\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// `<Include mode=\"interpreted\">` — the same tag, the NON-EXECUTABLE renderer. (R3-263)\n//\n// WHY THIS EXISTS. `<Include>` resolves a file through the module cache and *evaluates* it,\n// which is right for an app's own source and wrong for content. An interpreter app\n// (TRUST_MODES_SPEC §5) had no way to say \"include this file, but as data\": it could render\n// its own bodies through `<SafeContent>` while any file it pulled in with `<Include>` still\n// executed. Grove hit exactly that — a wiki declaring `render: safe` still ran author\n// JavaScript out of its `_layout.mdx`, because the layout chain went through `<Include>`.\n//\n// It is also what makes such a file renderable AT ALL when it does not live in the app: the\n// compiled path evaluates an app-source module, which a file resident in a content mount is\n// not. That is the `AGENT_AUTHORING §10` MDX-from-mount gate, which `TRUST_MODES §5.1` says\n// MUST terminate in the safe renderer and never in compiled MDX.\n//\n// The component map comes from `useMDXComponents` — the SAME provider map `boot({\n// mdxComponents })` establishes for the compiled path — so a document resolves the identical\n// vocabulary under either renderer and an app wires nothing extra to get it.\n\n/** Strip a leading YAML frontmatter block (and an optional BOM). The compiled path never\n * renders frontmatter — the MDX loader consumes it — so the safe path must not either, or\n * the same file gains a wall of raw YAML when the renderer changes. */\nexport function stripFrontmatter(source: string): string {\n return source.replace(/^\\uFEFF?---\\r?\\n[\\s\\S]*?\\r?\\n---[ \\t]*\\r?\\n?/, '');\n}\n\n/** `/app/content/x.mdx` → `content/x.mdx`. {@link openAppFs} is anchored at the app mount,\n * so an absolute module path has to be made mount-relative before it is read. A path that\n * is already relative is passed through untouched. */\nexport function appMountRelative(filename: string): string {\n const root = getAppMountPath().replace(/\\/+$/, '');\n const abs = filename.startsWith(root + '/') ? filename.slice(root.length) : filename;\n return abs.replace(/^\\/+/, '');\n}\n\n/** Is this an absolute path that lies OUTSIDE this app's own repo mount — i.e. a file\n * resident in a content mount rather than in the app? */\nexport function isForeignMountPath(filename: string): boolean {\n if (!filename.startsWith('/')) return false;\n const root = getAppMountPath().replace(/\\/+$/, '');\n return !filename.startsWith(`${root}/`) && filename !== root;\n}\n\n/**\n * Read an interpreted include's bytes.\n *\n * The reason this is not just `openAppFs()`: **the whole point of the interpreted include\n * is files the app did not author**, and the two consumers named in this file's header —\n * a dispatched wiki's `_layout.mdx`, a whiteboard object body — live in a MOUNT. An\n * app-anchored read cannot reach them, and it does not fail loudly either: the absolute\n * path is stripped to a relative one and looked up *inside the app*, so\n * `/task/<slot>/dir/_layout.mdx` becomes a miss at `task/<slot>/dir/_layout.mdx` in the\n * app's own tree. A silent wrong-file lookup, in the component whose job is rendering\n * other people's content.\n *\n * So: a path under the app mount reads exactly as before, and an absolute path outside it\n * — which could only ever have failed — reads from the unified sandbox namespace, where\n * every mount is addressable. This widens what resolves and changes nothing that resolved.\n */\nfunction readInterpretedSource(filename: string): Promise<string> {\n if (isForeignMountPath(filename)) {\n // Anchored at the namespace root: mounts are addressable by their absolute paths, and\n // this component has no way to know (and no need to know) which mount a path belongs\n // to. It confers nothing — the ZenFS port is already chroot/`ro`-enforced host-side.\n return openFs({ path: '/', type: 'mount' } as never).readFile(\n filename.replace(/^\\/+/, ''),\n 'utf8',\n ) as Promise<string>;\n }\n return openAppFs().readFile(appMountRelative(filename), 'utf8') as Promise<string>;\n}\n\n// One cache for every interpreted include in the app: a layout file is included on every\n// page, so a per-component cache would re-read it per navigation. See `../sourceCache` for\n// why a REJECTED read must not be memoised.\nconst defaultSources = createSourceCache(readInterpretedSource);\n\nfunction InterpretedBody({\n filename,\n components,\n readSource,\n}: {\n filename: string;\n components?: Record<string, unknown>;\n readSource?: SourceReader;\n}) {\n // A caller-supplied reader gets its own cache; the shared one is used otherwise. Memoised\n // on the reader identity so `use()` still sees a stable promise across renders.\n const sources = useMemo(() => (readSource ? createSourceCache(readSource) : defaultSources), [readSource]);\n const raw = use(sources.read(filename));\n const provided = useMDXComponents(components) as Record<string, unknown>;\n const body = stripFrontmatter(raw);\n return (\n // Publishing the included file's path is what lets the WikiLink resolver know which file\n // a relative `[[target]]` (and its `#sec-…` fragment) is relative TO — the same context\n // the compiled path publishes from the module's evaluation.\n <RenderExportedComponentContext\n value={{ evaluationContext: { evaluation: { module: { filepath: filename } } } } as never}\n >\n <SafeContent source={body} components={provided as never} />\n </RenderExportedComponentContext>\n );\n}\n\n/** Render another file's Markdown/MDX **as data** — no author JavaScript executes. Reached\n * via `<Include mode=\"interpreted\">` or `<IncludeModeContext value=\"interpreted\">`. */\nexport const SafeInclude = ({\n filename,\n components,\n readSource,\n LoadingComponent = defaultLoadingComponent,\n ErrorComponent = defaultErrorComponent,\n}: {\n filename: string;\n /** Extra/override components, merged OVER the surrounding MDXProvider map. */\n components?: Record<string, unknown>;\n /** Read the raw bytes yourself — e.g. from a content mount rather than the app repo. */\n readSource?: SourceReader;\n LoadingComponent?: typeof defaultLoadingComponent;\n ErrorComponent?: typeof defaultErrorComponent;\n}) => (\n // Same boundary shape as the compiled path: a file that fails to read renders the error\n // component rather than taking down the tree that included it.\n <ErrorBoundary fallbackRender={ErrorComponent}>\n <Suspense fallback={<LoadingComponent />}>\n <InterpretedBody filename={filename} components={components} readSource={readSource} />\n </Suspense>\n </ErrorBoundary>\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6GM;AA7GN,mBAAuC;AACvC,kCAA8B;AAE9B,gBAAkC;AAClC,oBAAgC;AAChC,yBAAiC;AACjC,yBAA4B;AAC5B,yBAAqD;AACrD,6BAA+C;AAC/C,sBAA+D;AAuBxD,SAAS,iBAAiB,QAAwB;AACvD,SAAO,OAAO,QAAQ,gDAAgD,EAAE;AAC1E;AAKO,SAAS,iBAAiB,UAA0B;AACzD,QAAM,WAAO,+BAAgB,EAAE,QAAQ,QAAQ,EAAE;AACjD,QAAM,MAAM,SAAS,WAAW,OAAO,GAAG,IAAI,SAAS,MAAM,KAAK,MAAM,IAAI;AAC5E,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAIO,SAAS,mBAAmB,UAA2B;AAC5D,MAAI,CAAC,SAAS,WAAW,GAAG,EAAG,QAAO;AACtC,QAAM,WAAO,+BAAgB,EAAE,QAAQ,QAAQ,EAAE;AACjD,SAAO,CAAC,SAAS,WAAW,GAAG,IAAI,GAAG,KAAK,aAAa;AAC1D;AAkBA,SAAS,sBAAsB,UAAmC;AAChE,MAAI,mBAAmB,QAAQ,GAAG;AAIhC,eAAO,kBAAO,EAAE,MAAM,KAAK,MAAM,QAAQ,CAAU,EAAE;AAAA,MACnD,SAAS,QAAQ,QAAQ,EAAE;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACA,aAAO,qBAAU,EAAE,SAAS,iBAAiB,QAAQ,GAAG,MAAM;AAChE;AAKA,MAAM,qBAAiB,sCAAkB,qBAAqB;AAE9D,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AAGD,QAAM,cAAU,sBAAQ,MAAO,iBAAa,sCAAkB,UAAU,IAAI,gBAAiB,CAAC,UAAU,CAAC;AACzG,QAAM,UAAM,kBAAI,QAAQ,KAAK,QAAQ,CAAC;AACtC,QAAM,eAAW,qCAAiB,UAAU;AAC5C,QAAM,OAAO,iBAAiB,GAAG;AACjC;AAAA;AAAA;AAAA;AAAA,IAIE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,SAAS,EAAE,EAAE,EAAE;AAAA,QAE/E,sDAAC,kCAAY,QAAQ,MAAM,YAAY,UAAmB;AAAA;AAAA,IAC5D;AAAA;AAEJ;AAIO,MAAM,cAAc,CAAC;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAAA;AAAA;AAAA,EAWE,4CAAC,6CAAc,gBAAgB,gBAC7B,sDAAC,yBAAS,UAAU,4CAAC,oBAAiB,GACpC,sDAAC,mBAAgB,UAAoB,YAAwB,YAAwB,GACvF,GACF;AAAA;","names":[]}
|
|
@@ -37,10 +37,7 @@ function InterpretedBody({
|
|
|
37
37
|
components,
|
|
38
38
|
readSource
|
|
39
39
|
}) {
|
|
40
|
-
const sources = useMemo(
|
|
41
|
-
() => readSource ? createSourceCache(readSource) : defaultSources,
|
|
42
|
-
[readSource]
|
|
43
|
-
);
|
|
40
|
+
const sources = useMemo(() => readSource ? createSourceCache(readSource) : defaultSources, [readSource]);
|
|
44
41
|
const raw = use(sources.read(filename));
|
|
45
42
|
const provided = useMDXComponents(components);
|
|
46
43
|
const body = stripFrontmatter(raw);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/SafeInclude.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\n\nimport { openAppFs, openFs } from '../fs';\nimport { getAppMountPath } from '../mounts';\nimport { useMDXComponents } from '../MDXProvider';\nimport { SafeContent } from '../safeContent/SafeContent';\nimport { createSourceCache, type SourceReader } from '../sourceCache';\nimport { RenderExportedComponentContext } from './includeContexts';\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// `<Include mode=\"interpreted\">` — the same tag, the NON-EXECUTABLE renderer. (R3-263)\n//\n// WHY THIS EXISTS. `<Include>` resolves a file through the module cache and *evaluates* it,\n// which is right for an app's own source and wrong for content. An interpreter app\n// (TRUST_MODES_SPEC §5) had no way to say \"include this file, but as data\": it could render\n// its own bodies through `<SafeContent>` while any file it pulled in with `<Include>` still\n// executed. Grove hit exactly that — a wiki declaring `render: safe` still ran author\n// JavaScript out of its `_layout.mdx`, because the layout chain went through `<Include>`.\n//\n// It is also what makes such a file renderable AT ALL when it does not live in the app: the\n// compiled path evaluates an app-source module, which a file resident in a content mount is\n// not. That is the `AGENT_AUTHORING §10` MDX-from-mount gate, which `TRUST_MODES §5.1` says\n// MUST terminate in the safe renderer and never in compiled MDX.\n//\n// The component map comes from `useMDXComponents` — the SAME provider map `boot({\n// mdxComponents })` establishes for the compiled path — so a document resolves the identical\n// vocabulary under either renderer and an app wires nothing extra to get it.\n\n/** Strip a leading YAML frontmatter block (and an optional BOM). The compiled path never\n * renders frontmatter — the MDX loader consumes it — so the safe path must not either, or\n * the same file gains a wall of raw YAML when the renderer changes. */\nexport function stripFrontmatter(source: string): string {\n return source.replace(/^\\uFEFF?---\\r?\\n[\\s\\S]*?\\r?\\n---[ \\t]*\\r?\\n?/, '');\n}\n\n/** `/app/content/x.mdx` → `content/x.mdx`. {@link openAppFs} is anchored at the app mount,\n * so an absolute module path has to be made mount-relative before it is read. A path that\n * is already relative is passed through untouched. */\nexport function appMountRelative(filename: string): string {\n const root = getAppMountPath().replace(/\\/+$/, '');\n const abs = filename.startsWith(root + '/') ? filename.slice(root.length) : filename;\n return abs.replace(/^\\/+/, '');\n}\n\n/** Is this an absolute path that lies OUTSIDE this app's own repo mount — i.e. a file\n * resident in a content mount rather than in the app? */\nexport function isForeignMountPath(filename: string): boolean {\n if (!filename.startsWith('/')) return false;\n const root = getAppMountPath().replace(/\\/+$/, '');\n return !filename.startsWith(`${root}/`) && filename !== root;\n}\n\n/**\n * Read an interpreted include's bytes.\n *\n * The reason this is not just `openAppFs()`: **the whole point of the interpreted include\n * is files the app did not author**, and the two consumers named in this file's header —\n * a dispatched wiki's `_layout.mdx`, a whiteboard object body — live in a MOUNT. An\n * app-anchored read cannot reach them, and it does not fail loudly either: the absolute\n * path is stripped to a relative one and looked up *inside the app*, so\n * `/task/<slot>/dir/_layout.mdx` becomes a miss at `task/<slot>/dir/_layout.mdx` in the\n * app's own tree. A silent wrong-file lookup, in the component whose job is rendering\n * other people's content.\n *\n * So: a path under the app mount reads exactly as before, and an absolute path outside it\n * — which could only ever have failed — reads from the unified sandbox namespace, where\n * every mount is addressable. This widens what resolves and changes nothing that resolved.\n */\nfunction readInterpretedSource(filename: string): Promise<string> {\n if (isForeignMountPath(filename)) {\n // Anchored at the namespace root: mounts are addressable by their absolute paths, and\n // this component has no way to know (and no need to know) which mount a path belongs\n // to. It confers nothing — the ZenFS port is already chroot/`ro`-enforced host-side.\n return openFs({ path: '/', type: 'mount' } as never).readFile(\n filename.replace(/^\\/+/, ''),\n 'utf8',\n ) as Promise<string>;\n }\n return openAppFs().readFile(appMountRelative(filename), 'utf8') as Promise<string>;\n}\n\n// One cache for every interpreted include in the app: a layout file is included on every\n// page, so a per-component cache would re-read it per navigation. See `../sourceCache` for\n// why a REJECTED read must not be memoised.\nconst defaultSources = createSourceCache(readInterpretedSource);\n\nfunction InterpretedBody({\n filename,\n components,\n readSource,\n}: {\n filename: string;\n components?: Record<string, unknown>;\n readSource?: SourceReader;\n}) {\n // A caller-supplied reader gets its own cache; the shared one is used otherwise. Memoised\n // on the reader identity so `use()` still sees a stable promise across renders.\n const sources = useMemo(
|
|
1
|
+
{"version":3,"sources":["../../src/components/SafeInclude.tsx"],"sourcesContent":["import { Suspense, use, useMemo } from 'react';\nimport { ErrorBoundary } from 'react-error-boundary';\n\nimport { openAppFs, openFs } from '../fs';\nimport { getAppMountPath } from '../mounts';\nimport { useMDXComponents } from '../MDXProvider';\nimport { SafeContent } from '../safeContent/SafeContent';\nimport { createSourceCache, type SourceReader } from '../sourceCache';\nimport { RenderExportedComponentContext } from './includeContexts';\nimport { defaultErrorComponent, defaultLoadingComponent } from './defaults';\n\n// `<Include mode=\"interpreted\">` — the same tag, the NON-EXECUTABLE renderer. (R3-263)\n//\n// WHY THIS EXISTS. `<Include>` resolves a file through the module cache and *evaluates* it,\n// which is right for an app's own source and wrong for content. An interpreter app\n// (TRUST_MODES_SPEC §5) had no way to say \"include this file, but as data\": it could render\n// its own bodies through `<SafeContent>` while any file it pulled in with `<Include>` still\n// executed. Grove hit exactly that — a wiki declaring `render: safe` still ran author\n// JavaScript out of its `_layout.mdx`, because the layout chain went through `<Include>`.\n//\n// It is also what makes such a file renderable AT ALL when it does not live in the app: the\n// compiled path evaluates an app-source module, which a file resident in a content mount is\n// not. That is the `AGENT_AUTHORING §10` MDX-from-mount gate, which `TRUST_MODES §5.1` says\n// MUST terminate in the safe renderer and never in compiled MDX.\n//\n// The component map comes from `useMDXComponents` — the SAME provider map `boot({\n// mdxComponents })` establishes for the compiled path — so a document resolves the identical\n// vocabulary under either renderer and an app wires nothing extra to get it.\n\n/** Strip a leading YAML frontmatter block (and an optional BOM). The compiled path never\n * renders frontmatter — the MDX loader consumes it — so the safe path must not either, or\n * the same file gains a wall of raw YAML when the renderer changes. */\nexport function stripFrontmatter(source: string): string {\n return source.replace(/^\\uFEFF?---\\r?\\n[\\s\\S]*?\\r?\\n---[ \\t]*\\r?\\n?/, '');\n}\n\n/** `/app/content/x.mdx` → `content/x.mdx`. {@link openAppFs} is anchored at the app mount,\n * so an absolute module path has to be made mount-relative before it is read. A path that\n * is already relative is passed through untouched. */\nexport function appMountRelative(filename: string): string {\n const root = getAppMountPath().replace(/\\/+$/, '');\n const abs = filename.startsWith(root + '/') ? filename.slice(root.length) : filename;\n return abs.replace(/^\\/+/, '');\n}\n\n/** Is this an absolute path that lies OUTSIDE this app's own repo mount — i.e. a file\n * resident in a content mount rather than in the app? */\nexport function isForeignMountPath(filename: string): boolean {\n if (!filename.startsWith('/')) return false;\n const root = getAppMountPath().replace(/\\/+$/, '');\n return !filename.startsWith(`${root}/`) && filename !== root;\n}\n\n/**\n * Read an interpreted include's bytes.\n *\n * The reason this is not just `openAppFs()`: **the whole point of the interpreted include\n * is files the app did not author**, and the two consumers named in this file's header —\n * a dispatched wiki's `_layout.mdx`, a whiteboard object body — live in a MOUNT. An\n * app-anchored read cannot reach them, and it does not fail loudly either: the absolute\n * path is stripped to a relative one and looked up *inside the app*, so\n * `/task/<slot>/dir/_layout.mdx` becomes a miss at `task/<slot>/dir/_layout.mdx` in the\n * app's own tree. A silent wrong-file lookup, in the component whose job is rendering\n * other people's content.\n *\n * So: a path under the app mount reads exactly as before, and an absolute path outside it\n * — which could only ever have failed — reads from the unified sandbox namespace, where\n * every mount is addressable. This widens what resolves and changes nothing that resolved.\n */\nfunction readInterpretedSource(filename: string): Promise<string> {\n if (isForeignMountPath(filename)) {\n // Anchored at the namespace root: mounts are addressable by their absolute paths, and\n // this component has no way to know (and no need to know) which mount a path belongs\n // to. It confers nothing — the ZenFS port is already chroot/`ro`-enforced host-side.\n return openFs({ path: '/', type: 'mount' } as never).readFile(\n filename.replace(/^\\/+/, ''),\n 'utf8',\n ) as Promise<string>;\n }\n return openAppFs().readFile(appMountRelative(filename), 'utf8') as Promise<string>;\n}\n\n// One cache for every interpreted include in the app: a layout file is included on every\n// page, so a per-component cache would re-read it per navigation. See `../sourceCache` for\n// why a REJECTED read must not be memoised.\nconst defaultSources = createSourceCache(readInterpretedSource);\n\nfunction InterpretedBody({\n filename,\n components,\n readSource,\n}: {\n filename: string;\n components?: Record<string, unknown>;\n readSource?: SourceReader;\n}) {\n // A caller-supplied reader gets its own cache; the shared one is used otherwise. Memoised\n // on the reader identity so `use()` still sees a stable promise across renders.\n const sources = useMemo(() => (readSource ? createSourceCache(readSource) : defaultSources), [readSource]);\n const raw = use(sources.read(filename));\n const provided = useMDXComponents(components) as Record<string, unknown>;\n const body = stripFrontmatter(raw);\n return (\n // Publishing the included file's path is what lets the WikiLink resolver know which file\n // a relative `[[target]]` (and its `#sec-…` fragment) is relative TO — the same context\n // the compiled path publishes from the module's evaluation.\n <RenderExportedComponentContext\n value={{ evaluationContext: { evaluation: { module: { filepath: filename } } } } as never}\n >\n <SafeContent source={body} components={provided as never} />\n </RenderExportedComponentContext>\n );\n}\n\n/** Render another file's Markdown/MDX **as data** — no author JavaScript executes. Reached\n * via `<Include mode=\"interpreted\">` or `<IncludeModeContext value=\"interpreted\">`. */\nexport const SafeInclude = ({\n filename,\n components,\n readSource,\n LoadingComponent = defaultLoadingComponent,\n ErrorComponent = defaultErrorComponent,\n}: {\n filename: string;\n /** Extra/override components, merged OVER the surrounding MDXProvider map. */\n components?: Record<string, unknown>;\n /** Read the raw bytes yourself — e.g. from a content mount rather than the app repo. */\n readSource?: SourceReader;\n LoadingComponent?: typeof defaultLoadingComponent;\n ErrorComponent?: typeof defaultErrorComponent;\n}) => (\n // Same boundary shape as the compiled path: a file that fails to read renders the error\n // component rather than taking down the tree that included it.\n <ErrorBoundary fallbackRender={ErrorComponent}>\n <Suspense fallback={<LoadingComponent />}>\n <InterpretedBody filename={filename} components={components} readSource={readSource} />\n </Suspense>\n </ErrorBoundary>\n);\n"],"mappings":";AA6GM;AA7GN,SAAS,UAAU,KAAK,eAAe;AACvC,SAAS,qBAAqB;AAE9B,SAAS,WAAW,cAAc;AAClC,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAC5B,SAAS,yBAA4C;AACrD,SAAS,sCAAsC;AAC/C,SAAS,uBAAuB,+BAA+B;AAuBxD,SAAS,iBAAiB,QAAwB;AACvD,SAAO,OAAO,QAAQ,gDAAgD,EAAE;AAC1E;AAKO,SAAS,iBAAiB,UAA0B;AACzD,QAAM,OAAO,gBAAgB,EAAE,QAAQ,QAAQ,EAAE;AACjD,QAAM,MAAM,SAAS,WAAW,OAAO,GAAG,IAAI,SAAS,MAAM,KAAK,MAAM,IAAI;AAC5E,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAIO,SAAS,mBAAmB,UAA2B;AAC5D,MAAI,CAAC,SAAS,WAAW,GAAG,EAAG,QAAO;AACtC,QAAM,OAAO,gBAAgB,EAAE,QAAQ,QAAQ,EAAE;AACjD,SAAO,CAAC,SAAS,WAAW,GAAG,IAAI,GAAG,KAAK,aAAa;AAC1D;AAkBA,SAAS,sBAAsB,UAAmC;AAChE,MAAI,mBAAmB,QAAQ,GAAG;AAIhC,WAAO,OAAO,EAAE,MAAM,KAAK,MAAM,QAAQ,CAAU,EAAE;AAAA,MACnD,SAAS,QAAQ,QAAQ,EAAE;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACA,SAAO,UAAU,EAAE,SAAS,iBAAiB,QAAQ,GAAG,MAAM;AAChE;AAKA,MAAM,iBAAiB,kBAAkB,qBAAqB;AAE9D,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AAGD,QAAM,UAAU,QAAQ,MAAO,aAAa,kBAAkB,UAAU,IAAI,gBAAiB,CAAC,UAAU,CAAC;AACzG,QAAM,MAAM,IAAI,QAAQ,KAAK,QAAQ,CAAC;AACtC,QAAM,WAAW,iBAAiB,UAAU;AAC5C,QAAM,OAAO,iBAAiB,GAAG;AACjC;AAAA;AAAA;AAAA;AAAA,IAIE;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,SAAS,EAAE,EAAE,EAAE;AAAA,QAE/E,8BAAC,eAAY,QAAQ,MAAM,YAAY,UAAmB;AAAA;AAAA,IAC5D;AAAA;AAEJ;AAIO,MAAM,cAAc,CAAC;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;AAAA;AAAA;AAAA,EAWE,oBAAC,iBAAc,gBAAgB,gBAC7B,8BAAC,YAAS,UAAU,oBAAC,oBAAiB,GACpC,8BAAC,mBAAgB,UAAoB,YAAwB,YAAwB,GACvF,GACF;AAAA;","names":[]}
|
|
@@ -76,16 +76,7 @@ const WikiLink = ({
|
|
|
76
76
|
}
|
|
77
77
|
const exists = !loaded || resolved in files;
|
|
78
78
|
if (!exists) {
|
|
79
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
80
|
-
"span",
|
|
81
|
-
{
|
|
82
|
-
className: "ir-wikilink ir-wikilink-broken",
|
|
83
|
-
"data-state": "broken",
|
|
84
|
-
title: `No file at ${resolved}`,
|
|
85
|
-
...rest,
|
|
86
|
-
children: text
|
|
87
|
-
}
|
|
88
|
-
);
|
|
79
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ir-wikilink ir-wikilink-broken", "data-state": "broken", title: `No file at ${resolved}`, ...rest, children: text });
|
|
89
80
|
}
|
|
90
81
|
}
|
|
91
82
|
const translated = pathPart.startsWith(import_linkSpace.FS_PREFIX) || corpusRoot !== null && pathPart.startsWith("/");
|
|
@@ -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';\nimport { splitHash } from '../urlUtils';\nimport { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from '../linkSpace';\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/**\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 { corpusRoot } = use(LinkSpaceContext);\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 // R3-273 link spaces: resolution is the SHARED resolver (`linkSpace.ts`) —\n // default space (corpus-rooted absolute targets when an enclosing provider\n // declares a corpusRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n // A malformed `$fs:` target renders BROKEN, never an anchor.\n const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });\n if (resolution.state === 'invalid') {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`Invalid ${FS_PREFIX} target: ${pathPart}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n const resolved = resolution.state === 'resolved' ? resolution.path : undefined;\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
|
|
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';\nimport { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from '../linkSpace';\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/**\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 { corpusRoot } = use(LinkSpaceContext);\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 // R3-273 link spaces: resolution is the SHARED resolver (`linkSpace.ts`) —\n // default space (corpus-rooted absolute targets when an enclosing provider\n // declares a corpusRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n // A malformed `$fs:` target renders BROKEN, never an anchor.\n const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });\n if (resolution.state === 'invalid') {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`Invalid ${FS_PREFIX} target: ${pathPart}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n const resolved = resolution.state === 'resolved' ? resolution.path : undefined;\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 className=\"ir-wikilink ir-wikilink-broken\" data-state=\"broken\" title={`No file at ${resolved}`} {...rest}>\n {text}\n </span>\n );\n }\n }\n // Resolved cross-file target: route through <Link>. A space-translated target\n // (`$fs:` prefix, or a corpus-rooted absolute) navigates to the RESOLVED path —\n // the raw text is not a routable path in those shapes; everything else carries\n // the raw target bit-for-bit so its `#fragment` rides through navigation to the\n // scroll-after-nav effect (§13.5). The fragment is re-attached either way.\n const translated = pathPart.startsWith(FS_PREFIX) || (corpusRoot !== null && pathPart.startsWith('/'));\n const href = translated && resolved !== undefined ? `${resolved}${frag ? `#${frag}` : ''}` : rawTarget;\n return (\n <Link href={href} className=\"ir-wikilink\" data-state=\"resolved\" {...rest}>\n {text}\n </Link>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA6EM;AA7EN,mBAA+B;AAC/B,kBAAqB;AACrB,qBAA+C;AAC/C,+BAAkC;AAClC,sBAA0B;AAC1B,uBAA+D;AAG/D,MAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAM,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,KAAK;AAC5C,SAAO,KAAK,QAAQ,YAAY,EAAE,KAAK;AACzC;AA2CO,MAAM,WAAW,CAAC;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAI+E;AAC7E,QAAM,EAAE,cAAc,QAAI,kBAAI,0CAAiB;AAC/C,QAAM,EAAE,WAAW,QAAI,kBAAI,iCAAgB;AAC3C,QAAM,oBAAgB,kBAAI,6CAA8B;AACxD,QAAM,cAAc,eAAe,mBAAmB,YAAY,QAAQ;AAE1E,QAAM,YAAY,UAAU;AAC5B,QAAM,CAAC,UAAU,IAAI,QAAI,2BAAU,SAAS;AAC5C,QAAM,OAAO,YAAY,UAAU,YAAY,gBAAgB,YAAY,QAAQ,SAAS,IAAI;AAIhG,MAAI,CAAC,WAAW;AACd,WACE,4CAAC,UAAK,WAAU,eAAe,GAAG,MAC/B,gBACH;AAAA,EAEJ;AAIA,MAAI,aAAa,MAAM,MAAM;AAC3B,WACE,4CAAC,oBAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,EAEJ;AAMA,QAAM,iBAAa,oCAAkB,UAAU,EAAE,aAAa,WAAW,CAAC;AAC1E,MAAI,WAAW,UAAU,WAAW;AAClC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,cAAW;AAAA,QACX,OAAO,WAAW,0BAAS,YAAY,QAAQ;AAAA,QAC9C,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,QAAM,WAAW,WAAW,UAAU,aAAa,WAAW,OAAO;AACrE,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,4CAAC,oBAAK,MAAM,IAAI,IAAI,IAAI,WAAU,eAAc,cAAW,UAAU,GAAG,MACrE,gBACH;AAAA,MAEJ;AACA,aACE,4CAAC,UAAK,WAAU,gCAA+B,cAAW,QAAQ,GAAG,MAClE,gBACH;AAAA,IAEJ;AAEA,UAAM,SAAS,CAAC,UAAU,YAAY;AACtC,QAAI,CAAC,QAAQ;AACX,aACE,4CAAC,UAAK,WAAU,kCAAiC,cAAW,UAAS,OAAO,cAAc,QAAQ,IAAK,GAAG,MACvG,gBACH;AAAA,IAEJ;AAAA,EACF;AAMA,QAAM,aAAa,SAAS,WAAW,0BAAS,KAAM,eAAe,QAAQ,SAAS,WAAW,GAAG;AACpG,QAAM,OAAO,cAAc,aAAa,SAAY,GAAG,QAAQ,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,KAAK;AAC7F,SACE,4CAAC,oBAAK,MAAY,WAAU,eAAc,cAAW,YAAY,GAAG,MACjE,gBACH;AAEJ;","names":[]}
|
|
@@ -54,16 +54,7 @@ const WikiLink = ({
|
|
|
54
54
|
}
|
|
55
55
|
const exists = !loaded || resolved in files;
|
|
56
56
|
if (!exists) {
|
|
57
|
-
return /* @__PURE__ */ jsx(
|
|
58
|
-
"span",
|
|
59
|
-
{
|
|
60
|
-
className: "ir-wikilink ir-wikilink-broken",
|
|
61
|
-
"data-state": "broken",
|
|
62
|
-
title: `No file at ${resolved}`,
|
|
63
|
-
...rest,
|
|
64
|
-
children: text
|
|
65
|
-
}
|
|
66
|
-
);
|
|
57
|
+
return /* @__PURE__ */ jsx("span", { className: "ir-wikilink ir-wikilink-broken", "data-state": "broken", title: `No file at ${resolved}`, ...rest, children: text });
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
60
|
const translated = pathPart.startsWith(FS_PREFIX) || corpusRoot !== null && pathPart.startsWith("/");
|
|
@@ -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';\nimport { splitHash } from '../urlUtils';\nimport { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from '../linkSpace';\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/**\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 { corpusRoot } = use(LinkSpaceContext);\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 // R3-273 link spaces: resolution is the SHARED resolver (`linkSpace.ts`) —\n // default space (corpus-rooted absolute targets when an enclosing provider\n // declares a corpusRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n // A malformed `$fs:` target renders BROKEN, never an anchor.\n const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });\n if (resolution.state === 'invalid') {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`Invalid ${FS_PREFIX} target: ${pathPart}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n const resolved = resolution.state === 'resolved' ? resolution.path : undefined;\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
|
|
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';\nimport { FS_PREFIX, LinkSpaceContext, resolveLinkTarget } from '../linkSpace';\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/**\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 { corpusRoot } = use(LinkSpaceContext);\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 // R3-273 link spaces: resolution is the SHARED resolver (`linkSpace.ts`) —\n // default space (corpus-rooted absolute targets when an enclosing provider\n // declares a corpusRoot; fs-rooted otherwise) or the explicit `$fs:` prefix.\n // A malformed `$fs:` target renders BROKEN, never an anchor.\n const resolution = resolveLinkTarget(pathPart, { currentFile, corpusRoot });\n if (resolution.state === 'invalid') {\n return (\n <span\n className=\"ir-wikilink ir-wikilink-broken\"\n data-state=\"broken\"\n title={`Invalid ${FS_PREFIX} target: ${pathPart}`}\n {...rest}\n >\n {text}\n </span>\n );\n }\n const resolved = resolution.state === 'resolved' ? resolution.path : undefined;\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 className=\"ir-wikilink ir-wikilink-broken\" data-state=\"broken\" title={`No file at ${resolved}`} {...rest}>\n {text}\n </span>\n );\n }\n }\n // Resolved cross-file target: route through <Link>. A space-translated target\n // (`$fs:` prefix, or a corpus-rooted absolute) navigates to the RESOLVED path —\n // the raw text is not a routable path in those shapes; everything else carries\n // the raw target bit-for-bit so its `#fragment` rides through navigation to the\n // scroll-after-nav effect (§13.5). The fragment is re-attached either way.\n const translated = pathPart.startsWith(FS_PREFIX) || (corpusRoot !== null && pathPart.startsWith('/'));\n const href = translated && resolved !== undefined ? `${resolved}${frag ? `#${frag}` : ''}` : rawTarget;\n return (\n <Link href={href} className=\"ir-wikilink\" data-state=\"resolved\" {...rest}>\n {text}\n </Link>\n );\n};\n"],"mappings":";AA6EM;AA7EN,SAAoB,WAAW;AAC/B,SAAS,YAAY;AACrB,SAAS,sCAAsC;AAC/C,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,WAAW,kBAAkB,yBAAyB;AAG/D,MAAM,kBAAkB,CAAC,WAA2B;AAClD,QAAM,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,KAAK;AAC5C,SAAO,KAAK,QAAQ,YAAY,EAAE,KAAK;AACzC;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,EAAE,WAAW,IAAI,IAAI,gBAAgB;AAC3C,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;AAMA,QAAM,aAAa,kBAAkB,UAAU,EAAE,aAAa,WAAW,CAAC;AAC1E,MAAI,WAAW,UAAU,WAAW;AAClC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,cAAW;AAAA,QACX,OAAO,WAAW,SAAS,YAAY,QAAQ;AAAA,QAC9C,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AACA,QAAM,WAAW,WAAW,UAAU,aAAa,WAAW,OAAO;AACrE,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,oBAAC,UAAK,WAAU,kCAAiC,cAAW,UAAS,OAAO,cAAc,QAAQ,IAAK,GAAG,MACvG,gBACH;AAAA,IAEJ;AAAA,EACF;AAMA,QAAM,aAAa,SAAS,WAAW,SAAS,KAAM,eAAe,QAAQ,SAAS,WAAW,GAAG;AACpG,QAAM,OAAO,cAAc,aAAa,SAAY,GAAG,QAAQ,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,KAAK;AAC7F,SACE,oBAAC,QAAK,MAAY,WAAU,eAAc,cAAW,YAAY,GAAG,MACjE,gBACH;AAEJ;","names":[]}
|