@vmz/core 0.1.32 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{client-nav.d.ts → browser/client-nav.d.ts} +4 -13
  2. package/dist/{client-nav.js → browser/client-nav.js} +16 -74
  3. package/dist/browser/direct-api.types.d.ts +58 -0
  4. package/dist/browser/direct-api.types.js +4 -0
  5. package/dist/browser/direct-host-box.d.ts +11 -0
  6. package/dist/{direct-host-box.js → browser/direct-host-box.js} +2 -18
  7. package/dist/{dom-core.d.ts → browser/dom-core.d.ts} +64 -220
  8. package/dist/browser/dom-core.js +2453 -0
  9. package/dist/browser/dom-core.types.d.ts +138 -0
  10. package/dist/browser/dom-core.types.js +4 -0
  11. package/dist/{position-context.js → browser/position-context.js} +1 -1
  12. package/dist/browser/unknown-component.d.ts +14 -0
  13. package/dist/{unknown-component.js → browser/unknown-component.js} +4 -28
  14. package/dist/{dom.browser.d.ts → faces/dom.browser.d.ts} +2 -2
  15. package/dist/{dom.browser.js → faces/dom.browser.js} +2 -3
  16. package/dist/faces/dom.client.d.ts +4 -0
  17. package/dist/faces/dom.client.js +4 -0
  18. package/dist/faces/dom.d.ts +6 -0
  19. package/dist/faces/dom.js +6 -0
  20. package/dist/{http.d.ts → faces/http.d.ts} +1 -1
  21. package/dist/{http.js → faces/http.js} +1 -2
  22. package/dist/{server.d.ts → faces/server.d.ts} +8 -25
  23. package/dist/{server.js → faces/server.js} +25 -155
  24. package/dist/{vmz-dom.d.ts → faces/vmz-dom.d.ts} +1 -1
  25. package/dist/{vmz-dom.js → faces/vmz-dom.js} +1 -2
  26. package/dist/{vmz-runtime.d.ts → faces/vmz-runtime.d.ts} +1 -1
  27. package/dist/{vmz-runtime.js → faces/vmz-runtime.js} +1 -2
  28. package/dist/host/deployment-registry.d.ts +16 -0
  29. package/dist/{deployment-registry.js → host/deployment-registry.js} +1 -68
  30. package/dist/host/list-client-components.d.ts +14 -0
  31. package/dist/{list-client-components.js → host/list-client-components.js} +0 -37
  32. package/dist/{localize-body-links.js → host/localize-body-links.js} +0 -1
  33. package/dist/host/native-addon.d.ts +6 -0
  34. package/dist/{native-addon.js → host/native-addon.js} +0 -20
  35. package/dist/host/render-host.d.ts +20 -0
  36. package/dist/{render-host.js → host/render-host.js} +0 -16
  37. package/dist/{serve-host.d.ts → host/serve-host.d.ts} +3 -3
  38. package/dist/{serve-host.mjs → host/serve-host.mjs} +45 -168
  39. package/dist/shared/host.types.d.ts +67 -0
  40. package/dist/shared/host.types.js +4 -0
  41. package/dist/shared/server.types.d.ts +39 -0
  42. package/dist/shared/server.types.js +4 -0
  43. package/dist/{dom-ssr.d.ts → ssr/dom-ssr.d.ts} +8 -37
  44. package/dist/{dom-ssr.js → ssr/dom-ssr.js} +159 -217
  45. package/package.json +34 -34
  46. package/dist/deployment-registry.d.ts +0 -70
  47. package/dist/direct-host-box.d.ts +0 -26
  48. package/dist/dom-core.js +0 -4815
  49. package/dist/dom.client.d.ts +0 -4
  50. package/dist/dom.client.js +0 -5
  51. package/dist/dom.d.ts +0 -6
  52. package/dist/dom.js +0 -7
  53. package/dist/list-client-components.d.ts +0 -62
  54. package/dist/native-addon.d.ts +0 -12
  55. package/dist/render-host.d.ts +0 -32
  56. package/dist/unknown-component.d.ts +0 -40
  57. /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
  58. /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
  59. /package/dist/{index.js → faces/index.js} +0 -0
  60. /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
  61. /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
  62. /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
@@ -1,12 +1,10 @@
1
- // @ts-nocheck
2
1
  /**
3
- * VMZ DOM SSR / hydrate / resume — precise patches, no VDOM diff.
2
+ * VMZ DOM SSR / hydrate / resume ??precise patches, no VDOM diff.
4
3
  * Imports client DOM primitives from ./dom-core.js for tree-shakeable browser entry.
5
4
  */
6
- import { applyDirectHostBox, directHostBoxStyleAttr } from './direct-host-box.js';
7
- import { applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, eventPropHandlerName, findOwnedDefaultSlot, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy, mount, noteDomCreate, resolveComponent, runDirectCreate, scheduleClientOn, settlePendingChildMounts, snapshotInstanceState, stripFns, } from './dom-core.js';
8
- import { markUnknownComponentHost, serializeUnknownComponentNode } from './unknown-component.js';
9
- /** @type {Error | null} last linkedom resolve failure (for clear SSR errors) */
5
+ import { applyDirectHostBox, directHostBoxStyleAttr } from '../browser/direct-host-box.js';
6
+ import { applyPreservedState, BOOLEAN_HTML_ATTRS, createInstance, destroy, directApi, eventPropHandlerName, findOwnedDefaultSlot, getRegisteredComponent, hasMeaningfulChild, isEventEntryStrategy, mount, noteDomCreate, resolveComponent, runDirectCreate, scheduleClientOn, settlePendingChildMounts, snapshotInstanceState, stripFns, } from '../browser/dom-core.js';
7
+ import { markUnknownComponentHost, serializeUnknownComponentNode } from '../browser/unknown-component.js';
10
8
  let _ssrDocumentLastError = null;
11
9
  /**
12
10
  * Node SSR has no browser `document`. rowKernel prefers document-free HTML fill;
@@ -16,7 +14,7 @@ let _ssrDocumentLastError = null;
16
14
  * and browser hosts load that barrel.
17
15
  *
18
16
  * When this file is copied into an app `dist/`, `createRequire(import.meta.url)` cannot
19
- * see `@vmz/core`'s dependency tree — resolve linkedom from cwd / `@vmz/core` as well.
17
+ * see `@vmz/core`'s dependency tree ??resolve linkedom from cwd / `@vmz/core` as well.
20
18
  */
21
19
  function ensureSsrDocument() {
22
20
  if (typeof globalThis.document !== 'undefined' && typeof globalThis.document.createElement === 'function') {
@@ -36,13 +34,11 @@ function ensureSsrDocument() {
36
34
  }
37
35
  const pathMod = typeof proc.getBuiltinModule === 'function' ? proc.getBuiltinModule('path') : null;
38
36
  const createRequire = mod.createRequire;
39
- /** @type {string[]} */
40
37
  const bases = [];
41
38
  bases.push(import.meta.url);
42
39
  if (pathMod && typeof proc.cwd === 'function') {
43
40
  bases.push(pathMod.join(proc.cwd(), 'package.json'));
44
41
  }
45
- /** @type {string[]} */
46
42
  const errors = [];
47
43
  let parseHTML = null;
48
44
  for (const base of bases) {
@@ -59,7 +55,7 @@ function ensureSsrDocument() {
59
55
  break;
60
56
  }
61
57
  catch (e) {
62
- errors.push(`${base} → linkedom: ${e && e.message ? e.message : e}`);
58
+ errors.push(`${base} ??linkedom: ${e && e.message ? e.message : e}`);
63
59
  }
64
60
  // Walk into @vmz/core's dependency tree (linkedom is declared there).
65
61
  for (const coreId of ['@vmz/core', '@vmz/core/dom', '@vmz/core/server']) {
@@ -69,7 +65,7 @@ function ensureSsrDocument() {
69
65
  break;
70
66
  }
71
67
  catch (e) {
72
- errors.push(`${base} → ${coreId}/linkedom: ${e && e.message ? e.message : e}`);
68
+ errors.push(`${base} ??${coreId}/linkedom: ${e && e.message ? e.message : e}`);
73
69
  }
74
70
  }
75
71
  if (parseHTML)
@@ -101,7 +97,7 @@ export async function renderToString(Component, props = {}, opts = {}) {
101
97
  }
102
98
  if (signal && signal.aborted)
103
99
  return '';
104
- // production Direct emit: SSR only via Direct serialize schedule — never `render`.
100
+ // production Direct emit: SSR only via Direct serialize schedule ??never `render`.
105
101
  if (!(Component && Component.__vmzDirect && typeof Component.__vmzCreate === 'function')) {
106
102
  throw new Error(`vmz:dom renderToString() requires __vmzCreate (Direct); blueprint render() removed (production Direct emit)`);
107
103
  }
@@ -112,12 +108,8 @@ export async function renderToString(Component, props = {}, opts = {}) {
112
108
  }
113
109
  /**
114
110
  * Stream SSR via the same Direct serialize schedule as `renderToString`.
115
- * Yields HTML chunks (open tag → children → close). Joining chunks equals `renderToString`.
111
+ * Yields HTML chunks (open tag ??children ??close). Joining chunks equals `renderToString`.
116
112
  * Supports AbortSignal for cancel; consumers should respect backpressure (await between chunks).
117
- * @param {new (props?: object) => any} Component
118
- * @param {object} [props]
119
- * @param {{ signal?: AbortSignal, slotHtml?: string }} [opts]
120
- * @returns {AsyncGenerator<string, void, void>}
121
113
  */
122
114
  export async function* renderToStream(Component, props = {}, opts = {}) {
123
115
  ensureSsrDocument();
@@ -155,11 +147,9 @@ export async function* renderToStream(Component, props = {}, opts = {}) {
155
147
  }
156
148
  /**
157
149
  * Fill the layout-owned default `<slot>` with pre-rendered HTML (layout SSR wrap).
158
- * Skips nested component hosts (`data-vmz`) — their slots are for child projection
159
- * (e.g. Button label), not the page outlet. Without this, DFS hits LocaleToggle→Button
150
+ * Skips nested component hosts (`data-vmz`) ??their slots are for child projection
151
+ * (e.g. Button label), not the page outlet. Without this, DFS hits LocaleToggle?Button
160
152
  * before Layout's `<main><slot>`, and the entire page HTML lands inside a button.
161
- * @param {any} node
162
- * @param {string} html
163
153
  */
164
154
  function injectDefaultSlotHtml(node, html) {
165
155
  if (!node || typeof node !== 'object')
@@ -169,7 +159,7 @@ function injectDefaultSlotHtml(node, html) {
169
159
  node.children = [];
170
160
  return true;
171
161
  }
172
- // Nested Direct component wrapper from serializeApi.component — do not search inside.
162
+ // Nested Direct component wrapper from serializeApi.component ??do not search inside.
173
163
  if (node.__kind === 'el' && node.attrs && node.attrs['data-vmz'] != null) {
174
164
  return false;
175
165
  }
@@ -186,20 +176,13 @@ function injectDefaultSlotHtml(node, html) {
186
176
  * Live-DOM counterpart: first default `<slot>` owned by this tree, not by a nested
187
177
  * `[data-vmz]` component (Button/Link labels, etc.).
188
178
  * Re-exported from dom-core (single implementation).
189
- * @param {Element | null | undefined} root
190
- * @returns {Element | null}
191
179
  */
192
180
  export { findOwnedDefaultSlot };
193
181
  /**
194
- * Hydrate/mount a file-route page inside an optional layout chain (outer → inner).
182
+ * Hydrate/mount a file-route page inside an optional layout chain (outer ??inner).
195
183
  * Mirrors SSR `slotHtml` wrapping: each layout's owned default slot becomes the
196
184
  * outlet for the next layout or the page. Retains layout instances on `container`
197
185
  * so SPA transitions can dispose only the page host.
198
- * @param {new (props?: object) => any} Page
199
- * @param {Element} container
200
- * @param {object} [props]
201
- * @param {Array<new (props?: object) => any>} [layoutCtors] outer → inner
202
- * @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
203
186
  */
204
187
  export async function hydrateRoute(Page, container, props = {}, layoutCtors = [], opts = {}) {
205
188
  if (typeof document === 'undefined') {
@@ -211,7 +194,6 @@ export async function hydrateRoute(Page, container, props = {}, layoutCtors = []
211
194
  }
212
195
  container.__vmzPageHost = null;
213
196
  container.__vmzLayoutInsts = null;
214
- /** @type {object[]} */
215
197
  const layoutInsts = [];
216
198
  let host = container;
217
199
  const ctors = Array.isArray(layoutCtors) ? layoutCtors.filter(Boolean) : [];
@@ -241,10 +223,6 @@ export async function hydrateRoute(Page, container, props = {}, layoutCtors = []
241
223
  /**
242
224
  * SPA same-layout transition: dispose only the page host, keep layout instances.
243
225
  * Callers must verify `data-vmz-layout` is unchanged before using this.
244
- * @param {new (props?: object) => any} Page
245
- * @param {Element} container `#app` that already has `__vmzPageHost` / `__vmzLayoutInsts`
246
- * @param {object} [props]
247
- * @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
248
226
  */
249
227
  export async function hydrateRoutePage(Page, container, props = {}, opts = {}) {
250
228
  if (typeof document === 'undefined') {
@@ -265,8 +243,6 @@ export async function hydrateRoutePage(Page, container, props = {}, opts = {}) {
265
243
  }
266
244
  /**
267
245
  * SSR: run the same __vmzCreate schedule against a serialize host (no render).
268
- * @param {new (props?: object) => any} Component
269
- * @param {object} inst
270
246
  */
271
247
  function runDirectSerializeTree(Component, inst) {
272
248
  serializeApi._inst = inst;
@@ -279,14 +255,10 @@ function runDirectSerializeTree(Component, inst) {
279
255
  }
280
256
  /**
281
257
  * SSR child onMount: sync `__vmzCreate` cannot await nested mounts.
282
- * Expand rounds — reuse prior child instances, await newly discovered onMounts, re-emit.
283
- * @param {new (props?: object) => any} Component
284
- * @param {object} inst
258
+ * Expand rounds ??reuse prior child instances, await newly discovered onMounts, re-emit.
285
259
  */
286
260
  async function runDirectSerializeTreeWithMounts(Component, inst) {
287
- /** @type {object[]} */
288
261
  let preMounted = [];
289
- /** @type {any} */
290
262
  let tree = null;
291
263
  for (let round = 0; round < 32; round++) {
292
264
  serializeApi._ssrPreMounted = preMounted;
@@ -330,6 +302,8 @@ function flattenSerializeNode(node) {
330
302
  return escapeHtml(node);
331
303
  if (node.__kind === 'text')
332
304
  return escapeHtml(node.value);
305
+ if (node.__kind === 'comment')
306
+ return '';
333
307
  if (node.__kind === 'frag') {
334
308
  if (node.__rawHtml != null)
335
309
  return String(node.__rawHtml);
@@ -356,8 +330,6 @@ function flattenSerializeNode(node) {
356
330
  }
357
331
  /**
358
332
  * Progressive HTML chunks from a serialize tree (same nodes as flattenSerializeNode).
359
- * @param {any} node
360
- * @returns {Generator<string, void, void>}
361
333
  */
362
334
  function* streamSerializeChunks(node) {
363
335
  if (node == null || node === false)
@@ -370,6 +342,8 @@ function* streamSerializeChunks(node) {
370
342
  yield escapeHtml(node.value);
371
343
  return;
372
344
  }
345
+ if (node.__kind === 'comment')
346
+ return;
373
347
  if (node.__kind === 'frag') {
374
348
  if (node.__rawHtml != null) {
375
349
  yield String(node.__rawHtml);
@@ -409,19 +383,14 @@ function* streamSerializeChunks(node) {
409
383
  /**
410
384
  * Document-free rowKernel SSR fill (transitional / pre-0.1.7 emit only).
411
385
  * Prefer `serializeItem` (IR schedule). Do not treat this as the long-term contract.
412
- * @param {{ html: string, textSlots?: Record<string, number>, hostFields?: string[] }} rk
413
- * @param {any} item
414
- * @param {any} key
415
- * @returns {string | null} filled outer HTML, or null if shape is not fillable
416
386
  */
417
387
  function fillRowKernelHtml(rk, item, key) {
418
388
  const slots = rk.textSlots;
419
389
  if (!slots || typeof slots !== 'object')
420
390
  return null;
421
- /** @type {string[]} */
422
391
  const fields = Object.entries(slots)
423
392
  .filter(([, i]) => typeof i === 'number' && Number.isFinite(i))
424
- .sort((a, b) => /** @type {number} */ (a[1]) - /** @type {number} */ (b[1]))
393
+ .sort((a, b) => Number(a[1]) - Number(b[1]))
425
394
  .map(([f]) => f);
426
395
  if (!fields.length)
427
396
  return null;
@@ -446,10 +415,6 @@ function fillRowKernelHtml(rk, item, key) {
446
415
  * SSR row when `createItem` was omitted (rowKernel client emit).
447
416
  * Prefer document-free fill from `html` + `textSlots`; fall back to linkedom + hydrate
448
417
  * when host class ternaries need live DOM, or when placeholders cannot be string-filled.
449
- * @param {object} inst
450
- * @param {{ html: string, hydrate?: Function, textSlots?: Record<string, number>, hostFields?: string[] }} rk
451
- * @param {{ item: any, index: number }} box
452
- * @param {any} key
453
418
  */
454
419
  function serializeRowFromKernel(inst, rk, box, key) {
455
420
  const item = box.item;
@@ -508,24 +473,71 @@ function serializeRowFromKernel(inst, rk, box, key) {
508
473
  appendChild() { },
509
474
  };
510
475
  }
511
- /** Serialize host mirroring directApi — returns virtual nodes, not DOM. */
476
+ function virtualLink(parent, child) {
477
+ if (child != null && typeof child === 'object')
478
+ child.parentNode = parent;
479
+ }
480
+ function virtualAppend(parent, child) {
481
+ if (parent == null || child == null)
482
+ return;
483
+ if (!Array.isArray(parent.children))
484
+ parent.children = [];
485
+ parent.children.push(child);
486
+ virtualLink(parent, child);
487
+ }
488
+ function virtualInsertBefore(parent, node, ref) {
489
+ if (parent == null || node == null)
490
+ return;
491
+ if (!Array.isArray(parent.children))
492
+ parent.children = [];
493
+ if (ref == null) {
494
+ virtualAppend(parent, node);
495
+ return;
496
+ }
497
+ const idx = parent.children.indexOf(ref);
498
+ if (idx < 0)
499
+ virtualAppend(parent, node);
500
+ else {
501
+ parent.children.splice(idx, 0, node);
502
+ virtualLink(parent, node);
503
+ }
504
+ }
505
+ function virtualRemove(node) {
506
+ const p = node && node.parentNode;
507
+ if (!p || !Array.isArray(p.children))
508
+ return;
509
+ const idx = p.children.indexOf(node);
510
+ if (idx >= 0)
511
+ p.children.splice(idx, 1);
512
+ node.parentNode = null;
513
+ }
514
+ function makeVirtualEl(tag) {
515
+ return {
516
+ __kind: 'el',
517
+ tag: tag || 'div',
518
+ attrs: {},
519
+ children: [],
520
+ style: {},
521
+ parentNode: null,
522
+ setAttribute(name, value) {
523
+ applySerializeAttr(this, name, value);
524
+ },
525
+ appendChild(c) {
526
+ virtualAppend(this, c);
527
+ },
528
+ insertBefore(node, ref) {
529
+ virtualInsertBefore(this, node, ref);
530
+ },
531
+ };
532
+ }
512
533
  const serializeApi = {
513
- /** @type {object | null} */
514
534
  _inst: null,
515
- /** @type {null} */
516
535
  _branchBinds: null,
517
- /** @type {null} */
518
536
  _itemPatches: null,
519
- /** @type {object[] | null} reused child instances from prior SSR mount rounds */
537
+ _eachCtx: null,
520
538
  _ssrPreMounted: null,
521
- /** @type {number} */
522
539
  _ssrPreIdx: 0,
523
- /** @type {object[] | null} newly created child instances this round */
524
540
  _ssrCollected: null,
525
- /**
526
- * @param {new (props?: object) => any} Ctor
527
- * @param {object} resolved
528
- */
529
541
  _ssrChildInstance(Ctor, resolved) {
530
542
  const pre = serializeApi._ssrPreMounted;
531
543
  if (pre && serializeApi._ssrPreIdx < pre.length) {
@@ -537,30 +549,85 @@ const serializeApi = {
537
549
  return child;
538
550
  },
539
551
  el(tag) {
540
- return {
541
- __kind: 'el',
542
- tag: tag || 'div',
543
- attrs: {},
544
- children: [],
545
- appendChild(c) {
546
- if (c != null)
547
- this.children.push(c);
548
- },
549
- };
552
+ return makeVirtualEl(tag);
550
553
  },
551
554
  text(value) {
552
- return { __kind: 'text', value: value == null ? '' : String(value) };
555
+ const node = { __kind: 'text', value: value == null ? '' : String(value), parentNode: null };
556
+ // Generated Direct patches use DOM `textContent`; virtual text uses `value`.
557
+ Object.defineProperty(node, 'textContent', {
558
+ configurable: true,
559
+ enumerable: false,
560
+ get() {
561
+ return node.value;
562
+ },
563
+ set(v) {
564
+ node.value = v == null ? '' : String(v);
565
+ },
566
+ });
567
+ return node;
568
+ },
569
+ comment(value) {
570
+ return { __kind: 'comment', value: value == null ? '' : String(value), parentNode: null };
553
571
  },
554
572
  frag() {
555
573
  return {
556
574
  __kind: 'frag',
557
575
  children: [],
576
+ parentNode: null,
558
577
  appendChild(c) {
559
- if (c != null)
560
- this.children.push(c);
578
+ virtualAppend(this, c);
579
+ },
580
+ insertBefore(node, ref) {
581
+ virtualInsertBefore(this, node, ref);
561
582
  },
562
583
  };
563
584
  },
585
+ insertBefore(parent, node, ref) {
586
+ virtualInsertBefore(parent, node, ref);
587
+ },
588
+ removeNode(node) {
589
+ virtualRemove(node);
590
+ },
591
+ trackPatch(inst, deps, patch, bindingId = null) {
592
+ if (typeof patch !== 'function')
593
+ return;
594
+ if (serializeApi._branchBinds) {
595
+ serializeApi._branchBinds.push({ deps: deps || [], fn: patch, bindingId });
596
+ try {
597
+ patch.call(inst);
598
+ }
599
+ catch (err) {
600
+ console.error('vmz:ssr trackPatch branch', err);
601
+ }
602
+ return;
603
+ }
604
+ if (serializeApi._itemPatches) {
605
+ patch.__vmzItemLocal = true;
606
+ serializeApi._itemPatches.push(patch);
607
+ if (serializeApi._eachCtx) {
608
+ serializeApi._eachCtx.noteItemBind(bindingId, deps || [], patch);
609
+ }
610
+ try {
611
+ patch.call(inst);
612
+ }
613
+ catch (err) {
614
+ console.error('vmz:ssr trackPatch item', err);
615
+ }
616
+ return;
617
+ }
618
+ try {
619
+ patch.call(inst);
620
+ }
621
+ catch (err) {
622
+ console.error('vmz:ssr trackPatch', err);
623
+ }
624
+ },
625
+ untrackPatch() {
626
+ /* SSR one-shot schedule ??no reactive unregister */
627
+ },
628
+ disposeTree() {
629
+ /* SSR one-shot schedule */
630
+ },
564
631
  attr(el, name, value) {
565
632
  if (!el || el.__kind !== 'el')
566
633
  return;
@@ -581,16 +648,6 @@ const serializeApi = {
581
648
  onComponentEvent() {
582
649
  /* component events attach only on the client */
583
650
  },
584
- bindText(inst, bindingId, deps, get, textNode) {
585
- let raw = '';
586
- try {
587
- raw = get.call(inst);
588
- }
589
- catch {
590
- raw = '';
591
- }
592
- textNode.value = String(raw ?? '');
593
- },
594
651
  specFieldText(inst, _bindingId, fieldName, textNode) {
595
652
  let raw;
596
653
  try {
@@ -601,16 +658,6 @@ const serializeApi = {
601
658
  }
602
659
  textNode.value = String(raw ?? '');
603
660
  },
604
- bindAttr(inst, bindingId, deps, get, el, name) {
605
- let raw;
606
- try {
607
- raw = get.call(inst);
608
- }
609
- catch {
610
- raw = null;
611
- }
612
- applySerializeAttr(el, name, raw);
613
- },
614
661
  specFieldAttr(inst, _bindingId, fieldName, el, name) {
615
662
  let raw;
616
663
  try {
@@ -660,7 +707,7 @@ const serializeApi = {
660
707
  slot.__rawHtml = null;
661
708
  if (!Array.isArray(slot.children))
662
709
  slot.children = [];
663
- // Append — multiple projectDefaultSlot calls must accumulate (SSR).
710
+ // Append ??multiple projectDefaultSlot calls must accumulate (SSR).
664
711
  // Client path replaces the live <slot> then appends siblings; serialize must push.
665
712
  slot.children.push(node);
666
713
  return;
@@ -675,85 +722,16 @@ const serializeApi = {
675
722
  el.children = [];
676
723
  },
677
724
  bindHtml(inst, bindingId, deps, get, el) {
678
- let raw = '';
679
- try {
680
- raw = get.call(inst);
681
- }
682
- catch {
683
- raw = '';
684
- }
685
- el.__rawHtml = raw == null ? '' : String(raw);
686
- el.children = [];
687
- },
688
- ifBlock(inst, bindingId, deps, branches) {
689
- // No empty `span[data-vmz-if]` box (`ui-vif-dom`): false → empty frag.
690
- const frag = serializeApi.frag();
691
- let idx = -1;
692
- for (let i = 0; i < branches.length; i++) {
693
- const b = branches[i];
694
- if (!b.cond) {
695
- idx = i;
696
- break;
697
- }
725
+ serializeApi.trackPatch(inst, deps || [], function bindHtmlPatch() {
726
+ let raw;
698
727
  try {
699
- if (b.cond.call(inst)) {
700
- idx = i;
701
- break;
702
- }
728
+ raw = get.call(inst);
703
729
  }
704
730
  catch {
705
- /* continue */
706
- }
707
- }
708
- if (idx >= 0 && branches[idx].create) {
709
- const created = branches[idx].create.call(inst, serializeApi);
710
- if (created)
711
- frag.appendChild(created);
712
- }
713
- return frag;
714
- },
715
- eachBlock(inst, bindingId, deps, spec) {
716
- const frag = serializeApi.frag();
717
- let list = [];
718
- try {
719
- list = spec.list.call(inst) || [];
720
- }
721
- catch {
722
- list = [];
723
- }
724
- if (!Array.isArray(list))
725
- list = [...list];
726
- for (let i = 0; i < list.length; i++) {
727
- const box = { item: list[i], index: i };
728
- let k = i;
729
- if (typeof spec.key === 'function') {
730
- try {
731
- k = spec.key.call(inst, box);
732
- }
733
- catch {
734
- k = i;
735
- }
736
- }
737
- let dom = null;
738
- if (typeof spec.createItem === 'function') {
739
- dom = spec.createItem.call(inst, serializeApi, box);
740
- }
741
- else if (typeof spec.serializeItem === 'function') {
742
- // IR-homologous schedule (v0.1.7): same Direct body as fat createItem.
743
- dom = spec.serializeItem.call(inst, serializeApi, box);
731
+ raw = '';
744
732
  }
745
- else if (spec.rowKernel && typeof spec.rowKernel.html === 'string') {
746
- // Transitional only: pre-0.1.7 emit without serializeItem.
747
- dom = serializeRowFromKernel(inst, spec.rowKernel, box, k);
748
- }
749
- if (dom) {
750
- // SSR only: serialize key into HTML for hydrate/debug. Direct client does not write this attr.
751
- if (dom.__kind === 'el' && !dom.__rawOuter)
752
- serializeApi.attr(dom, 'data-vmz-key', String(k));
753
- frag.appendChild(dom);
754
- }
755
- }
756
- return frag;
733
+ serializeApi.setHtml(el, raw);
734
+ }, bindingId);
757
735
  },
758
736
  component(hostInst, nameOrCtor, props, client) {
759
737
  const name = typeof nameOrCtor === 'function' ? nameOrCtor.__vmzTag || nameOrCtor.name || 'Component' : nameOrCtor;
@@ -761,7 +739,6 @@ const serializeApi = {
761
739
  if (!Ctor) {
762
740
  return serializeUnknownComponentNode(name);
763
741
  }
764
- /** @type {Record<string, any>} */
765
742
  const resolved = {};
766
743
  for (const [k, v] of Object.entries(props || {})) {
767
744
  const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
@@ -797,7 +774,6 @@ const serializeApi = {
797
774
  planSchema: plan?.schema || null,
798
775
  planRootIds: plan?.root_ids || [],
799
776
  };
800
- /** @type {Record<string, string>} */
801
777
  const attrs = {
802
778
  'data-vmz': name,
803
779
  'data-vmz-island': name,
@@ -828,7 +804,6 @@ const serializeApi = {
828
804
  serializeApi._inst = child;
829
805
  try {
830
806
  const node = Ctor.__vmzCreate.call(child, serializeApi);
831
- /** @type {Record<string, string>} */
832
807
  const attrs = { 'data-vmz': name };
833
808
  const boxStyle = directHostBoxStyleAttr(name, Ctor);
834
809
  if (boxStyle)
@@ -853,9 +828,6 @@ const serializeApi = {
853
828
  };
854
829
  /**
855
830
  * Serialize-tree attr write (SSR).
856
- * @param {any} el
857
- * @param {string} name
858
- * @param {any} value
859
831
  */
860
832
  function applySerializeAttr(el, name, value) {
861
833
  if (!el || el.__kind !== 'el')
@@ -876,9 +848,6 @@ function applySerializeAttr(el, name, value) {
876
848
  /**
877
849
  * resume: attach to existing Island DOM without re-running construct structure or onMount.
878
850
  * Consumes ResumeEntry product (`data-vmz-resume`) derived from the same Execution Plan.
879
- * @param {new (props?: object) => any} Component
880
- * @param {HTMLElement} container
881
- * @param {{ props?: object, state?: Record<string, unknown>, strategy?: string } | null} [slice]
882
851
  */
883
852
  export async function resume(Component, container, slice = null) {
884
853
  if (typeof document === 'undefined') {
@@ -916,7 +885,7 @@ export async function resume(Component, container, slice = null) {
916
885
  const inst = createInstance(Component, props);
917
886
  if (parsed.state)
918
887
  applyPreservedState(inst, parsed.state);
919
- // Intentionally never call onMount — SSR already completed that work.
888
+ // Intentionally never call onMount ??SSR already completed that work.
920
889
  if (Component.__vmzDirect && typeof Component.__vmzCreate === 'function') {
921
890
  if (!hasMeaningfulChild(container)) {
922
891
  const node = runDirectCreate(Component, inst);
@@ -942,7 +911,6 @@ export async function resume(Component, container, slice = null) {
942
911
  /**
943
912
  * Resume all `[data-vmz-island]` hosts (prefer ResumeEntry / EventEntry over mount).
944
913
  * Event strategy islands wait for the DOM event before attach (lazy EventEntry).
945
- * @param {ParentNode} [root]
946
914
  */
947
915
  export function resumeIslands(root = globalThis.document) {
948
916
  if (!root || typeof root.querySelectorAll !== 'function') {
@@ -965,7 +933,6 @@ export function resumeIslands(root = globalThis.document) {
965
933
  /**
966
934
  * EventEntry attach: only wire `client:event` / `client:event:*` islands.
967
935
  * Idle/load/visible ResumeEntries are left alone (static shell can defer framework work).
968
- * @param {ParentNode} [root]
969
936
  */
970
937
  export function attachEventEntries(root = globalThis.document) {
971
938
  if (!root || typeof root.querySelectorAll !== 'function') {
@@ -1001,22 +968,15 @@ export function attachEventEntries(root = globalThis.document) {
1001
968
  * - `beginBranchScope()` moves one top-level SSR node into an isolated pool so
1002
969
  * ifBlock branch create cannot steal parent siblings (Drawer / Button hosts).
1003
970
  *
1004
- * @param {DocumentFragment} pool
1005
- * @param {Element} rootEl
1006
971
  */
1007
972
  function createResumeAdopt(pool, rootEl) {
1008
973
  const used = new WeakSet();
1009
- /** @type {DocumentFragment[]} */
1010
974
  const scopeStack = [pool];
1011
- /**
1012
- * @param {Node} root
1013
- * @param {(n: Text) => void} visit
1014
- */
1015
975
  const walkTexts = (root, visit) => {
1016
976
  if (!root)
1017
977
  return;
1018
978
  if (root.nodeType === 3) {
1019
- visit(/** @type {Text} */ (root));
979
+ visit(root);
1020
980
  return;
1021
981
  }
1022
982
  if (root.nodeType !== 1)
@@ -1032,7 +992,7 @@ function createResumeAdopt(pool, rootEl) {
1032
992
  if (k != null && n.__vmzKey == null)
1033
993
  n.__vmzKey = k;
1034
994
  }
1035
- // Private child pool — never dump into the parent/global pool (that let
995
+ // Private child pool ??never dump into the parent/global pool (that let
1036
996
  // if branches reclaim Drawer/sibling hosts parked as uncles).
1037
997
  const childPool = document.createDocumentFragment();
1038
998
  while (n.firstChild)
@@ -1040,25 +1000,22 @@ function createResumeAdopt(pool, rootEl) {
1040
1000
  n.__vmzResumePool = childPool;
1041
1001
  return n;
1042
1002
  };
1043
- /** @returns {Generator<Element>} */
1044
1003
  function* elementCandidates() {
1045
1004
  const scope = currentScope();
1046
1005
  if (scope === pool && rootEl && !used.has(rootEl))
1047
1006
  yield rootEl;
1048
- // Snapshot — adopt may move nodes out of scope while iterating.
1007
+ // Snapshot ??adopt may move nodes out of scope while iterating.
1049
1008
  const kids = [...scope.childNodes];
1050
1009
  for (const n of kids) {
1051
- if (n.nodeType === 1 && !used.has(/** @type {Element} */ (n))) {
1052
- yield /** @type {Element} */ (n);
1010
+ if (n.nodeType === 1 && !used.has(n)) {
1011
+ yield n;
1053
1012
  }
1054
1013
  }
1055
1014
  }
1056
- /** @returns {Generator<Text>} */
1057
1015
  function* textCandidates() {
1058
1016
  const scope = currentScope();
1059
1017
  const kids = [...scope.childNodes];
1060
1018
  for (const n of kids) {
1061
- /** @type {Text[]} */
1062
1019
  const found = [];
1063
1020
  walkTexts(n, (t) => {
1064
1021
  if (!used.has(t))
@@ -1087,7 +1044,6 @@ function createResumeAdopt(pool, rootEl) {
1087
1044
  noteDomCreate();
1088
1045
  return document.createTextNode(value == null ? '' : String(value));
1089
1046
  };
1090
- /** Reclaim SSR `<div data-vmz="Name">` hosts for nested Direct components. */
1091
1047
  const componentHost = (name) => {
1092
1048
  const want = String(name || '');
1093
1049
  for (const el of elementCandidates()) {
@@ -1119,13 +1075,12 @@ function createResumeAdopt(pool, rootEl) {
1119
1075
  /**
1120
1076
  * Isolate the next top-level SSR sibling as the only candidates for an
1121
1077
  * if/else branch create (commercial Card: Empty branch must not adopt Drawer).
1122
- * @returns {() => void} end callback
1123
1078
  */
1124
1079
  const beginBranchScope = () => {
1125
1080
  const parent = currentScope();
1126
1081
  const branchPool = document.createDocumentFragment();
1127
1082
  for (const n of [...parent.childNodes]) {
1128
- if (n.nodeType === 1 && !used.has(/** @type {Element} */ (n))) {
1083
+ if (n.nodeType === 1 && !used.has(n)) {
1129
1084
  branchPool.appendChild(n);
1130
1085
  break;
1131
1086
  }
@@ -1151,9 +1106,6 @@ function createResumeAdopt(pool, rootEl) {
1151
1106
  }
1152
1107
  /**
1153
1108
  * Adopt existing Island DOM while running the same `__vmzCreate` schedule (resume).
1154
- * @param {new (props?: object) => any} Component
1155
- * @param {object} inst
1156
- * @param {Element} container
1157
1109
  */
1158
1110
  function runDirectResume(Component, inst, container) {
1159
1111
  const rootEl = [...container.childNodes].find((n) => n.nodeType === 1 || (n.nodeType === 3 && String(n.textContent).trim() !== ''));
@@ -1187,17 +1139,10 @@ function runDirectResume(Component, inst, container) {
1187
1139
  pool.removeChild(pool.firstChild);
1188
1140
  }
1189
1141
  }
1190
- /**
1191
- * @param {new (props?: object) => any} Component
1192
- * @param {HTMLElement} container
1193
- * @param {object} [props]
1194
- * @param {{ preserveState?: boolean | Record<string, unknown>, skipOnMount?: boolean }} [opts]
1195
- */
1196
1142
  export async function hydrate(Component, container, props = {}, opts = {}) {
1197
1143
  if (typeof document === 'undefined') {
1198
1144
  throw new Error('vmz:dom hydrate() requires a document (browser)');
1199
1145
  }
1200
- /** @type {Record<string, unknown> | null} */
1201
1146
  let preserved = null;
1202
1147
  if (opts.preserveState && typeof opts.preserveState === 'object') {
1203
1148
  preserved = opts.preserveState;
@@ -1241,9 +1186,6 @@ export async function hydrate(Component, container, props = {}, opts = {}) {
1241
1186
  }
1242
1187
  return inst;
1243
1188
  }
1244
- /**
1245
- * @param {ParentNode} [root]
1246
- */
1247
1189
  export function hydrateIslands(root = globalThis.document) {
1248
1190
  // resume: hydrateIslands is an alias for resumeIslands (same Plan attach).
1249
1191
  return resumeIslands(root);