@mostra/react 0.0.0-stage → 0.1.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.
@@ -0,0 +1,516 @@
1
+ import { b as MemoryJournal, d as connectShell, f as listenShell, g as fieldKey, h as Store, m as reportReady, t as RuntimeProvider, u as builtVersion, v as HttpTransport, x as MostraError, y as IndexedDbJournal } from "./hooks-Dh_l8KqJ.js";
2
+ import { BRIDGE_FIELDS_MAX, BRIDGE_TEXT_MAX, VERIFY_PROBE, VERIFY_REPORT, isFieldId, mostraFieldFocus, mostraFields, mostraFlush, mostraPage, mostraVersionAvailable, readViewContext } from "@mostra/protocol";
3
+ import { createElement } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ //#region src/runtime/bridge-limits.ts
6
+ /**
7
+ * What the page sends the shell, fitted to the bridge's limits (Claude review finding 11): a long
8
+ * label or validation message is shortened, and a page with more fields than one message lists
9
+ * sends those that matter first. A report the bridge refuses would leave the shell's rail and its
10
+ * blocker list stale, and a refused flush answer would stop every trusted Submit.
11
+ */
12
+ /** Text shortened to what the bridge carries. */
13
+ function clip(text) {
14
+ return text.length <= BRIDGE_TEXT_MAX ? text : `${text.slice(0, BRIDGE_TEXT_MAX - 1)}…`;
15
+ }
16
+ /** At most {@link BRIDGE_FIELDS_MAX} items, those that matter first, in their order otherwise. */
17
+ function firstThatMatter(items, matters) {
18
+ if (items.length <= BRIDGE_FIELDS_MAX) return [...items];
19
+ return [...items.filter(matters), ...items.filter((item) => !matters(item))].slice(0, BRIDGE_FIELDS_MAX);
20
+ }
21
+ /** Blockers as the bridge carries them: labels shortened, at most {@link BRIDGE_FIELDS_MAX}. */
22
+ function blockersForShell(blockers) {
23
+ return blockers.slice(0, BRIDGE_FIELDS_MAX).map((blocker) => blocker.label === void 0 ? blocker : {
24
+ ...blocker,
25
+ label: clip(blocker.label)
26
+ });
27
+ }
28
+ //#endregion
29
+ //#region src/runtime/verify-probe.ts
30
+ /**
31
+ * `verify`'s field probe (D138 item 3): in the `verify` target only, the page keeps a record of
32
+ * every field it mounts (the hook, the scope and the type of the initial value) and how much
33
+ * server work it waits on, and publishes both on `globalThis[VERIFY_PROBE]`, where the verify
34
+ * runner reads them. Viewers' pages never install it.
35
+ */
36
+ /** `typeof`, with arrays and `null` named as such. */
37
+ function typeName(value) {
38
+ if (value === null) return "null";
39
+ if (Array.isArray(value)) return "array";
40
+ return typeof value;
41
+ }
42
+ /** Records the store's field mounts on `globalThis[VERIFY_PROBE]`. */
43
+ function installVerifyProbe(store) {
44
+ const entries = /* @__PURE__ */ new Map();
45
+ store.observeMounts((registration, mounted) => {
46
+ const shape = {
47
+ hook: registration.hook ?? "useField",
48
+ scope: registration.scope,
49
+ initialType: typeName(registration.initial)
50
+ };
51
+ let entry = entries.get(registration.id);
52
+ if (!entry) {
53
+ entry = {
54
+ first: shape,
55
+ mounts: 0,
56
+ conflicts: /* @__PURE__ */ new Set()
57
+ };
58
+ entries.set(registration.id, entry);
59
+ }
60
+ if (mounted) {
61
+ entry.mounts += 1;
62
+ if (shape.hook !== entry.first.hook) entry.conflicts.add(`${entry.first.hook} and ${shape.hook}`);
63
+ if (shape.scope !== entry.first.scope) entry.conflicts.add(`scopes ${entry.first.scope} and ${shape.scope}`);
64
+ if (shape.initialType !== entry.first.initialType) entry.conflicts.add(`initial values of types ${entry.first.initialType} and ${shape.initialType}`);
65
+ } else entry.mounts = Math.max(0, entry.mounts - 1);
66
+ });
67
+ const fields = () => [...entries].map(([id, entry]) => ({
68
+ id,
69
+ scope: entry.first.scope,
70
+ hook: entry.first.hook,
71
+ initialType: entry.first.initialType,
72
+ mounts: entry.mounts,
73
+ conflicts: [...entry.conflicts]
74
+ }));
75
+ const pending = () => store.server.pending();
76
+ Object.defineProperty(globalThis, VERIFY_PROBE, {
77
+ value: {
78
+ fields,
79
+ pending
80
+ },
81
+ configurable: true
82
+ });
83
+ }
84
+ //#endregion
85
+ //#region src/runtime/boot.ts
86
+ /**
87
+ * Starts the client runtime for the page (D111): reads the delivery target, connects to the shell
88
+ * and the gateway in live mode, opens the journal, and wires the page's lifecycle (blur,
89
+ * `visibilitychange`, `pagehide`) and the bridge (`mostra/fields`, `mostra/flush`,
90
+ * `mostra/field.focus`, `mostra/version.available`) to the store.
91
+ */
92
+ /** The delivery target the page was built or exported for: its `mostra-target` meta tag (D111). */
93
+ function deliveryTarget() {
94
+ if (typeof document === "undefined") return "live";
95
+ const content = document.querySelector("meta[name=\"mostra-target\"]")?.getAttribute("content")?.trim();
96
+ return content === "static" || content === "mcp-app" || content === "verify" ? content : "live";
97
+ }
98
+ /** A random ID for this tab. */
99
+ function tabId() {
100
+ const bytes = /* @__PURE__ */ new Uint8Array(12);
101
+ globalThis.crypto.getRandomValues(bytes);
102
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
103
+ }
104
+ function locks() {
105
+ return globalThis.navigator?.locks;
106
+ }
107
+ const TAB_LOCK = "mostra-tab:";
108
+ /** How long a tab waits for other tabs to say they are open, where Web Locks are missing. */
109
+ const PRESENCE_WAIT_MS = 300;
110
+ /**
111
+ * Where Web Locks are missing (they need a secure context, and an artifact served over plain
112
+ * HTTP on a LAN address isn't one), open tabs answer a roll call on a BroadcastChannel, which
113
+ * works everywhere: a tab that doesn't answer within {@link PRESENCE_WAIT_MS} is gone.
114
+ */
115
+ function rollCall(tab) {
116
+ if (typeof BroadcastChannel === "undefined") return void 0;
117
+ const channel = new BroadcastChannel("mostra-tabs");
118
+ channel.onmessage = (event) => {
119
+ if (event.data?.who) channel.postMessage({ here: tab });
120
+ };
121
+ return (tabs) => new Promise((resolve) => {
122
+ const open = /* @__PURE__ */ new Set();
123
+ const listen = new BroadcastChannel("mostra-tabs");
124
+ listen.onmessage = (event) => {
125
+ if (typeof event.data?.here === "string") open.add(event.data.here);
126
+ };
127
+ listen.postMessage({ who: true });
128
+ setTimeout(() => {
129
+ listen.close();
130
+ resolve(new Set(tabs.filter((other) => !open.has(other))));
131
+ }, PRESENCE_WAIT_MS);
132
+ });
133
+ }
134
+ /**
135
+ * Holds this tab's lock for as long as the page lives, so other pages know its journal entries
136
+ * belong to an open tab (Web Locks). Returns which of some tabs hold no lock: they are gone.
137
+ */
138
+ function tabPresence(tab) {
139
+ const api = locks();
140
+ if (!api) return rollCall(tab) ?? (async (tabs) => new Set(tabs));
141
+ api.request(`${TAB_LOCK}${tab}`, () => new Promise(() => void 0));
142
+ return async (tabs) => {
143
+ const held = new Set(((await api.query()).held ?? []).map((lock) => lock.name).filter((name) => name?.startsWith(TAB_LOCK) === true).map((name) => name.slice(11)));
144
+ return new Set(tabs.filter((other) => !held.has(other)));
145
+ };
146
+ }
147
+ /** Runs `fn` while no other page of the artifact adopts journal entries. */
148
+ function exclusive(fn) {
149
+ const api = locks();
150
+ if (!api) return fn();
151
+ return api.request("mostra-journal", fn);
152
+ }
153
+ /** The longest pause between two attempts at the shell's handshake. */
154
+ const SHELL_RETRY_MAX_MS = 8e3;
155
+ /**
156
+ * The shell's connection for a framed page. A page inside a frame is inside Mostra's shell (its
157
+ * origin is framed by the shell only): if the handshake fails (the daemon restarting during the
158
+ * frame's load, a slow shell), it tries again rather than run on its own, where its answers
159
+ * would stay in this browser while the shell said "Saved" (FAILURE_MODEL §1 rule 4; Claude review
160
+ * finding 4). A page opened on its own has no shell and runs locally.
161
+ */
162
+ async function framedShell() {
163
+ if (typeof window === "undefined" || window.parent === window) return void 0;
164
+ for (let attempt = 0;; attempt += 1) {
165
+ const shell = await connectShell();
166
+ if (shell) return shell;
167
+ if (attempt === 1) console.warn("Mostra: waiting for the shell; the page connects when it can.");
168
+ await new Promise((resolve) => setTimeout(resolve, Math.min(500 * 2 ** attempt, SHELL_RETRY_MAX_MS)));
169
+ }
170
+ }
171
+ /**
172
+ * Starts the runtime: live behind the gateway inside Mostra's shell, local otherwise (a static
173
+ * export, an MCP App until its transport arrives, a page opened on its own).
174
+ */
175
+ async function startRuntime() {
176
+ const target = deliveryTarget();
177
+ const tab = tabId();
178
+ const shell = target === "live" || target === "verify" ? await framedShell() : void 0;
179
+ const view = shell?.host.mostra ? readViewContext(shell.host.mostra) : void 0;
180
+ const built = builtVersion();
181
+ if (shell && view && built !== void 0 && built !== view.version.id) {
182
+ const scratch = /* @__PURE__ */ new Map();
183
+ const store = new Store({
184
+ mode: "local",
185
+ journal: new MemoryJournal(),
186
+ tab,
187
+ local: {
188
+ getItem: (key) => scratch.get(key) ?? null,
189
+ setItem: (key, value) => void scratch.set(key, value),
190
+ removeItem: (key) => void scratch.delete(key)
191
+ }
192
+ });
193
+ store.start();
194
+ return {
195
+ store,
196
+ target,
197
+ mode: view.target,
198
+ shell
199
+ };
200
+ }
201
+ if (!shell || !view) {
202
+ const store = new Store({
203
+ mode: "local",
204
+ journal: new MemoryJournal(),
205
+ tab
206
+ });
207
+ store.start();
208
+ return {
209
+ store,
210
+ target,
211
+ mode: target === "live" ? "static" : target,
212
+ shell: void 0
213
+ };
214
+ }
215
+ const journal = await IndexedDbJournal.open() ?? new MemoryJournal();
216
+ const store = new Store({
217
+ mode: "live",
218
+ transport: new HttpTransport(view.viewId, { ...shell.session.csrf !== "" ? { csrf: shell.session.csrf } : {} }),
219
+ journal,
220
+ view,
221
+ scopes: shell.session.scopes,
222
+ ...shell.session.viewer ? { viewer: shell.session.viewer } : {},
223
+ tab,
224
+ goneTabs: tabPresence(tab),
225
+ exclusive,
226
+ channel: (name) => typeof BroadcastChannel === "undefined" ? void 0 : new BroadcastChannel(name)
227
+ });
228
+ if (target === "verify") installVerifyProbe(store);
229
+ store.start();
230
+ wireLifecycle(store);
231
+ wireShell(store, shell);
232
+ return {
233
+ store,
234
+ target,
235
+ mode: view.target,
236
+ shell
237
+ };
238
+ }
239
+ /** Saves on blur, and when the page is hidden or closed (IN-02). */
240
+ function wireLifecycle(store) {
241
+ if (typeof document === "undefined") return;
242
+ document.addEventListener("focusout", () => void store.flush(), true);
243
+ document.addEventListener("visibilitychange", () => {
244
+ if (document.visibilityState === "hidden") store.flush({ keepalive: true });
245
+ });
246
+ window.addEventListener("pagehide", () => void store.flush({ keepalive: true }));
247
+ }
248
+ /** Focuses the element a field is bound to, when the viewer picks it in the shell. */
249
+ function focusField(id) {
250
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id;
251
+ const element = document.querySelector(`[data-mostra-field="${escaped}"], [name="${escaped}"], #${escaped}`);
252
+ element?.scrollIntoView({ block: "center" });
253
+ element?.focus();
254
+ }
255
+ /** Reports fields and save state to the shell, and answers its requests. */
256
+ function wireShell(store, shell) {
257
+ let last = "";
258
+ let scheduled = false;
259
+ const report = () => {
260
+ scheduled = false;
261
+ const params = {
262
+ fields: firstThatMatter(store.registrations().map((registration) => ({
263
+ id: registration.id,
264
+ scope: registration.scope,
265
+ ...registration.label !== void 0 ? { label: clip(registration.label) } : {},
266
+ ...registration.required !== void 0 ? { required: registration.required } : {},
267
+ ...registration.sensitive !== void 0 ? { sensitive: registration.sensitive } : {},
268
+ ...registration.error !== void 0 ? { error: clip(registration.error) } : {},
269
+ ...store.conflict(`${registration.scope}:${registration.id}`) ? { conflict: true } : {}
270
+ })), (field) => Boolean(field.required || field.error !== void 0 || field.conflict)),
271
+ save: store.pageSave(),
272
+ unsaved: store.unsaved()
273
+ };
274
+ const text = JSON.stringify(params);
275
+ if (text === last) return;
276
+ last = text;
277
+ try {
278
+ shell.bridge.notify(mostraFields, params);
279
+ } catch (error) {
280
+ console.error("Mostra: the page's fields couldn't be reported to the shell.", error);
281
+ }
282
+ };
283
+ store.subscribe(() => {
284
+ if (scheduled) return;
285
+ scheduled = true;
286
+ queueMicrotask(report);
287
+ });
288
+ report();
289
+ listenShell({
290
+ request: async (message, params) => {
291
+ if (message !== mostraFlush) return void 0;
292
+ if (params.reason === "submit") {
293
+ const prepared = await store.prepareSubmit();
294
+ return {
295
+ revision: String(prepared.revision),
296
+ unsaved: prepared.unsaved,
297
+ blockers: blockersForShell(prepared.blockers)
298
+ };
299
+ }
300
+ const flushed = await store.flush();
301
+ return {
302
+ revision: String(flushed.revision),
303
+ unsaved: flushed.unsaved,
304
+ blockers: blockersForShell(store.blockers())
305
+ };
306
+ },
307
+ notification: (message, params) => {
308
+ if (message === mostraFieldFocus) focusField(params.id);
309
+ if (message === mostraVersionAvailable) window.dispatchEvent(new CustomEvent("mostra:version-available", { detail: params }));
310
+ }
311
+ });
312
+ }
313
+ //#endregion
314
+ //#region src/server-reference.ts
315
+ const unavailable = async (id) => {
316
+ const error = /* @__PURE__ */ new Error(`The server function ${id} can't run here: this delivery target has no Mostra server. Open the artifact through its Mostra link to use it.`);
317
+ error.code = "MOSTRA_TARGET_UNAVAILABLE";
318
+ throw error;
319
+ };
320
+ let callServer = unavailable;
321
+ /**
322
+ * Installs how server references call the server: the runtime does it when the page starts (a
323
+ * live page calls Mostra; a static export keeps the default, which rejects). Returns the function
324
+ * that restores the previous one.
325
+ */
326
+ function setServerCallback(callback) {
327
+ const previous = callServer;
328
+ callServer = callback;
329
+ return () => {
330
+ callServer = previous;
331
+ };
332
+ }
333
+ /**
334
+ * Creates the client stub of one server function. The builder emits these calls; artifact code
335
+ * imports the server module as usual and never calls this itself.
336
+ */
337
+ function createServerReference(id) {
338
+ const reference = (...args) => callServer(id, args);
339
+ Object.defineProperty(reference, "mostraServerFunctionId", {
340
+ value: id,
341
+ enumerable: true
342
+ });
343
+ return reference;
344
+ }
345
+ //#endregion
346
+ //#region src/storage.ts
347
+ /**
348
+ * `window.storage`, compatible with Claude artifacts' persistent storage (D53, D112): agents that
349
+ * learned `await window.storage.get(key)` keep working, and their values become Mostra fields.
350
+ * Personal keys are the viewer's draft fields `storage.<key>`, carried over between versions and
351
+ * delivered with the submission; `shared: true` keys are artifact state, every viewer's (D63).
352
+ *
353
+ * Differences from Claude's, by design (D112): a value is any JSON value up to 1 MiB (Mostra's
354
+ * field limit; a larger one rejects with `MOSTRA_FIELD_TOO_LARGE`, never truncated), a missing key
355
+ * resolves `null`, and two writers of one key end with the last write (a write that keeps meeting
356
+ * another writer's change is refused with `MOSTRA_FIELD_CONFLICT`, never reported saved).
357
+ */
358
+ const PREFIX = "storage.";
359
+ /**
360
+ * How many times a write over another writer's change is tried (last write wins, D112) before it
361
+ * is refused with `MOSTRA_FIELD_CONFLICT`.
362
+ */
363
+ const ATTEMPTS = 6;
364
+ function fieldIdOf(key) {
365
+ if (typeof key !== "string" || !isFieldId(`${PREFIX}${key}`)) throw new MostraError("MOSTRA_FIELD_ID_INVALID", `${JSON.stringify(key)} can't be a storage key: use 1 to 248 characters without control characters or empty dot-separated parts.`);
366
+ return `${PREFIX}${key}`;
367
+ }
368
+ /** Creates the `window.storage` of a page's store. */
369
+ function createStorage(store) {
370
+ const write = async (key, value, shared) => {
371
+ const id = fieldIdOf(key);
372
+ const scope = shared ? "artifact" : "viewer";
373
+ await store.whenReady();
374
+ if (value !== void 0) {
375
+ if (JSON.stringify(value) === void 0) throw new MostraError("MOSTRA_HOST_INVALID_PARAMS", `The value of ${key} isn't JSON.`);
376
+ }
377
+ store.set(scope, id, value, {
378
+ immediate: true,
379
+ ...value === void 0 ? { delete: true } : {}
380
+ });
381
+ const k = fieldKey(scope, id);
382
+ const settled = () => new Promise((resolve) => {
383
+ const done = () => store.refusal(k) !== void 0 || store.conflict(k) !== void 0 || store.save(k) === "saved" || store.save(k) === "readonly";
384
+ if (done()) {
385
+ resolve();
386
+ return;
387
+ }
388
+ const stop = store.subscribe(() => {
389
+ if (!done()) return;
390
+ stop();
391
+ resolve();
392
+ });
393
+ });
394
+ for (let attempt = 0; attempt < ATTEMPTS; attempt += 1) {
395
+ await store.flush();
396
+ await settled();
397
+ const refused = store.refusal(k);
398
+ if (refused !== void 0) {
399
+ store.takeTheirs(k);
400
+ throw new MostraError(refused.code, refused.message);
401
+ }
402
+ if (!store.conflict(k)) return;
403
+ if (attempt === 5) break;
404
+ await new Promise((resolve) => setTimeout(resolve, 20 * 2 ** attempt * (1 + Math.random())));
405
+ store.keepMine(k);
406
+ }
407
+ store.takeTheirs(k);
408
+ throw new MostraError("MOSTRA_FIELD_CONFLICT", `${key} kept changing under another writer, so this write wasn't saved.`);
409
+ };
410
+ return {
411
+ async get(key, shared = false) {
412
+ const id = fieldIdOf(key);
413
+ await store.whenReady();
414
+ const value = store.value(fieldKey(shared ? "artifact" : "viewer", id), void 0);
415
+ return value === void 0 ? null : {
416
+ key,
417
+ value,
418
+ shared
419
+ };
420
+ },
421
+ async set(key, value, shared = false) {
422
+ await write(key, value, shared);
423
+ return {
424
+ key,
425
+ value,
426
+ shared
427
+ };
428
+ },
429
+ async delete(key, shared = false) {
430
+ await write(key, void 0, shared);
431
+ return {
432
+ key,
433
+ deleted: true,
434
+ shared
435
+ };
436
+ },
437
+ async list(prefix, shared = false) {
438
+ await store.whenReady();
439
+ return {
440
+ keys: store.ids(shared ? "artifact" : "viewer").filter((id) => id.startsWith(PREFIX)).map((id) => id.slice(8)).filter((key) => prefix === void 0 || key.startsWith(prefix)),
441
+ ...prefix !== void 0 ? { prefix } : {},
442
+ shared
443
+ };
444
+ }
445
+ };
446
+ }
447
+ /** Installs `window.storage` for the page's store. */
448
+ function installStorage(store) {
449
+ if (typeof window === "undefined") return;
450
+ Object.defineProperty(window, "storage", {
451
+ value: createStorage(store),
452
+ configurable: true,
453
+ enumerable: false,
454
+ writable: true
455
+ });
456
+ }
457
+ //#endregion
458
+ //#region src/mount.ts
459
+ /**
460
+ * Mounting an artifact (D57, D111): the generated client entry hands the page to
461
+ * {@link mountArtifact}, or its `pages/` routes to `mountRoutes` from `@mostra/react/router`. The
462
+ * client runtime starts first, so the page renders with the viewer's saved answers rather than
463
+ * flashing its defaults; after a short wait it renders anyway and catches up when the gateway
464
+ * answers.
465
+ */
466
+ /** How long the first render waits for the viewer's saved answers. */
467
+ const FIRST_RENDER_WAIT_MS = 3e3;
468
+ /** Starts the runtime, installs `window.storage`, and resolves once the page can render. */
469
+ async function prepareArtifact() {
470
+ const runtime = await startRuntime();
471
+ installStorage(runtime.store);
472
+ if (runtime.store.mode === "live") setServerCallback((id, args) => runtime.store.server.call(id, args));
473
+ await runtime.store.whenReady(FIRST_RENDER_WAIT_MS);
474
+ return runtime;
475
+ }
476
+ /**
477
+ * The root's error handlers: in the `verify` target, an error a boundary caught goes to verify's
478
+ * recorder with its `cause` chain (React's default logs it with `console.error`, which verify
479
+ * couldn't tell apart from the page's own logging); everywhere else, React's defaults.
480
+ */
481
+ function rootOptions(mode) {
482
+ if (mode !== "verify") return void 0;
483
+ const report = globalThis[VERIFY_REPORT];
484
+ if (typeof report !== "function") return void 0;
485
+ return { onCaughtError: (error) => report(error) };
486
+ }
487
+ /** Renders `node` inside the runtime into `#root`, and reports `mostra/ready`. */
488
+ function renderArtifact(runtime, node) {
489
+ const root = document.getElementById("root");
490
+ if (!root) throw new Error("The page has no #root element.");
491
+ createRoot(root, rootOptions(runtime.mode)).render(createElement(RuntimeProvider, {
492
+ store: runtime.store,
493
+ mode: runtime.mode
494
+ }, node));
495
+ reportReady();
496
+ }
497
+ /** Tells the shell which page the viewer is on (D57), so comments record it. */
498
+ function reportPage(runtime, path, title) {
499
+ runtime.shell?.bridge.notify(mostraPage, {
500
+ path,
501
+ ...title !== void 0 && title !== "" ? { title } : {}
502
+ });
503
+ }
504
+ /**
505
+ * Mounts a single-page artifact: the generated entry calls it with the entry's default export.
506
+ * Artifact code doesn't need to.
507
+ */
508
+ async function mountArtifact(options) {
509
+ const runtime = await prepareArtifact();
510
+ renderArtifact(runtime, createElement(options.page));
511
+ reportPage(runtime, "/", document.title);
512
+ }
513
+ //#endregion
514
+ export { createServerReference as a, reportPage as i, prepareArtifact as n, setServerCallback as o, renderArtifact as r, mountArtifact as t };
515
+
516
+ //# sourceMappingURL=mount-Bpg0M5ln.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-Bpg0M5ln.js","names":[],"sources":["../src/runtime/bridge-limits.ts","../src/runtime/verify-probe.ts","../src/runtime/boot.ts","../src/server-reference.ts","../src/storage.ts","../src/mount.ts"],"sourcesContent":["/**\n * What the page sends the shell, fitted to the bridge's limits (Claude review finding 11): a long\n * label or validation message is shortened, and a page with more fields than one message lists\n * sends those that matter first. A report the bridge refuses would leave the shell's rail and its\n * blocker list stale, and a refused flush answer would stop every trusted Submit.\n */\nimport { BRIDGE_FIELDS_MAX, BRIDGE_TEXT_MAX, type SubmitBlocker } from \"@mostra/protocol\";\n\n/** Text shortened to what the bridge carries. */\nexport function clip(text: string): string {\n return text.length <= BRIDGE_TEXT_MAX ? text : `${text.slice(0, BRIDGE_TEXT_MAX - 1)}…`;\n}\n\n/** At most {@link BRIDGE_FIELDS_MAX} items, those that matter first, in their order otherwise. */\nexport function firstThatMatter<T>(items: readonly T[], matters: (item: T) => boolean): T[] {\n if (items.length <= BRIDGE_FIELDS_MAX) return [...items];\n return [...items.filter(matters), ...items.filter((item) => !matters(item))].slice(\n 0,\n BRIDGE_FIELDS_MAX,\n );\n}\n\n/** Blockers as the bridge carries them: labels shortened, at most {@link BRIDGE_FIELDS_MAX}. */\nexport function blockersForShell(blockers: readonly SubmitBlocker[]): SubmitBlocker[] {\n return blockers\n .slice(0, BRIDGE_FIELDS_MAX)\n .map((blocker) =>\n blocker.label === undefined ? blocker : { ...blocker, label: clip(blocker.label) },\n );\n}\n","\"use client\";\n\n/**\n * `verify`'s field probe (D138 item 3): in the `verify` target only, the page keeps a record of\n * every field it mounts (the hook, the scope and the type of the initial value) and how much\n * server work it waits on, and publishes both on `globalThis[VERIFY_PROBE]`, where the verify\n * runner reads them. Viewers' pages never install it.\n */\nimport { type ProbedField, VERIFY_PROBE } from \"@mostra/protocol\";\nimport type { Registration, Store } from \"./store.ts\";\n\n/** `typeof`, with arrays and `null` named as such. */\nfunction typeName(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n return typeof value;\n}\n\ninterface Entry {\n readonly first: { readonly hook: string; readonly scope: string; readonly initialType: string };\n mounts: number;\n readonly conflicts: Set<string>;\n}\n\n/** Records the store's field mounts on `globalThis[VERIFY_PROBE]`. */\nexport function installVerifyProbe(store: Store): void {\n const entries = new Map<string, Entry>();\n store.observeMounts((registration: Registration, mounted: boolean) => {\n const shape = {\n hook: registration.hook ?? \"useField\",\n scope: registration.scope,\n initialType: typeName(registration.initial),\n };\n let entry = entries.get(registration.id);\n if (!entry) {\n entry = { first: shape, mounts: 0, conflicts: new Set() };\n entries.set(registration.id, entry);\n }\n if (mounted) {\n entry.mounts += 1;\n if (shape.hook !== entry.first.hook)\n entry.conflicts.add(`${entry.first.hook} and ${shape.hook}`);\n if (shape.scope !== entry.first.scope)\n entry.conflicts.add(`scopes ${entry.first.scope} and ${shape.scope}`);\n if (shape.initialType !== entry.first.initialType)\n entry.conflicts.add(\n `initial values of types ${entry.first.initialType} and ${shape.initialType}`,\n );\n } else entry.mounts = Math.max(0, entry.mounts - 1);\n });\n const fields = (): ProbedField[] =>\n [...entries].map(([id, entry]) => ({\n id,\n scope: entry.first.scope as ProbedField[\"scope\"],\n hook: entry.first.hook,\n initialType: entry.first.initialType,\n mounts: entry.mounts,\n conflicts: [...entry.conflicts],\n }));\n // `pending`: the server work the page waits on (calls, runs, first live values), which the\n // runner's quiescence wait includes (Opus review finding 1).\n const pending = (): number => store.server.pending();\n Object.defineProperty(globalThis, VERIFY_PROBE, {\n value: { fields, pending },\n configurable: true,\n });\n}\n","/**\n * Starts the client runtime for the page (D111): reads the delivery target, connects to the shell\n * and the gateway in live mode, opens the journal, and wires the page's lifecycle (blur,\n * `visibilitychange`, `pagehide`) and the bridge (`mostra/fields`, `mostra/flush`,\n * `mostra/field.focus`, `mostra/version.available`) to the store.\n */\nimport {\n type DeliveryTarget,\n type FieldsParams,\n type FlushResult,\n mostraFieldFocus,\n mostraFields,\n mostraFlush,\n mostraVersionAvailable,\n readViewContext,\n type VersionAvailableParams,\n type ViewContext,\n} from \"@mostra/protocol\";\nimport { builtVersion, connectShell, listenShell, type ShellConnection } from \"../shell-bridge.ts\";\nimport { blockersForShell, clip, firstThatMatter } from \"./bridge-limits.ts\";\nimport { IndexedDbJournal, type Journal, MemoryJournal } from \"./journal.ts\";\nimport { Store } from \"./store.ts\";\nimport { HttpTransport } from \"./transport.ts\";\nimport { installVerifyProbe } from \"./verify-probe.ts\";\n\n/** The page's runtime: its store, where it runs, and its shell connection. */\nexport interface Runtime {\n readonly store: Store;\n /** The delivery target the page was built or exported for (D111): it chooses the history. */\n readonly target: DeliveryTarget;\n /**\n * Where the page runs, as `useArtifact` reports it: the target, except that a live page opened\n * outside Mostra's shell has no server features and reports `static`.\n */\n readonly mode: DeliveryTarget;\n /** The shell connection, inside Mostra's shell. */\n readonly shell: ShellConnection | undefined;\n}\n\n/** The delivery target the page was built or exported for: its `mostra-target` meta tag (D111). */\nexport function deliveryTarget(): DeliveryTarget {\n if (typeof document === \"undefined\") return \"live\";\n const content = document\n .querySelector('meta[name=\"mostra-target\"]')\n ?.getAttribute(\"content\")\n ?.trim();\n return content === \"static\" || content === \"mcp-app\" || content === \"verify\" ? content : \"live\";\n}\n\n/** A random ID for this tab. */\nfunction tabId(): string {\n const bytes = new Uint8Array(12);\n globalThis.crypto.getRandomValues(bytes);\n return [...bytes].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\ninterface Locks {\n request(name: string, callback: () => Promise<unknown>): Promise<unknown>;\n request(\n name: string,\n options: { mode?: \"exclusive\" | \"shared\" },\n callback: () => Promise<unknown>,\n ): Promise<unknown>;\n query(): Promise<{ held?: { name?: string }[] }>;\n}\n\nfunction locks(): Locks | undefined {\n const navigatorLocks = (globalThis.navigator as { locks?: Locks } | undefined)?.locks;\n return navigatorLocks;\n}\n\nconst TAB_LOCK = \"mostra-tab:\";\n\n/** How long a tab waits for other tabs to say they are open, where Web Locks are missing. */\nconst PRESENCE_WAIT_MS = 300;\n\n/**\n * Where Web Locks are missing (they need a secure context, and an artifact served over plain\n * HTTP on a LAN address isn't one), open tabs answer a roll call on a BroadcastChannel, which\n * works everywhere: a tab that doesn't answer within {@link PRESENCE_WAIT_MS} is gone.\n */\nfunction rollCall(\n tab: string,\n): ((tabs: readonly string[]) => Promise<ReadonlySet<string>>) | undefined {\n if (typeof BroadcastChannel === \"undefined\") return undefined;\n const channel = new BroadcastChannel(\"mostra-tabs\");\n channel.onmessage = (event: MessageEvent<{ who?: true; here?: string }>) => {\n if (event.data?.who) channel.postMessage({ here: tab });\n };\n return (tabs) =>\n new Promise((resolve) => {\n const open = new Set<string>();\n const listen = new BroadcastChannel(\"mostra-tabs\");\n listen.onmessage = (event: MessageEvent<{ here?: string }>) => {\n if (typeof event.data?.here === \"string\") open.add(event.data.here);\n };\n listen.postMessage({ who: true });\n setTimeout(() => {\n listen.close();\n resolve(new Set(tabs.filter((other) => !open.has(other))));\n }, PRESENCE_WAIT_MS);\n });\n}\n\n/**\n * Holds this tab's lock for as long as the page lives, so other pages know its journal entries\n * belong to an open tab (Web Locks). Returns which of some tabs hold no lock: they are gone.\n */\nfunction tabPresence(tab: string): (tabs: readonly string[]) => Promise<ReadonlySet<string>> {\n const api = locks();\n if (!api) return rollCall(tab) ?? (async (tabs) => new Set(tabs));\n void api.request(`${TAB_LOCK}${tab}`, () => new Promise<never>(() => undefined));\n return async (tabs) => {\n const held = new Set(\n ((await api.query()).held ?? [])\n .map((lock) => lock.name)\n .filter((name): name is string => name?.startsWith(TAB_LOCK) === true)\n .map((name) => name.slice(TAB_LOCK.length)),\n );\n return new Set(tabs.filter((other) => !held.has(other)));\n };\n}\n\n/** Runs `fn` while no other page of the artifact adopts journal entries. */\nfunction exclusive<T>(fn: () => Promise<T>): Promise<T> {\n const api = locks();\n if (!api) return fn();\n return api.request(\"mostra-journal\", fn) as Promise<T>;\n}\n\n/** The longest pause between two attempts at the shell's handshake. */\nconst SHELL_RETRY_MAX_MS = 8_000;\n\n/**\n * The shell's connection for a framed page. A page inside a frame is inside Mostra's shell (its\n * origin is framed by the shell only): if the handshake fails (the daemon restarting during the\n * frame's load, a slow shell), it tries again rather than run on its own, where its answers\n * would stay in this browser while the shell said \"Saved\" (FAILURE_MODEL §1 rule 4; Claude review\n * finding 4). A page opened on its own has no shell and runs locally.\n */\nasync function framedShell(): Promise<ShellConnection | undefined> {\n if (typeof window === \"undefined\" || window.parent === window) return undefined;\n for (let attempt = 0; ; attempt += 1) {\n const shell = await connectShell();\n if (shell) return shell;\n if (attempt === 1)\n console.warn(\"Mostra: waiting for the shell; the page connects when it can.\");\n await new Promise((resolve) =>\n setTimeout(resolve, Math.min(500 * 2 ** attempt, SHELL_RETRY_MAX_MS)),\n );\n }\n}\n\n/**\n * Starts the runtime: live behind the gateway inside Mostra's shell, local otherwise (a static\n * export, an MCP App until its transport arrives, a page opened on its own).\n */\nexport async function startRuntime(): Promise<Runtime> {\n const target = deliveryTarget();\n const tab = tabId();\n const shell = target === \"live\" || target === \"verify\" ? await framedShell() : undefined;\n // Read the tolerant way: a later shell may send values this page doesn't know (Claude review\n // finding 5), which never cost it its view.\n const view: ViewContext | undefined = shell?.host.mostra\n ? readViewContext(shell.host.mostra)\n : undefined;\n const built = builtVersion();\n if (shell && view && built !== undefined && built !== view.version.id) {\n // This page is another version than the view the shell opened: another tab moved the frame\n // session on (D98 item 5). Nothing it shows may be saved as this view, nor kept: the shell,\n // told the page's version in `ui/initialize`, loads the frame again at its own version.\n const scratch = new Map<string, string>();\n const store = new Store({\n mode: \"local\",\n journal: new MemoryJournal(),\n tab,\n local: {\n getItem: (key) => scratch.get(key) ?? null,\n setItem: (key, value) => void scratch.set(key, value),\n removeItem: (key) => void scratch.delete(key),\n },\n });\n store.start();\n return { store, target, mode: view.target, shell };\n }\n if (!shell || !view) {\n const store = new Store({ mode: \"local\", journal: new MemoryJournal(), tab });\n store.start();\n return { store, target, mode: target === \"live\" ? \"static\" : target, shell: undefined };\n }\n const journal: Journal = (await IndexedDbJournal.open()) ?? new MemoryJournal();\n const store = new Store({\n mode: \"live\",\n transport: new HttpTransport(view.viewId, {\n ...(shell.session.csrf !== \"\" ? { csrf: shell.session.csrf } : {}),\n }),\n journal,\n view,\n scopes: shell.session.scopes,\n ...(shell.session.viewer ? { viewer: shell.session.viewer } : {}),\n tab,\n goneTabs: tabPresence(tab),\n exclusive,\n channel: (name) =>\n typeof BroadcastChannel === \"undefined\" ? undefined : new BroadcastChannel(name),\n });\n // A verify run reads which fields the page mounts (D138); a viewer's page never records them.\n if (target === \"verify\") installVerifyProbe(store);\n store.start();\n wireLifecycle(store);\n wireShell(store, shell);\n return { store, target, mode: view.target, shell };\n}\n\n/** Saves on blur, and when the page is hidden or closed (IN-02). */\nfunction wireLifecycle(store: Store): void {\n if (typeof document === \"undefined\") return;\n document.addEventListener(\"focusout\", () => void store.flush(), true);\n document.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") void store.flush({ keepalive: true });\n });\n window.addEventListener(\"pagehide\", () => void store.flush({ keepalive: true }));\n}\n\n/** Focuses the element a field is bound to, when the viewer picks it in the shell. */\nfunction focusField(id: string): void {\n const escaped = typeof CSS !== \"undefined\" && CSS.escape ? CSS.escape(id) : id;\n const element = document.querySelector<HTMLElement>(\n `[data-mostra-field=\"${escaped}\"], [name=\"${escaped}\"], #${escaped}`,\n );\n element?.scrollIntoView({ block: \"center\" });\n element?.focus();\n}\n\n/** Reports fields and save state to the shell, and answers its requests. */\nfunction wireShell(store: Store, shell: ShellConnection): void {\n let last = \"\";\n let scheduled = false;\n const report = (): void => {\n scheduled = false;\n const fields = store.registrations().map((registration) => ({\n id: registration.id,\n scope: registration.scope,\n ...(registration.label !== undefined ? { label: clip(registration.label) } : {}),\n ...(registration.required !== undefined ? { required: registration.required } : {}),\n ...(registration.sensitive !== undefined ? { sensitive: registration.sensitive } : {}),\n ...(registration.error !== undefined ? { error: clip(registration.error) } : {}),\n ...(store.conflict(`${registration.scope}:${registration.id}`) ? { conflict: true } : {}),\n }));\n const params: FieldsParams = {\n fields: firstThatMatter(fields, (field) =>\n Boolean(field.required || field.error !== undefined || field.conflict),\n ),\n save: store.pageSave(),\n unsaved: store.unsaved(),\n };\n const text = JSON.stringify(params);\n if (text === last) return;\n last = text;\n try {\n shell.bridge.notify(mostraFields, params);\n } catch (error) {\n // Never silently: the shell's rail would keep its last state (Claude review finding 11).\n console.error(\"Mostra: the page's fields couldn't be reported to the shell.\", error);\n }\n };\n store.subscribe(() => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(report);\n });\n report();\n listenShell({\n request: async (message, params) => {\n if (message !== mostraFlush) return undefined;\n const reason = (params as { reason: \"submit\" | \"switch\" }).reason;\n if (reason === \"submit\") {\n const prepared = await store.prepareSubmit();\n const result: FlushResult = {\n revision: String(prepared.revision),\n unsaved: prepared.unsaved,\n blockers: blockersForShell(prepared.blockers),\n };\n return result;\n }\n const flushed = await store.flush();\n const result: FlushResult = {\n revision: String(flushed.revision),\n unsaved: flushed.unsaved,\n blockers: blockersForShell(store.blockers()),\n };\n return result;\n },\n notification: (message, params) => {\n if (message === mostraFieldFocus) focusField((params as { id: string }).id);\n if (message === mostraVersionAvailable) {\n // The page may show its own notice; the shell's trusted banner always appears (§5).\n window.dispatchEvent(\n new CustomEvent<VersionAvailableParams>(\"mostra:version-available\", {\n detail: params as VersionAvailableParams,\n }),\n );\n }\n },\n });\n}\n","\"use client\";\n\n/**\n * The client side of `\"use server\"` functions (D19 seam 2, D54). The builder compiles every export\n * of a `\"use server\"` module in client code into a server reference: a function that carries the\n * server function's ID and hands the call to the page's runtime, as React's own server references\n * hand theirs to the callback `setServerCallback` installs. The runtime calls Mostra (a plain\n * call: no record, the result streamed back); where there is no Mostra server, such as a static\n * export, the call rejects with `MOSTRA_TARGET_UNAVAILABLE`.\n */\n\n/** A `\"use server\"` function as client code sees it. */\nexport interface ServerReference<A extends unknown[] = unknown[], R = unknown> {\n /** Calls the server function with `args`; resolves with its result. */\n (...args: A): Promise<R>;\n /** The server function's ID: its module path, `#` and its export name (`actions.ts#sendReply`). */\n readonly mostraServerFunctionId: string;\n}\n\n/** How the page's runtime calls a server function. */\nexport type CallServer = (id: string, args: unknown[]) => Promise<unknown>;\n\nconst unavailable: CallServer = async (id) => {\n const error = new Error(\n `The server function ${id} can't run here: this delivery target has no Mostra server. Open the artifact through its Mostra link to use it.`,\n ) as Error & { code: string };\n error.code = \"MOSTRA_TARGET_UNAVAILABLE\";\n throw error;\n};\n\nlet callServer: CallServer = unavailable;\n\n/**\n * Installs how server references call the server: the runtime does it when the page starts (a\n * live page calls Mostra; a static export keeps the default, which rejects). Returns the function\n * that restores the previous one.\n */\nexport function setServerCallback(callback: CallServer): () => void {\n const previous = callServer;\n callServer = callback;\n return () => {\n callServer = previous;\n };\n}\n\n/**\n * Creates the client stub of one server function. The builder emits these calls; artifact code\n * imports the server module as usual and never calls this itself.\n */\nexport function createServerReference(id: string): ServerReference {\n const reference = (...args: unknown[]): Promise<unknown> => callServer(id, args);\n Object.defineProperty(reference, \"mostraServerFunctionId\", { value: id, enumerable: true });\n return reference as unknown as ServerReference;\n}\n","\"use client\";\n\n/**\n * `window.storage`, compatible with Claude artifacts' persistent storage (D53, D112): agents that\n * learned `await window.storage.get(key)` keep working, and their values become Mostra fields.\n * Personal keys are the viewer's draft fields `storage.<key>`, carried over between versions and\n * delivered with the submission; `shared: true` keys are artifact state, every viewer's (D63).\n *\n * Differences from Claude's, by design (D112): a value is any JSON value up to 1 MiB (Mostra's\n * field limit; a larger one rejects with `MOSTRA_FIELD_TOO_LARGE`, never truncated), a missing key\n * resolves `null`, and two writers of one key end with the last write (a write that keeps meeting\n * another writer's change is refused with `MOSTRA_FIELD_CONFLICT`, never reported saved).\n */\nimport { isFieldId } from \"@mostra/protocol\";\nimport { MostraError } from \"./runtime/errors.ts\";\nimport { fieldKey, type Store } from \"./runtime/store.ts\";\n\n/** What `get` and `set` resolve with. */\nexport interface StorageEntry {\n /** The key. */\n readonly key: string;\n /** Its value: any JSON value. */\n readonly value: unknown;\n /** Whether it is shared by every viewer (artifact state) or the viewer's own. */\n readonly shared: boolean;\n}\n\n/** The storage API of Claude artifacts, backed by Mostra fields (D53). */\nexport interface ClaudeCompatibleStorage {\n /** The value of `key`, or `null` when it has none. */\n get(key: string, shared?: boolean): Promise<StorageEntry | null>;\n /** Stores `value` under `key`; resolves once Mostra saved it. */\n set(key: string, value: unknown, shared?: boolean): Promise<StorageEntry>;\n /** Removes `key`; resolves once Mostra saved that. */\n delete(\n key: string,\n shared?: boolean,\n ): Promise<{ readonly key: string; readonly deleted: true; readonly shared: boolean }>;\n /** The keys stored, those starting with `prefix` when it is given. */\n list(\n prefix?: string,\n shared?: boolean,\n ): Promise<{ readonly keys: string[]; readonly prefix?: string; readonly shared: boolean }>;\n}\n\nconst PREFIX = \"storage.\";\n\n/**\n * How many times a write over another writer's change is tried (last write wins, D112) before it\n * is refused with `MOSTRA_FIELD_CONFLICT`.\n */\nconst ATTEMPTS = 6;\n\nfunction fieldIdOf(key: unknown): string {\n if (typeof key !== \"string\" || !isFieldId(`${PREFIX}${key}`)) {\n throw new MostraError(\n \"MOSTRA_FIELD_ID_INVALID\",\n `${JSON.stringify(key)} can't be a storage key: use 1 to 248 characters without control characters or empty dot-separated parts.`,\n );\n }\n return `${PREFIX}${key}`;\n}\n\n/** Creates the `window.storage` of a page's store. */\nexport function createStorage(store: Store): ClaudeCompatibleStorage {\n const write = async (key: string, value: unknown, shared: boolean): Promise<void> => {\n const id = fieldIdOf(key);\n const scope = shared ? \"artifact\" : \"viewer\";\n await store.whenReady();\n if (value !== undefined) {\n const text = JSON.stringify(value);\n if (text === undefined) {\n throw new MostraError(\"MOSTRA_HOST_INVALID_PARAMS\", `The value of ${key} isn't JSON.`);\n }\n }\n store.set(scope, id, value, {\n immediate: true,\n ...(value === undefined ? { delete: true } : {}),\n });\n const k = fieldKey(scope, id);\n // Settled: saved, refused or in conflict. Offline, the write waits in the journal and the\n // promise with it: it resolves when Mostra has the value, never before (GPT review finding 10).\n const settled = (): Promise<void> =>\n new Promise((resolve) => {\n const done = (): boolean =>\n store.refusal(k) !== undefined ||\n store.conflict(k) !== undefined ||\n store.save(k) === \"saved\" ||\n store.save(k) === \"readonly\";\n if (done()) {\n resolve();\n return;\n }\n const stop = store.subscribe(() => {\n if (!done()) return;\n stop();\n resolve();\n });\n });\n for (let attempt = 0; attempt < ATTEMPTS; attempt += 1) {\n await store.flush();\n await settled();\n const refused = store.refusal(k);\n if (refused !== undefined) {\n store.takeTheirs(k);\n throw new MostraError(refused.code, refused.message);\n }\n if (!store.conflict(k)) return;\n if (attempt === ATTEMPTS - 1) break;\n // Another writer got in between: written again over theirs, after a short, growing pause.\n await new Promise((resolve) => setTimeout(resolve, 20 * 2 ** attempt * (1 + Math.random())));\n store.keepMine(k);\n }\n // Another writer kept changing it: the write is refused, never reported saved, and the field\n // keeps their value (Claude review finding 9).\n store.takeTheirs(k);\n throw new MostraError(\n \"MOSTRA_FIELD_CONFLICT\",\n `${key} kept changing under another writer, so this write wasn't saved.`,\n );\n };\n return {\n async get(key, shared = false) {\n const id = fieldIdOf(key);\n await store.whenReady();\n const value = store.value(fieldKey(shared ? \"artifact\" : \"viewer\", id), undefined);\n return value === undefined ? null : { key, value, shared };\n },\n async set(key, value, shared = false) {\n await write(key, value, shared);\n return { key, value, shared };\n },\n async delete(key, shared = false) {\n await write(key, undefined, shared);\n return { key, deleted: true as const, shared };\n },\n async list(prefix, shared = false) {\n await store.whenReady();\n const keys = store\n .ids(shared ? \"artifact\" : \"viewer\")\n .filter((id) => id.startsWith(PREFIX))\n .map((id) => id.slice(PREFIX.length))\n .filter((key) => prefix === undefined || key.startsWith(prefix));\n return { keys, ...(prefix !== undefined ? { prefix } : {}), shared };\n },\n };\n}\n\ndeclare global {\n interface Window {\n /** Claude-compatible persistent storage, backed by Mostra fields (D53). */\n storage: ClaudeCompatibleStorage;\n }\n}\n\n/** Installs `window.storage` for the page's store. */\nexport function installStorage(store: Store): void {\n if (typeof window === \"undefined\") return;\n Object.defineProperty(window, \"storage\", {\n value: createStorage(store),\n configurable: true,\n enumerable: false,\n writable: true,\n });\n}\n","\"use client\";\n\n/**\n * Mounting an artifact (D57, D111): the generated client entry hands the page to\n * {@link mountArtifact}, or its `pages/` routes to `mountRoutes` from `@mostra/react/router`. The\n * client runtime starts first, so the page renders with the viewer's saved answers rather than\n * flashing its defaults; after a short wait it renders anyway and catches up when the gateway\n * answers.\n */\nimport { mostraPage, VERIFY_REPORT } from \"@mostra/protocol\";\nimport { type ComponentType, createElement, type ReactNode } from \"react\";\nimport { createRoot, type RootOptions } from \"react-dom/client\";\nimport { type ArtifactMode, RuntimeProvider } from \"./hooks.ts\";\nimport { type Runtime, startRuntime } from \"./runtime/boot.ts\";\nimport { setServerCallback } from \"./server-reference.ts\";\nimport { reportReady } from \"./shell-bridge.ts\";\nimport { installStorage } from \"./storage.ts\";\n\n/** How long the first render waits for the viewer's saved answers. */\nconst FIRST_RENDER_WAIT_MS = 3_000;\n\n/** Starts the runtime, installs `window.storage`, and resolves once the page can render. */\nexport async function prepareArtifact(): Promise<Runtime> {\n const runtime = await startRuntime();\n installStorage(runtime.store);\n // Server references call through this page's runtime in live mode (D54); elsewhere (a static\n // export, a page opened outside its shell) they keep refusing with `MOSTRA_TARGET_UNAVAILABLE`,\n // naming the function.\n if (runtime.store.mode === \"live\") {\n setServerCallback((id, args) => runtime.store.server.call(id, args));\n }\n await runtime.store.whenReady(FIRST_RENDER_WAIT_MS);\n return runtime;\n}\n\n/**\n * The root's error handlers: in the `verify` target, an error a boundary caught goes to verify's\n * recorder with its `cause` chain (React's default logs it with `console.error`, which verify\n * couldn't tell apart from the page's own logging); everywhere else, React's defaults.\n */\nfunction rootOptions(mode: string): RootOptions | undefined {\n if (mode !== \"verify\") return undefined;\n const report = (globalThis as unknown as Record<string, unknown>)[VERIFY_REPORT];\n if (typeof report !== \"function\") return undefined;\n return { onCaughtError: (error: unknown) => (report as (error: unknown) => void)(error) };\n}\n\n/** Renders `node` inside the runtime into `#root`, and reports `mostra/ready`. */\nexport function renderArtifact(runtime: Runtime, node: ReactNode): void {\n const root = document.getElementById(\"root\");\n if (!root) throw new Error(\"The page has no #root element.\");\n createRoot(root, rootOptions(runtime.mode)).render(\n createElement(\n RuntimeProvider,\n { store: runtime.store, mode: runtime.mode as ArtifactMode },\n node,\n ),\n );\n reportReady();\n}\n\n/** Tells the shell which page the viewer is on (D57), so comments record it. */\nexport function reportPage(runtime: Runtime, path: string, title?: string): void {\n runtime.shell?.bridge.notify(mostraPage, {\n path,\n ...(title !== undefined && title !== \"\" ? { title } : {}),\n });\n}\n\n/**\n * Mounts a single-page artifact: the generated entry calls it with the entry's default export.\n * Artifact code doesn't need to.\n */\nexport async function mountArtifact(options: { readonly page: ComponentType }): Promise<void> {\n const runtime = await prepareArtifact();\n renderArtifact(runtime, createElement(options.page));\n reportPage(runtime, \"/\", document.title);\n}\n"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { DeliveryTarget } from "@mostra/protocol";
2
+ import { ComponentType, ReactNode } from "react";
3
+ import { RouteObject } from "react-router";
4
+ //#region src/router.d.ts
5
+ /** One route of a `pages/` artifact: its React Router path and its page. */
6
+ export interface PageRouteModule {
7
+ readonly path: string;
8
+ readonly page: ComponentType;
9
+ }
10
+ /** How a router keeps its location: in the address bar, in its hash, or in memory. */
11
+ export type HistoryMode = "browser" | "hash" | "memory";
12
+ /** The history mode for a delivery target (D57, D111). */
13
+ export declare function historyMode(target: DeliveryTarget): HistoryMode;
14
+ /** The route tree: the pages, inside the layout when there is one. */
15
+ export declare function routeTree(routes: readonly PageRouteModule[], layout?: ComponentType<{
16
+ children?: ReactNode;
17
+ }>): RouteObject[];
18
+ /**
19
+ * Mounts a `pages/` artifact (D57). The generated entry calls it; artifact code doesn't need to.
20
+ */
21
+ export declare function mountRoutes(options: {
22
+ readonly routes: readonly PageRouteModule[];
23
+ readonly layout?: ComponentType<{
24
+ children?: ReactNode;
25
+ }>;
26
+ }): Promise<void>;
27
+ //#endregion
28
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","names":[],"sources":["../src/router.ts"],"mappings":";;;;;iBAwBiB;WACN;WACA,MAAM;;;YAIL;;wBAGI,YAAY,QAAQ,iBAAiB;;wBAOrC,UACd,iBAAiB,mBACjB,SAAS;EAAgB,WAAW;KACnC;;;;wBAYmB,YAAY;WACvB,iBAAiB;WACjB,SAAS;IAAgB,WAAW;;IAC3C"}