@immediately-run/sdk 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/ambient.d.ts +47 -0
  2. package/dist/auth.cjs +3 -2
  3. package/dist/auth.cjs.map +1 -1
  4. package/dist/auth.js +3 -2
  5. package/dist/auth.js.map +1 -1
  6. package/dist/boot.cjs +3 -2
  7. package/dist/boot.cjs.map +1 -1
  8. package/dist/boot.js +3 -2
  9. package/dist/boot.js.map +1 -1
  10. package/dist/catalog.cjs +3 -2
  11. package/dist/catalog.cjs.map +1 -1
  12. package/dist/catalog.js +3 -2
  13. package/dist/catalog.js.map +1 -1
  14. package/dist/contribute.cjs +2 -1
  15. package/dist/contribute.cjs.map +1 -1
  16. package/dist/contribute.js +2 -1
  17. package/dist/contribute.js.map +1 -1
  18. package/dist/debug.cjs +6 -5
  19. package/dist/debug.cjs.map +1 -1
  20. package/dist/debug.js +12 -5
  21. package/dist/debug.js.map +1 -1
  22. package/dist/diagnostics.cjs +3 -2
  23. package/dist/diagnostics.cjs.map +1 -1
  24. package/dist/diagnostics.js +3 -2
  25. package/dist/diagnostics.js.map +1 -1
  26. package/dist/dnd.cjs +5 -3
  27. package/dist/dnd.cjs.map +1 -1
  28. package/dist/dnd.js +5 -3
  29. package/dist/dnd.js.map +1 -1
  30. package/dist/editor.cjs +3 -1
  31. package/dist/editor.cjs.map +1 -1
  32. package/dist/editor.js +3 -1
  33. package/dist/editor.js.map +1 -1
  34. package/dist/editorContext.cjs +3 -2
  35. package/dist/editorContext.cjs.map +1 -1
  36. package/dist/editorContext.js +3 -2
  37. package/dist/editorContext.js.map +1 -1
  38. package/dist/formFactor.cjs +3 -2
  39. package/dist/formFactor.cjs.map +1 -1
  40. package/dist/formFactor.js +3 -2
  41. package/dist/formFactor.js.map +1 -1
  42. package/dist/generated/protocol.cjs +23 -0
  43. package/dist/generated/protocol.cjs.map +1 -0
  44. package/dist/generated/protocol.d.cts +1 -0
  45. package/dist/generated/protocol.d.ts +1 -0
  46. package/dist/generated/protocol.js +2 -0
  47. package/dist/generated/protocol.js.map +1 -0
  48. package/dist/hooks.cjs +22 -14
  49. package/dist/hooks.cjs.map +1 -1
  50. package/dist/hooks.d.cts +26 -4
  51. package/dist/hooks.d.ts +26 -4
  52. package/dist/hooks.js +23 -15
  53. package/dist/hooks.js.map +1 -1
  54. package/dist/index.cjs +2 -0
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +2 -1
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.js +1 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/ipc.cjs +5 -3
  61. package/dist/ipc.cjs.map +1 -1
  62. package/dist/ipc.js +5 -3
  63. package/dist/ipc.js.map +1 -1
  64. package/dist/launch.cjs +5 -3
  65. package/dist/launch.cjs.map +1 -1
  66. package/dist/launch.js +5 -3
  67. package/dist/launch.js.map +1 -1
  68. package/dist/llm.cjs +3 -2
  69. package/dist/llm.cjs.map +1 -1
  70. package/dist/llm.js +3 -2
  71. package/dist/llm.js.map +1 -1
  72. package/dist/metadataSource.cjs +53 -0
  73. package/dist/metadataSource.cjs.map +1 -0
  74. package/dist/metadataSource.d.cts +51 -0
  75. package/dist/metadataSource.d.ts +51 -0
  76. package/dist/metadataSource.js +29 -0
  77. package/dist/metadataSource.js.map +1 -0
  78. package/dist/moduleCache.cjs +2 -1
  79. package/dist/moduleCache.cjs.map +1 -1
  80. package/dist/moduleCache.js +2 -1
  81. package/dist/moduleCache.js.map +1 -1
  82. package/dist/mounts.cjs +13 -10
  83. package/dist/mounts.cjs.map +1 -1
  84. package/dist/mounts.js +23 -10
  85. package/dist/mounts.js.map +1 -1
  86. package/dist/netFetch.cjs +4 -2
  87. package/dist/netFetch.cjs.map +1 -1
  88. package/dist/netFetch.js +4 -2
  89. package/dist/netFetch.js.map +1 -1
  90. package/dist/onFsChange.cjs +2 -1
  91. package/dist/onFsChange.cjs.map +1 -1
  92. package/dist/onFsChange.js +2 -1
  93. package/dist/onFsChange.js.map +1 -1
  94. package/dist/protocolSchemes.cjs +46 -0
  95. package/dist/protocolSchemes.cjs.map +1 -0
  96. package/dist/protocolSchemes.d.cts +18 -0
  97. package/dist/protocolSchemes.d.ts +18 -0
  98. package/dist/protocolSchemes.js +37 -0
  99. package/dist/protocolSchemes.js.map +1 -0
  100. package/dist/routing.cjs +2 -1
  101. package/dist/routing.cjs.map +1 -1
  102. package/dist/routing.js +2 -1
  103. package/dist/routing.js.map +1 -1
  104. package/dist/runtime.cjs +4 -2
  105. package/dist/runtime.cjs.map +1 -1
  106. package/dist/runtime.js +4 -2
  107. package/dist/runtime.js.map +1 -1
  108. package/dist/sandboxTypes.cjs.map +1 -1
  109. package/dist/sandboxTypes.d.cts +40 -6
  110. package/dist/sandboxTypes.d.ts +40 -6
  111. package/dist/secrets.cjs +5 -3
  112. package/dist/secrets.cjs.map +1 -1
  113. package/dist/secrets.js +5 -3
  114. package/dist/secrets.js.map +1 -1
  115. package/dist/tasks.cjs +6 -4
  116. package/dist/tasks.cjs.map +1 -1
  117. package/dist/tasks.js +6 -4
  118. package/dist/tasks.js.map +1 -1
  119. package/dist/theme.cjs +5 -3
  120. package/dist/theme.cjs.map +1 -1
  121. package/dist/theme.js +5 -3
  122. package/dist/theme.js.map +1 -1
  123. package/dist/urlUtils.cjs +3 -4
  124. package/dist/urlUtils.cjs.map +1 -1
  125. package/dist/urlUtils.d.cts +3 -10
  126. package/dist/urlUtils.d.ts +3 -10
  127. package/dist/urlUtils.js +1 -2
  128. package/dist/urlUtils.js.map +1 -1
  129. package/dist/vcs.cjs +5 -3
  130. package/dist/vcs.cjs.map +1 -1
  131. package/dist/vcs.js +5 -3
  132. package/dist/vcs.js.map +1 -1
  133. package/dist/version.cjs +1 -1
  134. package/dist/version.cjs.map +1 -1
  135. package/dist/version.d.cts +1 -1
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/version.js.map +1 -1
  139. package/package.json +11 -4
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/formFactor.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\n\n/**\n * The form factor of the surface your app is rendered into, mirrored from the\n * immediately.run host (UI_AS_APPS_SPEC §5.4.1). Read this to lay out\n * responsively — a narrow chrome panel, a full preview, or a mobile carousel\n * pane all report their box here. The host is the source of truth (it owns the\n * region); you cannot reliably measure your own viewport across the sandbox\n * boundary.\n *\n * Baseline capability `formFactor:read` — every app may read it.\n */\nexport type FormFactorClass = 'mobile' | 'tablet' | 'desktop';\n/** Whether the rendered surface is taller than wide (`portrait`) or wider (`landscape`). */\nexport type Orientation = 'portrait' | 'landscape';\n\n/** The host-reported size class, orientation, and pixel box of your app's surface. */\nexport interface FormFactor {\n class: FormFactorClass;\n orientation: Orientation;\n width: number;\n height: number;\n}\n\n/** Assumed before the host reports — a reasonable desktop default. */\nconst DEFAULT_FORM_FACTOR: FormFactor = {\n class: 'desktop',\n orientation: 'landscape',\n width: 1280,\n height: 800,\n};\n\nconst isFormFactor = (v: unknown): v is FormFactor => {\n const f = v as Partial<FormFactor> | null;\n return (\n !!f &&\n (f.class === 'mobile' || f.class === 'tablet' || f.class === 'desktop') &&\n (f.orientation === 'portrait' || f.orientation === 'landscape') &&\n typeof f.width === 'number' &&\n typeof f.height === 'number'\n );\n};\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `form-factor`\n// and answers `request-form-factor` (wire format: site-main channelBridge.ts).\nconst channel = createPushChannel<FormFactor>({\n pushType: 'form-factor',\n requestType: 'request-form-factor',\n initial: DEFAULT_FORM_FACTOR,\n parse: (msg) => (isFormFactor(msg.formFactor) ? (msg.formFactor as FormFactor) : undefined),\n});\n\n/** Returns the current form factor. Poll for a one-off read. */\nexport const getFormFactor = (): FormFactor => channel.get();\n\n/**\n * Subscribe to form-factor changes. The listener is invoked immediately with\n * the current value, then again on every change. Returns an unsubscribe fn.\n */\nexport const onFormFactorChange = (listener: (formFactor: FormFactor) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning the current form factor, re-rendering on change. */\nexport const useFormFactor = (): FormFactor => channel.use();\n"],"mappings":";AAAA,SAAS,yBAAyB;AAyBlC,MAAM,sBAAkC;AAAA,EACtC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,OAAO;AAAA,EACP,QAAQ;AACV;AAEA,MAAM,eAAe,CAAC,MAAgC;AACpD,QAAM,IAAI;AACV,SACE,CAAC,CAAC,MACD,EAAE,UAAU,YAAY,EAAE,UAAU,YAAY,EAAE,UAAU,eAC5D,EAAE,gBAAgB,cAAc,EAAE,gBAAgB,gBACnD,OAAO,EAAE,UAAU,YACnB,OAAO,EAAE,WAAW;AAExB;AAIA,MAAM,UAAU,kBAA8B;AAAA,EAC5C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAS,aAAa,IAAI,UAAU,IAAK,IAAI,aAA4B;AACnF,CAAC;AAGM,MAAM,gBAAgB,MAAkB,QAAQ,IAAI;AAMpD,MAAM,qBAAqB,CAAC,aACjC,QAAQ,SAAS,QAAQ;AAGpB,MAAM,gBAAgB,MAAkB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/formFactor.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\nimport { FORM_FACTOR, REQUEST_FORM_FACTOR } from './generated/protocol';\n\n/**\n * The form factor of the surface your app is rendered into, mirrored from the\n * immediately.run host (UI_AS_APPS_SPEC §5.4.1). Read this to lay out\n * responsively — a narrow chrome panel, a full preview, or a mobile carousel\n * pane all report their box here. The host is the source of truth (it owns the\n * region); you cannot reliably measure your own viewport across the sandbox\n * boundary.\n *\n * Baseline capability `formFactor:read` — every app may read it.\n */\nexport type FormFactorClass = 'mobile' | 'tablet' | 'desktop';\n/** Whether the rendered surface is taller than wide (`portrait`) or wider (`landscape`). */\nexport type Orientation = 'portrait' | 'landscape';\n\n/** The host-reported size class, orientation, and pixel box of your app's surface. */\nexport interface FormFactor {\n class: FormFactorClass;\n orientation: Orientation;\n width: number;\n height: number;\n}\n\n/** Assumed before the host reports — a reasonable desktop default. */\nconst DEFAULT_FORM_FACTOR: FormFactor = {\n class: 'desktop',\n orientation: 'landscape',\n width: 1280,\n height: 800,\n};\n\nconst isFormFactor = (v: unknown): v is FormFactor => {\n const f = v as Partial<FormFactor> | null;\n return (\n !!f &&\n (f.class === 'mobile' || f.class === 'tablet' || f.class === 'desktop') &&\n (f.orientation === 'portrait' || f.orientation === 'landscape') &&\n typeof f.width === 'number' &&\n typeof f.height === 'number'\n );\n};\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `form-factor`\n// and answers `request-form-factor` (wire format: site-main channelBridge.ts).\nconst channel = createPushChannel<FormFactor>({\n pushType: FORM_FACTOR,\n requestType: REQUEST_FORM_FACTOR,\n initial: DEFAULT_FORM_FACTOR,\n parse: (msg) => (isFormFactor(msg.formFactor) ? (msg.formFactor as FormFactor) : undefined),\n});\n\n/** Returns the current form factor. Poll for a one-off read. */\nexport const getFormFactor = (): FormFactor => channel.get();\n\n/**\n * Subscribe to form-factor changes. The listener is invoked immediately with\n * the current value, then again on every change. Returns an unsubscribe fn.\n */\nexport const onFormFactorChange = (listener: (formFactor: FormFactor) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning the current form factor, re-rendering on change. */\nexport const useFormFactor = (): FormFactor => channel.use();\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,aAAa,2BAA2B;AAyBjD,MAAM,sBAAkC;AAAA,EACtC,OAAO;AAAA,EACP,aAAa;AAAA,EACb,OAAO;AAAA,EACP,QAAQ;AACV;AAEA,MAAM,eAAe,CAAC,MAAgC;AACpD,QAAM,IAAI;AACV,SACE,CAAC,CAAC,MACD,EAAE,UAAU,YAAY,EAAE,UAAU,YAAY,EAAE,UAAU,eAC5D,EAAE,gBAAgB,cAAc,EAAE,gBAAgB,gBACnD,OAAO,EAAE,UAAU,YACnB,OAAO,EAAE,WAAW;AAExB;AAIA,MAAM,UAAU,kBAA8B;AAAA,EAC5C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAS,aAAa,IAAI,UAAU,IAAK,IAAI,aAA4B;AACnF,CAAC;AAGM,MAAM,gBAAgB,MAAkB,QAAQ,IAAI;AAMpD,MAAM,qBAAqB,CAAC,aACjC,QAAQ,SAAS,QAAQ;AAGpB,MAAM,gBAAgB,MAAkB,QAAQ,IAAI;","names":[]}
@@ -0,0 +1,23 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
+ var protocol_exports = {};
17
+ module.exports = __toCommonJS(protocol_exports);
18
+ __reExport(protocol_exports, require("@immediately-run/sandbox-protocol/sdk"), module.exports);
19
+ // Annotate the CommonJS export names for ESM import in node:
20
+ 0 && (module.exports = {
21
+ ...require("@immediately-run/sandbox-protocol/sdk")
22
+ });
23
+ //# sourceMappingURL=protocol.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/generated/protocol.ts"],"sourcesContent":["// The sandbox↔SDK wire vocabulary — re-exported from the published contract.\n//\n// Until R3-274b1 this file was a COPY of a module generated in the sandbox repo,\n// carried here by hand because this package does not depend on that one and a build\n// that reads a sibling checkout is the coupling R3-274d removed. It now comes from\n// `@immediately-run/sandbox-protocol`, which owns the descriptors and publishes a\n// module per side (PLATFORM_LAYERING_SPEC §2 / S1 target 1).\n//\n// The file stays at this path because its exports are public API — `api-snapshot.json`\n// pins them, and a fork pinned to an older SDK imports them from here.\n//\n// To change the wire: edit the descriptors in that package, publish, bump the pin.\n// `npm run protocol:check` fails until this repo's source and the pinned contract\n// agree.\nexport * from '@immediately-run/sandbox-protocol/sdk';\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAcA,6BAAc,kDAdd;","names":[]}
@@ -0,0 +1 @@
1
+ export * from '@immediately-run/sandbox-protocol/sdk';
@@ -0,0 +1 @@
1
+ export * from '@immediately-run/sandbox-protocol/sdk';
@@ -0,0 +1,2 @@
1
+ export * from "@immediately-run/sandbox-protocol/sdk";
2
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/generated/protocol.ts"],"sourcesContent":["// The sandbox↔SDK wire vocabulary — re-exported from the published contract.\n//\n// Until R3-274b1 this file was a COPY of a module generated in the sandbox repo,\n// carried here by hand because this package does not depend on that one and a build\n// that reads a sibling checkout is the coupling R3-274d removed. It now comes from\n// `@immediately-run/sandbox-protocol`, which owns the descriptors and publishes a\n// module per side (PLATFORM_LAYERING_SPEC §2 / S1 target 1).\n//\n// The file stays at this path because its exports are public API — `api-snapshot.json`\n// pins them, and a fork pinned to an older SDK imports them from here.\n//\n// To change the wire: edit the descriptors in that package, publish, bump the pin.\n// `npm run protocol:check` fails until this repo's source and the pinned contract\n// agree.\nexport * from '@immediately-run/sandbox-protocol/sdk';\n"],"mappings":"AAcA,cAAc;","names":[]}
package/dist/hooks.cjs CHANGED
@@ -24,9 +24,10 @@ __export(hooks_exports, {
24
24
  useObjectUrl: () => useObjectUrl
25
25
  });
26
26
  module.exports = __toCommonJS(hooks_exports);
27
+ var import_platform_constants = require("@immediately-run/platform-constants");
27
28
  var import_react = require("react");
28
- var import_TinkerableContext = require("./TinkerableContext");
29
29
  var import_fs = require("./fs");
30
+ var import_metadataSource = require("./metadataSource");
30
31
  const entriesEqual = (a, b) => {
31
32
  if (a.length !== b.length) {
32
33
  return false;
@@ -35,17 +36,25 @@ const entriesEqual = (a, b) => {
35
36
  if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {
36
37
  return false;
37
38
  }
39
+ const ax = a[i];
40
+ const bx = b[i];
41
+ const keys = /* @__PURE__ */ new Set([...Object.keys(ax), ...Object.keys(bx)]);
42
+ for (const k of keys) {
43
+ if (k === "path" || k === "meta") continue;
44
+ if (ax[k] !== bx[k]) return false;
45
+ }
38
46
  }
39
47
  return true;
40
48
  };
41
49
  const useMetadataQuery = (queryFunction) => {
42
- const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
50
+ const files = (0, import_metadataSource.useMetadataStore)();
43
51
  const previous = (0, import_react.useRef)([]);
44
52
  return (0, import_react.useMemo)(() => {
45
- const files = filesMetadata ?? {};
46
53
  let entries;
47
54
  try {
48
- entries = queryFunction(files).map((path) => ({ path, meta: files[path] }));
55
+ entries = queryFunction(files).map(
56
+ (selected) => typeof selected === "string" ? { path: selected, meta: files[selected] } : { ...selected, path: selected.path, meta: files[selected.path] }
57
+ );
49
58
  } catch (error) {
50
59
  return { error };
51
60
  }
@@ -54,19 +63,18 @@ const useMetadataQuery = (queryFunction) => {
54
63
  }
55
64
  previous.current = entries;
56
65
  return entries;
57
- }, [filesMetadata, queryFunction]);
66
+ }, [files, queryFunction]);
58
67
  };
59
68
  const useFileMetadata = (path) => {
60
- const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
61
- return (0, import_react.useMemo)(
62
- () => (filesMetadata ?? {})[path],
63
- [path, filesMetadata]
64
- );
65
- };
66
- const useAllMetadata = () => {
67
- const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
68
- return filesMetadata ?? {};
69
+ const files = (0, import_metadataSource.useMetadataStore)();
70
+ return (0, import_react.useMemo)(() => {
71
+ const direct = files[path];
72
+ if (direct !== void 0) return direct;
73
+ if (path === import_platform_constants.APP_ROOT || path.startsWith(`${import_platform_constants.APP_ROOT}/`)) return void 0;
74
+ return files[(0, import_platform_constants.underAppRoot)(path)];
75
+ }, [path, files]);
69
76
  };
77
+ const useAllMetadata = () => (0, import_metadataSource.useMetadataStore)();
70
78
  const useObjectUrl = (mount, relPath, opts) => {
71
79
  const [state, setState] = (0, import_react.useState)({
72
80
  url: null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { use, useEffect, useMemo, useRef, useState } from 'react';\nimport { TinkerableContext } from './TinkerableContext';\nimport { openFs, type FsError } from './fs';\nimport type { SandboxMount } from './mounts';\nimport {\n FilesMetadata,\n Metadata,\n MetadataQueryEntry,\n MetadataQueryFunction,\n MetadataQueryResult,\n} from './sandboxTypes';\n\nconst entriesEqual = <T>(a: MetadataQueryEntry<T>[], b: MetadataQueryEntry<T>[]): boolean => {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n // Unchanged frontmatter keeps its object identity across `metadata-update`\n // merges, so an identity check on `meta` is a sound \"did this match change?\".\n if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Query the file metadata store (MDX frontmatter) with a plain JS function.\n *\n * The query receives every file's frontmatter keyed by path and returns the paths\n * that match; the hook resolves each path back to its frontmatter and returns an\n * array of `{ path, meta }` entries — so a single call gives you everything you\n * filtered on, with no second lookup. A throwing query is reported as `{ error }`\n * rather than crashing the render.\n *\n * The query runs synchronously during render (no empty first frame), and the\n * returned array keeps its identity while the matches are unchanged, so it is safe\n * to use directly in downstream `useMemo`/`useEffect` dependency arrays.\n *\n * Pass a type parameter to get typed frontmatter throughout:\n * ```ts\n * interface PostMeta { title: string; date: string; draft?: boolean }\n * const posts = useMetadataQuery<PostMeta>((files) =>\n * Object.entries(files)\n * .filter(([, m]) => !m.draft)\n * .sort(([, a], [, b]) => b.date.localeCompare(a.date))\n * .map(([path]) => path),\n * );\n * ```\n */\nexport const useMetadataQuery = <T = Metadata>(\n queryFunction: MetadataQueryFunction<T>,\n): MetadataQueryResult<T> => {\n const { filesMetadata } = use(TinkerableContext);\n const previous = useRef<MetadataQueryEntry<T>[]>([]);\n return useMemo<MetadataQueryResult<T>>(() => {\n const files = (filesMetadata ?? {}) as FilesMetadata<T>;\n let entries: MetadataQueryEntry<T>[];\n try {\n entries = queryFunction(files).map((path) => ({ path, meta: files[path] }));\n } catch (error) {\n return { error };\n }\n // Preserve the prior array reference when nothing matched differently.\n if (entriesEqual(entries, previous.current)) {\n return previous.current;\n }\n previous.current = entries;\n return entries;\n }, [filesMetadata, queryFunction]);\n};\n\n/**\n * Read one file's metadata (MDX frontmatter) by repo-relative path. Returns\n * `undefined` when the path has no metadata. Pass a type parameter for typed\n * field access.\n */\nexport const useFileMetadata = <T = Metadata>(path: string): T | undefined => {\n const { filesMetadata } = use(TinkerableContext);\n return useMemo(\n () => (filesMetadata ?? {})[path] as T | undefined,\n [path, filesMetadata],\n );\n};\n\n/**\n * The raw, reactive metadata store: a map from file path to frontmatter. The\n * escape hatch for apps that want to render their own index rather than express it\n * as a path-returning query. Pass a type parameter for typed frontmatter values.\n */\nexport const useAllMetadata = <T = Metadata>(): FilesMetadata<T> => {\n const { filesMetadata } = use(TinkerableContext);\n return (filesMetadata ?? {}) as FilesMetadata<T>;\n};\n\n/** The reactive state returned by {@link useObjectUrl}. */\nexport interface ObjectUrlState {\n /** The object URL once the bytes have loaded; `null` while loading or on error. */\n url: string | null;\n /** True while the file is being read. */\n loading: boolean;\n /** The {@link FsError} if the read failed (`not-found`, `unavailable`, …), else `null`. */\n error: FsError | null;\n}\n\n/**\n * Read a file from a mount into an **object URL** for `<img src>`, revoking it\n * automatically on unmount or when `mount`/`relPath` changes. This is the React\n * answer to \"an opaque-origin iframe can't fetch a mount path\": it reads the bytes\n * off the sandbox ZenFS ({@link openFs}) and hands you a URL to drop into an\n * `<img>`, and it owns the create/revoke lifecycle so you never leak a URL.\n *\n * Pass `null`/`undefined` for `mount` or `relPath` to mean \"nothing to load yet\"\n * (idle state, no read). For a ready-made element use `MountImage`.\n *\n * ```tsx\n * const { url, loading, error } = useObjectUrl(mount, 'photos/cat.png');\n * if (loading) return <Spinner />;\n * if (error || !url) return <span>missing</span>;\n * return <img src={url} alt=\"cat\" />;\n * ```\n */\nexport const useObjectUrl = (\n mount: SandboxMount | null | undefined,\n relPath: string | null | undefined,\n opts?: { type?: string },\n): ObjectUrlState => {\n const [state, setState] = useState<ObjectUrlState>({\n url: null,\n loading: Boolean(mount && relPath),\n error: null,\n });\n const type = opts?.type;\n // Key the effect on the mount's stable `path` (its object identity churns as the\n // mount set re-announces) plus the relPath and the optional type override.\n const mountPath = mount?.path;\n useEffect(() => {\n if (!mount || !relPath) {\n setState({ url: null, loading: false, error: null });\n return;\n }\n let alive = true;\n let revoke: (() => void) | null = null;\n setState({ url: null, loading: true, error: null });\n openFs(mount)\n .readObjectUrl(relPath, type ? { type } : undefined)\n .then((res) => {\n // Lost the race (unmounted / prop changed): revoke immediately, don't set.\n if (!alive) {\n res.revoke();\n return;\n }\n revoke = res.revoke;\n setState({ url: res.url, loading: false, error: null });\n })\n .catch((e) => {\n if (alive) setState({ url: null, loading: false, error: e as FsError });\n });\n return () => {\n alive = false;\n if (revoke) revoke();\n };\n // `mount` is intentionally tracked via its stable `mountPath`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mountPath, relPath, type]);\n return state;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA0D;AAC1D,+BAAkC;AAClC,gBAAqC;AAUrC,MAAM,eAAe,CAAI,GAA4B,MAAwC;AAC3F,MAAI,EAAE,WAAW,EAAE,QAAQ;AACzB,WAAO;AAAA,EACT;AACA,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAGjC,QAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AA0BO,MAAM,mBAAmB,CAC9B,kBAC2B;AAC3B,QAAM,EAAE,cAAc,QAAI,kBAAI,0CAAiB;AAC/C,QAAM,eAAW,qBAAgC,CAAC,CAAC;AACnD,aAAO,sBAAgC,MAAM;AAC3C,UAAM,QAAS,iBAAiB,CAAC;AACjC,QAAI;AACJ,QAAI;AACF,gBAAU,cAAc,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,MAAM,MAAM,IAAI,EAAE,EAAE;AAAA,IAC5E,SAAS,OAAO;AACd,aAAO,EAAE,MAAM;AAAA,IACjB;AAEA,QAAI,aAAa,SAAS,SAAS,OAAO,GAAG;AAC3C,aAAO,SAAS;AAAA,IAClB;AACA,aAAS,UAAU;AACnB,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,aAAa,CAAC;AACnC;AAOO,MAAM,kBAAkB,CAAe,SAAgC;AAC5E,QAAM,EAAE,cAAc,QAAI,kBAAI,0CAAiB;AAC/C,aAAO;AAAA,IACL,OAAO,iBAAiB,CAAC,GAAG,IAAI;AAAA,IAChC,CAAC,MAAM,aAAa;AAAA,EACtB;AACF;AAOO,MAAM,iBAAiB,MAAsC;AAClE,QAAM,EAAE,cAAc,QAAI,kBAAI,0CAAiB;AAC/C,SAAQ,iBAAiB,CAAC;AAC5B;AA6BO,MAAM,eAAe,CAC1B,OACA,SACA,SACmB;AACnB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAyB;AAAA,IACjD,KAAK;AAAA,IACL,SAAS,QAAQ,SAAS,OAAO;AAAA,IACjC,OAAO;AAAA,EACT,CAAC;AACD,QAAM,OAAO,MAAM;AAGnB,QAAM,YAAY,OAAO;AACzB,8BAAU,MAAM;AACd,QAAI,CAAC,SAAS,CAAC,SAAS;AACtB,eAAS,EAAE,KAAK,MAAM,SAAS,OAAO,OAAO,KAAK,CAAC;AACnD;AAAA,IACF;AACA,QAAI,QAAQ;AACZ,QAAI,SAA8B;AAClC,aAAS,EAAE,KAAK,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AAClD,0BAAO,KAAK,EACT,cAAc,SAAS,OAAO,EAAE,KAAK,IAAI,MAAS,EAClD,KAAK,CAAC,QAAQ;AAEb,UAAI,CAAC,OAAO;AACV,YAAI,OAAO;AACX;AAAA,MACF;AACA,eAAS,IAAI;AACb,eAAS,EAAE,KAAK,IAAI,KAAK,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACxD,CAAC,EACA,MAAM,CAAC,MAAM;AACZ,UAAI,MAAO,UAAS,EAAE,KAAK,MAAM,SAAS,OAAO,OAAO,EAAa,CAAC;AAAA,IACxE,CAAC;AACH,WAAO,MAAM;AACX,cAAQ;AACR,UAAI,OAAQ,QAAO;AAAA,IACrB;AAAA,EAGF,GAAG,CAAC,WAAW,SAAS,IAAI,CAAC;AAC7B,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { APP_ROOT, underAppRoot } from '@immediately-run/platform-constants';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { openFs, type FsError } from './fs';\nimport { useMetadataStore } from './metadataSource';\nimport type { SandboxMount } from './mounts';\nimport {\n FilesMetadata,\n Metadata,\n MetadataQueryEntry,\n MetadataQueryFunction,\n MetadataQueryResult,\n} from './sandboxTypes';\n\nconst entriesEqual = <T>(\n a: MetadataQueryEntry<T, Record<string, unknown>>[],\n b: MetadataQueryEntry<T, Record<string, unknown>>[],\n): boolean => {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n // Unchanged frontmatter keeps its object identity across `metadata-update`\n // merges, so an identity check on `meta` is a sound \"did this match change?\".\n if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {\n return false;\n }\n // Extra fields a record-returning query computed (R3-276) are part of the\n // result, so a change in them is a change in the result — compared by value,\n // one level, because they are derived scalars, not the store's shared objects.\n const ax = a[i] as Record<string, unknown>;\n const bx = b[i] as Record<string, unknown>;\n const keys = new Set([...Object.keys(ax), ...Object.keys(bx)]);\n for (const k of keys) {\n if (k === 'path' || k === 'meta') continue;\n if (ax[k] !== bx[k]) return false;\n }\n }\n return true;\n};\n\n/**\n * Query the file metadata store (MDX frontmatter) with a plain JS function.\n *\n * The query receives every file's frontmatter keyed by path and returns the paths\n * that match; the hook resolves each path back to its frontmatter and returns an\n * array of `{ path, meta }` entries — so a single call gives you everything you\n * filtered on, with no second lookup. A throwing query is reported as `{ error }`\n * rather than crashing the render.\n *\n * The query runs synchronously during render (no empty first frame), and the\n * returned array keeps its identity while the matches are unchanged, so it is safe\n * to use directly in downstream `useMemo`/`useEffect` dependency arrays.\n *\n * A query may return RECORDS instead of bare paths (R3-276) — `{ path, ...extra }`\n * — and the extra fields ride along on each entry, so something derived while\n * selecting does not have to be recomputed downstream:\n * ```ts\n * const posts = useMetadataQuery<PostMeta, { year: string }>((files) =>\n * Object.entries(files).map(([path, m]) => ({ path, year: m.date.slice(0, 4) })),\n * );\n * ```\n * The store it queries is the nearest {@link MetadataSource}, else the host's.\n *\n * Pass a type parameter to get typed frontmatter throughout:\n * ```ts\n * interface PostMeta { title: string; date: string; draft?: boolean }\n * const posts = useMetadataQuery<PostMeta>((files) =>\n * Object.entries(files)\n * .filter(([, m]) => !m.draft)\n * .sort(([, a], [, b]) => b.date.localeCompare(a.date))\n * .map(([path]) => path),\n * );\n * ```\n */\nexport const useMetadataQuery = <T = Metadata, E extends object = {}>(\n queryFunction: MetadataQueryFunction<T>,\n): MetadataQueryResult<T, E> => {\n const files = useMetadataStore<T>();\n const previous = useRef<MetadataQueryEntry<T, Record<string, unknown>>[]>([]);\n return useMemo<MetadataQueryResult<T, E>>(() => {\n let entries: MetadataQueryEntry<T, Record<string, unknown>>[];\n try {\n // `path` and `meta` are applied AFTER the record's own fields: a query cannot\n // shadow the two the hook is responsible for, whatever it happened to name.\n entries = queryFunction(files).map((selected) =>\n typeof selected === 'string'\n ? { path: selected, meta: files[selected] }\n : { ...selected, path: selected.path, meta: files[selected.path] },\n );\n } catch (error) {\n return { error };\n }\n // Preserve the prior array reference when nothing matched differently.\n if (entriesEqual(entries, previous.current)) {\n return previous.current as MetadataQueryEntry<T, E>[];\n }\n previous.current = entries;\n return entries as MetadataQueryEntry<T, E>[];\n }, [files, queryFunction]);\n};\n\n/**\n * Read one file's metadata (MDX frontmatter) by path. Returns `undefined` when the\n * path has no metadata. Pass a type parameter for typed field access.\n *\n * **The store is keyed by ABSOLUTE module path** — `/app/content/post.mdx`, the same\n * identifier `fs`, `module.dynamicImport` and `<Include>` use — not by the\n * repo-relative path this doc claimed until R3-276. Keeping metadata in the file\n * space is what lets an app read a file's metadata and render that same file by the\n * same path (`sandbox/src/bundler/metadataKey.test.ts` pins it).\n *\n * A repo-relative path (`/content/post.mdx`) is accepted as a fallback: if the path\n * is not a key, it is retried under the app root via the shared\n * `underAppRoot` helper (R3-275). That is additive — it only turns a previous\n * `undefined` into a value — and it exists because the old doc told people to pass\n * exactly that form. A path a {@link MetadataSource} provided in some other key\n * space is looked up as given, unchanged.\n */\nexport const useFileMetadata = <T = Metadata>(path: string): T | undefined => {\n const files = useMetadataStore<T>();\n return useMemo(() => {\n const direct = files[path];\n if (direct !== undefined) return direct;\n // The fallback is for a path in the REPO-RELATIVE space. An already-app-rooted\n // path that missed is simply a miss: retrying it would consult `/app/app/…`,\n // which is not a key space anything writes — it would only ever hit by accident.\n if (path === APP_ROOT || path.startsWith(`${APP_ROOT}/`)) return undefined;\n return files[underAppRoot(path)];\n }, [path, files]);\n};\n\n/**\n * The raw, reactive metadata store: a map from file path to frontmatter. The\n * escape hatch for apps that want to render their own index rather than express it\n * as a path-returning query. Pass a type parameter for typed frontmatter values.\n */\nexport const useAllMetadata = <T = Metadata>(): FilesMetadata<T> => useMetadataStore<T>();\n\n/** The reactive state returned by {@link useObjectUrl}. */\nexport interface ObjectUrlState {\n /** The object URL once the bytes have loaded; `null` while loading or on error. */\n url: string | null;\n /** True while the file is being read. */\n loading: boolean;\n /** The {@link FsError} if the read failed (`not-found`, `unavailable`, …), else `null`. */\n error: FsError | null;\n}\n\n/**\n * Read a file from a mount into an **object URL** for `<img src>`, revoking it\n * automatically on unmount or when `mount`/`relPath` changes. This is the React\n * answer to \"an opaque-origin iframe can't fetch a mount path\": it reads the bytes\n * off the sandbox ZenFS ({@link openFs}) and hands you a URL to drop into an\n * `<img>`, and it owns the create/revoke lifecycle so you never leak a URL.\n *\n * Pass `null`/`undefined` for `mount` or `relPath` to mean \"nothing to load yet\"\n * (idle state, no read). For a ready-made element use `MountImage`.\n *\n * ```tsx\n * const { url, loading, error } = useObjectUrl(mount, 'photos/cat.png');\n * if (loading) return <Spinner />;\n * if (error || !url) return <span>missing</span>;\n * return <img src={url} alt=\"cat\" />;\n * ```\n */\nexport const useObjectUrl = (\n mount: SandboxMount | null | undefined,\n relPath: string | null | undefined,\n opts?: { type?: string },\n): ObjectUrlState => {\n const [state, setState] = useState<ObjectUrlState>({\n url: null,\n loading: Boolean(mount && relPath),\n error: null,\n });\n const type = opts?.type;\n // Key the effect on the mount's stable `path` (its object identity churns as the\n // mount set re-announces) plus the relPath and the optional type override.\n const mountPath = mount?.path;\n useEffect(() => {\n if (!mount || !relPath) {\n setState({ url: null, loading: false, error: null });\n return;\n }\n let alive = true;\n let revoke: (() => void) | null = null;\n setState({ url: null, loading: true, error: null });\n openFs(mount)\n .readObjectUrl(relPath, type ? { type } : undefined)\n .then((res) => {\n // Lost the race (unmounted / prop changed): revoke immediately, don't set.\n if (!alive) {\n res.revoke();\n return;\n }\n revoke = res.revoke;\n setState({ url: res.url, loading: false, error: null });\n })\n .catch((e) => {\n if (alive) setState({ url: null, loading: false, error: e as FsError });\n });\n return () => {\n alive = false;\n if (revoke) revoke();\n };\n // `mount` is intentionally tracked via its stable `mountPath`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mountPath, relPath, type]);\n return state;\n};\n"],"mappings":"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","names":[]}
package/dist/hooks.d.cts CHANGED
@@ -17,6 +17,16 @@ import './tasks.cjs';
17
17
  * returned array keeps its identity while the matches are unchanged, so it is safe
18
18
  * to use directly in downstream `useMemo`/`useEffect` dependency arrays.
19
19
  *
20
+ * A query may return RECORDS instead of bare paths (R3-276) — `{ path, ...extra }`
21
+ * — and the extra fields ride along on each entry, so something derived while
22
+ * selecting does not have to be recomputed downstream:
23
+ * ```ts
24
+ * const posts = useMetadataQuery<PostMeta, { year: string }>((files) =>
25
+ * Object.entries(files).map(([path, m]) => ({ path, year: m.date.slice(0, 4) })),
26
+ * );
27
+ * ```
28
+ * The store it queries is the nearest {@link MetadataSource}, else the host's.
29
+ *
20
30
  * Pass a type parameter to get typed frontmatter throughout:
21
31
  * ```ts
22
32
  * interface PostMeta { title: string; date: string; draft?: boolean }
@@ -28,11 +38,23 @@ import './tasks.cjs';
28
38
  * );
29
39
  * ```
30
40
  */
31
- declare const useMetadataQuery: <T = Metadata>(queryFunction: MetadataQueryFunction<T>) => MetadataQueryResult<T>;
41
+ declare const useMetadataQuery: <T = Metadata, E extends object = {}>(queryFunction: MetadataQueryFunction<T>) => MetadataQueryResult<T, E>;
32
42
  /**
33
- * Read one file's metadata (MDX frontmatter) by repo-relative path. Returns
34
- * `undefined` when the path has no metadata. Pass a type parameter for typed
35
- * field access.
43
+ * Read one file's metadata (MDX frontmatter) by path. Returns `undefined` when the
44
+ * path has no metadata. Pass a type parameter for typed field access.
45
+ *
46
+ * **The store is keyed by ABSOLUTE module path** — `/app/content/post.mdx`, the same
47
+ * identifier `fs`, `module.dynamicImport` and `<Include>` use — not by the
48
+ * repo-relative path this doc claimed until R3-276. Keeping metadata in the file
49
+ * space is what lets an app read a file's metadata and render that same file by the
50
+ * same path (`sandbox/src/bundler/metadataKey.test.ts` pins it).
51
+ *
52
+ * A repo-relative path (`/content/post.mdx`) is accepted as a fallback: if the path
53
+ * is not a key, it is retried under the app root via the shared
54
+ * `underAppRoot` helper (R3-275). That is additive — it only turns a previous
55
+ * `undefined` into a value — and it exists because the old doc told people to pass
56
+ * exactly that form. A path a {@link MetadataSource} provided in some other key
57
+ * space is looked up as given, unchanged.
36
58
  */
37
59
  declare const useFileMetadata: <T = Metadata>(path: string) => T | undefined;
38
60
  /**
package/dist/hooks.d.ts CHANGED
@@ -17,6 +17,16 @@ import './tasks.js';
17
17
  * returned array keeps its identity while the matches are unchanged, so it is safe
18
18
  * to use directly in downstream `useMemo`/`useEffect` dependency arrays.
19
19
  *
20
+ * A query may return RECORDS instead of bare paths (R3-276) — `{ path, ...extra }`
21
+ * — and the extra fields ride along on each entry, so something derived while
22
+ * selecting does not have to be recomputed downstream:
23
+ * ```ts
24
+ * const posts = useMetadataQuery<PostMeta, { year: string }>((files) =>
25
+ * Object.entries(files).map(([path, m]) => ({ path, year: m.date.slice(0, 4) })),
26
+ * );
27
+ * ```
28
+ * The store it queries is the nearest {@link MetadataSource}, else the host's.
29
+ *
20
30
  * Pass a type parameter to get typed frontmatter throughout:
21
31
  * ```ts
22
32
  * interface PostMeta { title: string; date: string; draft?: boolean }
@@ -28,11 +38,23 @@ import './tasks.js';
28
38
  * );
29
39
  * ```
30
40
  */
31
- declare const useMetadataQuery: <T = Metadata>(queryFunction: MetadataQueryFunction<T>) => MetadataQueryResult<T>;
41
+ declare const useMetadataQuery: <T = Metadata, E extends object = {}>(queryFunction: MetadataQueryFunction<T>) => MetadataQueryResult<T, E>;
32
42
  /**
33
- * Read one file's metadata (MDX frontmatter) by repo-relative path. Returns
34
- * `undefined` when the path has no metadata. Pass a type parameter for typed
35
- * field access.
43
+ * Read one file's metadata (MDX frontmatter) by path. Returns `undefined` when the
44
+ * path has no metadata. Pass a type parameter for typed field access.
45
+ *
46
+ * **The store is keyed by ABSOLUTE module path** — `/app/content/post.mdx`, the same
47
+ * identifier `fs`, `module.dynamicImport` and `<Include>` use — not by the
48
+ * repo-relative path this doc claimed until R3-276. Keeping metadata in the file
49
+ * space is what lets an app read a file's metadata and render that same file by the
50
+ * same path (`sandbox/src/bundler/metadataKey.test.ts` pins it).
51
+ *
52
+ * A repo-relative path (`/content/post.mdx`) is accepted as a fallback: if the path
53
+ * is not a key, it is retried under the app root via the shared
54
+ * `underAppRoot` helper (R3-275). That is additive — it only turns a previous
55
+ * `undefined` into a value — and it exists because the old doc told people to pass
56
+ * exactly that form. A path a {@link MetadataSource} provided in some other key
57
+ * space is looked up as given, unchanged.
36
58
  */
37
59
  declare const useFileMetadata: <T = Metadata>(path: string) => T | undefined;
38
60
  /**
package/dist/hooks.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import "./chunk-VHAA22YE.js";
2
- import { use, useEffect, useMemo, useRef, useState } from "react";
3
- import { TinkerableContext } from "./TinkerableContext";
2
+ import { APP_ROOT, underAppRoot } from "@immediately-run/platform-constants";
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { openFs } from "./fs";
5
+ import { useMetadataStore } from "./metadataSource";
5
6
  const entriesEqual = (a, b) => {
6
7
  if (a.length !== b.length) {
7
8
  return false;
@@ -10,17 +11,25 @@ const entriesEqual = (a, b) => {
10
11
  if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {
11
12
  return false;
12
13
  }
14
+ const ax = a[i];
15
+ const bx = b[i];
16
+ const keys = /* @__PURE__ */ new Set([...Object.keys(ax), ...Object.keys(bx)]);
17
+ for (const k of keys) {
18
+ if (k === "path" || k === "meta") continue;
19
+ if (ax[k] !== bx[k]) return false;
20
+ }
13
21
  }
14
22
  return true;
15
23
  };
16
24
  const useMetadataQuery = (queryFunction) => {
17
- const { filesMetadata } = use(TinkerableContext);
25
+ const files = useMetadataStore();
18
26
  const previous = useRef([]);
19
27
  return useMemo(() => {
20
- const files = filesMetadata ?? {};
21
28
  let entries;
22
29
  try {
23
- entries = queryFunction(files).map((path) => ({ path, meta: files[path] }));
30
+ entries = queryFunction(files).map(
31
+ (selected) => typeof selected === "string" ? { path: selected, meta: files[selected] } : { ...selected, path: selected.path, meta: files[selected.path] }
32
+ );
24
33
  } catch (error) {
25
34
  return { error };
26
35
  }
@@ -29,19 +38,18 @@ const useMetadataQuery = (queryFunction) => {
29
38
  }
30
39
  previous.current = entries;
31
40
  return entries;
32
- }, [filesMetadata, queryFunction]);
41
+ }, [files, queryFunction]);
33
42
  };
34
43
  const useFileMetadata = (path) => {
35
- const { filesMetadata } = use(TinkerableContext);
36
- return useMemo(
37
- () => (filesMetadata ?? {})[path],
38
- [path, filesMetadata]
39
- );
40
- };
41
- const useAllMetadata = () => {
42
- const { filesMetadata } = use(TinkerableContext);
43
- return filesMetadata ?? {};
44
+ const files = useMetadataStore();
45
+ return useMemo(() => {
46
+ const direct = files[path];
47
+ if (direct !== void 0) return direct;
48
+ if (path === APP_ROOT || path.startsWith(`${APP_ROOT}/`)) return void 0;
49
+ return files[underAppRoot(path)];
50
+ }, [path, files]);
44
51
  };
52
+ const useAllMetadata = () => useMetadataStore();
45
53
  const useObjectUrl = (mount, relPath, opts) => {
46
54
  const [state, setState] = useState({
47
55
  url: null,
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { use, useEffect, useMemo, useRef, useState } from 'react';\nimport { TinkerableContext } from './TinkerableContext';\nimport { openFs, type FsError } from './fs';\nimport type { SandboxMount } from './mounts';\nimport {\n FilesMetadata,\n Metadata,\n MetadataQueryEntry,\n MetadataQueryFunction,\n MetadataQueryResult,\n} from './sandboxTypes';\n\nconst entriesEqual = <T>(a: MetadataQueryEntry<T>[], b: MetadataQueryEntry<T>[]): boolean => {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n // Unchanged frontmatter keeps its object identity across `metadata-update`\n // merges, so an identity check on `meta` is a sound \"did this match change?\".\n if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {\n return false;\n }\n }\n return true;\n};\n\n/**\n * Query the file metadata store (MDX frontmatter) with a plain JS function.\n *\n * The query receives every file's frontmatter keyed by path and returns the paths\n * that match; the hook resolves each path back to its frontmatter and returns an\n * array of `{ path, meta }` entries — so a single call gives you everything you\n * filtered on, with no second lookup. A throwing query is reported as `{ error }`\n * rather than crashing the render.\n *\n * The query runs synchronously during render (no empty first frame), and the\n * returned array keeps its identity while the matches are unchanged, so it is safe\n * to use directly in downstream `useMemo`/`useEffect` dependency arrays.\n *\n * Pass a type parameter to get typed frontmatter throughout:\n * ```ts\n * interface PostMeta { title: string; date: string; draft?: boolean }\n * const posts = useMetadataQuery<PostMeta>((files) =>\n * Object.entries(files)\n * .filter(([, m]) => !m.draft)\n * .sort(([, a], [, b]) => b.date.localeCompare(a.date))\n * .map(([path]) => path),\n * );\n * ```\n */\nexport const useMetadataQuery = <T = Metadata>(\n queryFunction: MetadataQueryFunction<T>,\n): MetadataQueryResult<T> => {\n const { filesMetadata } = use(TinkerableContext);\n const previous = useRef<MetadataQueryEntry<T>[]>([]);\n return useMemo<MetadataQueryResult<T>>(() => {\n const files = (filesMetadata ?? {}) as FilesMetadata<T>;\n let entries: MetadataQueryEntry<T>[];\n try {\n entries = queryFunction(files).map((path) => ({ path, meta: files[path] }));\n } catch (error) {\n return { error };\n }\n // Preserve the prior array reference when nothing matched differently.\n if (entriesEqual(entries, previous.current)) {\n return previous.current;\n }\n previous.current = entries;\n return entries;\n }, [filesMetadata, queryFunction]);\n};\n\n/**\n * Read one file's metadata (MDX frontmatter) by repo-relative path. Returns\n * `undefined` when the path has no metadata. Pass a type parameter for typed\n * field access.\n */\nexport const useFileMetadata = <T = Metadata>(path: string): T | undefined => {\n const { filesMetadata } = use(TinkerableContext);\n return useMemo(\n () => (filesMetadata ?? {})[path] as T | undefined,\n [path, filesMetadata],\n );\n};\n\n/**\n * The raw, reactive metadata store: a map from file path to frontmatter. The\n * escape hatch for apps that want to render their own index rather than express it\n * as a path-returning query. Pass a type parameter for typed frontmatter values.\n */\nexport const useAllMetadata = <T = Metadata>(): FilesMetadata<T> => {\n const { filesMetadata } = use(TinkerableContext);\n return (filesMetadata ?? {}) as FilesMetadata<T>;\n};\n\n/** The reactive state returned by {@link useObjectUrl}. */\nexport interface ObjectUrlState {\n /** The object URL once the bytes have loaded; `null` while loading or on error. */\n url: string | null;\n /** True while the file is being read. */\n loading: boolean;\n /** The {@link FsError} if the read failed (`not-found`, `unavailable`, …), else `null`. */\n error: FsError | null;\n}\n\n/**\n * Read a file from a mount into an **object URL** for `<img src>`, revoking it\n * automatically on unmount or when `mount`/`relPath` changes. This is the React\n * answer to \"an opaque-origin iframe can't fetch a mount path\": it reads the bytes\n * off the sandbox ZenFS ({@link openFs}) and hands you a URL to drop into an\n * `<img>`, and it owns the create/revoke lifecycle so you never leak a URL.\n *\n * Pass `null`/`undefined` for `mount` or `relPath` to mean \"nothing to load yet\"\n * (idle state, no read). For a ready-made element use `MountImage`.\n *\n * ```tsx\n * const { url, loading, error } = useObjectUrl(mount, 'photos/cat.png');\n * if (loading) return <Spinner />;\n * if (error || !url) return <span>missing</span>;\n * return <img src={url} alt=\"cat\" />;\n * ```\n */\nexport const useObjectUrl = (\n mount: SandboxMount | null | undefined,\n relPath: string | null | undefined,\n opts?: { type?: string },\n): ObjectUrlState => {\n const [state, setState] = useState<ObjectUrlState>({\n url: null,\n loading: Boolean(mount && relPath),\n error: null,\n });\n const type = opts?.type;\n // Key the effect on the mount's stable `path` (its object identity churns as the\n // mount set re-announces) plus the relPath and the optional type override.\n const mountPath = mount?.path;\n useEffect(() => {\n if (!mount || !relPath) {\n setState({ url: null, loading: false, error: null });\n return;\n }\n let alive = true;\n let revoke: (() => void) | null = null;\n setState({ url: null, loading: true, error: null });\n openFs(mount)\n .readObjectUrl(relPath, type ? { type } : undefined)\n .then((res) => {\n // Lost the race (unmounted / prop changed): revoke immediately, don't set.\n if (!alive) {\n res.revoke();\n return;\n }\n revoke = res.revoke;\n setState({ url: res.url, loading: false, error: null });\n })\n .catch((e) => {\n if (alive) setState({ url: null, loading: false, error: e as FsError });\n });\n return () => {\n alive = false;\n if (revoke) revoke();\n };\n // `mount` is intentionally tracked via its stable `mountPath`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mountPath, relPath, type]);\n return state;\n};\n"],"mappings":";AAAA,SAAS,KAAK,WAAW,SAAS,QAAQ,gBAAgB;AAC1D,SAAS,yBAAyB;AAClC,SAAS,cAA4B;AAUrC,MAAM,eAAe,CAAI,GAA4B,MAAwC;AAC3F,MAAI,EAAE,WAAW,EAAE,QAAQ;AACzB,WAAO;AAAA,EACT;AACA,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAGjC,QAAI,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AA0BO,MAAM,mBAAmB,CAC9B,kBAC2B;AAC3B,QAAM,EAAE,cAAc,IAAI,IAAI,iBAAiB;AAC/C,QAAM,WAAW,OAAgC,CAAC,CAAC;AACnD,SAAO,QAAgC,MAAM;AAC3C,UAAM,QAAS,iBAAiB,CAAC;AACjC,QAAI;AACJ,QAAI;AACF,gBAAU,cAAc,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,MAAM,MAAM,IAAI,EAAE,EAAE;AAAA,IAC5E,SAAS,OAAO;AACd,aAAO,EAAE,MAAM;AAAA,IACjB;AAEA,QAAI,aAAa,SAAS,SAAS,OAAO,GAAG;AAC3C,aAAO,SAAS;AAAA,IAClB;AACA,aAAS,UAAU;AACnB,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,aAAa,CAAC;AACnC;AAOO,MAAM,kBAAkB,CAAe,SAAgC;AAC5E,QAAM,EAAE,cAAc,IAAI,IAAI,iBAAiB;AAC/C,SAAO;AAAA,IACL,OAAO,iBAAiB,CAAC,GAAG,IAAI;AAAA,IAChC,CAAC,MAAM,aAAa;AAAA,EACtB;AACF;AAOO,MAAM,iBAAiB,MAAsC;AAClE,QAAM,EAAE,cAAc,IAAI,IAAI,iBAAiB;AAC/C,SAAQ,iBAAiB,CAAC;AAC5B;AA6BO,MAAM,eAAe,CAC1B,OACA,SACA,SACmB;AACnB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyB;AAAA,IACjD,KAAK;AAAA,IACL,SAAS,QAAQ,SAAS,OAAO;AAAA,IACjC,OAAO;AAAA,EACT,CAAC;AACD,QAAM,OAAO,MAAM;AAGnB,QAAM,YAAY,OAAO;AACzB,YAAU,MAAM;AACd,QAAI,CAAC,SAAS,CAAC,SAAS;AACtB,eAAS,EAAE,KAAK,MAAM,SAAS,OAAO,OAAO,KAAK,CAAC;AACnD;AAAA,IACF;AACA,QAAI,QAAQ;AACZ,QAAI,SAA8B;AAClC,aAAS,EAAE,KAAK,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AAClD,WAAO,KAAK,EACT,cAAc,SAAS,OAAO,EAAE,KAAK,IAAI,MAAS,EAClD,KAAK,CAAC,QAAQ;AAEb,UAAI,CAAC,OAAO;AACV,YAAI,OAAO;AACX;AAAA,MACF;AACA,eAAS,IAAI;AACb,eAAS,EAAE,KAAK,IAAI,KAAK,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACxD,CAAC,EACA,MAAM,CAAC,MAAM;AACZ,UAAI,MAAO,UAAS,EAAE,KAAK,MAAM,SAAS,OAAO,OAAO,EAAa,CAAC;AAAA,IACxE,CAAC;AACH,WAAO,MAAM;AACX,cAAQ;AACR,UAAI,OAAQ,QAAO;AAAA,IACrB;AAAA,EAGF,GAAG,CAAC,WAAW,SAAS,IAAI,CAAC;AAC7B,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { APP_ROOT, underAppRoot } from '@immediately-run/platform-constants';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { openFs, type FsError } from './fs';\nimport { useMetadataStore } from './metadataSource';\nimport type { SandboxMount } from './mounts';\nimport {\n FilesMetadata,\n Metadata,\n MetadataQueryEntry,\n MetadataQueryFunction,\n MetadataQueryResult,\n} from './sandboxTypes';\n\nconst entriesEqual = <T>(\n a: MetadataQueryEntry<T, Record<string, unknown>>[],\n b: MetadataQueryEntry<T, Record<string, unknown>>[],\n): boolean => {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n // Unchanged frontmatter keeps its object identity across `metadata-update`\n // merges, so an identity check on `meta` is a sound \"did this match change?\".\n if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {\n return false;\n }\n // Extra fields a record-returning query computed (R3-276) are part of the\n // result, so a change in them is a change in the result — compared by value,\n // one level, because they are derived scalars, not the store's shared objects.\n const ax = a[i] as Record<string, unknown>;\n const bx = b[i] as Record<string, unknown>;\n const keys = new Set([...Object.keys(ax), ...Object.keys(bx)]);\n for (const k of keys) {\n if (k === 'path' || k === 'meta') continue;\n if (ax[k] !== bx[k]) return false;\n }\n }\n return true;\n};\n\n/**\n * Query the file metadata store (MDX frontmatter) with a plain JS function.\n *\n * The query receives every file's frontmatter keyed by path and returns the paths\n * that match; the hook resolves each path back to its frontmatter and returns an\n * array of `{ path, meta }` entries — so a single call gives you everything you\n * filtered on, with no second lookup. A throwing query is reported as `{ error }`\n * rather than crashing the render.\n *\n * The query runs synchronously during render (no empty first frame), and the\n * returned array keeps its identity while the matches are unchanged, so it is safe\n * to use directly in downstream `useMemo`/`useEffect` dependency arrays.\n *\n * A query may return RECORDS instead of bare paths (R3-276) — `{ path, ...extra }`\n * — and the extra fields ride along on each entry, so something derived while\n * selecting does not have to be recomputed downstream:\n * ```ts\n * const posts = useMetadataQuery<PostMeta, { year: string }>((files) =>\n * Object.entries(files).map(([path, m]) => ({ path, year: m.date.slice(0, 4) })),\n * );\n * ```\n * The store it queries is the nearest {@link MetadataSource}, else the host's.\n *\n * Pass a type parameter to get typed frontmatter throughout:\n * ```ts\n * interface PostMeta { title: string; date: string; draft?: boolean }\n * const posts = useMetadataQuery<PostMeta>((files) =>\n * Object.entries(files)\n * .filter(([, m]) => !m.draft)\n * .sort(([, a], [, b]) => b.date.localeCompare(a.date))\n * .map(([path]) => path),\n * );\n * ```\n */\nexport const useMetadataQuery = <T = Metadata, E extends object = {}>(\n queryFunction: MetadataQueryFunction<T>,\n): MetadataQueryResult<T, E> => {\n const files = useMetadataStore<T>();\n const previous = useRef<MetadataQueryEntry<T, Record<string, unknown>>[]>([]);\n return useMemo<MetadataQueryResult<T, E>>(() => {\n let entries: MetadataQueryEntry<T, Record<string, unknown>>[];\n try {\n // `path` and `meta` are applied AFTER the record's own fields: a query cannot\n // shadow the two the hook is responsible for, whatever it happened to name.\n entries = queryFunction(files).map((selected) =>\n typeof selected === 'string'\n ? { path: selected, meta: files[selected] }\n : { ...selected, path: selected.path, meta: files[selected.path] },\n );\n } catch (error) {\n return { error };\n }\n // Preserve the prior array reference when nothing matched differently.\n if (entriesEqual(entries, previous.current)) {\n return previous.current as MetadataQueryEntry<T, E>[];\n }\n previous.current = entries;\n return entries as MetadataQueryEntry<T, E>[];\n }, [files, queryFunction]);\n};\n\n/**\n * Read one file's metadata (MDX frontmatter) by path. Returns `undefined` when the\n * path has no metadata. Pass a type parameter for typed field access.\n *\n * **The store is keyed by ABSOLUTE module path** — `/app/content/post.mdx`, the same\n * identifier `fs`, `module.dynamicImport` and `<Include>` use — not by the\n * repo-relative path this doc claimed until R3-276. Keeping metadata in the file\n * space is what lets an app read a file's metadata and render that same file by the\n * same path (`sandbox/src/bundler/metadataKey.test.ts` pins it).\n *\n * A repo-relative path (`/content/post.mdx`) is accepted as a fallback: if the path\n * is not a key, it is retried under the app root via the shared\n * `underAppRoot` helper (R3-275). That is additive — it only turns a previous\n * `undefined` into a value — and it exists because the old doc told people to pass\n * exactly that form. A path a {@link MetadataSource} provided in some other key\n * space is looked up as given, unchanged.\n */\nexport const useFileMetadata = <T = Metadata>(path: string): T | undefined => {\n const files = useMetadataStore<T>();\n return useMemo(() => {\n const direct = files[path];\n if (direct !== undefined) return direct;\n // The fallback is for a path in the REPO-RELATIVE space. An already-app-rooted\n // path that missed is simply a miss: retrying it would consult `/app/app/…`,\n // which is not a key space anything writes — it would only ever hit by accident.\n if (path === APP_ROOT || path.startsWith(`${APP_ROOT}/`)) return undefined;\n return files[underAppRoot(path)];\n }, [path, files]);\n};\n\n/**\n * The raw, reactive metadata store: a map from file path to frontmatter. The\n * escape hatch for apps that want to render their own index rather than express it\n * as a path-returning query. Pass a type parameter for typed frontmatter values.\n */\nexport const useAllMetadata = <T = Metadata>(): FilesMetadata<T> => useMetadataStore<T>();\n\n/** The reactive state returned by {@link useObjectUrl}. */\nexport interface ObjectUrlState {\n /** The object URL once the bytes have loaded; `null` while loading or on error. */\n url: string | null;\n /** True while the file is being read. */\n loading: boolean;\n /** The {@link FsError} if the read failed (`not-found`, `unavailable`, …), else `null`. */\n error: FsError | null;\n}\n\n/**\n * Read a file from a mount into an **object URL** for `<img src>`, revoking it\n * automatically on unmount or when `mount`/`relPath` changes. This is the React\n * answer to \"an opaque-origin iframe can't fetch a mount path\": it reads the bytes\n * off the sandbox ZenFS ({@link openFs}) and hands you a URL to drop into an\n * `<img>`, and it owns the create/revoke lifecycle so you never leak a URL.\n *\n * Pass `null`/`undefined` for `mount` or `relPath` to mean \"nothing to load yet\"\n * (idle state, no read). For a ready-made element use `MountImage`.\n *\n * ```tsx\n * const { url, loading, error } = useObjectUrl(mount, 'photos/cat.png');\n * if (loading) return <Spinner />;\n * if (error || !url) return <span>missing</span>;\n * return <img src={url} alt=\"cat\" />;\n * ```\n */\nexport const useObjectUrl = (\n mount: SandboxMount | null | undefined,\n relPath: string | null | undefined,\n opts?: { type?: string },\n): ObjectUrlState => {\n const [state, setState] = useState<ObjectUrlState>({\n url: null,\n loading: Boolean(mount && relPath),\n error: null,\n });\n const type = opts?.type;\n // Key the effect on the mount's stable `path` (its object identity churns as the\n // mount set re-announces) plus the relPath and the optional type override.\n const mountPath = mount?.path;\n useEffect(() => {\n if (!mount || !relPath) {\n setState({ url: null, loading: false, error: null });\n return;\n }\n let alive = true;\n let revoke: (() => void) | null = null;\n setState({ url: null, loading: true, error: null });\n openFs(mount)\n .readObjectUrl(relPath, type ? { type } : undefined)\n .then((res) => {\n // Lost the race (unmounted / prop changed): revoke immediately, don't set.\n if (!alive) {\n res.revoke();\n return;\n }\n revoke = res.revoke;\n setState({ url: res.url, loading: false, error: null });\n })\n .catch((e) => {\n if (alive) setState({ url: null, loading: false, error: e as FsError });\n });\n return () => {\n alive = false;\n if (revoke) revoke();\n };\n // `mount` is intentionally tracked via its stable `mountPath`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mountPath, relPath, type]);\n return state;\n};\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -33,6 +33,7 @@ __reExport(index_exports, require("./linkSpace"), module.exports);
33
33
  __reExport(index_exports, require("./components/MountImage"), module.exports);
34
34
  __reExport(index_exports, require("./components/Routes"), module.exports);
35
35
  __reExport(index_exports, require("./hooks"), module.exports);
36
+ __reExport(index_exports, require("./metadataSource"), module.exports);
36
37
  __reExport(index_exports, require("./auth"), module.exports);
37
38
  __reExport(index_exports, require("./theme"), module.exports);
38
39
  __reExport(index_exports, require("./editorContext"), module.exports);
@@ -74,6 +75,7 @@ __reExport(index_exports, require("./safeContent"), module.exports);
74
75
  ...require("./components/MountImage"),
75
76
  ...require("./components/Routes"),
76
77
  ...require("./hooks"),
78
+ ...require("./metadataSource"),
77
79
  ...require("./auth"),
78
80
  ...require("./theme"),
79
81
  ...require("./editorContext"),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './linkSpace';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAQA,yBAA4B;AAC5B,0BAAc,0BATd;AAUA,0BAAc,uCAVd;AAWA,0BAAc,wBAXd;AAYA,0BAAc,oCAZd;AAaA,0BAAc,gCAbd;AAcA,0BAAc,oBAdd;AAeA,0BAAc,mBAfd;AAgBA,0BAAc,oBAhBd;AAiBA,0BAAc,4BAjBd;AAkBA,0BAAc,qBAlBd;AAmBA,0BAAc,yBAnBd;AAoBA,0BAAc,qBApBd;AAqBA,0BAAc,qBArBd;AAsBA,0BAAc,yBAtBd;AAuBA,0BAAc,sBAvBd;AAwBA,0BAAc,kBAxBd;AAyBA,0BAAc,kBAzBd;AA0BA,0BAAc,uBA1Bd;AA2BA,0BAAc,sBA3Bd;AA4BA,0BAAc,kBA5Bd;AA6BA,0BAAc,0BA7Bd;AA8BA,0BAAc,kBA9Bd;AA+BA,0BAAc,yBA/Bd;AAgCA,0BAAc,iBAhCd;AAiCA,0BAAc,oBAjCd;AAkCA,0BAAc,oBAlCd;AAmCA,0BAAc,qBAnCd;AAoCA,0BAAc,sBApCd;AAqCA,0BAAc,wBArCd;AAsCA,0BAAc,oBAtCd;AAuCA,0BAAc,sBAvCd;AAwCA,0BAAc,6BAxCd;AAyCA,0BAAc,2BAzCd;AA0CA,0BAAc,0BA1Cd;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './linkSpace';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\n// R3-276: the supported way for a viewer app to provide its own metadata store,\n// replacing a wholesale re-provision of `TinkerableContext` in app code.\nexport * from './metadataSource';\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAQA,yBAA4B;AAC5B,0BAAc,0BATd;AAUA,0BAAc,uCAVd;AAWA,0BAAc,wBAXd;AAYA,0BAAc,oCAZd;AAaA,0BAAc,gCAbd;AAcA,0BAAc,oBAdd;AAiBA,0BAAc,6BAjBd;AAkBA,0BAAc,mBAlBd;AAmBA,0BAAc,oBAnBd;AAoBA,0BAAc,4BApBd;AAqBA,0BAAc,qBArBd;AAsBA,0BAAc,yBAtBd;AAuBA,0BAAc,qBAvBd;AAwBA,0BAAc,qBAxBd;AAyBA,0BAAc,yBAzBd;AA0BA,0BAAc,sBA1Bd;AA2BA,0BAAc,kBA3Bd;AA4BA,0BAAc,kBA5Bd;AA6BA,0BAAc,uBA7Bd;AA8BA,0BAAc,sBA9Bd;AA+BA,0BAAc,kBA/Bd;AAgCA,0BAAc,0BAhCd;AAiCA,0BAAc,kBAjCd;AAkCA,0BAAc,yBAlCd;AAmCA,0BAAc,iBAnCd;AAoCA,0BAAc,oBApCd;AAqCA,0BAAc,oBArCd;AAsCA,0BAAc,qBAtCd;AAuCA,0BAAc,sBAvCd;AAwCA,0BAAc,wBAxCd;AAyCA,0BAAc,oBAzCd;AA0CA,0BAAc,sBA1Cd;AA2CA,0BAAc,6BA3Cd;AA4CA,0BAAc,2BA5Cd;AA6CA,0BAAc,0BA7Cd;","names":[]}
package/dist/index.d.cts CHANGED
@@ -9,6 +9,7 @@ export { FS_PREFIX, LinkSpace, LinkSpaceContext, ResolvedLinkTarget, normalizeAb
9
9
  export { MountImage, MountImageProps } from './components/MountImage.cjs';
10
10
  export { Route, RouteProps, Routes } from './components/Routes.cjs';
11
11
  export { ObjectUrlState, useAllMetadata, useFileMetadata, useMetadataQuery, useObjectUrl } from './hooks.cjs';
12
+ export { MetadataSource, MetadataSourceMode, MetadataSourceProps, useMetadataStore } from './metadataSource.cjs';
12
13
  export { AuthState, AuthStatus, SandboxUser, getAuthState, onAuthChange, useAuth } from './auth.cjs';
13
14
  export { HostTheme, getHostTheme, onHostThemeChange, setHostTheme, useHostTheme } from './theme.cjs';
14
15
  export { EditorContext, getEditorContext, onEditorContextChange, useEditorContext } from './editorContext.cjs';
@@ -35,7 +36,7 @@ export { ForwardedMarker, IR_MARKERS, IrMarkerName, isAllowedMarkerName, isIrMar
35
36
  export { ReadyState, __resetReady, __setReadyDeps, getReadyState, onReady, reportReady } from './ready.cjs';
36
37
  export { LOADING_TIMINGS, LoadingRegion, Skeleton, SkeletonArchetype, SkeletonRow, Spinner } from './loading.cjs';
37
38
  export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStream } from './protocolStream.cjs';
38
- export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.cjs';
39
+ export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryRecord, MetadataQueryResult, ModuleExports } from './sandboxTypes.cjs';
39
40
  export { SafeContent, SafeContentProps } from './safeContent/SafeContent.cjs';
40
41
  export { RenderMdastOptions, SafeContentComponents, renderMdast } from './safeContent/renderMdast.cjs';
41
42
  export { ParseSafeMdastOptions, SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.cjs';
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export { FS_PREFIX, LinkSpace, LinkSpaceContext, ResolvedLinkTarget, normalizeAb
9
9
  export { MountImage, MountImageProps } from './components/MountImage.js';
10
10
  export { Route, RouteProps, Routes } from './components/Routes.js';
11
11
  export { ObjectUrlState, useAllMetadata, useFileMetadata, useMetadataQuery, useObjectUrl } from './hooks.js';
12
+ export { MetadataSource, MetadataSourceMode, MetadataSourceProps, useMetadataStore } from './metadataSource.js';
12
13
  export { AuthState, AuthStatus, SandboxUser, getAuthState, onAuthChange, useAuth } from './auth.js';
13
14
  export { HostTheme, getHostTheme, onHostThemeChange, setHostTheme, useHostTheme } from './theme.js';
14
15
  export { EditorContext, getEditorContext, onEditorContextChange, useEditorContext } from './editorContext.js';
@@ -35,7 +36,7 @@ export { ForwardedMarker, IR_MARKERS, IrMarkerName, isAllowedMarkerName, isIrMar
35
36
  export { ReadyState, __resetReady, __setReadyDeps, getReadyState, onReady, reportReady } from './ready.js';
36
37
  export { LOADING_TIMINGS, LoadingRegion, Skeleton, SkeletonArchetype, SkeletonRow, Spinner } from './loading.js';
37
38
  export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStream } from './protocolStream.js';
38
- export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.js';
39
+ export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryRecord, MetadataQueryResult, ModuleExports } from './sandboxTypes.js';
39
40
  export { SafeContent, SafeContentProps } from './safeContent/SafeContent.js';
40
41
  export { RenderMdastOptions, SafeContentComponents, renderMdast } from './safeContent/renderMdast.js';
41
42
  export { ParseSafeMdastOptions, SafeMdastNode, SafeMdxAttribute, parseSafeMdast } from './safeContent/parseSafeMdast.js';
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ export * from "./linkSpace";
10
10
  export * from "./components/MountImage";
11
11
  export * from "./components/Routes";
12
12
  export * from "./hooks";
13
+ export * from "./metadataSource";
13
14
  export * from "./auth";
14
15
  export * from "./theme";
15
16
  export * from "./editorContext";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './linkSpace';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,mBAAmB;AAC5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\n// Only the component is public. `stripFrontmatter`/`appMountRelative` are module-level\n// exports so they can be unit-tested directly, NOT public API — the SDK's surface is\n// backwards-compatible forever, so an internal helper exported for a test's convenience is a\n// permanent commitment made for the wrong reason.\nexport { SafeInclude } from './components/SafeInclude';\nexport * from './sourceCache';\nexport * from './components/MDXComponents';\nexport * from './linkSpace';\nexport * from './components/MountImage';\nexport * from './components/Routes';\nexport * from './hooks'\n// R3-276: the supported way for a viewer app to provide its own metadata store,\n// replacing a wholesale re-provision of `TinkerableContext` in app code.\nexport * from './metadataSource';\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './region';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './dnd';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './llm';\nexport * from './diagnostics';\nexport * from './vcs';\nexport * from './onFsChange';\nexport * from './fs';\nexport * from './debug';\nexport * from './tasks';\nexport * from './launch';\nexport * from './runtime';\nexport * from './irMarkers';\nexport * from './ready';\nexport * from './loading';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\nexport * from './safeContent';\n"],"mappings":";AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAKd,SAAS,mBAAmB;AAC5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
package/dist/ipc.cjs CHANGED
@@ -26,8 +26,10 @@ __export(ipc_exports, {
26
26
  module.exports = __toCommonJS(ipc_exports);
27
27
  var import_react = require("react");
28
28
  var import_sandboxUtils = require("./sandboxUtils");
29
+ var import_protocol = require("./generated/protocol");
30
+ var import_protocolSchemes = require("./protocolSchemes");
29
31
  const postToRegion = async (region, data) => {
30
- const res = await (0, import_sandboxUtils.protocolRequest)("ipc", "post", [{ to: region, msg: data }]);
32
+ const res = await (0, import_sandboxUtils.protocolRequest)(import_protocolSchemes.SCHEMES[import_protocol.PROTOCOL_IPC], "post", [{ to: region, msg: data }]);
31
33
  if (!res || res.ok !== true) {
32
34
  const err = new Error(res?.message ?? "ipc post failed");
33
35
  err.code = res?.code ?? "unknown";
@@ -35,7 +37,7 @@ const postToRegion = async (region, data) => {
35
37
  }
36
38
  };
37
39
  const revealRegion = async (region) => {
38
- const res = await (0, import_sandboxUtils.protocolRequest)("ipc", "reveal", [{ to: region }]);
40
+ const res = await (0, import_sandboxUtils.protocolRequest)(import_protocolSchemes.SCHEMES[import_protocol.PROTOCOL_IPC], "reveal", [{ to: region }]);
39
41
  if (!res || res.ok !== true) {
40
42
  const err = new Error(res?.message ?? "ipc reveal failed");
41
43
  err.code = res?.code ?? "unknown";
@@ -43,7 +45,7 @@ const revealRegion = async (region) => {
43
45
  }
44
46
  };
45
47
  const onRegionMessage = (listener) => (0, import_sandboxUtils.addListener)(
46
- "region-message",
48
+ import_protocol.REGION_MESSAGE,
47
49
  (m) => listener({ from: m.from, data: m.data })
48
50
  );
49
51
  const useRegionMessage = () => {
package/dist/ipc.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ipc.ts"],"sourcesContent":["// Inter-app messaging (UI_AS_APPS_SPEC §5.6) — L2: chrome panels/regions talk to\n// each other across an edge BOTH sides declared in their bindings (sender `ipc.to`\n// + the `ipc` capability; receiver `ipc.accepts`). The host enforces both halves\n// and attaches an unspoofable `from`; you still treat the payload as untrusted.\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, addListener } from './sandboxUtils';\n\n/** A message delivered to this region. `from` is the SENDER's region, attached by\n * the host (unspoofable, T19); `data` is the sender-provided payload. */\nexport interface RegionMessage {\n from: string;\n data: unknown;\n}\n\n/**\n * Send a message to another region. Resolves when delivered; rejects (`forbidden`)\n * if the edge isn't two-sided-consented, the target isn't mounted, or this app\n * lacks the `ipc` capability.\n */\nexport const postToRegion = async (region: string, data: unknown): Promise<void> => {\n const res = (await protocolRequest('ipc', 'post', [{ to: region, msg: data }])) 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 ?? 'ipc post failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/**\n * Ask the host to bring the user to `region` — the `reveal`-class method of\n * UI_AS_APPS_SPEC §4.1's focus model (R3-243). On mobile that is the column\n * transition (the panel's carousel advances to the main pane); on desktop a focus\n * move. Use it when a tap in your panel produces something to look at in another\n * column, right after the `postToRegion` that sends it there:\n *\n * ```ts\n * onClick={() => { // a REAL user gesture\n * void postToRegion('stage.conversation', { type: 'select-conversation', id });\n * void revealRegion('stage.conversation');\n * }}\n * ```\n *\n * **Call it synchronously inside a user-gesture handler.** The host reads its own\n * transient user activation and refuses without one — an app flipping columns on a\n * timer is the attention-steal primitive §4.1 is written against. It reaches only\n * regions you already have a two-sided IPC edge to, and only within the active\n * activity, so it can neither jump the user into an unrelated surface nor pull\n * focus back to the caller's own column.\n *\n * Resolves once the host has *considered* the request — deliberately NOT reporting\n * whether focus actually moved. Whether the user is moved is the host's call\n * (activation, rate limit, already-there), and an app that could observe the\n * refusal would be tempted to retry, which is the behaviour the gate exists to\n * stop. Rejects only on a genuine authorization failure (`forbidden`): no `ipc`\n * capability, or no declared edge to `region`.\n */\nexport const revealRegion = async (region: string): Promise<void> => {\n const res = (await protocolRequest('ipc', 'reveal', [{ to: region }])) 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 ?? 'ipc reveal failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/** Subscribe to inbound region messages. Returns an unsubscribe fn. */\nexport const onRegionMessage = (listener: (msg: RegionMessage) => void): (() => void) =>\n addListener('region-message', (m: { from: string; data: unknown }) =>\n listener({ from: m.from, data: m.data }),\n );\n\n/** React hook: the most recent inbound region message (or `null`). */\nexport const useRegionMessage = (): RegionMessage | null => {\n const [msg, setMsg] = useState<RegionMessage | null>(null);\n useEffect(() => onRegionMessage(setMsg), []);\n return msg;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,mBAAoC;AACpC,0BAA6C;AActC,MAAM,eAAe,OAAO,QAAgB,SAAiC;AAClF,QAAM,MAAO,UAAM,qCAAgB,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,KAAK,KAAK,CAAC,CAAC;AAI7E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,iBAAiB;AACvD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AA8BO,MAAM,eAAe,OAAO,WAAkC;AACnE,QAAM,MAAO,UAAM,qCAAgB,OAAO,UAAU,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC;AAIpE,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,mBAAmB;AACzD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AAGO,MAAM,kBAAkB,CAAC,iBAC9B;AAAA,EAAY;AAAA,EAAkB,CAAC,MAC7B,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,CAAC;AACzC;AAGK,MAAM,mBAAmB,MAA4B;AAC1D,QAAM,CAAC,KAAK,MAAM,QAAI,uBAA+B,IAAI;AACzD,8BAAU,MAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC;AAC3C,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/ipc.ts"],"sourcesContent":["// Inter-app messaging (UI_AS_APPS_SPEC §5.6) — L2: chrome panels/regions talk to\n// each other across an edge BOTH sides declared in their bindings (sender `ipc.to`\n// + the `ipc` capability; receiver `ipc.accepts`). The host enforces both halves\n// and attaches an unspoofable `from`; you still treat the payload as untrusted.\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, addListener } from './sandboxUtils';\nimport { PROTOCOL_IPC, REGION_MESSAGE } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** A message delivered to this region. `from` is the SENDER's region, attached by\n * the host (unspoofable, T19); `data` is the sender-provided payload. */\nexport interface RegionMessage {\n from: string;\n data: unknown;\n}\n\n/**\n * Send a message to another region. Resolves when delivered; rejects (`forbidden`)\n * if the edge isn't two-sided-consented, the target isn't mounted, or this app\n * lacks the `ipc` capability.\n */\nexport const postToRegion = async (region: string, data: unknown): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_IPC], 'post', [{ to: region, msg: data }])) 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 ?? 'ipc post failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/**\n * Ask the host to bring the user to `region` — the `reveal`-class method of\n * UI_AS_APPS_SPEC §4.1's focus model (R3-243). On mobile that is the column\n * transition (the panel's carousel advances to the main pane); on desktop a focus\n * move. Use it when a tap in your panel produces something to look at in another\n * column, right after the `postToRegion` that sends it there:\n *\n * ```ts\n * onClick={() => { // a REAL user gesture\n * void postToRegion('stage.conversation', { type: 'select-conversation', id });\n * void revealRegion('stage.conversation');\n * }}\n * ```\n *\n * **Call it synchronously inside a user-gesture handler.** The host reads its own\n * transient user activation and refuses without one — an app flipping columns on a\n * timer is the attention-steal primitive §4.1 is written against. It reaches only\n * regions you already have a two-sided IPC edge to, and only within the active\n * activity, so it can neither jump the user into an unrelated surface nor pull\n * focus back to the caller's own column.\n *\n * Resolves once the host has *considered* the request — deliberately NOT reporting\n * whether focus actually moved. Whether the user is moved is the host's call\n * (activation, rate limit, already-there), and an app that could observe the\n * refusal would be tempted to retry, which is the behaviour the gate exists to\n * stop. Rejects only on a genuine authorization failure (`forbidden`): no `ipc`\n * capability, or no declared edge to `region`.\n */\nexport const revealRegion = async (region: string): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_IPC], 'reveal', [{ to: region }])) 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 ?? 'ipc reveal failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/** Subscribe to inbound region messages. Returns an unsubscribe fn. */\nexport const onRegionMessage = (listener: (msg: RegionMessage) => void): (() => void) =>\n addListener(REGION_MESSAGE, (m: { from: string; data: unknown }) =>\n listener({ from: m.from, data: m.data }),\n );\n\n/** React hook: the most recent inbound region message (or `null`). */\nexport const useRegionMessage = (): RegionMessage | null => {\n const [msg, setMsg] = useState<RegionMessage | null>(null);\n useEffect(() => onRegionMessage(setMsg), []);\n return msg;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,mBAAoC;AACpC,0BAA6C;AAC7C,sBAA6C;AAC7C,6BAAwB;AAcjB,MAAM,eAAe,OAAO,QAAgB,SAAiC;AAClF,QAAM,MAAO,UAAM,qCAAgB,+BAAQ,4BAAY,GAAG,QAAQ,CAAC,EAAE,IAAI,QAAQ,KAAK,KAAK,CAAC,CAAC;AAI7F,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,iBAAiB;AACvD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AA8BO,MAAM,eAAe,OAAO,WAAkC;AACnE,QAAM,MAAO,UAAM,qCAAgB,+BAAQ,4BAAY,GAAG,UAAU,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC;AAIpF,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,mBAAmB;AACzD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AAGO,MAAM,kBAAkB,CAAC,iBAC9B;AAAA,EAAY;AAAA,EAAgB,CAAC,MAC3B,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,CAAC;AACzC;AAGK,MAAM,mBAAmB,MAA4B;AAC1D,QAAM,CAAC,KAAK,MAAM,QAAI,uBAA+B,IAAI;AACzD,8BAAU,MAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC;AAC3C,SAAO;AACT;","names":[]}
package/dist/ipc.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { useEffect, useState } from "react";
3
3
  import { protocolRequest, addListener } from "./sandboxUtils";
4
+ import { PROTOCOL_IPC, REGION_MESSAGE } from "./generated/protocol";
5
+ import { SCHEMES } from "./protocolSchemes";
4
6
  const postToRegion = async (region, data) => {
5
- const res = await protocolRequest("ipc", "post", [{ to: region, msg: data }]);
7
+ const res = await protocolRequest(SCHEMES[PROTOCOL_IPC], "post", [{ to: region, msg: data }]);
6
8
  if (!res || res.ok !== true) {
7
9
  const err = new Error(res?.message ?? "ipc post failed");
8
10
  err.code = res?.code ?? "unknown";
@@ -10,7 +12,7 @@ const postToRegion = async (region, data) => {
10
12
  }
11
13
  };
12
14
  const revealRegion = async (region) => {
13
- const res = await protocolRequest("ipc", "reveal", [{ to: region }]);
15
+ const res = await protocolRequest(SCHEMES[PROTOCOL_IPC], "reveal", [{ to: region }]);
14
16
  if (!res || res.ok !== true) {
15
17
  const err = new Error(res?.message ?? "ipc reveal failed");
16
18
  err.code = res?.code ?? "unknown";
@@ -18,7 +20,7 @@ const revealRegion = async (region) => {
18
20
  }
19
21
  };
20
22
  const onRegionMessage = (listener) => addListener(
21
- "region-message",
23
+ REGION_MESSAGE,
22
24
  (m) => listener({ from: m.from, data: m.data })
23
25
  );
24
26
  const useRegionMessage = () => {
package/dist/ipc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ipc.ts"],"sourcesContent":["// Inter-app messaging (UI_AS_APPS_SPEC §5.6) — L2: chrome panels/regions talk to\n// each other across an edge BOTH sides declared in their bindings (sender `ipc.to`\n// + the `ipc` capability; receiver `ipc.accepts`). The host enforces both halves\n// and attaches an unspoofable `from`; you still treat the payload as untrusted.\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, addListener } from './sandboxUtils';\n\n/** A message delivered to this region. `from` is the SENDER's region, attached by\n * the host (unspoofable, T19); `data` is the sender-provided payload. */\nexport interface RegionMessage {\n from: string;\n data: unknown;\n}\n\n/**\n * Send a message to another region. Resolves when delivered; rejects (`forbidden`)\n * if the edge isn't two-sided-consented, the target isn't mounted, or this app\n * lacks the `ipc` capability.\n */\nexport const postToRegion = async (region: string, data: unknown): Promise<void> => {\n const res = (await protocolRequest('ipc', 'post', [{ to: region, msg: data }])) 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 ?? 'ipc post failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/**\n * Ask the host to bring the user to `region` — the `reveal`-class method of\n * UI_AS_APPS_SPEC §4.1's focus model (R3-243). On mobile that is the column\n * transition (the panel's carousel advances to the main pane); on desktop a focus\n * move. Use it when a tap in your panel produces something to look at in another\n * column, right after the `postToRegion` that sends it there:\n *\n * ```ts\n * onClick={() => { // a REAL user gesture\n * void postToRegion('stage.conversation', { type: 'select-conversation', id });\n * void revealRegion('stage.conversation');\n * }}\n * ```\n *\n * **Call it synchronously inside a user-gesture handler.** The host reads its own\n * transient user activation and refuses without one — an app flipping columns on a\n * timer is the attention-steal primitive §4.1 is written against. It reaches only\n * regions you already have a two-sided IPC edge to, and only within the active\n * activity, so it can neither jump the user into an unrelated surface nor pull\n * focus back to the caller's own column.\n *\n * Resolves once the host has *considered* the request — deliberately NOT reporting\n * whether focus actually moved. Whether the user is moved is the host's call\n * (activation, rate limit, already-there), and an app that could observe the\n * refusal would be tempted to retry, which is the behaviour the gate exists to\n * stop. Rejects only on a genuine authorization failure (`forbidden`): no `ipc`\n * capability, or no declared edge to `region`.\n */\nexport const revealRegion = async (region: string): Promise<void> => {\n const res = (await protocolRequest('ipc', 'reveal', [{ to: region }])) 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 ?? 'ipc reveal failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/** Subscribe to inbound region messages. Returns an unsubscribe fn. */\nexport const onRegionMessage = (listener: (msg: RegionMessage) => void): (() => void) =>\n addListener('region-message', (m: { from: string; data: unknown }) =>\n listener({ from: m.from, data: m.data }),\n );\n\n/** React hook: the most recent inbound region message (or `null`). */\nexport const useRegionMessage = (): RegionMessage | null => {\n const [msg, setMsg] = useState<RegionMessage | null>(null);\n useEffect(() => onRegionMessage(setMsg), []);\n return msg;\n};\n"],"mappings":";AAIA,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB,mBAAmB;AActC,MAAM,eAAe,OAAO,QAAgB,SAAiC;AAClF,QAAM,MAAO,MAAM,gBAAgB,OAAO,QAAQ,CAAC,EAAE,IAAI,QAAQ,KAAK,KAAK,CAAC,CAAC;AAI7E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,iBAAiB;AACvD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AA8BO,MAAM,eAAe,OAAO,WAAkC;AACnE,QAAM,MAAO,MAAM,gBAAgB,OAAO,UAAU,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC;AAIpE,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,mBAAmB;AACzD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AAGO,MAAM,kBAAkB,CAAC,aAC9B;AAAA,EAAY;AAAA,EAAkB,CAAC,MAC7B,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,CAAC;AACzC;AAGK,MAAM,mBAAmB,MAA4B;AAC1D,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AACzD,YAAU,MAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC;AAC3C,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/ipc.ts"],"sourcesContent":["// Inter-app messaging (UI_AS_APPS_SPEC §5.6) — L2: chrome panels/regions talk to\n// each other across an edge BOTH sides declared in their bindings (sender `ipc.to`\n// + the `ipc` capability; receiver `ipc.accepts`). The host enforces both halves\n// and attaches an unspoofable `from`; you still treat the payload as untrusted.\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, addListener } from './sandboxUtils';\nimport { PROTOCOL_IPC, REGION_MESSAGE } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** A message delivered to this region. `from` is the SENDER's region, attached by\n * the host (unspoofable, T19); `data` is the sender-provided payload. */\nexport interface RegionMessage {\n from: string;\n data: unknown;\n}\n\n/**\n * Send a message to another region. Resolves when delivered; rejects (`forbidden`)\n * if the edge isn't two-sided-consented, the target isn't mounted, or this app\n * lacks the `ipc` capability.\n */\nexport const postToRegion = async (region: string, data: unknown): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_IPC], 'post', [{ to: region, msg: data }])) 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 ?? 'ipc post failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/**\n * Ask the host to bring the user to `region` — the `reveal`-class method of\n * UI_AS_APPS_SPEC §4.1's focus model (R3-243). On mobile that is the column\n * transition (the panel's carousel advances to the main pane); on desktop a focus\n * move. Use it when a tap in your panel produces something to look at in another\n * column, right after the `postToRegion` that sends it there:\n *\n * ```ts\n * onClick={() => { // a REAL user gesture\n * void postToRegion('stage.conversation', { type: 'select-conversation', id });\n * void revealRegion('stage.conversation');\n * }}\n * ```\n *\n * **Call it synchronously inside a user-gesture handler.** The host reads its own\n * transient user activation and refuses without one — an app flipping columns on a\n * timer is the attention-steal primitive §4.1 is written against. It reaches only\n * regions you already have a two-sided IPC edge to, and only within the active\n * activity, so it can neither jump the user into an unrelated surface nor pull\n * focus back to the caller's own column.\n *\n * Resolves once the host has *considered* the request — deliberately NOT reporting\n * whether focus actually moved. Whether the user is moved is the host's call\n * (activation, rate limit, already-there), and an app that could observe the\n * refusal would be tempted to retry, which is the behaviour the gate exists to\n * stop. Rejects only on a genuine authorization failure (`forbidden`): no `ipc`\n * capability, or no declared edge to `region`.\n */\nexport const revealRegion = async (region: string): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_IPC], 'reveal', [{ to: region }])) 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 ?? 'ipc reveal failed') as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n};\n\n/** Subscribe to inbound region messages. Returns an unsubscribe fn. */\nexport const onRegionMessage = (listener: (msg: RegionMessage) => void): (() => void) =>\n addListener(REGION_MESSAGE, (m: { from: string; data: unknown }) =>\n listener({ from: m.from, data: m.data }),\n );\n\n/** React hook: the most recent inbound region message (or `null`). */\nexport const useRegionMessage = (): RegionMessage | null => {\n const [msg, setMsg] = useState<RegionMessage | null>(null);\n useEffect(() => onRegionMessage(setMsg), []);\n return msg;\n};\n"],"mappings":";AAIA,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB,mBAAmB;AAC7C,SAAS,cAAc,sBAAsB;AAC7C,SAAS,eAAe;AAcjB,MAAM,eAAe,OAAO,QAAgB,SAAiC;AAClF,QAAM,MAAO,MAAM,gBAAgB,QAAQ,YAAY,GAAG,QAAQ,CAAC,EAAE,IAAI,QAAQ,KAAK,KAAK,CAAC,CAAC;AAI7F,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,iBAAiB;AACvD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AA8BO,MAAM,eAAe,OAAO,WAAkC;AACnE,QAAM,MAAO,MAAM,gBAAgB,QAAQ,YAAY,GAAG,UAAU,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC;AAIpF,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,mBAAmB;AACzD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACF;AAGO,MAAM,kBAAkB,CAAC,aAC9B;AAAA,EAAY;AAAA,EAAgB,CAAC,MAC3B,SAAS,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,CAAC;AACzC;AAGK,MAAM,mBAAmB,MAA4B;AAC1D,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AACzD,YAAU,MAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC;AAC3C,SAAO;AACT;","names":[]}