@vmz/core 0.1.31 → 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 (63) 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/browser/dom-core.d.ts +246 -0
  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} +47 -185
  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} +162 -219
  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.d.ts +0 -402
  49. package/dist/dom-core.js +0 -4813
  50. package/dist/dom.client.d.ts +0 -4
  51. package/dist/dom.client.js +0 -5
  52. package/dist/dom.d.ts +0 -6
  53. package/dist/dom.js +0 -7
  54. package/dist/list-client-components.d.ts +0 -62
  55. package/dist/native-addon.d.ts +0 -12
  56. package/dist/render-host.d.ts +0 -32
  57. package/dist/unknown-component.d.ts +0 -40
  58. /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
  59. /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
  60. /package/dist/{index.js → faces/index.js} +0 -0
  61. /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
  62. /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
  63. /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
@@ -1,6 +1,5 @@
1
- // @ts-nocheck
2
1
  /**
3
- * Generic VMZ Node host — SSR Route Graph pages + dist static + RPC/REST.
2
+ * Generic VMZ Node host �?SSR Route Graph pages + dist static + RPC/REST.
4
3
  *
5
4
  * Invoked by `vmz serve` / `vmz dev` (or: node dist/vmz-serve-host.mjs).
6
5
  *
@@ -10,8 +9,8 @@
10
9
  *
11
10
  * `VMZ_DEV=1`: POST `/__vmz/reload` soft-reloads modules (cache-bust import);
12
11
  * GET `/__vmz/events` SSE notifies the browser:
13
- * - island HMR → re-import `entry-client.js` (no full document reload)
14
- * - otherwise → `location.reload`
12
+ * - island HMR �?re-import `entry-client.js` (no full document reload)
13
+ * - otherwise �?`location.reload`
15
14
  *
16
15
  * Dev resolve hook propagates `?t=` onto nested relative `file:` imports under
17
16
  * dist so soft reload does not keep a stale `lib/*.js` ESM cache entry.
@@ -27,12 +26,11 @@ import { LOCALE_LINK_PLAN_SCHEMA, linkRouteAliasesFromUnits, localeHrefTableFrom
27
26
  import { loadNativeAddon } from './native-addon.js';
28
27
  import { createRenderHost } from './render-host.js';
29
28
  import { resolveRouteLayoutChain } from './route-layout-chain.js';
30
- import { handleNodeRequest, setRoutes, setServerModuleResolver } from './vmz-runtime.js';
29
+ import { handleNodeRequest, setRoutes, setServerModuleResolver } from '../faces/vmz-runtime.js';
31
30
  const ROUTE_CATALOG_SCHEMA = 'vmz.route.catalog.v0';
32
31
  const ROUTE_CATALOG_REL = '_vmz/route-catalog.json';
33
32
  const LOCALE_LINK_PLAN_REL = '_vmz/locale-link-plan.json';
34
33
  const require = createRequire(import.meta.url);
35
- /** App delivery root. Host companions may live under `_vmz/host/` (0.1.31). */
36
34
  function resolveServeDistDir() {
37
35
  if (process.env.VMZ_DIST)
38
36
  return path.resolve(process.env.VMZ_DIST);
@@ -45,14 +43,12 @@ function resolveServeDistDir() {
45
43
  return here;
46
44
  }
47
45
  const distDir = resolveServeDistDir();
48
- /** App project root for bare import / JSON resolve (dev SSR ≡ build node_modules). */
49
46
  function projectRootForResolve() {
50
47
  const fromEnv = typeof process.env.VMZ_PROJECT_ROOT === 'string' ? process.env.VMZ_PROJECT_ROOT.trim() : '';
51
48
  if (fromEnv)
52
49
  return path.resolve(fromEnv);
53
50
  return process.cwd();
54
51
  }
55
- /** @type {ReturnType<typeof createRequire> | null} */
56
52
  let appPackageRequire = null;
57
53
  function appPackageRequireResolve() {
58
54
  if (!appPackageRequire) {
@@ -112,7 +108,7 @@ const isDev = process.env.VMZ_DEV === '1' || process.env.VMZ_DEV === 'true';
112
108
  globalThis.__VMZ_RPC_ORIGIN = `http://${host}:${port}`;
113
109
  /**
114
110
  * Soft reload only busts the top-level `import(page?t=token)`. Nested relative
115
- * imports (`../../lib/units.js`) keep the first-loaded ESM cache entry — so a
111
+ * imports (`../../lib/units.js`) keep the first-loaded ESM cache entry �?so a
116
112
  * page can demand exports that the stale dep never had (or vice versa).
117
113
  * Propagate `t` from parentURL onto file: children under this dist.
118
114
  */
@@ -151,34 +147,20 @@ if (isDev) {
151
147
  },
152
148
  });
153
149
  }
154
- /** @type {number} */
155
150
  let reloadToken = Date.now();
156
- /** @type {Awaited<ReturnType<typeof createRenderHost>> | null} */
157
151
  let ssrRenderHost = null;
158
- /** @type {string | null} Correlatable build id from vmz dev (Living §12.8). */
159
152
  let lastDevBuildId = null;
160
- /** @type {Array<{ chunkId: string, pageRel: string, segs: ReturnType<typeof parsePathPattern> }>} */
161
153
  let pageCatalog = [];
162
- /** @type {Map<string, any>} */
163
154
  const pageCtors = new Map();
164
- /** Stylesheet from deployment `cssEntry` (e.g. vmz.css). */
165
155
  let cssEntry = null;
166
- /** Fingerprint of style inputs — busts `@import` siblings when tokens change (VMZ-8). */
167
156
  let styleBundleHash = null;
168
- /** @type {{ defaultThemeId: string, themeIds: string[], activationAttr: string, contentHash: string|null } | null} */
169
157
  let styleTheme = null;
170
- /** Locale route realization artifact from `_vmz/locale-route-realization.json` (optional). */
171
158
  let localeArtifact = null;
172
- /** Frozen locale link plan from `_vmz/locale-link-plan.json` (0.1.30 authority). */
173
159
  let localeLinkPlan = null;
174
- /** @type {Set<import('node:http').ServerResponse>} */
175
160
  const sseClients = new Set();
176
- /** In-flight HTTP requests (graceful shutdown drain). */
177
161
  let inFlight = 0;
178
- /** When true, refuse new work except health. */
179
162
  let shuttingDown = false;
180
163
  let ready = false;
181
- /** @type {{ message: string, stack?: string, at: number } | null} */
182
164
  let lastDevError = null;
183
165
  setServerModuleResolver((moduleId) => {
184
166
  const rel = moduleId.replace(/^#server\//, '') + '.js';
@@ -190,13 +172,9 @@ try {
190
172
  }
191
173
  catch (err) {
192
174
  lastDevError = normalizeDevError(err);
193
- ready = true; // still accept HTTP — serve error page / recover on next reload
175
+ ready = true; // still accept HTTP �?serve error page / recover on next reload
194
176
  console.error('vmz serve: initial load failed (dev host stays up)', lastDevError.message);
195
177
  }
196
- /**
197
- * @param {import('node:http').IncomingMessage} req
198
- * @returns {Promise<string>}
199
- */
200
178
  function readRequestBody(req) {
201
179
  return new Promise((resolve, reject) => {
202
180
  const chunks = [];
@@ -205,17 +183,16 @@ function readRequestBody(req) {
205
183
  req.on('error', reject);
206
184
  });
207
185
  }
208
- /**
209
- * @param {string} pathname
210
- * @returns {Promise<string | null>}
211
- */
212
186
  async function renderPage(pathname, opts = {}) {
213
187
  const rendered = await renderPageStream(pathname, opts);
214
188
  if (!rendered)
215
189
  return null;
216
- const stream = rendered.stream ?? rendered;
190
+ const raw = rendered.stream ?? rendered;
191
+ if (!raw || typeof raw !== 'object' || typeof raw[Symbol.asyncIterator] !== 'function') {
192
+ return null;
193
+ }
217
194
  let html = '';
218
- for await (const chunk of stream) {
195
+ for await (const chunk of raw) {
219
196
  html += chunk;
220
197
  }
221
198
  return html;
@@ -224,9 +201,6 @@ async function renderPage(pathname, opts = {}) {
224
201
  * Stream shell + Direct serialize body for the matched file-route page.
225
202
  * Runs Page.access (closed allow/redirect/not-found/deny) before load;
226
203
  * POST may run Page.action before re-render.
227
- * @param {string} pathname
228
- * @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
229
- * @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
230
204
  */
231
205
  async function renderPageStream(pathname, opts = {}) {
232
206
  try {
@@ -239,11 +213,6 @@ async function renderPageStream(pathname, opts = {}) {
239
213
  return { status: 500, stream: emitDevErrorHtml(normalized) };
240
214
  }
241
215
  }
242
- /**
243
- * @param {string} pathname
244
- * @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
245
- * @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
246
- */
247
216
  async function renderPageStreamInner(pathname, opts = {}) {
248
217
  if (isDev && lastDevError && pageCtors.size === 0) {
249
218
  return { status: 500, stream: emitDevErrorHtml(lastDevError) };
@@ -376,10 +345,6 @@ async function renderPageStreamInner(pathname, opts = {}) {
376
345
  stream: emitPageHtml(Page, match.chunkId, eventOnlyShell, props, opts, layoutChain, localeCtx),
377
346
  };
378
347
  }
379
- /**
380
- * @param {unknown} access
381
- * @returns {{ kind: 'allow' } | { kind: 'redirect', location: string } | { kind: 'deny' } | { kind: 'not-found' }}
382
- */
383
348
  function normalizeAccessResult(access) {
384
349
  if (access == null || access === true)
385
350
  return { kind: 'allow' };
@@ -392,10 +357,11 @@ function normalizeAccessResult(access) {
392
357
  if (k === 'not-found' || k === 'notfound')
393
358
  return { kind: 'not-found' };
394
359
  }
395
- if (typeof access === 'object') {
396
- const kind = String(access.kind || access.type || 'allow').toLowerCase();
360
+ if (typeof access === 'object' && access) {
361
+ const row = access;
362
+ const kind = String(row.kind || row.type || 'allow').toLowerCase();
397
363
  if (kind === 'redirect') {
398
- const location = String(access.location || access.to || access.href || '');
364
+ const location = String(row.location || row.to || row.href || '');
399
365
  if (!location)
400
366
  return { kind: 'deny' };
401
367
  return { kind: 'redirect', location };
@@ -408,30 +374,28 @@ function normalizeAccessResult(access) {
408
374
  }
409
375
  return { kind: 'allow' };
410
376
  }
411
- /**
412
- * @param {unknown} acted
413
- * @returns {{ kind: 'allow', props?: Record<string, unknown> } | { kind: 'redirect', location: string } | { kind: 'deny' } | { kind: 'not-found' }}
414
- */
415
377
  function normalizeActionResult(acted) {
416
378
  const base = normalizeAccessResult(acted);
417
379
  if (base.kind !== 'allow')
418
380
  return base;
419
- if (acted && typeof acted === 'object' && acted.props && typeof acted.props === 'object') {
420
- return { kind: 'allow', props: acted.props };
421
- }
422
- if (acted && typeof acted === 'object' && !('kind' in acted) && !('type' in acted) && !Array.isArray(acted)) {
423
- return { kind: 'allow', props: acted };
381
+ if (acted && typeof acted === 'object' && !Array.isArray(acted)) {
382
+ const row = acted;
383
+ if (row.props && typeof row.props === 'object') {
384
+ return { kind: 'allow', props: row.props };
385
+ }
386
+ if (!('kind' in row) && !('type' in row)) {
387
+ return { kind: 'allow', props: row };
388
+ }
424
389
  }
425
390
  return { kind: 'allow' };
426
391
  }
427
392
  /**
428
393
  * Minimal HTML for closed access/action results when no NotFound page exists.
429
- * @param {string} marker
430
394
  */
431
395
  async function* emitAccessShell(marker) {
432
396
  const native = loadNativeAddon();
433
397
  if (typeof native.generateHtmlShell !== 'function') {
434
- throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
398
+ throw new Error('vmz native addon missing generateHtmlShell �?rebuild with `pnpm napi:build`');
435
399
  }
436
400
  yield native.generateHtmlShell({
437
401
  title: 'App',
@@ -442,8 +406,7 @@ async function* emitAccessShell(marker) {
442
406
  });
443
407
  }
444
408
  /**
445
- * Prefer page `static meta()` for document title/description — never brand the framework in business HTML.
446
- * @param {any} Page
409
+ * Prefer page `static meta()` for document title/description �?never brand the framework in business HTML.
447
410
  */
448
411
  function resolvePageDocumentMeta(Page) {
449
412
  try {
@@ -460,14 +423,6 @@ function resolvePageDocumentMeta(Page) {
460
423
  return { title: 'App', description: '' };
461
424
  }
462
425
  }
463
- /**
464
- * @param {any} Page
465
- * @param {string} chunkId
466
- * @param {boolean} eventOnlyShell
467
- * @param {Record<string, unknown>} props
468
- * @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string }} [opts]
469
- * @param {string[]} [layoutChain] layout chunk ids outer→inner
470
- */
471
426
  async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {}, layoutChain = [], localeCtx = {}) {
472
427
  const signal = opts.signal;
473
428
  const live = isDev
@@ -477,7 +432,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
477
432
  let sawDisconnect = false;
478
433
  es.onerror = () => { sawDisconnect = true; };
479
434
  es.onopen = () => {
480
- // Host respawn drops SSE — reload once the new process is up (no manual restart).
435
+ // Host respawn drops SSE �?reload once the new process is up (no manual restart).
481
436
  if (sawDisconnect) location.reload();
482
437
  };
483
438
  function showOverlay(err) {
@@ -501,7 +456,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
501
456
  + "<p style=\\"margin:0 0 .5rem;color:#f87171;font-weight:700\\">Dev Error</p>"
502
457
  + "<pre style=\\"white-space:pre-wrap;margin:0 0 1rem;font-size:13px;line-height:1.45\\">" + esc(msg) + "</pre>"
503
458
  + (stack ? "<pre style=\\"white-space:pre-wrap;opacity:.7;font-size:12px\\">" + esc(stack) + "</pre>" : "")
504
- + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save — soft reload will clear this overlay.</p>"
459
+ + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save �?soft reload will clear this overlay.</p>"
505
460
  + "</div>";
506
461
  }
507
462
  function hideOverlay() {
@@ -522,26 +477,11 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
522
477
  hideOverlay();
523
478
  if (msg.mode === "island") {
524
479
  try {
525
- const { registerComponents, hydrate } = await import("/dom.browser.js?t=" + msg.token);
526
- const names = (msg.affectedChunks || [])
527
- .map((c) => String(c))
528
- .filter((c) => c.startsWith("components/") || !c.includes("/"))
529
- .map((c) => c.split("/").pop())
530
- .filter(Boolean);
531
- const map = {};
532
- for (const name of names) {
533
- let mod;
534
- try {
535
- mod = await import("/components/" + name + ".client.js?t=" + msg.token);
536
- } catch {
537
- mod = await import("/" + name + ".client.js?t=" + msg.token);
538
- }
539
- map[name] = mod.default;
540
- }
541
- if (Object.keys(map).length) registerComponents(map);
480
+ const { hydrate } = await import("/dom.browser.js?t=" + msg.token);
542
481
  const root = document.getElementById("app");
543
482
  const pageChunk = root && root.getAttribute("data-vmz-page");
544
483
  if (root && pageChunk) {
484
+ // Child Ctors come from page static imports after reload with cache token.
545
485
  const pageMod = await import("/" + pageChunk + ".client.js?t=" + msg.token);
546
486
  let hmrProps = {};
547
487
  try {
@@ -583,7 +523,6 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
583
523
  const propsJson = JSON.stringify(props ?? {});
584
524
  const localeId = localeCtx.localeId || localeArtifact?.defaultLocale || 'en';
585
525
  const dir = localeCtx.dir || 'ltr';
586
- /** @type {string[]} */
587
526
  const htmlExtraAttrs = [...htmlThemeAttrPair(themeId)];
588
527
  if (localeArtifact?.routing) {
589
528
  htmlExtraAttrs.push('data-vmz-locale-routing', JSON.stringify({
@@ -617,7 +556,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
617
556
  }
618
557
  if (signal?.aborted)
619
558
  return;
620
- // Wrap page HTML in layout chain (outer → inner) via default slot injection.
559
+ // Wrap page HTML in layout chain (outer �?inner) via default slot injection.
621
560
  for (let i = layoutChain.length - 1; i >= 0; i--) {
622
561
  const Layout = await loadPageCtor(layoutChain[i]);
623
562
  if (!Layout)
@@ -626,7 +565,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
626
565
  if (signal?.aborted)
627
566
  return;
628
567
  }
629
- // Locale discipline: apply frozen link plan rows (0.1.30) — no path algebra.
568
+ // Locale discipline: apply frozen link plan rows (0.1.30) �?no path algebra.
630
569
  if (localeArtifact && localeId) {
631
570
  bodyHtml = localizeBodyLinks(bodyHtml, localeId, localeArtifact, undefined, localeLinkPlan);
632
571
  }
@@ -641,7 +580,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
641
580
  return;
642
581
  const native = loadNativeAddon();
643
582
  if (typeof native.generatePageShell !== 'function') {
644
- throw new Error('vmz native addon missing generatePageShell — rebuild with `pnpm napi:build`');
583
+ throw new Error('vmz native addon missing generatePageShell �?rebuild with `pnpm napi:build`');
645
584
  }
646
585
  const entrySrc = `/${eventOnlyShell ? 'entry-event.js' : 'entry-client.js'}?t=${reloadToken}`;
647
586
  const cssHref = cssEntryWithBust(cssEntry);
@@ -705,7 +644,7 @@ const server = http.createServer((req, res) => {
705
644
  .then((raw) => {
706
645
  let payload = {};
707
646
  try {
708
- payload = raw ? JSON.parse(raw) : {};
647
+ payload = raw ? JSON.parse(String(raw)) : {};
709
648
  }
710
649
  catch {
711
650
  payload = {};
@@ -754,7 +693,7 @@ async function gracefulShutdown(signal) {
754
693
  return;
755
694
  shuttingDown = true;
756
695
  ready = false;
757
- console.log(`vmz serve: ${signal} — draining in-flight=${inFlight} timeout=${SHUTDOWN_TIMEOUT_MS}ms`);
696
+ console.log(`vmz serve: ${signal} �?draining in-flight=${inFlight} timeout=${SHUTDOWN_TIMEOUT_MS}ms`);
758
697
  server.close();
759
698
  const start = Date.now();
760
699
  while (inFlight > 0 && Date.now() - start < SHUTDOWN_TIMEOUT_MS) {
@@ -780,8 +719,6 @@ process.on('SIGINT', () => {
780
719
  /**
781
720
  * Attach author Link RouteId aliases so localizeBodyLinks can match `data-vmz-route="IndexPage"`
782
721
  * against realization rows keyed by chunk path (`pages/index`).
783
- * @param {any} artifact
784
- * @param {string} dir
785
722
  */
786
723
  function attachLinkRouteAliases(artifact, dir) {
787
724
  if (!artifact || typeof artifact !== 'object')
@@ -801,7 +738,6 @@ function attachLinkRouteAliases(artifact, dir) {
801
738
  * Re-import routes / pages / components with a new cache-bust token.
802
739
  * Keeps the HTTP server process alive (no Node restart).
803
740
  * Failed reloads keep the previous in-memory modules (Vite-like resilience).
804
- * @param {{ quiet?: boolean, payload?: { affectedChunks?: string[], seedChunks?: string[], emitted?: string[], full?: boolean, islandHmr?: boolean, buildId?: string, sourceRevision?: string, bundleRevision?: string, changed?: string[] } }} [opts]
805
741
  */
806
742
  async function softReload(opts = {}) {
807
743
  const prevToken = reloadToken;
@@ -820,7 +756,7 @@ async function softReload(opts = {}) {
820
756
  const bundleRevision = opts.payload?.bundleRevision != null ? String(opts.payload.bundleRevision) : null;
821
757
  if (buildId)
822
758
  lastDevBuildId = buildId;
823
- // 0.1.31: payload-only scope. `reloadToken` is opaque cache-bust — never invent full/affected here.
759
+ // 0.1.31: payload-only scope. `reloadToken` is opaque cache-bust �?never invent full/affected here.
824
760
  const reloadAllPages = Boolean(full) || (!islandHmr && rerunLoaders.length === 0 && affected.length === 0);
825
761
  try {
826
762
  try {
@@ -852,7 +788,6 @@ async function softReload(opts = {}) {
852
788
  });
853
789
  const nextCatalog = await listPageClientFiles(distDir);
854
790
  // empty catalog throws inside listPageClientFiles
855
- /** @type {Map<string, any>} */
856
791
  const nextCtors = new Map();
857
792
  if (!islandHmr) {
858
793
  const reloadSet = rerunLoaders.length > 0
@@ -952,7 +887,6 @@ async function softReload(opts = {}) {
952
887
  throw err;
953
888
  }
954
889
  }
955
- /** @param {string} chunkId @param {string[]} affected */
956
890
  function pageNeedsReload(chunkId, affected) {
957
891
  if (chunkId === 'pages/Layout' || chunkId.endsWith('/Layout'))
958
892
  return true;
@@ -961,7 +895,6 @@ function pageNeedsReload(chunkId, affected) {
961
895
  return id === chunkId || chunkId.startsWith(`${id}/`) || id.startsWith(`${chunkId}/`);
962
896
  });
963
897
  }
964
- /** @param {string} event */
965
898
  function notifySse(event) {
966
899
  for (const client of [...sseClients]) {
967
900
  try {
@@ -972,10 +905,9 @@ function notifySse(event) {
972
905
  }
973
906
  }
974
907
  }
975
- /** @param {unknown} err */
976
908
  function normalizeDevError(err) {
977
909
  if (err && typeof err === 'object') {
978
- const e = /** @type {{ message?: string, stack?: string }} */ (err);
910
+ const e = err;
979
911
  return {
980
912
  message: e.message ? String(e.message) : String(err),
981
913
  stack: e.stack ? String(e.stack) : undefined,
@@ -984,7 +916,6 @@ function normalizeDevError(err) {
984
916
  }
985
917
  return { message: String(err), at: Date.now() };
986
918
  }
987
- /** @param {{ message: string, stack?: string }} err */
988
919
  async function* emitDevErrorHtml(err) {
989
920
  const msg = escapeHtml(err.message || 'Unknown error');
990
921
  const stack = err.stack ? escapeHtml(err.stack) : '';
@@ -1000,7 +931,7 @@ async function* emitDevErrorHtml(err) {
1000
931
  <h1>Dev Error</h1>
1001
932
  <pre>${msg}</pre>
1002
933
  ${stack ? `<pre style="opacity:.7;font-size:12px">${stack}</pre>` : ''}
1003
- <p class="hint">Dev host stayed up. Fix the source and save — soft reload will recover.</p>
934
+ <p class="hint">Dev host stayed up. Fix the source and save �?soft reload will recover.</p>
1004
935
  </main>
1005
936
  <script>
1006
937
  (() => {
@@ -1026,11 +957,10 @@ async function* emitDevErrorHtml(err) {
1026
957
  }
1027
958
  }
1028
959
  catch {
1029
- /* fall through to plain HTML — never throw from the error page itself */
960
+ /* fall through to plain HTML �?never throw from the error page itself */
1030
961
  }
1031
962
  yield `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Dev Error</title></head><body>${body}</body></html>`;
1032
963
  }
1033
- /** @param {string} s */
1034
964
  function escapeHtml(s) {
1035
965
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1036
966
  }
@@ -1039,8 +969,6 @@ function escapeHtml(s) {
1039
969
  * - `prefix`: LocaleId from URL path (existing).
1040
970
  * - `none`: Host preference from cookie `vmz.locale` (validated), else defaultLocale.
1041
971
  * URL never carries LocaleId.
1042
- * @param {string} pathname
1043
- * @param {string | undefined} cookieHeader
1044
972
  */
1045
973
  function resolveLocalePath(pathname, cookieHeader) {
1046
974
  const raw = String(pathname || '/');
@@ -1088,10 +1016,6 @@ function resolveLocalePath(pathname, cookieHeader) {
1088
1016
  redirectTo: null,
1089
1017
  };
1090
1018
  }
1091
- /**
1092
- * @param {string} chunkId
1093
- * @param {string} localeId
1094
- */
1095
1019
  function pageMetaAlternates(chunkId, localeId) {
1096
1020
  if (!localeArtifact?.pageMetas)
1097
1021
  return [];
@@ -1099,13 +1023,11 @@ function pageMetaAlternates(chunkId, localeId) {
1099
1023
  localeArtifact.pageMetas.find((m) => m.routeId === chunkId && m.locale === localeArtifact.defaultLocale);
1100
1024
  return Array.isArray(meta?.alternates) ? meta.alternates : [];
1101
1025
  }
1102
- /** @param {string} href */
1103
1026
  function bustUrl(href) {
1104
1027
  const u = new URL(href);
1105
1028
  u.searchParams.set('t', String(reloadToken));
1106
1029
  return u.href;
1107
1030
  }
1108
- /** @param {string} chunkId */
1109
1031
  async function loadPageCtor(chunkId) {
1110
1032
  const pageRel = `${chunkId}.client.js`;
1111
1033
  const href = bustUrl(pathToFileURL(path.join(distDir, pageRel)).href);
@@ -1116,7 +1038,6 @@ async function loadPageCtor(chunkId) {
1116
1038
  /**
1117
1039
  * Load compiled page catalog from `_vmz/route-catalog.json` (0.1.30 authority).
1118
1040
  * Hosts must not re-parse deployment pathPattern into a live catalog.
1119
- * @param {string} dir
1120
1041
  */
1121
1042
  async function listPageClientFiles(dir) {
1122
1043
  const fromCatalog = await listPagesFromRouteCatalog(dir);
@@ -1129,11 +1050,7 @@ async function listPageClientFiles(dir) {
1129
1050
  }
1130
1051
  return fromCatalog;
1131
1052
  }
1132
- /**
1133
- * @param {string} dir
1134
- */
1135
1053
  async function listPagesFromRouteCatalog(dir) {
1136
- /** @type {Array<{ chunkId: string, pageRel: string, routeId?: string, pathPattern?: string, segs: ReturnType<typeof parsePathPattern> }>} */
1137
1054
  const out = [];
1138
1055
  try {
1139
1056
  const raw = await readFile(path.join(dir, ...ROUTE_CATALOG_REL.split('/')), 'utf8');
@@ -1165,14 +1082,12 @@ async function listPagesFromRouteCatalog(dir) {
1165
1082
  }
1166
1083
  /**
1167
1084
  * Browser HTTP pattern (`/` / `/home` / `/users/:id` / `/blog/[...slug]`).
1168
- * @param {string} pattern
1169
1085
  */
1170
1086
  function parsePathPattern(pattern) {
1171
1087
  const raw = String(pattern || '').trim();
1172
1088
  if (!raw || raw === '/')
1173
1089
  return [];
1174
1090
  const parts = raw.replace(/^\/+/, '').split('/').filter(Boolean);
1175
- /** @type {Array<{ kind: 'static' | 'param' | 'catch', value?: string, name?: string }>} */
1176
1091
  const segs = [];
1177
1092
  for (const p of parts) {
1178
1093
  if (isRouteGroupDir(p))
@@ -1181,9 +1096,6 @@ function parsePathPattern(pattern) {
1181
1096
  }
1182
1097
  return segs;
1183
1098
  }
1184
- /**
1185
- * @param {string} p
1186
- */
1187
1099
  function parsePathSegment(p) {
1188
1100
  const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(p);
1189
1101
  const param = /^\[([^\]]+)\]$/.exec(p);
@@ -1202,10 +1114,6 @@ function isRouteGroupDir(seg) {
1202
1114
  function isRouteBoundaryStem(stem) {
1203
1115
  return stem === 'Layout' || stem === 'Loading' || stem === 'Error' || stem === 'NotFound';
1204
1116
  }
1205
- /**
1206
- * @param {string} pathname
1207
- * @param {typeof pageCatalog} catalog
1208
- */
1209
1117
  function matchFileRoute(pathname, catalog) {
1210
1118
  const pathParts = decodeURIComponent(pathname.split('?')[0] || '/')
1211
1119
  .replace(/\/+$/, '')
@@ -1225,17 +1133,11 @@ function matchFileRoute(pathname, catalog) {
1225
1133
  }
1226
1134
  return best;
1227
1135
  }
1228
- /**
1229
- * @param {ReturnType<typeof parsePathPattern>} segs
1230
- * @param {string} pathname
1231
- * @returns {Record<string, string>}
1232
- */
1233
1136
  function extractRouteParams(segs, pathname) {
1234
1137
  const pathParts = decodeURIComponent(pathname.split('?')[0] || '/')
1235
1138
  .replace(/\/+$/, '')
1236
1139
  .split('/')
1237
1140
  .filter(Boolean);
1238
- /** @type {Record<string, string>} */
1239
1141
  const params = {};
1240
1142
  let j = 0;
1241
1143
  for (let i = 0; i < segs.length; i++) {
@@ -1254,11 +1156,6 @@ function extractRouteParams(segs, pathname) {
1254
1156
  }
1255
1157
  return params;
1256
1158
  }
1257
- /**
1258
- * @param {ReturnType<typeof parsePathPattern>} segs
1259
- * @param {string[]} pathParts
1260
- * @returns {number | null}
1261
- */
1262
1159
  function scoreRoute(segs, pathParts) {
1263
1160
  let i = 0;
1264
1161
  let j = 0;
@@ -1266,7 +1163,7 @@ function scoreRoute(segs, pathParts) {
1266
1163
  while (i < segs.length) {
1267
1164
  const s = segs[i];
1268
1165
  if (s.kind === 'catch') {
1269
- // Required catch-all `[...slug]` needs ≥1 remaining segment (not `/`).
1166
+ // Required catch-all `[...slug]` needs �? remaining segment (not `/`).
1270
1167
  if (j >= pathParts.length)
1271
1168
  return null;
1272
1169
  score += 1;
@@ -1296,9 +1193,7 @@ function findRootCatchAll(catalog) {
1296
1193
  return catalog.find((p) => isRootCatchAll(p)) || null;
1297
1194
  }
1298
1195
  /**
1299
- * Optional app gate: `dist/vmz-route-gate.mjs` → `{ check(pathname, chunkId) => 'not_found' | null }`.
1300
- * @param {string} pathname
1301
- * @param {string | undefined} chunkId
1196
+ * Optional app gate: `dist/vmz-route-gate.mjs` �?`{ check(pathname, chunkId) => 'not_found' | null }`.
1302
1197
  */
1303
1198
  async function runRouteGate(pathname, chunkId) {
1304
1199
  try {
@@ -1312,29 +1207,23 @@ async function runRouteGate(pathname, chunkId) {
1312
1207
  return null;
1313
1208
  }
1314
1209
  }
1315
- /**
1316
- * @param {Array<{ name: string, entry: string }>} eager
1317
- * @param {Array<{ name: string, entry: string }>} lazy
1318
- * @param {number} token
1319
- */
1320
1210
  function emitEntryClient(eager, lazy, token) {
1321
1211
  const q = `?t=${token}`;
1322
1212
  const native = loadNativeAddon();
1323
1213
  if (typeof native.generateServeEntryClient !== 'function') {
1324
- throw new Error('vmz native addon missing generateServeEntryClient — rebuild with `pnpm napi:build`');
1214
+ throw new Error('vmz native addon missing generateServeEntryClient �?rebuild with `pnpm napi:build`');
1325
1215
  }
1326
1216
  return native.generateServeEntryClient(eager, lazy, q);
1327
1217
  }
1328
1218
  /**
1329
1219
  * EventEntry zero-framework bootstrap: no static import of vmz-dom / page / islands.
1330
1220
  * Framework bytes load only inside the first matching DOM event handler.
1331
- * @param {number} token
1332
1221
  */
1333
1222
  function emitEntryEvent(token) {
1334
1223
  const q = `?t=${token}`;
1335
1224
  const native = loadNativeAddon();
1336
1225
  if (typeof native.generateServeEntryEvent !== 'function') {
1337
- throw new Error('vmz native addon missing generateServeEntryEvent — rebuild with `pnpm napi:build`');
1226
+ throw new Error('vmz native addon missing generateServeEntryEvent �?rebuild with `pnpm napi:build`');
1338
1227
  }
1339
1228
  return native.generateServeEntryEvent(q);
1340
1229
  }
@@ -1342,11 +1231,9 @@ function emitEntryEvent(token) {
1342
1231
  * Style Theme cookie / localStorage key (host contract, not a second theme API).
1343
1232
  */
1344
1233
  const THEME_STORE_KEY = 'vmz-theme';
1345
- /** Host preference key for `routing.strategy: 'none'` (cookie + localStorage). */
1346
1234
  const LOCALE_STORE_KEY = 'vmz.locale';
1347
1235
  /**
1348
1236
  * Cache-bust stylesheet entry for dev reload (token + serve revision).
1349
- * @param {string | null | undefined} entry
1350
1237
  */
1351
1238
  function cssEntryWithBust(entry) {
1352
1239
  if (!entry)
@@ -1358,10 +1245,6 @@ function cssEntryWithBust(entry) {
1358
1245
  params.set('h', styleBundleHash);
1359
1246
  return `${base}?${params.toString()}`;
1360
1247
  }
1361
- /**
1362
- * @param {string} dir
1363
- * @returns {Promise<{ cssEntry: string|null, styleTheme: typeof styleTheme, styleBundleHash: string|null }>}
1364
- */
1365
1248
  async function loadDeploymentStyle(dir) {
1366
1249
  try {
1367
1250
  const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
@@ -1389,11 +1272,8 @@ async function loadDeploymentStyle(dir) {
1389
1272
  }
1390
1273
  }
1391
1274
  /**
1392
- * Priority: `?theme=` → cookie → none (CSS `:root` + prefers-color-scheme media).
1275
+ * Priority: `?theme=` �?cookie �?none (CSS `:root` + prefers-color-scheme media).
1393
1276
  * Explicit ids (including default) always win over OS preference via activation attr.
1394
- * @param {URLSearchParams | undefined} searchParams
1395
- * @param {string | undefined} cookieHeader
1396
- * @returns {string|null}
1397
1277
  */
1398
1278
  function resolveThemeId(searchParams, cookieHeader) {
1399
1279
  if (!styleTheme)
@@ -1409,8 +1289,6 @@ function resolveThemeId(searchParams, cookieHeader) {
1409
1289
  }
1410
1290
  /**
1411
1291
  * Always emit activation attr for an explicit theme id (incl. default) so it overrides OS media.
1412
- * @param {string|null} themeId
1413
- * @returns {[string, string] | []} flattened attr pair for generatePageShell
1414
1292
  */
1415
1293
  function htmlThemeAttrPair(themeId) {
1416
1294
  if (!styleTheme || !themeId)
@@ -1422,7 +1300,7 @@ function htmlThemeAttrPair(themeId) {
1422
1300
  }
1423
1301
  /**
1424
1302
  * Inline boot when SSR had no query/cookie: apply explicit `localStorage` only.
1425
- * No stored choice → leave bare `<html>` so CSS `@media (prefers-color-scheme)` follows OS live.
1303
+ * No stored choice �?leave bare `<html>` so CSS `@media (prefers-color-scheme)` follows OS live.
1426
1304
  * Explicit ids (incl. default) always set the activation attr so they override OS media.
1427
1305
  */
1428
1306
  function themeBootstrapScript() {
@@ -1436,7 +1314,7 @@ function themeBootstrapScript() {
1436
1314
  /**
1437
1315
  * LocaleId as client state (routing.strategy = none): apply localStorage before any
1438
1316
  * page/client module runs so `#locales/*` pick the right variant. Prefix strategy
1439
- * keeps LocaleId in the URL — no boot rewrite.
1317
+ * keeps LocaleId in the URL �?no boot rewrite.
1440
1318
  * Also mirrors into cookie so the next SSR negotiate sees Host preference.
1441
1319
  */
1442
1320
  function localeBootstrapScript() {
@@ -1453,8 +1331,8 @@ function localeBootstrapScript() {
1453
1331
  return ` <script>(function(){try{var k=${key},ids=${idList};var id=localStorage.getItem(k);if(!id||ids.indexOf(id)<0)return;document.documentElement.setAttribute("data-locale",id);document.documentElement.setAttribute("lang",id);window.__vmzLocaleIdHint=id;document.cookie=k+"="+encodeURIComponent(id)+"; path=/; max-age=31536000; SameSite=Lax";}catch(e){}})();</script>\n`;
1454
1332
  }
1455
1333
  /**
1456
- * Site favicon links from build artifact `_vmz/site-favicon.json` (author SVG → PNG/ICO).
1457
- * Empty when skipped / missing — do not invent broken <link>s.
1334
+ * Site favicon links from build artifact `_vmz/site-favicon.json` (author SVG �?PNG/ICO).
1335
+ * Empty when skipped / missing �?do not invent broken <link>s.
1458
1336
  */
1459
1337
  function siteFaviconHeadHtml() {
1460
1338
  try {
@@ -1472,10 +1350,6 @@ function siteFaviconHeadHtml() {
1472
1350
  return '';
1473
1351
  }
1474
1352
  }
1475
- /**
1476
- * @param {string|undefined} header
1477
- * @param {string} name
1478
- */
1479
1353
  function readCookie(header, name) {
1480
1354
  if (!header)
1481
1355
  return null;
@@ -1496,19 +1370,10 @@ function readCookie(header, name) {
1496
1370
  }
1497
1371
  return null;
1498
1372
  }
1499
- /**
1500
- * @param {string} dir
1501
- * @returns {Promise<string|null>}
1502
- */
1503
1373
  async function loadCssEntry(dir) {
1504
1374
  const meta = await loadDeploymentStyle(dir);
1505
1375
  return meta.cssEntry;
1506
1376
  }
1507
- /**
1508
- * @param {string} dir
1509
- * @param {string} chunkId
1510
- * @returns {Promise<Array<{ component: string, strategy: string }>>}
1511
- */
1512
1377
  async function loadPageResumeEntries(dir, chunkId) {
1513
1378
  try {
1514
1379
  const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
@@ -1525,17 +1390,14 @@ async function loadPageResumeEntries(dir, chunkId) {
1525
1390
  return [];
1526
1391
  }
1527
1392
  }
1528
- /** @param {string} strategy */
1529
1393
  function isEventStrategy(strategy) {
1530
1394
  return strategy === 'event' || strategy === 'click' || strategy.startsWith('event:');
1531
1395
  }
1532
- /** @param {string[]} strategies */
1533
1396
  function isEventOnlyShell(strategies) {
1534
1397
  if (!strategies.length)
1535
1398
  return false;
1536
1399
  return strategies.every((s) => isEventStrategy(s));
1537
1400
  }
1538
- /** @param {string} s */
1539
1401
  function escapeAttr(s) {
1540
1402
  return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
1541
1403
  }