@barefootjs/cli 0.27.0 → 0.28.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 (2) hide show
  1. package/dist/index.js +664 -38
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -11161,7 +11161,8 @@ function transformConditionalBranch(node, ctx2) {
11161
11161
  const callsReactive = exprCallsReactiveGetters(node, ctx2);
11162
11162
  const hasCalls = exprHasFunctionCalls(node);
11163
11163
  const reactive = isReactiveExpression(exprText, ctx2, node) || isReactiveOrigin(branchOrigin);
11164
- const needsSlot = reactive || callsReactive;
11164
+ const refsLoopParam = branchOrigin.freeRefs?.some((r2) => r2.kind === "render-item") ?? false;
11165
+ const needsSlot = reactive || callsReactive || refsLoopParam;
11165
11166
  const slotId = needsSlot ? generateSlotId(ctx2) : null;
11166
11167
  return {
11167
11168
  type: "expression",
@@ -14761,19 +14762,54 @@ function summarizeLoopChildBranch(node, ctx2, siblingOffsets, loopParam, loopPar
14761
14762
  // on the branch root yields exactly this branch's direct bindings
14762
14763
  // without re-collecting what a nested arm already owns (#2347).
14763
14764
  reactiveAttrs: collectLoopChildReactiveAttrs(node, ctx2, loopParam, loopParamBindings, true),
14764
- // Skip when the branch's ENTIRE content is a single bare `expression`
14765
- // (no wrapping element) e.g. a hoisted `renderNode={(n) => <Pill/>}`
14766
- // callback (#1211/#1213). That value is already fully re-evaluated and
14767
- // spliced via `__bfSlot` whenever `insert()` (re-)mounts this branch;
14768
- // an *additional* nested createEffect for the same expression re-calls
14769
- // it and creates a second, independent live element (a JSX-callback
14770
- // result isn't idempotent to re-invoke the way a plain signal read is),
14771
- // and the loop-child arm's `$t()`-based anchor lookup designed for
14772
- // text nodes doesn't cleanly displace an already-mounted Element,
14773
- // so the second instance lands beside the first instead of replacing
14774
- // it. A text *nested inside* a static wrapper element in the branch
14775
- // (the element-descent case) is unaffected and still collected below.
14776
- reactiveTexts: node.type === "expression" ? [] : collectLoopChildReactiveTexts(node, ctx2, loopParam, loopParamBindings, true)
14765
+ // Skip ONLY when the branch's entire content is a single bare
14766
+ // `expression` (no wrapping element) that MAY yield a live DOM node
14767
+ // i.e. it contains a call anywhere (`node.hasFunctionCalls`, computed
14768
+ // by the AST walk in `exprHasFunctionCalls`/jsx-to-ir.ts, recursively
14769
+ // catches a call nested in a template literal or a nested ternary, not
14770
+ // just a top-level call). A call can return an Element (a hoisted
14771
+ // `renderNode={(n) => <Pill/>}` callback lowered to a component call,
14772
+ // #1211/#1213) and the compiler cannot tell from syntax whether it does
14773
+ // — both `renderChild(...)` and `_p.renderCell(...)` are a
14774
+ // `CallExpression` so this stays conservative for ANY call, not just
14775
+ // ones known to return JSX. Re-evaluating a non-idempotent call inside
14776
+ // an *additional* nested createEffect (on top of the eval already done
14777
+ // for the `__bfSlot`-wrapped splice whenever `insert()` (re-)mounts this
14778
+ // branch) would call it again on every unrelated tick and discard the
14779
+ // previous element's listeners/state.
14780
+ //
14781
+ // Everything else — a property access (`row.label`), an identifier, a
14782
+ // literal, a template literal, string concatenation, or a nested
14783
+ // ternary of those — cannot itself construct a DOM node (only a JSX
14784
+ // literal or a call can, and a JSX literal branch is never `type:
14785
+ // 'expression'` in the first place — see `transformJsxExpression`), so
14786
+ // it is safe to collect. This is the fix for the loop-branch-stale-text
14787
+ // defect: a keyed loop row whose branch is e.g. `row.done ? row.label :
14788
+ // 'pending'` previously had NO update path at all when `row.label`
14789
+ // changed without `row.done` flipping — `insert()` (runtime/insert.ts)
14790
+ // correctly no-ops when its condition is unchanged (branch-internal
14791
+ // updates are deliberately the effect system's job, not DOM
14792
+ // replacement's), but with reactiveTexts always `[]` for this shape,
14793
+ // no effect existed either, so the branch was frozen at its mount-time
14794
+ // value. The stale rationale in a prior version of this comment blamed
14795
+ // "the loop-child arm's `$t()`-based anchor lookup", but `$t()` no
14796
+ // longer exists — it was one of four content mechanisms deleted by the
14797
+ // slot-unification work. The current claim door
14798
+ // (`stringifyLoopChildArm` → `lazySlots(..., kind: 'markup')` →
14799
+ // `writeMarkup`, runtime/claim-slots.ts) clears the claimed range and
14800
+ // splices the Node by identity, so "lands beside the first" cannot
14801
+ // happen through it; only the call's non-idempotence still applies, and
14802
+ // that alone is why the skip narrows rather than disappears.
14803
+ //
14804
+ // Collecting here is necessary but not sufficient: `n.slotId` must also
14805
+ // be set for `collectLoopChildReactiveTexts` (ir-to-client-js/
14806
+ // reactivity.ts) to do anything — see `transformConditionalBranch`'s
14807
+ // `refsLoopParam` check (jsx-to-ir.ts) for the matching half of this fix
14808
+ // that gives a bare loop-item-reading branch a slotId at all, which also
14809
+ // makes `irToHtmlTemplate` emit its `<!--bf:sN-->…<!--/-->` marker (the
14810
+ // same call builds both the SSR and the CSR/hydration template, so the
14811
+ // two can't disagree on shape).
14812
+ reactiveTexts: node.type === "expression" && node.hasFunctionCalls ? [] : collectLoopChildReactiveTexts(node, ctx2, loopParam, loopParamBindings, true)
14777
14813
  };
14778
14814
  }
14779
14815
  var EMPTY_RENDER_EXPRS, branchInnerLoopOptions;
@@ -15339,6 +15375,7 @@ var init_imports = __esm({
15339
15375
  "getLoopNodes",
15340
15376
  "mapArray",
15341
15377
  "mapArrayAnchored",
15378
+ "mapArrayLazy",
15342
15379
  "patchLeaf",
15343
15380
  "createDisposableEffect",
15344
15381
  "createComponent",
@@ -15370,8 +15407,16 @@ var init_imports = __esm({
15370
15407
  // Claim-plan interpreter (slot unification A2/A3, spec/slot-unification.md)
15371
15408
  // — the "one claim mechanism" that replaced `patchSlotRange` and
15372
15409
  // `updateClientMarker` (both deleted) as the content-slot update door.
15410
+ // `lazyClaimSlots` is the read-capable twin of `lazySlots` over the same
15411
+ // claim — emitted only by lazy loops that seed an outer-involving TEXT
15412
+ // binding by read-compare-write (§9.3(1)).
15413
+ // `textOrNode` is the 'text' door's Node guard: a child-position value that
15414
+ // turns out to be a live Node must reach the writer as a Node so the claim
15415
+ // can promote to 'markup', never as `String(node)`.
15373
15416
  "claimSlots",
15374
15417
  "lazySlots",
15418
+ "lazyClaimSlots",
15419
+ "textOrNode",
15375
15420
  // Profile mode (#1690, SR3) — turn-boundary markers around event handlers.
15376
15421
  "beginTurn",
15377
15422
  "endTurn",
@@ -18494,8 +18539,340 @@ var init_build_event_delegation = __esm({
18494
18539
  }
18495
18540
  });
18496
18541
 
18542
+ // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts
18543
+ function lazyRowEligibility(args2) {
18544
+ const { shape, bindings, arraySourceIdentifiers, scope } = args2;
18545
+ if (scope.profile) return NO("profile mode keeps the granular eager emission");
18546
+ if (shape.callSite !== "plain" && shape.callSite !== "branch-plain") {
18547
+ return NO(`call site '${shape.callSite}' is not a plain loop row`);
18548
+ }
18549
+ if (shape.flatMapLeafItem) return NO("flatMap descriptor loop (build-or-patch renderItem)");
18550
+ if (shape.anchored) return NO("anchored whole-item-conditional loop");
18551
+ if (shape.bodyIsMultiRoot) return NO("multi-root (Fragment) row");
18552
+ if (!shape.hasExplicitKey) return NO("index-keyed loop (no explicit key)");
18553
+ if (shape.conditionalCount > 0) return NO("row contains a reactive conditional");
18554
+ if (shape.childRefCount > 0) return NO("row has imperative child refs");
18555
+ if (shape.hasChildComponent) return NO("row body is a child component");
18556
+ if (shape.nestedComponentCount > 0) return NO("row contains nested child components");
18557
+ if (shape.innerLoopCount > 0) return NO("row contains an inner loop");
18558
+ if (shape.hasMapPreamble) return NO("row has a map-callback preamble (may declare row-local reactivity)");
18559
+ if (shape.preambleRegionCount > 0) return NO("row has preamble-patched regions");
18560
+ if (shape.hasParamUnwrap) return NO("destructured loop param without param bindings");
18561
+ for (const b of bindings) {
18562
+ if (b.referencesIndex) {
18563
+ return NO(`binding on slot ${b.slotId} references the loop index parameter`);
18564
+ }
18565
+ if (b.opaqueOuterNames.includes(UNKNOWN_IDENTIFIERS)) {
18566
+ return NO(`binding on slot ${b.slotId} has no analyzable identifier set`);
18567
+ }
18568
+ }
18569
+ if (!arraySourceIdentifiers) return NO("loop source free identifiers unavailable");
18570
+ const sourceGate = checkSourceConsistency(arraySourceIdentifiers, scope);
18571
+ if (sourceGate) return NO(`loop source is not provably hydration-consistent: ${sourceGate}`);
18572
+ return { eligible: true };
18573
+ }
18574
+ function checkSourceConsistency(names, scope) {
18575
+ const seen = /* @__PURE__ */ new Set();
18576
+ const resolve12 = (name2) => {
18577
+ if (seen.has(name2)) return null;
18578
+ seen.add(name2);
18579
+ if (scope.props.has(name2)) return null;
18580
+ if (PURE_SOURCE_GLOBALS.has(name2)) return null;
18581
+ const constFree = scope.constants.get(name2);
18582
+ if (constFree !== void 0) {
18583
+ if (constFree === null) return `constant '${name2}' has no analyzable value`;
18584
+ for (const inner of constFree) {
18585
+ const failure = resolve12(inner);
18586
+ if (failure) return failure;
18587
+ }
18588
+ return null;
18589
+ }
18590
+ const signal2 = scope.signals.get(name2);
18591
+ if (signal2 !== void 0) {
18592
+ if (signal2.initializerFreeIdentifiers === null) {
18593
+ return `signal '${name2}' has no structured initializer to prove props/literal derivation`;
18594
+ }
18595
+ for (const inner of signal2.initializerFreeIdentifiers) {
18596
+ const failure = resolve12(inner);
18597
+ if (failure) return failure;
18598
+ }
18599
+ return null;
18600
+ }
18601
+ if (scope.memos.has(name2)) return `memo '${name2}' initializer is not analyzed by the v1 gate`;
18602
+ if (scope.inert.has(name2)) return `'${name2}' is an import or local function`;
18603
+ return `'${name2}' does not resolve to a prop, literal-derived const, or props/literal-derived signal`;
18604
+ };
18605
+ for (const name2 of names) {
18606
+ const failure = resolve12(name2);
18607
+ if (failure) return failure;
18608
+ }
18609
+ return null;
18610
+ }
18611
+ function classifyLazyBinding(args2) {
18612
+ const { kind: kind2, slotId, free, rowLocalNames, indexParam, scope } = args2;
18613
+ if (free === null) {
18614
+ return {
18615
+ kind: kind2,
18616
+ slotId,
18617
+ readsItem: true,
18618
+ readsOuter: true,
18619
+ reactiveOuterNames: [],
18620
+ opaqueOuterNames: [UNKNOWN_IDENTIFIERS],
18621
+ // An ASSUMPTION, not knowledge — which is precisely why
18622
+ // `UNKNOWN_IDENTIFIERS` must keep refusing the loop: with no
18623
+ // identifier set we cannot rule out an index read either, and
18624
+ // `applyItem` / `applyOuter` have no index parameter to give it.
18625
+ referencesIndex: false
18626
+ };
18627
+ }
18628
+ let readsItem = false;
18629
+ let referencesIndex = false;
18630
+ const reactiveOuterNames = [];
18631
+ const opaqueOuterNames = [];
18632
+ for (const name2 of free) {
18633
+ if (rowLocalNames.has(name2)) {
18634
+ readsItem = true;
18635
+ continue;
18636
+ }
18637
+ if (name2 === indexParam) {
18638
+ referencesIndex = true;
18639
+ continue;
18640
+ }
18641
+ if (INERT_BINDING_GLOBALS.has(name2)) continue;
18642
+ if (scope.signals.has(name2) || scope.memos.has(name2)) {
18643
+ if (!reactiveOuterNames.includes(name2)) reactiveOuterNames.push(name2);
18644
+ continue;
18645
+ }
18646
+ const constFree = scope.constants.get(name2);
18647
+ if (constFree !== void 0 && constFree !== null && constFree.size === 0) continue;
18648
+ opaqueOuterNames.push(name2);
18649
+ }
18650
+ return {
18651
+ kind: kind2,
18652
+ slotId,
18653
+ readsItem,
18654
+ readsOuter: reactiveOuterNames.length > 0 || opaqueOuterNames.length > 0,
18655
+ reactiveOuterNames,
18656
+ opaqueOuterNames,
18657
+ referencesIndex
18658
+ };
18659
+ }
18660
+ var PURE_SOURCE_GLOBALS, INERT_BINDING_GLOBALS, UNKNOWN_IDENTIFIERS, NO;
18661
+ var init_lazy_row_eligibility = __esm({
18662
+ "../jsx/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts"() {
18663
+ "use strict";
18664
+ PURE_SOURCE_GLOBALS = /* @__PURE__ */ new Set([
18665
+ "Object",
18666
+ "Array",
18667
+ "JSON",
18668
+ "Number",
18669
+ "String",
18670
+ "Boolean"
18671
+ ]);
18672
+ INERT_BINDING_GLOBALS = /* @__PURE__ */ new Set([
18673
+ "Object",
18674
+ "Array",
18675
+ "JSON",
18676
+ "Number",
18677
+ "String",
18678
+ "Boolean",
18679
+ "Math",
18680
+ "Date",
18681
+ "Intl",
18682
+ "Symbol",
18683
+ "Map",
18684
+ "Set",
18685
+ "WeakMap",
18686
+ "WeakSet",
18687
+ "Promise",
18688
+ "RegExp",
18689
+ "Error",
18690
+ "BigInt",
18691
+ "console",
18692
+ "undefined",
18693
+ "NaN",
18694
+ "Infinity",
18695
+ "globalThis",
18696
+ "parseInt",
18697
+ "parseFloat",
18698
+ "isNaN",
18699
+ "isFinite",
18700
+ "encodeURIComponent",
18701
+ "decodeURIComponent",
18702
+ "encodeURI",
18703
+ "decodeURI"
18704
+ ]);
18705
+ UNKNOWN_IDENTIFIERS = "<unknown>";
18706
+ NO = (reason2) => ({ eligible: false, reason: reason2 });
18707
+ }
18708
+ });
18709
+
18710
+ // ../jsx/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts
18711
+ function buildLazyRowPlan(args2) {
18712
+ return decideLazyRow(args2).plan;
18713
+ }
18714
+ function decideLazyRow(args2) {
18715
+ const { loop, scope } = args2;
18716
+ if (!scope) {
18717
+ return { plan: null, decision: { eligible: false, reason: "no component scope info supplied" } };
18718
+ }
18719
+ const wrap = (expr) => wrapLoopParamAsAccessor(expr, loop.param, loop.paramBindings);
18720
+ const rowLocalNames = /* @__PURE__ */ new Set([loop.param]);
18721
+ for (const b of loop.paramBindings ?? []) rowLocalNames.add(b.name);
18722
+ const classified = [];
18723
+ const attrClass = /* @__PURE__ */ new Map();
18724
+ loop.bindings.reactiveAttrs.forEach((attr, i) => {
18725
+ const c = classifyLazyBinding({
18726
+ kind: "attr",
18727
+ slotId: attr.childSlotId,
18728
+ free: attrFreeIdentifiers2(attr.expression),
18729
+ rowLocalNames,
18730
+ indexParam: args2.indexParam,
18731
+ scope
18732
+ });
18733
+ attrClass.set(i, c);
18734
+ classified.push(c);
18735
+ });
18736
+ const textClass = /* @__PURE__ */ new Map();
18737
+ loop.bindings.reactiveTexts.forEach((text, i) => {
18738
+ const c = classifyLazyBinding({
18739
+ kind: "text",
18740
+ slotId: text.slotId,
18741
+ free: text.freeIdentifiers ?? null,
18742
+ rowLocalNames,
18743
+ indexParam: args2.indexParam,
18744
+ scope
18745
+ });
18746
+ textClass.set(i, c);
18747
+ classified.push(c);
18748
+ });
18749
+ const shape = {
18750
+ callSite: args2.callSite,
18751
+ flatMapLeafItem: args2.flatMapLeafItem,
18752
+ anchored: args2.anchored,
18753
+ bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18754
+ hasExplicitKey: loop.key != null,
18755
+ conditionalCount: loop.bindings.conditionals?.length ?? 0,
18756
+ childRefCount: loop.bindings.refs?.length ?? 0,
18757
+ nestedComponentCount: loop.nestedComponents?.length ?? 0,
18758
+ innerLoopCount: loop.innerLoops?.length ?? 0,
18759
+ hasChildComponent: "childComponent" in loop && loop.childComponent != null,
18760
+ hasMapPreamble: args2.mapPreambleWrapped.length > 0,
18761
+ preambleRegionCount: args2.preambleRegionCount,
18762
+ hasParamUnwrap: args2.paramUnwrap.length > 0
18763
+ };
18764
+ const decision = lazyRowEligibility({
18765
+ shape,
18766
+ bindings: classified,
18767
+ arraySourceIdentifiers: loopSourceIdentifiers(loop, args2.arrayExpr),
18768
+ scope
18769
+ });
18770
+ if (!decision.eligible) return { plan: null, decision };
18771
+ const attrSlotIds = [];
18772
+ for (const attr of loop.bindings.reactiveAttrs) {
18773
+ if (!attrSlotIds.includes(attr.childSlotId)) attrSlotIds.push(attr.childSlotId);
18774
+ }
18775
+ let ordinal = 0;
18776
+ const attrs = loop.bindings.reactiveAttrs.map((attr, i) => {
18777
+ const c = attrClass.get(i);
18778
+ return {
18779
+ slotId: attr.childSlotId,
18780
+ attrName: attr.attrName,
18781
+ wrappedExpression: wrap(attr.expression),
18782
+ meta: pickAttrMeta(attr),
18783
+ refIndex: attrSlotIds.indexOf(attr.childSlotId),
18784
+ ordinal: ordinal++,
18785
+ readsItem: c.readsItem,
18786
+ readsOuter: c.readsOuter
18787
+ };
18788
+ });
18789
+ const texts = loop.bindings.reactiveTexts.map((text, i) => {
18790
+ const c = textClass.get(i);
18791
+ return {
18792
+ slotId: text.slotId,
18793
+ wrappedExpression: wrap(text.expression),
18794
+ ordinal: ordinal++,
18795
+ // A text that classified as NEITHER item- nor outer-driven still gets
18796
+ // applied on item change (harmless, dedup-guarded) rather than
18797
+ // silently never being written. Anything the classifier did place in
18798
+ // a list keeps exactly the classifier's answer.
18799
+ readsItem: c.readsItem || !c.readsOuter,
18800
+ readsOuter: c.readsOuter
18801
+ };
18802
+ });
18803
+ const outerPrimeGetters = [];
18804
+ for (const c of classified) {
18805
+ for (const name2 of c.reactiveOuterNames) {
18806
+ if (!outerPrimeGetters.includes(name2)) outerPrimeGetters.push(name2);
18807
+ }
18808
+ }
18809
+ return {
18810
+ plan: {
18811
+ attrSlotIds,
18812
+ attrs,
18813
+ texts,
18814
+ writerIndex: texts.length > 0 ? attrSlotIds.length : -1,
18815
+ textNeedsRead: texts.some((t) => t.readsOuter),
18816
+ lastCount: ordinal,
18817
+ outerPrimeGetters,
18818
+ hasOuter: attrs.some((a) => a.readsOuter) || texts.some((t) => t.readsOuter)
18819
+ },
18820
+ decision
18821
+ };
18822
+ }
18823
+ function buildLazyRowScopeInfo(ctx2) {
18824
+ const signals = /* @__PURE__ */ new Map();
18825
+ for (const s of ctx2.signals) {
18826
+ signals.set(s.getter, {
18827
+ // `parsed` is the analyzer's structured initializer. Absent (or
18828
+ // refused by `freeIdentifiers`) ⇒ unprovable, which the source gate
18829
+ // treats as a hard stop rather than an assumption.
18830
+ initializerFreeIdentifiers: s.parsed ? freeIdentifiers(s.parsed) : null
18831
+ });
18832
+ }
18833
+ const memos = new Set(ctx2.memos.map((m) => m.name));
18834
+ const props = /* @__PURE__ */ new Set([PROPS_PARAM]);
18835
+ if (ctx2.propsObjectName) props.add(ctx2.propsObjectName);
18836
+ for (const p of ctx2.propsParams) props.add(p.name);
18837
+ const constants = /* @__PURE__ */ new Map();
18838
+ for (const c of ctx2.localConstants) {
18839
+ constants.set(c.name, c.freeIdentifiers ?? null);
18840
+ }
18841
+ const inert = /* @__PURE__ */ new Set();
18842
+ for (const f of ctx2.localFunctions) inert.add(f.name);
18843
+ for (const imp of ctx2.imports) {
18844
+ if (imp.isTypeOnly) continue;
18845
+ for (const spec of imp.specifiers) inert.add(spec.alias || spec.name);
18846
+ }
18847
+ return { signals, memos, props, constants, inert, profile: ctx2.profile };
18848
+ }
18849
+ function attrFreeIdentifiers2(expression) {
18850
+ if (!expression || expression.trim().length === 0) return /* @__PURE__ */ new Set();
18851
+ try {
18852
+ return freeIdentifiers(parseExpression(expression));
18853
+ } catch {
18854
+ return null;
18855
+ }
18856
+ }
18857
+ function loopSourceIdentifiers(loop, arrayExpr) {
18858
+ if (!loop.arrayFreeIdentifiers) return null;
18859
+ const names = new Set(loop.arrayFreeIdentifiers);
18860
+ for (const name2 of extractFreeIdentifiersFromText(arrayExpr)) names.add(name2);
18861
+ return names;
18862
+ }
18863
+ var init_build_lazy_row = __esm({
18864
+ "../jsx/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts"() {
18865
+ "use strict";
18866
+ init_expression_parser();
18867
+ init_types();
18868
+ init_csr_substitute();
18869
+ init_utils();
18870
+ init_lazy_row_eligibility();
18871
+ }
18872
+ });
18873
+
18497
18874
  // ../jsx/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts
18498
- function buildBranchLoopPlan(loop, profileComponentName) {
18875
+ function buildBranchLoopPlan(loop, profileComponentName, lazyScope) {
18499
18876
  const containerSlotId = loop.containerSlotId;
18500
18877
  const cv = varSlotId(containerSlotId);
18501
18878
  const containerVar = `__loop_${cv}`;
@@ -18511,6 +18888,13 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18511
18888
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings);
18512
18889
  const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0 || loop.bindings.reactiveTexts.length > 0 || loop.bindings.conditionals.length > 0;
18513
18890
  const fm = loop.flatMapClient;
18891
+ const arrayExpr = fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop);
18892
+ const indexParam = loop.index || "__idx";
18893
+ const mapPreambleWrapped = loop.preamble ? renderPreamble(loop.preamble, {
18894
+ transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
18895
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0, true)
18896
+ }) : "";
18897
+ const preambleRegions = buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings);
18514
18898
  const plan = {
18515
18899
  kind: "plain",
18516
18900
  rowConstruction: "string-template",
@@ -18518,17 +18902,27 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18518
18902
  containerVar,
18519
18903
  markerId: loop.markerId,
18520
18904
  flatMapLeafItem: fm ? true : void 0,
18521
- arrayExpr: fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop),
18905
+ arrayExpr,
18522
18906
  keyFn: fm ? fm.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null" : loopKeyFn(loop),
18523
18907
  paramHead,
18524
18908
  paramUnwrap,
18525
- indexParam: loop.index || "__idx",
18909
+ indexParam,
18526
18910
  // Wrap loop-param references to signal-accessor form so the preamble
18527
18911
  // matches the template literal's already-wrapped reads (#1065).
18528
- mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
18529
- transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
18530
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0, true)
18531
- }) : "",
18912
+ mapPreambleWrapped,
18913
+ // Lazy row graph (§9, L3) — undefined for every ineligible loop.
18914
+ lazyRow: buildLazyRowPlan({
18915
+ loop,
18916
+ arrayExpr,
18917
+ indexParam,
18918
+ paramUnwrap,
18919
+ mapPreambleWrapped,
18920
+ preambleRegionCount: preambleRegions.length,
18921
+ callSite: "branch-plain",
18922
+ flatMapLeafItem: Boolean(fm),
18923
+ anchored: false,
18924
+ scope: lazyScope
18925
+ }) ?? void 0,
18532
18926
  template: loop.template,
18533
18927
  reactiveEffects: hasReactiveEffects ? buildReactiveEffectsPlan({
18534
18928
  attrs: loop.bindings.reactiveAttrs,
@@ -18540,7 +18934,7 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18540
18934
  }) : null,
18541
18935
  eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
18542
18936
  childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
18543
- preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
18937
+ preambleRegions,
18544
18938
  bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18545
18939
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : void 0
18546
18940
  };
@@ -18553,6 +18947,7 @@ var init_build_branch_loop = __esm({
18553
18947
  init_build_composite_loop();
18554
18948
  init_build_event_delegation();
18555
18949
  init_build_reactive_effects();
18950
+ init_build_lazy_row();
18556
18951
  init_shared();
18557
18952
  init_html_template();
18558
18953
  }
@@ -18609,13 +19004,13 @@ function buildArmBody(branch, options2) {
18609
19004
  expression: t.expression
18610
19005
  })),
18611
19006
  // Branch-scoped loops, fully Plan-built (Item 2 final migration).
18612
- loops: branch.loops.map((l) => buildBranchLoopPlan(l, pc)),
19007
+ loops: branch.loops.map((l) => buildBranchLoopPlan(l, pc, options2.lazyScope)),
18613
19008
  // Nested conditionals are themselves InsertPlans — built recursively so
18614
19009
  // the same stringifier handles arbitrary depth. Their scope is always
18615
19010
  // `__branchScope` (the parent arm's bindEvents argument), regardless of
18616
19011
  // the outer scope; only the eventNameMode is inherited.
18617
19012
  conditionals: branch.conditionals.map(
18618
- (c) => buildInsertPlan(c, { scope: { kind: "branchScope" }, eventNameMode: options2.eventNameMode, profileComponentName: pc })
19013
+ (c) => buildInsertPlan(c, { scope: { kind: "branchScope" }, eventNameMode: options2.eventNameMode, profileComponentName: pc, lazyScope: options2.lazyScope })
18619
19014
  )
18620
19015
  };
18621
19016
  }
@@ -19008,7 +19403,7 @@ function stringifyBranchEventBindings(lines, plan, indent) {
19008
19403
  }
19009
19404
  function stringifyBranchChildComponentInits(lines, plan, indent) {
19010
19405
  for (const init of plan) {
19011
- lines.push(`${indent}{ let __c = qsa(__branchScope, ${init.selector}); if (!__c) { const __ph = __branchScope.querySelector('[${BF_PLACEHOLDER}="${init.placeholderId}"]'); if (__ph) { __c = createComponent('${nameForRegistryRef(init.name)}', ${init.propsExpr}); __ph.replaceWith(__c) } } if (__c) initChild('${nameForRegistryRef(init.name)}', __c, ${init.propsExpr}) }`);
19406
+ lines.push(`${indent}{ let __c = qsa(__branchScope, ${init.selector}); if (!__c) { const __ph = __branchScope.querySelector('[${BF_PLACEHOLDER}="${init.placeholderId}"]'); if (__ph) { __c = createComponent('${nameForRegistryRef(init.name)}', ${init.propsExpr}, undefined, undefined, __ph) } } if (__c) initChild('${nameForRegistryRef(init.name)}', __c, ${init.propsExpr}) }`);
19012
19407
  }
19013
19408
  }
19014
19409
  function stringifyBranchInnerLoops(lines, plan, indent, pc) {
@@ -19356,6 +19751,179 @@ var init_component_loop = __esm({
19356
19751
  }
19357
19752
  });
19358
19753
 
19754
+ // ../jsx/src/ir-to-client-js/control-flow/stringify/lazy-row.ts
19755
+ function stringifyLazyRowLoop(lines, o) {
19756
+ const { indent, lazyRow, paramHead } = o;
19757
+ const mid = o.markerId.replace(/[^A-Za-z0-9_$]/g, "_");
19758
+ const tplVar = `__tpl_${mid}`;
19759
+ const claimVar = `__lzc_${mid}`;
19760
+ const hasRefs = lazyRow.attrSlotIds.length > 0 || lazyRow.texts.length > 0;
19761
+ const hasBindings = lazyRow.attrs.length > 0 || lazyRow.texts.length > 0;
19762
+ const rwDoor = lazyRow.textNeedsRead;
19763
+ const paths = o.skeletonPaths;
19764
+ const useHoisted = Boolean(o.skeletonTemplate);
19765
+ if (useHoisted) emitHoistedTemplateDecl(lines, indent, tplVar, o.skeletonTemplate);
19766
+ const textPathVar = useHoisted && paths && lazyRow.texts.length > 0 ? `__lzp_${mid}` : null;
19767
+ if (textPathVar) {
19768
+ const arrays = lazyRow.texts.map((t) => `[${(paths.textMarkerPaths.get(t.slotId) ?? []).join(", ")}]`);
19769
+ lines.push(`${indent}const ${textPathVar} = [${arrays.join(", ")}]`);
19770
+ }
19771
+ let adoptedPlanVar = null;
19772
+ let freshPlanVar = null;
19773
+ if (lazyRow.texts.length > 0) {
19774
+ adoptedPlanVar = `__lzs_${mid}`;
19775
+ const adoptedSlots = lazyRow.texts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19776
+ lines.push(`${indent}const ${adoptedPlanVar} = ${claimPlanLiteral(adoptedSlots)}`);
19777
+ if (textPathVar) {
19778
+ freshPlanVar = `__lzsc_${mid}`;
19779
+ const freshSlots = lazyRow.texts.map((t, i) => ({
19780
+ id: t.slotId,
19781
+ kind: "text",
19782
+ path: [],
19783
+ pathExpr: `${textPathVar}[${i}]`
19784
+ }));
19785
+ lines.push(`${indent}const ${freshPlanVar} = ${claimPlanLiteral(freshSlots)}`);
19786
+ } else {
19787
+ freshPlanVar = adoptedPlanVar;
19788
+ }
19789
+ }
19790
+ if (hasRefs) {
19791
+ const parts = refParts(lazyRow, "__el", null, adoptedPlanVar, true);
19792
+ lines.push(`${indent}const ${claimVar} = (__e) => {`);
19793
+ if (parts.some((p) => p.includes("__el"))) lines.push(`${indent} const __el = __e.primaryEl`);
19794
+ lines.push(`${indent} return [${parts.join(", ")}]`);
19795
+ lines.push(`${indent}}`);
19796
+ }
19797
+ const call = `mapArrayLazy(() => ${o.arrayExpr}, ${o.containerVar}, ${o.keyFn}, {`;
19798
+ lines.push(`${indent}${o.guardContainer ? `if (${o.containerVar}) ` : ""}${call}`);
19799
+ const b1 = `${indent} `;
19800
+ const b2 = `${indent} `;
19801
+ lines.push(`${b1}createRow: (__e, ${o.indexParam}) => {`);
19802
+ lines.push(`${b2}const ${paramHead} = () => __e.item`);
19803
+ const cloneExpr = useHoisted ? hoistedCloneExpr(tplVar, o.skeletonTemplate) : `(() => { ${emitTemplateCloneInline(o.template)} })()`;
19804
+ lines.push(`${b2}const __el = ${cloneExpr}`);
19805
+ if (hasRefs) {
19806
+ lines.push(`${b2}const __r = __e.refs = [${refParts(lazyRow, "__el", useHoisted ? paths ?? null : null, freshPlanVar).join(", ")}]`);
19807
+ }
19808
+ if (hasBindings) {
19809
+ lines.push(`${b2}const __l = __e.last = []`);
19810
+ for (const a of lazyRow.attrs) emitAttrBinding(lines, b2, a, "create");
19811
+ const createDoor = `__r[${lazyRow.writerIndex}]`;
19812
+ for (const t of lazyRow.texts) emitTextBinding(lines, b2, t, createDoor, "create", rwDoor);
19813
+ }
19814
+ lines.push(`${b2}return __el`);
19815
+ lines.push(`${b1}},`);
19816
+ const itemAttrs = lazyRow.attrs.filter((a) => a.readsItem);
19817
+ const itemTexts = lazyRow.texts.filter((t) => t.readsItem);
19818
+ if (itemAttrs.length === 0 && itemTexts.length === 0) {
19819
+ lines.push(`${b1}applyItem: () => {},`);
19820
+ } else {
19821
+ lines.push(`${b1}applyItem: (__e) => {`);
19822
+ lines.push(`${b2}const ${paramHead} = () => __e.item`);
19823
+ lines.push(`${b2}const __r = __e.refs ?? (__e.refs = ${claimVar}(__e))`);
19824
+ lines.push(`${b2}const __l = __e.last ?? (__e.last = [])`);
19825
+ for (const a of itemAttrs) emitAttrBinding(lines, b2, a, "item");
19826
+ if (itemTexts.length > 0) {
19827
+ lines.push(`${b2}const __d = ${doorAccess(lazyRow, lazyRow.writerIndex, adoptedPlanVar)}`);
19828
+ for (const t of itemTexts) emitTextBinding(lines, b2, t, "__d", "item", rwDoor);
19829
+ }
19830
+ lines.push(`${b1}},`);
19831
+ }
19832
+ const outerAttrs = lazyRow.attrs.filter((a) => a.readsOuter);
19833
+ const outerTexts = lazyRow.texts.filter((t) => t.readsOuter);
19834
+ if (outerAttrs.length > 0 || outerTexts.length > 0) {
19835
+ const b3 = `${indent} `;
19836
+ lines.push(`${b1}applyOuter: (__es, __seed) => {`);
19837
+ for (const g of lazyRow.outerPrimeGetters) lines.push(`${b2}${g}()`);
19838
+ lines.push(`${b2}for (const __e of __es) {`);
19839
+ lines.push(`${b3}const ${paramHead} = () => __e.item`);
19840
+ lines.push(`${b3}const __r = __e.refs ?? (__e.refs = ${claimVar}(__e))`);
19841
+ lines.push(`${b3}const __l = __e.last ?? (__e.last = [])`);
19842
+ for (const a of outerAttrs) emitAttrBinding(lines, b3, a, "outer");
19843
+ if (outerTexts.length > 0) {
19844
+ lines.push(`${b3}const __d = ${doorAccess(lazyRow, lazyRow.writerIndex, adoptedPlanVar)}`);
19845
+ for (const t of outerTexts) emitTextBinding(lines, b3, t, "__d", "outer", rwDoor);
19846
+ }
19847
+ lines.push(`${b2}}`);
19848
+ lines.push(`${b1}},`);
19849
+ }
19850
+ lines.push(`${indent}}, '${o.markerId}')`);
19851
+ }
19852
+ function refParts(lazyRow, elVar, skeletonPaths, planVar, deferDoor = false) {
19853
+ const parts = [];
19854
+ for (const slotId of lazyRow.attrSlotIds) {
19855
+ const path25 = skeletonPaths?.elementPaths.get(slotId);
19856
+ parts.push(path25 ? pathExpr(elVar, path25) : `qsa(${elVar}, '[bf="${slotId}"]')`);
19857
+ }
19858
+ if (lazyRow.texts.length > 0) {
19859
+ parts.push(deferDoor ? "null" : `${doorCtor(lazyRow)}(${elVar}, ${planVar})`);
19860
+ }
19861
+ return parts;
19862
+ }
19863
+ function doorCtor(lazyRow) {
19864
+ return lazyRow.textNeedsRead ? "lazyClaimSlots" : "lazySlots";
19865
+ }
19866
+ function doorAccess(lazyRow, writerIndex, adoptedPlanVar) {
19867
+ const slot = `__r[${writerIndex}]`;
19868
+ return `${slot} ?? (${slot} = ${doorCtor(lazyRow)}(__e.primaryEl, ${adoptedPlanVar}))`;
19869
+ }
19870
+ function dedupGuard(ordinal) {
19871
+ return `!(${ordinal} in __l) || !Object.is(__l[${ordinal}], __x)`;
19872
+ }
19873
+ function emitAttrBinding(lines, ind, a, mode2) {
19874
+ lines.push(`${ind}{ const __t = __r[${a.refIndex}]`);
19875
+ lines.push(`${ind}if (__t) {`);
19876
+ lines.push(`${ind} const __x = ${a.wrappedExpression}`);
19877
+ const guard = mode2 === "create" ? null : mode2 === "item" ? dedupGuard(a.ordinal) : `__seed ? (${seedDiffersExpr("__t", a)}) : (${dedupGuard(a.ordinal)})`;
19878
+ const writeIndent = guard ? `${ind} ` : `${ind} `;
19879
+ if (guard) lines.push(`${ind} if (${guard}) {`);
19880
+ for (const stmt of emitAttrUpdate("__t", a.attrName, "__x", a.meta)) {
19881
+ lines.push(`${writeIndent}${stmt}`);
19882
+ }
19883
+ if (guard) lines.push(`${ind} }`);
19884
+ lines.push(`${ind} __l[${a.ordinal}] = __x`);
19885
+ lines.push(`${ind}} }`);
19886
+ }
19887
+ function emitTextBinding(lines, ind, t, doorExpr, mode2, rwDoor) {
19888
+ lines.push(`${ind}{ const __x = ${t.wrappedExpression}`);
19889
+ const writeOf = (valueExpr) => rwDoor ? `${doorExpr}.write('${t.slotId}', ${valueExpr})` : `${doorExpr}('${t.slotId}', ${valueExpr})`;
19890
+ if (mode2 === "outer") {
19891
+ lines.push(`${ind}if (__seed) {`);
19892
+ lines.push(`${ind} const __s = textOrNode(__x)`);
19893
+ lines.push(`${ind} if (${doorExpr}.read('${t.slotId}') !== __s) ${writeOf("__s")}`);
19894
+ lines.push(`${ind}} else if (${dedupGuard(t.ordinal)}) ${writeOf("textOrNode(__x)")}`);
19895
+ } else if (mode2 === "item") {
19896
+ lines.push(`${ind}if (${dedupGuard(t.ordinal)}) ${writeOf("textOrNode(__x)")}`);
19897
+ } else {
19898
+ lines.push(`${ind}${writeOf("textOrNode(__x)")}`);
19899
+ }
19900
+ lines.push(`${ind}__l[${t.ordinal}] = __x }`);
19901
+ }
19902
+ function seedDiffersExpr(target2, a) {
19903
+ const html = toHTMLAttrName(a.attrName);
19904
+ if (a.attrName === "dangerouslySetInnerHTML" || html === "dangerouslySetInnerHTML") return "true";
19905
+ if (html === "style") return `${target2}.getAttribute('style') !== styleToCss(__x)`;
19906
+ if (html === "class") return `${target2}.getAttribute('class') !== (__x != null ? String(__x) : null)`;
19907
+ if (html === "value") return `${target2}.value !== String(__x)`;
19908
+ if (isBooleanAttr(html)) return `${target2}.${html} !== !!(__x)`;
19909
+ if (a.meta.presenceOrUndefined) {
19910
+ const written = html.startsWith("aria-") ? "true" : "";
19911
+ return `${target2}.getAttribute('${html}') !== (__x ? '${written}' : null)`;
19912
+ }
19913
+ return `${target2}.getAttribute('${html}') !== (__x != null ? String(__x) : null)`;
19914
+ }
19915
+ var init_lazy_row = __esm({
19916
+ "../jsx/src/ir-to-client-js/control-flow/stringify/lazy-row.ts"() {
19917
+ "use strict";
19918
+ init_html_constants();
19919
+ init_emit_reactive();
19920
+ init_utils();
19921
+ init_claim_plan();
19922
+ init_skeleton_paths();
19923
+ init_template_parse();
19924
+ }
19925
+ });
19926
+
19359
19927
  // ../jsx/src/ir-to-client-js/control-flow/stringify/loop.ts
19360
19928
  function emitLoopChildRefs(lines, refs, opts) {
19361
19929
  if (refs.length === 0) return;
@@ -19432,6 +20000,23 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
19432
20000
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
19433
20001
  return;
19434
20002
  }
20003
+ if (plan.lazyRow) {
20004
+ stringifyLazyRowLoop(lines, {
20005
+ indent: topIndent,
20006
+ containerVar,
20007
+ guardContainer: false,
20008
+ markerId,
20009
+ arrayExpr,
20010
+ keyFn,
20011
+ paramHead,
20012
+ indexParam,
20013
+ template,
20014
+ skeletonTemplate,
20015
+ skeletonPaths: plan.skeletonPaths,
20016
+ lazyRow: plan.lazyRow
20017
+ });
20018
+ return;
20019
+ }
19435
20020
  const hoistedTpl = !bodyIsMultiRoot && skeletonTemplate ? skeletonTemplate : null;
19436
20021
  const tplVar = `__tpl_${markerId.replace(/[^A-Za-z0-9_$]/g, "_")}`;
19437
20022
  if (hoistedTpl) {
@@ -19619,6 +20204,7 @@ var init_loop = __esm({
19619
20204
  init_component_loop();
19620
20205
  init_composite_loop();
19621
20206
  init_claim_plan();
20207
+ init_lazy_row();
19622
20208
  }
19623
20209
  });
19624
20210
 
@@ -20052,6 +20638,23 @@ function emitPlain(lines, plan) {
20052
20638
  stringifyEventDelegation(lines, eventDelegation);
20053
20639
  return;
20054
20640
  }
20641
+ if (plan.lazyRow) {
20642
+ stringifyLazyRowLoop(lines, {
20643
+ indent: " ",
20644
+ containerVar,
20645
+ guardContainer: true,
20646
+ markerId,
20647
+ arrayExpr,
20648
+ keyFn,
20649
+ paramHead,
20650
+ indexParam,
20651
+ template,
20652
+ lazyRow: plan.lazyRow
20653
+ });
20654
+ lines.push(` }))`);
20655
+ stringifyEventDelegation(lines, eventDelegation);
20656
+ return;
20657
+ }
20055
20658
  if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
20056
20659
  const cloneExpr = emitTemplateCloneInline(template);
20057
20660
  if (mapPreambleWrapped) {
@@ -20097,6 +20700,7 @@ var init_branch_loop = __esm({
20097
20700
  init_reactive_effects();
20098
20701
  init_template_parse();
20099
20702
  init_loop();
20703
+ init_lazy_row();
20100
20704
  }
20101
20705
  });
20102
20706
 
@@ -20283,7 +20887,7 @@ var init_build_component_loop = __esm({
20283
20887
  // ../jsx/src/ir-to-client-js/control-flow/plan/build-loop.ts
20284
20888
  function buildLoopPlan(elem, opts) {
20285
20889
  if (elem.bodyIsItemConditional) {
20286
- return buildPlainLoopPlan(elem, opts.profileComponentName);
20890
+ return buildPlainLoopPlan(elem, opts.profileComponentName, opts.lazyScope);
20287
20891
  }
20288
20892
  if (elem.isStaticArray) {
20289
20893
  return buildStaticLoopPlan(elem, opts.unsafeLocalNames, opts.profileComponentName);
@@ -20295,9 +20899,9 @@ function buildLoopPlan(elem, opts) {
20295
20899
  if (elem.childComponent) {
20296
20900
  return buildComponentLoopPlan(elem, opts.profileComponentName);
20297
20901
  }
20298
- return buildPlainLoopPlan(elem, opts.profileComponentName);
20902
+ return buildPlainLoopPlan(elem, opts.profileComponentName, opts.lazyScope);
20299
20903
  }
20300
- function buildPlainLoopPlan(elem, profileComponentName) {
20904
+ function buildPlainLoopPlan(elem, profileComponentName, lazyScope) {
20301
20905
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
20302
20906
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
20303
20907
  const hasReactive2 = elem.bindings.reactiveAttrs.length > 0 || elem.bindings.reactiveTexts.length > 0 || elem.bindings.conditionals.length > 0;
@@ -20326,30 +20930,48 @@ function buildPlainLoopPlan(elem, profileComponentName) {
20326
20930
  preambleRegions: []
20327
20931
  };
20328
20932
  }
20933
+ const arrayExpr = buildChainedArrayExpr(elem);
20934
+ const indexParam = elem.index || "__idx";
20935
+ const mapPreambleWrapped = elem.preamble ? renderPreamble(elem.preamble, {
20936
+ transformJs: wrap,
20937
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
20938
+ }) : "";
20939
+ const preambleRegions = buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings);
20329
20940
  return {
20330
20941
  kind: "plain",
20331
20942
  rowConstruction: "string-template",
20332
20943
  containerVar: `_${varSlotId(elem.slotId)}`,
20333
20944
  markerId: elem.markerId,
20334
20945
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : void 0,
20335
- arrayExpr: buildChainedArrayExpr(elem),
20946
+ arrayExpr,
20336
20947
  keyFn: loopKeyFn(elem),
20337
20948
  paramHead,
20338
20949
  paramUnwrap,
20339
- indexParam: elem.index || "__idx",
20950
+ indexParam,
20951
+ // Lazy row graph (§9, L3). `null` for every ineligible loop, which then
20952
+ // keeps the eager emission below byte-for-byte.
20953
+ lazyRow: buildLazyRowPlan({
20954
+ loop: elem,
20955
+ arrayExpr,
20956
+ indexParam,
20957
+ paramUnwrap,
20958
+ mapPreambleWrapped,
20959
+ preambleRegionCount: preambleRegions.length,
20960
+ callSite: "plain",
20961
+ flatMapLeafItem: false,
20962
+ anchored: elem.bodyIsItemConditional ?? false,
20963
+ scope: lazyScope
20964
+ }) ?? void 0,
20340
20965
  // Stage 3 / D4 — js segments get the loop-param accessor wrap; jsx leaves
20341
20966
  // render as HTML-string templates under this loop's param context so a
20342
20967
  // leaf that reads the item (`r`) becomes `r()`.
20343
- mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
20344
- transformJs: wrap,
20345
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
20346
- }) : "",
20968
+ mapPreambleWrapped,
20347
20969
  template: elem.template,
20348
20970
  skeletonTemplate: elem.skeletonTemplate,
20349
20971
  skeletonPaths: elem.skeletonPaths,
20350
20972
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
20351
20973
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
20352
- preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
20974
+ preambleRegions,
20353
20975
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
20354
20976
  anchored: elem.bodyIsItemConditional ?? false,
20355
20977
  // Fall back to the iteration index when the loop has no key. A whole-item
@@ -20411,6 +21033,7 @@ var init_build_loop = __esm({
20411
21033
  init_utils();
20412
21034
  init_shared();
20413
21035
  init_build_reactive_effects();
21036
+ init_build_lazy_row();
20414
21037
  init_build_component_loop();
20415
21038
  init_build_composite_loop();
20416
21039
  init_html_template();
@@ -20421,7 +21044,7 @@ var init_build_loop = __esm({
20421
21044
  function emitConditionalUpdates(lines, ctx2) {
20422
21045
  const profileComponentName = ctx2.profile ? ctx2.componentName : void 0;
20423
21046
  for (const elem of ctx2.conditionalElements) {
20424
- const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "dom", profileComponentName });
21047
+ const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "dom", profileComponentName, lazyScope: buildLazyRowScopeInfo(ctx2) });
20425
21048
  stringifyInsert(lines, plan, { leadingIndent: " ", bodyIndent: " " });
20426
21049
  lines.push("");
20427
21050
  }
@@ -20429,17 +21052,19 @@ function emitConditionalUpdates(lines, ctx2) {
20429
21052
  function emitClientOnlyConditionals(lines, ctx2) {
20430
21053
  const profileComponentName = ctx2.profile ? ctx2.componentName : void 0;
20431
21054
  for (const elem of ctx2.clientOnlyConditionals) {
20432
- const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "raw", profileComponentName });
21055
+ const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "raw", profileComponentName, lazyScope: buildLazyRowScopeInfo(ctx2) });
20433
21056
  lines.push(` // @client conditional: ${elem.slotId}`);
20434
21057
  stringifyInsert(lines, plan, { leadingIndent: " ", bodyIndent: " " });
20435
21058
  lines.push("");
20436
21059
  }
20437
21060
  }
20438
21061
  function emitLoopUpdates(lines, ctx2, unsafeLocalNames) {
21062
+ const lazyScope = buildLazyRowScopeInfo(ctx2);
20439
21063
  for (const elem of ctx2.loopElements) {
20440
21064
  const plan = buildLoopPlan(elem, {
20441
21065
  unsafeLocalNames,
20442
- profileComponentName: ctx2.profile ? ctx2.componentName : void 0
21066
+ profileComponentName: ctx2.profile ? ctx2.componentName : void 0,
21067
+ lazyScope
20443
21068
  });
20444
21069
  internalInvariant(
20445
21070
  !(elem.preamble && elem.preamble.builderNames.length > 0 && plan.rowConstruction === "dom-ops"),
@@ -20467,6 +21092,7 @@ var init_control_flow = __esm({
20467
21092
  init_build_insert();
20468
21093
  init_insert();
20469
21094
  init_build_loop();
21095
+ init_build_lazy_row();
20470
21096
  init_loop();
20471
21097
  init_build_event_delegation();
20472
21098
  init_event_delegation();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "CLI for agent-driven UI component discovery and scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,11 +29,11 @@
29
29
  "dependencies": {
30
30
  "esbuild": "^0.25.0",
31
31
  "typescript": "^5.0.0",
32
- "@barefootjs/client": "0.27.0",
33
- "@barefootjs/shared": "0.27.0"
32
+ "@barefootjs/client": "0.28.0",
33
+ "@barefootjs/shared": "0.28.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@barefootjs/jsx": "0.27.0",
36
+ "@barefootjs/jsx": "0.28.0",
37
37
  "@types/node": "^22.0.0",
38
38
  "@happy-dom/global-registrator": "^20.0.11",
39
39
  "happy-dom": "^20.0.11"