@markless/router 0.4.0 → 0.5.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.
@@ -1,5 +1,756 @@
1
- function protocolStateVersion(storage) {
2
- return (storage?.length ?? 0) > 0 ? 2 : 1;
1
+ import { i as protocolStateVersion, n as protocolInstanceQualifies, t as protocolInstancePath } from "../../protocol-constants-B4WxrZAS.js";
2
+ //#region ../serializer/src/storage-cookie.ts
3
+ const STORAGE_RENDER_CONTEXT_READER = "storageValue";
4
+ //#endregion
5
+ //#region ../web/src/ssr-data/awaitable.ts
6
+ function marklessIsThenable(value) {
7
+ return typeof value?.then === "function";
8
+ }
9
+ function marklessThen(value, next) {
10
+ return marklessIsThenable(value) ? value.then(next) : next(value);
11
+ }
12
+ /** `then` with the `finally` of the await it replaces: released on both edges. */
13
+ function marklessSettled(value, release, next) {
14
+ if (!marklessIsThenable(value)) {
15
+ release();
16
+ return next(value);
17
+ }
18
+ return value.then((settled) => {
19
+ release();
20
+ return next(settled);
21
+ }, (error) => {
22
+ release();
23
+ throw error;
24
+ });
25
+ }
26
+ //#endregion
27
+ //#region ../web/src/fns/instance-scope.ts
28
+ const INSTANCE_PATH = /^(?:[cpm]\d+:|r:[^:]*:)+/;
29
+ function marklessInstancePath(prefix) {
30
+ return prefix && INSTANCE_PATH.exec(prefix)?.[0] || "";
31
+ }
32
+ const ROW_SEGMENT = /r:[^:]*:/g;
33
+ function marklessRowFreeSymbolId(symbolId, instancePath) {
34
+ const path = instancePath ?? INSTANCE_PATH.exec(symbolId)?.[0] ?? "";
35
+ if (!path.includes("r:")) return symbolId;
36
+ return path.replace(ROW_SEGMENT, "") + symbolId.slice(path.length);
37
+ }
38
+ function qualifyingGraph(scoped, graph, qualify) {
39
+ const outerQualify = graph.marklessQualifyGraphNodeId;
40
+ const qualified = (method) => (operation) => graph[method].call(graph, {
41
+ ...operation,
42
+ graphNodeId: qualify(operation.graphNodeId)
43
+ });
44
+ return Object.assign(scoped, {
45
+ marklessQualifyGraphNodeId: (graphNodeId) => outerQualify ? outerQualify(qualify(graphNodeId)) : qualify(graphNodeId),
46
+ read: (graphNodeId, path) => graph.read(qualify(graphNodeId), path),
47
+ write: qualified("write"),
48
+ update: qualified("update"),
49
+ call: qualified("call"),
50
+ delete: qualified("delete"),
51
+ subscribe: qualified("subscribe")
52
+ });
53
+ }
54
+ const composedSymbols = /* @__PURE__ */ new WeakSet();
55
+ const ISLAND_SEGMENT = /^m\d+:/;
56
+ /**
57
+ * An island's symbol invokes callbacks by ids spelled in the island's own module
58
+ * space, but the page loader that answers them only knows page-space ids, so an
59
+ * id without an island segment is sent back with this island's.
60
+ */
61
+ function marklessIslandSpacedSymbol(symbol, symbolPrefix) {
62
+ const island = ISLAND_SEGMENT.exec(symbolPrefix)?.[0];
63
+ if (!island) return symbol;
64
+ const spaced = (context) => {
65
+ const invokeSymbol = context.invokeSymbol;
66
+ return symbol(invokeSymbol ? {
67
+ ...context,
68
+ invokeSymbol: (symbolId, invokeContext) => invokeSymbol(ISLAND_SEGMENT.test(symbolId) ? symbolId : island + symbolId, invokeContext)
69
+ } : context);
70
+ };
71
+ if (composedSymbols.has(symbol)) composedSymbols.add(spaced);
72
+ return spaced;
73
+ }
74
+ function marklessInstanceScopedLoadSymbol(loadSymbol) {
75
+ return (symbolId) => {
76
+ const instancePath = INSTANCE_PATH.exec(symbolId)?.[0];
77
+ if (!instancePath) return loadSymbol(symbolId);
78
+ return marklessThen(loadSymbol(marklessRowFreeSymbolId(symbolId, instancePath)), (symbol) => scopeSymbol(symbol, instancePath));
79
+ };
80
+ }
81
+ function scopeSymbol(symbol, instancePath) {
82
+ if (composedSymbols.has(symbol)) return symbol;
83
+ const rescope = symbol.marklessRescope;
84
+ if (rescope) return rescope(instancePath, scopeSymbol);
85
+ const { marklessScopedBase, marklessScopedPath } = symbol;
86
+ if (marklessScopedBase && (marklessScopedPath === instancePath || marklessScopedPath === instancePath.replace(ROW_SEGMENT, ""))) return scopeSymbol(marklessScopedBase, instancePath);
87
+ const scoped = (context) => {
88
+ const graph = context.graph;
89
+ return symbol({
90
+ ...context,
91
+ ...graph ? { graph: marklessInstanceScopedGraph(graph, instancePath) } : {},
92
+ getElementHandle: marklessInstanceScopedElementHandle(context.getElementHandle, instancePath, graph),
93
+ ...context.read && graph ? { read: (graphNodeId, path) => graph.read(marklessComposedGraphNodeId(graphNodeId, instancePath, marklessGraphWidgetRegistry(graph)), path) } : {}
94
+ });
95
+ };
96
+ return Object.assign(scoped, {
97
+ marklessScopedBase: symbol,
98
+ marklessScopedPath: instancePath
99
+ });
100
+ }
101
+ /**
102
+ * A widget-scoped `element()` handle names one element PER RENDERED WIDGET, and
103
+ * its compiled id is one module-level string every instance of that widget
104
+ * spells. Qualifying it exactly as the widget's graph nodes are qualified is
105
+ * what turns it back into a key: the registration and the reading handler both
106
+ * land on the rendered widget's own root path, so a page carrying two instances
107
+ * answers each handler with its own element instead of the last registration.
108
+ *
109
+ * A handle that is not widget-scoped is returned untouched. A component-local
110
+ * handle is already one element per key, and a page-scoped `shared()` graph is
111
+ * page space by design.
112
+ */
113
+ function marklessWidgetHandleId(handleId, instancePath, registry = marklessWidgetScope.active) {
114
+ if (!instancePath) return handleId;
115
+ const pageSpace = PAGE_SPACE_ID.exec(handleId);
116
+ if (!pageSpace || pageSpace[2] !== "shared") return handleId;
117
+ return marklessComposedGraphNodeId(handleId, instancePath, registry);
118
+ }
119
+ /**
120
+ * The reading half of the same key.
121
+ *
122
+ * The compiled symbol asks for the module-level id, so the instance it is
123
+ * running in is what has to be added here. Every registration a rendered widget
124
+ * makes is qualified the same way - the page-level ones at composition, the
125
+ * ones an `@if` arm files at resume - so a reader whose instance is named asks
126
+ * the qualified key and nothing else.
127
+ */
128
+ function marklessInstanceScopedElementHandle(getElementHandle, instancePath, graph) {
129
+ if (typeof getElementHandle !== "function") return getElementHandle;
130
+ if (getElementHandle.marklessHandleScope === instancePath) return getElementHandle;
131
+ const scopedReader = (handleIdOrName) => {
132
+ const scoped = marklessWidgetHandleId(handleIdOrName, instancePath, graph ? marklessGraphWidgetRegistry(graph) : marklessWidgetScope.active);
133
+ return scoped === handleIdOrName ? getElementHandle(handleIdOrName) : getElementHandle(scoped);
134
+ };
135
+ scopedReader.marklessHandleScope = instancePath;
136
+ return scopedReader;
137
+ }
138
+ function marklessInstanceScopedGraph(graph, instancePath) {
139
+ if (!instancePath) return graph;
140
+ if (graph.marklessInstancePath === instancePath) return graph;
141
+ const registry = marklessGraphWidgetRegistry(graph);
142
+ if (!composedRegistryViews.has(registry)) marklessNoteGraphWidgetRoots(registry, graph);
143
+ const takeWidgetView = () => composedRegistryViews.has(registry) ? registry : composedWidgetRegistryView(registry, instancePath);
144
+ let widgetView = takeWidgetView();
145
+ const scoped = qualifyingGraph({
146
+ ...graph,
147
+ marklessPageGraph: graph.marklessPageGraph ?? graph,
148
+ marklessInstancePath: instancePath,
149
+ get marklessComposedWidgets() {
150
+ return widgetView ??= takeWidgetView();
151
+ }
152
+ }, graph, (graphNodeId) => marklessComposedGraphNodeId(graphNodeId, instancePath, registry));
153
+ if (widgetView) marklessShareWidgetRegistry(scoped, widgetView);
154
+ return scoped;
155
+ }
156
+ /**
157
+ * The rendered widgets a part reading in THIS scope's LOCAL space can name.
158
+ *
159
+ * A component's compiled parts spell every id against that module's own edges.
160
+ * When the component is itself composed one wrapper deep - its markup projected
161
+ * through another component's children - the family root it composes is filed in
162
+ * the registry at its PAGE path (`c0:p1:c0:`) while the part reads with the
163
+ * module-local path (`c0:p1:p2:p3:`). Neither is a prefix of the other and
164
+ * `widgetRootPathFor` can only chop segments off the right, so every lookup
165
+ * misses, the id stays bare, and the write lands on a node no rendered widget
166
+ * owns. Re-spelling the roots that stand under this adapter's path without it
167
+ * puts reader and registry in one space; the adapter's own rule for an
168
+ * already-prefixed `shared:` id then carries the answer back to page space.
169
+ *
170
+ * Roots OUTSIDE the path are dropped rather than carried through unstripped,
171
+ * because a local path and a page path can spell the same segments: an
172
+ * unstripped entry would answer a local reader with a page root and this adapter
173
+ * would prefix it a second time. An id no root here claims is left bare, which
174
+ * is exactly what hands it back to this adapter, which resolves it as before.
175
+ */
176
+ function composedWidgetRegistryView(registry, instancePath) {
177
+ let view;
178
+ for (const [id, rootPath] of registry.rootPaths) {
179
+ if (!id.startsWith(instancePath) || !rootPath.startsWith(instancePath)) continue;
180
+ view ??= {
181
+ rootPaths: /* @__PURE__ */ new Map(),
182
+ rowRooted: /* @__PURE__ */ new Set()
183
+ };
184
+ marklessNoteWidgetRoot(view, id.slice(instancePath.length), rootPath.slice(instancePath.length));
185
+ }
186
+ if (view) composedRegistryViews.add(view);
187
+ return view;
188
+ }
189
+ const PAGE_SPACE_ID = /^((?:[cpm]\d+:|r:[^:]*:)*)(shared|storage):/;
190
+ /**
191
+ * One registry per RUNTIME GRAPH, and a graph is one rendered page.
192
+ *
193
+ * A widget root path is only ever an answer about the container that rendered
194
+ * it, so a realm-wide map is a category error: two Markless roots on one page
195
+ * each render their own widgets, and a torn-down container's roots must not
196
+ * answer for the render that replaces it. Filing the registry against the graph
197
+ * makes both true by construction - the entries die with the graph, and a
198
+ * lookup can only reach the rendered widgets of the graph it was asked about.
199
+ *
200
+ * The key is the PAGE graph, recovered through `marklessPageGraph`, because
201
+ * every scope adapter below hands its symbol a fresh wrapper object over the
202
+ * same page graph and all of them must reach the same answer.
203
+ */
204
+ const graphRegistries = /* @__PURE__ */ new WeakMap();
205
+ const composedRegistryViews = /* @__PURE__ */ new WeakSet();
206
+ /**
207
+ * Which registry the lookups below read when the CALLER names no graph.
208
+ *
209
+ * Two readers cannot name one. Inside a compose there is no graph yet, so
210
+ * composition.ts swaps this to the render's own registry and swaps it back.
211
+ * And the generated symbol-resolver module's bound-symbol adapter holds the
212
+ * dispatching graph in a closure it never passes on, so it reaches this field
213
+ * instead - which is why every entry point that DOES hold a graph points this
214
+ * at that graph's registry before running anything (`marklessScopeWidgetsTo`).
215
+ *
216
+ * `composing` is what keeps a dispatch from stealing the field out from under a
217
+ * compose. Only a server ever composes and dispatches at once.
218
+ */
219
+ const marklessWidgetScope = {
220
+ active: {
221
+ rootPaths: /* @__PURE__ */ new Map(),
222
+ rowRooted: /* @__PURE__ */ new Set()
223
+ },
224
+ composing: false
225
+ };
226
+ /**
227
+ * Points the graph-less readers at the registry of the graph now being read.
228
+ *
229
+ * A dispatch is not synchronous, so this is a pointer re-aimed per entry rather
230
+ * than a scope that unwinds: the next entry re-aims it, and a torn-down
231
+ * container's registry is never re-aimed at again. Two containers dispatching
232
+ * async bodies that interleave their writes are the one case it cannot separate
233
+ * - the readers that hold a graph are exact regardless.
234
+ */
235
+ function marklessScopeWidgetsTo(registry) {
236
+ if (!marklessWidgetScope.composing) marklessWidgetScope.active = registry;
237
+ return registry;
238
+ }
239
+ function marklessRegistryHolder(graph) {
240
+ const scoped = graph;
241
+ return scoped.marklessPageGraph ?? scoped.marklessWidgetHostGraph ?? graph;
242
+ }
243
+ /**
244
+ * This graph's rendered widgets, minted on first ask and filled from the graph
245
+ * itself: the payload's widget-scoped definitions carry their qualified root ids
246
+ * and the projection sites composition registered them under, which is every
247
+ * answer a lookup needs and the only place resume has ever read them from.
248
+ */
249
+ function marklessGraphWidgetRegistry(graph) {
250
+ if (!graph) return marklessWidgetScope.active;
251
+ const own = graph.marklessComposedWidgets ?? graphRegistries.get(graph);
252
+ if (own) return marklessScopeWidgetsTo(own);
253
+ const holder = marklessRegistryHolder(graph);
254
+ const held = graphRegistries.get(holder);
255
+ if (held) return marklessScopeWidgetsTo(held);
256
+ const registry = {
257
+ rootPaths: /* @__PURE__ */ new Map(),
258
+ rowRooted: /* @__PURE__ */ new Set()
259
+ };
260
+ graphRegistries.set(holder, registry);
261
+ marklessNoteGraphWidgetRoots(registry, graph);
262
+ return marklessScopeWidgetsTo(registry);
263
+ }
264
+ function marklessShareWidgetRegistry(graph, registry) {
265
+ graphRegistries.set(graph, registry);
266
+ }
267
+ function marklessNoteGraphWidgetRoots(registry, graph) {
268
+ for (const definition of graph.listSharedDefinitions?.() ?? []) if (definition.scope === "widget") {
269
+ const rootPath = marklessInstancePath(definition.id);
270
+ marklessNoteWidgetRoot(registry, definition.id, rootPath);
271
+ for (const projectionId of definition.projectionIds ?? []) marklessNoteWidgetRoot(registry, projectionId, rootPath);
272
+ }
273
+ }
274
+ function marklessNoteWidgetRoot(registry, id, rootPath) {
275
+ registry.rootPaths.set(id, rootPath);
276
+ if (rootPath.includes("r:")) registry.rowRooted.add(id.slice(marklessInstancePath(id).length));
277
+ }
278
+ /**
279
+ * The rendered widgets a client-minted repeat row is standing INSIDE.
280
+ *
281
+ * A minted row composes alone, so the registry that render builds holds only the
282
+ * roots the row itself is. A part reading a widget rooted in an ancestor - the
283
+ * collection the repeat iterates, most of all - would find nothing and mint a
284
+ * second instance of it, whose writes no served node ever sees. The mint reads
285
+ * the live page's roots off its graph and installs them here for the row's
286
+ * render; the `rowSegment` guard is what keeps a render running beside it from
287
+ * reading the answer, the same separation `marklessWidgetScope` settles for.
288
+ */
289
+ const enclosingWidgetScope = {
290
+ rowSegment: "",
291
+ roots: void 0
292
+ };
293
+ function enclosingRootPathFor(graphNodeId, roots) {
294
+ return byDefinition(graphNodeId, (key) => roots.get(key));
295
+ }
296
+ /**
297
+ * The same question asked of a path that may carry rows the ROOT does not.
298
+ *
299
+ * One root per rendered widget means a root inside a repeat is registered under
300
+ * its row and a root outside one is registered without any - but a part
301
+ * projected INTO an outside root from inside the repeat carries the row in its
302
+ * own path regardless. The walk below chops segments off the right, so a leading
303
+ * `r:<key>:` blocks it from ever reaching that root. Rows are asked first, so a
304
+ * per-row root always wins over the row-free reading; only a path no rendered
305
+ * row answers is asked again with its rows dropped.
306
+ */
307
+ function marklessWidgetRootPathThroughRows(graphNodeId, instancePath, registry) {
308
+ const withRows = widgetRootPathFor(graphNodeId, instancePath, registry);
309
+ if (withRows !== void 0) return withRows;
310
+ const rowFree = instancePath.replace(ROW_SEGMENT, "");
311
+ if (rowFree === instancePath) return "";
312
+ return widgetRootPathFor(graphNodeId, rowFree, registry) ?? "";
313
+ }
314
+ /**
315
+ * The two spellings of one containment, and the one that has to wait for a row.
316
+ *
317
+ * A widget root is filed under the path it RENDERED at, rows and all
318
+ * (`r:page%3A5:c0:p2:`). A dispatching bound symbol runs at the compose tree's
319
+ * path for that same containment, which names the component edges and no row
320
+ * (`c0:p2:p3:`) - a bound symbol is minted per edge, and a row key is a runtime
321
+ * value no symbol id can hold. Neither spelling is a prefix of the other, so the
322
+ * walk above can never get from one to the other on its own.
323
+ *
324
+ * Leaving the id in page space here is what breaks the projection bridge: the
325
+ * write lands on an id no rendered widget owns, the record matches, the symbol
326
+ * runs, and nothing moves. Keeping the reading path ON the id instead hands the
327
+ * question to `marklessRowWidgetGraphNodeId`, which is spelled for exactly this
328
+ * input - it holds the dispatched record's rows and re-asks the registry with
329
+ * them, reaching the ONE root this row rendered. An alias resolved at lookup,
330
+ * never a second registry entry, so one containment still yields one instance.
331
+ *
332
+ * Two guards keep that from minting a phantom instance instead. The path must
333
+ * carry no row of its own, because a path that already names its rows and STILL
334
+ * found no root is not waiting on one. And the definition must be one some row
335
+ * really did root: an id belonging to no widget at all - a page-scoped
336
+ * `shared()` graph, a storage slot - is page space and stays exactly as spelled.
337
+ */
338
+ function marklessUnresolvedWidgetGraphNodeId(graphNodeId, instancePath, registry) {
339
+ const rowRooted = registry.rowRooted;
340
+ if (instancePath.includes("r:") || rowRooted.size === 0) return graphNodeId;
341
+ return byDefinition(graphNodeId, (key) => rowRooted.has(key) || void 0) ? instancePath + graphNodeId : graphNodeId;
342
+ }
343
+ function widgetRootPathFor(graphNodeId, instancePath, registry) {
344
+ const widgetRootPaths = registry.rootPaths;
345
+ if (widgetRootPaths.size === 0) return void 0;
346
+ for (let end = instancePath.length; end > 0; end--) {
347
+ if (instancePath[end - 1] !== ":") continue;
348
+ const prefix = instancePath.slice(0, end);
349
+ const rootPath = byDefinition(graphNodeId, (key) => widgetRootPaths.get(prefix + key));
350
+ if (rootPath !== void 0) return rootPath;
351
+ }
352
+ }
353
+ function byDefinition(id, lookup) {
354
+ const slash = id.lastIndexOf("/");
355
+ return lookup(id) ?? (slash > 0 ? lookup(id.slice(0, slash)) : void 0);
356
+ }
357
+ function marklessComposedGraphNodeId(graphNodeId, instancePath, registry = marklessWidgetScope.active) {
358
+ if (!instancePath) return graphNodeId;
359
+ const pageSpace = PAGE_SPACE_ID.exec(graphNodeId);
360
+ if (!pageSpace) return instancePath + graphNodeId;
361
+ if (pageSpace[2] === "storage") return graphNodeId;
362
+ if (pageSpace[1]) {
363
+ const sharedId = graphNodeId.slice(pageSpace[1].length);
364
+ return ((!pageSpace[1].includes("r:") && instancePath.includes("r:") ? widgetRootPathFor(sharedId, instancePath + pageSpace[1], registry) : void 0) ?? instancePath + pageSpace[1]) + sharedId;
365
+ }
366
+ const rootPath = marklessWidgetRootPathThroughRows(graphNodeId, instancePath, registry);
367
+ if (rootPath) return rootPath + graphNodeId;
368
+ const enclosing = enclosingWidgetScope.roots && instancePath.startsWith(enclosingWidgetScope.rowSegment) ? enclosingRootPathFor(graphNodeId, enclosingWidgetScope.roots) : void 0;
369
+ if (enclosing) return enclosing + graphNodeId;
370
+ return marklessUnresolvedWidgetGraphNodeId(graphNodeId, instancePath, registry);
371
+ }
372
+ //#endregion
373
+ //#region ../web/src/fns/projection-html.ts
374
+ const PROJECTION_MARK_PREFIX = "<!--markless-projection:";
375
+ //#endregion
376
+ //#region ../web/src/prerender/shared-seed-slot.ts
377
+ /**
378
+ * The seed-map key a widget root's instance token travels under, restating
379
+ * MARKLESS_WIDGET_INSTANCE_KEY in @markless/compiler (public-render's
380
+ * residue-reader) so the browser never imports the compiler. It is not a graph
381
+ * node id: it names WHICH rendered widget the parts seeded from this map belong
382
+ * to, which is what a shared() element() handle's minted id has to carry.
383
+ * shared-seed-widget.test.ts pins this spelling; element-handle-idref.test.ts
384
+ * proves the two sides agree end to end.
385
+ */
386
+ const MARKLESS_WIDGET_INSTANCE_KEY = "markless:widget-instance";
387
+ /**
388
+ * The same token, filed PER shared definition.
389
+ *
390
+ * One element can carry handles declared by two different widget families - a
391
+ * radio item's own field handle and the group's set, a tree label's item handle
392
+ * and the root's - so "which rendered widget am I inside" has no single answer
393
+ * on that element. It has one answer per DEFINITION, which is what the reading
394
+ * handle names: a handle's graph node id is `<definitionId>/element:<name>`, so
395
+ * the mint asks for its own family's token and gets the instance the handle was
396
+ * DECLARED in, never whichever family last seeded this scope.
397
+ *
398
+ * The plain key above stays filed too: a page with one widget family reads it
399
+ * exactly as before, and it is the fallback when no per-definition token exists.
400
+ */
401
+ function marklessWidgetInstanceKey(definitionId) {
402
+ return `${MARKLESS_WIDGET_INSTANCE_KEY}|${definitionId}`;
403
+ }
404
+ /**
405
+ * Where a part stands in its family's roster, answered for the two regimes that
406
+ * ask it: at render there is no DOM, so the answer is the order this widget
407
+ * instance EMITS its members; after resume the roster is live, so the answer is
408
+ * its document order. The compiler lowers `roster.indexOf(mine)` to one call in
409
+ * each regime with the same two node ids, so the two cannot disagree.
410
+ */
411
+ /**
412
+ * The seed-map key this render's position counter travels under. The seed map
413
+ * is the one channel that reaches both the compiled SSR child render and the
414
+ * CSR component evaluation, and it already carries the widget-instance token a
415
+ * position counts within.
416
+ */
417
+ const MARKLESS_ROSTER_POSITIONS_KEY = "markless:roster-positions";
418
+ function marklessRosterPositions(seeds) {
419
+ const held = seeds?.get(MARKLESS_ROSTER_POSITIONS_KEY);
420
+ return held && typeof held === "object" ? held : void 0;
421
+ }
422
+ /** The render's seed map with a counter filed on it, minted once per render. */
423
+ function marklessRosterPositionSeeds(seeds) {
424
+ if (marklessRosterPositions(seeds)) return seeds;
425
+ const filed = new Map(seeds ?? []);
426
+ filed.set(MARKLESS_ROSTER_POSITIONS_KEY, { taken: /* @__PURE__ */ new Map() });
427
+ return filed;
428
+ }
429
+ /**
430
+ * Which rendered widget a roster belongs to, asked of its own family exactly as
431
+ * a minted handle id asks it: a handle's graph node id carries a module path, so
432
+ * the family is everything up to the LAST slash.
433
+ */
434
+ function rosterInstance(scope, rosterGraphNodeId) {
435
+ const family = rosterGraphNodeId.slice(0, rosterGraphNodeId.lastIndexOf("/"));
436
+ return scope?.get(marklessWidgetInstanceKey(family)) ?? scope?.get("markless:widget-instance");
437
+ }
438
+ /**
439
+ * The nth member of this instance's roster to ask is the nth in it.
440
+ *
441
+ * Render walks a widget instance in document order, so counting the asks IS
442
+ * that order — there is no DOM yet to compare against. The count is per widget
443
+ * instance, which is what keeps a second collection on the page starting at
444
+ * zero.
445
+ */
446
+ function renderRosterPosition(positions, seeds, rosterGraphNodeId, handleGraphNodeId) {
447
+ const instance = rosterInstance(seeds ?? positions.seeds, rosterGraphNodeId);
448
+ const key = `${String(instance)}|${rosterGraphNodeId}|${handleGraphNodeId}`;
449
+ const ledger = positions.seeding ?? positions.taken;
450
+ const taken = ledger.get(key) ?? 0;
451
+ ledger.set(key, taken + 1);
452
+ return taken;
453
+ }
454
+ /**
455
+ * How many parts this instance puts in the roster - asked, unlike a position,
456
+ * by a part that may render BEFORE the members it is counting. The root asking
457
+ * `ui-max` is exactly that: server render is one forward pass.
458
+ *
459
+ * So the ask does not answer. It writes a placeholder naming the roster's own
460
+ * registration key — the widget instance's path then the module-level handle id,
461
+ * which is how marklessWidgetHandleId qualifies it at composition — and the
462
+ * surface resolves it once composition has made the count a fact.
463
+ */
464
+ function renderRosterCount(positions, seeds, rosterGraphNodeId) {
465
+ const instance = rosterInstance(seeds ?? positions.seeds, rosterGraphNodeId);
466
+ const key = (typeof instance === "string" ? instance : "") + rosterGraphNodeId;
467
+ positions.counted = true;
468
+ return "" + key + "";
469
+ }
470
+ /**
471
+ * The two roster questions a derive may ask at render time, on one context.
472
+ * Every render-side evaluation site hands the symbol this same object, so a
473
+ * compiled call cannot reach a regime that answers only one of them.
474
+ */
475
+ function marklessRosterRenderContext(positions, seeds) {
476
+ if (!positions) return {};
477
+ return {
478
+ rosterPosition: (roster, handle) => renderRosterPosition(positions, seeds, roster, handle),
479
+ rosterCount: (roster) => renderRosterCount(positions, seeds, roster),
480
+ deferCount: (thunk) => positions.counted ? "" + String((positions.deferred ??= []).push(thunk) - 1) + "" : thunk(resolvedCount)
481
+ };
482
+ }
483
+ function resolvedCount(value) {
484
+ if (typeof value !== "number") throw new Error("MARKLESS_ROSTER_COUNT_UNRESOLVED");
485
+ return value;
486
+ }
487
+ //#endregion
488
+ //#region ../web/src/ssr-data/renderer.ts
489
+ const projectionSpans = /* @__PURE__ */ new Map();
490
+ let nextProjectionKey = 0;
491
+ function openProjectionSpan(tokens) {
492
+ const key = String(nextProjectionKey++);
493
+ const span = {
494
+ key,
495
+ mark: `${PROJECTION_MARK_PREFIX}${key}-->`,
496
+ tokens,
497
+ consumed: false
498
+ };
499
+ projectionSpans.set(key, span);
500
+ return span;
501
+ }
502
+ /**
503
+ * Runs one child render with a projection's token span registered, for a caller
504
+ * that places a projection itself rather than through `renderChunk` - the client
505
+ * mint of a `@for` row whose component projects children. `consumed` answers
506
+ * whether the child's own `{children}` slot reported the span back.
507
+ */
508
+ function withProjectionSpan(tokens, render) {
509
+ const span = openProjectionSpan(tokens);
510
+ const release = () => projectionSpans.delete(span.key);
511
+ let rendered;
512
+ try {
513
+ rendered = render(span.mark);
514
+ } catch (error) {
515
+ release();
516
+ throw error;
517
+ }
518
+ return marklessSettled(rendered, release, (result) => ({
519
+ result,
520
+ consumed: span.consumed
521
+ }));
522
+ }
523
+ //#endregion
524
+ //#region ../web/src/fns/roster-position.ts
525
+ /**
526
+ * The server render context with the roster channels on it. One counter per
527
+ * render, held by the closure rather than by a module, so two page renders in
528
+ * one process never share a count.
529
+ */
530
+ function marklessSsrRosterPositionContext(renderContext) {
531
+ const context = renderContext ?? {};
532
+ if (typeof context.rosterPosition === "function") return renderContext;
533
+ const sharedSeeds = marklessRosterPositionSeeds(context.sharedSeeds);
534
+ return {
535
+ ...context,
536
+ sharedSeeds,
537
+ ...marklessRosterRenderContext(marklessRosterPositions(sharedSeeds), void 0)
538
+ };
539
+ }
540
+ //#endregion
541
+ //#region ../web/src/prerender/island-roster.ts
542
+ /** The counts and count-spending expressions one island minted, made numbers. */
543
+ async function marklessSsrIslandRosterAnswered(renderContext, surface) {
544
+ const positions = marklessRosterPositions(renderContext?.sharedSeeds);
545
+ if (!positions?.counted) return surface;
546
+ const roster = await globalThis.__marklessRosterResume?.();
547
+ if (!roster) throw new Error("MARKLESS_ROSTER_COUNT_UNRESOLVED");
548
+ return roster.marklessResolveRosterCounts(roster.marklessResolveDeferredCounts(surface, positions.deferred ?? []));
549
+ }
550
+ //#endregion
551
+ //#region ../web/src/resume-anchor-census.ts
552
+ function isArmBranchAnchorComment(comment) {
553
+ const text = comment.data ?? comment.textContent ?? "";
554
+ return text.startsWith("markless:arm-branch:") || text.startsWith("/markless:arm-branch:");
555
+ }
556
+ //#endregion
557
+ //#region ../web/src/fns/scalar-specialized.ts
558
+ const e = (c, s) => {
559
+ throw Object.assign(Error(c), {
560
+ code: c,
561
+ site: s
562
+ }, c === "MARKLESS_PAYLOAD_INVALID" ? { docsUrl: "https://markless.dev/errors/MARKLESS_PAYLOAD_INVALID" } : {});
563
+ };
564
+ const marklessDecodeScalarCell = (c, g, s) => {
565
+ const u = c?.valueKind === "unknown";
566
+ try {
567
+ const v = c?.value, r = v?.root;
568
+ if (!c || c.graphNodeId !== g || c.valueKind !== "scalar" && !u || v?.version !== 1 || v.records?.length !== 0) e("MARKLESS_PAYLOAD_INVALID", s);
569
+ if (r == null || typeof r !== "object") return r;
570
+ if (r.$type === "undefined") return void 0;
571
+ if (r.$type === "bigint") return BigInt(r.value);
572
+ if (r.$type === "number") return Number(r.value);
573
+ if (r.$type === "date") {
574
+ const d = new Date(r.value);
575
+ if (!Number.isNaN(d.getTime())) return d;
576
+ }
577
+ } catch {}
578
+ e(u ? "MARKLESS_SCALAR_SPECIALIZED_ESCALATE" : "MARKLESS_PAYLOAD_INVALID", s);
579
+ };
580
+ //#endregion
581
+ //#region ../web/src/fns/dom-order.ts
582
+ const marklessFindElementAtDomOrderIndex = (r, i) => {
583
+ let c = r.__marklessCensus;
584
+ if (!c) {
585
+ const w = document.createTreeWalker(r, 1);
586
+ c = r.__marklessCensus = r.nodeType === 1 ? [r] : [];
587
+ for (let n; n = w.nextNode();) c.push(n);
588
+ }
589
+ return c[i];
590
+ };
591
+ //#endregion
592
+ //#region src/vite/runtime/mdx-scalar.ts
593
+ async function tryResumeMdxScalar(input, loadPlan, loadSymbol) {
594
+ const { root, event, element } = input;
595
+ if (root.__asyncResumeRuntimeStarted || root.isConnected === false || !element) return false;
596
+ if (root.querySelector("[overlay]:not([hidden])") || root.ownerDocument?.querySelector("script[type=\"markless/arm\"],script[type=\"markless/state-patch\"]")) return false;
597
+ let payload = payloads.get(root);
598
+ if (!payload) {
599
+ const state = readScript(root, "markless/state");
600
+ const view = readScript(root, "markless/view");
601
+ if (!state || !view || state.version !== protocolStateVersion(state.storage) || view.version !== 1) return false;
602
+ payload = {
603
+ state,
604
+ view,
605
+ plans: /* @__PURE__ */ new Map()
606
+ };
607
+ payloads.set(root, payload);
608
+ }
609
+ if (event !== 0 && !input.eventRecord && payload.view.events.some((record) => {
610
+ if (record.eventName !== event.type) return false;
611
+ const host = locate(root, payload.view, record.hostNodeId);
612
+ return host === element || host?.contains(element);
613
+ })) return false;
614
+ const priming = event === 0 || !input.eventRecord && (event.type === "focusin" || element.tagName === "BUTTON" && (event.type === "keydown" || event.type === "keyup") && (event.key === "Enter" || event.key === " "));
615
+ const record = priming ? payload.view.events.find((record) => record.eventName !== "visible" && locate(root, payload.view, record.hostNodeId) === element) : input.eventRecord;
616
+ if (!record) return false;
617
+ if (!priming && event.type !== record.eventName) return false;
618
+ if (!priming && event.bubbles === false && event.target !== element) return false;
619
+ if (payload.view.behaviors.some((behavior) => {
620
+ const host = locate(root, payload.view, behavior.hostNodeId);
621
+ return !host || host === element || host.contains(element);
622
+ })) return false;
623
+ const path = [{
624
+ record,
625
+ element
626
+ }];
627
+ if (!priming && event.bubbles !== false && !input.propagationStopped && !event.cancelBubble && element !== root) {
628
+ const candidates = payload.view.events.filter((record) => record.eventName === event.type).map((record) => ({
629
+ record,
630
+ element: locate(root, payload.view, record.hostNodeId)
631
+ }));
632
+ if (candidates.some((candidate) => !candidate.element)) return false;
633
+ for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
634
+ const record = candidates.find((candidate) => candidate.element === ancestor)?.record;
635
+ if (record) path.push({
636
+ record,
637
+ element: ancestor
638
+ });
639
+ if (ancestor === root) break;
640
+ }
641
+ }
642
+ const actions = [];
643
+ for (const matched of path) {
644
+ const record = matched.record;
645
+ if (record.action || record.symbolIds.length !== 1 || record.syncPolicy && !priming && (matched !== path[0] || !input.syncPolicyAlreadyApplied)) return false;
646
+ const symbolId = record.symbolIds[0];
647
+ const key = record.hostNodeId + "\n" + record.eventName + "\n" + symbolId;
648
+ let prepared = payload.plans.get(key);
649
+ if (prepared === void 0) {
650
+ const action = await loadPlan(symbolId);
651
+ prepared = action ? prepare(root, payload, action, record) : null;
652
+ payload.plans.set(key, prepared);
653
+ }
654
+ if (!prepared || prepared.element !== matched.element || prepared.element.isConnected === false || prepared.targets.some((target) => target.element.isConnected === false)) return false;
655
+ actions.push({
656
+ symbolId,
657
+ prepared
658
+ });
659
+ }
660
+ if (priming) return true;
661
+ for (const action of actions) {
662
+ const symbol = await loadSymbol(action.symbolId);
663
+ if (isDetached(root)) return true;
664
+ if (await runScalar(root, event, action.prepared, symbol)) break;
665
+ }
666
+ return true;
667
+ }
668
+ async function runScalar(root, event, prepared, symbol) {
669
+ const values = root.__marklessEventOnlyGraph ?? /* @__PURE__ */ new Map();
670
+ let value = values.has(prepared.cell) ? values.get(prepared.cell) : prepared.initial;
671
+ const previous = value;
672
+ const check = (id, path) => {
673
+ if (id !== prepared.cell || path?.length) throw new Error("MARKLESS_SCALAR_PLAN_MISMATCH");
674
+ };
675
+ const result = symbol({
676
+ graph: {
677
+ read(id, path) {
678
+ check(id, path);
679
+ return value;
680
+ },
681
+ write(write) {
682
+ check(write.graphNodeId, write.path);
683
+ value = write.value;
684
+ },
685
+ update(update) {
686
+ check(update.graphNodeId, update.path);
687
+ const old = value;
688
+ value = update.update(value);
689
+ return update.returnValue === "previous" ? old : update.returnValue === "next" ? value : void 0;
690
+ }
691
+ },
692
+ event,
693
+ element: prepared.element,
694
+ getElementHandle: () => void 0
695
+ });
696
+ const stopped = event.cancelBubble;
697
+ await result;
698
+ values.set(prepared.cell, value);
699
+ root.__marklessEventOnlyGraph = values;
700
+ if (!Object.is(previous, value)) for (const target of prepared.targets) target.element.textContent = target.prefix + (value == null ? "" : String(value)) + target.suffix;
701
+ return stopped || event.cancelBubble;
702
+ }
703
+ const payloads = /* @__PURE__ */ new WeakMap();
704
+ function prepare(root, { state, view }, action, record) {
705
+ const { plan, scope } = action;
706
+ if (plan.kind !== "scalar" || !scope || protocolInstancePath(scope) !== scope || protocolInstanceQualifies(plan.cell) !== true) return null;
707
+ const cell = scope + plan.cell;
708
+ if (scope + action.hostNodeId !== record.hostNodeId || action.eventName !== record.eventName || scope + plan.symbolId !== record.symbolIds[0]) return null;
709
+ const servedEvent = view.events.find((event) => event.hostNodeId === record.hostNodeId && event.eventName === record.eventName);
710
+ if (!servedEvent || servedEvent.symbolIds.length !== 1 || servedEvent.symbolIds[0] !== record.symbolIds[0]) return null;
711
+ const cells = state.cells.filter((candidate) => candidate.graphNodeId === cell);
712
+ if (cells.length !== 1 || cells[0].valueKind !== "scalar") return null;
713
+ const updates = view.domUpdates.filter((update) => update.graphNodeId === cell);
714
+ if (!updates.length || updates.length !== plan.textUpdates.length) return null;
715
+ const { cells: _cells, ...stateDependencies } = state;
716
+ const { domUpdates: _updates, ...viewDependencies } = view;
717
+ if (references(stateDependencies, cell) || references(viewDependencies, cell)) return null;
718
+ const element = locate(root, view, record.hostNodeId);
719
+ if (!element) return null;
720
+ const targets = [];
721
+ for (const expected of plan.textUpdates) {
722
+ const update = updates.find((candidate) => candidate.hostNodeId === scope + expected.hostNodeId && candidate.symbolId === scope + expected.symbolId);
723
+ if (!update || scope + expected.graphNodeId !== cell || update.path.length || update.target?.kind !== "text" || update.target.trueValue !== void 0 || update.target.falseValue !== void 0 || (update.target.prefix ?? "") !== (expected.prefix ?? "") || (update.target.suffix ?? "") !== (expected.suffix ?? "")) return null;
724
+ const target = locate(root, view, update.hostNodeId);
725
+ if (!target) return null;
726
+ targets.push({
727
+ element: target,
728
+ prefix: expected.prefix ?? "",
729
+ suffix: expected.suffix ?? ""
730
+ });
731
+ }
732
+ return {
733
+ cell,
734
+ initial: marklessDecodeScalarCell(cells[0], cell, "mdx-scalar"),
735
+ element,
736
+ targets
737
+ };
738
+ }
739
+ function locate(root, view, host) {
740
+ const locator = view.locators.find((locator) => locator.hostNodeId === host);
741
+ if (!locator || locator.strategy !== "dom-order") return null;
742
+ const element = marklessFindElementAtDomOrderIndex(root, locator.index);
743
+ return element?.tagName.toLowerCase() === locator.tagName.toLowerCase() ? element : null;
744
+ }
745
+ function readScript(root, type) {
746
+ const text = root.querySelector(`script[type="${type}"]`)?.textContent;
747
+ return text ? JSON.parse(text) : void 0;
748
+ }
749
+ function references(value, id) {
750
+ return value === id || !!value && typeof value === "object" && Object.values(value).some((child) => references(child, id));
751
+ }
752
+ function isDetached(root) {
753
+ return root.isConnected === false;
3
754
  }
4
755
  //#endregion
5
756
  //#region src/vite/runtime/mdx-route.ts
@@ -120,14 +871,34 @@ function createMdxRenderDataSurface(parts, children) {
120
871
  imports: Object.fromEntries(children.flatMap((child) => child.surface.rootComponentName ? [[child.surface.rootComponentName, child.surface]] : []))
121
872
  };
122
873
  }
123
- async function renderMdxChild(children, component, props, child) {
124
- const output = await component.renderSsr?.(props);
874
+ async function renderMdxChild(children, component, props, child, pageRenderContext) {
875
+ const storageValue = pageRenderContext?.[STORAGE_RENDER_CONTEXT_READER];
876
+ const renderContext = marklessSsrRosterPositionContext({
877
+ elementIdScope: child.hostPrefix,
878
+ ...storageValue ? { [STORAGE_RENDER_CONTEXT_READER]: storageValue } : {}
879
+ });
880
+ const rendered = await renderWithMdxChildren(component, props, child, renderContext);
881
+ const output = rendered && typeof rendered.html === "string" ? await marklessSsrIslandRosterAnswered(renderContext, rendered) : rendered;
125
882
  if (output) children.push({
126
883
  ...child,
127
884
  output
128
885
  });
129
886
  return output?.html ?? "";
130
887
  }
888
+ async function renderWithMdxChildren(component, props, child, renderContext) {
889
+ const tags = child.childrenElementTags;
890
+ const childrenHtml = props?.children;
891
+ if (!tags?.length || typeof childrenHtml !== "string") return component.renderSsr?.(props, renderContext);
892
+ const { result } = await withProjectionSpan(tags.map((tagName, index) => ({
893
+ kind: "element",
894
+ hostNodeId: `__mdx:children:${index}`,
895
+ tagName
896
+ })), (mark) => component.renderSsr?.({
897
+ ...props,
898
+ children: mark + childrenHtml
899
+ }, renderContext));
900
+ return result;
901
+ }
131
902
  function composeMdxState(children) {
132
903
  const childStates = children.map((child) => ({
133
904
  child,
@@ -137,62 +908,285 @@ function composeMdxState(children) {
137
908
  const storage = childStates.flatMap(({ state }) => state.storage ?? []);
138
909
  return {
139
910
  version: protocolStateVersion(storage),
140
- cells: childStates.flatMap(({ state }) => state.cells ?? []),
141
- computed: childStates.flatMap(({ child, state }) => (state.computed ?? []).map((record) => prefixMdxComputedRecord(record, child))),
142
- ...childStates.some(({ state }) => state.sharedDefinitions?.length) ? { sharedDefinitions: childStates.flatMap(({ state }) => state.sharedDefinitions ?? []) } : {},
911
+ cells: childStates.flatMap(({ child, state }) => {
912
+ const island = islandScope(child, state);
913
+ return (state.cells ?? []).map((cell) => islandScopedGraphRecord(cell, island));
914
+ }),
915
+ computed: childStates.flatMap(({ child, state }) => {
916
+ const island = islandScope(child, state);
917
+ return (state.computed ?? []).map((record) => ({
918
+ ...islandScopedGraphRecord(record, island),
919
+ ...record.dependencies ? { dependencies: record.dependencies.map((read) => islandScopedGraphRecord(read, island)) } : {},
920
+ ...record.deriveSymbolId ? { deriveSymbolId: child.symbolPrefix + record.deriveSymbolId } : {}
921
+ }));
922
+ }),
923
+ ...childStates.some(({ state }) => state.sharedSeeds?.length) ? { sharedSeeds: childStates.flatMap(({ child, state }) => {
924
+ const island = islandScope(child, state);
925
+ return (state.sharedSeeds ?? []).map((seed) => ({
926
+ ...islandScopedGraphRecord(seed, island),
927
+ deriveSymbolId: child.symbolPrefix + seed.deriveSymbolId,
928
+ ...seed.dependencies ? { dependencies: seed.dependencies.map((dependency) => ({
929
+ ...islandScopedGraphRecord(dependency, island),
930
+ ...dependency.reads ? { reads: islandScopedGraphRecord(dependency.reads, island) } : {}
931
+ })) } : {}
932
+ }));
933
+ }) } : {},
934
+ ...childStates.some(({ state }) => state.sharedDefinitions?.length) ? { sharedDefinitions: childStates.flatMap(({ child, state }) => {
935
+ const island = islandScope(child, state);
936
+ return (state.sharedDefinitions ?? []).map((definition) => islandScopedSharedDefinition(definition, island));
937
+ }) } : {},
143
938
  ...storage.length > 0 ? { storage } : {}
144
939
  };
145
940
  }
146
- function composeMdxView(parts, children, initialElementOffset) {
147
- const childViews = children.map((child) => ({
941
+ function islandScope(child, state) {
942
+ const prefix = child.symbolPrefix;
943
+ return {
944
+ segment: prefix && protocolInstancePath(prefix) === prefix ? prefix : "",
945
+ widgetDefinitionIds: new Set((state.sharedDefinitions ?? []).filter((definition) => definition.scope === "widget").map((definition) => definition.id))
946
+ };
947
+ }
948
+ /**
949
+ * Which of a child's ids belong to the island, and which are the whole page's.
950
+ *
951
+ * Two islands of one component compose from their own roots, so both spell the
952
+ * same `c`/`p` path and their cells, computeds and element-handle rosters land
953
+ * on one node unless the island segment separates them. What must NOT take the
954
+ * segment is the page-space families that mean one node per page on purpose: a
955
+ * `scope: 'page' | 'container' | 'request'` shared() graph and a storage slot
956
+ * are shared BETWEEN islands, and prefixing them gives each island a private
957
+ * copy of state the author asked to be common.
958
+ *
959
+ * A widget-scoped shared() id is the exception inside the exception — one graph
960
+ * per rendered widget, so it is the island's — and a bare one (no path, because
961
+ * no registered widget root claimed it at compose) is recognised by asking the
962
+ * child's own definitions which scope it was declared at.
963
+ */
964
+ function islandScopedGraphNodeId(graphNodeId, island) {
965
+ if (!island.segment) return graphNodeId;
966
+ if (protocolInstancePath(graphNodeId)) return island.segment + graphNodeId;
967
+ if (protocolInstanceQualifies(graphNodeId) !== false) return island.segment + graphNodeId;
968
+ const slash = graphNodeId.lastIndexOf("/");
969
+ return island.widgetDefinitionIds.has(graphNodeId) || slash > 0 && island.widgetDefinitionIds.has(graphNodeId.slice(0, slash)) ? island.segment + graphNodeId : graphNodeId;
970
+ }
971
+ /**
972
+ * The registering half of a handle key, mirrored on the reading half.
973
+ *
974
+ * Resume re-spells only a widget-scoped `shared:` handle id per rendered widget;
975
+ * a component-local handle is already one element per key and the reading symbol
976
+ * asks for it exactly as its module compiled it. Segmenting that second kind
977
+ * here would file it under a key nothing ever asks for.
978
+ */
979
+ function islandScopedHandleId(handleId, island) {
980
+ return protocolInstanceQualifies(handleId) !== false ? handleId : islandScopedGraphNodeId(handleId, island);
981
+ }
982
+ function islandScopedGraphRecord(record, island) {
983
+ return record.graphNodeId ? {
984
+ ...record,
985
+ graphNodeId: islandScopedGraphNodeId(record.graphNodeId, island)
986
+ } : record;
987
+ }
988
+ function islandScopedSharedDefinition(definition, island) {
989
+ const scoped = (id) => islandScopedGraphNodeId(id, island);
990
+ return {
991
+ ...definition,
992
+ id: scoped(definition.id),
993
+ ...definition.graphNodeIds ? { graphNodeIds: definition.graphNodeIds.map(scoped) } : {},
994
+ ...definition.projectionIds ? { projectionIds: definition.projectionIds.map(scoped) } : {},
995
+ ...definition.returnProperties ? { returnProperties: definition.returnProperties.map((property) => islandScopedGraphRecord(property, island)) } : {}
996
+ };
997
+ }
998
+ function composeMdxView(parts, children, initialElementOffset, initialCommentOffset = 0) {
999
+ const childCensus = children.map((child) => ({
148
1000
  ...child,
149
1001
  view: child.output?.view,
150
- hostCount: child.output?.view?.locators?.length ?? 0
151
- })).filter((child) => Boolean(child.view));
152
- if (childViews.length === 0) return;
153
- const childByIndex = new Map(childViews.map((child) => [child.componentIndex, child]));
1002
+ census: mdxRenderedCensus(child.output)
1003
+ }));
1004
+ const firstView = childCensus.find((child) => child.view)?.view;
1005
+ if (!firstView) return;
1006
+ const childByIndex = new Map(childCensus.map((child) => [child.componentIndex, child]));
154
1007
  const locators = [];
155
1008
  const events = [];
156
1009
  const domUpdates = [];
157
1010
  const behaviors = [];
158
1011
  const elementHandles = [];
1012
+ const keyedRepeats = [];
1013
+ const branches = [];
1014
+ const asyncBoundaries = [];
1015
+ const asyncRunners = {};
159
1016
  let elementOffset = initialElementOffset;
1017
+ let commentOffset = initialCommentOffset;
160
1018
  for (const part of parts) {
161
1019
  if (part.kind === "html") {
162
1020
  elementOffset += part.elementCount;
1021
+ commentOffset += part.commentCount ?? 0;
163
1022
  continue;
164
1023
  }
165
1024
  const child = childByIndex.get(part.componentIndex);
166
1025
  if (!child) continue;
167
- appendMdxChildView({
1026
+ if (child.view) appendMdxChildView({
168
1027
  child,
169
1028
  elementOffset,
1029
+ commentOffset,
170
1030
  locators,
171
1031
  events,
172
1032
  domUpdates,
173
1033
  behaviors,
174
- elementHandles
1034
+ elementHandles,
1035
+ keyedRepeats,
1036
+ branches,
1037
+ asyncBoundaries,
1038
+ asyncRunners
175
1039
  });
176
- elementOffset += child.hostCount;
1040
+ elementOffset += child.census.elements;
1041
+ commentOffset += child.census.comments;
177
1042
  }
178
1043
  locators.sort((a, b) => a.index - b.index);
179
1044
  return {
180
- version: childViews[0].view.version,
1045
+ version: firstView.version,
181
1046
  locators,
182
1047
  events,
183
1048
  domUpdates,
184
1049
  behaviors,
185
1050
  elementHandles,
186
- asyncBoundaries: []
1051
+ ...keyedRepeats.length > 0 ? { keyedRepeats } : {},
1052
+ ...branches.length > 0 ? { branches } : {},
1053
+ asyncBoundaries,
1054
+ ...Object.keys(asyncRunners).length > 0 ? { asyncRunners } : {}
1055
+ };
1056
+ }
1057
+ /**
1058
+ * The tally the client keeps of one island's markup.
1059
+ *
1060
+ * Resume pins a depth-first census of every element under the container and
1061
+ * resolves each locator by its index into it, and it walks comments the same
1062
+ * way for anchors, so both offsets have to be counted the way the DOM counts
1063
+ * them or a later island's records land on the wrong nodes.
1064
+ */
1065
+ function mdxRenderedCensus(output) {
1066
+ if (!output) return {
1067
+ elements: 0,
1068
+ comments: 0
1069
+ };
1070
+ if (output.root) return mdxDomCensus(output.root);
1071
+ const scanned = typeof output.html === "string" ? mdxHtmlCensus(output.html) : void 0;
1072
+ return {
1073
+ elements: typeof output.elementCount === "number" ? output.elementCount : scanned?.elements ?? 0,
1074
+ comments: scanned?.comments ?? 0
1075
+ };
1076
+ }
1077
+ function mdxDomCensus(root) {
1078
+ let elements = 0;
1079
+ let comments = 0;
1080
+ (function visit(node) {
1081
+ if (node.nodeType === 1) elements++;
1082
+ else if (node.nodeType === 8 && !isArmBranchAnchorComment({
1083
+ nodeType: 8,
1084
+ data: node.data
1085
+ })) comments++;
1086
+ for (const child of Array.from(node.childNodes ?? [])) visit(child);
1087
+ })(root);
1088
+ return {
1089
+ elements,
1090
+ comments
1091
+ };
1092
+ }
1093
+ const MDX_RAW_TEXT_TAGS = /* @__PURE__ */ new Set([
1094
+ "script",
1095
+ "style",
1096
+ "textarea",
1097
+ "title"
1098
+ ]);
1099
+ function mdxHtmlCensus(html) {
1100
+ let elements = 0;
1101
+ let comments = 0;
1102
+ let templateDepth = 0;
1103
+ let at = 0;
1104
+ while (at < html.length) {
1105
+ const open = html.indexOf("<", at);
1106
+ if (open < 0) break;
1107
+ if (html.startsWith("<!--", open)) {
1108
+ const close = html.indexOf("-->", open + 4);
1109
+ const text = html.slice(open + 4, close < 0 ? html.length : close);
1110
+ if (templateDepth === 0 && !isArmBranchAnchorComment({
1111
+ nodeType: 8,
1112
+ data: text
1113
+ })) comments++;
1114
+ at = close < 0 ? html.length : close + 3;
1115
+ continue;
1116
+ }
1117
+ if (html[open + 1] === "!" || html[open + 1] === "?") {
1118
+ at = mdxTagEnd(html, open);
1119
+ continue;
1120
+ }
1121
+ const closing = html[open + 1] === "/";
1122
+ const name = mdxTagName(html, open + (closing ? 2 : 1));
1123
+ if (!name) {
1124
+ at = open + 1;
1125
+ continue;
1126
+ }
1127
+ const tagEnd = mdxTagEnd(html, open);
1128
+ at = tagEnd;
1129
+ if (closing) {
1130
+ if (name === "template" && templateDepth > 0) templateDepth--;
1131
+ continue;
1132
+ }
1133
+ if (templateDepth === 0) elements++;
1134
+ if (html.slice(open, tagEnd).trimEnd().endsWith("/>")) continue;
1135
+ if (name === "template") templateDepth++;
1136
+ else if (MDX_RAW_TEXT_TAGS.has(name)) {
1137
+ const close = html.toLowerCase().indexOf(`</${name}`, tagEnd);
1138
+ at = close < 0 ? html.length : close;
1139
+ }
1140
+ }
1141
+ return {
1142
+ elements,
1143
+ comments
187
1144
  };
188
1145
  }
1146
+ function mdxTagName(html, at) {
1147
+ let end = at;
1148
+ while (end < html.length && !/[\s/>]/.test(html[end])) end++;
1149
+ const name = html.slice(at, end).toLowerCase();
1150
+ return /^[a-z][a-z0-9:-]*$/.test(name) ? name : "";
1151
+ }
1152
+ function mdxTagEnd(html, at) {
1153
+ let quote = "";
1154
+ for (let index = at + 1; index < html.length; index++) {
1155
+ const character = html[index];
1156
+ if (quote) {
1157
+ if (character === quote) quote = "";
1158
+ continue;
1159
+ }
1160
+ if (character === "\"" || character === "'") quote = character;
1161
+ else if (character === ">") return index + 1;
1162
+ }
1163
+ return html.length;
1164
+ }
189
1165
  function loadMdxSymbol(symbolId, children, loaders) {
190
- for (const child of children) if (symbolId.startsWith(child.symbolPrefix) && child.output?.loadSymbol) return child.output.loadSymbol(symbolId.slice(child.symbolPrefix.length));
191
- for (const loader of loaders) if (symbolId.startsWith(loader.prefix)) return loader.loadSymbol(symbolId);
1166
+ for (const child of children) {
1167
+ if (!symbolId.startsWith(child.symbolPrefix)) continue;
1168
+ const load = child.output?.loadSymbol?.bind(child.output);
1169
+ if (load) return loadScopedMdxSymbol(symbolId, child.symbolPrefix, load);
1170
+ }
1171
+ for (const loader of loaders) if (symbolId.startsWith(loader.prefix)) return loadScopedMdxSymbol(symbolId, loader.prefix, (id) => loader.loadSymbol(loader.prefix + id));
192
1172
  return Promise.reject(/* @__PURE__ */ new Error(`Unknown Markless MDX symbol ${symbolId}`));
193
1173
  }
1174
+ function loadScopedMdxSymbol(symbolId, prefix, load) {
1175
+ const loadChildLocal = ((childSymbolId) => marklessThen(load(childSymbolId.slice(prefix.length)), (symbol) => marklessIslandSpacedSymbol(symbol, prefix)));
1176
+ return marklessInstanceScopedLoadSymbol(loadChildLocal)(symbolId);
1177
+ }
194
1178
  function appendMdxChildView(context) {
195
1179
  const childView = context.child.view;
1180
+ const island = islandScope(context.child, context.child.output?.state ?? { version: 1 });
1181
+ const familyContext = {
1182
+ child: context.child,
1183
+ island,
1184
+ commentOffset: context.commentOffset
1185
+ };
1186
+ for (const repeat of childView.keyedRepeats ?? []) context.keyedRepeats.push(islandScopedFamilyRecord(repeat, familyContext));
1187
+ for (const branch of childView.branches ?? []) context.branches.push(islandScopedFamilyRecord(branch, familyContext));
1188
+ for (const boundary of childView.asyncBoundaries ?? []) context.asyncBoundaries.push(islandScopedFamilyRecord(boundary, familyContext));
1189
+ for (const [graphNodeId, symbolId] of Object.entries(childView.asyncRunners ?? {})) context.asyncRunners[islandScopedGraphNodeId(graphNodeId, island)] = context.child.symbolPrefix + symbolId;
196
1190
  for (const locator of childView.locators ?? []) context.locators.push({
197
1191
  ...locator,
198
1192
  hostNodeId: context.child.hostPrefix + locator.hostNodeId,
@@ -203,23 +1197,92 @@ function appendMdxChildView(context) {
203
1197
  hostNodeId: context.child.hostPrefix + event.hostNodeId,
204
1198
  symbolIds: event.symbolIds.map((symbolId) => context.child.symbolPrefix + symbolId)
205
1199
  });
206
- for (const update of childView.domUpdates ?? []) context.domUpdates.push(prefixMdxSymbolRecord(update, context.child));
207
- for (const behavior of childView.behaviors ?? []) context.behaviors.push(prefixMdxSymbolRecord(behavior, context.child));
1200
+ for (const update of childView.domUpdates ?? []) context.domUpdates.push(prefixMdxSymbolRecord(update, context.child, island));
1201
+ for (const behavior of childView.behaviors ?? []) context.behaviors.push(prefixMdxSymbolRecord(behavior, context.child, island));
208
1202
  for (const handle of childView.elementHandles ?? []) context.elementHandles.push({
209
1203
  ...handle,
210
- hostNodeId: context.child.hostPrefix + handle.hostNodeId
1204
+ hostNodeId: context.child.hostPrefix + handle.hostNodeId,
1205
+ handleId: islandScopedHandleId(handle.handleId, island)
211
1206
  });
212
1207
  }
213
- function prefixMdxComputedRecord(record, child) {
214
- return {
215
- ...record,
216
- ...record.deriveSymbolId ? { deriveSymbolId: child.symbolPrefix + record.deriveSymbolId } : {}
217
- };
1208
+ const MDX_PAGE_ANCHOR_STRATEGY = "dom-order-comment";
1209
+ const MDX_HOST_ID_KEYS = [
1210
+ "hostNodeId",
1211
+ "parentHostNodeId",
1212
+ "ownerHostNodeId"
1213
+ ];
1214
+ const MDX_SYMBOL_ID_KEYS = [
1215
+ "symbolId",
1216
+ "updateSymbolId",
1217
+ "runnerSymbolId"
1218
+ ];
1219
+ const MDX_GRAPH_ID_KEYS = [
1220
+ "graphNodeId",
1221
+ "runnerGraphNodeId",
1222
+ "collectionGraphNodeId"
1223
+ ];
1224
+ const MDX_INSTANCE_PATH_KEYS = ["instancePath", "composedInstancePath"];
1225
+ const MDX_NESTED_RECORD_KEYS = [
1226
+ "testReads",
1227
+ "contentReads",
1228
+ "asyncReads",
1229
+ "inputGraphReads",
1230
+ "composedGraphProps",
1231
+ "idrefSites",
1232
+ "locators",
1233
+ "events",
1234
+ "domUpdates",
1235
+ "behaviors",
1236
+ "elementHandles",
1237
+ "rowEvents",
1238
+ "rowElementHandles",
1239
+ "keyedRepeats",
1240
+ "branches",
1241
+ "armRecords",
1242
+ "servedArmRecords"
1243
+ ];
1244
+ /**
1245
+ * One island's repeat / branch / boundary record in the composed page's spaces.
1246
+ *
1247
+ * Every id the record carries takes the island's own prefix for the same reason
1248
+ * the flat records do — two embeds of one component spell identical ids — and
1249
+ * the two comment anchors take the page's comment offset. Arm-relative
1250
+ * coordinates inside an arm record set are island-local by construction and are
1251
+ * deliberately left alone.
1252
+ */
1253
+ function islandScopedFamilyRecord(record, context) {
1254
+ const { child, island } = context;
1255
+ const mapped = { ...record };
1256
+ if (typeof record.id === "string") mapped.id = child.hostPrefix + record.id;
1257
+ for (const key of MDX_HOST_ID_KEYS) if (typeof record[key] === "string") mapped[key] = child.hostPrefix + record[key];
1258
+ for (const key of MDX_SYMBOL_ID_KEYS) if (typeof record[key] === "string") mapped[key] = child.symbolPrefix + record[key];
1259
+ if (Array.isArray(record.symbolIds)) mapped.symbolIds = record.symbolIds.map((symbolId) => child.symbolPrefix + String(symbolId));
1260
+ for (const key of MDX_GRAPH_ID_KEYS) if (typeof record[key] === "string") mapped[key] = islandScopedGraphNodeId(record[key], island);
1261
+ if (typeof record.handleId === "string") mapped.handleId = islandScopedHandleId(record.handleId, island);
1262
+ for (const key of MDX_INSTANCE_PATH_KEYS) if (typeof record[key] === "string") mapped[key] = island.segment + record[key];
1263
+ if (isFamilyRecord(record.elementHandleIds)) mapped.elementHandleIds = Object.fromEntries(Object.entries(record.elementHandleIds).map(([readId, handleId]) => [readId, islandScopedHandleId(String(handleId), island)]));
1264
+ for (const key of ["startAnchor", "endAnchor"]) {
1265
+ const anchor = record[key];
1266
+ if (isFamilyRecord(anchor) && anchor.strategy === MDX_PAGE_ANCHOR_STRATEGY) mapped[key] = {
1267
+ ...anchor,
1268
+ index: context.commentOffset + Number(anchor.index)
1269
+ };
1270
+ }
1271
+ for (const key of MDX_NESTED_RECORD_KEYS) {
1272
+ const nested = record[key];
1273
+ if (Array.isArray(nested)) mapped[key] = nested.map((entry) => isFamilyRecord(entry) ? islandScopedFamilyRecord(entry, context) : entry);
1274
+ else if (isFamilyRecord(nested)) mapped[key] = islandScopedFamilyRecord(nested, context);
1275
+ }
1276
+ return mapped;
218
1277
  }
219
- function prefixMdxSymbolRecord(record, child) {
1278
+ function isFamilyRecord(value) {
1279
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1280
+ }
1281
+ function prefixMdxSymbolRecord(record, child, island) {
220
1282
  return {
221
- ...record,
1283
+ ...islandScopedGraphRecord(record, island),
222
1284
  hostNodeId: child.hostPrefix + record.hostNodeId,
1285
+ ...record.inputGraphReads ? { inputGraphReads: record.inputGraphReads.map((read) => islandScopedGraphRecord(read, island)) } : {},
223
1286
  ...record.symbolId ? { symbolId: child.symbolPrefix + record.symbolId } : {}
224
1287
  };
225
1288
  }
@@ -227,4 +1290,4 @@ function isDefined(value) {
227
1290
  return value !== void 0;
228
1291
  }
229
1292
  //#endregion
230
- export { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild };
1293
+ export { composeMdxState, composeMdxView, createMdxRenderDataSurface, loadMdxSymbol, renderMdxChild, tryResumeMdxScalar };