@barefootjs/jsx 0.34.0 → 0.35.1

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 (148) hide show
  1. package/dist/analyzer-context.d.ts.map +1 -1
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1072 -903
  6. package/dist/ir-to-client-js/collect-elements.d.ts +22 -2
  7. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  8. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  9. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
  10. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
  11. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts +14 -2
  12. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +11 -0
  14. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +6 -1
  16. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/control-flow/plan/build-plain-row.d.ts +52 -0
  19. package/dist/ir-to-client-js/control-flow/plan/build-plain-row.d.ts.map +1 -0
  20. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +5 -2
  21. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +24 -44
  23. package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts +6 -7
  25. package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts.map +1 -1
  26. package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts +5 -7
  27. package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts.map +1 -1
  28. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -1
  29. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +14 -0
  30. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  31. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -6
  32. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  33. package/dist/ir-to-client-js/control-flow/shared.d.ts +11 -9
  34. package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
  35. package/dist/ir-to-client-js/control-flow/stringify/composite-loop.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +6 -14
  37. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +1 -3
  39. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +19 -3
  41. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  43. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +5 -1
  44. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  45. package/dist/ir-to-client-js/csr-substitute.d.ts +16 -0
  46. package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
  47. package/dist/ir-to-client-js/emit-reactive.d.ts +54 -0
  48. package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
  49. package/dist/ir-to-client-js/emit-registration.d.ts.map +1 -1
  50. package/dist/ir-to-client-js/html-template.d.ts +2 -2
  51. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  52. package/dist/ir-to-client-js/imports.d.ts +2 -2
  53. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  54. package/dist/ir-to-client-js/index.d.ts.map +1 -1
  55. package/dist/ir-to-client-js/markup-slots.d.ts +21 -0
  56. package/dist/ir-to-client-js/markup-slots.d.ts.map +1 -0
  57. package/dist/ir-to-client-js/reactivity.d.ts +62 -18
  58. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  59. package/dist/ir-to-client-js/safe-html.d.ts +158 -0
  60. package/dist/ir-to-client-js/safe-html.d.ts.map +1 -0
  61. package/dist/ir-to-client-js/types.d.ts +18 -2
  62. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  63. package/dist/ir-to-client-js/utils.d.ts +34 -1
  64. package/dist/ir-to-client-js/utils.d.ts.map +1 -1
  65. package/dist/jsx-to-ir.d.ts.map +1 -1
  66. package/dist/prop-rewrite.d.ts +8 -3
  67. package/dist/prop-rewrite.d.ts.map +1 -1
  68. package/dist/props-binding.d.ts +22 -1
  69. package/dist/props-binding.d.ts.map +1 -1
  70. package/dist/reactivity-checker.d.ts +18 -0
  71. package/dist/reactivity-checker.d.ts.map +1 -1
  72. package/dist/ssr-defaults.d.ts.map +1 -1
  73. package/dist/types.d.ts +5 -10
  74. package/dist/types.d.ts.map +1 -1
  75. package/package.json +2 -2
  76. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +138 -22
  77. package/src/__tests__/binding-scope-ratchet.test.ts +25 -2
  78. package/src/__tests__/build-plain-row-consolidation.test.ts +120 -0
  79. package/src/__tests__/child-prop-fallback-wrap.test.ts +4 -2
  80. package/src/__tests__/client-template-escape-soundness.test.ts +538 -0
  81. package/src/__tests__/compiler-stress-1244.test.ts +8 -8
  82. package/src/__tests__/csr-template-loop-shadowing.test.ts +4 -2
  83. package/src/__tests__/flatmap-segments.test.ts +1 -1
  84. package/src/__tests__/issue-2868-cond-arm-tag-collision.test.ts +185 -0
  85. package/src/__tests__/issue-2869-attr-dedup-guard.test.ts +141 -0
  86. package/src/__tests__/lazy-conditional.test.ts +52 -12
  87. package/src/__tests__/lazy-preamble.test.ts +11 -11
  88. package/src/__tests__/lazy-row-eligibility.test.ts +8 -3
  89. package/src/__tests__/loop-child-reactive-attr-const-shadow.test.ts +25 -3
  90. package/src/__tests__/loop-index-reactivity.test.ts +208 -0
  91. package/src/__tests__/loop-item-conditional-codegen.test.ts +4 -2
  92. package/src/__tests__/map-arbitrary-body.test.ts +1 -1
  93. package/src/__tests__/map-body-brand-checker.test.ts +176 -0
  94. package/src/__tests__/nested-loop-index-param.test.ts +41 -8
  95. package/src/__tests__/nested-loop-non-outer-reactivity.test.ts +177 -0
  96. package/src/__tests__/nested-loop-plain.test.ts +39 -25
  97. package/src/__tests__/nested-loop-reactive-attrs.test.ts +16 -6
  98. package/src/__tests__/preamble-branch-arm-rerun.test.ts +135 -0
  99. package/src/__tests__/reactive-attrs-in-map.test.ts +13 -10
  100. package/src/__tests__/rewrite-destructured-props.test.ts +37 -0
  101. package/src/__tests__/safe-html-splice-door.test.ts +61 -0
  102. package/src/__tests__/ssr-defaults.test.ts +59 -0
  103. package/src/__tests__/style-css-var-reactive.test.ts +4 -1
  104. package/src/analyzer-context.ts +12 -10
  105. package/src/analyzer.ts +4 -0
  106. package/src/index.ts +8 -2
  107. package/src/ir-to-client-js/collect-elements.ts +126 -32
  108. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +20 -28
  109. package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +9 -7
  110. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +12 -6
  111. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +36 -59
  112. package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +25 -4
  113. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +36 -9
  114. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +35 -35
  115. package/src/ir-to-client-js/control-flow/plan/build-plain-row.ts +116 -0
  116. package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +23 -8
  117. package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +24 -45
  118. package/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts +11 -10
  119. package/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts +11 -13
  120. package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +13 -3
  121. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +14 -0
  122. package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -6
  123. package/src/ir-to-client-js/control-flow/shared.ts +40 -17
  124. package/src/ir-to-client-js/control-flow/stringify/composite-loop.ts +2 -1
  125. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +20 -66
  126. package/src/ir-to-client-js/control-flow/stringify/insert.ts +4 -2
  127. package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +22 -15
  128. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +53 -11
  129. package/src/ir-to-client-js/control-flow/stringify/loop.ts +4 -2
  130. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +29 -18
  131. package/src/ir-to-client-js/csr-substitute.ts +29 -0
  132. package/src/ir-to-client-js/emit-reactive.ts +92 -6
  133. package/src/ir-to-client-js/emit-registration.ts +2 -4
  134. package/src/ir-to-client-js/html-template.ts +67 -245
  135. package/src/ir-to-client-js/imports.ts +3 -0
  136. package/src/ir-to-client-js/index.ts +4 -4
  137. package/src/ir-to-client-js/markup-slots.ts +25 -0
  138. package/src/ir-to-client-js/reactivity.ts +73 -27
  139. package/src/ir-to-client-js/safe-html.ts +243 -0
  140. package/src/ir-to-client-js/types.ts +19 -3
  141. package/src/ir-to-client-js/utils.ts +58 -10
  142. package/src/jsx-to-ir.ts +136 -24
  143. package/src/prop-rewrite.ts +8 -4
  144. package/src/props-binding.ts +37 -1
  145. package/src/reactivity-checker.ts +60 -0
  146. package/src/ssr-defaults.ts +45 -0
  147. package/src/types.ts +5 -10
  148. package/src/__tests__/text-slot-escaping.test.ts +0 -65
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Stringify `InnerLoopsPlan` into source lines.
3
3
  *
4
- * Two emission shapes (mode-discriminated, byte-identical to the legacy
5
- * `emitInnerLoopSetup`):
4
+ * One emission shape (#2865 the former second "static forEach,
5
+ * setup-only" shape for a nested loop whose array didn't reference the
6
+ * outer item was removed: it answered "does the array mention the outer
7
+ * item" instead of "is this reactive at all", so it silently froze any
8
+ * other reactivity — a signal, a memo, a prop, a preamble local — the
9
+ * inner array or its rows depended on):
6
10
  *
7
11
  * reactive — full mapArray:
8
12
  * <indent>// Reactive inner loop: <arraySrc>
@@ -17,23 +21,11 @@
17
21
  * <indent> per-item conditionals: insert() over __innerEl<uid> (#2706)
18
22
  * <indent> return __innerEl<uid>
19
23
  * <indent>}) }
20
- *
21
- * static — forEach setup-only:
22
- * <indent>// Initialize <arraySrc> loop components and events
23
- * <indent>{ const __ic<uid> = <containerExpr>
24
- * <indent>if (__ic<uid> && <arrayExpr>) <arrayExpr>.forEach((<param>, __innerIdx<uid>) => {
25
- * <indent> const __innerEl<uid> = __ic<uid>.children[__innerIdx<uid>]
26
- * <indent> if (!__innerEl<uid>) return
27
- * <indent> <preludeStatements*> // raw preamble (#1064)
28
- * <indent> __innerEl<uid>.setAttribute('<keyAttr>', String(<rawKey>))?
29
- * <indent> emitComponentAndEventSetup(...)
30
- * <indent> recurse on childLevels
31
- * <indent>}) }
32
24
  */
33
25
 
34
26
  import { keyAttrName, mapArrayKeyArgs, profileBindingId, varSlotId } from '../../utils.ts'
35
27
  import { emitComponentAndEventSetup } from '../shared.ts'
36
- import { emitAttrUpdate } from '../../emit-reactive.ts'
28
+ import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
37
29
  import { emitMultiRootTemplateCloneLines, namespaceWrapForTemplate } from './template-parse.ts'
38
30
  import { emitLoopChildRefs } from './loop.ts'
39
31
  import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
@@ -50,18 +42,13 @@ export function stringifyInnerLoops(
50
42
  pc?: string,
51
43
  ): void {
52
44
  for (const inner of plan) {
53
- if (inner.emit.mode === 'reactive') {
54
- emitReactive(lines, inner, indent, pc)
55
- } else {
56
- emitStatic(lines, inner, indent, pc)
57
- }
45
+ emitReactive(lines, inner, indent, pc)
58
46
  }
59
47
  }
60
48
 
61
49
  function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
62
50
  const uid = inner.uidSuffix
63
51
  const emit = inner.emit
64
- if (emit.mode !== 'reactive') return // narrow
65
52
 
66
53
  lines.push(`${indent}// Reactive inner loop: ${inner.arraySrc}`)
67
54
  lines.push(`${indent}{ const __ic${uid} = ${inner.containerExpr}`)
@@ -108,6 +95,8 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
108
95
  [...emit.events],
109
96
  inner.outerLoopParam,
110
97
  inner.outerLoopParamBindings,
98
+ false,
99
+ inner.outerLoopIndex,
111
100
  )
112
101
  }
113
102
  if (inner.childLevels.length > 0) {
@@ -132,11 +121,13 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
132
121
  lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`)
133
122
  }
134
123
  }
124
+ if (emit.reactiveAttrs.length > 0) lines.push(`${indent} ${DEDUP_STORE_DECL}`)
125
+ let attrOrdinal = 0
135
126
  for (const attr of emit.reactiveAttrs) {
136
127
  const targetVar = `__ta_${attr.slotId.replace(/[^a-zA-Z0-9]/g, '_')}`
137
128
  lines.push(`${indent} { const ${targetVar} = qsa(__innerEl${uid}, '[bf="${attr.slotId}"]')`)
138
129
  lines.push(`${indent} if (${targetVar}) createEffect(() => {`)
139
- for (const stmt of emitAttrUpdate(targetVar, attr.attrName, attr.wrappedExpression, attr.meta)) {
130
+ for (const stmt of emitDedupedAttrUpdate(targetVar, attr.attrName, attr.wrappedExpression, attr.meta, attrOrdinal++)) {
140
131
  lines.push(`${indent} ${stmt}`)
141
132
  }
142
133
  lines.push(`${indent} }${profileBindingId(pc, attr.slotId)}) }`)
@@ -146,7 +137,13 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
146
137
  // ternary. Mirrors `stringifyBranchInnerLoops`'s identical call for a
147
138
  // branch-scoped inner loop's own conditionals.
148
139
  if (emit.conditionals.length > 0) {
149
- stringifyLoopChildConditionals(lines, emit.conditionals, `${indent} `, pc)
140
+ // This inner loop's own preamble (`preludeStatements` above) is not a
141
+ // single re-runnable string like the outer row's `mapPreambleWrapped`,
142
+ // so there is nothing correct to thread here — `readsPreamble &&
143
+ // mapPreambleWrapped`'s guard downstream makes this `undefined` a
144
+ // no-op, exactly as before this fix (inner-loop preambles are out of
145
+ // scope — see PR notes on #2596 follow-up).
146
+ stringifyLoopChildConditionals(lines, emit.conditionals, `${indent} `, pc, undefined)
150
147
  }
151
148
  // Imperative ref callbacks fire on every renderItem invocation, which
152
149
  // means every mount: SSR hydration, initial CSR creation, and same-key
@@ -163,46 +160,3 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
163
160
  // instead of the default `data-key`.
164
161
  lines.push(`${indent}}, '${inner.markerId}'${mapArrayKeyArgs(profileBindingId(pc, inner.slotId), !!emit.wrappedKey, inner.keyDepth)}) }`)
165
162
  }
166
-
167
- function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
168
- const uid = inner.uidSuffix
169
- const emit = inner.emit
170
- if (emit.mode !== 'static') return
171
-
172
- lines.push(`${indent}// Initialize ${inner.arraySrc} loop components and events`)
173
- lines.push(`${indent}{ const __ic${uid} = ${inner.containerExpr}`)
174
- // Guard: inner array may be undefined when inside a conditional branch.
175
- lines.push(`${indent}if (__ic${uid} && ${inner.arrayExpr}) ${inner.arrayExpr}.forEach((${inner.param}, __innerIdx${uid}) => {`)
176
- lines.push(`${indent} const __innerEl${uid} = __ic${uid}.children[__innerIdx${uid}]`)
177
- lines.push(`${indent} if (!__innerEl${uid}) return`)
178
- // Body-entry statements: inner-`.map()` preamble locals (raw, since the
179
- // forEach param is the literal item — #1064). Emitted before component
180
- // and event setup so their prop getters / handlers resolve the locals.
181
- for (const stmt of emit.preludeStatements) {
182
- lines.push(`${indent} ${stmt}`)
183
- }
184
- if (emit.rawKey) {
185
- lines.push(`${indent} __innerEl${uid}.setAttribute('${keyAttrName(inner.keyDepth)}', String(${emit.rawKey}))`)
186
- }
187
- emitComponentAndEventSetup(
188
- lines,
189
- `${indent} `,
190
- `__innerEl${uid}`,
191
- [...emit.components],
192
- [...emit.events],
193
- inner.outerLoopParam,
194
- inner.outerLoopParamBindings,
195
- )
196
- if (inner.childLevels.length > 0) {
197
- stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc)
198
- }
199
- // Imperative ref callbacks for static inner loops — fire once per
200
- // forEach iteration (#1244). Static arrays don't reactively re-iterate,
201
- // so this is effectively a one-shot per item.
202
- emitLoopChildRefs(lines, emit.childRefs, {
203
- indent: `${indent} `,
204
- elVar: `__innerEl${uid}`,
205
- bodyIsMultiRoot: false,
206
- })
207
- lines.push(`${indent}}) }`)
208
- }
@@ -29,7 +29,7 @@
29
29
  */
30
30
 
31
31
  import { emitRefCall, varSlotId } from '../../utils.ts'
32
- import { emitAttrUpdate } from '../../emit-reactive.ts'
32
+ import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
33
33
  import type { InsertPlan, InsertArm, ArmBody, ScopeRef } from '../plan/types.ts'
34
34
  import { stringifyBranchLoops } from './branch-loop.ts'
35
35
  import { emitListenerLine } from './event-listener.ts'
@@ -163,10 +163,12 @@ function emitArmBody(
163
163
  const v = varSlotId(slotId)
164
164
  const elVar = `__ra_${v}`
165
165
  lines.push(`${indent}{ const ${elVar} = qsa(__branchScope, '[bf="${slotId}"]')`)
166
+ lines.push(`${indent}${DEDUP_STORE_DECL}`)
166
167
  lines.push(`${indent}if (${elVar}) {`)
168
+ let ordinal = 0
167
169
  for (const attr of attrs) {
168
170
  lines.push(`${indent} __disposers.push(createDisposableEffect(() => {`)
169
- for (const stmt of emitAttrUpdate(elVar, attr.attrName, attr.expression, attr)) {
171
+ for (const stmt of emitDedupedAttrUpdate(elVar, attr.attrName, attr.expression, attr, ordinal++)) {
170
172
  lines.push(`${indent} ${stmt}`)
171
173
  }
172
174
  lines.push(`${indent} }${bindingBfId(slotId)}))`)
@@ -60,7 +60,7 @@
60
60
  */
61
61
 
62
62
  import { isBooleanAttr } from '../../../html-constants.ts'
63
- import { emitAttrUpdate } from '../../emit-reactive.ts'
63
+ import { emitDedupedAttrUpdate, dedupGuard } from '../../emit-reactive.ts'
64
64
  import { toHtmlAttrName } from '../../utils.ts'
65
65
  import type { SkeletonSlotPaths } from '../../html-template.ts'
66
66
  import { claimPlanLiteral, type ClaimSlotSpec } from './claim-plan.ts'
@@ -191,6 +191,11 @@ export function stringifyLazyRowLoop(lines: string[], o: StringifyLazyRowOptions
191
191
  // --- createRow ---------------------------------------------------------
192
192
  const b1 = `${indent} `
193
193
  const b2 = `${indent} `
194
+ // Tells the runtime to also call `applyItem` on a pure reorder (#2859
195
+ // follow-up) — see `LazyRowPlan.indexDriven`'s docstring. Omitted (not just
196
+ // `false`) for every loop that never reads the index, byte-stable with
197
+ // this widening's predecessor.
198
+ if (lazyRow.readsIndex) lines.push(`${b1}indexDriven: true,`)
194
199
  lines.push(`${b1}createRow: (__e, ${o.indexParam}) => {`)
195
200
  // Always bound, even with no reactive bindings: the non-hoisted clone
196
201
  // interpolates the per-row template, which reads the param for at least
@@ -225,6 +230,9 @@ export function stringifyLazyRowLoop(lines: string[], o: StringifyLazyRowOptions
225
230
  } else {
226
231
  lines.push(`${b1}applyItem: (__e) => {`)
227
232
  lines.push(`${b2}const ${paramHead} = () => __e.item`)
233
+ // The row's current position (#2859 follow-up) — only when some binding
234
+ // here actually reads it, so every other loop's applyItem is unchanged.
235
+ if (lazyRow.readsIndex) lines.push(`${b2}const ${o.indexParam} = __e.index`)
228
236
  lines.push(`${b2}const __r = __e.refs ?? (__e.refs = [])`)
229
237
  lines.push(`${b2}const __l = __e.last ?? (__e.last = [])`)
230
238
  // Re-run the preamble only when a binding in THIS body reads a local it
@@ -255,6 +263,8 @@ export function stringifyLazyRowLoop(lines: string[], o: StringifyLazyRowOptions
255
263
  for (const g of lazyRow.outerPrimeGetters) lines.push(`${b2}${g}()`)
256
264
  lines.push(`${b2}for (const __e of __es) {`)
257
265
  lines.push(`${b3}const ${paramHead} = () => __e.item`)
266
+ // Same as `applyItem` — the row's current position (#2859 follow-up).
267
+ if (lazyRow.readsIndex) lines.push(`${b3}const ${o.indexParam} = __e.index`)
258
268
  lines.push(`${b3}const __r = __e.refs ?? (__e.refs = [])`)
259
269
  lines.push(`${b3}const __l = __e.last ?? (__e.last = [])`)
260
270
  // Per ENTRY, not once for the batch: the preamble reads the item, so its
@@ -448,11 +458,13 @@ function emitConditional(
448
458
  lines.push(`${ind}} }`)
449
459
  }
450
460
 
451
- /** `entry.last`-backed dedup test. `in` (not a truthiness check) so a
452
- * legitimately `undefined` value still records and still writes once. */
453
- function dedupGuard(ordinal: number): string {
454
- return `!(${ordinal} in __l) || !Object.is(__l[${ordinal}], __x)`
455
- }
461
+ // `dedupGuard` (the `entry.last`-backed dedup test) and the actual guarded
462
+ // write now live in `emit-reactive.ts` as `emitDedupedAttrUpdate` (#2869)
463
+ // shared with every eager reactive-attr emission site. This module's only
464
+ // remaining substrate-specific piece is WHERE `__l` comes from: the
465
+ // reconciler's `entry.last` (see the per-mode `applyItem`/`applyOuter`
466
+ // callers below), not a closure-local `const __l = []`, because those are
467
+ // separate invocations with no shared closure of their own to own it.
456
468
 
457
469
  function emitAttrBinding(
458
470
  lines: string[],
@@ -464,21 +476,16 @@ function emitAttrBinding(
464
476
  // plain read. An ADOPTED row starts with an EMPTY `refs` array and each
465
477
  // binding claims its OWN slot on first use — see `elementAccess`.
466
478
  const target = mode === 'create' ? `__r[${a.refIndex}]` : elementAccess(a)
467
- lines.push(`${ind}{ const __t = ${target}`)
468
- lines.push(`${ind}if (__t) {`)
469
- lines.push(`${ind} const __x = ${a.wrappedExpression}`)
470
479
  const guard = mode === 'create'
471
480
  ? null
472
481
  : mode === 'item'
473
482
  ? dedupGuard(a.ordinal)
474
483
  : `__seed ? (${seedDiffersExpr('__t', a)}) : (${dedupGuard(a.ordinal)})`
475
- const writeIndent = guard ? `${ind} ` : `${ind} `
476
- if (guard) lines.push(`${ind} if (${guard}) {`)
477
- for (const stmt of emitAttrUpdate('__t', a.attrName, '__x', a.meta)) {
478
- lines.push(`${writeIndent}${stmt}`)
484
+ lines.push(`${ind}{ const __t = ${target}`)
485
+ lines.push(`${ind}if (__t) {`)
486
+ for (const stmt of emitDedupedAttrUpdate('__t', a.attrName, a.wrappedExpression, a.meta, a.ordinal, guard)) {
487
+ lines.push(`${ind} ${stmt}`)
479
488
  }
480
- if (guard) lines.push(`${ind} }`)
481
- lines.push(`${ind} __l[${a.ordinal}] = __x`)
482
489
  lines.push(`${ind}} }`)
483
490
  }
484
491
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  import { varSlotId, DATA_BF_PH, keyAttrName, mapArrayKeyArgs, profileBindingId } from '../../utils.ts'
15
15
  import { emitComponentAndEventSetup } from '../shared.ts'
16
- import { emitAttrUpdate } from '../../emit-reactive.ts'
16
+ import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
17
17
  import { namespaceWrapForTemplate } from './template-parse.ts'
18
18
  import { emitListenerLine } from './event-listener.ts'
19
19
  import { nameForRegistryRef } from '../../component-scope.ts'
@@ -46,15 +46,25 @@ export function stringifyBranchReactiveAttrs(
46
46
  lines: string[],
47
47
  plan: readonly ReactiveAttrSlot[],
48
48
  indent: string,
49
- pc?: string,
49
+ pc: string | undefined,
50
+ /** The row's preamble re-run statement, when this scope has one (#2596/#2447) — see `conditionGetterExpr`'s docstring for the shared contract. */
51
+ mapPreambleWrapped: string | undefined,
50
52
  ): void {
51
53
  for (const slot of plan) {
52
54
  const varName = `__ra_${varSlotId(slot.slotId)}`
53
55
  lines.push(`${indent}{ const ${varName} = qsa(__branchScope, '[bf="${slot.slotId}"]')`)
56
+ lines.push(`${indent}${DEDUP_STORE_DECL}`)
54
57
  lines.push(`${indent}if (${varName}) {`)
58
+ let ordinal = 0
55
59
  for (const attr of slot.attrs) {
56
60
  lines.push(`${indent} __disposers.push(createDisposableEffect(() => {`)
57
- for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
61
+ // One effect per attr, so an attr reading a preamble local needs the
62
+ // declarations inside ITS OWN effect — same rule, same line, as
63
+ // `emitAttrSlotsGranular` (reactive-effects.ts, #2447 follow-up).
64
+ if (attr.readsPreamble && mapPreambleWrapped) {
65
+ lines.push(`${indent} ${mapPreambleWrapped}`)
66
+ }
67
+ for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta, ordinal++)) {
58
68
  lines.push(`${indent} ${stmt}`)
59
69
  }
60
70
  lines.push(`${indent} }${profileBindingId(pc, slot.slotId)}))`)
@@ -156,6 +166,8 @@ export function stringifyBranchInnerLoops(
156
166
  [...inner.legacyEvents],
157
167
  inner.outerLoopParam,
158
168
  inner.outerLoopParamBindings,
169
+ false,
170
+ inner.outerLoopIndex,
159
171
  )
160
172
  }
161
173
  const conditionalTexts = inner.reactiveTexts.filter(t => t.insideConditional)
@@ -177,7 +189,13 @@ export function stringifyBranchInnerLoops(
177
189
  }
178
190
  }
179
191
  if (inner.nestedConditionals.length > 0) {
180
- stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `, pc)
192
+ // An inner loop's own conditionals are classified against the INNER
193
+ // loop's preamble names, not the outer row's — `BranchInnerLoop`
194
+ // carries no re-runnable preamble string for that scope, so there is
195
+ // nothing correct to inject here. The `readsPreamble && mapPreambleWrapped`
196
+ // guard downstream makes this `undefined` a no-op, exactly as before
197
+ // this fix (inner-loop preambles are out of scope — see PR notes).
198
+ stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `, pc, undefined)
181
199
  }
182
200
  lines.push(`${indent} return __bel${uid}`)
183
201
  // #2753 Shape B: see the identical comment in `inner-loop.ts` —
@@ -198,33 +216,55 @@ export function stringifyLoopChildConditionals(
198
216
  lines: string[],
199
217
  conditionals: readonly LoopChildConditionalPlan[],
200
218
  indent: string,
201
- pc?: string,
219
+ pc: string | undefined,
220
+ /** The row's preamble re-run statement, when this scope has one (#2596/#2447). */
221
+ mapPreambleWrapped: string | undefined,
202
222
  ): void {
203
223
  for (const cond of conditionals) {
204
- stringifyLoopChildConditional(lines, cond, indent, pc)
224
+ stringifyLoopChildConditional(lines, cond, indent, pc, mapPreambleWrapped)
205
225
  }
206
226
  }
207
227
 
228
+ /**
229
+ * The `() => cond` getter handed to `insert()`. A condition flagged
230
+ * `readsPreamble` (#2596) re-runs the row preamble INSIDE the getter —
231
+ * `insert()` re-invokes this closure on every dependency change to decide
232
+ * the branch, and a preamble local is a plain per-row `const`, not a signal
233
+ * `insert()` can see through on its own. ONE implementation for both the
234
+ * outer (`emitOuterConditional`, reactive-effects.ts) and nested
235
+ * (`stringifyLoopChildConditional`, here) levels so the two can't drift.
236
+ */
237
+ export function conditionGetterExpr(
238
+ wrappedCondition: string,
239
+ readsPreamble: boolean | undefined,
240
+ mapPreambleWrapped: string | undefined,
241
+ ): string {
242
+ return readsPreamble && mapPreambleWrapped
243
+ ? `() => { ${mapPreambleWrapped}; return (${wrappedCondition}) }`
244
+ : `() => ${wrappedCondition}`
245
+ }
246
+
208
247
  function stringifyLoopChildConditional(
209
248
  lines: string[],
210
249
  cond: LoopChildConditionalPlan,
211
250
  indent: string,
212
251
  pc: string | undefined,
252
+ mapPreambleWrapped: string | undefined,
213
253
  ): void {
214
254
  const armIndent = `${indent} `
215
255
  // Body-form arrows wire `__bfSlot` captures into the runtime so live
216
256
  // `Node` returns from Child-position interpolations are spliced into
217
257
  // the parsed fragment instead of being stringified by the surrounding
218
258
  // template literal (#1213).
219
- lines.push(`${indent}insert(${cond.scopeVar}, '${cond.slotId}', () => ${cond.wrappedCondition}, {`)
259
+ lines.push(`${indent}insert(${cond.scopeVar}, '${cond.slotId}', ${conditionGetterExpr(cond.wrappedCondition, cond.readsPreamble, mapPreambleWrapped)}, {`)
220
260
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`)
221
261
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
222
- stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc)
262
+ stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc, mapPreambleWrapped)
223
263
  lines.push(`${indent} }`)
224
264
  lines.push(`${indent}}, {`)
225
265
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`)
226
266
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
227
- stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc)
267
+ stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc, mapPreambleWrapped)
228
268
  lines.push(`${indent} }`)
229
269
  lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`)
230
270
  }
@@ -242,6 +282,8 @@ export function stringifyLoopChildArm(
242
282
  arm: LoopChildArmPlan,
243
283
  armIndent: string,
244
284
  pc: string | undefined,
285
+ /** The row's preamble re-run statement, when this scope has one (#2596/#2447) — forwarded to this arm's own reactive attrs and nested conditionals. */
286
+ mapPreambleWrapped: string | undefined,
245
287
  ): void {
246
288
  stringifyBranchEventBindings(lines, arm.events, armIndent)
247
289
  stringifyBranchChildComponentInits(lines, arm.childComponents, armIndent)
@@ -257,7 +299,7 @@ export function stringifyLoopChildArm(
257
299
  if (!hasDisposables) return
258
300
 
259
301
  lines.push(`${armIndent}const __disposers = []`)
260
- stringifyBranchReactiveAttrs(lines, arm.attrs, armIndent, pc)
302
+ stringifyBranchReactiveAttrs(lines, arm.attrs, armIndent, pc, mapPreambleWrapped)
261
303
 
262
304
  // Nested conditionals: each inner `insert()` is wrapped in its own
263
305
  // disposable effect so disposing THIS entry cascades to whatever
@@ -265,7 +307,7 @@ export function stringifyLoopChildArm(
265
307
  // conditional's nested-conditional handling in stringify/insert.ts).
266
308
  for (const cond of arm.nestedConditionals) {
267
309
  lines.push(`${armIndent}__disposers.push(createDisposableEffect(() => {`)
268
- stringifyLoopChildConditional(lines, cond, `${armIndent} `, pc)
310
+ stringifyLoopChildConditional(lines, cond, `${armIndent} `, pc, mapPreambleWrapped)
269
311
  lines.push(`${armIndent}}))`)
270
312
  }
271
313
 
@@ -30,7 +30,7 @@
30
30
  */
31
31
 
32
32
  import { emitRefCall, varSlotId, profileBindingId } from '../../utils.ts'
33
- import { emitAttrUpdate } from '../../emit-reactive.ts'
33
+ import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
34
34
  import { stringifyReactiveEffects } from './reactive-effects.ts'
35
35
  import { emitTemplateCloneInline, emitLoopItemElementSetup, emitHoistedTemplateDecl, hoistedCloneExpr, namespaceWrapForTemplate, multiRootNamespaceWrapForTemplate, wrapHtmlForNamespace } from './template-parse.ts'
36
36
  import { buildSkeletonPathPlan, type SkeletonPathPlan } from './skeleton-paths.ts'
@@ -452,13 +452,15 @@ export function stringifyStaticLoop(lines: string[], plan: StaticLoopPlan): void
452
452
  lines.push(` }`)
453
453
  }
454
454
  lines.push(` if (__iterEl) {`)
455
+ if (attrsBySlot.length > 0) lines.push(` ${DEDUP_STORE_DECL}`)
456
+ let ordinal = 0
455
457
  for (const [slotId, attrs] of attrsBySlot) {
456
458
  const varName = `__t_${varSlotId(slotId)}`
457
459
  lines.push(` const ${varName} = qsa(__iterEl, '[bf="${slotId}"]')`)
458
460
  lines.push(` if (${varName}) {`)
459
461
  for (const attr of attrs) {
460
462
  lines.push(` createEffect(() => {`)
461
- for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.expression, attr)) {
463
+ for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.expression, attr, ordinal++)) {
462
464
  lines.push(` ${stmt}`)
463
465
  }
464
466
  lines.push(` }${profileBindingId(pc, slotId)})`)
@@ -32,8 +32,8 @@
32
32
  */
33
33
 
34
34
  import { varSlotId, profileBindingId } from '../../utils.ts'
35
- import { emitAttrUpdate } from '../../emit-reactive.ts'
36
- import { stringifyLoopChildArm } from './loop-child-arm.ts'
35
+ import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
36
+ import { stringifyLoopChildArm, conditionGetterExpr } from './loop-child-arm.ts'
37
37
  import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
38
38
  import type {
39
39
  NestedConditionalPlan,
@@ -103,7 +103,11 @@ export interface StringifyReactiveEffectsOptions {
103
103
  * The loop callback's pre-return preamble (already wrapped with the loop
104
104
  * param accessor), re-run once per row-effect tick — BEFORE any preamble
105
105
  * region read — so `preambleRegions`' `valueExpr`s see freshly recomputed
106
- * locals. Only meaningful alongside `preambleRegions`.
106
+ * locals. Also consumed by every OTHER `readsPreamble` site in this row's
107
+ * scope: row-level attrs (`emitAttrSlotsGranular`/`emitConsolidatedRowEffect`),
108
+ * outer condition getters (`emitOuterConditional`), and — forwarded
109
+ * through `stringifyLoopChildArm` (#2596 follow-up) — that row's branch
110
+ * arms' own reactive attrs and nested condition getters.
107
111
  */
108
112
  mapPreambleWrapped?: string
109
113
  }
@@ -160,7 +164,9 @@ function emitAttrSlotsGranular(
160
164
  const varName = `__ra_${varSlotId(slot.slotId)}`
161
165
  const lookupExpr = attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)
162
166
  lines.push(`${indent}{ const ${varName} = ${lookupExpr}`)
167
+ lines.push(`${indent}${DEDUP_STORE_DECL}`)
163
168
  lines.push(`${indent}if (${varName}) {`)
169
+ let ordinal = 0
164
170
  for (const attr of slot.attrs) {
165
171
  lines.push(`${indent} createEffect(() => {`)
166
172
  // Profile mode keeps one effect per attr, so an attr reading a preamble
@@ -171,7 +177,7 @@ function emitAttrSlotsGranular(
171
177
  if (attr.readsPreamble && mapPreambleWrapped) {
172
178
  lines.push(`${indent} ${mapPreambleWrapped}`)
173
179
  }
174
- for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
180
+ for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta, ordinal++)) {
175
181
  lines.push(`${indent} ${stmt}`)
176
182
  }
177
183
  lines.push(`${indent} }${bindingBfId(slot.slotId)})`)
@@ -233,11 +239,14 @@ function attrLookupExpr(
233
239
  * dispatches per-slot on `kind`), so a row pays for at most one `lazySlots`
234
240
  * closure + one claim `Map` instead of one per category.
235
241
  *
236
- * Every attr's write statements are wrapped in their own `{ }` block (not
237
- * just each slot's) `emitAttrUpdate`'s 'value'-attr shape emits a bare
238
- * `const __val = …` with no block of its own, and merging multiple attrs
239
- * into one function body (previously each had its own arrow-function scope)
240
- * would let two 'value' attrs in the same row effect collide on `__val`.
242
+ * Every attr write goes through `emitDedupedAttrUpdate` (#2869): a
243
+ * self-contained `{ }` block (so two 'value' attrs in the same row effect
244
+ * can't collide on `emitAttrUpdate`'s bare `const __val`) behind a
245
+ * previous-value guard on the row's ONE `__l` store, ordinals sequential
246
+ * across every slot in the row. Without the guard, fusing bindings into one
247
+ * effect meant a change to ANY binding rewrote ALL of them on every rerun —
248
+ * `untrack()` on one binding's read couldn't stop a sibling binding's
249
+ * legitimate change from re-triggering this effect and re-writing it anyway.
241
250
  */
242
251
  function emitConsolidatedRowEffect(
243
252
  lines: string[],
@@ -258,6 +267,11 @@ function emitConsolidatedRowEffect(
258
267
  const varName = `__ra_${varSlotId(slot.slotId)}`
259
268
  lines.push(`${indent}const ${varName} = ${attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)}`)
260
269
  }
270
+ // Row-wide dedup store (#2869) — one `__l`, shared by every attr write
271
+ // below via a sequential ordinal, so a sibling binding's legitimate
272
+ // rerun of this shared effect can't force an unchanged binding to
273
+ // rewrite too.
274
+ if (attrSlots.length > 0) lines.push(`${indent}${DEDUP_STORE_DECL}`)
261
275
 
262
276
  // 2. ONE claimed-slot writer covering outer texts (kind 'text') and
263
277
  // preamble regions (kind 'markup') — mixed-kind plans are supported by
@@ -293,15 +307,14 @@ function emitConsolidatedRowEffect(
293
307
  if (mapPreambleWrapped && (preambleRegions.length > 0 || attrsReadPreamble(attrSlots))) {
294
308
  lines.push(`${indent} ${mapPreambleWrapped}`)
295
309
  }
310
+ let ordinal = 0
296
311
  for (const slot of attrSlots) {
297
312
  const varName = `__ra_${varSlotId(slot.slotId)}`
298
313
  lines.push(`${indent} if (${varName}) {`)
299
314
  for (const attr of slot.attrs) {
300
- lines.push(`${indent} {`)
301
- for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
302
- lines.push(`${indent} ${stmt}`)
315
+ for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta, ordinal++)) {
316
+ lines.push(`${indent} ${stmt}`)
303
317
  }
304
- lines.push(`${indent} }`)
305
318
  }
306
319
  lines.push(`${indent} }`)
307
320
  }
@@ -354,18 +367,16 @@ function emitOuterConditional(
354
367
  // scope here (it's a plain per-row `const`, not a signal `insert()` can see
355
368
  // through on its own). Same treatment as `readsPreamble` attrs
356
369
  // (`emitAttrUpdate`'s callers) get ahead of their own write.
357
- const conditionGetter = cond.readsPreamble && mapPreambleWrapped
358
- ? `() => { ${mapPreambleWrapped}; return (${cond.wrappedCondition}) }`
359
- : `() => ${cond.wrappedCondition}`
370
+ const conditionGetter = conditionGetterExpr(cond.wrappedCondition, cond.readsPreamble, mapPreambleWrapped)
360
371
  lines.push(`${indent}insert(${elVar}, '${cond.slotId}', ${conditionGetter}, {`)
361
372
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`)
362
373
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
363
- stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc)
374
+ stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc, mapPreambleWrapped)
364
375
  lines.push(`${indent} }`)
365
376
  lines.push(`${indent}}, {`)
366
377
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`)
367
378
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
368
- stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc)
379
+ stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc, mapPreambleWrapped)
369
380
  lines.push(`${indent} }`)
370
381
  lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`)
371
382
  }
@@ -486,6 +486,35 @@ export function resolveGetterAliases(
486
486
  return aliases
487
487
  }
488
488
 
489
+ /**
490
+ * Which names in `signals`/`memos` are eligible ALIAS-HOP TARGETS for
491
+ * `resolveGetterAliases`'s `isGetter` predicate — a non-module,
492
+ * non-env-reader signal getter, or a non-module memo name. Env-signal
493
+ * getters are excluded because they resolve through their own
494
+ * request-scoped binding (an adapter's `searchParamsFieldRef`-style
495
+ * lookup or equivalent), never a seeded/`out`-keyed value an alias could
496
+ * usefully point at.
497
+ *
498
+ * Shared by every `resolveGetterAliases` call site (#2813's `ssr-defaults.ts`
499
+ * seeding and the Go adapter's `rootFieldRef` field-routing) so the
500
+ * eligibility rule can't silently drift between them — the "one decision,
501
+ * two implementations" defect class CLAUDE.md calls out (pullfrog review
502
+ * on #2854).
503
+ */
504
+ export function collectAliasableGetterNames(
505
+ signals: readonly SignalInfo[],
506
+ memos: readonly MemoInfo[],
507
+ ): Set<string> {
508
+ const getterNames = new Set<string>()
509
+ for (const sig of signals) {
510
+ if (sig.getter && !sig.isModule && !sig.envReader) getterNames.add(sig.getter)
511
+ }
512
+ for (const memo of memos) {
513
+ if (!memo.isModule) getterNames.add(memo.name)
514
+ }
515
+ return getterNames
516
+ }
517
+
489
518
  /**
490
519
  * Build the CSR substitution env from the live `ClientJsContext`.
491
520
  *