@vesk/vesk-cli 0.2.24 → 0.2.26

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 (2) hide show
  1. package/dist/cli.js +268 -82
  2. package/package.json +5 -5
package/dist/cli.js CHANGED
@@ -2154,6 +2154,9 @@ function stripDirectTextNodes(el) {
2154
2154
  if (el.childNodes[i].nodeType === 3) el.childNodes[i].remove();
2155
2155
  }
2156
2156
  }
2157
+ function captureSsrElementChildren(el) {
2158
+ el.__vsk_ssrEls = Array.prototype.slice.call(el.children);
2159
+ }
2157
2160
  function assertFullyHydrated(container) {
2158
2161
  let unclaimed = 0;
2159
2162
  let sample = "";
@@ -2198,6 +2201,19 @@ function adoptElement(marker, tag) {
2198
2201
  if (tag && el.tagName.toLowerCase() !== tag) return null;
2199
2202
  marker.remove();
2200
2203
  stripDirectTextNodes(el);
2204
+ captureSsrElementChildren(el);
2205
+ stampClaimed(el);
2206
+ return el;
2207
+ }
2208
+ function adoptElementRaw(marker, tag) {
2209
+ const el = marker.nextElementSibling;
2210
+ if (!el) {
2211
+ marker.remove();
2212
+ return null;
2213
+ }
2214
+ if (tag && el.tagName.toLowerCase() !== tag) return null;
2215
+ marker.remove();
2216
+ captureSsrElementChildren(el);
2201
2217
  stampClaimed(el);
2202
2218
  return el;
2203
2219
  }
@@ -2205,30 +2221,6 @@ function createHydrateWalker(container, markerList) {
2205
2221
  const markers = markerList || (container ? collectVskMarkers(container) : []);
2206
2222
  return new WalkerEngine(container, markers);
2207
2223
  }
2208
- function createHydrateChildWalker(parentEl) {
2209
- let childIdx = 0;
2210
- const children = parentEl ? parentEl.children : [];
2211
- return {
2212
- root: parentEl,
2213
- done() {
2214
- return childIdx >= children.length;
2215
- },
2216
- nextElement(tag) {
2217
- while (childIdx < children.length) {
2218
- const child = children[childIdx++];
2219
- if (!tag || child.tagName.toLowerCase() === tag) {
2220
- stripDirectTextNodes(child);
2221
- stampClaimed(child);
2222
- return child;
2223
- }
2224
- }
2225
- return document.createElement(tag || "div");
2226
- },
2227
- subWalker(rootEl) {
2228
- return createHydrateChildWalker(rootEl);
2229
- }
2230
- };
2231
- }
2232
2224
  function hydrateViewport(container, componentFn, props, rootMargin = 500) {
2233
2225
  if (document.readyState !== "complete") {
2234
2226
  return new Promise((resolve39) => {
@@ -2375,6 +2367,13 @@ var init_hydrate = __esm({
2375
2367
  root;
2376
2368
  markers;
2377
2369
  idx = 0;
2370
+ // Elements already adopted by an earlier claim in this walk. Marker-only SSR
2371
+ // can leave TWO `<!--vsk-->` marks on the same element (the call site and
2372
+ // the callee's own root marker — e.g. Link self-prefixes `<!--vsk-->` and
2373
+ // the shared walker claims its call-site marker), and a claim must never
2374
+ // adopt the same node twice: double-adoption strips its SSR text a second
2375
+ // time and shells the cursor so every later claim misses its element.
2376
+ adopted = /* @__PURE__ */ new WeakSet();
2378
2377
  constructor(root3, markers) {
2379
2378
  this.root = root3;
2380
2379
  this.markers = markers.map((c) => ({ comment: c, state: "unclaimed" }));
@@ -2382,6 +2381,43 @@ var init_hydrate = __esm({
2382
2381
  done() {
2383
2382
  return this.idx >= this.markers.length;
2384
2383
  }
2384
+ // A marker whose `nextElementSibling` was already adopted is a dead alias of
2385
+ // a live claim point (double-marker collision). Retire it and move on so the
2386
+ // cursor never claims a node twice.
2387
+ isAlreadyAdopted(tm, el) {
2388
+ if (el !== null && this.adopted.has(el)) {
2389
+ tm.state = "claimed";
2390
+ this.idx++;
2391
+ tm.comment.remove();
2392
+ return true;
2393
+ }
2394
+ return false;
2395
+ }
2396
+ recordAdopted(el) {
2397
+ this.adopted.add(el);
2398
+ }
2399
+ // Marker-only SSR can stack several `<!--vsk-->` marks before ONE element
2400
+ // (call-site marker + the callee's own root marker). The FIRST claim adopts
2401
+ // the element; the alias markers that immediately follow it are dead — they
2402
+ // point at an already-claimed node and must be physically retired so the
2403
+ // canary never counts them and a trailing alias never lingers in the DOM.
2404
+ retireAliases(el) {
2405
+ let i = this.idx;
2406
+ while (i < this.markers.length) {
2407
+ const tm = this.markers[i];
2408
+ if (tm.state === "claimed") {
2409
+ i++;
2410
+ continue;
2411
+ }
2412
+ if (tm.comment.nextElementSibling === el) {
2413
+ tm.state = "claimed";
2414
+ tm.comment.remove();
2415
+ i++;
2416
+ continue;
2417
+ }
2418
+ break;
2419
+ }
2420
+ }
2385
2421
  nextElement(tag) {
2386
2422
  while (this.idx < this.markers.length) {
2387
2423
  const tm = this.markers[this.idx];
@@ -2390,6 +2426,7 @@ var init_hydrate = __esm({
2390
2426
  continue;
2391
2427
  }
2392
2428
  const el = tm.comment.nextElementSibling;
2429
+ if (this.isAlreadyAdopted(tm, el)) continue;
2393
2430
  if (tag && el && el.tagName.toLowerCase() !== tag) {
2394
2431
  break;
2395
2432
  }
@@ -2399,6 +2436,8 @@ var init_hydrate = __esm({
2399
2436
  tm.state = "claimed";
2400
2437
  break;
2401
2438
  }
2439
+ this.recordAdopted(adopted);
2440
+ this.retireAliases(adopted);
2402
2441
  tm.state = "claimed";
2403
2442
  return adopted;
2404
2443
  }
@@ -2410,6 +2449,45 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2410
2449
  }
2411
2450
  return document.createElement(tag || "div");
2412
2451
  }
2452
+ claimOnly(tag) {
2453
+ while (this.idx < this.markers.length) {
2454
+ const tm = this.markers[this.idx];
2455
+ if (tm.state === "claimed") {
2456
+ this.idx++;
2457
+ continue;
2458
+ }
2459
+ const el = tm.comment.nextElementSibling;
2460
+ if (this.isAlreadyAdopted(tm, el)) continue;
2461
+ if (tag && el && el.tagName.toLowerCase() !== tag) break;
2462
+ this.idx++;
2463
+ const adopted = adoptElementRaw(tm.comment, tag);
2464
+ if (adopted === null) {
2465
+ tm.state = "claimed";
2466
+ break;
2467
+ }
2468
+ this.recordAdopted(adopted);
2469
+ this.retireAliases(adopted);
2470
+ tm.state = "claimed";
2471
+ return adopted;
2472
+ }
2473
+ return document.createElement(tag || "div");
2474
+ }
2475
+ retireDetached() {
2476
+ while (this.idx < this.markers.length) {
2477
+ const tm = this.markers[this.idx];
2478
+ if (tm.state === "claimed") {
2479
+ this.idx++;
2480
+ continue;
2481
+ }
2482
+ const c = tm.comment;
2483
+ if (!c || !c.parentNode) {
2484
+ tm.state = "claimed";
2485
+ this.idx++;
2486
+ continue;
2487
+ }
2488
+ break;
2489
+ }
2490
+ }
2413
2491
  subWalker(rootEl) {
2414
2492
  const subMarkers = this.markers.slice(this.idx).filter((m) => {
2415
2493
  if (m.state === "claimed") return false;
@@ -2420,9 +2498,6 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2420
2498
  });
2421
2499
  this.idx += subMarkers.length;
2422
2500
  for (const m of subMarkers) m.state = "claimed";
2423
- if (subMarkers.length === 0 && rootEl && rootEl.tagName === "SPAN" && rootEl.style && rootEl.style.display === "contents") {
2424
- return createHydrateChildWalker(rootEl);
2425
- }
2426
2501
  return new _WalkerEngine(rootEl, subMarkers.map((m) => m.comment));
2427
2502
  }
2428
2503
  claimByKey(key) {
@@ -2431,10 +2506,14 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2431
2506
  const tm = this.markers[i];
2432
2507
  if (tm.state === "claimed") continue;
2433
2508
  const el = tm.comment.nextElementSibling;
2509
+ if (this.isAlreadyAdopted(tm, el)) continue;
2434
2510
  if (el && el.getAttribute("data-vsk-key") === strKey) {
2435
2511
  tm.state = "claimed";
2512
+ this.recordAdopted(el);
2513
+ this.retireAliases(el);
2436
2514
  tm.comment.remove();
2437
2515
  stripDirectTextNodes(el);
2516
+ captureSsrElementChildren(el);
2438
2517
  stampClaimed(el);
2439
2518
  return { el };
2440
2519
  }
@@ -2448,6 +2527,7 @@ ${String(this.root.outerHTML).slice(0, 800)}` : "")
2448
2527
  const tm = this.markers[i];
2449
2528
  if (tm.state === "claimed") continue;
2450
2529
  const el = tm.comment.nextElementSibling;
2530
+ if (el && this.adopted.has(el)) continue;
2451
2531
  if (el && el.getAttribute("data-vsk-key") === strKey) return el;
2452
2532
  }
2453
2533
  return null;
@@ -2835,11 +2915,6 @@ function isRouteHref(href) {
2835
2915
  function setIsHydrating(v) {
2836
2916
  __isHydrating = v;
2837
2917
  }
2838
- function captureInitialScroll() {
2839
- if (typeof window === "undefined") return;
2840
- _initialSavedScroll = window.scrollY || 0;
2841
- _initialScrollRestored = false;
2842
- }
2843
2918
  function setIsPopStateNavigation(v) {
2844
2919
  _isPopStateNavigation = v;
2845
2920
  }
@@ -2907,11 +2982,6 @@ function handleScroll(pathname, isReplace, scrollBehavior) {
2907
2982
  requestAnimationFrame(() => {
2908
2983
  window.scrollTo({ top: savedY !== void 0 ? savedY : 0, behavior });
2909
2984
  });
2910
- } else if (isReplace && !_initialScrollRestored) {
2911
- _initialScrollRestored = true;
2912
- requestAnimationFrame(() => {
2913
- window.scrollTo({ top: _initialSavedScroll, behavior });
2914
- });
2915
2985
  } else if (!isReplace) {
2916
2986
  requestAnimationFrame(() => window.scrollTo({ top: 0, behavior }));
2917
2987
  }
@@ -2976,10 +3046,6 @@ function Link(props, registry, hydrate) {
2976
3046
  const href = props.href || "#";
2977
3047
  if (hydrate && hydrate.nextElement) {
2978
3048
  let a2 = hydrate.nextElement("a");
2979
- if (a2 && !a2.parentNode && hydrate.root) {
2980
- const existing = hydrate.root.querySelector("a");
2981
- if (existing) a2 = existing;
2982
- }
2983
3049
  const claimed = a2 ? a2.parentNode !== null : false;
2984
3050
  if (!a2) a2 = document.createElement("a");
2985
3051
  applyLinkDom(a2, props, href);
@@ -3222,7 +3288,7 @@ function useRouter() {
3222
3288
  }
3223
3289
  };
3224
3290
  }
3225
- var Redirect, NotFoundError, RouterCtx, _currentRouter, _outletId, __isHydrating, _state, _scrollPositions, _isPopStateNavigation, _initialScrollRestored, _initialSavedScroll, HEAD_MARKER;
3291
+ var Redirect, NotFoundError, RouterCtx, _currentRouter, _outletId, __isHydrating, _state, _scrollPositions, _isPopStateNavigation, HEAD_MARKER;
3226
3292
  var init_router_components = __esm({
3227
3293
  "../runtime/src/router-components.ts"() {
3228
3294
  "use strict";
@@ -3257,8 +3323,6 @@ var init_router_components = __esm({
3257
3323
  };
3258
3324
  _scrollPositions = /* @__PURE__ */ new Map();
3259
3325
  _isPopStateNavigation = false;
3260
- _initialScrollRestored = false;
3261
- _initialSavedScroll = 0;
3262
3326
  HEAD_MARKER = "data-vesk-head";
3263
3327
  }
3264
3328
  });
@@ -4316,10 +4380,6 @@ function createRouter(routes, options = {}) {
4316
4380
  },
4317
4381
  start() {
4318
4382
  setCurrentRouter(this);
4319
- if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
4320
- captureInitialScroll();
4321
- window.history.scrollRestoration = "manual";
4322
- }
4323
4383
  if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
4324
4384
  let _scrollTimer = null;
4325
4385
  window.addEventListener("scroll", () => {
@@ -4639,10 +4699,6 @@ function createFileRouter(routeTree, options = {}) {
4639
4699
  },
4640
4700
  start() {
4641
4701
  setCurrentRouter(this);
4642
- if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
4643
- captureInitialScroll();
4644
- window.history.scrollRestoration = "manual";
4645
- }
4646
4702
  if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
4647
4703
  let _scrollTimer = null;
4648
4704
  const _onScroll = () => {
@@ -27583,15 +27639,21 @@ function emitStatic(ctx2, node, tracked2, effectsVar) {
27583
27639
  ctx2.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(escapeHtml4(attr.value))});`);
27584
27640
  }
27585
27641
  }
27642
+ let residueBefore = 0;
27586
27643
  for (const child of children) {
27587
27644
  const childVar = emitNode(ctx2, child, tracked2, effectsVar, el);
27588
27645
  if (childVar) {
27589
27646
  if (ctx2.hydrate) {
27590
- ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
27647
+ if (child instanceof TextNode || child instanceof DynamicBinding) {
27648
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { const __ssr = ${el}.__vsk_ssrEls || []; if (${residueBefore} < __ssr.length) ${el}.insertBefore(${childVar}, __ssr[${residueBefore}]); else ${el}.appendChild(${childVar}); }`);
27649
+ } else {
27650
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
27651
+ }
27591
27652
  } else {
27592
27653
  ctx2.push(`${el}.appendChild(${childVar});`);
27593
27654
  }
27594
27655
  }
27656
+ residueBefore += ssrResidueEstimate(child);
27595
27657
  }
27596
27658
  for (const attr of dynAttrs) {
27597
27659
  const target = attr.target || "";
@@ -27762,22 +27824,28 @@ function emitComponentCall(ctx2, node, tracked2, effectsVar, parentVar, compPref
27762
27824
  }
27763
27825
  const propsObj = `{ ${propsEntries.join(", ")} }`;
27764
27826
  const v = ctx2.n();
27765
- const awaitKw = ctx2.asyncComps.has(node.componentName) ? "await " : "";
27827
+ const awaitKw = ctx2.isAsyncScope ? "await " : "";
27766
27828
  const calleeExpr = node.calleeExpr ? `(${node.calleeExpr})` : null;
27767
27829
  if (ctx2.hydrate) {
27768
27830
  const access = calleeExpr ?? (ctx2.importedNames.has(node.componentName) ? node.componentName : `__components[${JSON.stringify(node.componentName)}]`);
27769
- const subScope = () => ctx2.inTryBody ? `${ctx2.walker}.subWalker(${parentVar})` : `${ctx2.walker}.subWalker(${ctx2.walker}.nextElement())`;
27831
+ const plainTarget = !!(calleeExpr || ctx2.importedNames.has(node.componentName)) && !ctx2.selfClaimNames.has(node.componentName);
27832
+ const walkerArg = ctx2.walker;
27833
+ const maybeReplace = (v2) => {
27834
+ if (!plainTarget) return;
27835
+ ctx2.push(`if (${v2} && ${v2}.parentNode == null && ${v2}.nodeType !== 11) { const __sr = ${walkerArg}.claimOnly(); if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v2}, __sr); }`);
27836
+ };
27837
+ const maybeRetire = () => {
27838
+ if (ctx2.selfClaimNames.has(node.componentName)) {
27839
+ ctx2.push(`${walkerArg}.retireDetached();`);
27840
+ }
27841
+ };
27770
27842
  if (node.children.length > 0) {
27771
- const walkerVar = ctx2.n();
27772
- ctx2.push(`const ${walkerVar} = ${subScope()};`);
27773
27843
  const frag = ctx2.n();
27774
27844
  ctx2.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
27775
- const savedWalker = ctx2.walker;
27776
27845
  const savedEffects = ctx2.effects;
27777
27846
  const wipeMount = ctx2.linkNames.has(node.componentName);
27778
27847
  const savedHydrate = ctx2.hydrate;
27779
27848
  if (wipeMount) ctx2.hydrate = false;
27780
- ctx2.walker = walkerVar;
27781
27849
  ctx2.effects = [];
27782
27850
  for (const child of node.children) {
27783
27851
  const childVar = emitNode(ctx2, child, tracked2, effectsVar, "$f");
@@ -27785,14 +27853,17 @@ function emitComponentCall(ctx2, node, tracked2, effectsVar, parentVar, compPref
27785
27853
  }
27786
27854
  for (const eff of ctx2.effects) ctx2.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr(eff)});` : eff);
27787
27855
  ctx2.effects = savedEffects;
27788
- ctx2.walker = savedWalker;
27789
27856
  ctx2.hydrate = savedHydrate;
27790
27857
  ctx2.push(`return $f; })();`);
27791
27858
  propsEntries.push(`children: ${frag}`);
27792
- ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerVar});`);
27859
+ ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerArg});`);
27860
+ maybeReplace(v);
27861
+ maybeRetire();
27793
27862
  return v;
27794
27863
  }
27795
- ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${subScope()});`);
27864
+ ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${walkerArg});`);
27865
+ maybeReplace(v);
27866
+ maybeRetire();
27796
27867
  return v;
27797
27868
  } else {
27798
27869
  if (node.children.length > 0) {
@@ -28521,11 +28592,12 @@ ${renderAllItems(" ", null)}
28521
28592
  function computeAsyncComponents(comps) {
28522
28593
  return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
28523
28594
  }
28524
- function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set()) {
28595
+ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set(), selfClaimNames = /* @__PURE__ */ new Set()) {
28525
28596
  const tracked2 = collectTrackedNames(comp.body);
28526
28597
  const ctx2 = new Ctx();
28527
28598
  ctx2.importedNames = importedNames;
28528
28599
  ctx2.linkNames = linkNames;
28600
+ ctx2.selfClaimNames = selfClaimNames;
28529
28601
  ctx2.hydrate = hydrate;
28530
28602
  ctx2.asyncComps = asyncComps;
28531
28603
  ctx2.isAsyncScope = comp.isAsync || asyncComps.has(comp.name);
@@ -28549,10 +28621,16 @@ function generateComponent(comp, importedNames = /* @__PURE__ */ new Set(), hydr
28549
28621
  const paramInit = buildParamInit2(comp.paramNames);
28550
28622
  if (paramInit) ctx2.push(indent2(paramInit));
28551
28623
  for (const node of comp.body) {
28624
+ if (node instanceof TrackDecl) {
28625
+ emitNode(ctx2, node, tracked2, null);
28626
+ }
28627
+ }
28628
+ for (const node of comp.body) {
28629
+ if (node instanceof TrackDecl) continue;
28552
28630
  const v = emitNode(ctx2, node, tracked2, null);
28553
28631
  if (v) {
28554
28632
  if (ctx2.hydrate) {
28555
- ctx2.push(indent2(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !$root.contains(${v})) $root.appendChild(${v}); }`));
28633
+ ctx2.push(indent2(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !${v}.parentNode.contains(${v})) $root.appendChild(${v}); }`));
28556
28634
  } else {
28557
28635
  ctx2.push(indent2(`$root.appendChild(${v});`));
28558
28636
  }
@@ -28579,7 +28657,13 @@ function buildComponentMap(irRoot, hydrate = false) {
28579
28657
  mapLines.push(`const __components = {};`);
28580
28658
  const asyncComps = computeAsyncComponents(irRoot.components);
28581
28659
  const linkNames = /* @__PURE__ */ new Set();
28660
+ const selfClaimNames = /* @__PURE__ */ new Set();
28661
+ const SELF_CLAIM_RUNTIME = /* @__PURE__ */ new Set(["Link", "NavLink", "Md", "Form", "Field", "LoadingIndicator"]);
28582
28662
  for (const imp of irRoot.imports) {
28663
+ if (importModuleTarget(imp) !== "@vesk/runtime") continue;
28664
+ for (const pair of importBindingPairs(imp)) {
28665
+ if (SELF_CLAIM_RUNTIME.has(pair.imported)) selfClaimNames.add(pair.local);
28666
+ }
28583
28667
  for (const pair of importBindingPairs(imp)) {
28584
28668
  if (pair.imported === "Link" || pair.imported === "NavLink") linkNames.add(pair.local);
28585
28669
  }
@@ -28589,11 +28673,11 @@ function buildComponentMap(irRoot, hydrate = false) {
28589
28673
  const directNames = /* @__PURE__ */ new Set([...extractRuntimeNames(irRoot.imports), ...localValueImportNames(irRoot.imports), ...topValueNames]);
28590
28674
  for (const comp of irRoot.components) {
28591
28675
  if (hydrate && isStaticComponent(comp)) {
28592
- const stub = `(props, __registry, __hydrate) => { return __hydrate.root; }`;
28676
+ const stub = `(props, __registry, __hydrate) => { return __hydrate.claimOnly(); }`;
28593
28677
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
28594
28678
  continue;
28595
28679
  }
28596
- const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames);
28680
+ const code = generateComponent(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames);
28597
28681
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
28598
28682
  }
28599
28683
  mapLines.push(`function __cleanup(start, end) {`);
@@ -28622,6 +28706,32 @@ function buildComponentMap(irRoot, hydrate = false) {
28622
28706
  mapLines.push(`}`);
28623
28707
  return mapLines.join("\n\n");
28624
28708
  }
28709
+ function ssrResidueEstimate(node) {
28710
+ if (Array.isArray(node)) {
28711
+ let total = 0;
28712
+ for (const n of node) total += ssrResidueEstimate(n);
28713
+ return total;
28714
+ }
28715
+ if (node instanceof StaticNode) return 1;
28716
+ if (node instanceof ComponentCall) return 1;
28717
+ if (node instanceof OpaqueDynamicRegion) {
28718
+ return Math.max(ssrResidueEstimate(node.consequentNodes), ssrResidueEstimate(node.alternateNodes));
28719
+ }
28720
+ if (node instanceof MapRegion) {
28721
+ return Math.max(ssrResidueEstimate(node.bodyTemplate), ssrResidueEstimate(node.alternateNodes));
28722
+ }
28723
+ if (node instanceof ForLoop) return ssrResidueEstimate(node.bodyTemplate);
28724
+ if (node instanceof WhileLoop) return ssrResidueEstimate(node.bodyTemplate);
28725
+ if (node instanceof SwitchBlock) {
28726
+ let max = 0;
28727
+ for (const c of node.cases) max = Math.max(max, ssrResidueEstimate(c.body));
28728
+ return max;
28729
+ }
28730
+ if (node instanceof TryCatch) {
28731
+ return Math.max(ssrResidueEstimate(node.bodyTemplate), ssrResidueEstimate(node.catchBody));
28732
+ }
28733
+ return 0;
28734
+ }
28625
28735
  function isStaticIR(body) {
28626
28736
  for (const node of body) {
28627
28737
  if (node instanceof StaticNode) {
@@ -28851,6 +28961,7 @@ var init_client_codegen = __esm({
28851
28961
  init_server_utils();
28852
28962
  init_module_imports();
28853
28963
  init_scan();
28964
+ init_tokens();
28854
28965
  init_md_inline();
28855
28966
  init_strip_ts();
28856
28967
  NON_BUBBLING_EVENTS = /* @__PURE__ */ new Set([
@@ -28892,6 +29003,11 @@ var init_client_codegen = __esm({
28892
29003
  c = 0;
28893
29004
  importedNames = /* @__PURE__ */ new Set();
28894
29005
  linkNames = /* @__PURE__ */ new Set();
29006
+ // Runtime component imports that claim their own SSR root via `nextElement`
29007
+ // (Link/NavLink/Md/Form/Field/LoadingIndicator) and return a DocumentFragment
29008
+ // when claimed. They need no call-site adoption guard — the guard targets
29009
+ // genuinely-plain components that never touch the walker.
29010
+ selfClaimNames = /* @__PURE__ */ new Set();
28895
29011
  delegatedEvents = /* @__PURE__ */ new Set();
28896
29012
  directEvents = /* @__PURE__ */ new Set();
28897
29013
  hydrate = false;
@@ -29280,7 +29396,7 @@ function componentCallToJS(node, importedNames, isAsync = false, tracked2) {
29280
29396
  lines.push(`const ${calleeVar} = ${callee};`);
29281
29397
  const callExpr3 = `${awaitKw}${calleeVar}(${propsObj}, __registry, (${calleeVar}.__veskScope || __vesk))`;
29282
29398
  if (__vskHydrate) {
29283
- lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr3} || '') + '</span>');`);
29399
+ lines.push(`__out.push(${JSON.stringify(HYDRATE_COMPONENT_WRAPPER)} + (${callExpr3} || ''));`);
29284
29400
  } else {
29285
29401
  lines.push(`__out.push(${callExpr3} || '');`);
29286
29402
  }
@@ -29384,7 +29500,7 @@ var init_server_jsgen = __esm({
29384
29500
  init_scan();
29385
29501
  init_server_utils();
29386
29502
  init_module_imports();
29387
- HYDRATE_COMPONENT_WRAPPER = '<!--vsk--><span style="display:contents">';
29503
+ HYDRATE_COMPONENT_WRAPPER = "<!--vsk-->";
29388
29504
  __currentCompName = "";
29389
29505
  }
29390
29506
  });
@@ -40831,6 +40947,7 @@ init_actions();
40831
40947
  init_server_utils();
40832
40948
  init_module_imports();
40833
40949
  init_scan();
40950
+ init_tokens();
40834
40951
  init_md_inline();
40835
40952
  init_strip_ts();
40836
40953
  import { walk as walk6 } from "zimmerframe";
@@ -41052,6 +41169,11 @@ var Ctx2 = class {
41052
41169
  c = 0;
41053
41170
  importedNames = /* @__PURE__ */ new Set();
41054
41171
  linkNames = /* @__PURE__ */ new Set();
41172
+ // Runtime component imports that claim their own SSR root via `nextElement`
41173
+ // (Link/NavLink/Md/Form/Field/LoadingIndicator) and return a DocumentFragment
41174
+ // when claimed. They need no call-site adoption guard — the guard targets
41175
+ // genuinely-plain components that never touch the walker.
41176
+ selfClaimNames = /* @__PURE__ */ new Set();
41055
41177
  delegatedEvents = /* @__PURE__ */ new Set();
41056
41178
  directEvents = /* @__PURE__ */ new Set();
41057
41179
  hydrate = false;
@@ -41209,15 +41331,21 @@ function emitStatic2(ctx2, node, tracked2, effectsVar) {
41209
41331
  ctx2.push(`${el}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(escapeHtml5(attr.value))});`);
41210
41332
  }
41211
41333
  }
41334
+ let residueBefore = 0;
41212
41335
  for (const child of children) {
41213
41336
  const childVar = emitNode2(ctx2, child, tracked2, effectsVar, el);
41214
41337
  if (childVar) {
41215
41338
  if (ctx2.hydrate) {
41216
- ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
41339
+ if (child instanceof TextNode || child instanceof DynamicBinding) {
41340
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { const __ssr = ${el}.__vsk_ssrEls || []; if (${residueBefore} < __ssr.length) ${el}.insertBefore(${childVar}, __ssr[${residueBefore}]); else ${el}.appendChild(${childVar}); }`);
41341
+ } else {
41342
+ ctx2.push(`if (${childVar}.parentNode !== ${el}) { if (!${el} || ${childVar}.parentNode == null || !${el}.contains(${childVar})) ${el}.appendChild(${childVar}); }`);
41343
+ }
41217
41344
  } else {
41218
41345
  ctx2.push(`${el}.appendChild(${childVar});`);
41219
41346
  }
41220
41347
  }
41348
+ residueBefore += ssrResidueEstimate2(child);
41221
41349
  }
41222
41350
  for (const attr of dynAttrs) {
41223
41351
  const target = attr.target || "";
@@ -41408,23 +41536,30 @@ function emitComponentCall2(ctx2, node, tracked2, effectsVar, parentVar, compPre
41408
41536
  }
41409
41537
  const propsObj = `{ ${propsEntries.join(", ")} }`;
41410
41538
  const v = ctx2.n();
41411
- const awaitKw = ctx2.asyncComps.has(node.componentName) ? "await " : "";
41539
+ const awaitKw = ctx2.isAsyncScope ? "await " : "";
41412
41540
  const calleeExpr = node.calleeExpr ? `(${node.calleeExpr})` : null;
41413
41541
  if (ctx2.hydrate) {
41414
41542
  const access = calleeExpr ?? (ctx2.importedNames.has(node.componentName) ? node.componentName : `__components[${JSON.stringify(node.componentName)}]`);
41415
- const subScope = () => ctx2.inTryBody ? `${ctx2.walker}.subWalker(${parentVar})` : `${ctx2.walker}.subWalker(${ctx2.walker}.nextElement())`;
41543
+ const plainTarget = !!(calleeExpr || ctx2.importedNames.has(node.componentName)) && !ctx2.selfClaimNames.has(node.componentName);
41544
+ const walkerArg = ctx2.walker;
41545
+ const maybeReplace = (v2) => {
41546
+ if (!plainTarget)
41547
+ return;
41548
+ ctx2.push(`if (${v2} && ${v2}.parentNode == null && ${v2}.nodeType !== 11) { const __sr = ${walkerArg}.claimOnly(); if (__sr && __sr.parentNode) __sr.parentNode.replaceChild(${v2}, __sr); }`);
41549
+ };
41550
+ const maybeRetire = () => {
41551
+ if (ctx2.selfClaimNames.has(node.componentName)) {
41552
+ ctx2.push(`${walkerArg}.retireDetached();`);
41553
+ }
41554
+ };
41416
41555
  if (node.children.length > 0) {
41417
- const walkerVar = ctx2.n();
41418
- ctx2.push(`const ${walkerVar} = ${subScope()};`);
41419
41556
  const frag = ctx2.n();
41420
41557
  ctx2.push(`const ${frag} = (() => { const $f = document.createDocumentFragment();`);
41421
- const savedWalker = ctx2.walker;
41422
41558
  const savedEffects = ctx2.effects;
41423
41559
  const wipeMount = ctx2.linkNames.has(node.componentName);
41424
41560
  const savedHydrate = ctx2.hydrate;
41425
41561
  if (wipeMount)
41426
41562
  ctx2.hydrate = false;
41427
- ctx2.walker = walkerVar;
41428
41563
  ctx2.effects = [];
41429
41564
  for (const child of node.children) {
41430
41565
  const childVar = emitNode2(ctx2, child, tracked2, effectsVar, "$f");
@@ -41434,14 +41569,17 @@ function emitComponentCall2(ctx2, node, tracked2, effectsVar, parentVar, compPre
41434
41569
  for (const eff of ctx2.effects)
41435
41570
  ctx2.push(effectsVar ? `${effectsVar}.push(${effectBlockToHandlerExpr2(eff)});` : eff);
41436
41571
  ctx2.effects = savedEffects;
41437
- ctx2.walker = savedWalker;
41438
41572
  ctx2.hydrate = savedHydrate;
41439
41573
  ctx2.push(`return $f; })();`);
41440
41574
  propsEntries.push(`children: ${frag}`);
41441
- ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerVar});`);
41575
+ ctx2.push(`const ${v} = ${awaitKw}${access}({ ${propsEntries.join(", ")} }, __registry, ${walkerArg});`);
41576
+ maybeReplace(v);
41577
+ maybeRetire();
41442
41578
  return v;
41443
41579
  }
41444
- ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${subScope()});`);
41580
+ ctx2.push(`const ${v} = ${awaitKw}${access}(${propsObj}, __registry, ${walkerArg});`);
41581
+ maybeReplace(v);
41582
+ maybeRetire();
41445
41583
  return v;
41446
41584
  } else {
41447
41585
  if (node.children.length > 0) {
@@ -42255,11 +42393,12 @@ ${renderAllItems(" ", null)}
42255
42393
  function computeAsyncComponents2(comps) {
42256
42394
  return new Set(comps.filter((c) => c.isAsync).map((c) => c.name));
42257
42395
  }
42258
- function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set()) {
42396
+ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hydrate = false, asyncComps = /* @__PURE__ */ new Set(), linkNames = /* @__PURE__ */ new Set(), selfClaimNames = /* @__PURE__ */ new Set()) {
42259
42397
  const tracked2 = collectTrackedNames2(comp.body);
42260
42398
  const ctx2 = new Ctx2();
42261
42399
  ctx2.importedNames = importedNames;
42262
42400
  ctx2.linkNames = linkNames;
42401
+ ctx2.selfClaimNames = selfClaimNames;
42263
42402
  ctx2.hydrate = hydrate;
42264
42403
  ctx2.asyncComps = asyncComps;
42265
42404
  ctx2.isAsyncScope = comp.isAsync || asyncComps.has(comp.name);
@@ -42284,10 +42423,17 @@ function generateComponent2(comp, importedNames = /* @__PURE__ */ new Set(), hyd
42284
42423
  if (paramInit)
42285
42424
  ctx2.push(indent3(paramInit));
42286
42425
  for (const node of comp.body) {
42426
+ if (node instanceof TrackDecl) {
42427
+ emitNode2(ctx2, node, tracked2, null);
42428
+ }
42429
+ }
42430
+ for (const node of comp.body) {
42431
+ if (node instanceof TrackDecl)
42432
+ continue;
42287
42433
  const v = emitNode2(ctx2, node, tracked2, null);
42288
42434
  if (v) {
42289
42435
  if (ctx2.hydrate) {
42290
- ctx2.push(indent3(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !$root.contains(${v})) $root.appendChild(${v}); }`));
42436
+ ctx2.push(indent3(`if (${v}.parentNode !== $root) { if (!$root || ${v}.parentNode == null || !${v}.parentNode.contains(${v})) $root.appendChild(${v}); }`));
42291
42437
  } else {
42292
42438
  ctx2.push(indent3(`$root.appendChild(${v});`));
42293
42439
  }
@@ -42318,7 +42464,15 @@ function buildComponentMap3(irRoot, hydrate = false) {
42318
42464
  mapLines.push(`const __components = {};`);
42319
42465
  const asyncComps = computeAsyncComponents2(irRoot.components);
42320
42466
  const linkNames = /* @__PURE__ */ new Set();
42467
+ const selfClaimNames = /* @__PURE__ */ new Set();
42468
+ const SELF_CLAIM_RUNTIME = /* @__PURE__ */ new Set(["Link", "NavLink", "Md", "Form", "Field", "LoadingIndicator"]);
42321
42469
  for (const imp of irRoot.imports) {
42470
+ if (importModuleTarget(imp) !== "@vesk/runtime")
42471
+ continue;
42472
+ for (const pair of importBindingPairs(imp)) {
42473
+ if (SELF_CLAIM_RUNTIME.has(pair.imported))
42474
+ selfClaimNames.add(pair.local);
42475
+ }
42322
42476
  for (const pair of importBindingPairs(imp)) {
42323
42477
  if (pair.imported === "Link" || pair.imported === "NavLink")
42324
42478
  linkNames.add(pair.local);
@@ -42329,11 +42483,11 @@ function buildComponentMap3(irRoot, hydrate = false) {
42329
42483
  const directNames = /* @__PURE__ */ new Set([...extractRuntimeNames(irRoot.imports), ...localValueImportNames(irRoot.imports), ...topValueNames]);
42330
42484
  for (const comp of irRoot.components) {
42331
42485
  if (hydrate && isStaticComponent2(comp)) {
42332
- const stub = `(props, __registry, __hydrate) => { return __hydrate.root; }`;
42486
+ const stub = `(props, __registry, __hydrate) => { return __hydrate.claimOnly(); }`;
42333
42487
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${stub};`);
42334
42488
  continue;
42335
42489
  }
42336
- const code = generateComponent2(comp, directNames, hydrate, asyncComps, linkNames);
42490
+ const code = generateComponent2(comp, directNames, hydrate, asyncComps, linkNames, selfClaimNames);
42337
42491
  mapLines.push(`__components[${JSON.stringify(comp.name)}] = ${code};`);
42338
42492
  }
42339
42493
  mapLines.push(`function __cleanup(start, end) {`);
@@ -42362,6 +42516,38 @@ function buildComponentMap3(irRoot, hydrate = false) {
42362
42516
  mapLines.push(`}`);
42363
42517
  return mapLines.join("\n\n");
42364
42518
  }
42519
+ function ssrResidueEstimate2(node) {
42520
+ if (Array.isArray(node)) {
42521
+ let total = 0;
42522
+ for (const n of node)
42523
+ total += ssrResidueEstimate2(n);
42524
+ return total;
42525
+ }
42526
+ if (node instanceof StaticNode)
42527
+ return 1;
42528
+ if (node instanceof ComponentCall)
42529
+ return 1;
42530
+ if (node instanceof OpaqueDynamicRegion) {
42531
+ return Math.max(ssrResidueEstimate2(node.consequentNodes), ssrResidueEstimate2(node.alternateNodes));
42532
+ }
42533
+ if (node instanceof MapRegion) {
42534
+ return Math.max(ssrResidueEstimate2(node.bodyTemplate), ssrResidueEstimate2(node.alternateNodes));
42535
+ }
42536
+ if (node instanceof ForLoop)
42537
+ return ssrResidueEstimate2(node.bodyTemplate);
42538
+ if (node instanceof WhileLoop)
42539
+ return ssrResidueEstimate2(node.bodyTemplate);
42540
+ if (node instanceof SwitchBlock) {
42541
+ let max = 0;
42542
+ for (const c of node.cases)
42543
+ max = Math.max(max, ssrResidueEstimate2(c.body));
42544
+ return max;
42545
+ }
42546
+ if (node instanceof TryCatch) {
42547
+ return Math.max(ssrResidueEstimate2(node.bodyTemplate), ssrResidueEstimate2(node.catchBody));
42548
+ }
42549
+ return 0;
42550
+ }
42365
42551
  function isStaticIR2(body) {
42366
42552
  for (const node of body) {
42367
42553
  if (node instanceof StaticNode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/vesk-cli",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "type": "module",
5
5
  "description": "Vesk — compiler-first framework for the post-VDOM web. vesk dev, vesk build.",
6
6
  "bin": {
@@ -8,10 +8,10 @@
8
8
  },
9
9
  "main": "./dist/cli.js",
10
10
  "dependencies": {
11
- "@vesk/adapter": "^0.2.24",
12
- "@vesk/compiler": "^0.2.24",
13
- "@vesk/plugin-tailwind": "^0.2.24",
14
- "@vesk/runtime": "^0.2.24",
11
+ "@vesk/adapter": "^0.2.26",
12
+ "@vesk/compiler": "^0.2.26",
13
+ "@vesk/plugin-tailwind": "^0.2.26",
14
+ "@vesk/runtime": "^0.2.26",
15
15
  "esrap": "^2.3.0",
16
16
  "typescript": "^5.8.0",
17
17
  "ws": "^8.18.0",