@immediately-run/sdk 0.10.0 → 0.12.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/boot.cjs +12 -1
- package/dist/boot.cjs.map +1 -1
- package/dist/boot.js +14 -3
- package/dist/boot.js.map +1 -1
- package/dist/diagnostics.cjs +51 -0
- package/dist/diagnostics.cjs.map +1 -0
- package/dist/diagnostics.d.cts +43 -0
- package/dist/diagnostics.d.ts +43 -0
- package/dist/diagnostics.js +25 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/dnd.cjs +56 -0
- package/dist/dnd.cjs.map +1 -0
- package/dist/dnd.d.cts +50 -0
- package/dist/dnd.d.ts +50 -0
- package/dist/dnd.js +29 -0
- package/dist/dnd.js.map +1 -0
- package/dist/hostRuntime.cjs.map +1 -1
- package/dist/hostRuntime.d.cts +5 -0
- package/dist/hostRuntime.d.ts +5 -0
- package/dist/hostRuntime.js.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/markers.cjs +59 -0
- package/dist/markers.cjs.map +1 -0
- package/dist/markers.d.cts +21 -0
- package/dist/markers.d.ts +21 -0
- package/dist/markers.js +32 -0
- package/dist/markers.js.map +1 -0
- package/dist/region.cjs +47 -0
- package/dist/region.cjs.map +1 -0
- package/dist/region.d.cts +14 -0
- package/dist/region.d.ts +14 -0
- package/dist/region.js +22 -0
- package/dist/region.js.map +1 -0
- package/dist/testing.cjs +74 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +53 -0
- package/dist/testing.d.ts +53 -0
- package/dist/testing.js +50 -0
- package/dist/testing.js.map +1 -0
- 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 +1 -1
package/dist/boot.cjs
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(boot_exports);
|
|
|
26
26
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
27
27
|
var import_react = require("react");
|
|
28
28
|
var import_client = require("react-dom/client");
|
|
29
|
+
var import_markers = require("./markers");
|
|
29
30
|
var import_errors = require("./components/errors");
|
|
30
31
|
var import_FileRouter = require("./components/FileRouter");
|
|
31
32
|
var import_MainContent = require("./components/MainContent");
|
|
@@ -85,6 +86,13 @@ const TinkerableApp = ({ routingSpec }) => {
|
|
|
85
86
|
}, [setContext]);
|
|
86
87
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TinkerableContext.TinkerableContext, { value: context, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_routing.Router, {}) });
|
|
87
88
|
};
|
|
89
|
+
const BootMarkers = () => {
|
|
90
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
91
|
+
(0, import_markers.emitMarkerOnce)("ir.fmp");
|
|
92
|
+
(0, import_markers.emitMarkerOnce)("ir.interactive");
|
|
93
|
+
}, []);
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
88
96
|
const escapeForRegexp = (str) => str.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
|
|
89
97
|
const DEFAULT_ROUTING_SPEC = {
|
|
90
98
|
routes: [
|
|
@@ -108,7 +116,10 @@ const boot = ({
|
|
|
108
116
|
const moduleCache = new import_moduleCache.ModuleCache();
|
|
109
117
|
const root = (0, import_client.createRoot)(rootElement);
|
|
110
118
|
root.render(
|
|
111
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_moduleCache.ModuleCacheContextProvider, { moduleCache, children: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
119
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_moduleCache.ModuleCacheContextProvider, { moduleCache, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_MDXProvider.MDXProvider, { components: mdxComponents, children: [
|
|
120
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BootMarkers, {}),
|
|
121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TinkerableApp, { routingSpec })
|
|
122
|
+
] }) }) })
|
|
112
123
|
);
|
|
113
124
|
};
|
|
114
125
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/boot.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, StrictMode, useEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\n\nexport type BootProps = {\n mdxComponents?: Record<string, FC>;\n routingSpec?: RoutingSpec;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\nexport const TinkerableApp = ({ routingSpec }: { routingSpec: RoutingSpec }) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener('urlchange', ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n 'metadata-update',\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n <Router />\n </TinkerableContext>\n );\n};\n\n// from: https://stackoverflow.com/a/63838890\nconst escapeForRegexp = (str: string) => str.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: /^\\/$/, reactNode: <MainContent /> },\n {\n name: 'FileRouter',\n pattern: new RegExp(`^${escapeForRegexp(FILES_PREFIX)}(?<filename>\\/.+)$`),\n reactNode: <FileRouter />,\n },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, reactNode: <ErrorNotFound /> },\n ],\n};\n\nexport const boot = ({\n mdxComponents = DEFAULT_MDX_COMPONENTS,\n routingSpec = DEFAULT_ROUTING_SPEC,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={mdxComponents}>\n <TinkerableApp routingSpec={routingSpec} />\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, StrictMode, useEffect, useLayoutEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { emitMarkerOnce } from './markers';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\n\nexport type BootProps = {\n mdxComponents?: Record<string, FC>;\n routingSpec?: RoutingSpec;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\nexport const TinkerableApp = ({ routingSpec }: { routingSpec: RoutingSpec }) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener('urlchange', ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n 'metadata-update',\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n <Router />\n </TinkerableContext>\n );\n};\n\n// Boot marker emitter (LOAD_PROFILING_SPEC §3, R3-46). Rendered at the top of the\n// app tree so its layout effect fires on the FIRST root-render commit: that instant\n// is `ir.fmp` (the content is in the DOM, about to paint) and the baseline for\n// `ir.interactive` (the host treats a forwarded `ir.interactive` as the root-commit\n// signal and resolves `max(commit, reportReady)` — LP2-3 — so this can only ever be\n// delayed by an app's `reportReady()`, never advanced). Emitted in canonical stream\n// order (fmp then interactive); idempotent per name (StrictMode-safe). Renders null.\nconst BootMarkers = (): null => {\n useLayoutEffect(() => {\n emitMarkerOnce('ir.fmp');\n emitMarkerOnce('ir.interactive');\n }, []);\n return null;\n};\n\n// from: https://stackoverflow.com/a/63838890\nconst escapeForRegexp = (str: string) => str.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: /^\\/$/, reactNode: <MainContent /> },\n {\n name: 'FileRouter',\n pattern: new RegExp(`^${escapeForRegexp(FILES_PREFIX)}(?<filename>\\/.+)$`),\n reactNode: <FileRouter />,\n },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, reactNode: <ErrorNotFound /> },\n ],\n};\n\nexport const boot = ({\n mdxComponents = DEFAULT_MDX_COMPONENTS,\n routingSpec = DEFAULT_ROUTING_SPEC,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={mdxComponents}>\n <BootMarkers />\n <TinkerableApp routingSpec={routingSpec} />\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgFM;AAhFN,mBAAqE;AACrE,oBAA2B;AAE3B,qBAA+B;AAE/B,oBAA8B;AAC9B,wBAA2B;AAC3B,yBAA4B;AAC5B,2BAAuC;AACvC,0BAAiD;AACjD,6BAAkE;AAClE,yBAA4B;AAC5B,yBAAwD;AACxD,qBAAuB;AAGvB,0BAA4B;AAC5B,+BAAmD;AACnD,sBAA6B;AAO7B,MAAM,uBAAuB,CAAC,eAA8B,WAA0B;AACpF,WAAS,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,QAAI,cAAc,GAAG,MAAM,OAAO;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,MAAM,gBAAgB,CAAC,EAAE,YAAY,MAAoC;AAC9E,QAAM,CAAC,SAAS,UAAU,QAAI,2BAA0B,uCAAkB,WAAW,CAAC;AACtF,8BAAU,MAAM;AACd,UAAM,qBAAiB,iCAAY,aAAa,CAAC,EAAE,IAAI,MAAM;AAC3D,iBAAW,CAACA,aAAY;AACtB,cAAM,qBAAiB,mCAAcA,UAAS,GAAG;AACjD,YAAI,mBAAmBA,UAAS;AAC9B,kBAAQ;AAAA,YACN,gCAAgCA,SAAQ,gBAAgB,WAAW,OAAO,eAAe,gBAAgB,WAAW;AAAA,UACtH;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AACf,8BAAU,MAAM;AAKd,UAAM,aAAS,kDAAsB,mDAA2B,CAAC;AACjE,UAAM,cAAU;AAAA,MACd;AAAA,MACA,CAAC,EAAE,OAAO,MAA2B;AACnC;AAAA,UAAW,CAAC,gBACV,qBAAqB,YAAY,eAAe,MAAM,IAClD,cACA;AAAA,YACE,GAAG;AAAA,YACH,eAAe;AAAA;AAAA,cAEb,GAAG,YAAY;AAAA,cACf,GAAG;AAAA,YACL;AAAA,UACF;AAAA,QACN;AAAA,MACF;AAAA,MACA,OAAO;AAAA,IACT;AACA,WAAO,OAAO;AACd,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAEf,SACE,4CAAC,8CAAkB,OAAO,SACxB,sDAAC,yBAAO,GACV;AAEJ;AASA,MAAM,cAAc,MAAY;AAC9B,oCAAgB,MAAM;AACpB,uCAAe,QAAQ;AACvB,uCAAe,gBAAgB;AAAA,EACjC,GAAG,CAAC,CAAC;AACL,SAAO;AACT;AAGA,MAAM,kBAAkB,CAAC,QAAgB,IAAI,QAAQ,yBAAyB,MAAM;AAE7E,MAAM,uBAAoC;AAAA,EAC/C,QAAQ;AAAA,IACN,EAAE,MAAM,eAAe,SAAS,QAAQ,WAAW,4CAAC,kCAAY,EAAG;AAAA,IACnE;AAAA,MACE,MAAM;AAAA,MACN,SAAS,IAAI,OAAO,IAAI,gBAAgB,4BAAY,CAAC,mBAAoB;AAAA,MACzE,WAAW,4CAAC,gCAAW;AAAA,IACzB;AAAA,IACA,EAAE,MAAM,iBAAiB,SAAS,iBAAiB,WAAW,4CAAC,+BAAc,EAAG;AAAA,EAClF;AACF;AAEO,MAAM,OAAO,CAAC;AAAA,EACnB,gBAAgB;AAAA,EAChB,cAAc;AAChB,IAAe,CAAC,MAAM;AACpB,QAAM,cAAc,SAAS,eAAe,MAAM;AAClD,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,cAAc,IAAI,+BAAY;AACpC,QAAM,WAAO,0BAAW,WAAW;AACnC,OAAK;AAAA,IACH,4CAAC,2BACC,sDAAC,iDAA2B,aAC1B,uDAAC,kCAAY,YAAY,eACvB;AAAA,kDAAC,eAAY;AAAA,MACb,4CAAC,iBAAc,aAA0B;AAAA,OAC3C,GACF,GACF;AAAA,EACF;AACF;","names":["context"]}
|
package/dist/boot.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { StrictMode, useEffect, useState } from "react";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StrictMode, useEffect, useLayoutEffect, useState } from "react";
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { emitMarkerOnce } from "./markers";
|
|
4
5
|
import { ErrorNotFound } from "./components/errors";
|
|
5
6
|
import { FileRouter } from "./components/FileRouter";
|
|
6
7
|
import { MainContent } from "./components/MainContent";
|
|
@@ -60,6 +61,13 @@ const TinkerableApp = ({ routingSpec }) => {
|
|
|
60
61
|
}, [setContext]);
|
|
61
62
|
return /* @__PURE__ */ jsx(TinkerableContext, { value: context, children: /* @__PURE__ */ jsx(Router, {}) });
|
|
62
63
|
};
|
|
64
|
+
const BootMarkers = () => {
|
|
65
|
+
useLayoutEffect(() => {
|
|
66
|
+
emitMarkerOnce("ir.fmp");
|
|
67
|
+
emitMarkerOnce("ir.interactive");
|
|
68
|
+
}, []);
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
63
71
|
const escapeForRegexp = (str) => str.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
|
|
64
72
|
const DEFAULT_ROUTING_SPEC = {
|
|
65
73
|
routes: [
|
|
@@ -83,7 +91,10 @@ const boot = ({
|
|
|
83
91
|
const moduleCache = new ModuleCache();
|
|
84
92
|
const root = createRoot(rootElement);
|
|
85
93
|
root.render(
|
|
86
|
-
/* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(ModuleCacheContextProvider, { moduleCache, children: /* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(ModuleCacheContextProvider, { moduleCache, children: /* @__PURE__ */ jsxs(MDXProvider, { components: mdxComponents, children: [
|
|
95
|
+
/* @__PURE__ */ jsx(BootMarkers, {}),
|
|
96
|
+
/* @__PURE__ */ jsx(TinkerableApp, { routingSpec })
|
|
97
|
+
] }) }) })
|
|
87
98
|
);
|
|
88
99
|
};
|
|
89
100
|
export {
|
package/dist/boot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, StrictMode, useEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\n\nexport type BootProps = {\n mdxComponents?: Record<string, FC>;\n routingSpec?: RoutingSpec;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\nexport const TinkerableApp = ({ routingSpec }: { routingSpec: RoutingSpec }) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener('urlchange', ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n 'metadata-update',\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n <Router />\n </TinkerableContext>\n );\n};\n\n// from: https://stackoverflow.com/a/63838890\nconst escapeForRegexp = (str: string) => str.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: /^\\/$/, reactNode: <MainContent /> },\n {\n name: 'FileRouter',\n pattern: new RegExp(`^${escapeForRegexp(FILES_PREFIX)}(?<filename>\\/.+)$`),\n reactNode: <FileRouter />,\n },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, reactNode: <ErrorNotFound /> },\n ],\n};\n\nexport const boot = ({\n mdxComponents = DEFAULT_MDX_COMPONENTS,\n routingSpec = DEFAULT_ROUTING_SPEC,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={mdxComponents}>\n <TinkerableApp routingSpec={routingSpec} />\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, StrictMode, useEffect, useLayoutEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { emitMarkerOnce } from './markers';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\n\nexport type BootProps = {\n mdxComponents?: Record<string, FC>;\n routingSpec?: RoutingSpec;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\nexport const TinkerableApp = ({ routingSpec }: { routingSpec: RoutingSpec }) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener('urlchange', ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n 'metadata-update',\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n <Router />\n </TinkerableContext>\n );\n};\n\n// Boot marker emitter (LOAD_PROFILING_SPEC §3, R3-46). Rendered at the top of the\n// app tree so its layout effect fires on the FIRST root-render commit: that instant\n// is `ir.fmp` (the content is in the DOM, about to paint) and the baseline for\n// `ir.interactive` (the host treats a forwarded `ir.interactive` as the root-commit\n// signal and resolves `max(commit, reportReady)` — LP2-3 — so this can only ever be\n// delayed by an app's `reportReady()`, never advanced). Emitted in canonical stream\n// order (fmp then interactive); idempotent per name (StrictMode-safe). Renders null.\nconst BootMarkers = (): null => {\n useLayoutEffect(() => {\n emitMarkerOnce('ir.fmp');\n emitMarkerOnce('ir.interactive');\n }, []);\n return null;\n};\n\n// from: https://stackoverflow.com/a/63838890\nconst escapeForRegexp = (str: string) => str.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: /^\\/$/, reactNode: <MainContent /> },\n {\n name: 'FileRouter',\n pattern: new RegExp(`^${escapeForRegexp(FILES_PREFIX)}(?<filename>\\/.+)$`),\n reactNode: <FileRouter />,\n },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, reactNode: <ErrorNotFound /> },\n ],\n};\n\nexport const boot = ({\n mdxComponents = DEFAULT_MDX_COMPONENTS,\n routingSpec = DEFAULT_ROUTING_SPEC,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={mdxComponents}>\n <BootMarkers />\n <TinkerableApp routingSpec={routingSpec} />\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":"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","names":["context"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var diagnostics_exports = {};
|
|
20
|
+
__export(diagnostics_exports, {
|
|
21
|
+
getDiagnostics: () => getDiagnostics,
|
|
22
|
+
onDiagnosticsChange: () => onDiagnosticsChange,
|
|
23
|
+
useDiagnostics: () => useDiagnostics
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(diagnostics_exports);
|
|
26
|
+
var import_pushChannel = require("./pushChannel");
|
|
27
|
+
const EMPTY = { buildErrors: [], consoleEntries: [], provenance: null };
|
|
28
|
+
const channel = (0, import_pushChannel.createPushChannel)({
|
|
29
|
+
pushType: "diagnostics",
|
|
30
|
+
requestType: "request-diagnostics",
|
|
31
|
+
initial: EMPTY,
|
|
32
|
+
parse: (msg) => {
|
|
33
|
+
if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return void 0;
|
|
34
|
+
const provenance = msg.provenance && typeof msg.provenance === "object" ? msg.provenance : null;
|
|
35
|
+
return {
|
|
36
|
+
buildErrors: msg.buildErrors,
|
|
37
|
+
consoleEntries: msg.consoleEntries,
|
|
38
|
+
provenance
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const getDiagnostics = () => channel.get();
|
|
43
|
+
const onDiagnosticsChange = (listener) => channel.onChange(listener);
|
|
44
|
+
const useDiagnostics = () => channel.use();
|
|
45
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
46
|
+
0 && (module.exports = {
|
|
47
|
+
getDiagnostics,
|
|
48
|
+
onDiagnosticsChange,
|
|
49
|
+
useDiagnostics
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=diagnostics.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// The `diagnostics:read` channel — app-facing surface (LLM_AND_AGENTS_SPEC §3.3/§4,\n// D4; roadmap R3-74 / P3-72).\n//\n// A *sibling* agent app (one holding `diagnostics:read`) observes the result of its\n// edits to the PREVIEWED app: the build/transpile errors from the sandbox bundler\n// and the previewed app's captured `console.*`. This is the in-browser analogue of\n// a local coding agent reading compiler/test output (P3-73's `get_diagnostics()`\n// tool). Read-only and scoped host-side to the paired previewed app's OWN\n// diagnostics — never another app's (the host channel projection enforces it; the\n// previewed app itself holds no `diagnostics:read`, so it is origin-excluded).\n//\n// Recipe-A push channel, identical get/onChange/use trio as `secrets`/`catalog`:\n// the host pushes `diagnostics` on change and answers a `request-diagnostics` poll,\n// gated per-frame by the read ACL. Inert until the host wires the channel\n// (site-main `channelBridge`); the contract ships here so the agent app (P3-73) can\n// be written against it.\nimport { createPushChannel } from './pushChannel';\n\n/** One build/transpile error from the sandbox bundler's compile of the previewed\n * app. `path` is repo-relative (leading slash) when the error is file-located. */\nexport interface BuildError {\n message: string;\n path?: string;\n line?: number;\n column?: number;\n}\n\nexport type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';\n\n/** One captured `console.*` entry from the previewed app. The host renders the\n * console arguments to text host-side — the agent never receives live object\n * handles across the boundary. */\nexport interface ConsoleEntry {\n level: ConsoleLevel;\n text: string;\n /** Host-side timestamp (ms) at capture. */\n at: number;\n}\n\n/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this\n * snapshot describes, so a consumer can tell stale output from fresh and never\n * conflates two apps' diagnostics. `null` until a first compile is observed. */\nexport interface DiagnosticsProvenance {\n /** The previewed app's stable key (`provider/ns/repo`). */\n appKey?: string;\n /** Monotonic id of the compile that produced these build errors. */\n compileId?: string;\n}\n\nexport interface Diagnostics {\n buildErrors: BuildError[];\n consoleEntries: ConsoleEntry[];\n provenance: DiagnosticsProvenance | null;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `diagnostics:read`): an empty, provenance-less snapshot. */\nconst EMPTY: Diagnostics = { buildErrors: [], consoleEntries: [], provenance: null };\n\nconst channel = createPushChannel<Diagnostics>({\n pushType: 'diagnostics',\n requestType: 'request-diagnostics',\n initial: EMPTY,\n parse: (msg) => {\n // Require both arrays; tolerate an absent/partial provenance. A malformed push\n // is ignored (returns undefined) so the last good snapshot stands.\n if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return undefined;\n const provenance =\n msg.provenance && typeof msg.provenance === 'object'\n ? (msg.provenance as DiagnosticsProvenance)\n : null;\n return {\n buildErrors: msg.buildErrors as BuildError[],\n consoleEntries: msg.consoleEntries as ConsoleEntry[],\n provenance,\n };\n },\n});\n\n/** One-off read of the previewed app's current diagnostics. Returns the empty\n * snapshot until the host answers (or if the app lacks `diagnostics:read`). Use\n * {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */\nexport const getDiagnostics = (): Diagnostics => channel.get();\n\n/** Subscribe to diagnostics. Invoked immediately with the current value, then on\n * every host push. Returns an unsubscribe. */\nexport const onDiagnosticsChange = (listener: (d: Diagnostics) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current diagnostics, re-rendering on every change. */\nexport const useDiagnostics = (): Diagnostics => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,yBAAkC;AAyClC,MAAM,QAAqB,EAAE,aAAa,CAAC,GAAG,gBAAgB,CAAC,GAAG,YAAY,KAAK;AAEnF,MAAM,cAAU,sCAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAQ;AAGd,QAAI,CAAC,MAAM,QAAQ,IAAI,WAAW,KAAK,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAClF,UAAM,aACJ,IAAI,cAAc,OAAO,IAAI,eAAe,WACvC,IAAI,aACL;AACN,WAAO;AAAA,MACL,aAAa,IAAI;AAAA,MACjB,gBAAgB,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAKM,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;AAItD,MAAM,sBAAsB,CAAC,aAClC,QAAQ,SAAS,QAAQ;AAGpB,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** One build/transpile error from the sandbox bundler's compile of the previewed
|
|
2
|
+
* app. `path` is repo-relative (leading slash) when the error is file-located. */
|
|
3
|
+
interface BuildError {
|
|
4
|
+
message: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
column?: number;
|
|
8
|
+
}
|
|
9
|
+
type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
10
|
+
/** One captured `console.*` entry from the previewed app. The host renders the
|
|
11
|
+
* console arguments to text host-side — the agent never receives live object
|
|
12
|
+
* handles across the boundary. */
|
|
13
|
+
interface ConsoleEntry {
|
|
14
|
+
level: ConsoleLevel;
|
|
15
|
+
text: string;
|
|
16
|
+
/** Host-side timestamp (ms) at capture. */
|
|
17
|
+
at: number;
|
|
18
|
+
}
|
|
19
|
+
/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this
|
|
20
|
+
* snapshot describes, so a consumer can tell stale output from fresh and never
|
|
21
|
+
* conflates two apps' diagnostics. `null` until a first compile is observed. */
|
|
22
|
+
interface DiagnosticsProvenance {
|
|
23
|
+
/** The previewed app's stable key (`provider/ns/repo`). */
|
|
24
|
+
appKey?: string;
|
|
25
|
+
/** Monotonic id of the compile that produced these build errors. */
|
|
26
|
+
compileId?: string;
|
|
27
|
+
}
|
|
28
|
+
interface Diagnostics {
|
|
29
|
+
buildErrors: BuildError[];
|
|
30
|
+
consoleEntries: ConsoleEntry[];
|
|
31
|
+
provenance: DiagnosticsProvenance | null;
|
|
32
|
+
}
|
|
33
|
+
/** One-off read of the previewed app's current diagnostics. Returns the empty
|
|
34
|
+
* snapshot until the host answers (or if the app lacks `diagnostics:read`). Use
|
|
35
|
+
* {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */
|
|
36
|
+
declare const getDiagnostics: () => Diagnostics;
|
|
37
|
+
/** Subscribe to diagnostics. Invoked immediately with the current value, then on
|
|
38
|
+
* every host push. Returns an unsubscribe. */
|
|
39
|
+
declare const onDiagnosticsChange: (listener: (d: Diagnostics) => void) => (() => void);
|
|
40
|
+
/** React hook: the current diagnostics, re-rendering on every change. */
|
|
41
|
+
declare const useDiagnostics: () => Diagnostics;
|
|
42
|
+
|
|
43
|
+
export { type BuildError, type ConsoleEntry, type ConsoleLevel, type Diagnostics, type DiagnosticsProvenance, getDiagnostics, onDiagnosticsChange, useDiagnostics };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** One build/transpile error from the sandbox bundler's compile of the previewed
|
|
2
|
+
* app. `path` is repo-relative (leading slash) when the error is file-located. */
|
|
3
|
+
interface BuildError {
|
|
4
|
+
message: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
column?: number;
|
|
8
|
+
}
|
|
9
|
+
type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
10
|
+
/** One captured `console.*` entry from the previewed app. The host renders the
|
|
11
|
+
* console arguments to text host-side — the agent never receives live object
|
|
12
|
+
* handles across the boundary. */
|
|
13
|
+
interface ConsoleEntry {
|
|
14
|
+
level: ConsoleLevel;
|
|
15
|
+
text: string;
|
|
16
|
+
/** Host-side timestamp (ms) at capture. */
|
|
17
|
+
at: number;
|
|
18
|
+
}
|
|
19
|
+
/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this
|
|
20
|
+
* snapshot describes, so a consumer can tell stale output from fresh and never
|
|
21
|
+
* conflates two apps' diagnostics. `null` until a first compile is observed. */
|
|
22
|
+
interface DiagnosticsProvenance {
|
|
23
|
+
/** The previewed app's stable key (`provider/ns/repo`). */
|
|
24
|
+
appKey?: string;
|
|
25
|
+
/** Monotonic id of the compile that produced these build errors. */
|
|
26
|
+
compileId?: string;
|
|
27
|
+
}
|
|
28
|
+
interface Diagnostics {
|
|
29
|
+
buildErrors: BuildError[];
|
|
30
|
+
consoleEntries: ConsoleEntry[];
|
|
31
|
+
provenance: DiagnosticsProvenance | null;
|
|
32
|
+
}
|
|
33
|
+
/** One-off read of the previewed app's current diagnostics. Returns the empty
|
|
34
|
+
* snapshot until the host answers (or if the app lacks `diagnostics:read`). Use
|
|
35
|
+
* {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */
|
|
36
|
+
declare const getDiagnostics: () => Diagnostics;
|
|
37
|
+
/** Subscribe to diagnostics. Invoked immediately with the current value, then on
|
|
38
|
+
* every host push. Returns an unsubscribe. */
|
|
39
|
+
declare const onDiagnosticsChange: (listener: (d: Diagnostics) => void) => (() => void);
|
|
40
|
+
/** React hook: the current diagnostics, re-rendering on every change. */
|
|
41
|
+
declare const useDiagnostics: () => Diagnostics;
|
|
42
|
+
|
|
43
|
+
export { type BuildError, type ConsoleEntry, type ConsoleLevel, type Diagnostics, type DiagnosticsProvenance, getDiagnostics, onDiagnosticsChange, useDiagnostics };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createPushChannel } from "./pushChannel";
|
|
2
|
+
const EMPTY = { buildErrors: [], consoleEntries: [], provenance: null };
|
|
3
|
+
const channel = createPushChannel({
|
|
4
|
+
pushType: "diagnostics",
|
|
5
|
+
requestType: "request-diagnostics",
|
|
6
|
+
initial: EMPTY,
|
|
7
|
+
parse: (msg) => {
|
|
8
|
+
if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return void 0;
|
|
9
|
+
const provenance = msg.provenance && typeof msg.provenance === "object" ? msg.provenance : null;
|
|
10
|
+
return {
|
|
11
|
+
buildErrors: msg.buildErrors,
|
|
12
|
+
consoleEntries: msg.consoleEntries,
|
|
13
|
+
provenance
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
const getDiagnostics = () => channel.get();
|
|
18
|
+
const onDiagnosticsChange = (listener) => channel.onChange(listener);
|
|
19
|
+
const useDiagnostics = () => channel.use();
|
|
20
|
+
export {
|
|
21
|
+
getDiagnostics,
|
|
22
|
+
onDiagnosticsChange,
|
|
23
|
+
useDiagnostics
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// The `diagnostics:read` channel — app-facing surface (LLM_AND_AGENTS_SPEC §3.3/§4,\n// D4; roadmap R3-74 / P3-72).\n//\n// A *sibling* agent app (one holding `diagnostics:read`) observes the result of its\n// edits to the PREVIEWED app: the build/transpile errors from the sandbox bundler\n// and the previewed app's captured `console.*`. This is the in-browser analogue of\n// a local coding agent reading compiler/test output (P3-73's `get_diagnostics()`\n// tool). Read-only and scoped host-side to the paired previewed app's OWN\n// diagnostics — never another app's (the host channel projection enforces it; the\n// previewed app itself holds no `diagnostics:read`, so it is origin-excluded).\n//\n// Recipe-A push channel, identical get/onChange/use trio as `secrets`/`catalog`:\n// the host pushes `diagnostics` on change and answers a `request-diagnostics` poll,\n// gated per-frame by the read ACL. Inert until the host wires the channel\n// (site-main `channelBridge`); the contract ships here so the agent app (P3-73) can\n// be written against it.\nimport { createPushChannel } from './pushChannel';\n\n/** One build/transpile error from the sandbox bundler's compile of the previewed\n * app. `path` is repo-relative (leading slash) when the error is file-located. */\nexport interface BuildError {\n message: string;\n path?: string;\n line?: number;\n column?: number;\n}\n\nexport type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';\n\n/** One captured `console.*` entry from the previewed app. The host renders the\n * console arguments to text host-side — the agent never receives live object\n * handles across the boundary. */\nexport interface ConsoleEntry {\n level: ConsoleLevel;\n text: string;\n /** Host-side timestamp (ms) at capture. */\n at: number;\n}\n\n/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this\n * snapshot describes, so a consumer can tell stale output from fresh and never\n * conflates two apps' diagnostics. `null` until a first compile is observed. */\nexport interface DiagnosticsProvenance {\n /** The previewed app's stable key (`provider/ns/repo`). */\n appKey?: string;\n /** Monotonic id of the compile that produced these build errors. */\n compileId?: string;\n}\n\nexport interface Diagnostics {\n buildErrors: BuildError[];\n consoleEntries: ConsoleEntry[];\n provenance: DiagnosticsProvenance | null;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `diagnostics:read`): an empty, provenance-less snapshot. */\nconst EMPTY: Diagnostics = { buildErrors: [], consoleEntries: [], provenance: null };\n\nconst channel = createPushChannel<Diagnostics>({\n pushType: 'diagnostics',\n requestType: 'request-diagnostics',\n initial: EMPTY,\n parse: (msg) => {\n // Require both arrays; tolerate an absent/partial provenance. A malformed push\n // is ignored (returns undefined) so the last good snapshot stands.\n if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return undefined;\n const provenance =\n msg.provenance && typeof msg.provenance === 'object'\n ? (msg.provenance as DiagnosticsProvenance)\n : null;\n return {\n buildErrors: msg.buildErrors as BuildError[],\n consoleEntries: msg.consoleEntries as ConsoleEntry[],\n provenance,\n };\n },\n});\n\n/** One-off read of the previewed app's current diagnostics. Returns the empty\n * snapshot until the host answers (or if the app lacks `diagnostics:read`). Use\n * {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */\nexport const getDiagnostics = (): Diagnostics => channel.get();\n\n/** Subscribe to diagnostics. Invoked immediately with the current value, then on\n * every host push. Returns an unsubscribe. */\nexport const onDiagnosticsChange = (listener: (d: Diagnostics) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current diagnostics, re-rendering on every change. */\nexport const useDiagnostics = (): Diagnostics => channel.use();\n"],"mappings":"AAgBA,SAAS,yBAAyB;AAyClC,MAAM,QAAqB,EAAE,aAAa,CAAC,GAAG,gBAAgB,CAAC,GAAG,YAAY,KAAK;AAEnF,MAAM,UAAU,kBAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAQ;AAGd,QAAI,CAAC,MAAM,QAAQ,IAAI,WAAW,KAAK,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAClF,UAAM,aACJ,IAAI,cAAc,OAAO,IAAI,eAAe,WACvC,IAAI,aACL;AACN,WAAO;AAAA,MACL,aAAa,IAAI;AAAA,MACjB,gBAAgB,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAKM,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;AAItD,MAAM,sBAAsB,CAAC,aAClC,QAAQ,SAAS,QAAQ;AAGpB,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;","names":[]}
|
package/dist/dnd.cjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var dnd_exports = {};
|
|
20
|
+
__export(dnd_exports, {
|
|
21
|
+
cancelItemDrag: () => cancelItemDrag,
|
|
22
|
+
onItemDrop: () => onItemDrop,
|
|
23
|
+
startItemDrag: () => startItemDrag,
|
|
24
|
+
useDroppedItem: () => useDroppedItem
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(dnd_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
var import_sandboxUtils = require("./sandboxUtils");
|
|
29
|
+
const startItemDrag = async (item) => {
|
|
30
|
+
const res = await (0, import_sandboxUtils.protocolRequest)("dnd", "startDrag", [item]);
|
|
31
|
+
if (!res || res.ok !== true) {
|
|
32
|
+
const err = new Error(res?.message ?? "dnd startDrag failed");
|
|
33
|
+
err.code = res?.code ?? "unknown";
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const cancelItemDrag = () => {
|
|
38
|
+
(0, import_sandboxUtils.sendMessage)("dnd-cancel", {});
|
|
39
|
+
};
|
|
40
|
+
const onItemDrop = (listener) => (0, import_sandboxUtils.addListener)(
|
|
41
|
+
"dropped-item",
|
|
42
|
+
(m) => listener({ item: m.item, from: m.from, position: m.position })
|
|
43
|
+
);
|
|
44
|
+
const useDroppedItem = () => {
|
|
45
|
+
const [dropped, setDropped] = (0, import_react.useState)(null);
|
|
46
|
+
(0, import_react.useEffect)(() => onItemDrop(setDropped), []);
|
|
47
|
+
return dropped;
|
|
48
|
+
};
|
|
49
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
50
|
+
0 && (module.exports = {
|
|
51
|
+
cancelItemDrag,
|
|
52
|
+
onItemDrop,
|
|
53
|
+
startItemDrag,
|
|
54
|
+
useDroppedItem
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=dnd.cjs.map
|
package/dist/dnd.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dnd.ts"],"sourcesContent":["// Cross-app drag-out (FILE_EXPLORER_SPEC §7; UI_AS_APPS_SPEC §2 host-mediated).\n//\n// Native HTML5 drag-and-drop does NOT cross the sandboxed cross-origin iframe\n// boundary, and pointer events inside one app's iframe never reach the host or a\n// sibling. So a drag that STARTS in one app (the file explorer) and ENDS over\n// another (the previewed app) can only be mediated by the host (the TCB). This\n// module is the SDK surface for that one net-new platform primitive:\n//\n// - SOURCE side (the file explorer, needs the first-party `dnd:source` cap):\n// `startItemDrag(item)` asks the host to begin a host-mediated drag carrying a\n// file/dir reference (+ optional inlined bytes for a small file). The host draws\n// the trusted drag ghost, tracks the pointer across regions, and on drop over the\n// preview delivers the item to that app. `cancelItemDrag()` aborts.\n// - RECEIVER side (the previewed app, NO new grant — it opts in by subscribing):\n// `onItemDrop(cb)` / `useDroppedItem()` deliver the dropped item with host-attached,\n// unspoofable provenance (`from` = source region id) and the drop position.\n//\n// The payload is UNTRUSTED app data (CLAUDE.md §5): the host attaches `from`; the app\n// validates everything else. v1 inlines bytes only for small files (the source can\n// only relay data it can already read — no new read authority is minted).\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\n\n/** A file/dir being dragged out of an app. `bytes` is present only for a small file\n * the source chose to inline (transferred zero-copy); a dir or an over-cap file\n * carries the reference only (`kind`/`name`/`mountId`/`relPath`). */\nexport interface DraggableItem {\n kind: 'file' | 'dir';\n /** Basename — display only. */\n name: string;\n /** Which mounted filesystem the item lives in. */\n mountId: string;\n /** Path within that mount (leading slash, no `..`). */\n relPath: string;\n /** Optional inlined content for a small file. */\n bytes?: Uint8Array;\n}\n\n/** An item dropped onto THIS app by a host-mediated cross-app drag. */\nexport interface DroppedItem {\n /** The dragged item (`bytes` present iff the source inlined them). */\n item: DraggableItem;\n /** Host-attached source region id — unspoofable (T19), like an `ipc` `from`. */\n from: string;\n /** Drop point in this app's viewport. */\n position: { x: number; y: number };\n}\n\n/** An error from {@link startItemDrag}, carrying a machine-readable `.code`. */\nexport interface ItemDragError extends Error {\n code:\n | 'forbidden' // the frame lacks the first-party `dnd:source` capability\n | 'invalid-params' // the item was malformed (empty path / `..` / URI / bad kind)\n | 'too-large' // inlined `bytes` exceed the host's size limit\n | 'rate-limited' // too many drags started too fast (capacity-class, fail-open)\n | 'unknown';\n}\n\n/**\n * Begin a host-mediated drag of `item` out of this app. Resolves once the host has\n * taken over the drag (drawn the ghost, installed the pointer-capture layer); rejects\n * with an {@link ItemDragError} if this app may not initiate drags (`forbidden`) or the\n * item is invalid. Only a first-party chrome app holding `dnd:source` may call this — a\n * previewed/third-party app is refused at the gate (it must not synthesize drags into\n * sibling apps).\n */\nexport const startItemDrag = async (item: DraggableItem): Promise<void> => {\n const res = (await protocolRequest('dnd', 'startDrag', [item])) as\n | { ok: true }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'dnd startDrag failed') as ItemDragError;\n err.code = (res?.code as ItemDragError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Abort an in-progress host-mediated drag this app started (e.g. the user pressed\n * Escape, or the gesture was cancelled). Best-effort and fire-and-forget. */\nexport const cancelItemDrag = (): void => {\n sendMessage('dnd-cancel', {});\n};\n\n/** Subscribe to items dropped onto this app by a host-mediated cross-app drag.\n * Returns an unsubscribe fn. Subscribing is the opt-in: an app that never subscribes\n * receives nothing (the host shows a \"not accepted\" cue and the drop is a no-op). */\nexport const onItemDrop = (listener: (d: DroppedItem) => void): (() => void) =>\n addListener('dropped-item', (m: { item: DraggableItem; from: string; position: { x: number; y: number } }) =>\n listener({ item: m.item, from: m.from, position: m.position }),\n );\n\n/** React hook: the most recently dropped item (or `null`). */\nexport const useDroppedItem = (): DroppedItem | null => {\n const [dropped, setDropped] = useState<DroppedItem | null>(null);\n useEffect(() => onItemDrop(setDropped), []);\n return dropped;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA,mBAAoC;AACpC,0BAA0D;AA6CnD,MAAM,gBAAgB,OAAO,SAAuC;AACzE,QAAM,MAAO,UAAM,qCAAgB,OAAO,aAAa,CAAC,IAAI,CAAC;AAI7D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,sBAAsB;AAC5D,QAAI,OAAQ,KAAK,QAAkC;AACnD,UAAM;AAAA,EACR;AACF;AAIO,MAAM,iBAAiB,MAAY;AACxC,uCAAY,cAAc,CAAC,CAAC;AAC9B;AAKO,MAAM,aAAa,CAAC,iBACzB;AAAA,EAAY;AAAA,EAAgB,CAAC,MAC3B,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,CAAC;AAC/D;AAGK,MAAM,iBAAiB,MAA0B;AACtD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAA6B,IAAI;AAC/D,8BAAU,MAAM,WAAW,UAAU,GAAG,CAAC,CAAC;AAC1C,SAAO;AACT;","names":[]}
|
package/dist/dnd.d.cts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** A file/dir being dragged out of an app. `bytes` is present only for a small file
|
|
2
|
+
* the source chose to inline (transferred zero-copy); a dir or an over-cap file
|
|
3
|
+
* carries the reference only (`kind`/`name`/`mountId`/`relPath`). */
|
|
4
|
+
interface DraggableItem {
|
|
5
|
+
kind: 'file' | 'dir';
|
|
6
|
+
/** Basename — display only. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Which mounted filesystem the item lives in. */
|
|
9
|
+
mountId: string;
|
|
10
|
+
/** Path within that mount (leading slash, no `..`). */
|
|
11
|
+
relPath: string;
|
|
12
|
+
/** Optional inlined content for a small file. */
|
|
13
|
+
bytes?: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
/** An item dropped onto THIS app by a host-mediated cross-app drag. */
|
|
16
|
+
interface DroppedItem {
|
|
17
|
+
/** The dragged item (`bytes` present iff the source inlined them). */
|
|
18
|
+
item: DraggableItem;
|
|
19
|
+
/** Host-attached source region id — unspoofable (T19), like an `ipc` `from`. */
|
|
20
|
+
from: string;
|
|
21
|
+
/** Drop point in this app's viewport. */
|
|
22
|
+
position: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** An error from {@link startItemDrag}, carrying a machine-readable `.code`. */
|
|
28
|
+
interface ItemDragError extends Error {
|
|
29
|
+
code: 'forbidden' | 'invalid-params' | 'too-large' | 'rate-limited' | 'unknown';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Begin a host-mediated drag of `item` out of this app. Resolves once the host has
|
|
33
|
+
* taken over the drag (drawn the ghost, installed the pointer-capture layer); rejects
|
|
34
|
+
* with an {@link ItemDragError} if this app may not initiate drags (`forbidden`) or the
|
|
35
|
+
* item is invalid. Only a first-party chrome app holding `dnd:source` may call this — a
|
|
36
|
+
* previewed/third-party app is refused at the gate (it must not synthesize drags into
|
|
37
|
+
* sibling apps).
|
|
38
|
+
*/
|
|
39
|
+
declare const startItemDrag: (item: DraggableItem) => Promise<void>;
|
|
40
|
+
/** Abort an in-progress host-mediated drag this app started (e.g. the user pressed
|
|
41
|
+
* Escape, or the gesture was cancelled). Best-effort and fire-and-forget. */
|
|
42
|
+
declare const cancelItemDrag: () => void;
|
|
43
|
+
/** Subscribe to items dropped onto this app by a host-mediated cross-app drag.
|
|
44
|
+
* Returns an unsubscribe fn. Subscribing is the opt-in: an app that never subscribes
|
|
45
|
+
* receives nothing (the host shows a "not accepted" cue and the drop is a no-op). */
|
|
46
|
+
declare const onItemDrop: (listener: (d: DroppedItem) => void) => (() => void);
|
|
47
|
+
/** React hook: the most recently dropped item (or `null`). */
|
|
48
|
+
declare const useDroppedItem: () => DroppedItem | null;
|
|
49
|
+
|
|
50
|
+
export { type DraggableItem, type DroppedItem, type ItemDragError, cancelItemDrag, onItemDrop, startItemDrag, useDroppedItem };
|
package/dist/dnd.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** A file/dir being dragged out of an app. `bytes` is present only for a small file
|
|
2
|
+
* the source chose to inline (transferred zero-copy); a dir or an over-cap file
|
|
3
|
+
* carries the reference only (`kind`/`name`/`mountId`/`relPath`). */
|
|
4
|
+
interface DraggableItem {
|
|
5
|
+
kind: 'file' | 'dir';
|
|
6
|
+
/** Basename — display only. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Which mounted filesystem the item lives in. */
|
|
9
|
+
mountId: string;
|
|
10
|
+
/** Path within that mount (leading slash, no `..`). */
|
|
11
|
+
relPath: string;
|
|
12
|
+
/** Optional inlined content for a small file. */
|
|
13
|
+
bytes?: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
/** An item dropped onto THIS app by a host-mediated cross-app drag. */
|
|
16
|
+
interface DroppedItem {
|
|
17
|
+
/** The dragged item (`bytes` present iff the source inlined them). */
|
|
18
|
+
item: DraggableItem;
|
|
19
|
+
/** Host-attached source region id — unspoofable (T19), like an `ipc` `from`. */
|
|
20
|
+
from: string;
|
|
21
|
+
/** Drop point in this app's viewport. */
|
|
22
|
+
position: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** An error from {@link startItemDrag}, carrying a machine-readable `.code`. */
|
|
28
|
+
interface ItemDragError extends Error {
|
|
29
|
+
code: 'forbidden' | 'invalid-params' | 'too-large' | 'rate-limited' | 'unknown';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Begin a host-mediated drag of `item` out of this app. Resolves once the host has
|
|
33
|
+
* taken over the drag (drawn the ghost, installed the pointer-capture layer); rejects
|
|
34
|
+
* with an {@link ItemDragError} if this app may not initiate drags (`forbidden`) or the
|
|
35
|
+
* item is invalid. Only a first-party chrome app holding `dnd:source` may call this — a
|
|
36
|
+
* previewed/third-party app is refused at the gate (it must not synthesize drags into
|
|
37
|
+
* sibling apps).
|
|
38
|
+
*/
|
|
39
|
+
declare const startItemDrag: (item: DraggableItem) => Promise<void>;
|
|
40
|
+
/** Abort an in-progress host-mediated drag this app started (e.g. the user pressed
|
|
41
|
+
* Escape, or the gesture was cancelled). Best-effort and fire-and-forget. */
|
|
42
|
+
declare const cancelItemDrag: () => void;
|
|
43
|
+
/** Subscribe to items dropped onto this app by a host-mediated cross-app drag.
|
|
44
|
+
* Returns an unsubscribe fn. Subscribing is the opt-in: an app that never subscribes
|
|
45
|
+
* receives nothing (the host shows a "not accepted" cue and the drop is a no-op). */
|
|
46
|
+
declare const onItemDrop: (listener: (d: DroppedItem) => void) => (() => void);
|
|
47
|
+
/** React hook: the most recently dropped item (or `null`). */
|
|
48
|
+
declare const useDroppedItem: () => DroppedItem | null;
|
|
49
|
+
|
|
50
|
+
export { type DraggableItem, type DroppedItem, type ItemDragError, cancelItemDrag, onItemDrop, startItemDrag, useDroppedItem };
|
package/dist/dnd.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { protocolRequest, sendMessage, addListener } from "./sandboxUtils";
|
|
3
|
+
const startItemDrag = async (item) => {
|
|
4
|
+
const res = await protocolRequest("dnd", "startDrag", [item]);
|
|
5
|
+
if (!res || res.ok !== true) {
|
|
6
|
+
const err = new Error(res?.message ?? "dnd startDrag failed");
|
|
7
|
+
err.code = res?.code ?? "unknown";
|
|
8
|
+
throw err;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const cancelItemDrag = () => {
|
|
12
|
+
sendMessage("dnd-cancel", {});
|
|
13
|
+
};
|
|
14
|
+
const onItemDrop = (listener) => addListener(
|
|
15
|
+
"dropped-item",
|
|
16
|
+
(m) => listener({ item: m.item, from: m.from, position: m.position })
|
|
17
|
+
);
|
|
18
|
+
const useDroppedItem = () => {
|
|
19
|
+
const [dropped, setDropped] = useState(null);
|
|
20
|
+
useEffect(() => onItemDrop(setDropped), []);
|
|
21
|
+
return dropped;
|
|
22
|
+
};
|
|
23
|
+
export {
|
|
24
|
+
cancelItemDrag,
|
|
25
|
+
onItemDrop,
|
|
26
|
+
startItemDrag,
|
|
27
|
+
useDroppedItem
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=dnd.js.map
|
package/dist/dnd.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dnd.ts"],"sourcesContent":["// Cross-app drag-out (FILE_EXPLORER_SPEC §7; UI_AS_APPS_SPEC §2 host-mediated).\n//\n// Native HTML5 drag-and-drop does NOT cross the sandboxed cross-origin iframe\n// boundary, and pointer events inside one app's iframe never reach the host or a\n// sibling. So a drag that STARTS in one app (the file explorer) and ENDS over\n// another (the previewed app) can only be mediated by the host (the TCB). This\n// module is the SDK surface for that one net-new platform primitive:\n//\n// - SOURCE side (the file explorer, needs the first-party `dnd:source` cap):\n// `startItemDrag(item)` asks the host to begin a host-mediated drag carrying a\n// file/dir reference (+ optional inlined bytes for a small file). The host draws\n// the trusted drag ghost, tracks the pointer across regions, and on drop over the\n// preview delivers the item to that app. `cancelItemDrag()` aborts.\n// - RECEIVER side (the previewed app, NO new grant — it opts in by subscribing):\n// `onItemDrop(cb)` / `useDroppedItem()` deliver the dropped item with host-attached,\n// unspoofable provenance (`from` = source region id) and the drop position.\n//\n// The payload is UNTRUSTED app data (CLAUDE.md §5): the host attaches `from`; the app\n// validates everything else. v1 inlines bytes only for small files (the source can\n// only relay data it can already read — no new read authority is minted).\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\n\n/** A file/dir being dragged out of an app. `bytes` is present only for a small file\n * the source chose to inline (transferred zero-copy); a dir or an over-cap file\n * carries the reference only (`kind`/`name`/`mountId`/`relPath`). */\nexport interface DraggableItem {\n kind: 'file' | 'dir';\n /** Basename — display only. */\n name: string;\n /** Which mounted filesystem the item lives in. */\n mountId: string;\n /** Path within that mount (leading slash, no `..`). */\n relPath: string;\n /** Optional inlined content for a small file. */\n bytes?: Uint8Array;\n}\n\n/** An item dropped onto THIS app by a host-mediated cross-app drag. */\nexport interface DroppedItem {\n /** The dragged item (`bytes` present iff the source inlined them). */\n item: DraggableItem;\n /** Host-attached source region id — unspoofable (T19), like an `ipc` `from`. */\n from: string;\n /** Drop point in this app's viewport. */\n position: { x: number; y: number };\n}\n\n/** An error from {@link startItemDrag}, carrying a machine-readable `.code`. */\nexport interface ItemDragError extends Error {\n code:\n | 'forbidden' // the frame lacks the first-party `dnd:source` capability\n | 'invalid-params' // the item was malformed (empty path / `..` / URI / bad kind)\n | 'too-large' // inlined `bytes` exceed the host's size limit\n | 'rate-limited' // too many drags started too fast (capacity-class, fail-open)\n | 'unknown';\n}\n\n/**\n * Begin a host-mediated drag of `item` out of this app. Resolves once the host has\n * taken over the drag (drawn the ghost, installed the pointer-capture layer); rejects\n * with an {@link ItemDragError} if this app may not initiate drags (`forbidden`) or the\n * item is invalid. Only a first-party chrome app holding `dnd:source` may call this — a\n * previewed/third-party app is refused at the gate (it must not synthesize drags into\n * sibling apps).\n */\nexport const startItemDrag = async (item: DraggableItem): Promise<void> => {\n const res = (await protocolRequest('dnd', 'startDrag', [item])) as\n | { ok: true }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'dnd startDrag failed') as ItemDragError;\n err.code = (res?.code as ItemDragError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Abort an in-progress host-mediated drag this app started (e.g. the user pressed\n * Escape, or the gesture was cancelled). Best-effort and fire-and-forget. */\nexport const cancelItemDrag = (): void => {\n sendMessage('dnd-cancel', {});\n};\n\n/** Subscribe to items dropped onto this app by a host-mediated cross-app drag.\n * Returns an unsubscribe fn. Subscribing is the opt-in: an app that never subscribes\n * receives nothing (the host shows a \"not accepted\" cue and the drop is a no-op). */\nexport const onItemDrop = (listener: (d: DroppedItem) => void): (() => void) =>\n addListener('dropped-item', (m: { item: DraggableItem; from: string; position: { x: number; y: number } }) =>\n listener({ item: m.item, from: m.from, position: m.position }),\n );\n\n/** React hook: the most recently dropped item (or `null`). */\nexport const useDroppedItem = (): DroppedItem | null => {\n const [dropped, setDropped] = useState<DroppedItem | null>(null);\n useEffect(() => onItemDrop(setDropped), []);\n return dropped;\n};\n"],"mappings":"AAoBA,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB,aAAa,mBAAmB;AA6CnD,MAAM,gBAAgB,OAAO,SAAuC;AACzE,QAAM,MAAO,MAAM,gBAAgB,OAAO,aAAa,CAAC,IAAI,CAAC;AAI7D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,sBAAsB;AAC5D,QAAI,OAAQ,KAAK,QAAkC;AACnD,UAAM;AAAA,EACR;AACF;AAIO,MAAM,iBAAiB,MAAY;AACxC,cAAY,cAAc,CAAC,CAAC;AAC9B;AAKO,MAAM,aAAa,CAAC,aACzB;AAAA,EAAY;AAAA,EAAgB,CAAC,MAC3B,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,CAAC;AAC/D;AAGK,MAAM,iBAAiB,MAA0B;AACtD,QAAM,CAAC,SAAS,UAAU,IAAI,SAA6B,IAAI;AAC/D,YAAU,MAAM,WAAW,UAAU,GAAG,CAAC,CAAC;AAC1C,SAAO;AACT;","names":[]}
|
package/dist/hostRuntime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hostRuntime.ts"],"sourcesContent":["// Runtime-discovery global (SDK_PACKAGING_SPEC §4) — a LEAF module that imports\n// nothing, so it can be read by both `sandboxUtils` (transport resolver) and\n// `runtime` (handshake) without forming an import cycle. `sandboxUtils → runtime`\n// + `runtime → sandboxUtils` was a cycle the sandbox bundler cannot evaluate\n// (infinite re-require → \"Maximum call stack size exceeded\"); routing the shared\n// `getHostRuntime` through this leaf breaks it.\n\n/** The sandbox runtime's pre-evaluation discovery global (§4). */\nexport interface ImmediatelyRunGlobal {\n /** Sandbox-runtime protocol version (semver). */\n runtimeVersion?: string;\n /** postMessage envelope/protocol version. */\n protocolVersion?: string;\n /** The host channel the SDK talks over (MessagePort | message bus). */\n transport?: unknown;\n /** Resolves when ports arrive, if they arrive async after register-frame. */\n ready?: Promise<void>;\n /** Canonical `/mnt/{hash}` path of the app's own repo mount (FILE_SHARING §11.2);\n * surfaced to apps via `getAppMountPath()`. Absent until the host reports it. */\n appMountPath?: string;\n}\n\n/**\n * Read the sandbox runtime's discovery global (§4), or null when absent — in which\n * case the SDK uses the current INJECTED path (`module.evaluation.*`). Lets the SDK\n * detect a host too old/new and fail closed (§6) once the global ships.\n */\nexport function getHostRuntime(): ImmediatelyRunGlobal | null {\n try {\n return (globalThis as { __immediatelyRun__?: ImmediatelyRunGlobal }).__immediatelyRun__ ?? null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../src/hostRuntime.ts"],"sourcesContent":["// Runtime-discovery global (SDK_PACKAGING_SPEC §4) — a LEAF module that imports\n// nothing, so it can be read by both `sandboxUtils` (transport resolver) and\n// `runtime` (handshake) without forming an import cycle. `sandboxUtils → runtime`\n// + `runtime → sandboxUtils` was a cycle the sandbox bundler cannot evaluate\n// (infinite re-require → \"Maximum call stack size exceeded\"); routing the shared\n// `getHostRuntime` through this leaf breaks it.\n\n/** The sandbox runtime's pre-evaluation discovery global (§4). */\nexport interface ImmediatelyRunGlobal {\n /** Sandbox-runtime protocol version (semver). */\n runtimeVersion?: string;\n /** postMessage envelope/protocol version. */\n protocolVersion?: string;\n /** The host channel the SDK talks over (MessagePort | message bus). */\n transport?: unknown;\n /** Resolves when ports arrive, if they arrive async after register-frame. */\n ready?: Promise<void>;\n /** Canonical `/mnt/{hash}` path of the app's own repo mount (FILE_SHARING §11.2);\n * surfaced to apps via `getAppMountPath()`. Absent until the host reports it. */\n appMountPath?: string;\n /** The chrome region this app instance occupies, e.g. `\"panel.agent\"` or\n * `\"stage.conversation\"` (UI_AS_APPS_SPEC §4.1); surfaced via `getRegion()`.\n * Absent for a standalone app, in local dev, or on a host that doesn't report\n * it. Descriptive only — it grants nothing. */\n region?: string;\n}\n\n/**\n * Read the sandbox runtime's discovery global (§4), or null when absent — in which\n * case the SDK uses the current INJECTED path (`module.evaluation.*`). Lets the SDK\n * detect a host too old/new and fail closed (§6) once the global ships.\n */\nexport function getHostRuntime(): ImmediatelyRunGlobal | null {\n try {\n return (globalThis as { __immediatelyRun__?: ImmediatelyRunGlobal }).__immediatelyRun__ ?? null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,SAAS,iBAA8C;AAC5D,MAAI;AACF,WAAQ,WAA6D,sBAAsB;AAAA,EAC7F,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/hostRuntime.d.cts
CHANGED
|
@@ -11,6 +11,11 @@ interface ImmediatelyRunGlobal {
|
|
|
11
11
|
/** Canonical `/mnt/{hash}` path of the app's own repo mount (FILE_SHARING §11.2);
|
|
12
12
|
* surfaced to apps via `getAppMountPath()`. Absent until the host reports it. */
|
|
13
13
|
appMountPath?: string;
|
|
14
|
+
/** The chrome region this app instance occupies, e.g. `"panel.agent"` or
|
|
15
|
+
* `"stage.conversation"` (UI_AS_APPS_SPEC §4.1); surfaced via `getRegion()`.
|
|
16
|
+
* Absent for a standalone app, in local dev, or on a host that doesn't report
|
|
17
|
+
* it. Descriptive only — it grants nothing. */
|
|
18
|
+
region?: string;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Read the sandbox runtime's discovery global (§4), or null when absent — in which
|
package/dist/hostRuntime.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ interface ImmediatelyRunGlobal {
|
|
|
11
11
|
/** Canonical `/mnt/{hash}` path of the app's own repo mount (FILE_SHARING §11.2);
|
|
12
12
|
* surfaced to apps via `getAppMountPath()`. Absent until the host reports it. */
|
|
13
13
|
appMountPath?: string;
|
|
14
|
+
/** The chrome region this app instance occupies, e.g. `"panel.agent"` or
|
|
15
|
+
* `"stage.conversation"` (UI_AS_APPS_SPEC §4.1); surfaced via `getRegion()`.
|
|
16
|
+
* Absent for a standalone app, in local dev, or on a host that doesn't report
|
|
17
|
+
* it. Descriptive only — it grants nothing. */
|
|
18
|
+
region?: string;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* Read the sandbox runtime's discovery global (§4), or null when absent — in which
|
package/dist/hostRuntime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hostRuntime.ts"],"sourcesContent":["// Runtime-discovery global (SDK_PACKAGING_SPEC §4) — a LEAF module that imports\n// nothing, so it can be read by both `sandboxUtils` (transport resolver) and\n// `runtime` (handshake) without forming an import cycle. `sandboxUtils → runtime`\n// + `runtime → sandboxUtils` was a cycle the sandbox bundler cannot evaluate\n// (infinite re-require → \"Maximum call stack size exceeded\"); routing the shared\n// `getHostRuntime` through this leaf breaks it.\n\n/** The sandbox runtime's pre-evaluation discovery global (§4). */\nexport interface ImmediatelyRunGlobal {\n /** Sandbox-runtime protocol version (semver). */\n runtimeVersion?: string;\n /** postMessage envelope/protocol version. */\n protocolVersion?: string;\n /** The host channel the SDK talks over (MessagePort | message bus). */\n transport?: unknown;\n /** Resolves when ports arrive, if they arrive async after register-frame. */\n ready?: Promise<void>;\n /** Canonical `/mnt/{hash}` path of the app's own repo mount (FILE_SHARING §11.2);\n * surfaced to apps via `getAppMountPath()`. Absent until the host reports it. */\n appMountPath?: string;\n}\n\n/**\n * Read the sandbox runtime's discovery global (§4), or null when absent — in which\n * case the SDK uses the current INJECTED path (`module.evaluation.*`). Lets the SDK\n * detect a host too old/new and fail closed (§6) once the global ships.\n */\nexport function getHostRuntime(): ImmediatelyRunGlobal | null {\n try {\n return (globalThis as { __immediatelyRun__?: ImmediatelyRunGlobal }).__immediatelyRun__ ?? null;\n } catch {\n return null;\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/hostRuntime.ts"],"sourcesContent":["// Runtime-discovery global (SDK_PACKAGING_SPEC §4) — a LEAF module that imports\n// nothing, so it can be read by both `sandboxUtils` (transport resolver) and\n// `runtime` (handshake) without forming an import cycle. `sandboxUtils → runtime`\n// + `runtime → sandboxUtils` was a cycle the sandbox bundler cannot evaluate\n// (infinite re-require → \"Maximum call stack size exceeded\"); routing the shared\n// `getHostRuntime` through this leaf breaks it.\n\n/** The sandbox runtime's pre-evaluation discovery global (§4). */\nexport interface ImmediatelyRunGlobal {\n /** Sandbox-runtime protocol version (semver). */\n runtimeVersion?: string;\n /** postMessage envelope/protocol version. */\n protocolVersion?: string;\n /** The host channel the SDK talks over (MessagePort | message bus). */\n transport?: unknown;\n /** Resolves when ports arrive, if they arrive async after register-frame. */\n ready?: Promise<void>;\n /** Canonical `/mnt/{hash}` path of the app's own repo mount (FILE_SHARING §11.2);\n * surfaced to apps via `getAppMountPath()`. Absent until the host reports it. */\n appMountPath?: string;\n /** The chrome region this app instance occupies, e.g. `\"panel.agent\"` or\n * `\"stage.conversation\"` (UI_AS_APPS_SPEC §4.1); surfaced via `getRegion()`.\n * Absent for a standalone app, in local dev, or on a host that doesn't report\n * it. Descriptive only — it grants nothing. */\n region?: string;\n}\n\n/**\n * Read the sandbox runtime's discovery global (§4), or null when absent — in which\n * case the SDK uses the current INJECTED path (`module.evaluation.*`). Lets the SDK\n * detect a host too old/new and fail closed (§6) once the global ships.\n */\nexport function getHostRuntime(): ImmediatelyRunGlobal | null {\n try {\n return (globalThis as { __immediatelyRun__?: ImmediatelyRunGlobal }).__immediatelyRun__ ?? null;\n } catch {\n return null;\n }\n}\n"],"mappings":"AAgCO,SAAS,iBAA8C;AAC5D,MAAI;AACF,WAAQ,WAA6D,sBAAsB;AAAA,EAC7F,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|