@barefootjs/cli 0.26.4 → 0.27.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.
@@ -195,7 +195,7 @@ createEffect(() => {
195
195
  ### 4. Code Generation Order
196
196
 
197
197
  ```javascript
198
- import { $, $t, createEffect, createMemo, createSignal, hydrate, onMount } from '@barefootjs/client'
198
+ import { $, createEffect, createMemo, createSignal, escapeTextOrNode, hydrate, lazySlots, onMount } from '@barefootjs/client/runtime'
199
199
 
200
200
  export function initCounter(__scope, _p = {}) {
201
201
  if (!__scope) return
@@ -216,15 +216,16 @@ export function initCounter(__scope, _p = {}) {
216
216
  const doubled = createMemo(() => count() * 2)
217
217
 
218
218
  // 4. Element references (always destructured, always returns array)
219
- // $() — regular elements: querySelector('[bf="id"]') within scope
220
- // $t() — text nodes: find comment marker <!--bf:id-->
219
+ // $() — regular elements: querySelector('[bf="id"]') within scope
221
220
  const [_s3] = $(__scope, 's3')
222
- const [_s0, _s2] = $t(__scope, 's0', 's2')
223
221
 
224
- // 5. Dynamic text updates
222
+ // 5. Dynamic content updates — the compiler emits a claim plan (data)
223
+ // per slot; lazySlots() claims the <!--bf:id-->…<!--/--> marker
224
+ // range once on first write, then writes through the held reference
225
+ const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
225
226
  createEffect(() => {
226
227
  const __val = count()
227
- if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
228
+ __bfw_s0('s0', escapeTextOrNode(__val))
228
229
  })
229
230
 
230
231
  // 6. Reactive attribute updates
@@ -266,7 +267,7 @@ hydrate('Counter', {
266
267
  Only used imports are included:
267
268
 
268
269
  ```javascript
269
- import { $, $t, createEffect, createMemo, createSignal, hydrate, onMount } from '@barefootjs/client'
270
+ import { $, createEffect, createMemo, createSignal, escapeTextOrNode, hydrate, lazySlots, onMount } from '@barefootjs/client/runtime'
270
271
  ```
271
272
 
272
273
  ### 6. Template Registration
@@ -51,7 +51,7 @@ The compiler detects static arrays and skips reconciliation:
51
51
  const tabs = ['Home', 'About', 'Contact']
52
52
  {tabs.map(tab => <Tab label={tab} />)}
53
53
 
54
- // Dynamic — reconcileElements needed
54
+ // Dynamic — keyed reconciliation (mapArray) needed
55
55
  const [items, setItems] = createSignal([...])
56
56
  {items().map(item => <Item key={item.id} data={item} />)}
57
57
  ```
@@ -82,7 +82,7 @@ export function Counter({ __instanceId, ... }) {
82
82
  Client JS (Phase 2b):
83
83
 
84
84
  ```js
85
- import { $, $t, createEffect, createSignal, hydrate } from '@barefootjs/client'
85
+ import { $, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
86
86
 
87
87
  export function initCounter(__scope, _p = {}) {
88
88
  if (!__scope) return
@@ -90,11 +90,13 @@ export function initCounter(__scope, _p = {}) {
90
90
  const [count, setCount] = createSignal(0)
91
91
 
92
92
  const [_s1] = $(__scope, 's1') // element lookup
93
- const [_s0] = $t(__scope, 's0') // text node lookup
94
93
 
94
+ // Content slot: claimed lazily from the <!--bf:s0-->…<!--/--> marker
95
+ // pair on first write, then updated through the held reference
96
+ const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
95
97
  createEffect(() => {
96
98
  const __val = count()
97
- if (_s0) _s0.nodeValue = String(__val ?? '')
99
+ __bfw_s0('s0', escapeTextOrNode(__val))
98
100
  })
99
101
 
100
102
  if (_s1) _s1.addEventListener('click', () => { setCount(n => n + 1) })
@@ -102,7 +104,7 @@ export function initCounter(__scope, _p = {}) {
102
104
 
103
105
  hydrate('Counter', {
104
106
  init: initCounter,
105
- template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${(0)}<!--/--></button>`
107
+ template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${escapeText((0))}<!--/--></button>`
106
108
  })
107
109
  ```
108
110
 
@@ -135,9 +137,11 @@ Marker-driven hydration attaches behavior to server-rendered HTML.
135
137
  4. Init function runs per scope — signals, effects, handlers
136
138
  5. Runtime tracks scopes to prevent double initialization
137
139
 
138
- ### Scoped Queries
140
+ ### Scoped Queries and Slot Claiming
139
141
 
140
- `$()` and `$t()` search within a scope, excluding child component scopes:
142
+ Element lookups (`$()`) and content-slot claims (`claimSlots()` /
143
+ `lazySlots()`) both operate within a scope, excluding child component
144
+ scopes:
141
145
 
142
146
  ```html
143
147
  <div bf-s="TodoApp_x1">
@@ -148,4 +152,16 @@ Marker-driven hydration attaches behavior to server-rendered HTML.
148
152
  </div>
149
153
  ```
150
154
 
151
- `$(__scope, 's0')` in TodoApp finds `<h1>`, not the `<span>` inside TodoItem. The `~` prefix marks a child scope excluded from parent queries.
155
+ `$(__scope, 's0')` in TodoApp finds `<h1>`, not the `<span>` inside
156
+ TodoItem. The `~` prefix marks a child scope excluded from parent
157
+ queries.
158
+
159
+ Dynamic *content* (reactive text and markup regions) is not looked up
160
+ per update. Instead the compiler emits a **claim plan** — a data
161
+ description of each slot (`{ id, kind, path }`) — and the runtime claims
162
+ the slot's DOM position **once**: `lazySlots()` scans for the slot's
163
+ `<!--bf:id-->…<!--/-->` marker pair on the first write (skipping child
164
+ scopes, same as `$()`), holds the reference, and every later write goes
165
+ through that held reference with no re-scanning. `claimSlots()` is the
166
+ eager variant used where content may be mutated before the first write
167
+ (streaming, portals).
@@ -63,7 +63,7 @@ export function Counter({ __instanceId, ... }) {
63
63
  **Client script** — Wires up only the interactive parts:
64
64
 
65
65
  ```js
66
- import { $, $t, createEffect, createSignal, hydrate } from '@barefootjs/client'
66
+ import { $, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
67
67
 
68
68
  export function initCounter(__scope, _p = {}) {
69
69
  if (!__scope) return
@@ -71,11 +71,13 @@ export function initCounter(__scope, _p = {}) {
71
71
  const [count, setCount] = createSignal(0)
72
72
 
73
73
  const [_s1] = $(__scope, 's1') // find element with bf="s1"
74
- const [_s0] = $t(__scope, 's0') // find text node at <!--bf:s0-->
75
74
 
75
+ // Claim the content slot between <!--bf:s0--> and <!--/--> on first
76
+ // write; later writes reuse the claimed reference (no re-scanning)
77
+ const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
76
78
  createEffect(() => {
77
79
  const __val = count()
78
- if (_s0) _s0.nodeValue = String(__val ?? '')
80
+ __bfw_s0('s0', escapeTextOrNode(__val))
79
81
  })
80
82
 
81
83
  if (_s1) _s1.addEventListener('click', () => { setCount(n => n + 1) })
@@ -83,6 +85,6 @@ export function initCounter(__scope, _p = {}) {
83
85
 
84
86
  hydrate('Counter', {
85
87
  init: initCounter,
86
- template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${(0)}<!--/--></button>`
88
+ template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${escapeText((0))}<!--/--></button>`
87
89
  })
88
90
  ```
@@ -32,23 +32,40 @@ See [JSX Compatibility — Limitations](./jsx-compatibility.md#limitations) for
32
32
 
33
33
  ## How It Works
34
34
 
35
- The compiler skips template generation for the expression. The server outputs a comment marker; the client JS evaluates it:
35
+ The compiler skips template generation for the expression: the server can never evaluate it, so the SSR-rendered width is always zero. The client claims a slot for it and writes the real value once the browser evaluates the expression — the general case behind both compiled shapes below (`spec/slot-unification.md` §4).
36
36
 
37
- **Server output:**
37
+ **Claimed slot (the common case):** a marker pair is still emitted so the client has an anchor comment to claim against.
38
38
 
39
39
  ```html
40
- <!--bf-client:s2--><!--/-->
40
+ <!-- server output -->
41
+ <!--bf:s0--><!--/--> items left
41
42
  ```
42
43
 
43
- **Client JS:**
44
+ ```js
45
+ // client JS
46
+ { const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'text', path: [] }])
47
+ createEffect(() => {
48
+ __bfw_s0('s0', todos().filter(t => !t.done).length)
49
+ }) }
50
+ ```
51
+
52
+ **Markerless elision (Step B):** when the expression is the ONLY content of its own element — not adjacent to other text/expressions, and not inside a loop or conditional branch — the compiler proves a static child-index path to the slot's position and drops the marker pair entirely from both SSR and CSR output. `<strong>{/* @client */ todos().filter(t => !t.done).length}</strong>` from the [TodoApp example](https://github.com/piconic-ai/barefootjs/blob/main/integrations/shared/components/TodoApp.tsx) qualifies:
53
+
54
+ ```html
55
+ <!-- server output -->
56
+ <strong bf="s1"></strong>
57
+ ```
44
58
 
45
59
  ```js
46
- // @client: s2
60
+ // client JS
61
+ { const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'text', path: [0, 0], markerless: true }])
47
62
  createEffect(() => {
48
- updateClientMarker(__scope, 's2', todos().filter(t => !t.done).length)
49
- })
63
+ __bfw_s0('s0', todos().filter(t => !t.done).length)
64
+ }) }
50
65
  ```
51
66
 
67
+ Either way, the claim happens lazily on the first write — nothing is touched until the effect actually runs — and every later write goes through the held reference, never re-scanning the DOM (`packages/client/src/runtime/claim-slots.ts`).
68
+
52
69
 
53
70
  ## Examples
54
71
 
package/dist/index.js CHANGED
@@ -3926,6 +3926,10 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3926
3926
  return escapeHtml(node.value);
3927
3927
  case "expression": {
3928
3928
  if (node.expr === "null" || node.expr === "undefined") return "";
3929
+ if (node.markerless) {
3930
+ const bare = wrapInterpolation(wrapExpr(node.expr));
3931
+ return `\${${bare}}`;
3932
+ }
3929
3933
  const inner = wrapInterpolation(wrapExpr(node.expr));
3930
3934
  const valueExpr = node.joinArrayChild ? `Array.isArray(${inner}) ? ${inner}.join('') : (${inner} ?? '')` : inner;
3931
3935
  if (node.slotId) {
@@ -4741,7 +4745,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4741
4745
  case "expression":
4742
4746
  if (node.expr === "null" || node.expr === "undefined") return "";
4743
4747
  if (node.clientOnly && node.slotId) {
4744
- return `<!--bf-client:${node.slotId}--><!--/-->`;
4748
+ return `<!--bf:${node.slotId}--><!--/-->`;
4745
4749
  }
4746
4750
  {
4747
4751
  const transformed = transformExpr(node.expr, node.templateExpr);
@@ -14323,7 +14327,11 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14323
14327
  },
14324
14328
  expression: ({ node: ex, scope: inCond }) => {
14325
14329
  if (ex.clientOnly && ex.slotId) {
14326
- ctx2.clientOnlyElements.push({ slotId: ex.slotId, expression: ex.expr });
14330
+ ctx2.clientOnlyElements.push({
14331
+ slotId: ex.slotId,
14332
+ expression: ex.expr,
14333
+ elidedPath: ex.markerless ? ex.elidedPath : void 0
14334
+ });
14327
14335
  return;
14328
14336
  }
14329
14337
  if (!ex.slotId || inCond) return;
@@ -15327,13 +15335,11 @@ var init_imports = __esm({
15327
15335
  "onMount",
15328
15336
  "hydrate",
15329
15337
  "insert",
15330
- "reconcileElements",
15331
15338
  "getLoopChildren",
15332
15339
  "getLoopNodes",
15333
15340
  "mapArray",
15334
15341
  "mapArrayAnchored",
15335
15342
  "patchLeaf",
15336
- "patchSlotRange",
15337
15343
  "createDisposableEffect",
15338
15344
  "createComponent",
15339
15345
  "renderChild",
@@ -15341,7 +15347,6 @@ var init_imports = __esm({
15341
15347
  "registerTemplate",
15342
15348
  "initChild",
15343
15349
  "upsertChild",
15344
- "updateClientMarker",
15345
15350
  "createPortal",
15346
15351
  "provideContext",
15347
15352
  "createContext",
@@ -15353,6 +15358,7 @@ var init_imports = __esm({
15353
15358
  "styleToCss",
15354
15359
  "escapeAttr",
15355
15360
  "escapeText",
15361
+ "escapeTextOrNode",
15356
15362
  "qsa",
15357
15363
  "qsaItem",
15358
15364
  "qsaChildScope",
@@ -15361,7 +15367,11 @@ var init_imports = __esm({
15361
15367
  "__slot",
15362
15368
  "__bfSlot",
15363
15369
  "__bfText",
15364
- "tAfter",
15370
+ // Claim-plan interpreter (slot unification A2/A3, spec/slot-unification.md)
15371
+ // — the "one claim mechanism" that replaced `patchSlotRange` and
15372
+ // `updateClientMarker` (both deleted) as the content-slot update door.
15373
+ "claimSlots",
15374
+ "lazySlots",
15365
15375
  // Profile mode (#1690, SR3) — turn-boundary markers around event handlers.
15366
15376
  "beginTurn",
15367
15377
  "endTurn",
@@ -18617,6 +18627,25 @@ var init_build_insert = __esm({
18617
18627
  }
18618
18628
  });
18619
18629
 
18630
+ // ../jsx/src/ir-to-client-js/control-flow/stringify/claim-plan.ts
18631
+ function slotSpecLiteral(slot) {
18632
+ const pathSrc = slot.pathExpr ?? `[${slot.path.join(", ")}]`;
18633
+ const markerlessSrc = slot.markerless ? ", markerless: true" : "";
18634
+ return `{ id: '${slot.id}', kind: '${slot.kind}', path: ${pathSrc}${markerlessSrc} }`;
18635
+ }
18636
+ function claimPlanLiteral(slots) {
18637
+ return `[${slots.map(slotSpecLiteral).join(", ")}]`;
18638
+ }
18639
+ function claimWriterVarName(slots, sanitize) {
18640
+ const first = slots[0]?.id ?? "0";
18641
+ return `__bfw_${sanitize(first)}`;
18642
+ }
18643
+ var init_claim_plan = __esm({
18644
+ "../jsx/src/ir-to-client-js/control-flow/stringify/claim-plan.ts"() {
18645
+ "use strict";
18646
+ }
18647
+ });
18648
+
18620
18649
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
18621
18650
  import ts14 from "typescript";
18622
18651
  function bindingIdArg(ctx2, slotId) {
@@ -18795,17 +18824,18 @@ function emitDynamicTextUpdates(lines, ctx2) {
18795
18824
  const conditionalElems = elems.filter((e) => e.insideConditional);
18796
18825
  const normalElems = elems.filter((e) => !e.insideConditional);
18797
18826
  if (normalElems.length > 0 || conditionalElems.length > 0) {
18798
- for (const elem of normalElems) {
18799
- const v = varSlotId(elem.slotId);
18800
- lines.push(` let __anchor_${v} = _${v}`);
18801
- }
18802
18827
  const __textSlot = (normalElems[0] ?? conditionalElems[0])?.slotId;
18828
+ let writer = "";
18829
+ if (normalElems.length > 0) {
18830
+ const slots = normalElems.map((elem) => ({ id: elem.slotId, kind: "markup", path: [] }));
18831
+ writer = claimWriterVarName(slots, varSlotId);
18832
+ lines.push(` const ${writer} = lazySlots(__scope, ${claimPlanLiteral(slots)})`);
18833
+ }
18803
18834
  lines.push(` createEffect(() => {`);
18804
18835
  if (normalElems.length > 0) {
18805
18836
  lines.push(` const __val = ${expr}`);
18806
18837
  for (const elem of normalElems) {
18807
- const v = varSlotId(elem.slotId);
18808
- lines.push(` __anchor_${v} = __bfText(__anchor_${v}, __val)`);
18838
+ lines.push(` ${writer}('${elem.slotId}', escapeTextOrNode(__val))`);
18809
18839
  }
18810
18840
  for (const elem of conditionalElems) {
18811
18841
  const v = varSlotId(elem.slotId);
@@ -18828,10 +18858,13 @@ function emitDynamicTextUpdates(lines, ctx2) {
18828
18858
  }
18829
18859
  function emitClientOnlyExpressions(lines, ctx2) {
18830
18860
  for (const elem of ctx2.clientOnlyElements) {
18861
+ const slots = elem.elidedPath ? [{ id: elem.slotId, kind: "text", path: elem.elidedPath, markerless: true }] : [{ id: elem.slotId, kind: "text", path: [] }];
18862
+ const writer = claimWriterVarName(slots, varSlotId);
18831
18863
  lines.push(` // @client: ${elem.slotId}`);
18864
+ lines.push(` { const ${writer} = lazySlots(__scope, ${claimPlanLiteral(slots)})`);
18832
18865
  lines.push(` createEffect(() => {`);
18833
- lines.push(` updateClientMarker(__scope, '${elem.slotId}', ${elem.expression})`);
18834
- lines.push(` }${bindingIdArg(ctx2, elem.slotId)})`);
18866
+ lines.push(` ${writer}('${elem.slotId}', ${elem.expression})`);
18867
+ lines.push(` }${bindingIdArg(ctx2, elem.slotId)}) }`);
18835
18868
  lines.push("");
18836
18869
  }
18837
18870
  }
@@ -18939,6 +18972,7 @@ var init_emit_reactive = __esm({
18939
18972
  "use strict";
18940
18973
  init_html_constants();
18941
18974
  init_utils();
18975
+ init_claim_plan();
18942
18976
  init_html_template();
18943
18977
  init_date_lowering();
18944
18978
  init_to_locale_date_lowering();
@@ -19008,13 +19042,18 @@ function stringifyBranchInnerLoops(lines, plan, indent, pc) {
19008
19042
  inner.outerLoopParamBindings
19009
19043
  );
19010
19044
  }
19011
- for (const text of inner.reactiveTexts) {
19045
+ const conditionalTexts = inner.reactiveTexts.filter((t) => t.insideConditional);
19046
+ const plainTexts = inner.reactiveTexts.filter((t) => !t.insideConditional);
19047
+ for (const text of conditionalTexts) {
19012
19048
  const bf = profileBindingId(pc, text.slotId);
19013
- if (text.insideConditional) {
19014
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__bel${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`);
19015
- } else {
19016
- lines.push(`${indent} { const [__rt] = $t(__bel${uid}, '${text.slotId}')`);
19017
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`);
19049
+ lines.push(`${indent} createEffect(() => { claimSlots(__bel${uid}, [{ id: '${text.slotId}', kind: 'text', path: [] }]).write('${text.slotId}', String(${text.wrappedExpression})) }${bf})`);
19050
+ }
19051
+ if (plainTexts.length > 0) {
19052
+ const slots = plainTexts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19053
+ const writer = claimWriterVarName(slots, varSlotId);
19054
+ lines.push(`${indent} const ${writer} = lazySlots(__bel${uid}, ${claimPlanLiteral(slots)})`);
19055
+ for (const text of plainTexts) {
19056
+ lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`);
19018
19057
  }
19019
19058
  }
19020
19059
  if (inner.nestedConditionals.length > 0) {
@@ -19056,10 +19095,13 @@ function stringifyLoopChildArm(lines, arm, armIndent, pc) {
19056
19095
  stringifyLoopChildConditional(lines, cond, `${armIndent} `, pc);
19057
19096
  lines.push(`${armIndent}}))`);
19058
19097
  }
19059
- for (const text of arm.texts) {
19060
- const varName = `__rt_${varSlotId(text.slotId)}`;
19061
- lines.push(`${armIndent}let ${varName} = $t(__branchScope, '${text.slotId}')[0]`);
19062
- lines.push(`${armIndent}__disposers.push(createDisposableEffect(() => { ${varName} = __bfText(${varName}, ${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}))`);
19098
+ if (arm.texts.length > 0) {
19099
+ const slots = arm.texts.map((t) => ({ id: t.slotId, kind: "markup", path: [] }));
19100
+ const writer = claimWriterVarName(slots, varSlotId);
19101
+ lines.push(`${armIndent}const ${writer} = lazySlots(__branchScope, ${claimPlanLiteral(slots)})`);
19102
+ for (const text of arm.texts) {
19103
+ lines.push(`${armIndent}__disposers.push(createDisposableEffect(() => { ${writer}('${text.slotId}', escapeTextOrNode(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)}))`);
19104
+ }
19063
19105
  }
19064
19106
  lines.push(`${armIndent}return () => __disposers.forEach(d => d())`);
19065
19107
  }
@@ -19072,19 +19114,45 @@ var init_loop_child_arm = __esm({
19072
19114
  init_template_parse();
19073
19115
  init_event_listener();
19074
19116
  init_component_scope();
19117
+ init_claim_plan();
19075
19118
  }
19076
19119
  });
19077
19120
 
19078
19121
  // ../jsx/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts
19079
19122
  function stringifyReactiveEffects(lines, plan, opts) {
19080
- const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textIndexBySlot } = opts;
19123
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textClaimPathExprs, preambleRegions = [], mapPreambleWrapped } = opts;
19081
19124
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
19082
- const pc = plan.profileComponentName;
19125
+ const pc = plan?.profileComponentName;
19083
19126
  const bindingBfId = (slotId) => profileBindingId(pc, slotId);
19084
- for (const slot of plan.attrSlots) {
19127
+ const attrSlots = plan?.attrSlots ?? [];
19128
+ const outerTexts = plan?.outerTexts ?? [];
19129
+ const conditionals = plan?.conditionals ?? [];
19130
+ if (pc) {
19131
+ emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId);
19132
+ emitOuterTexts(lines, indent, elVar, outerTexts, bindingBfId, textClaimPathExprs);
19133
+ emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPreambleWrapped);
19134
+ } else {
19135
+ emitConsolidatedRowEffect(
19136
+ lines,
19137
+ indent,
19138
+ elVar,
19139
+ lookup,
19140
+ attrSlots,
19141
+ outerTexts,
19142
+ elementIndexBySlot,
19143
+ textClaimPathExprs,
19144
+ preambleRegions,
19145
+ mapPreambleWrapped
19146
+ );
19147
+ }
19148
+ for (const cond of conditionals) {
19149
+ emitOuterConditional(lines, indent, elVar, cond, pc);
19150
+ }
19151
+ }
19152
+ function emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId) {
19153
+ for (const slot of attrSlots) {
19085
19154
  const varName = `__ra_${varSlotId(slot.slotId)}`;
19086
- const pIdx = elementIndexBySlot?.get(slot.slotId);
19087
- const lookupExpr = pIdx !== void 0 ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slot.slotId}"]')` : `${lookup}(${elVar}, '[bf="${slot.slotId}"]')`;
19155
+ const lookupExpr = attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot);
19088
19156
  lines.push(`${indent}{ const ${varName} = ${lookupExpr}`);
19089
19157
  lines.push(`${indent}if (${varName}) {`);
19090
19158
  for (const attr of slot.attrs) {
@@ -19096,21 +19164,79 @@ function stringifyReactiveEffects(lines, plan, opts) {
19096
19164
  }
19097
19165
  lines.push(`${indent}} }`);
19098
19166
  }
19099
- for (const text of plan.outerTexts) {
19100
- emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId), textIndexBySlot?.get(text.slotId));
19167
+ }
19168
+ function emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPreambleWrapped) {
19169
+ if (preambleRegions.length === 0) return;
19170
+ const slots = preambleRegions.map((r2) => ({ id: r2.slotId, kind: "markup", path: [] }));
19171
+ const writer = claimWriterVarName(slots, varSlotId);
19172
+ lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(slots)})`);
19173
+ lines.push(`${indent}createEffect(() => {`);
19174
+ if (mapPreambleWrapped) lines.push(`${indent} ${mapPreambleWrapped}`);
19175
+ for (const region of preambleRegions) {
19176
+ lines.push(`${indent} ${writer}('${region.slotId}', ${region.valueExpr})`);
19101
19177
  }
19102
- for (const cond of plan.conditionals) {
19103
- emitOuterConditional(lines, indent, elVar, cond, pc);
19178
+ lines.push(`${indent}})`);
19179
+ }
19180
+ function attrLookupExpr(slotId, varName, elVar, lookup, elementIndexBySlot) {
19181
+ const pIdx = elementIndexBySlot?.get(slotId);
19182
+ return pIdx !== void 0 ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slotId}"]')` : `${lookup}(${elVar}, '[bf="${slotId}"]')`;
19183
+ }
19184
+ function emitConsolidatedRowEffect(lines, indent, elVar, lookup, attrSlots, outerTexts, elementIndexBySlot, textClaimPathExprs, preambleRegions, mapPreambleWrapped) {
19185
+ if (attrSlots.length === 0 && outerTexts.length === 0 && preambleRegions.length === 0) return;
19186
+ for (const slot of attrSlots) {
19187
+ const varName = `__ra_${varSlotId(slot.slotId)}`;
19188
+ lines.push(`${indent}const ${varName} = ${attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)}`);
19189
+ }
19190
+ const claimSlots = [
19191
+ ...outerTexts.map((t) => ({ id: t.slotId, kind: "text", path: [], pathExpr: textClaimPathExprs?.get(t.slotId) })),
19192
+ ...preambleRegions.map((r2) => ({ id: r2.slotId, kind: "markup", path: [] }))
19193
+ ];
19194
+ const writer = claimSlots.length > 0 ? claimWriterVarName(claimSlots, varSlotId) : null;
19195
+ if (writer) {
19196
+ lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(claimSlots)})`);
19197
+ }
19198
+ if (attrSlots.length === 0 && preambleRegions.length === 0 && outerTexts.length === 1) {
19199
+ const text = outerTexts[0];
19200
+ lines.push(`${indent}createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) })`);
19201
+ return;
19202
+ }
19203
+ lines.push(`${indent}createEffect(() => {`);
19204
+ for (const slot of attrSlots) {
19205
+ const varName = `__ra_${varSlotId(slot.slotId)}`;
19206
+ lines.push(`${indent} if (${varName}) {`);
19207
+ for (const attr of slot.attrs) {
19208
+ lines.push(`${indent} {`);
19209
+ for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
19210
+ lines.push(`${indent} ${stmt}`);
19211
+ }
19212
+ lines.push(`${indent} }`);
19213
+ }
19214
+ lines.push(`${indent} }`);
19104
19215
  }
19216
+ if (preambleRegions.length > 0 && mapPreambleWrapped) {
19217
+ lines.push(`${indent} ${mapPreambleWrapped}`);
19218
+ }
19219
+ for (const text of outerTexts) {
19220
+ lines.push(`${indent} ${writer}('${text.slotId}', String(${text.wrappedExpression}))`);
19221
+ }
19222
+ for (const region of preambleRegions) {
19223
+ lines.push(`${indent} ${writer}('${region.slotId}', ${region.valueExpr})`);
19224
+ }
19225
+ lines.push(`${indent}})`);
19105
19226
  }
19106
- function emitOuterText(lines, indent, elVar, text, bfId = "", pIdx) {
19107
- const varName = `__rt_${varSlotId(text.slotId)}`;
19108
- if (pIdx !== void 0) {
19109
- lines.push(`${indent}{ const ${varName} = __p ? tAfter(__p[${pIdx}]) : $t(${elVar}, '${text.slotId}')[0]`);
19110
- } else {
19111
- lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
19227
+ function emitOuterTexts(lines, indent, elVar, texts, bindingBfId, textClaimPathExprs) {
19228
+ if (texts.length === 0) return;
19229
+ const slots = texts.map((t) => ({
19230
+ id: t.slotId,
19231
+ kind: "text",
19232
+ path: [],
19233
+ pathExpr: textClaimPathExprs?.get(t.slotId)
19234
+ }));
19235
+ const writer = claimWriterVarName(slots, varSlotId);
19236
+ lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(slots)})`);
19237
+ for (const text of texts) {
19238
+ lines.push(`${indent}createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${bindingBfId(text.slotId)})`);
19112
19239
  }
19113
- lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`);
19114
19240
  }
19115
19241
  function emitOuterConditional(lines, indent, elVar, cond, pc) {
19116
19242
  const armIndent = `${indent} `;
@@ -19132,6 +19258,7 @@ var init_reactive_effects = __esm({
19132
19258
  init_utils();
19133
19259
  init_emit_reactive();
19134
19260
  init_loop_child_arm();
19261
+ init_claim_plan();
19135
19262
  }
19136
19263
  });
19137
19264
 
@@ -19242,20 +19369,6 @@ function emitLoopChildRefs(lines, refs, opts) {
19242
19369
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
19243
19370
  }
19244
19371
  }
19245
- function emitPreambleRegionEffects(lines, regions, mapPreambleWrapped, opts) {
19246
- if (regions.length === 0) return;
19247
- const { indent, elVar } = opts;
19248
- for (const region of regions) {
19249
- const v = varSlotId(region.slotId);
19250
- lines.push(`${indent}{ let __last_${v}`);
19251
- lines.push(`${indent}createEffect(() => {`);
19252
- if (mapPreambleWrapped) lines.push(`${indent} ${mapPreambleWrapped}`);
19253
- lines.push(`${indent} const __html_${v} = ${region.valueExpr}`);
19254
- lines.push(`${indent} if (__last_${v} === undefined) { __last_${v} = __html_${v}; return }`);
19255
- lines.push(`${indent} if (__html_${v} !== __last_${v}) { __last_${v} = __html_${v}; patchSlotRange(${elVar}, '${region.slotId}', __html_${v}) }`);
19256
- lines.push(`${indent}}) }`);
19257
- }
19258
- }
19259
19372
  function stringifyLoop(lines, plan) {
19260
19373
  switch (plan.kind) {
19261
19374
  case "static":
@@ -19354,26 +19467,33 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
19354
19467
  ...reactiveEffects?.attrSlots.map((s) => s.slotId) ?? [],
19355
19468
  ...childRefs.map((r2) => r2.childSlotId)
19356
19469
  ];
19357
- const textSlotIds = reactiveEffects?.outerTexts.map((t) => t.slotId) ?? [];
19358
- if (elementSlotIds.length > 0 || textSlotIds.length > 0) {
19359
- const built = buildSkeletonPathPlan(plan.skeletonPaths, "__el", { elementSlotIds, textSlotIds });
19470
+ if (elementSlotIds.length > 0) {
19471
+ const built = buildSkeletonPathPlan(plan.skeletonPaths, "__el", { elementSlotIds, textSlotIds: [] });
19360
19472
  if (built.arrayElems.length > 0) {
19361
19473
  pathPlan = built;
19362
19474
  lines.push(`${bodyIndent}const __p = __existing ? null : [${built.arrayElems.join(", ")}]`);
19363
19475
  }
19364
19476
  }
19365
19477
  }
19366
- if (reactiveEffects !== null) {
19478
+ const textClaimPathExprs = /* @__PURE__ */ new Map();
19479
+ if (hoistedTpl && plan.skeletonPaths) {
19480
+ for (const text of reactiveEffects?.outerTexts ?? []) {
19481
+ const path25 = plan.skeletonPaths.textMarkerPaths.get(text.slotId);
19482
+ if (path25) textClaimPathExprs.set(text.slotId, `__existing ? [] : [${path25.join(", ")}]`);
19483
+ }
19484
+ }
19485
+ if (reactiveEffects !== null || preambleRegions.length > 0) {
19367
19486
  stringifyReactiveEffects(lines, reactiveEffects, {
19368
19487
  indent: bodyIndent,
19369
19488
  elVar: "__el",
19370
19489
  bodyIsMultiRoot,
19371
19490
  elementIndexBySlot: pathPlan?.elementIndexBySlot,
19372
- textIndexBySlot: pathPlan?.textIndexBySlot
19491
+ textClaimPathExprs,
19492
+ preambleRegions,
19493
+ mapPreambleWrapped
19373
19494
  });
19374
19495
  }
19375
19496
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
19376
- emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: "__el" });
19377
19497
  lines.push(`${bodyIndent}return __el`);
19378
19498
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
19379
19499
  }
@@ -19474,10 +19594,13 @@ function stringifyStaticLoop(lines, plan) {
19474
19594
  }
19475
19595
  lines.push(` }`);
19476
19596
  }
19477
- for (const text of texts) {
19478
- const vn = `__rt_${varSlotId(text.slotId)}`;
19479
- lines.push(` { const [${vn}] = $t(__iterEl, '${text.slotId}')`);
19480
- lines.push(` if (${vn}) createEffect(() => { ${vn}.textContent = String(${text.expression}) }${profileBindingId(pc, text.slotId)}) }`);
19597
+ if (texts.length > 0) {
19598
+ const slots = texts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19599
+ const writer = claimWriterVarName(slots, varSlotId);
19600
+ lines.push(` const ${writer} = lazySlots(__iterEl, ${claimPlanLiteral(slots)})`);
19601
+ for (const text of texts) {
19602
+ lines.push(` createEffect(() => { ${writer}('${text.slotId}', String(${text.expression})) }${profileBindingId(pc, text.slotId)})`);
19603
+ }
19481
19604
  }
19482
19605
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__iterEl", bodyIsMultiRoot: false });
19483
19606
  lines.push(` }`);
@@ -19495,6 +19618,7 @@ var init_loop = __esm({
19495
19618
  init_skeleton_paths();
19496
19619
  init_component_loop();
19497
19620
  init_composite_loop();
19621
+ init_claim_plan();
19498
19622
  }
19499
19623
  });
19500
19624
 
@@ -19550,13 +19674,18 @@ function emitReactive(lines, inner, indent, pc) {
19550
19674
  if (inner.childLevels.length > 0) {
19551
19675
  stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc);
19552
19676
  }
19553
- for (const text of emit.reactiveTexts) {
19677
+ const conditionalTexts = emit.reactiveTexts.filter((t) => t.insideConditional);
19678
+ const plainTexts = emit.reactiveTexts.filter((t) => !t.insideConditional);
19679
+ for (const text of conditionalTexts) {
19554
19680
  const bf = profileBindingId(pc, text.slotId);
19555
- if (text.insideConditional) {
19556
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__innerEl${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`);
19557
- } else {
19558
- lines.push(`${indent} { const [__rt] = $t(__innerEl${uid}, '${text.slotId}')`);
19559
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`);
19681
+ lines.push(`${indent} createEffect(() => { claimSlots(__innerEl${uid}, [{ id: '${text.slotId}', kind: 'text', path: [] }]).write('${text.slotId}', String(${text.wrappedExpression})) }${bf})`);
19682
+ }
19683
+ if (plainTexts.length > 0) {
19684
+ const slots = plainTexts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19685
+ const writer = claimWriterVarName(slots, varSlotId);
19686
+ lines.push(`${indent} const ${writer} = lazySlots(__innerEl${uid}, ${claimPlanLiteral(slots)})`);
19687
+ for (const text of plainTexts) {
19688
+ lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`);
19560
19689
  }
19561
19690
  }
19562
19691
  for (const attr of emit.reactiveAttrs) {
@@ -19618,6 +19747,7 @@ var init_inner_loop = __esm({
19618
19747
  init_emit_reactive();
19619
19748
  init_template_parse();
19620
19749
  init_loop();
19750
+ init_claim_plan();
19621
19751
  }
19622
19752
  });
19623
19753
 
@@ -19943,11 +20073,16 @@ function emitPlain(lines, plan) {
19943
20073
  indent: " ",
19944
20074
  singleRootLayout: "inline"
19945
20075
  });
19946
- if (reactiveEffects !== null) {
19947
- stringifyReactiveEffects(lines, reactiveEffects, { indent: " ", elVar: "__el", bodyIsMultiRoot });
20076
+ if (reactiveEffects !== null || preambleRegions.length > 0) {
20077
+ stringifyReactiveEffects(lines, reactiveEffects, {
20078
+ indent: " ",
20079
+ elVar: "__el",
20080
+ bodyIsMultiRoot,
20081
+ preambleRegions,
20082
+ mapPreambleWrapped
20083
+ });
19948
20084
  }
19949
20085
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19950
- emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: " ", elVar: "__el" });
19951
20086
  lines.push(` return __el`);
19952
20087
  lines.push(` }, '${markerId}'${loopBfId})`);
19953
20088
  }
@@ -20038,13 +20173,13 @@ function emitArmBody(lines, body2, mode2, indent, profileComponentName) {
20038
20173
  }
20039
20174
  lines.push(`${indent}} }`);
20040
20175
  }
20041
- for (const te of body2.textEffects) {
20042
- const v = varSlotId(te.slotId);
20043
- lines.push(`${indent}let __anchor_${v} = $t(__branchScope, '${te.slotId}')[0]`);
20044
- lines.push(`${indent}__disposers.push(createDisposableEffect(() => {`);
20045
- lines.push(`${indent} const __val = ${te.expression}`);
20046
- lines.push(`${indent} __anchor_${v} = __bfText(__anchor_${v}, __val)`);
20047
- lines.push(`${indent}}${bindingBfId(te.slotId)}))`);
20176
+ if (body2.textEffects.length > 0) {
20177
+ const slots = body2.textEffects.map((te) => ({ id: te.slotId, kind: "markup", path: [] }));
20178
+ const writer = claimWriterVarName(slots, varSlotId);
20179
+ lines.push(`${indent}const ${writer} = lazySlots(__branchScope, ${claimPlanLiteral(slots)})`);
20180
+ for (const te of body2.textEffects) {
20181
+ lines.push(`${indent}__disposers.push(createDisposableEffect(() => { ${writer}('${te.slotId}', escapeTextOrNode(${te.expression})) }${bindingBfId(te.slotId)}))`);
20182
+ }
20048
20183
  }
20049
20184
  if (body2.loops.length > 0) {
20050
20185
  stringifyBranchLoops(lines, body2.loops);
@@ -20077,6 +20212,7 @@ var init_insert = __esm({
20077
20212
  init_branch_loop();
20078
20213
  init_event_listener();
20079
20214
  init_component_scope();
20215
+ init_claim_plan();
20080
20216
  }
20081
20217
  });
20082
20218
 
@@ -20340,7 +20476,6 @@ var init_control_flow = __esm({
20340
20476
  // ../jsx/src/ir-to-client-js/element-refs.ts
20341
20477
  function generateElementRefs(ctx2) {
20342
20478
  const regularSlots = /* @__PURE__ */ new Set();
20343
- const textSlots = /* @__PURE__ */ new Set();
20344
20479
  const componentSlots = /* @__PURE__ */ new Set();
20345
20480
  const conditionalSlotIds = collectConditionalSlotIds(ctx2);
20346
20481
  for (const elem of ctx2.interactiveElements) {
@@ -20348,11 +20483,6 @@ function generateElementRefs(ctx2) {
20348
20483
  regularSlots.add(elem.slotId);
20349
20484
  }
20350
20485
  }
20351
- for (const elem of ctx2.dynamicElements) {
20352
- if (!elem.insideConditional) {
20353
- textSlots.add(elem.slotId);
20354
- }
20355
- }
20356
20486
  for (const elem of ctx2.conditionalElements) {
20357
20487
  regularSlots.add(elem.slotId);
20358
20488
  }
@@ -20381,10 +20511,9 @@ function generateElementRefs(ctx2) {
20381
20511
  for (const slotId of componentSlots) {
20382
20512
  regularSlots.delete(slotId);
20383
20513
  }
20384
- if (regularSlots.size === 0 && textSlots.size === 0 && componentSlots.size === 0) return "";
20514
+ if (regularSlots.size === 0 && componentSlots.size === 0) return "";
20385
20515
  const refLines = [];
20386
20516
  emitSlotRefs(refLines, [...regularSlots], "$");
20387
- emitSlotRefs(refLines, [...textSlots], "$t");
20388
20517
  emitSlotRefs(refLines, [...componentSlots], "$c");
20389
20518
  return refLines.join("\n");
20390
20519
  }
@@ -21034,6 +21163,107 @@ var init_ir_to_client_js = __esm({
21034
21163
  }
21035
21164
  });
21036
21165
 
21166
+ // ../jsx/src/ir-to-client-js/client-only-elision.ts
21167
+ function decideClientOnlyElision(root2) {
21168
+ walkNode(root2, [], /* @__PURE__ */ new Set(), { bailed: false });
21169
+ }
21170
+ function walkNode(node, path25, forceCloseAncestors, state2) {
21171
+ if (state2.bailed) return;
21172
+ if (node.type === "element") {
21173
+ if (!elementIsPathSafe(node.tag, flattenSkeletonChildren(node.children))) {
21174
+ state2.bailed = true;
21175
+ return;
21176
+ }
21177
+ const groupIdx = skeletonForceCloseGroup(node.tag);
21178
+ if (groupIdx >= 0 && forceCloseAncestors.has(groupIdx)) {
21179
+ state2.bailed = true;
21180
+ return;
21181
+ }
21182
+ const nextAncestors = groupIdx >= 0 ? /* @__PURE__ */ new Set([...forceCloseAncestors, groupIdx]) : forceCloseAncestors;
21183
+ walkChildren(flattenSkeletonChildren(node.children), path25, nextAncestors, state2);
21184
+ } else if (node.type === "fragment") {
21185
+ walkChildren(flattenSkeletonChildren(node.children), path25, forceCloseAncestors, state2);
21186
+ }
21187
+ }
21188
+ function walkChildren(children2, parentPath, forceCloseAncestors, state2) {
21189
+ let frozen = false;
21190
+ let idx = 0;
21191
+ let pendingText = false;
21192
+ for (let i = 0; i < children2.length; i++) {
21193
+ if (state2.bailed) return;
21194
+ const child = children2[i];
21195
+ switch (child.type) {
21196
+ case "text": {
21197
+ if (child.value === "") continue;
21198
+ if (!pendingText) idx += 1;
21199
+ pendingText = true;
21200
+ continue;
21201
+ }
21202
+ case "expression": {
21203
+ if (child.expr === "null" || child.expr === "undefined") continue;
21204
+ if (child.clientOnly && child.slotId) {
21205
+ const adjacent = isTextLike(children2[i - 1]) || isTextLike(children2[i + 1]);
21206
+ if (!frozen && !adjacent) {
21207
+ markElided(child, [...parentPath, idx]);
21208
+ frozen = true;
21209
+ } else {
21210
+ frozen = true;
21211
+ }
21212
+ idx += 1;
21213
+ pendingText = false;
21214
+ continue;
21215
+ }
21216
+ frozen = true;
21217
+ idx += 1;
21218
+ pendingText = false;
21219
+ continue;
21220
+ }
21221
+ case "element": {
21222
+ if (!elementIsPathSafe(child.tag, flattenSkeletonChildren(child.children))) {
21223
+ state2.bailed = true;
21224
+ return;
21225
+ }
21226
+ if (!frozen) {
21227
+ walkNode(child, [...parentPath, idx], forceCloseAncestors, state2);
21228
+ }
21229
+ idx += 1;
21230
+ pendingText = false;
21231
+ continue;
21232
+ }
21233
+ case "fragment":
21234
+ continue;
21235
+ // already flattened
21236
+ default:
21237
+ frozen = true;
21238
+ idx += 1;
21239
+ pendingText = false;
21240
+ continue;
21241
+ }
21242
+ }
21243
+ }
21244
+ function isTextLike(node) {
21245
+ if (!node) return false;
21246
+ if (node.type === "text") return node.value !== "";
21247
+ if (node.type === "expression") return node.expr !== "null" && node.expr !== "undefined";
21248
+ return false;
21249
+ }
21250
+ function markElided(expr, path25) {
21251
+ expr.markerless = true;
21252
+ expr.elidedPath = path25;
21253
+ }
21254
+ function elementIsPathSafe(tag, flatChildren) {
21255
+ if (SKELETON_PATH_HAZARD_TAGS.has(tag)) return false;
21256
+ if (VOID_ELEMENTS.has(tag) && flatChildren.length > 0) return false;
21257
+ if (tag === "tr" && hasForeignTableRowContent(flatChildren)) return false;
21258
+ return true;
21259
+ }
21260
+ var init_client_only_elision = __esm({
21261
+ "../jsx/src/ir-to-client-js/client-only-elision.ts"() {
21262
+ "use strict";
21263
+ init_html_template();
21264
+ }
21265
+ });
21266
+
21037
21267
  // ../jsx/src/css-layer-prefixer.ts
21038
21268
  function prefixClass(cls, layerName) {
21039
21269
  if (!cls || cls.startsWith("layer-")) return cls;
@@ -22208,7 +22438,7 @@ function checkRichTypeMethodCalls(root2, metadata, errors) {
22208
22438
  if (!metadata.propsType) return;
22209
22439
  const matchers = prepareLoweringMatchers(metadata);
22210
22440
  const seen = /* @__PURE__ */ new Set();
22211
- walkNode(root2, metadata, EMPTY_BINDINGS2, matchers, errors, seen);
22441
+ walkNode2(root2, metadata, EMPTY_BINDINGS2, matchers, errors, seen);
22212
22442
  }
22213
22443
  function isLoweringClaimed(matchers, callee, args2) {
22214
22444
  return matchers.some((m) => m(callee, args2) !== null);
@@ -22336,7 +22566,7 @@ function walkAttrValue(value2, clientOnly, loc, meta, bindings, matchers, errors
22336
22566
  walkTemplateParts(value2.parts, loc, meta, bindings, matchers, errors, seen);
22337
22567
  }
22338
22568
  }
22339
- function walkNode(node, meta, bindings, matchers, errors, seen) {
22569
+ function walkNode2(node, meta, bindings, matchers, errors, seen) {
22340
22570
  if (node.type === "expression") {
22341
22571
  if (!node.clientOnly && node.parsed) checkExpr(node.parsed, node.loc, meta, bindings, matchers, errors, seen);
22342
22572
  } else if (node.type === "conditional") {
@@ -22356,11 +22586,11 @@ function walkNode(node, meta, bindings, matchers, errors, seen) {
22356
22586
  case "component":
22357
22587
  case "fragment":
22358
22588
  case "provider":
22359
- for (const child of node.children) walkNode(child, meta, bindings, matchers, errors, seen);
22589
+ for (const child of node.children) walkNode2(child, meta, bindings, matchers, errors, seen);
22360
22590
  break;
22361
22591
  case "async":
22362
- walkNode(node.fallback, meta, bindings, matchers, errors, seen);
22363
- for (const child of node.children) walkNode(child, meta, bindings, matchers, errors, seen);
22592
+ walkNode2(node.fallback, meta, bindings, matchers, errors, seen);
22593
+ for (const child of node.children) walkNode2(child, meta, bindings, matchers, errors, seen);
22364
22594
  break;
22365
22595
  case "loop": {
22366
22596
  if (node.clientOnly) break;
@@ -22369,29 +22599,29 @@ function walkNode(node, meta, bindings, matchers, errors, seen) {
22369
22599
  const arrayType = node.arrayParsed ? resolveReceiverType(node.arrayParsed, meta, bindings) : null;
22370
22600
  loopBindings.set(node.param, arrayType?.kind === "array" ? arrayType.elementType ?? null : null);
22371
22601
  if (node.index) loopBindings.set(node.index, null);
22372
- for (const child of node.children) walkNode(child, meta, loopBindings, matchers, errors, seen);
22602
+ for (const child of node.children) walkNode2(child, meta, loopBindings, matchers, errors, seen);
22373
22603
  if (node.childComponent) {
22374
- for (const child of node.childComponent.children) walkNode(child, meta, loopBindings, matchers, errors, seen);
22604
+ for (const child of node.childComponent.children) walkNode2(child, meta, loopBindings, matchers, errors, seen);
22375
22605
  }
22376
22606
  for (const nested of node.nestedComponents ?? []) {
22377
- for (const child of nested.children) walkNode(child, meta, loopBindings, matchers, errors, seen);
22607
+ for (const child of nested.children) walkNode2(child, meta, loopBindings, matchers, errors, seen);
22378
22608
  }
22379
22609
  for (const seg of node.flatMapCallback?.segments ?? []) {
22380
- if (seg.kind === "jsx") walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
22610
+ if (seg.kind === "jsx") walkNode2(seg.ir, meta, loopBindings, matchers, errors, seen);
22381
22611
  }
22382
22612
  for (const seg of node.preamble?.segments ?? []) {
22383
- if (seg.kind === "jsx") walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
22613
+ if (seg.kind === "jsx") walkNode2(seg.ir, meta, loopBindings, matchers, errors, seen);
22384
22614
  }
22385
22615
  break;
22386
22616
  }
22387
22617
  case "conditional":
22388
22618
  if (node.clientOnly) break;
22389
- walkNode(node.whenTrue, meta, bindings, matchers, errors, seen);
22390
- walkNode(node.whenFalse, meta, bindings, matchers, errors, seen);
22619
+ walkNode2(node.whenTrue, meta, bindings, matchers, errors, seen);
22620
+ walkNode2(node.whenFalse, meta, bindings, matchers, errors, seen);
22391
22621
  break;
22392
22622
  case "if-statement":
22393
- walkNode(node.consequent, meta, bindings, matchers, errors, seen);
22394
- if (node.alternate) walkNode(node.alternate, meta, bindings, matchers, errors, seen);
22623
+ walkNode2(node.consequent, meta, bindings, matchers, errors, seen);
22624
+ if (node.alternate) walkNode2(node.alternate, meta, bindings, matchers, errors, seen);
22395
22625
  break;
22396
22626
  }
22397
22627
  }
@@ -22464,6 +22694,7 @@ function compileMultipleComponents(source, filePath, componentNames, options2) {
22464
22694
  };
22465
22695
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
22466
22696
  checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
22697
+ decideClientOnlyElision(componentIR.root);
22467
22698
  if (options2.cssLayerPrefix) {
22468
22699
  applyCssLayerPrefix(componentIR, options2.cssLayerPrefix);
22469
22700
  }
@@ -22801,6 +23032,7 @@ function compileJSX(source, filePath, options2) {
22801
23032
  };
22802
23033
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
22803
23034
  checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
23035
+ decideClientOnlyElision(componentIR.root);
22804
23036
  if (ctx2.importedClientSignalNames.size > 0) {
22805
23037
  const sources = /* @__PURE__ */ new Set();
22806
23038
  for (const imp of ctx2.imports) {
@@ -22922,6 +23154,7 @@ var init_compiler = __esm({
22922
23154
  init_jsx_to_ir();
22923
23155
  init_builtins();
22924
23156
  init_ir_to_client_js();
23157
+ init_client_only_elision();
22925
23158
  init_emit_module_level();
22926
23159
  init_imports();
22927
23160
  init_component_scope();
@@ -26867,6 +27100,7 @@ __export(src_exports, {
26867
27100
  createProgramForFile: () => createProgramForFile,
26868
27101
  dangerousInnerHtmlDiagnostic: () => dangerousInnerHtmlDiagnostic,
26869
27102
  dangerousInnerHtmlMetacharViolation: () => dangerousInnerHtmlMetacharViolation,
27103
+ decideClientOnlyElision: () => decideClientOnlyElision,
26870
27104
  describeFallback: () => describeFallback,
26871
27105
  diffProfiles: () => diffProfiles,
26872
27106
  diffStaticBudget: () => diffStaticBudget,
@@ -26981,6 +27215,7 @@ var init_src2 = __esm({
26981
27215
  init_analyzer();
26982
27216
  init_shared_program();
26983
27217
  init_jsx_to_ir();
27218
+ init_client_only_elision();
26984
27219
  init_module_exports();
26985
27220
  init_interface();
26986
27221
  init_test_adapter();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/cli",
3
- "version": "0.26.4",
3
+ "version": "0.27.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.26.4",
33
- "@barefootjs/shared": "0.26.4"
32
+ "@barefootjs/client": "0.27.0",
33
+ "@barefootjs/shared": "0.27.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@barefootjs/jsx": "0.26.4",
36
+ "@barefootjs/jsx": "0.27.0",
37
37
  "@types/node": "^22.0.0",
38
38
  "@happy-dom/global-registrator": "^20.0.11",
39
39
  "happy-dom": "^20.0.11"