@immediately-run/sdk 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -3
- package/dist/MDXProvider.cjs.map +1 -1
- package/dist/MDXProvider.d.cts +4 -0
- package/dist/MDXProvider.d.ts +4 -0
- package/dist/MDXProvider.js.map +1 -1
- package/dist/RoutingSpec.cjs.map +1 -1
- package/dist/RoutingSpec.d.cts +20 -3
- package/dist/RoutingSpec.d.ts +20 -3
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js.map +1 -1
- package/dist/boot.cjs +17 -7
- package/dist/boot.cjs.map +1 -1
- package/dist/boot.d.cts +28 -4
- package/dist/boot.d.ts +28 -4
- package/dist/boot.js +16 -7
- package/dist/boot.js.map +1 -1
- package/dist/components/Include.cjs.map +1 -1
- package/dist/components/Include.d.cts +7 -0
- package/dist/components/Include.d.ts +7 -0
- package/dist/components/Include.js.map +1 -1
- package/dist/components/MDXComponents.cjs.map +1 -1
- package/dist/components/MDXComponents.d.cts +6 -0
- package/dist/components/MDXComponents.d.ts +6 -0
- package/dist/components/MDXComponents.js.map +1 -1
- package/dist/components/Routes.cjs +59 -0
- package/dist/components/Routes.cjs.map +1 -0
- package/dist/components/Routes.d.cts +34 -0
- package/dist/components/Routes.d.ts +34 -0
- package/dist/components/Routes.js +34 -0
- package/dist/components/Routes.js.map +1 -0
- package/dist/contribute.cjs.map +1 -1
- package/dist/contribute.d.cts +2 -0
- package/dist/contribute.d.ts +2 -0
- package/dist/contribute.js.map +1 -1
- package/dist/debug.cjs +168 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.cts +22 -0
- package/dist/debug.d.ts +22 -0
- package/dist/debug.js +141 -0
- package/dist/debug.js.map +1 -0
- package/dist/diagnostics.cjs.map +1 -1
- package/dist/diagnostics.d.cts +3 -0
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/formFactor.cjs.map +1 -1
- package/dist/formFactor.d.cts +2 -0
- package/dist/formFactor.d.ts +2 -0
- package/dist/formFactor.js.map +1 -1
- package/dist/hooks.cjs +27 -28
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +39 -4
- package/dist/hooks.d.ts +39 -4
- package/dist/hooks.js +27 -29
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/irMarkers.cjs.map +1 -1
- package/dist/irMarkers.d.cts +1 -0
- package/dist/irMarkers.d.ts +1 -0
- package/dist/irMarkers.js.map +1 -1
- package/dist/llm.cjs.map +1 -1
- package/dist/llm.d.cts +5 -0
- package/dist/llm.d.ts +5 -0
- package/dist/llm.js.map +1 -1
- package/dist/loading.cjs +186 -0
- package/dist/loading.cjs.map +1 -0
- package/dist/loading.d.cts +48 -0
- package/dist/loading.d.ts +48 -0
- package/dist/loading.js +162 -0
- package/dist/loading.js.map +1 -0
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +3 -1
- package/dist/mounts.d.ts +3 -1
- package/dist/mounts.js.map +1 -1
- package/dist/netFetch.cjs.map +1 -1
- package/dist/netFetch.d.cts +2 -0
- package/dist/netFetch.d.ts +2 -0
- package/dist/netFetch.js.map +1 -1
- package/dist/onFsChange.cjs.map +1 -1
- package/dist/onFsChange.d.cts +1 -0
- package/dist/onFsChange.d.ts +1 -0
- package/dist/onFsChange.js.map +1 -1
- package/dist/protocolStream.cjs.map +1 -1
- package/dist/protocolStream.d.cts +3 -0
- package/dist/protocolStream.d.ts +3 -0
- package/dist/protocolStream.js.map +1 -1
- package/dist/ready.cjs.map +1 -1
- package/dist/ready.d.cts +7 -0
- package/dist/ready.d.ts +7 -0
- package/dist/ready.js.map +1 -1
- package/dist/routeMatch.cjs +72 -0
- package/dist/routeMatch.cjs.map +1 -0
- package/dist/routeMatch.d.cts +19 -0
- package/dist/routeMatch.d.ts +19 -0
- package/dist/routeMatch.js +46 -0
- package/dist/routeMatch.js.map +1 -0
- package/dist/routing.cjs +35 -14
- package/dist/routing.cjs.map +1 -1
- package/dist/routing.d.cts +33 -4
- package/dist/routing.d.ts +33 -4
- package/dist/routing.js +32 -14
- package/dist/routing.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +1 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js.map +1 -1
- package/dist/sandboxTypes.cjs.map +1 -1
- package/dist/sandboxTypes.d.cts +30 -7
- package/dist/sandboxTypes.d.ts +30 -7
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -2
package/dist/debug.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createPushChannel } from "./pushChannel";
|
|
2
|
+
import { sendMessage, addListener } from "./sandboxUtils";
|
|
3
|
+
const enabledChannel = createPushChannel({
|
|
4
|
+
pushType: "debug-enabled",
|
|
5
|
+
requestType: "request-debug-enabled",
|
|
6
|
+
initial: false,
|
|
7
|
+
parse: (msg) => typeof msg.enabled === "boolean" ? msg.enabled : void 0
|
|
8
|
+
});
|
|
9
|
+
const isDebugEnabled = () => enabledChannel.get();
|
|
10
|
+
const useDebugEnabled = () => enabledChannel.use();
|
|
11
|
+
const MAX_DATA_BYTES = 16 * 1024;
|
|
12
|
+
function safeData(data) {
|
|
13
|
+
if (data === void 0) return void 0;
|
|
14
|
+
try {
|
|
15
|
+
const json = JSON.stringify(data);
|
|
16
|
+
if (json === void 0) return "[unserializable]";
|
|
17
|
+
if (json.length > MAX_DATA_BYTES) return `[truncated ${json.length}B]`;
|
|
18
|
+
return JSON.parse(json);
|
|
19
|
+
} catch {
|
|
20
|
+
return "[unserializable]";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function log(level, message, data) {
|
|
24
|
+
if (!enabledChannel.get()) return;
|
|
25
|
+
try {
|
|
26
|
+
sendMessage("debug-log", { level, message: String(message), data: safeData(data) });
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const ATTR_ALLOW = /* @__PURE__ */ new Set(["role", "aria-hidden", "data-theme", "data-active", "href", "type", "hidden"]);
|
|
31
|
+
const MAX_NODES = 2e3;
|
|
32
|
+
const MAX_DEPTH = 25;
|
|
33
|
+
const MAX_TEXT = 200;
|
|
34
|
+
function round(n) {
|
|
35
|
+
return Math.round(n);
|
|
36
|
+
}
|
|
37
|
+
function snapshotDom(params) {
|
|
38
|
+
if (typeof document === "undefined") return null;
|
|
39
|
+
const root = params.selector ? document.querySelector(params.selector) : document.body;
|
|
40
|
+
if (!root) return null;
|
|
41
|
+
const maxDepth = Math.min(params.maxDepth ?? MAX_DEPTH, MAX_DEPTH);
|
|
42
|
+
const maxNodes = Math.min(params.maxNodes ?? MAX_NODES, MAX_NODES);
|
|
43
|
+
let budget = maxNodes;
|
|
44
|
+
const walk = (el, depth) => {
|
|
45
|
+
budget--;
|
|
46
|
+
const r = el.getBoundingClientRect();
|
|
47
|
+
const classes = el.classList.length ? [...el.classList] : void 0;
|
|
48
|
+
const attrs = {};
|
|
49
|
+
for (const name of el.getAttributeNames()) {
|
|
50
|
+
if (ATTR_ALLOW.has(name)) attrs[name] = el.getAttribute(name) ?? "";
|
|
51
|
+
}
|
|
52
|
+
const ownText = [...el.childNodes].filter((n) => n.nodeType === 3).map((n) => (n.textContent ?? "").trim()).join(" ").trim();
|
|
53
|
+
const node = {
|
|
54
|
+
tag: el.tagName.toLowerCase(),
|
|
55
|
+
...el.id ? { id: el.id } : {},
|
|
56
|
+
...classes ? { classes } : {},
|
|
57
|
+
...Object.keys(attrs).length ? { attrs } : {},
|
|
58
|
+
rect: { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) },
|
|
59
|
+
...ownText ? { text: ownText.slice(0, MAX_TEXT) } : {}
|
|
60
|
+
};
|
|
61
|
+
if (depth < maxDepth && el.children.length && budget > 0) {
|
|
62
|
+
const children = [];
|
|
63
|
+
for (const child of el.children) {
|
|
64
|
+
if (budget <= 0) {
|
|
65
|
+
node.truncated = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
children.push(walk(child, depth + 1));
|
|
69
|
+
}
|
|
70
|
+
if (children.length) node.children = children;
|
|
71
|
+
} else if (el.children.length) {
|
|
72
|
+
node.truncated = true;
|
|
73
|
+
}
|
|
74
|
+
return node;
|
|
75
|
+
};
|
|
76
|
+
return walk(root, 0);
|
|
77
|
+
}
|
|
78
|
+
function computedStyle(params) {
|
|
79
|
+
if (typeof document === "undefined") return null;
|
|
80
|
+
const el = document.querySelector(params.selector);
|
|
81
|
+
if (!el) return null;
|
|
82
|
+
const cs = getComputedStyle(el);
|
|
83
|
+
const out = {};
|
|
84
|
+
for (const p of params.props.slice(0, 50)) out[p] = cs.getPropertyValue(p) || cs[p]?.toString?.() || "";
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function rects(params) {
|
|
88
|
+
if (typeof document === "undefined") return [];
|
|
89
|
+
return [...document.querySelectorAll(params.selector)].slice(0, 200).map((el) => {
|
|
90
|
+
const r = el.getBoundingClientRect();
|
|
91
|
+
return { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
let responderStarted = false;
|
|
95
|
+
function startResponder() {
|
|
96
|
+
if (responderStarted || typeof window === "undefined") return;
|
|
97
|
+
responderStarted = true;
|
|
98
|
+
addListener("debug-query", (msg) => {
|
|
99
|
+
if (!enabledChannel.get()) return;
|
|
100
|
+
const id = msg.id;
|
|
101
|
+
const method = msg.method;
|
|
102
|
+
const params = msg.params ?? {};
|
|
103
|
+
let ok = true;
|
|
104
|
+
let result = null;
|
|
105
|
+
let error;
|
|
106
|
+
try {
|
|
107
|
+
switch (method) {
|
|
108
|
+
case "snapshotDom":
|
|
109
|
+
result = snapshotDom(params);
|
|
110
|
+
break;
|
|
111
|
+
case "computedStyle":
|
|
112
|
+
result = computedStyle(params);
|
|
113
|
+
break;
|
|
114
|
+
case "rect":
|
|
115
|
+
result = rects(params);
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
ok = false;
|
|
119
|
+
error = `unknown debug method: ${String(method)}`;
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
ok = false;
|
|
123
|
+
error = e instanceof Error ? e.message : String(e);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
sendMessage("debug-query-result", { id, ok, result, error });
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
enabledChannel.onChange((enabled) => {
|
|
132
|
+
if (enabled) startResponder();
|
|
133
|
+
});
|
|
134
|
+
const debug = { log, isEnabled: isDebugEnabled };
|
|
135
|
+
export {
|
|
136
|
+
debug,
|
|
137
|
+
isDebugEnabled,
|
|
138
|
+
log,
|
|
139
|
+
useDebugEnabled
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/debug.ts"],"sourcesContent":["// System-app devtools — the app-facing surface (plan: docs/plans/system-app-devtools.md).\n//\n// Two opt-in, DEV-ONLY instruments for debugging a sandboxed UI-as-app region:\n// 1. `debug.log(...)` — an app→host one-way log surfaced in the host dev panel\n// / CLI `/debug` stream (instead of hand-fishing console output out of a\n// cross-origin iframe's devtools).\n// 2. a READ-ONLY DOM/layout responder the host can query from outside — the\n// thing a cross-origin screenshot can't reliably give you (a blank capture\n// is ambiguous between a real 0-height collapse and a paint artifact).\n//\n// SECURITY (the gating constraint — see the plan's §0):\n// - Both are inert unless the HOST signals dev mode via the `debug-enabled`\n// channel. The host only sets it for a dev/override session (the `ir-dev-*`\n// deep link) or an explicit operator developer-mode. A published app served\n// to a normal user gets `enabled:false` → `debug.log` is a no-op and the\n// responder never answers. Production isolation is therefore unchanged.\n// - The responder is READ-ONLY with a fixed vocabulary (snapshotDom /\n// computedStyle / rect). There is deliberately NO eval bridge — that would\n// turn a debug aid into remote code execution into the sandbox.\n// - The responder reads only its OWN `document` (it lives in its own opaque\n// iframe and cannot reach a sibling app), so there is no app↔app leak even\n// in dev.\n// - Output is bounded (node/depth/text caps) so a query can't exfiltrate an\n// unbounded payload or wedge the app.\n//\n// Apps that want the strongest guarantee can additionally guard their own usage\n// behind `import.meta.env.DEV` so the calls are tree-shaken from prod bundles;\n// the runtime gate here is the backstop that holds regardless.\n\nimport { createPushChannel } from './pushChannel';\nimport { sendMessage, addListener } from './sandboxUtils';\n\n/** Severity of a {@link debug.log} entry. */\nexport type DebugLevel = 'debug' | 'info' | 'warn' | 'error';\n\n// ── Dev gate ────────────────────────────────────────────────────────────────\n// The host pushes `debug-enabled:true` only for a dev/override session. Until\n// then (and always in production) it stays false and every instrument is inert.\nconst enabledChannel = createPushChannel<boolean>({\n pushType: 'debug-enabled',\n requestType: 'request-debug-enabled',\n initial: false,\n parse: (msg) => (typeof msg.enabled === 'boolean' ? msg.enabled : undefined),\n});\n\n/** Is the host dev-debug surface active for this session? `false` in production. */\nexport const isDebugEnabled = (): boolean => enabledChannel.get();\n\n/** React hook: whether the host dev-debug surface is active (re-renders on change).\n * Handy for showing a debug affordance only when it would do something. */\nexport const useDebugEnabled = (): boolean => enabledChannel.use();\n\n// ── 1. App→host debug log ─────────────────────────────────────────────────────\n// Best-effort: a value that can't be structured-cloned is replaced with a marker\n// rather than throwing — `debug.log` must never break the app.\nconst MAX_DATA_BYTES = 16 * 1024;\n\nfunction safeData(data: unknown): unknown {\n if (data === undefined) return undefined;\n try {\n const json = JSON.stringify(data);\n if (json === undefined) return '[unserializable]';\n if (json.length > MAX_DATA_BYTES) return `[truncated ${json.length}B]`;\n return JSON.parse(json);\n } catch {\n return '[unserializable]';\n }\n}\n\n/**\n * Emit a structured debug entry to the host dev surface. A NO-OP unless the host\n * has enabled the dev-debug session ({@link isDebugEnabled}); in production it\n * does nothing and sends nothing.\n *\n * debug.log('info', 'mounted', { activeFile });\n */\nexport function log(level: DebugLevel, message: string, data?: unknown): void {\n if (!enabledChannel.get()) return; // inert in prod / non-dev sessions\n try {\n sendMessage('debug-log', { level, message: String(message), data: safeData(data) });\n } catch {\n /* transport not ready — drop silently; logging must never throw */\n }\n}\n\n// ── 2. Read-only DOM / layout responder ───────────────────────────────────────\n// The host sends `debug-query` { id, method, params }; we reply with\n// `debug-query-result` { id, ok, result | error }. Only ever active while the dev\n// gate is enabled. Vocabulary is fixed and read-only.\n\ninterface DomNode {\n tag: string;\n id?: string;\n classes?: string[];\n attrs?: Record<string, string>;\n rect?: { x: number; y: number; w: number; h: number };\n text?: string;\n children?: DomNode[];\n truncated?: true;\n}\n\nconst ATTR_ALLOW = new Set(['role', 'aria-hidden', 'data-theme', 'data-active', 'href', 'type', 'hidden']);\nconst MAX_NODES = 2000;\nconst MAX_DEPTH = 25;\nconst MAX_TEXT = 200;\n\nfunction round(n: number): number {\n return Math.round(n);\n}\n\nfunction snapshotDom(params: { selector?: string; maxDepth?: number; maxNodes?: number }): DomNode | null {\n if (typeof document === 'undefined') return null;\n const root = params.selector ? document.querySelector(params.selector) : document.body;\n if (!root) return null;\n const maxDepth = Math.min(params.maxDepth ?? MAX_DEPTH, MAX_DEPTH);\n const maxNodes = Math.min(params.maxNodes ?? MAX_NODES, MAX_NODES);\n let budget = maxNodes;\n\n const walk = (el: Element, depth: number): DomNode => {\n budget--;\n const r = el.getBoundingClientRect();\n const classes = el.classList.length ? [...el.classList] : undefined;\n const attrs: Record<string, string> = {};\n for (const name of el.getAttributeNames()) {\n if (ATTR_ALLOW.has(name)) attrs[name] = el.getAttribute(name) ?? '';\n }\n // Direct text (not descendants') so a leaf's label is visible without dumping\n // the whole subtree's text.\n const ownText = [...el.childNodes]\n .filter((n) => n.nodeType === 3)\n .map((n) => (n.textContent ?? '').trim())\n .join(' ')\n .trim();\n const node: DomNode = {\n tag: el.tagName.toLowerCase(),\n ...(el.id ? { id: el.id } : {}),\n ...(classes ? { classes } : {}),\n ...(Object.keys(attrs).length ? { attrs } : {}),\n rect: { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) },\n ...(ownText ? { text: ownText.slice(0, MAX_TEXT) } : {}),\n };\n if (depth < maxDepth && el.children.length && budget > 0) {\n const children: DomNode[] = [];\n for (const child of el.children) {\n if (budget <= 0) {\n node.truncated = true;\n break;\n }\n children.push(walk(child, depth + 1));\n }\n if (children.length) node.children = children;\n } else if (el.children.length) {\n node.truncated = true;\n }\n return node;\n };\n\n return walk(root, 0);\n}\n\nfunction computedStyle(params: { selector: string; props: string[] }): Record<string, string> | null {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector(params.selector);\n if (!el) return null;\n const cs = getComputedStyle(el);\n const out: Record<string, string> = {};\n for (const p of params.props.slice(0, 50)) out[p] = cs.getPropertyValue(p) || cs[p as keyof CSSStyleDeclaration]?.toString?.() || '';\n return out;\n}\n\nfunction rects(params: { selector: string }): Array<{ x: number; y: number; w: number; h: number }> {\n if (typeof document === 'undefined') return [];\n return [...document.querySelectorAll(params.selector)].slice(0, 200).map((el) => {\n const r = el.getBoundingClientRect();\n return { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) };\n });\n}\n\nlet responderStarted = false;\n\n/** Wire the read-only DOM/layout responder. Idempotent; called lazily once the\n * dev gate turns on. No effect when `document` is absent (non-browser realm). */\nfunction startResponder(): void {\n if (responderStarted || typeof window === 'undefined') return;\n responderStarted = true;\n addListener('debug-query', (msg: { id?: unknown; method?: unknown; params?: unknown }) => {\n if (!enabledChannel.get()) return; // gate: ignore unless dev-enabled\n const id = msg.id;\n const method = msg.method;\n const params = (msg.params ?? {}) as Record<string, unknown>;\n let ok = true;\n let result: unknown = null;\n let error: string | undefined;\n try {\n switch (method) {\n case 'snapshotDom':\n result = snapshotDom(params as never);\n break;\n case 'computedStyle':\n result = computedStyle(params as never);\n break;\n case 'rect':\n result = rects(params as never);\n break;\n default:\n ok = false;\n error = `unknown debug method: ${String(method)}`;\n }\n } catch (e) {\n ok = false;\n error = e instanceof Error ? e.message : String(e);\n }\n try {\n sendMessage('debug-query-result', { id, ok, result, error });\n } catch {\n /* transport gone — nothing to do */\n }\n });\n}\n\n// Start the responder as soon as the gate flips on (and not before).\nenabledChannel.onChange((enabled) => {\n if (enabled) startResponder();\n});\n\n/** The dev-only debug surface. Inert unless the host enables it ({@link isDebugEnabled}). */\nexport const debug = { log, isEnabled: isDebugEnabled } as const;\n"],"mappings":"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","names":[]}
|
package/dist/diagnostics.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// The `diagnostics:read` channel — app-facing surface (LLM_AND_AGENTS_SPEC §3.3/§4,\n// D4; roadmap R3-74 / P3-72).\n//\n// A *sibling* agent app (one holding `diagnostics:read`) observes the result of its\n// edits to the PREVIEWED app: the build/transpile errors from the sandbox bundler\n// and the previewed app's captured `console.*`. This is the in-browser analogue of\n// a local coding agent reading compiler/test output (P3-73's `get_diagnostics()`\n// tool). Read-only and scoped host-side to the paired previewed app's OWN\n// diagnostics — never another app's (the host channel projection enforces it; the\n// previewed app itself holds no `diagnostics:read`, so it is origin-excluded).\n//\n// Recipe-A push channel, identical get/onChange/use trio as `secrets`/`catalog`:\n// the host pushes `diagnostics` on change and answers a `request-diagnostics` poll,\n// gated per-frame by the read ACL. Inert until the host wires the channel\n// (site-main `channelBridge`); the contract ships here so the agent app (P3-73) can\n// be written against it.\nimport { createPushChannel } from './pushChannel';\n\n/** One build/transpile error from the sandbox bundler's compile of the previewed\n * app. `path` is repo-relative (leading slash) when the error is file-located. */\nexport interface BuildError {\n message: string;\n path?: string;\n line?: number;\n column?: number;\n}\n\nexport type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';\n\n/** One captured `console.*` entry from the previewed app. The host renders the\n * console arguments to text host-side — the agent never receives live object\n * handles across the boundary. */\nexport interface ConsoleEntry {\n level: ConsoleLevel;\n text: string;\n /** Host-side timestamp (ms) at capture. */\n at: number;\n}\n\n/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this\n * snapshot describes, so a consumer can tell stale output from fresh and never\n * conflates two apps' diagnostics. `null` until a first compile is observed. */\nexport interface DiagnosticsProvenance {\n /** The previewed app's stable key (`provider/ns/repo`). */\n appKey?: string;\n /** Monotonic id of the compile that produced these build errors. */\n compileId?: string;\n}\n\nexport interface Diagnostics {\n buildErrors: BuildError[];\n consoleEntries: ConsoleEntry[];\n provenance: DiagnosticsProvenance | null;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `diagnostics:read`): an empty, provenance-less snapshot. */\nconst EMPTY: Diagnostics = { buildErrors: [], consoleEntries: [], provenance: null };\n\nconst channel = createPushChannel<Diagnostics>({\n pushType: 'diagnostics',\n requestType: 'request-diagnostics',\n initial: EMPTY,\n parse: (msg) => {\n // Require both arrays; tolerate an absent/partial provenance. A malformed push\n // is ignored (returns undefined) so the last good snapshot stands.\n if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return undefined;\n const provenance =\n msg.provenance && typeof msg.provenance === 'object'\n ? (msg.provenance as DiagnosticsProvenance)\n : null;\n return {\n buildErrors: msg.buildErrors as BuildError[],\n consoleEntries: msg.consoleEntries as ConsoleEntry[],\n provenance,\n };\n },\n});\n\n/** One-off read of the previewed app's current diagnostics. Returns the empty\n * snapshot until the host answers (or if the app lacks `diagnostics:read`). Use\n * {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */\nexport const getDiagnostics = (): Diagnostics => channel.get();\n\n/** Subscribe to diagnostics. Invoked immediately with the current value, then on\n * every host push. Returns an unsubscribe. */\nexport const onDiagnosticsChange = (listener: (d: Diagnostics) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current diagnostics, re-rendering on every change. */\nexport const useDiagnostics = (): Diagnostics => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,yBAAkC;
|
|
1
|
+
{"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// The `diagnostics:read` channel — app-facing surface (LLM_AND_AGENTS_SPEC §3.3/§4,\n// D4; roadmap R3-74 / P3-72).\n//\n// A *sibling* agent app (one holding `diagnostics:read`) observes the result of its\n// edits to the PREVIEWED app: the build/transpile errors from the sandbox bundler\n// and the previewed app's captured `console.*`. This is the in-browser analogue of\n// a local coding agent reading compiler/test output (P3-73's `get_diagnostics()`\n// tool). Read-only and scoped host-side to the paired previewed app's OWN\n// diagnostics — never another app's (the host channel projection enforces it; the\n// previewed app itself holds no `diagnostics:read`, so it is origin-excluded).\n//\n// Recipe-A push channel, identical get/onChange/use trio as `secrets`/`catalog`:\n// the host pushes `diagnostics` on change and answers a `request-diagnostics` poll,\n// gated per-frame by the read ACL. Inert until the host wires the channel\n// (site-main `channelBridge`); the contract ships here so the agent app (P3-73) can\n// be written against it.\nimport { createPushChannel } from './pushChannel';\n\n/** One build/transpile error from the sandbox bundler's compile of the previewed\n * app. `path` is repo-relative (leading slash) when the error is file-located. */\nexport interface BuildError {\n message: string;\n path?: string;\n line?: number;\n column?: number;\n}\n\n/** The `console.*` method a captured entry came from. */\nexport type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';\n\n/** One captured `console.*` entry from the previewed app. The host renders the\n * console arguments to text host-side — the agent never receives live object\n * handles across the boundary. */\nexport interface ConsoleEntry {\n level: ConsoleLevel;\n text: string;\n /** Host-side timestamp (ms) at capture. */\n at: number;\n}\n\n/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this\n * snapshot describes, so a consumer can tell stale output from fresh and never\n * conflates two apps' diagnostics. `null` until a first compile is observed. */\nexport interface DiagnosticsProvenance {\n /** The previewed app's stable key (`provider/ns/repo`). */\n appKey?: string;\n /** Monotonic id of the compile that produced these build errors. */\n compileId?: string;\n}\n\n/** A snapshot of the previewed app's diagnostics: its build errors, captured\n * console output, and the {@link DiagnosticsProvenance} of the compile. */\nexport interface Diagnostics {\n buildErrors: BuildError[];\n consoleEntries: ConsoleEntry[];\n provenance: DiagnosticsProvenance | null;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `diagnostics:read`): an empty, provenance-less snapshot. */\nconst EMPTY: Diagnostics = { buildErrors: [], consoleEntries: [], provenance: null };\n\nconst channel = createPushChannel<Diagnostics>({\n pushType: 'diagnostics',\n requestType: 'request-diagnostics',\n initial: EMPTY,\n parse: (msg) => {\n // Require both arrays; tolerate an absent/partial provenance. A malformed push\n // is ignored (returns undefined) so the last good snapshot stands.\n if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return undefined;\n const provenance =\n msg.provenance && typeof msg.provenance === 'object'\n ? (msg.provenance as DiagnosticsProvenance)\n : null;\n return {\n buildErrors: msg.buildErrors as BuildError[],\n consoleEntries: msg.consoleEntries as ConsoleEntry[],\n provenance,\n };\n },\n});\n\n/** One-off read of the previewed app's current diagnostics. Returns the empty\n * snapshot until the host answers (or if the app lacks `diagnostics:read`). Use\n * {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */\nexport const getDiagnostics = (): Diagnostics => channel.get();\n\n/** Subscribe to diagnostics. Invoked immediately with the current value, then on\n * every host push. Returns an unsubscribe. */\nexport const onDiagnosticsChange = (listener: (d: Diagnostics) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current diagnostics, re-rendering on every change. */\nexport const useDiagnostics = (): Diagnostics => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,yBAAkC;AA4ClC,MAAM,QAAqB,EAAE,aAAa,CAAC,GAAG,gBAAgB,CAAC,GAAG,YAAY,KAAK;AAEnF,MAAM,cAAU,sCAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAQ;AAGd,QAAI,CAAC,MAAM,QAAQ,IAAI,WAAW,KAAK,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAClF,UAAM,aACJ,IAAI,cAAc,OAAO,IAAI,eAAe,WACvC,IAAI,aACL;AACN,WAAO;AAAA,MACL,aAAa,IAAI;AAAA,MACjB,gBAAgB,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAKM,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;AAItD,MAAM,sBAAsB,CAAC,aAClC,QAAQ,SAAS,QAAQ;AAGpB,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;","names":[]}
|
package/dist/diagnostics.d.cts
CHANGED
|
@@ -6,6 +6,7 @@ interface BuildError {
|
|
|
6
6
|
line?: number;
|
|
7
7
|
column?: number;
|
|
8
8
|
}
|
|
9
|
+
/** The `console.*` method a captured entry came from. */
|
|
9
10
|
type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
10
11
|
/** One captured `console.*` entry from the previewed app. The host renders the
|
|
11
12
|
* console arguments to text host-side — the agent never receives live object
|
|
@@ -25,6 +26,8 @@ interface DiagnosticsProvenance {
|
|
|
25
26
|
/** Monotonic id of the compile that produced these build errors. */
|
|
26
27
|
compileId?: string;
|
|
27
28
|
}
|
|
29
|
+
/** A snapshot of the previewed app's diagnostics: its build errors, captured
|
|
30
|
+
* console output, and the {@link DiagnosticsProvenance} of the compile. */
|
|
28
31
|
interface Diagnostics {
|
|
29
32
|
buildErrors: BuildError[];
|
|
30
33
|
consoleEntries: ConsoleEntry[];
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ interface BuildError {
|
|
|
6
6
|
line?: number;
|
|
7
7
|
column?: number;
|
|
8
8
|
}
|
|
9
|
+
/** The `console.*` method a captured entry came from. */
|
|
9
10
|
type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
10
11
|
/** One captured `console.*` entry from the previewed app. The host renders the
|
|
11
12
|
* console arguments to text host-side — the agent never receives live object
|
|
@@ -25,6 +26,8 @@ interface DiagnosticsProvenance {
|
|
|
25
26
|
/** Monotonic id of the compile that produced these build errors. */
|
|
26
27
|
compileId?: string;
|
|
27
28
|
}
|
|
29
|
+
/** A snapshot of the previewed app's diagnostics: its build errors, captured
|
|
30
|
+
* console output, and the {@link DiagnosticsProvenance} of the compile. */
|
|
28
31
|
interface Diagnostics {
|
|
29
32
|
buildErrors: BuildError[];
|
|
30
33
|
consoleEntries: ConsoleEntry[];
|
package/dist/diagnostics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// The `diagnostics:read` channel — app-facing surface (LLM_AND_AGENTS_SPEC §3.3/§4,\n// D4; roadmap R3-74 / P3-72).\n//\n// A *sibling* agent app (one holding `diagnostics:read`) observes the result of its\n// edits to the PREVIEWED app: the build/transpile errors from the sandbox bundler\n// and the previewed app's captured `console.*`. This is the in-browser analogue of\n// a local coding agent reading compiler/test output (P3-73's `get_diagnostics()`\n// tool). Read-only and scoped host-side to the paired previewed app's OWN\n// diagnostics — never another app's (the host channel projection enforces it; the\n// previewed app itself holds no `diagnostics:read`, so it is origin-excluded).\n//\n// Recipe-A push channel, identical get/onChange/use trio as `secrets`/`catalog`:\n// the host pushes `diagnostics` on change and answers a `request-diagnostics` poll,\n// gated per-frame by the read ACL. Inert until the host wires the channel\n// (site-main `channelBridge`); the contract ships here so the agent app (P3-73) can\n// be written against it.\nimport { createPushChannel } from './pushChannel';\n\n/** One build/transpile error from the sandbox bundler's compile of the previewed\n * app. `path` is repo-relative (leading slash) when the error is file-located. */\nexport interface BuildError {\n message: string;\n path?: string;\n line?: number;\n column?: number;\n}\n\nexport type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';\n\n/** One captured `console.*` entry from the previewed app. The host renders the\n * console arguments to text host-side — the agent never receives live object\n * handles across the boundary. */\nexport interface ConsoleEntry {\n level: ConsoleLevel;\n text: string;\n /** Host-side timestamp (ms) at capture. */\n at: number;\n}\n\n/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this\n * snapshot describes, so a consumer can tell stale output from fresh and never\n * conflates two apps' diagnostics. `null` until a first compile is observed. */\nexport interface DiagnosticsProvenance {\n /** The previewed app's stable key (`provider/ns/repo`). */\n appKey?: string;\n /** Monotonic id of the compile that produced these build errors. */\n compileId?: string;\n}\n\nexport interface Diagnostics {\n buildErrors: BuildError[];\n consoleEntries: ConsoleEntry[];\n provenance: DiagnosticsProvenance | null;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `diagnostics:read`): an empty, provenance-less snapshot. */\nconst EMPTY: Diagnostics = { buildErrors: [], consoleEntries: [], provenance: null };\n\nconst channel = createPushChannel<Diagnostics>({\n pushType: 'diagnostics',\n requestType: 'request-diagnostics',\n initial: EMPTY,\n parse: (msg) => {\n // Require both arrays; tolerate an absent/partial provenance. A malformed push\n // is ignored (returns undefined) so the last good snapshot stands.\n if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return undefined;\n const provenance =\n msg.provenance && typeof msg.provenance === 'object'\n ? (msg.provenance as DiagnosticsProvenance)\n : null;\n return {\n buildErrors: msg.buildErrors as BuildError[],\n consoleEntries: msg.consoleEntries as ConsoleEntry[],\n provenance,\n };\n },\n});\n\n/** One-off read of the previewed app's current diagnostics. Returns the empty\n * snapshot until the host answers (or if the app lacks `diagnostics:read`). Use\n * {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */\nexport const getDiagnostics = (): Diagnostics => channel.get();\n\n/** Subscribe to diagnostics. Invoked immediately with the current value, then on\n * every host push. Returns an unsubscribe. */\nexport const onDiagnosticsChange = (listener: (d: Diagnostics) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current diagnostics, re-rendering on every change. */\nexport const useDiagnostics = (): Diagnostics => channel.use();\n"],"mappings":"AAgBA,SAAS,yBAAyB;
|
|
1
|
+
{"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// The `diagnostics:read` channel — app-facing surface (LLM_AND_AGENTS_SPEC §3.3/§4,\n// D4; roadmap R3-74 / P3-72).\n//\n// A *sibling* agent app (one holding `diagnostics:read`) observes the result of its\n// edits to the PREVIEWED app: the build/transpile errors from the sandbox bundler\n// and the previewed app's captured `console.*`. This is the in-browser analogue of\n// a local coding agent reading compiler/test output (P3-73's `get_diagnostics()`\n// tool). Read-only and scoped host-side to the paired previewed app's OWN\n// diagnostics — never another app's (the host channel projection enforces it; the\n// previewed app itself holds no `diagnostics:read`, so it is origin-excluded).\n//\n// Recipe-A push channel, identical get/onChange/use trio as `secrets`/`catalog`:\n// the host pushes `diagnostics` on change and answers a `request-diagnostics` poll,\n// gated per-frame by the read ACL. Inert until the host wires the channel\n// (site-main `channelBridge`); the contract ships here so the agent app (P3-73) can\n// be written against it.\nimport { createPushChannel } from './pushChannel';\n\n/** One build/transpile error from the sandbox bundler's compile of the previewed\n * app. `path` is repo-relative (leading slash) when the error is file-located. */\nexport interface BuildError {\n message: string;\n path?: string;\n line?: number;\n column?: number;\n}\n\n/** The `console.*` method a captured entry came from. */\nexport type ConsoleLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';\n\n/** One captured `console.*` entry from the previewed app. The host renders the\n * console arguments to text host-side — the agent never receives live object\n * handles across the boundary. */\nexport interface ConsoleEntry {\n level: ConsoleLevel;\n text: string;\n /** Host-side timestamp (ms) at capture. */\n at: number;\n}\n\n/** Provenance (D4 / EDITOR_AS_APP_SPEC §12.3): WHICH previewed app + compile this\n * snapshot describes, so a consumer can tell stale output from fresh and never\n * conflates two apps' diagnostics. `null` until a first compile is observed. */\nexport interface DiagnosticsProvenance {\n /** The previewed app's stable key (`provider/ns/repo`). */\n appKey?: string;\n /** Monotonic id of the compile that produced these build errors. */\n compileId?: string;\n}\n\n/** A snapshot of the previewed app's diagnostics: its build errors, captured\n * console output, and the {@link DiagnosticsProvenance} of the compile. */\nexport interface Diagnostics {\n buildErrors: BuildError[];\n consoleEntries: ConsoleEntry[];\n provenance: DiagnosticsProvenance | null;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `diagnostics:read`): an empty, provenance-less snapshot. */\nconst EMPTY: Diagnostics = { buildErrors: [], consoleEntries: [], provenance: null };\n\nconst channel = createPushChannel<Diagnostics>({\n pushType: 'diagnostics',\n requestType: 'request-diagnostics',\n initial: EMPTY,\n parse: (msg) => {\n // Require both arrays; tolerate an absent/partial provenance. A malformed push\n // is ignored (returns undefined) so the last good snapshot stands.\n if (!Array.isArray(msg.buildErrors) || !Array.isArray(msg.consoleEntries)) return undefined;\n const provenance =\n msg.provenance && typeof msg.provenance === 'object'\n ? (msg.provenance as DiagnosticsProvenance)\n : null;\n return {\n buildErrors: msg.buildErrors as BuildError[],\n consoleEntries: msg.consoleEntries as ConsoleEntry[],\n provenance,\n };\n },\n});\n\n/** One-off read of the previewed app's current diagnostics. Returns the empty\n * snapshot until the host answers (or if the app lacks `diagnostics:read`). Use\n * {@link onDiagnosticsChange}/{@link useDiagnostics} to react to live updates. */\nexport const getDiagnostics = (): Diagnostics => channel.get();\n\n/** Subscribe to diagnostics. Invoked immediately with the current value, then on\n * every host push. Returns an unsubscribe. */\nexport const onDiagnosticsChange = (listener: (d: Diagnostics) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current diagnostics, re-rendering on every change. */\nexport const useDiagnostics = (): Diagnostics => channel.use();\n"],"mappings":"AAgBA,SAAS,yBAAyB;AA4ClC,MAAM,QAAqB,EAAE,aAAa,CAAC,GAAG,gBAAgB,CAAC,GAAG,YAAY,KAAK;AAEnF,MAAM,UAAU,kBAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAQ;AAGd,QAAI,CAAC,MAAM,QAAQ,IAAI,WAAW,KAAK,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAClF,UAAM,aACJ,IAAI,cAAc,OAAO,IAAI,eAAe,WACvC,IAAI,aACL;AACN,WAAO;AAAA,MACL,aAAa,IAAI;AAAA,MACjB,gBAAgB,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAKM,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;AAItD,MAAM,sBAAsB,CAAC,aAClC,QAAQ,SAAS,QAAQ;AAGpB,MAAM,iBAAiB,MAAmB,QAAQ,IAAI;","names":[]}
|
package/dist/formFactor.cjs.map
CHANGED
|
@@ -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';\nexport type Orientation = 'portrait' | 'landscape';\n\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAkC;
|
|
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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAkC;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,cAAU,sCAA8B;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":[]}
|
package/dist/formFactor.d.cts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* Baseline capability `formFactor:read` — every app may read it.
|
|
10
10
|
*/
|
|
11
11
|
type FormFactorClass = 'mobile' | 'tablet' | 'desktop';
|
|
12
|
+
/** Whether the rendered surface is taller than wide (`portrait`) or wider (`landscape`). */
|
|
12
13
|
type Orientation = 'portrait' | 'landscape';
|
|
14
|
+
/** The host-reported size class, orientation, and pixel box of your app's surface. */
|
|
13
15
|
interface FormFactor {
|
|
14
16
|
class: FormFactorClass;
|
|
15
17
|
orientation: Orientation;
|
package/dist/formFactor.d.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* Baseline capability `formFactor:read` — every app may read it.
|
|
10
10
|
*/
|
|
11
11
|
type FormFactorClass = 'mobile' | 'tablet' | 'desktop';
|
|
12
|
+
/** Whether the rendered surface is taller than wide (`portrait`) or wider (`landscape`). */
|
|
12
13
|
type Orientation = 'portrait' | 'landscape';
|
|
14
|
+
/** The host-reported size class, orientation, and pixel box of your app's surface. */
|
|
13
15
|
interface FormFactor {
|
|
14
16
|
class: FormFactorClass;
|
|
15
17
|
orientation: Orientation;
|
package/dist/formFactor.js.map
CHANGED
|
@@ -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';\nexport type Orientation = 'portrait' | 'landscape';\n\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;
|
|
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":[]}
|
package/dist/hooks.cjs
CHANGED
|
@@ -18,25 +18,19 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var hooks_exports = {};
|
|
20
20
|
__export(hooks_exports, {
|
|
21
|
+
useAllMetadata: () => useAllMetadata,
|
|
21
22
|
useFileMetadata: () => useFileMetadata,
|
|
22
23
|
useMetadataQuery: () => useMetadataQuery
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(hooks_exports);
|
|
25
26
|
var import_react = require("react");
|
|
26
27
|
var import_TinkerableContext = require("./TinkerableContext");
|
|
27
|
-
const
|
|
28
|
-
try {
|
|
29
|
-
return { result: queryFunction(filesMetadata) };
|
|
30
|
-
} catch (e) {
|
|
31
|
-
return { error: e };
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const arraysEqual = (a, b) => {
|
|
28
|
+
const entriesEqual = (a, b) => {
|
|
35
29
|
if (a.length !== b.length) {
|
|
36
30
|
return false;
|
|
37
31
|
}
|
|
38
32
|
for (let i = 0; i < a.length; i++) {
|
|
39
|
-
if (a[i] !== b[i]) {
|
|
33
|
+
if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {
|
|
40
34
|
return false;
|
|
41
35
|
}
|
|
42
36
|
}
|
|
@@ -44,31 +38,36 @@ const arraysEqual = (a, b) => {
|
|
|
44
38
|
};
|
|
45
39
|
const useMetadataQuery = (queryFunction) => {
|
|
46
40
|
const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
47
|
-
const
|
|
48
|
-
(0, import_react.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}, [filesMetadata, setQueryResult, queryFunction]);
|
|
63
|
-
return queryResult;
|
|
41
|
+
const previous = (0, import_react.useRef)([]);
|
|
42
|
+
return (0, import_react.useMemo)(() => {
|
|
43
|
+
const files = filesMetadata ?? {};
|
|
44
|
+
let entries;
|
|
45
|
+
try {
|
|
46
|
+
entries = queryFunction(files).map((path) => ({ path, meta: files[path] }));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return { error };
|
|
49
|
+
}
|
|
50
|
+
if (entriesEqual(entries, previous.current)) {
|
|
51
|
+
return previous.current;
|
|
52
|
+
}
|
|
53
|
+
previous.current = entries;
|
|
54
|
+
return entries;
|
|
55
|
+
}, [filesMetadata, queryFunction]);
|
|
64
56
|
};
|
|
65
57
|
const useFileMetadata = (path) => {
|
|
66
58
|
const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
return (0, import_react.useMemo)(
|
|
60
|
+
() => (filesMetadata ?? {})[path],
|
|
61
|
+
[path, filesMetadata]
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
const useAllMetadata = () => {
|
|
65
|
+
const { filesMetadata } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
66
|
+
return filesMetadata ?? {};
|
|
69
67
|
};
|
|
70
68
|
// Annotate the CommonJS export names for ESM import in node:
|
|
71
69
|
0 && (module.exports = {
|
|
70
|
+
useAllMetadata,
|
|
72
71
|
useFileMetadata,
|
|
73
72
|
useMetadataQuery
|
|
74
73
|
});
|
package/dist/hooks.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { use,
|
|
1
|
+
{"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { use, useMemo, useRef } from 'react';\nimport { TinkerableContext } from './TinkerableContext';\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"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAqC;AACrC,+BAAkC;AASlC,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;","names":[]}
|
package/dist/hooks.d.cts
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
|
-
import { Metadata, MetadataQueryFunction, MetadataQueryResult } from './sandboxTypes.cjs';
|
|
1
|
+
import { Metadata, FilesMetadata, MetadataQueryFunction, MetadataQueryResult } from './sandboxTypes.cjs';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Query the file metadata store (MDX frontmatter) with a plain JS function.
|
|
5
|
+
*
|
|
6
|
+
* The query receives every file's frontmatter keyed by path and returns the paths
|
|
7
|
+
* that match; the hook resolves each path back to its frontmatter and returns an
|
|
8
|
+
* array of `{ path, meta }` entries — so a single call gives you everything you
|
|
9
|
+
* filtered on, with no second lookup. A throwing query is reported as `{ error }`
|
|
10
|
+
* rather than crashing the render.
|
|
11
|
+
*
|
|
12
|
+
* The query runs synchronously during render (no empty first frame), and the
|
|
13
|
+
* returned array keeps its identity while the matches are unchanged, so it is safe
|
|
14
|
+
* to use directly in downstream `useMemo`/`useEffect` dependency arrays.
|
|
15
|
+
*
|
|
16
|
+
* Pass a type parameter to get typed frontmatter throughout:
|
|
17
|
+
* ```ts
|
|
18
|
+
* interface PostMeta { title: string; date: string; draft?: boolean }
|
|
19
|
+
* const posts = useMetadataQuery<PostMeta>((files) =>
|
|
20
|
+
* Object.entries(files)
|
|
21
|
+
* .filter(([, m]) => !m.draft)
|
|
22
|
+
* .sort(([, a], [, b]) => b.date.localeCompare(a.date))
|
|
23
|
+
* .map(([path]) => path),
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare const useMetadataQuery: <T = Metadata>(queryFunction: MetadataQueryFunction<T>) => MetadataQueryResult<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Read one file's metadata (MDX frontmatter) by repo-relative path. Returns
|
|
30
|
+
* `undefined` when the path has no metadata. Pass a type parameter for typed
|
|
31
|
+
* field access.
|
|
32
|
+
*/
|
|
33
|
+
declare const useFileMetadata: <T = Metadata>(path: string) => T | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The raw, reactive metadata store: a map from file path to frontmatter. The
|
|
36
|
+
* escape hatch for apps that want to render their own index rather than express it
|
|
37
|
+
* as a path-returning query. Pass a type parameter for typed frontmatter values.
|
|
38
|
+
*/
|
|
39
|
+
declare const useAllMetadata: <T = Metadata>() => FilesMetadata<T>;
|
|
5
40
|
|
|
6
|
-
export { useFileMetadata, useMetadataQuery };
|
|
41
|
+
export { useAllMetadata, useFileMetadata, useMetadataQuery };
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
|
-
import { Metadata, MetadataQueryFunction, MetadataQueryResult } from './sandboxTypes.js';
|
|
1
|
+
import { Metadata, FilesMetadata, MetadataQueryFunction, MetadataQueryResult } from './sandboxTypes.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Query the file metadata store (MDX frontmatter) with a plain JS function.
|
|
5
|
+
*
|
|
6
|
+
* The query receives every file's frontmatter keyed by path and returns the paths
|
|
7
|
+
* that match; the hook resolves each path back to its frontmatter and returns an
|
|
8
|
+
* array of `{ path, meta }` entries — so a single call gives you everything you
|
|
9
|
+
* filtered on, with no second lookup. A throwing query is reported as `{ error }`
|
|
10
|
+
* rather than crashing the render.
|
|
11
|
+
*
|
|
12
|
+
* The query runs synchronously during render (no empty first frame), and the
|
|
13
|
+
* returned array keeps its identity while the matches are unchanged, so it is safe
|
|
14
|
+
* to use directly in downstream `useMemo`/`useEffect` dependency arrays.
|
|
15
|
+
*
|
|
16
|
+
* Pass a type parameter to get typed frontmatter throughout:
|
|
17
|
+
* ```ts
|
|
18
|
+
* interface PostMeta { title: string; date: string; draft?: boolean }
|
|
19
|
+
* const posts = useMetadataQuery<PostMeta>((files) =>
|
|
20
|
+
* Object.entries(files)
|
|
21
|
+
* .filter(([, m]) => !m.draft)
|
|
22
|
+
* .sort(([, a], [, b]) => b.date.localeCompare(a.date))
|
|
23
|
+
* .map(([path]) => path),
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare const useMetadataQuery: <T = Metadata>(queryFunction: MetadataQueryFunction<T>) => MetadataQueryResult<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Read one file's metadata (MDX frontmatter) by repo-relative path. Returns
|
|
30
|
+
* `undefined` when the path has no metadata. Pass a type parameter for typed
|
|
31
|
+
* field access.
|
|
32
|
+
*/
|
|
33
|
+
declare const useFileMetadata: <T = Metadata>(path: string) => T | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The raw, reactive metadata store: a map from file path to frontmatter. The
|
|
36
|
+
* escape hatch for apps that want to render their own index rather than express it
|
|
37
|
+
* as a path-returning query. Pass a type parameter for typed frontmatter values.
|
|
38
|
+
*/
|
|
39
|
+
declare const useAllMetadata: <T = Metadata>() => FilesMetadata<T>;
|
|
5
40
|
|
|
6
|
-
export { useFileMetadata, useMetadataQuery };
|
|
41
|
+
export { useAllMetadata, useFileMetadata, useMetadataQuery };
|
package/dist/hooks.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import { use,
|
|
1
|
+
import { use, useMemo, useRef } from "react";
|
|
2
2
|
import { TinkerableContext } from "./TinkerableContext";
|
|
3
|
-
const
|
|
4
|
-
try {
|
|
5
|
-
return { result: queryFunction(filesMetadata) };
|
|
6
|
-
} catch (e) {
|
|
7
|
-
return { error: e };
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
const arraysEqual = (a, b) => {
|
|
3
|
+
const entriesEqual = (a, b) => {
|
|
11
4
|
if (a.length !== b.length) {
|
|
12
5
|
return false;
|
|
13
6
|
}
|
|
14
7
|
for (let i = 0; i < a.length; i++) {
|
|
15
|
-
if (a[i] !== b[i]) {
|
|
8
|
+
if (a[i].path !== b[i].path || a[i].meta !== b[i].meta) {
|
|
16
9
|
return false;
|
|
17
10
|
}
|
|
18
11
|
}
|
|
@@ -20,30 +13,35 @@ const arraysEqual = (a, b) => {
|
|
|
20
13
|
};
|
|
21
14
|
const useMetadataQuery = (queryFunction) => {
|
|
22
15
|
const { filesMetadata } = use(TinkerableContext);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}, [filesMetadata, setQueryResult, queryFunction]);
|
|
39
|
-
return queryResult;
|
|
16
|
+
const previous = useRef([]);
|
|
17
|
+
return useMemo(() => {
|
|
18
|
+
const files = filesMetadata ?? {};
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = queryFunction(files).map((path) => ({ path, meta: files[path] }));
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return { error };
|
|
24
|
+
}
|
|
25
|
+
if (entriesEqual(entries, previous.current)) {
|
|
26
|
+
return previous.current;
|
|
27
|
+
}
|
|
28
|
+
previous.current = entries;
|
|
29
|
+
return entries;
|
|
30
|
+
}, [filesMetadata, queryFunction]);
|
|
40
31
|
};
|
|
41
32
|
const useFileMetadata = (path) => {
|
|
42
33
|
const { filesMetadata } = use(TinkerableContext);
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
return useMemo(
|
|
35
|
+
() => (filesMetadata ?? {})[path],
|
|
36
|
+
[path, filesMetadata]
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
const useAllMetadata = () => {
|
|
40
|
+
const { filesMetadata } = use(TinkerableContext);
|
|
41
|
+
return filesMetadata ?? {};
|
|
45
42
|
};
|
|
46
43
|
export {
|
|
44
|
+
useAllMetadata,
|
|
47
45
|
useFileMetadata,
|
|
48
46
|
useMetadataQuery
|
|
49
47
|
};
|