@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
package/dist/loading.cjs
CHANGED
|
@@ -67,22 +67,12 @@ function SkeletonRow({
|
|
|
67
67
|
style
|
|
68
68
|
}) {
|
|
69
69
|
useLoadingStyles();
|
|
70
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
71
|
-
"div",
|
|
72
|
-
{
|
|
73
|
-
className: "ir-sdk-shim",
|
|
74
|
-
"aria-hidden": "true",
|
|
75
|
-
style: { ...PLACEHOLDER, width, height, ...style }
|
|
76
|
-
}
|
|
77
|
-
);
|
|
70
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ir-sdk-shim", "aria-hidden": "true", style: { ...PLACEHOLDER, width, height, ...style } });
|
|
78
71
|
}
|
|
79
72
|
function rows(specs) {
|
|
80
73
|
return specs.map(({ key, ...s }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ir-sdk-shim", "aria-hidden": "true", style: { ...PLACEHOLDER, ...s } }, key));
|
|
81
74
|
}
|
|
82
|
-
function Skeleton({
|
|
83
|
-
archetype = "generic",
|
|
84
|
-
style
|
|
85
|
-
}) {
|
|
75
|
+
function Skeleton({ archetype = "generic", style }) {
|
|
86
76
|
useLoadingStyles();
|
|
87
77
|
const wrap = {
|
|
88
78
|
display: "flex",
|
|
@@ -118,7 +108,14 @@ function Skeleton({
|
|
|
118
108
|
case "generic":
|
|
119
109
|
default:
|
|
120
110
|
body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
121
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
112
|
+
"div",
|
|
113
|
+
{
|
|
114
|
+
className: "ir-sdk-shim",
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
style: { ...PLACEHOLDER, height: 16, width: "45%", borderRadius: 4 }
|
|
117
|
+
}
|
|
118
|
+
),
|
|
122
119
|
rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))
|
|
123
120
|
] });
|
|
124
121
|
}
|
|
@@ -170,7 +167,14 @@ function LoadingRegion({
|
|
|
170
167
|
"div",
|
|
171
168
|
{
|
|
172
169
|
"aria-busy": "true",
|
|
173
|
-
style: {
|
|
170
|
+
style: {
|
|
171
|
+
display: "flex",
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
justifyContent: "center",
|
|
174
|
+
width: "100%",
|
|
175
|
+
height: "100%",
|
|
176
|
+
minHeight: 48
|
|
177
|
+
},
|
|
174
178
|
children: fallback ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { label })
|
|
175
179
|
}
|
|
176
180
|
);
|
package/dist/loading.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loading.tsx"],"sourcesContent":["// In-app loading primitives (LOADING_UX_SPEC §9, Surface E; design brief 19).\n//\n// Opt-in, presentational components an app author drops in for IN-APP waits (a\n// React.lazy chunk, a slow fetch, a pending action) so they match the platform's\n// loading language for free. Per product_values §3 this is convenience — a plain\n// app that ignores it still works.\n//\n// TRUST BOUNDARY (read brief 19): these render INSIDE the app's iframe, under the\n// app's principal — presentational only, NO capability, NO host round-trip. They\n// share the token VOCABULARY (radii, the shimmer sweep, soft placeholder fills)\n// with the host skeletons, but they are deliberately NOT the host's trusted chrome:\n// no reserved landmark, no wordmark, no \"platform is loading\" framing. They use\n// ordinary app a11y (`aria-hidden` for decorative skeletons, `role=\"status\"` for an\n// indeterminate indicator) — never the host's reserved landmark/accessible name\n// (that is the trusted-path control, reserved to the host). An in-app loader that\n// mimicked host chrome would be a spoofing vector.\n//\n// Self-contained: the SDK ships no CSS pipeline, so the keyframes are injected once\n// into the document and everything else is inline styles — zero config for the author.\nimport {\n useEffect,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\n/** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22\n * design bundle). Exported so authors who hand-roll still match the platform. */\nexport const LOADING_TIMINGS = {\n /** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */\n spinThresholdMs: 150,\n /** The reveal cross-fade duration the host uses. */\n fadeMs: 150,\n /** One slow shimmer sweep across a placeholder. */\n shimmerMs: 1900,\n} as const;\n\n/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */\nexport type SkeletonArchetype =\n | \"panel.list\"\n | \"panel.tree\"\n | \"panel.editor\"\n | \"panel.conversation\"\n | \"generic\";\n\nconst STYLE_ID = \"ir-sdk-loading-styles\";\nconst STYLE_TEXT = `\n@keyframes ir-sdk-sweep{0%{transform:translateX(-130%)}60%,100%{transform:translateX(130%)}}\n@keyframes ir-sdk-spin{to{transform:rotate(360deg)}}\n.ir-sdk-shim{position:relative;overflow:hidden}\n.ir-sdk-shim::after{content:\"\";position:absolute;inset:0;background:linear-gradient(100deg,transparent 28%,rgba(127,127,127,.18) 50%,transparent 72%);transform:translateX(-130%);animation:ir-sdk-sweep ${LOADING_TIMINGS.shimmerMs}ms ease-in-out infinite}\n.ir-sdk-spin{animation:ir-sdk-spin .8s linear infinite}\n@media (prefers-reduced-motion:reduce){.ir-sdk-shim::after{display:none}.ir-sdk-spin{animation:none}}\n`;\n\n/** Inject the shimmer/spin keyframes once (idempotent, browser-only). */\nfunction ensureLoadingStyles(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = STYLE_TEXT;\n document.head.appendChild(el);\n}\n\n/** Ensure the loading keyframes exist before the component paints. */\nfunction useLoadingStyles(): void {\n useLayoutEffect(() => {\n ensureLoadingStyles();\n }, []);\n}\n\n// A soft, low-contrast placeholder fill that reads on a light OR dark app surface.\nconst PLACEHOLDER: CSSProperties = {\n background: \"rgba(127,127,127,0.20)\",\n borderRadius: 6,\n};\n\n/** A single placeholder bar — compose these into a custom skeleton shape. */\nexport function SkeletonRow({\n width = \"100%\",\n height = 12,\n style,\n}: {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n return (\n <div\n className=\"ir-sdk-shim\"\n aria-hidden=\"true\"\n style={{ ...PLACEHOLDER, width, height, ...style }}\n />\n );\n}\n\nfunction rows(specs: Array<CSSProperties & { key: number }>): ReactNode {\n return specs.map(({ key, ...s }) => (\n <div key={key} className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, ...s }} />\n ));\n}\n\n/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy\n * region (e.g. `<Suspense fallback={<Skeleton archetype=\"panel.list\" />}>`).\n * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */\nexport function Skeleton({\n archetype = \"generic\",\n style,\n}: {\n archetype?: SkeletonArchetype;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n const wrap: CSSProperties = {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n padding: 12,\n width: \"100%\",\n boxSizing: \"border-box\",\n ...style,\n };\n let body: ReactNode;\n switch (archetype) {\n case \"panel.list\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14 })));\n break;\n case \"panel.tree\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14, marginLeft: (key % 3) * 16 })));\n break;\n case \"panel.editor\":\n body = rows([62, 88, 73, 41, 80].map((w, key) => ({ key, height: 11, width: `${w}%` })));\n break;\n case \"panel.conversation\":\n body = rows(\n [0, 1, 2, 3].map((key) => ({\n key,\n height: 40,\n width: \"70%\",\n borderRadius: 12,\n alignSelf: key % 2 ? \"flex-end\" : \"flex-start\",\n })),\n );\n break;\n case \"generic\":\n default:\n body = (\n <>\n <div className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, height: 16, width: \"45%\", borderRadius: 4 }} />\n {rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))}\n </>\n );\n }\n return (\n <div aria-hidden=\"true\" style={wrap}>\n {body}\n </div>\n );\n}\n\n/** Become true after `ms`, so a fast wait never flashes an indicator (§6.2 floor). */\nfunction useDelayedFlag(ms: number): boolean {\n const [on, setOn] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setOn(true), ms);\n return () => clearTimeout(t);\n }, [ms]);\n return on;\n}\n\n/** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending\n * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it\n * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion\n * stills the rotation (the ring stays as a static indicator). In-app a11y only. */\nexport function Spinner({\n size = 18,\n thresholdMs = LOADING_TIMINGS.spinThresholdMs,\n label = \"Loading\",\n}: {\n size?: number;\n thresholdMs?: number;\n label?: string;\n}) {\n useLoadingStyles();\n const show = useDelayedFlag(thresholdMs);\n if (!show) return null;\n return (\n <span\n className=\"ir-sdk-spin\"\n role=\"status\"\n aria-label={label}\n style={{\n display: \"inline-block\",\n width: size,\n height: size,\n border: \"2px solid rgba(127,127,127,0.3)\",\n borderTopColor: \"currentColor\",\n borderRadius: \"50%\",\n boxSizing: \"border-box\",\n }}\n />\n );\n}\n\n/** Wrap an in-app region whose content is still loading: shows a centered spinner\n * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped\n * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */\nexport function LoadingRegion({\n loading,\n fallback,\n label = \"Loading\",\n children,\n}: {\n loading: boolean;\n fallback?: ReactNode;\n label?: string;\n children?: ReactNode;\n}) {\n useLoadingStyles();\n if (!loading) return <>{children}</>;\n return (\n <div\n aria-busy=\"true\"\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", width: \"100%\", height: \"100%\", minHeight: 48 }}\n >\n {fallback ?? <Spinner label={label} />}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/loading.tsx"],"sourcesContent":["// In-app loading primitives (LOADING_UX_SPEC §9, Surface E; design brief 19).\n//\n// Opt-in, presentational components an app author drops in for IN-APP waits (a\n// React.lazy chunk, a slow fetch, a pending action) so they match the platform's\n// loading language for free. Per product_values §3 this is convenience — a plain\n// app that ignores it still works.\n//\n// TRUST BOUNDARY (read brief 19): these render INSIDE the app's iframe, under the\n// app's principal — presentational only, NO capability, NO host round-trip. They\n// share the token VOCABULARY (radii, the shimmer sweep, soft placeholder fills)\n// with the host skeletons, but they are deliberately NOT the host's trusted chrome:\n// no reserved landmark, no wordmark, no \"platform is loading\" framing. They use\n// ordinary app a11y (`aria-hidden` for decorative skeletons, `role=\"status\"` for an\n// indeterminate indicator) — never the host's reserved landmark/accessible name\n// (that is the trusted-path control, reserved to the host). An in-app loader that\n// mimicked host chrome would be a spoofing vector.\n//\n// Self-contained: the SDK ships no CSS pipeline, so the keyframes are injected once\n// into the document and everything else is inline styles — zero config for the author.\nimport { useEffect, useLayoutEffect, useState, type CSSProperties, type ReactNode } from 'react';\n\n/** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22\n * design bundle). Exported so authors who hand-roll still match the platform. */\nexport const LOADING_TIMINGS = {\n /** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */\n spinThresholdMs: 150,\n /** The reveal cross-fade duration the host uses. */\n fadeMs: 150,\n /** One slow shimmer sweep across a placeholder. */\n shimmerMs: 1900,\n} as const;\n\n/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */\nexport type SkeletonArchetype = 'panel.list' | 'panel.tree' | 'panel.editor' | 'panel.conversation' | 'generic';\n\nconst STYLE_ID = 'ir-sdk-loading-styles';\nconst STYLE_TEXT = `\n@keyframes ir-sdk-sweep{0%{transform:translateX(-130%)}60%,100%{transform:translateX(130%)}}\n@keyframes ir-sdk-spin{to{transform:rotate(360deg)}}\n.ir-sdk-shim{position:relative;overflow:hidden}\n.ir-sdk-shim::after{content:\"\";position:absolute;inset:0;background:linear-gradient(100deg,transparent 28%,rgba(127,127,127,.18) 50%,transparent 72%);transform:translateX(-130%);animation:ir-sdk-sweep ${LOADING_TIMINGS.shimmerMs}ms ease-in-out infinite}\n.ir-sdk-spin{animation:ir-sdk-spin .8s linear infinite}\n@media (prefers-reduced-motion:reduce){.ir-sdk-shim::after{display:none}.ir-sdk-spin{animation:none}}\n`;\n\n/** Inject the shimmer/spin keyframes once (idempotent, browser-only). */\nfunction ensureLoadingStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement('style');\n el.id = STYLE_ID;\n el.textContent = STYLE_TEXT;\n document.head.appendChild(el);\n}\n\n/** Ensure the loading keyframes exist before the component paints. */\nfunction useLoadingStyles(): void {\n useLayoutEffect(() => {\n ensureLoadingStyles();\n }, []);\n}\n\n// A soft, low-contrast placeholder fill that reads on a light OR dark app surface.\nconst PLACEHOLDER: CSSProperties = {\n background: 'rgba(127,127,127,0.20)',\n borderRadius: 6,\n};\n\n/** A single placeholder bar — compose these into a custom skeleton shape. */\nexport function SkeletonRow({\n width = '100%',\n height = 12,\n style,\n}: {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n return <div className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, width, height, ...style }} />;\n}\n\nfunction rows(specs: Array<CSSProperties & { key: number }>): ReactNode {\n return specs.map(({ key, ...s }) => (\n <div key={key} className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, ...s }} />\n ));\n}\n\n/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy\n * region (e.g. `<Suspense fallback={<Skeleton archetype=\"panel.list\" />}>`).\n * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */\nexport function Skeleton({ archetype = 'generic', style }: { archetype?: SkeletonArchetype; style?: CSSProperties }) {\n useLoadingStyles();\n const wrap: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: 10,\n padding: 12,\n width: '100%',\n boxSizing: 'border-box',\n ...style,\n };\n let body: ReactNode;\n switch (archetype) {\n case 'panel.list':\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14 })));\n break;\n case 'panel.tree':\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14, marginLeft: (key % 3) * 16 })));\n break;\n case 'panel.editor':\n body = rows([62, 88, 73, 41, 80].map((w, key) => ({ key, height: 11, width: `${w}%` })));\n break;\n case 'panel.conversation':\n body = rows(\n [0, 1, 2, 3].map((key) => ({\n key,\n height: 40,\n width: '70%',\n borderRadius: 12,\n alignSelf: key % 2 ? 'flex-end' : 'flex-start',\n })),\n );\n break;\n case 'generic':\n default:\n body = (\n <>\n <div\n className=\"ir-sdk-shim\"\n aria-hidden=\"true\"\n style={{ ...PLACEHOLDER, height: 16, width: '45%', borderRadius: 4 }}\n />\n {rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))}\n </>\n );\n }\n return (\n <div aria-hidden=\"true\" style={wrap}>\n {body}\n </div>\n );\n}\n\n/** Become true after `ms`, so a fast wait never flashes an indicator (§6.2 floor). */\nfunction useDelayedFlag(ms: number): boolean {\n const [on, setOn] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setOn(true), ms);\n return () => clearTimeout(t);\n }, [ms]);\n return on;\n}\n\n/** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending\n * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it\n * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion\n * stills the rotation (the ring stays as a static indicator). In-app a11y only. */\nexport function Spinner({\n size = 18,\n thresholdMs = LOADING_TIMINGS.spinThresholdMs,\n label = 'Loading',\n}: {\n size?: number;\n thresholdMs?: number;\n label?: string;\n}) {\n useLoadingStyles();\n const show = useDelayedFlag(thresholdMs);\n if (!show) return null;\n return (\n <span\n className=\"ir-sdk-spin\"\n role=\"status\"\n aria-label={label}\n style={{\n display: 'inline-block',\n width: size,\n height: size,\n border: '2px solid rgba(127,127,127,0.3)',\n borderTopColor: 'currentColor',\n borderRadius: '50%',\n boxSizing: 'border-box',\n }}\n />\n );\n}\n\n/** Wrap an in-app region whose content is still loading: shows a centered spinner\n * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped\n * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */\nexport function LoadingRegion({\n loading,\n fallback,\n label = 'Loading',\n children,\n}: {\n loading: boolean;\n fallback?: ReactNode;\n label?: string;\n children?: ReactNode;\n}) {\n useLoadingStyles();\n if (!loading) return <>{children}</>;\n return (\n <div\n aria-busy=\"true\"\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n height: '100%',\n minHeight: 48,\n }}\n >\n {fallback ?? <Spinner label={label} />}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+ES;AA5DT,mBAAyF;AAIlF,MAAM,kBAAkB;AAAA;AAAA,EAE7B,iBAAiB;AAAA;AAAA,EAEjB,QAAQ;AAAA;AAAA,EAER,WAAW;AACb;AAKA,MAAM,WAAW;AACjB,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA,2MAIwL,gBAAgB,SAAS;AAAA;AAAA;AAAA;AAMpO,SAAS,sBAA4B;AACnC,MAAI,OAAO,aAAa,YAAa;AACrC,MAAI,SAAS,eAAe,QAAQ,EAAG;AACvC,QAAM,KAAK,SAAS,cAAc,OAAO;AACzC,KAAG,KAAK;AACR,KAAG,cAAc;AACjB,WAAS,KAAK,YAAY,EAAE;AAC9B;AAGA,SAAS,mBAAyB;AAChC,oCAAgB,MAAM;AACpB,wBAAoB;AAAA,EACtB,GAAG,CAAC,CAAC;AACP;AAGA,MAAM,cAA6B;AAAA,EACjC,YAAY;AAAA,EACZ,cAAc;AAChB;AAGO,SAAS,YAAY;AAAA,EAC1B,QAAQ;AAAA,EACR,SAAS;AAAA,EACT;AACF,GAIG;AACD,mBAAiB;AACjB,SAAO,4CAAC,SAAI,WAAU,eAAc,eAAY,QAAO,OAAO,EAAE,GAAG,aAAa,OAAO,QAAQ,GAAG,MAAM,GAAG;AAC7G;AAEA,SAAS,KAAK,OAA0D;AACtE,SAAO,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG,EAAE,MAC5B,4CAAC,SAAc,WAAU,eAAc,eAAY,QAAO,OAAO,EAAE,GAAG,aAAa,GAAG,EAAE,KAA9E,GAAiF,CAC5F;AACH;AAKO,SAAS,SAAS,EAAE,YAAY,WAAW,MAAM,GAA6D;AACnH,mBAAiB;AACjB,QAAM,OAAsB;AAAA,IAC1B,SAAS;AAAA,IACT,eAAe;AAAA,IACf,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,WAAW;AAAA,IACX,GAAG;AAAA,EACL;AACA,MAAI;AACJ,UAAQ,WAAW;AAAA,IACjB,KAAK;AACH,aAAO,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,QAAQ,GAAG,EAAE,CAAC;AAC/D;AAAA,IACF,KAAK;AACH,aAAO,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,QAAQ,IAAI,YAAa,MAAM,IAAK,GAAG,EAAE,CAAC;AAC3F;AAAA,IACF,KAAK;AACH,aAAO,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,SAAS,EAAE,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACvF;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS;AAAA,UACzB;AAAA,UACA,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,cAAc;AAAA,UACd,WAAW,MAAM,IAAI,aAAa;AAAA,QACpC,EAAE;AAAA,MACJ;AACA;AAAA,IACF,KAAK;AAAA,IACL;AACE,aACE,4EACE;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAY;AAAA,YACZ,OAAO,EAAE,GAAG,aAAa,QAAQ,IAAI,OAAO,OAAO,cAAc,EAAE;AAAA;AAAA,QACrE;AAAA,QACC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,QAAQ,IAAI,cAAc,EAAE,EAAE,CAAC;AAAA,SACnE;AAAA,EAEN;AACA,SACE,4CAAC,SAAI,eAAY,QAAO,OAAO,MAC5B,gBACH;AAEJ;AAGA,SAAS,eAAe,IAAqB;AAC3C,QAAM,CAAC,IAAI,KAAK,QAAI,uBAAS,KAAK;AAClC,8BAAU,MAAM;AACd,UAAM,IAAI,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE;AAC1C,WAAO,MAAM,aAAa,CAAC;AAAA,EAC7B,GAAG,CAAC,EAAE,CAAC;AACP,SAAO;AACT;AAMO,SAAS,QAAQ;AAAA,EACtB,OAAO;AAAA,EACP,cAAc,gBAAgB;AAAA,EAC9B,QAAQ;AACV,GAIG;AACD,mBAAiB;AACjB,QAAM,OAAO,eAAe,WAAW;AACvC,MAAI,CAAC,KAAM,QAAO;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,OAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,gBAAgB;AAAA,QAChB,cAAc;AAAA,QACd,WAAW;AAAA,MACb;AAAA;AAAA,EACF;AAEJ;AAKO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,GAKG;AACD,mBAAiB;AACjB,MAAI,CAAC,QAAS,QAAO,2EAAG,UAAS;AACjC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,OAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,WAAW;AAAA,MACb;AAAA,MAEC,sBAAY,4CAAC,WAAQ,OAAc;AAAA;AAAA,EACtC;AAEJ;","names":[]}
|
package/dist/loading.d.cts
CHANGED
|
@@ -12,7 +12,7 @@ declare const LOADING_TIMINGS: {
|
|
|
12
12
|
readonly shimmerMs: 1900;
|
|
13
13
|
};
|
|
14
14
|
/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */
|
|
15
|
-
type SkeletonArchetype =
|
|
15
|
+
type SkeletonArchetype = 'panel.list' | 'panel.tree' | 'panel.editor' | 'panel.conversation' | 'generic';
|
|
16
16
|
/** A single placeholder bar — compose these into a custom skeleton shape. */
|
|
17
17
|
declare function SkeletonRow({ width, height, style, }: {
|
|
18
18
|
width?: number | string;
|
|
@@ -22,7 +22,7 @@ declare function SkeletonRow({ width, height, style, }: {
|
|
|
22
22
|
/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy
|
|
23
23
|
* region (e.g. `<Suspense fallback={<Skeleton archetype="panel.list" />}>`).
|
|
24
24
|
* Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */
|
|
25
|
-
declare function Skeleton({ archetype, style
|
|
25
|
+
declare function Skeleton({ archetype, style }: {
|
|
26
26
|
archetype?: SkeletonArchetype;
|
|
27
27
|
style?: CSSProperties;
|
|
28
28
|
}): react.JSX.Element;
|
package/dist/loading.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ declare const LOADING_TIMINGS: {
|
|
|
12
12
|
readonly shimmerMs: 1900;
|
|
13
13
|
};
|
|
14
14
|
/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */
|
|
15
|
-
type SkeletonArchetype =
|
|
15
|
+
type SkeletonArchetype = 'panel.list' | 'panel.tree' | 'panel.editor' | 'panel.conversation' | 'generic';
|
|
16
16
|
/** A single placeholder bar — compose these into a custom skeleton shape. */
|
|
17
17
|
declare function SkeletonRow({ width, height, style, }: {
|
|
18
18
|
width?: number | string;
|
|
@@ -22,7 +22,7 @@ declare function SkeletonRow({ width, height, style, }: {
|
|
|
22
22
|
/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy
|
|
23
23
|
* region (e.g. `<Suspense fallback={<Skeleton archetype="panel.list" />}>`).
|
|
24
24
|
* Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */
|
|
25
|
-
declare function Skeleton({ archetype, style
|
|
25
|
+
declare function Skeleton({ archetype, style }: {
|
|
26
26
|
archetype?: SkeletonArchetype;
|
|
27
27
|
style?: CSSProperties;
|
|
28
28
|
}): react.JSX.Element;
|
package/dist/loading.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import "./chunk-VHAA22YE.js";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
useEffect,
|
|
5
|
-
useLayoutEffect,
|
|
6
|
-
useState
|
|
7
|
-
} from "react";
|
|
3
|
+
import { useEffect, useLayoutEffect, useState } from "react";
|
|
8
4
|
const LOADING_TIMINGS = {
|
|
9
5
|
/** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */
|
|
10
6
|
spinThresholdMs: 150,
|
|
@@ -45,22 +41,12 @@ function SkeletonRow({
|
|
|
45
41
|
style
|
|
46
42
|
}) {
|
|
47
43
|
useLoadingStyles();
|
|
48
|
-
return /* @__PURE__ */ jsx(
|
|
49
|
-
"div",
|
|
50
|
-
{
|
|
51
|
-
className: "ir-sdk-shim",
|
|
52
|
-
"aria-hidden": "true",
|
|
53
|
-
style: { ...PLACEHOLDER, width, height, ...style }
|
|
54
|
-
}
|
|
55
|
-
);
|
|
44
|
+
return /* @__PURE__ */ jsx("div", { className: "ir-sdk-shim", "aria-hidden": "true", style: { ...PLACEHOLDER, width, height, ...style } });
|
|
56
45
|
}
|
|
57
46
|
function rows(specs) {
|
|
58
47
|
return specs.map(({ key, ...s }) => /* @__PURE__ */ jsx("div", { className: "ir-sdk-shim", "aria-hidden": "true", style: { ...PLACEHOLDER, ...s } }, key));
|
|
59
48
|
}
|
|
60
|
-
function Skeleton({
|
|
61
|
-
archetype = "generic",
|
|
62
|
-
style
|
|
63
|
-
}) {
|
|
49
|
+
function Skeleton({ archetype = "generic", style }) {
|
|
64
50
|
useLoadingStyles();
|
|
65
51
|
const wrap = {
|
|
66
52
|
display: "flex",
|
|
@@ -96,7 +82,14 @@ function Skeleton({
|
|
|
96
82
|
case "generic":
|
|
97
83
|
default:
|
|
98
84
|
body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
99
|
-
/* @__PURE__ */ jsx(
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
"div",
|
|
87
|
+
{
|
|
88
|
+
className: "ir-sdk-shim",
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
style: { ...PLACEHOLDER, height: 16, width: "45%", borderRadius: 4 }
|
|
91
|
+
}
|
|
92
|
+
),
|
|
100
93
|
rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))
|
|
101
94
|
] });
|
|
102
95
|
}
|
|
@@ -148,7 +141,14 @@ function LoadingRegion({
|
|
|
148
141
|
"div",
|
|
149
142
|
{
|
|
150
143
|
"aria-busy": "true",
|
|
151
|
-
style: {
|
|
144
|
+
style: {
|
|
145
|
+
display: "flex",
|
|
146
|
+
alignItems: "center",
|
|
147
|
+
justifyContent: "center",
|
|
148
|
+
width: "100%",
|
|
149
|
+
height: "100%",
|
|
150
|
+
minHeight: 48
|
|
151
|
+
},
|
|
152
152
|
children: fallback ?? /* @__PURE__ */ jsx(Spinner, { label })
|
|
153
153
|
}
|
|
154
154
|
);
|
package/dist/loading.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loading.tsx"],"sourcesContent":["// In-app loading primitives (LOADING_UX_SPEC §9, Surface E; design brief 19).\n//\n// Opt-in, presentational components an app author drops in for IN-APP waits (a\n// React.lazy chunk, a slow fetch, a pending action) so they match the platform's\n// loading language for free. Per product_values §3 this is convenience — a plain\n// app that ignores it still works.\n//\n// TRUST BOUNDARY (read brief 19): these render INSIDE the app's iframe, under the\n// app's principal — presentational only, NO capability, NO host round-trip. They\n// share the token VOCABULARY (radii, the shimmer sweep, soft placeholder fills)\n// with the host skeletons, but they are deliberately NOT the host's trusted chrome:\n// no reserved landmark, no wordmark, no \"platform is loading\" framing. They use\n// ordinary app a11y (`aria-hidden` for decorative skeletons, `role=\"status\"` for an\n// indeterminate indicator) — never the host's reserved landmark/accessible name\n// (that is the trusted-path control, reserved to the host). An in-app loader that\n// mimicked host chrome would be a spoofing vector.\n//\n// Self-contained: the SDK ships no CSS pipeline, so the keyframes are injected once\n// into the document and everything else is inline styles — zero config for the author.\nimport {\n useEffect,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\n/** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22\n * design bundle). Exported so authors who hand-roll still match the platform. */\nexport const LOADING_TIMINGS = {\n /** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */\n spinThresholdMs: 150,\n /** The reveal cross-fade duration the host uses. */\n fadeMs: 150,\n /** One slow shimmer sweep across a placeholder. */\n shimmerMs: 1900,\n} as const;\n\n/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */\nexport type SkeletonArchetype =\n | \"panel.list\"\n | \"panel.tree\"\n | \"panel.editor\"\n | \"panel.conversation\"\n | \"generic\";\n\nconst STYLE_ID = \"ir-sdk-loading-styles\";\nconst STYLE_TEXT = `\n@keyframes ir-sdk-sweep{0%{transform:translateX(-130%)}60%,100%{transform:translateX(130%)}}\n@keyframes ir-sdk-spin{to{transform:rotate(360deg)}}\n.ir-sdk-shim{position:relative;overflow:hidden}\n.ir-sdk-shim::after{content:\"\";position:absolute;inset:0;background:linear-gradient(100deg,transparent 28%,rgba(127,127,127,.18) 50%,transparent 72%);transform:translateX(-130%);animation:ir-sdk-sweep ${LOADING_TIMINGS.shimmerMs}ms ease-in-out infinite}\n.ir-sdk-spin{animation:ir-sdk-spin .8s linear infinite}\n@media (prefers-reduced-motion:reduce){.ir-sdk-shim::after{display:none}.ir-sdk-spin{animation:none}}\n`;\n\n/** Inject the shimmer/spin keyframes once (idempotent, browser-only). */\nfunction ensureLoadingStyles(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = STYLE_TEXT;\n document.head.appendChild(el);\n}\n\n/** Ensure the loading keyframes exist before the component paints. */\nfunction useLoadingStyles(): void {\n useLayoutEffect(() => {\n ensureLoadingStyles();\n }, []);\n}\n\n// A soft, low-contrast placeholder fill that reads on a light OR dark app surface.\nconst PLACEHOLDER: CSSProperties = {\n background: \"rgba(127,127,127,0.20)\",\n borderRadius: 6,\n};\n\n/** A single placeholder bar — compose these into a custom skeleton shape. */\nexport function SkeletonRow({\n width = \"100%\",\n height = 12,\n style,\n}: {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n return (\n <div\n className=\"ir-sdk-shim\"\n aria-hidden=\"true\"\n style={{ ...PLACEHOLDER, width, height, ...style }}\n />\n );\n}\n\nfunction rows(specs: Array<CSSProperties & { key: number }>): ReactNode {\n return specs.map(({ key, ...s }) => (\n <div key={key} className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, ...s }} />\n ));\n}\n\n/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy\n * region (e.g. `<Suspense fallback={<Skeleton archetype=\"panel.list\" />}>`).\n * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */\nexport function Skeleton({\n archetype = \"generic\",\n style,\n}: {\n archetype?: SkeletonArchetype;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n const wrap: CSSProperties = {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n padding: 12,\n width: \"100%\",\n boxSizing: \"border-box\",\n ...style,\n };\n let body: ReactNode;\n switch (archetype) {\n case \"panel.list\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14 })));\n break;\n case \"panel.tree\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14, marginLeft: (key % 3) * 16 })));\n break;\n case \"panel.editor\":\n body = rows([62, 88, 73, 41, 80].map((w, key) => ({ key, height: 11, width: `${w}%` })));\n break;\n case \"panel.conversation\":\n body = rows(\n [0, 1, 2, 3].map((key) => ({\n key,\n height: 40,\n width: \"70%\",\n borderRadius: 12,\n alignSelf: key % 2 ? \"flex-end\" : \"flex-start\",\n })),\n );\n break;\n case \"generic\":\n default:\n body = (\n <>\n <div className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, height: 16, width: \"45%\", borderRadius: 4 }} />\n {rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))}\n </>\n );\n }\n return (\n <div aria-hidden=\"true\" style={wrap}>\n {body}\n </div>\n );\n}\n\n/** Become true after `ms`, so a fast wait never flashes an indicator (§6.2 floor). */\nfunction useDelayedFlag(ms: number): boolean {\n const [on, setOn] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setOn(true), ms);\n return () => clearTimeout(t);\n }, [ms]);\n return on;\n}\n\n/** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending\n * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it\n * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion\n * stills the rotation (the ring stays as a static indicator). In-app a11y only. */\nexport function Spinner({\n size = 18,\n thresholdMs = LOADING_TIMINGS.spinThresholdMs,\n label = \"Loading\",\n}: {\n size?: number;\n thresholdMs?: number;\n label?: string;\n}) {\n useLoadingStyles();\n const show = useDelayedFlag(thresholdMs);\n if (!show) return null;\n return (\n <span\n className=\"ir-sdk-spin\"\n role=\"status\"\n aria-label={label}\n style={{\n display: \"inline-block\",\n width: size,\n height: size,\n border: \"2px solid rgba(127,127,127,0.3)\",\n borderTopColor: \"currentColor\",\n borderRadius: \"50%\",\n boxSizing: \"border-box\",\n }}\n />\n );\n}\n\n/** Wrap an in-app region whose content is still loading: shows a centered spinner\n * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped\n * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */\nexport function LoadingRegion({\n loading,\n fallback,\n label = \"Loading\",\n children,\n}: {\n loading: boolean;\n fallback?: ReactNode;\n label?: string;\n children?: ReactNode;\n}) {\n useLoadingStyles();\n if (!loading) return <>{children}</>;\n return (\n <div\n aria-busy=\"true\"\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", width: \"100%\", height: \"100%\", minHeight: 48 }}\n >\n {fallback ?? <Spinner label={label} />}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/loading.tsx"],"sourcesContent":["// In-app loading primitives (LOADING_UX_SPEC §9, Surface E; design brief 19).\n//\n// Opt-in, presentational components an app author drops in for IN-APP waits (a\n// React.lazy chunk, a slow fetch, a pending action) so they match the platform's\n// loading language for free. Per product_values §3 this is convenience — a plain\n// app that ignores it still works.\n//\n// TRUST BOUNDARY (read brief 19): these render INSIDE the app's iframe, under the\n// app's principal — presentational only, NO capability, NO host round-trip. They\n// share the token VOCABULARY (radii, the shimmer sweep, soft placeholder fills)\n// with the host skeletons, but they are deliberately NOT the host's trusted chrome:\n// no reserved landmark, no wordmark, no \"platform is loading\" framing. They use\n// ordinary app a11y (`aria-hidden` for decorative skeletons, `role=\"status\"` for an\n// indeterminate indicator) — never the host's reserved landmark/accessible name\n// (that is the trusted-path control, reserved to the host). An in-app loader that\n// mimicked host chrome would be a spoofing vector.\n//\n// Self-contained: the SDK ships no CSS pipeline, so the keyframes are injected once\n// into the document and everything else is inline styles — zero config for the author.\nimport { useEffect, useLayoutEffect, useState, type CSSProperties, type ReactNode } from 'react';\n\n/** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22\n * design bundle). Exported so authors who hand-roll still match the platform. */\nexport const LOADING_TIMINGS = {\n /** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */\n spinThresholdMs: 150,\n /** The reveal cross-fade duration the host uses. */\n fadeMs: 150,\n /** One slow shimmer sweep across a placeholder. */\n shimmerMs: 1900,\n} as const;\n\n/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */\nexport type SkeletonArchetype = 'panel.list' | 'panel.tree' | 'panel.editor' | 'panel.conversation' | 'generic';\n\nconst STYLE_ID = 'ir-sdk-loading-styles';\nconst STYLE_TEXT = `\n@keyframes ir-sdk-sweep{0%{transform:translateX(-130%)}60%,100%{transform:translateX(130%)}}\n@keyframes ir-sdk-spin{to{transform:rotate(360deg)}}\n.ir-sdk-shim{position:relative;overflow:hidden}\n.ir-sdk-shim::after{content:\"\";position:absolute;inset:0;background:linear-gradient(100deg,transparent 28%,rgba(127,127,127,.18) 50%,transparent 72%);transform:translateX(-130%);animation:ir-sdk-sweep ${LOADING_TIMINGS.shimmerMs}ms ease-in-out infinite}\n.ir-sdk-spin{animation:ir-sdk-spin .8s linear infinite}\n@media (prefers-reduced-motion:reduce){.ir-sdk-shim::after{display:none}.ir-sdk-spin{animation:none}}\n`;\n\n/** Inject the shimmer/spin keyframes once (idempotent, browser-only). */\nfunction ensureLoadingStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement('style');\n el.id = STYLE_ID;\n el.textContent = STYLE_TEXT;\n document.head.appendChild(el);\n}\n\n/** Ensure the loading keyframes exist before the component paints. */\nfunction useLoadingStyles(): void {\n useLayoutEffect(() => {\n ensureLoadingStyles();\n }, []);\n}\n\n// A soft, low-contrast placeholder fill that reads on a light OR dark app surface.\nconst PLACEHOLDER: CSSProperties = {\n background: 'rgba(127,127,127,0.20)',\n borderRadius: 6,\n};\n\n/** A single placeholder bar — compose these into a custom skeleton shape. */\nexport function SkeletonRow({\n width = '100%',\n height = 12,\n style,\n}: {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n return <div className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, width, height, ...style }} />;\n}\n\nfunction rows(specs: Array<CSSProperties & { key: number }>): ReactNode {\n return specs.map(({ key, ...s }) => (\n <div key={key} className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, ...s }} />\n ));\n}\n\n/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy\n * region (e.g. `<Suspense fallback={<Skeleton archetype=\"panel.list\" />}>`).\n * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */\nexport function Skeleton({ archetype = 'generic', style }: { archetype?: SkeletonArchetype; style?: CSSProperties }) {\n useLoadingStyles();\n const wrap: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: 10,\n padding: 12,\n width: '100%',\n boxSizing: 'border-box',\n ...style,\n };\n let body: ReactNode;\n switch (archetype) {\n case 'panel.list':\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14 })));\n break;\n case 'panel.tree':\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14, marginLeft: (key % 3) * 16 })));\n break;\n case 'panel.editor':\n body = rows([62, 88, 73, 41, 80].map((w, key) => ({ key, height: 11, width: `${w}%` })));\n break;\n case 'panel.conversation':\n body = rows(\n [0, 1, 2, 3].map((key) => ({\n key,\n height: 40,\n width: '70%',\n borderRadius: 12,\n alignSelf: key % 2 ? 'flex-end' : 'flex-start',\n })),\n );\n break;\n case 'generic':\n default:\n body = (\n <>\n <div\n className=\"ir-sdk-shim\"\n aria-hidden=\"true\"\n style={{ ...PLACEHOLDER, height: 16, width: '45%', borderRadius: 4 }}\n />\n {rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))}\n </>\n );\n }\n return (\n <div aria-hidden=\"true\" style={wrap}>\n {body}\n </div>\n );\n}\n\n/** Become true after `ms`, so a fast wait never flashes an indicator (§6.2 floor). */\nfunction useDelayedFlag(ms: number): boolean {\n const [on, setOn] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setOn(true), ms);\n return () => clearTimeout(t);\n }, [ms]);\n return on;\n}\n\n/** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending\n * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it\n * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion\n * stills the rotation (the ring stays as a static indicator). In-app a11y only. */\nexport function Spinner({\n size = 18,\n thresholdMs = LOADING_TIMINGS.spinThresholdMs,\n label = 'Loading',\n}: {\n size?: number;\n thresholdMs?: number;\n label?: string;\n}) {\n useLoadingStyles();\n const show = useDelayedFlag(thresholdMs);\n if (!show) return null;\n return (\n <span\n className=\"ir-sdk-spin\"\n role=\"status\"\n aria-label={label}\n style={{\n display: 'inline-block',\n width: size,\n height: size,\n border: '2px solid rgba(127,127,127,0.3)',\n borderTopColor: 'currentColor',\n borderRadius: '50%',\n boxSizing: 'border-box',\n }}\n />\n );\n}\n\n/** Wrap an in-app region whose content is still loading: shows a centered spinner\n * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped\n * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */\nexport function LoadingRegion({\n loading,\n fallback,\n label = 'Loading',\n children,\n}: {\n loading: boolean;\n fallback?: ReactNode;\n label?: string;\n children?: ReactNode;\n}) {\n useLoadingStyles();\n if (!loading) return <>{children}</>;\n return (\n <div\n aria-busy=\"true\"\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '100%',\n height: '100%',\n minHeight: 48,\n }}\n >\n {fallback ?? <Spinner label={label} />}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/markers.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/markers.ts"],"sourcesContent":["// Runtime emission of the boot `ir.*` markers (LOAD_PROFILING_SPEC §3/§3.2, R3-46).\n//\n// The host already MERGED the receive end: it validates a forwarded\n// `{ type:'ir-marker', name, at, attrs? }` against the LP-5 allowlist\n// (`irMarkers.ts`) and brackets request→interactive, treating an accepted\n// `ir.interactive` as the root-render-commit signal (site-main\n// `routePerfMessage`/`perfTimeline`). This module is the SANDBOX-side emitter for\n// the boot marks the SDK owns — `ir.fmp` (first meaningful paint) and\n// `ir.interactive` (root render commit) — forwarded over the same transport as\n// `reportReady()` (`ready.ts`). The per-marker timestamp is the sandbox-relative\n// `performance.now()` at emission (§3.2).\n//\n// Mirrors `ready.ts`'s dependency seam so the emission is unit-testable without a\n// host transport, and is idempotent per name so React StrictMode's double-invoke\n// (or any re-commit) can't mint a boot mark twice.\nimport { sendMessage as defaultSend } from './sandboxUtils';\nimport { isIrMarkerName, type IrMarkerName } from './irMarkers';\n\ninterface MarkerDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
1
|
+
{"version":3,"sources":["../src/markers.ts"],"sourcesContent":["// Runtime emission of the boot `ir.*` markers (LOAD_PROFILING_SPEC §3/§3.2, R3-46).\n//\n// The host already MERGED the receive end: it validates a forwarded\n// `{ type:'ir-marker', name, at, attrs? }` against the LP-5 allowlist\n// (`irMarkers.ts`) and brackets request→interactive, treating an accepted\n// `ir.interactive` as the root-render-commit signal (site-main\n// `routePerfMessage`/`perfTimeline`). This module is the SANDBOX-side emitter for\n// the boot marks the SDK owns — `ir.fmp` (first meaningful paint) and\n// `ir.interactive` (root render commit) — forwarded over the same transport as\n// `reportReady()` (`ready.ts`). The per-marker timestamp is the sandbox-relative\n// `performance.now()` at emission (§3.2).\n//\n// Mirrors `ready.ts`'s dependency seam so the emission is unit-testable without a\n// host transport, and is idempotent per name so React StrictMode's double-invoke\n// (or any re-commit) can't mint a boot mark twice.\nimport { sendMessage as defaultSend } from './sandboxUtils';\nimport { isIrMarkerName, type IrMarkerName } from './irMarkers';\n\ninterface MarkerDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now();\n\nconst defaultDeps: MarkerDeps = { send: defaultSend, now: realNow };\n\nlet deps: MarkerDeps = defaultDeps;\nconst emitted = new Set<string>();\n\n/**\n * Forward one `ir.*` marker to the host (`ir-marker`). The host re-validates against\n * the allowlist, so a bad name/attr is dropped there; we only emit names the SDK\n * itself owns. A transport that isn't ready yet is swallowed — a missing boot mark\n * must never break the app's boot.\n */\nexport function emitMarker(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (!isIrMarkerName(name)) return;\n try {\n deps.send('ir-marker', { name, at: deps.now(), ...(attrs !== undefined ? { attrs } : {}) });\n } catch {\n /* transport not ready — boot continues; the host simply lacks this mark */\n }\n}\n\n/** Emit a boot one-shot at most once per name (idempotent across StrictMode/re-commit). */\nexport function emitMarkerOnce(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (emitted.has(name)) return;\n emitted.add(name);\n emitMarker(name, attrs);\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setMarkerDeps(d: Partial<MarkerDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state (the once-guard + deps) between cases. */\nexport function __resetMarkers(): void {\n deps = defaultDeps;\n emitted.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,0BAA2C;AAC3C,uBAAkD;AAOlD,MAAM,UAAU,MACd,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,aAAa,YAAY,IAAI,IAAI,KAAK,IAAI;AAE7G,MAAM,cAA0B,EAAE,MAAM,oBAAAA,aAAa,KAAK,QAAQ;AAElE,IAAI,OAAmB;AACvB,MAAM,UAAU,oBAAI,IAAY;AAQzB,SAAS,WAAW,MAAoB,OAAuC;AACpF,MAAI,KAAC,iCAAe,IAAI,EAAG;AAC3B,MAAI;AACF,SAAK,KAAK,aAAa,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAAA,EAC5F,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,eAAe,MAAoB,OAAuC;AACxF,MAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,UAAQ,IAAI,IAAI;AAChB,aAAW,MAAM,KAAK;AACxB;AAGO,SAAS,gBAAgB,GAA8B;AAC5D,SAAO,EAAE,GAAG,aAAa,GAAG,EAAE;AAChC;AAGO,SAAS,iBAAuB;AACrC,SAAO;AACP,UAAQ,MAAM;AAChB;","names":["defaultSend"]}
|
package/dist/markers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/markers.ts"],"sourcesContent":["// Runtime emission of the boot `ir.*` markers (LOAD_PROFILING_SPEC §3/§3.2, R3-46).\n//\n// The host already MERGED the receive end: it validates a forwarded\n// `{ type:'ir-marker', name, at, attrs? }` against the LP-5 allowlist\n// (`irMarkers.ts`) and brackets request→interactive, treating an accepted\n// `ir.interactive` as the root-render-commit signal (site-main\n// `routePerfMessage`/`perfTimeline`). This module is the SANDBOX-side emitter for\n// the boot marks the SDK owns — `ir.fmp` (first meaningful paint) and\n// `ir.interactive` (root render commit) — forwarded over the same transport as\n// `reportReady()` (`ready.ts`). The per-marker timestamp is the sandbox-relative\n// `performance.now()` at emission (§3.2).\n//\n// Mirrors `ready.ts`'s dependency seam so the emission is unit-testable without a\n// host transport, and is idempotent per name so React StrictMode's double-invoke\n// (or any re-commit) can't mint a boot mark twice.\nimport { sendMessage as defaultSend } from './sandboxUtils';\nimport { isIrMarkerName, type IrMarkerName } from './irMarkers';\n\ninterface MarkerDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
1
|
+
{"version":3,"sources":["../src/markers.ts"],"sourcesContent":["// Runtime emission of the boot `ir.*` markers (LOAD_PROFILING_SPEC §3/§3.2, R3-46).\n//\n// The host already MERGED the receive end: it validates a forwarded\n// `{ type:'ir-marker', name, at, attrs? }` against the LP-5 allowlist\n// (`irMarkers.ts`) and brackets request→interactive, treating an accepted\n// `ir.interactive` as the root-render-commit signal (site-main\n// `routePerfMessage`/`perfTimeline`). This module is the SANDBOX-side emitter for\n// the boot marks the SDK owns — `ir.fmp` (first meaningful paint) and\n// `ir.interactive` (root render commit) — forwarded over the same transport as\n// `reportReady()` (`ready.ts`). The per-marker timestamp is the sandbox-relative\n// `performance.now()` at emission (§3.2).\n//\n// Mirrors `ready.ts`'s dependency seam so the emission is unit-testable without a\n// host transport, and is idempotent per name so React StrictMode's double-invoke\n// (or any re-commit) can't mint a boot mark twice.\nimport { sendMessage as defaultSend } from './sandboxUtils';\nimport { isIrMarkerName, type IrMarkerName } from './irMarkers';\n\ninterface MarkerDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now();\n\nconst defaultDeps: MarkerDeps = { send: defaultSend, now: realNow };\n\nlet deps: MarkerDeps = defaultDeps;\nconst emitted = new Set<string>();\n\n/**\n * Forward one `ir.*` marker to the host (`ir-marker`). The host re-validates against\n * the allowlist, so a bad name/attr is dropped there; we only emit names the SDK\n * itself owns. A transport that isn't ready yet is swallowed — a missing boot mark\n * must never break the app's boot.\n */\nexport function emitMarker(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (!isIrMarkerName(name)) return;\n try {\n deps.send('ir-marker', { name, at: deps.now(), ...(attrs !== undefined ? { attrs } : {}) });\n } catch {\n /* transport not ready — boot continues; the host simply lacks this mark */\n }\n}\n\n/** Emit a boot one-shot at most once per name (idempotent across StrictMode/re-commit). */\nexport function emitMarkerOnce(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (emitted.has(name)) return;\n emitted.add(name);\n emitMarker(name, attrs);\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setMarkerDeps(d: Partial<MarkerDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state (the once-guard + deps) between cases. */\nexport function __resetMarkers(): void {\n deps = defaultDeps;\n emitted.clear();\n}\n"],"mappings":";AAeA,SAAS,eAAe,mBAAmB;AAC3C,SAAS,sBAAyC;AAOlD,MAAM,UAAU,MACd,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,aAAa,YAAY,IAAI,IAAI,KAAK,IAAI;AAE7G,MAAM,cAA0B,EAAE,MAAM,aAAa,KAAK,QAAQ;AAElE,IAAI,OAAmB;AACvB,MAAM,UAAU,oBAAI,IAAY;AAQzB,SAAS,WAAW,MAAoB,OAAuC;AACpF,MAAI,CAAC,eAAe,IAAI,EAAG;AAC3B,MAAI;AACF,SAAK,KAAK,aAAa,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAAA,EAC5F,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,eAAe,MAAoB,OAAuC;AACxF,MAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,UAAQ,IAAI,IAAI;AAChB,aAAW,MAAM,KAAK;AACxB;AAGO,SAAS,gBAAgB,GAA8B;AAC5D,SAAO,EAAE,GAAG,aAAa,GAAG,EAAE;AAChC;AAGO,SAAS,iBAAuB;AACrC,SAAO;AACP,UAAQ,MAAM;AAChB;","names":[]}
|
package/dist/metadataSource.cjs
CHANGED
|
@@ -26,11 +26,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
26
26
|
var import_react = require("react");
|
|
27
27
|
var import_TinkerableContext = require("./TinkerableContext");
|
|
28
28
|
const MetadataSourceContext = (0, import_react.createContext)(void 0);
|
|
29
|
-
const MetadataSource = ({
|
|
30
|
-
value,
|
|
31
|
-
mode = "replace",
|
|
32
|
-
children
|
|
33
|
-
}) => {
|
|
29
|
+
const MetadataSource = ({ value, mode = "replace", children }) => {
|
|
34
30
|
const outer = (0, import_react.use)(MetadataSourceContext);
|
|
35
31
|
const host = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
36
32
|
const provided = (0, import_react.useMemo)(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/metadataSource.tsx"],"sourcesContent":["// A supported surface for a viewer app to provide its own metadata store\n// (PLATFORM_LAYERING_SPEC §4 / S3, R3-276).\n//\n// WHAT IT REPLACES. A viewer that scans its own corpus — Grove under dispatch, any\n// app whose content is not the app-root MDX the sandbox pre-scans — had exactly one\n// way to get that map in front of the metadata hooks: re-provide the platform's own\n// context wholesale,\n//\n// <TinkerableContext.Provider value={{ ...host, filesMetadata: corpus.metadata }}>\n//\n// which is a fork of a platform contract in app code. It works until the platform\n// adds a field to `TinkerableState`, at which point every such app is silently\n// re-providing a stale copy of whatever it spread from — and nothing tells it.\n//\n// This narrows that to the one thing the app actually wanted to say: here is the\n// metadata store for my subtree. The platform stays free to grow `TinkerableState`.\n\nimport React, { createContext, use, useMemo } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport type { FilesMetadata, Metadata } from './sandboxTypes';\n\n/** How a provided store combines with the one already in scope. */\nexport type MetadataSourceMode =\n /** The provided map IS the store for descendants — the host's entries are not\n * visible. The default: a viewer that scanned its own corpus is describing a\n * different file space, not adding to the platform's. */\n | 'replace'\n /** The provided entries are layered OVER what is already in scope: a path the\n * provider does not name still resolves to the outer store's value. For a viewer\n * that augments the app-root scan rather than replacing it. */\n | 'merge';\n\ninterface MetadataSourceValue {\n filesMetadata: FilesMetadata<Metadata>;\n}\n\n/** Undefined outside any provider, which is how the hooks know to read the host's\n * store instead of an empty one — the two are NOT the same state. */\nconst MetadataSourceContext = createContext<MetadataSourceValue | undefined>(undefined);\n\nexport interface MetadataSourceProps {\n /** The metadata store to provide: path → frontmatter. */\n value: FilesMetadata<Metadata>;\n /** Default `'replace'`. See {@link MetadataSourceMode}. */\n mode?: MetadataSourceMode;\n children?: React.ReactNode;\n}\n\n/**\n * Provide a metadata store to `useMetadataQuery`, `useFileMetadata` and\n * `useAllMetadata` for everything rendered inside.\n *\n * ```tsx\n * <MetadataSource value={corpus.metadata}>\n * <Wiki />\n * </MetadataSource>\n * ```\n *\n * Nesting is allowed and the innermost provider wins; `mode=\"merge\"` layers over\n * whatever is in scope (an outer provider, else the host's store). Unmounting the\n * provider restores the outer store — there is no registry to clean up, so an app\n * cannot leak a stale source by forgetting to unregister.\n *\n * The value is used as given: pass a stable reference (a `useMemo`, module state)\n * if you care about the query hooks' identity-preservation, which is what keeps\n * their results usable in dependency arrays.\n */\nexport const MetadataSource = ({
|
|
1
|
+
{"version":3,"sources":["../src/metadataSource.tsx"],"sourcesContent":["// A supported surface for a viewer app to provide its own metadata store\n// (PLATFORM_LAYERING_SPEC §4 / S3, R3-276).\n//\n// WHAT IT REPLACES. A viewer that scans its own corpus — Grove under dispatch, any\n// app whose content is not the app-root MDX the sandbox pre-scans — had exactly one\n// way to get that map in front of the metadata hooks: re-provide the platform's own\n// context wholesale,\n//\n// <TinkerableContext.Provider value={{ ...host, filesMetadata: corpus.metadata }}>\n//\n// which is a fork of a platform contract in app code. It works until the platform\n// adds a field to `TinkerableState`, at which point every such app is silently\n// re-providing a stale copy of whatever it spread from — and nothing tells it.\n//\n// This narrows that to the one thing the app actually wanted to say: here is the\n// metadata store for my subtree. The platform stays free to grow `TinkerableState`.\n\nimport React, { createContext, use, useMemo } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport type { FilesMetadata, Metadata } from './sandboxTypes';\n\n/** How a provided store combines with the one already in scope. */\nexport type MetadataSourceMode =\n /** The provided map IS the store for descendants — the host's entries are not\n * visible. The default: a viewer that scanned its own corpus is describing a\n * different file space, not adding to the platform's. */\n | 'replace'\n /** The provided entries are layered OVER what is already in scope: a path the\n * provider does not name still resolves to the outer store's value. For a viewer\n * that augments the app-root scan rather than replacing it. */\n | 'merge';\n\ninterface MetadataSourceValue {\n filesMetadata: FilesMetadata<Metadata>;\n}\n\n/** Undefined outside any provider, which is how the hooks know to read the host's\n * store instead of an empty one — the two are NOT the same state. */\nconst MetadataSourceContext = createContext<MetadataSourceValue | undefined>(undefined);\n\nexport interface MetadataSourceProps {\n /** The metadata store to provide: path → frontmatter. */\n value: FilesMetadata<Metadata>;\n /** Default `'replace'`. See {@link MetadataSourceMode}. */\n mode?: MetadataSourceMode;\n children?: React.ReactNode;\n}\n\n/**\n * Provide a metadata store to `useMetadataQuery`, `useFileMetadata` and\n * `useAllMetadata` for everything rendered inside.\n *\n * ```tsx\n * <MetadataSource value={corpus.metadata}>\n * <Wiki />\n * </MetadataSource>\n * ```\n *\n * Nesting is allowed and the innermost provider wins; `mode=\"merge\"` layers over\n * whatever is in scope (an outer provider, else the host's store). Unmounting the\n * provider restores the outer store — there is no registry to clean up, so an app\n * cannot leak a stale source by forgetting to unregister.\n *\n * The value is used as given: pass a stable reference (a `useMemo`, module state)\n * if you care about the query hooks' identity-preservation, which is what keeps\n * their results usable in dependency arrays.\n */\nexport const MetadataSource = ({ value, mode = 'replace', children }: MetadataSourceProps): React.ReactElement => {\n const outer = use(MetadataSourceContext);\n const host = use(TinkerableContext);\n const provided = useMemo<MetadataSourceValue>(() => {\n if (mode !== 'merge') return { filesMetadata: value };\n const base = outer?.filesMetadata ?? host?.filesMetadata ?? {};\n return { filesMetadata: { ...base, ...value } };\n }, [value, mode, outer, host]);\n return <MetadataSourceContext.Provider value={provided}>{children}</MetadataSourceContext.Provider>;\n};\n\n/**\n * The metadata store in scope: the nearest {@link MetadataSource}, else the host's.\n *\n * The metadata hooks read through this, so an app never has to know which of the\n * two answered. Exported because a component that wants the raw map without the\n * query machinery should not have to reach for `TinkerableContext` and get the\n * answer wrong when a provider is present.\n */\nexport const useMetadataStore = <T = Metadata,>(): FilesMetadata<T> => {\n const source = use(MetadataSourceContext);\n const host = use(TinkerableContext);\n return (source?.filesMetadata ?? host?.filesMetadata ?? {}) as FilesMetadata<T>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4ES;AA3DT,mBAAmD;AAEnD,+BAAkC;AAoBlC,MAAM,4BAAwB,4BAA+C,MAAS;AA6B/E,MAAM,iBAAiB,CAAC,EAAE,OAAO,OAAO,WAAW,SAAS,MAA+C;AAChH,QAAM,YAAQ,kBAAI,qBAAqB;AACvC,QAAM,WAAO,kBAAI,0CAAiB;AAClC,QAAM,eAAW,sBAA6B,MAAM;AAClD,QAAI,SAAS,QAAS,QAAO,EAAE,eAAe,MAAM;AACpD,UAAM,OAAO,OAAO,iBAAiB,MAAM,iBAAiB,CAAC;AAC7D,WAAO,EAAE,eAAe,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE;AAAA,EAChD,GAAG,CAAC,OAAO,MAAM,OAAO,IAAI,CAAC;AAC7B,SAAO,4CAAC,sBAAsB,UAAtB,EAA+B,OAAO,UAAW,UAAS;AACpE;AAUO,MAAM,mBAAmB,MAAuC;AACrE,QAAM,aAAS,kBAAI,qBAAqB;AACxC,QAAM,WAAO,kBAAI,0CAAiB;AAClC,SAAQ,QAAQ,iBAAiB,MAAM,iBAAiB,CAAC;AAC3D;","names":[]}
|
|
@@ -37,7 +37,7 @@ interface MetadataSourceProps {
|
|
|
37
37
|
* if you care about the query hooks' identity-preservation, which is what keeps
|
|
38
38
|
* their results usable in dependency arrays.
|
|
39
39
|
*/
|
|
40
|
-
declare const MetadataSource: ({ value, mode, children
|
|
40
|
+
declare const MetadataSource: ({ value, mode, children }: MetadataSourceProps) => react__default.ReactElement;
|
|
41
41
|
/**
|
|
42
42
|
* The metadata store in scope: the nearest {@link MetadataSource}, else the host's.
|
|
43
43
|
*
|
package/dist/metadataSource.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ interface MetadataSourceProps {
|
|
|
37
37
|
* if you care about the query hooks' identity-preservation, which is what keeps
|
|
38
38
|
* their results usable in dependency arrays.
|
|
39
39
|
*/
|
|
40
|
-
declare const MetadataSource: ({ value, mode, children
|
|
40
|
+
declare const MetadataSource: ({ value, mode, children }: MetadataSourceProps) => react__default.ReactElement;
|
|
41
41
|
/**
|
|
42
42
|
* The metadata store in scope: the nearest {@link MetadataSource}, else the host's.
|
|
43
43
|
*
|
package/dist/metadataSource.js
CHANGED
|
@@ -3,11 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { createContext, use, useMemo } from "react";
|
|
4
4
|
import { TinkerableContext } from "./TinkerableContext";
|
|
5
5
|
const MetadataSourceContext = createContext(void 0);
|
|
6
|
-
const MetadataSource = ({
|
|
7
|
-
value,
|
|
8
|
-
mode = "replace",
|
|
9
|
-
children
|
|
10
|
-
}) => {
|
|
6
|
+
const MetadataSource = ({ value, mode = "replace", children }) => {
|
|
11
7
|
const outer = use(MetadataSourceContext);
|
|
12
8
|
const host = use(TinkerableContext);
|
|
13
9
|
const provided = useMemo(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/metadataSource.tsx"],"sourcesContent":["// A supported surface for a viewer app to provide its own metadata store\n// (PLATFORM_LAYERING_SPEC §4 / S3, R3-276).\n//\n// WHAT IT REPLACES. A viewer that scans its own corpus — Grove under dispatch, any\n// app whose content is not the app-root MDX the sandbox pre-scans — had exactly one\n// way to get that map in front of the metadata hooks: re-provide the platform's own\n// context wholesale,\n//\n// <TinkerableContext.Provider value={{ ...host, filesMetadata: corpus.metadata }}>\n//\n// which is a fork of a platform contract in app code. It works until the platform\n// adds a field to `TinkerableState`, at which point every such app is silently\n// re-providing a stale copy of whatever it spread from — and nothing tells it.\n//\n// This narrows that to the one thing the app actually wanted to say: here is the\n// metadata store for my subtree. The platform stays free to grow `TinkerableState`.\n\nimport React, { createContext, use, useMemo } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport type { FilesMetadata, Metadata } from './sandboxTypes';\n\n/** How a provided store combines with the one already in scope. */\nexport type MetadataSourceMode =\n /** The provided map IS the store for descendants — the host's entries are not\n * visible. The default: a viewer that scanned its own corpus is describing a\n * different file space, not adding to the platform's. */\n | 'replace'\n /** The provided entries are layered OVER what is already in scope: a path the\n * provider does not name still resolves to the outer store's value. For a viewer\n * that augments the app-root scan rather than replacing it. */\n | 'merge';\n\ninterface MetadataSourceValue {\n filesMetadata: FilesMetadata<Metadata>;\n}\n\n/** Undefined outside any provider, which is how the hooks know to read the host's\n * store instead of an empty one — the two are NOT the same state. */\nconst MetadataSourceContext = createContext<MetadataSourceValue | undefined>(undefined);\n\nexport interface MetadataSourceProps {\n /** The metadata store to provide: path → frontmatter. */\n value: FilesMetadata<Metadata>;\n /** Default `'replace'`. See {@link MetadataSourceMode}. */\n mode?: MetadataSourceMode;\n children?: React.ReactNode;\n}\n\n/**\n * Provide a metadata store to `useMetadataQuery`, `useFileMetadata` and\n * `useAllMetadata` for everything rendered inside.\n *\n * ```tsx\n * <MetadataSource value={corpus.metadata}>\n * <Wiki />\n * </MetadataSource>\n * ```\n *\n * Nesting is allowed and the innermost provider wins; `mode=\"merge\"` layers over\n * whatever is in scope (an outer provider, else the host's store). Unmounting the\n * provider restores the outer store — there is no registry to clean up, so an app\n * cannot leak a stale source by forgetting to unregister.\n *\n * The value is used as given: pass a stable reference (a `useMemo`, module state)\n * if you care about the query hooks' identity-preservation, which is what keeps\n * their results usable in dependency arrays.\n */\nexport const MetadataSource = ({
|
|
1
|
+
{"version":3,"sources":["../src/metadataSource.tsx"],"sourcesContent":["// A supported surface for a viewer app to provide its own metadata store\n// (PLATFORM_LAYERING_SPEC §4 / S3, R3-276).\n//\n// WHAT IT REPLACES. A viewer that scans its own corpus — Grove under dispatch, any\n// app whose content is not the app-root MDX the sandbox pre-scans — had exactly one\n// way to get that map in front of the metadata hooks: re-provide the platform's own\n// context wholesale,\n//\n// <TinkerableContext.Provider value={{ ...host, filesMetadata: corpus.metadata }}>\n//\n// which is a fork of a platform contract in app code. It works until the platform\n// adds a field to `TinkerableState`, at which point every such app is silently\n// re-providing a stale copy of whatever it spread from — and nothing tells it.\n//\n// This narrows that to the one thing the app actually wanted to say: here is the\n// metadata store for my subtree. The platform stays free to grow `TinkerableState`.\n\nimport React, { createContext, use, useMemo } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport type { FilesMetadata, Metadata } from './sandboxTypes';\n\n/** How a provided store combines with the one already in scope. */\nexport type MetadataSourceMode =\n /** The provided map IS the store for descendants — the host's entries are not\n * visible. The default: a viewer that scanned its own corpus is describing a\n * different file space, not adding to the platform's. */\n | 'replace'\n /** The provided entries are layered OVER what is already in scope: a path the\n * provider does not name still resolves to the outer store's value. For a viewer\n * that augments the app-root scan rather than replacing it. */\n | 'merge';\n\ninterface MetadataSourceValue {\n filesMetadata: FilesMetadata<Metadata>;\n}\n\n/** Undefined outside any provider, which is how the hooks know to read the host's\n * store instead of an empty one — the two are NOT the same state. */\nconst MetadataSourceContext = createContext<MetadataSourceValue | undefined>(undefined);\n\nexport interface MetadataSourceProps {\n /** The metadata store to provide: path → frontmatter. */\n value: FilesMetadata<Metadata>;\n /** Default `'replace'`. See {@link MetadataSourceMode}. */\n mode?: MetadataSourceMode;\n children?: React.ReactNode;\n}\n\n/**\n * Provide a metadata store to `useMetadataQuery`, `useFileMetadata` and\n * `useAllMetadata` for everything rendered inside.\n *\n * ```tsx\n * <MetadataSource value={corpus.metadata}>\n * <Wiki />\n * </MetadataSource>\n * ```\n *\n * Nesting is allowed and the innermost provider wins; `mode=\"merge\"` layers over\n * whatever is in scope (an outer provider, else the host's store). Unmounting the\n * provider restores the outer store — there is no registry to clean up, so an app\n * cannot leak a stale source by forgetting to unregister.\n *\n * The value is used as given: pass a stable reference (a `useMemo`, module state)\n * if you care about the query hooks' identity-preservation, which is what keeps\n * their results usable in dependency arrays.\n */\nexport const MetadataSource = ({ value, mode = 'replace', children }: MetadataSourceProps): React.ReactElement => {\n const outer = use(MetadataSourceContext);\n const host = use(TinkerableContext);\n const provided = useMemo<MetadataSourceValue>(() => {\n if (mode !== 'merge') return { filesMetadata: value };\n const base = outer?.filesMetadata ?? host?.filesMetadata ?? {};\n return { filesMetadata: { ...base, ...value } };\n }, [value, mode, outer, host]);\n return <MetadataSourceContext.Provider value={provided}>{children}</MetadataSourceContext.Provider>;\n};\n\n/**\n * The metadata store in scope: the nearest {@link MetadataSource}, else the host's.\n *\n * The metadata hooks read through this, so an app never has to know which of the\n * two answered. Exported because a component that wants the raw map without the\n * query machinery should not have to reach for `TinkerableContext` and get the\n * answer wrong when a provider is present.\n */\nexport const useMetadataStore = <T = Metadata,>(): FilesMetadata<T> => {\n const source = use(MetadataSourceContext);\n const host = use(TinkerableContext);\n return (source?.filesMetadata ?? host?.filesMetadata ?? {}) as FilesMetadata<T>;\n};\n"],"mappings":";AA4ES;AA3DT,SAAgB,eAAe,KAAK,eAAe;AAEnD,SAAS,yBAAyB;AAoBlC,MAAM,wBAAwB,cAA+C,MAAS;AA6B/E,MAAM,iBAAiB,CAAC,EAAE,OAAO,OAAO,WAAW,SAAS,MAA+C;AAChH,QAAM,QAAQ,IAAI,qBAAqB;AACvC,QAAM,OAAO,IAAI,iBAAiB;AAClC,QAAM,WAAW,QAA6B,MAAM;AAClD,QAAI,SAAS,QAAS,QAAO,EAAE,eAAe,MAAM;AACpD,UAAM,OAAO,OAAO,iBAAiB,MAAM,iBAAiB,CAAC;AAC7D,WAAO,EAAE,eAAe,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE;AAAA,EAChD,GAAG,CAAC,OAAO,MAAM,OAAO,IAAI,CAAC;AAC7B,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAO,UAAW,UAAS;AACpE;AAUO,MAAM,mBAAmB,MAAuC;AACrE,QAAM,SAAS,IAAI,qBAAqB;AACxC,QAAM,OAAO,IAAI,iBAAiB;AAClC,SAAQ,QAAQ,iBAAiB,MAAM,iBAAiB,CAAC;AAC3D;","names":[]}
|
package/dist/moduleCache.cjs
CHANGED
|
@@ -55,7 +55,10 @@ class ModuleCache {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
const ModuleCacheContext = (0, import_react.createContext)(null);
|
|
58
|
-
const ModuleCacheContextProvider = ({
|
|
58
|
+
const ModuleCacheContextProvider = ({
|
|
59
|
+
children,
|
|
60
|
+
moduleCache
|
|
61
|
+
}) => {
|
|
59
62
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModuleCacheContext.Provider, { value: moduleCache, children });
|
|
60
63
|
};
|
|
61
64
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/moduleCache.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/moduleCache.tsx"],"sourcesContent":["// based on: https://www.bbss.dev/posts/react-learn-suspense/#fetchcache-provider\n\nimport { createContext, ReactNode, useCallback, useState } from
|
|
1
|
+
{"version":3,"sources":["../src/moduleCache.tsx"],"sourcesContent":["// based on: https://www.bbss.dev/posts/react-learn-suspense/#fetchcache-provider\n\nimport { createContext, ReactNode, useCallback, useState } from 'react';\nimport { EvaluationContext } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { COMPILE } from './generated/protocol';\n\nexport class ModuleCache {\n nameResolutionPromises: Record<string, Promise<string>> = {};\n evaluationContextPromises: Record<string, Promise<EvaluationContext>> = {};\n\n constructor() {\n // reset cache on compile\n addListener(COMPILE, () => {\n // NOTE: THIS CAUSES AN UNNECESSARY RELOAD\n // the <Include> component's module evaluation context promise is replaced\n // with a new promise for the same value when a compilation occurs which\n // doesn't affect the current module. Commenting out the following lines\n // eliminates the unnecessary component state loss.\n // this.nameResolutionPromises = {};\n // this.evaluationContextPromises = {};\n });\n }\n\n private getCacheKey(mod: EvaluationContext, moduleName: string): string {\n return `${mod.evaluation.module.filepath}|${moduleName}`;\n }\n\n resolveModuleName(moduleName: string, baseModule?: EvaluationContext): Promise<string> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.nameResolutionPromises)) {\n this.nameResolutionPromises[cacheKey] = mod.resolve(moduleName);\n }\n return this.nameResolutionPromises[cacheKey];\n }\n\n getEvaluationContext(moduleName: string, baseModule?: EvaluationContext): Promise<EvaluationContext> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.evaluationContextPromises)) {\n this.evaluationContextPromises[cacheKey] = mod.getModuleEvaluationContext(moduleName);\n }\n return this.evaluationContextPromises[cacheKey];\n }\n}\n\nexport const ModuleCacheContext = createContext<null | ModuleCache>(null);\n\nexport const ModuleCacheContextProvider = ({\n children,\n moduleCache,\n}: {\n children: ReactNode;\n moduleCache: ModuleCache;\n}) => {\n return <ModuleCacheContext.Provider value={moduleCache}>{children}</ModuleCacheContext.Provider>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4DS;AA1DT,mBAAgE;AAEhE,0BAA4B;AAC5B,sBAAwB;AAEjB,MAAM,YAAY;AAAA,EAIvB,cAAc;AAHd,kCAA0D,CAAC;AAC3D,qCAAwE,CAAC;AAIvE,yCAAY,yBAAS,MAAM;AAAA,IAQ3B,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,KAAwB,YAA4B;AACtE,WAAO,GAAG,IAAI,WAAW,OAAO,QAAQ,IAAI,UAAU;AAAA,EACxD;AAAA,EAEA,kBAAkB,YAAoB,YAAiD;AAGrF,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,yBAAyB;AAC9C,WAAK,uBAAuB,QAAQ,IAAI,IAAI,QAAQ,UAAU;AAAA,IAChE;AACA,WAAO,KAAK,uBAAuB,QAAQ;AAAA,EAC7C;AAAA,EAEA,qBAAqB,YAAoB,YAA4D;AAGnG,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,4BAA4B;AACjD,WAAK,0BAA0B,QAAQ,IAAI,IAAI,2BAA2B,UAAU;AAAA,IACtF;AACA,WAAO,KAAK,0BAA0B,QAAQ;AAAA,EAChD;AACF;AAEO,MAAM,yBAAqB,4BAAkC,IAAI;AAEjE,MAAM,6BAA6B,CAAC;AAAA,EACzC;AAAA,EACA;AACF,MAGM;AACJ,SAAO,4CAAC,mBAAmB,UAAnB,EAA4B,OAAO,aAAc,UAAS;AACpE;","names":[]}
|
package/dist/moduleCache.d.cts
CHANGED
|
@@ -11,7 +11,7 @@ declare class ModuleCache {
|
|
|
11
11
|
getEvaluationContext(moduleName: string, baseModule?: EvaluationContext): Promise<EvaluationContext>;
|
|
12
12
|
}
|
|
13
13
|
declare const ModuleCacheContext: react.Context<ModuleCache | null>;
|
|
14
|
-
declare const ModuleCacheContextProvider: ({ children, moduleCache }: {
|
|
14
|
+
declare const ModuleCacheContextProvider: ({ children, moduleCache, }: {
|
|
15
15
|
children: ReactNode;
|
|
16
16
|
moduleCache: ModuleCache;
|
|
17
17
|
}) => react.JSX.Element;
|
package/dist/moduleCache.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ declare class ModuleCache {
|
|
|
11
11
|
getEvaluationContext(moduleName: string, baseModule?: EvaluationContext): Promise<EvaluationContext>;
|
|
12
12
|
}
|
|
13
13
|
declare const ModuleCacheContext: react.Context<ModuleCache | null>;
|
|
14
|
-
declare const ModuleCacheContextProvider: ({ children, moduleCache }: {
|
|
14
|
+
declare const ModuleCacheContextProvider: ({ children, moduleCache, }: {
|
|
15
15
|
children: ReactNode;
|
|
16
16
|
moduleCache: ModuleCache;
|
|
17
17
|
}) => react.JSX.Element;
|
package/dist/moduleCache.js
CHANGED
|
@@ -31,7 +31,10 @@ class ModuleCache {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
const ModuleCacheContext = createContext(null);
|
|
34
|
-
const ModuleCacheContextProvider = ({
|
|
34
|
+
const ModuleCacheContextProvider = ({
|
|
35
|
+
children,
|
|
36
|
+
moduleCache
|
|
37
|
+
}) => {
|
|
35
38
|
return /* @__PURE__ */ jsx(ModuleCacheContext.Provider, { value: moduleCache, children });
|
|
36
39
|
};
|
|
37
40
|
export {
|
package/dist/moduleCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/moduleCache.tsx"],"sourcesContent":["// based on: https://www.bbss.dev/posts/react-learn-suspense/#fetchcache-provider\n\nimport { createContext, ReactNode, useCallback, useState } from
|
|
1
|
+
{"version":3,"sources":["../src/moduleCache.tsx"],"sourcesContent":["// based on: https://www.bbss.dev/posts/react-learn-suspense/#fetchcache-provider\n\nimport { createContext, ReactNode, useCallback, useState } from 'react';\nimport { EvaluationContext } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { COMPILE } from './generated/protocol';\n\nexport class ModuleCache {\n nameResolutionPromises: Record<string, Promise<string>> = {};\n evaluationContextPromises: Record<string, Promise<EvaluationContext>> = {};\n\n constructor() {\n // reset cache on compile\n addListener(COMPILE, () => {\n // NOTE: THIS CAUSES AN UNNECESSARY RELOAD\n // the <Include> component's module evaluation context promise is replaced\n // with a new promise for the same value when a compilation occurs which\n // doesn't affect the current module. Commenting out the following lines\n // eliminates the unnecessary component state loss.\n // this.nameResolutionPromises = {};\n // this.evaluationContextPromises = {};\n });\n }\n\n private getCacheKey(mod: EvaluationContext, moduleName: string): string {\n return `${mod.evaluation.module.filepath}|${moduleName}`;\n }\n\n resolveModuleName(moduleName: string, baseModule?: EvaluationContext): Promise<string> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.nameResolutionPromises)) {\n this.nameResolutionPromises[cacheKey] = mod.resolve(moduleName);\n }\n return this.nameResolutionPromises[cacheKey];\n }\n\n getEvaluationContext(moduleName: string, baseModule?: EvaluationContext): Promise<EvaluationContext> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.evaluationContextPromises)) {\n this.evaluationContextPromises[cacheKey] = mod.getModuleEvaluationContext(moduleName);\n }\n return this.evaluationContextPromises[cacheKey];\n }\n}\n\nexport const ModuleCacheContext = createContext<null | ModuleCache>(null);\n\nexport const ModuleCacheContextProvider = ({\n children,\n moduleCache,\n}: {\n children: ReactNode;\n moduleCache: ModuleCache;\n}) => {\n return <ModuleCacheContext.Provider value={moduleCache}>{children}</ModuleCacheContext.Provider>;\n};\n"],"mappings":";AA4DS;AA1DT,SAAS,qBAAuD;AAEhE,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAEjB,MAAM,YAAY;AAAA,EAIvB,cAAc;AAHd,kCAA0D,CAAC;AAC3D,qCAAwE,CAAC;AAIvE,gBAAY,SAAS,MAAM;AAAA,IAQ3B,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,KAAwB,YAA4B;AACtE,WAAO,GAAG,IAAI,WAAW,OAAO,QAAQ,IAAI,UAAU;AAAA,EACxD;AAAA,EAEA,kBAAkB,YAAoB,YAAiD;AAGrF,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,yBAAyB;AAC9C,WAAK,uBAAuB,QAAQ,IAAI,IAAI,QAAQ,UAAU;AAAA,IAChE;AACA,WAAO,KAAK,uBAAuB,QAAQ;AAAA,EAC7C;AAAA,EAEA,qBAAqB,YAAoB,YAA4D;AAGnG,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,4BAA4B;AACjD,WAAK,0BAA0B,QAAQ,IAAI,IAAI,2BAA2B,UAAU;AAAA,IACtF;AACA,WAAO,KAAK,0BAA0B,QAAQ;AAAA,EAChD;AACF;AAEO,MAAM,qBAAqB,cAAkC,IAAI;AAEjE,MAAM,6BAA6B,CAAC;AAAA,EACzC;AAAA,EACA;AACF,MAGM;AACJ,SAAO,oBAAC,mBAAmB,UAAnB,EAA4B,OAAO,aAAc,UAAS;AACpE;","names":[]}
|
package/dist/mountMatch.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mountMatch.ts"],"sourcesContent":["/**\n * Pure predicate behind {@link findMount} / {@link waitForMount}: does `mount`\n * satisfy every field present on `query`? An absent query field matches anything;\n * a present one must equal the mount's value. Kept framework-free and free of the\n * injected sandbox runtime so it is unit-testable in isolation (ways_of_working\n * §5 — separate pure logic from the effectful service).\n *\n * `name` (the human-readable mount label, R3-69) is matchable alongside the\n * `type`/`id`/`path` coordinates so an app/agent can locate a mount by the name a\n * user would recognise — e.g. `findMount({ name: 'Design notes' })` — rather than\n * an opaque `/mnt/{hash}` address or a non-unique space name guessed by hand.\n */\nexport interface MountMatchFields {\n type?: string;\n id?: string;\n path?: string;\n name?: string;\n}\n\nexport const mountMatches = (
|
|
1
|
+
{"version":3,"sources":["../src/mountMatch.ts"],"sourcesContent":["/**\n * Pure predicate behind {@link findMount} / {@link waitForMount}: does `mount`\n * satisfy every field present on `query`? An absent query field matches anything;\n * a present one must equal the mount's value. Kept framework-free and free of the\n * injected sandbox runtime so it is unit-testable in isolation (ways_of_working\n * §5 — separate pure logic from the effectful service).\n *\n * `name` (the human-readable mount label, R3-69) is matchable alongside the\n * `type`/`id`/`path` coordinates so an app/agent can locate a mount by the name a\n * user would recognise — e.g. `findMount({ name: 'Design notes' })` — rather than\n * an opaque `/mnt/{hash}` address or a non-unique space name guessed by hand.\n */\nexport interface MountMatchFields {\n type?: string;\n id?: string;\n path?: string;\n name?: string;\n}\n\nexport const mountMatches = (mount: MountMatchFields, query: MountMatchFields): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path) &&\n (query.name === undefined || mount.name === query.name);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBO,MAAM,eAAe,CAAC,OAAyB,WACnD,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,OAAO,UAAa,MAAM,OAAO,MAAM,QAC7C,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM;","names":[]}
|
package/dist/mountMatch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mountMatch.ts"],"sourcesContent":["/**\n * Pure predicate behind {@link findMount} / {@link waitForMount}: does `mount`\n * satisfy every field present on `query`? An absent query field matches anything;\n * a present one must equal the mount's value. Kept framework-free and free of the\n * injected sandbox runtime so it is unit-testable in isolation (ways_of_working\n * §5 — separate pure logic from the effectful service).\n *\n * `name` (the human-readable mount label, R3-69) is matchable alongside the\n * `type`/`id`/`path` coordinates so an app/agent can locate a mount by the name a\n * user would recognise — e.g. `findMount({ name: 'Design notes' })` — rather than\n * an opaque `/mnt/{hash}` address or a non-unique space name guessed by hand.\n */\nexport interface MountMatchFields {\n type?: string;\n id?: string;\n path?: string;\n name?: string;\n}\n\nexport const mountMatches = (
|
|
1
|
+
{"version":3,"sources":["../src/mountMatch.ts"],"sourcesContent":["/**\n * Pure predicate behind {@link findMount} / {@link waitForMount}: does `mount`\n * satisfy every field present on `query`? An absent query field matches anything;\n * a present one must equal the mount's value. Kept framework-free and free of the\n * injected sandbox runtime so it is unit-testable in isolation (ways_of_working\n * §5 — separate pure logic from the effectful service).\n *\n * `name` (the human-readable mount label, R3-69) is matchable alongside the\n * `type`/`id`/`path` coordinates so an app/agent can locate a mount by the name a\n * user would recognise — e.g. `findMount({ name: 'Design notes' })` — rather than\n * an opaque `/mnt/{hash}` address or a non-unique space name guessed by hand.\n */\nexport interface MountMatchFields {\n type?: string;\n id?: string;\n path?: string;\n name?: string;\n}\n\nexport const mountMatches = (mount: MountMatchFields, query: MountMatchFields): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path) &&\n (query.name === undefined || mount.name === query.name);\n"],"mappings":";AAmBO,MAAM,eAAe,CAAC,OAAyB,WACnD,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,OAAO,UAAa,MAAM,OAAO,MAAM,QAC7C,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM;","names":[]}
|
package/dist/mounts.cjs
CHANGED
|
@@ -194,7 +194,12 @@ const mount = (mountId) => requestMountInternal("mount", { mount: mountId });
|
|
|
194
194
|
const mountSpace = (query) => mount(`space:${query.spaceId}`);
|
|
195
195
|
const requestMount = () => requestMountInternal("request", {});
|
|
196
196
|
const requestSpace = requestMount;
|
|
197
|
-
const makeContentRef = (ref, opts) => ({
|
|
197
|
+
const makeContentRef = (ref, opts) => ({
|
|
198
|
+
$cap: "file",
|
|
199
|
+
mountId: ref.mountId,
|
|
200
|
+
relPath: ref.relPath,
|
|
201
|
+
mode: opts.mode
|
|
202
|
+
});
|
|
198
203
|
const resolveContentRef = async (ref) => {
|
|
199
204
|
const path = await request("resolveRef", { ref });
|
|
200
205
|
return { path };
|