@kudzujs/core 0.16.22 → 0.16.24

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.
package/PERFORMANCE.md CHANGED
@@ -76,6 +76,80 @@ route handlers. Benchmark contract and budget deltas are zero; the production AI
76
76
  comparison is not rerun until selected-branch reactive text is also addressed.
77
77
  This follow-up ships as `@kudzujs/core@0.16.22`.
78
78
 
79
+ The 2026-09-04 selected-branch follow-up lowers a reactive scalar branch through
80
+ the existing text-binding compiler path. It adds no semantic primitive,
81
+ compiler pass, runtime concept, API, dependency, or shared-runtime source byte;
82
+ net production growth is 11 lines in one existing compiler file. The focused
83
+ fixture covers an update that remains in the selected branch and a later branch
84
+ replacement. Required Chrome, all 302 tests, `npm run check`, and package smoke
85
+ pass. Rebuilding the five retained commerce sources against this tree changes
86
+ their frozen `below` check from false to true, so all five pass build, behavior,
87
+ accessibility, browser, and output acceptance while retaining the zero-JavaScript
88
+ static sibling. The existing checkout handler grows by 147-154 transferred bytes
89
+ across those authored variants; no shared runtime module was added. Benchmark
90
+ contracts and budgets are unchanged. This is a same-source compiler follow-up,
91
+ not a new equal-condition model run, so the published 11/25 versus 24/25 result
92
+ and the `1.0.0` block remain unchanged pending the complete production rerun.
93
+ This follow-up ships as `@kudzujs/core@0.16.23`.
94
+
95
+ ### Imported Search Source Replay (2026-09-07)
96
+
97
+ Compiler follow-up shipping as `0.16.24` to `f88279024e68c875ea35592a1ab363bde1cec9d0`
98
+ (`0.16.23`), on Linux x64, Node 24.14.0 and Chrome 152.0.7977.64. Authorizing
99
+ evidence is the initial aliased search patch at line 50 of
100
+ `test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r3-kudzu-0.16.23-continuation-20260907/content/attempts/content-kudzu-0/adapter.stdout`.
101
+ The reduced fixture first reproduced both the source-located generic expression
102
+ diagnostic and the direct-filter-count `query.trim is not a function` build error.
103
+
104
+ Imported collection discovery now follows top-level const selector aliases from
105
+ map/count uses. Filter predicates encode proven pure query locals using the
106
+ existing expression language, and count dependencies use signal build values in
107
+ scratch rather than invoking string methods on signal objects. Existing binding,
108
+ selector, keyed, and conditional consumers remain authoritative.
109
+
110
+ | Metric | Result |
111
+ |---|---|
112
+ | Semantic primitives / IR kinds / runtime concepts / APIs | +0 / +0 / +0 / +0 |
113
+ | Core passes / ordered normalization entries / adapters | +0 / +0 / +0 |
114
+ | Existing normalization/proof boundaries extended | 3: import roots, pure filter locals, count build values |
115
+ | Core semantic LOC, unchanged 0.9 file set | +39, 5,466 -> 5,505; source compiler +26, collection analysis +13 |
116
+ | Regression evidence | 1 reduced fixture; 3 positive source variants, 6 negative forms, 4 tests |
117
+ | Reduced route JS | 9 files; 32,190 raw / 12,324 aggregate gzip B in every variant |
118
+ | Delta from fully inlined control | 0 raw / 0 gzip B; identical JS paths and contents |
119
+ | Reduced static sibling | Complete HTML, 0 B JavaScript |
120
+ | Full retained application replay | 11 pages, 2 interactive; build and existing content acceptance pass |
121
+ | Full application JS vs earlier retained inlined artifacts | 13 files; 55,447 raw / 20,976 aggregate gzip B; all paths/bytes identical |
122
+ | Build/browser timing and AI-cost delta | Not measured; no fresh model benchmark or ranking claim |
123
+
124
+ The full replay restores the initial page and stylesheet patch over the content
125
+ starter while preserving the retained application's other source. It uses the
126
+ current workspace compiler and the unchanged existing content acceptance runner,
127
+ not a new adapter invocation. It passes search/count/empty/restore, accessibility,
128
+ zero-JavaScript static output, and no browser exceptions or failed requests.
129
+ Raw build/acceptance output and `replay.json` are retained separately under
130
+ `test-results/ai-delivery-production/imported-article-search-source-replay-20260907/`. Its sorted
131
+ HTML/CSS/JS path-plus-NUL-plus-content SHA-256 is
132
+ `92d96f7cfdd4535de28ff9b7665aa241815ed2649e6db14fc22c5eadc8258904`.
133
+ The original attempt and earlier benchmark results are not rewritten.
134
+
135
+ Release verification: `npm run check` (224 pages, 2 interactive),
136
+ `KUDZU_REQUIRE_CHROME=1 npm test` (standalone project ownership gate, then
137
+ 308/308 tests), `npm run test:package` (4 installed packages, one static page),
138
+ and both package dry-runs pass. The release gates ran sequentially with a
139
+ 1,200-second tool limit, avoiding overlapping fixture tests.
140
+ Chrome regression checks also prove retained rows and fresh remounts; they are
141
+ correctness checks, not latency samples. Mutable aliases, impure calls, cycles,
142
+ collection escapes, mutating sort, and predicate-parameter state collisions remain
143
+ diagnosed. No general local/callback graph or effect cleanup claim is added.
144
+ The same release includes separate r4 benchmark lifecycle integrity changes:
145
+ atomic partial-usage checkpoints, bounded incremental stdout/stderr evidence,
146
+ setup-inclusive deadlines, owned process-tree termination, and incomplete timeout
147
+ attribution. Two Linux lifecycle regressions cover setup and model timeouts,
148
+ partial usage, descendant termination, and passing acceptance after adapter timeout.
149
+ The Windows `taskkill` path remains untested. Future r4 protocols pin published
150
+ `0.16.23` starters; historical raw evidence remains untouched. The published
151
+ 11/25 versus 24/25 comparison and `1.0.0` block remain unchanged.
152
+
79
153
  ## 0.21.2 Browser Performance And Memory
80
154
 
81
155
  Measured 2026-09-03 at revision
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.16.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.16.22 - reactive collection count reuse.** Filtered collection aliases now feed keyed rendering, top-level counts, reactive text, and count-selected branches without duplicated state. This removes the first blocker selected by the failed `0.21.4` AI delivery gate; `1.0.0` remains blocked. Read the [release notes](./RELEASES.md#01622---reactive-collection-count-reuse), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.22), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.16.24 - imported article search and benchmark integrity.** Ordinary imported search aliases and counts compile through existing semantics with byte-identical JavaScript to the inlined control. Revision-4 benchmark protocols preserve partial evidence and enforce setup-inclusive deadlines for future runs; no fresh AI performance result is claimed, and `1.0.0` remains blocked. Read the [release notes](./RELEASES.md#01624---imported-article-search-and-benchmark-integrity), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.24), or follow the [architecture packet](./docs/next-architecture/README.md).
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
package/RELEASES.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.24 - Imported Article Search And Benchmark Integrity
4
+
5
+ Kudzu 0.16.24 compiles the original authored imported-article search shape
6
+ without an inlined workaround and hardens evidence lifecycle integrity for
7
+ future AI delivery runs. This is source replay evidence, not a new AI score.
8
+
9
+ ### Changed in 0.16.24
10
+
11
+ - Follows imported collection selector aliases, normalizes proven pure query
12
+ locals, and evaluates count dependencies from signal build values.
13
+ - Adds 39 net core semantic lines across two existing compiler files, with no
14
+ new semantic primitive, pass, IR kind, runtime concept, API, or dependency.
15
+ - Covers three byte-identical search variants, six rejected unsafe forms,
16
+ retained keyed identity, fresh remounts, and a zero-JavaScript static sibling.
17
+ - Replays the original authored application search successfully through the
18
+ unchanged content acceptance: 13 JavaScript files, 55,447 raw / 20,976 gzip
19
+ bytes, identical to the retained inlined application's JavaScript.
20
+ - Checkpoints adapter usage atomically, streams bounded output evidence, shares
21
+ the deadline with setup, kills owned process trees on timeout, and prevents
22
+ incomplete or timed-out attempts from becoming successful attribution.
23
+ - Freezes r4 protocols with published 0.16.23 starters for future runs only.
24
+ Historical raw evidence is unchanged. Linux lifecycle regressions cover the
25
+ termination paths; the Windows `taskkill` path remains untested.
26
+ - Makes no fresh timing, cost, or AI performance claim. The published 11/25
27
+ versus 24/25 comparison and the `1.0.0` block remain unchanged.
28
+ - Updates `create-kudzu@0.1.149` to generate `@kudzujs/core@^0.16.24` projects.
29
+
30
+ ### Upgrade
31
+
32
+ ```sh
33
+ npm install @kudzujs/core@^0.16.24
34
+ ```
35
+
36
+ ## 0.16.23 - Reactive Conditional Branch Text
37
+
38
+ Kudzu 0.16.23 removes the second commerce compiler blocker selected by the
39
+ corrected `0.21.4` AI delivery evidence: scalar text inside a selected
40
+ conditional branch remains reactive without restructuring ordinary TSX.
41
+
42
+ ### Changed in 0.16.23
43
+
44
+ - Lowers reactive scalar conditional branches through the existing text-binding
45
+ compiler path while preserving conditional ownership and static HTML.
46
+ - Verifies updates that remain in one branch and later replace that branch in
47
+ required Chrome.
48
+ - Passes all five retained commerce sources with `empty`, `below`, and `reached`
49
+ true while preserving their zero-JavaScript static siblings.
50
+ - Adds no semantic primitive, compiler pass, runtime concept, public API,
51
+ dependency, or shared-runtime source byte; `1.0.0` remains unauthorized pending
52
+ a complete equal-condition rerun.
53
+ - Updates `create-kudzu@0.1.148` to generate projects on
54
+ `@kudzujs/core@^0.16.23`.
55
+
56
+ ### Upgrade
57
+
58
+ ```sh
59
+ npm install @kudzujs/core@^0.16.23
60
+ ```
61
+
3
62
  ## 0.16.22 - Reactive Collection Count Reuse
4
63
 
5
64
  Kudzu 0.16.22 removes the first compiler blocker selected by the corrected
@@ -2169,6 +2169,47 @@ comparison must be rerun only after the remaining selected-branch reactive-text
2169
2169
  blocker is addressed. This follow-up is released as `@kudzujs/core@0.16.22`
2170
2170
  with `create-kudzu@0.1.147`. `1.0.0` remains unauthorized.
2171
2171
 
2172
+ The second evidence-selected follow-up now lowers reactive scalar text inside a
2173
+ selected conditional branch through the existing binding descriptor and
2174
+ conditional ownership paths. It adds no semantic primitive, compiler pass,
2175
+ runtime concept, API, dependency, or shared-runtime source byte; one production
2176
+ compiler file grows by net 11 lines. A focused fixture proves same-branch text
2177
+ updates and branch replacement, and required Chrome, all 302 tests, package
2178
+ smoke, and `npm run check` pass. The five retained commerce sources now pass
2179
+ their complete acceptance with `empty`, `below`, and `reached` all true and a
2180
+ zero-JavaScript static sibling. Their route-specific checkout handlers add
2181
+ 147-154 transferred bytes; no runtime module or benchmark contract changes.
2182
+ This does not revise the published equal-condition score without a complete
2183
+ rerun, so `1.0.0` remains unauthorized. This follow-up is released as
2184
+ `@kudzujs/core@0.16.23` with `create-kudzu@0.1.148`.
2185
+
2186
+ The user-approved 2026-09-07 `0.16.24` continuation addresses the initial r3
2187
+ content patch's imported collection alias and direct filter-count failures.
2188
+ [`imported-article-search`](../../test/fixtures/imported-article-search/README.md)
2189
+ reproduced both failures before changes. The reduction extends existing import
2190
+ discovery, pure selector normalization, and count build-value lowering, with
2191
+ existing BindingIR/DerivedIR/KeyedBlockIR consumers and route/keyed/conditional
2192
+ ownership. It adds no primitive, IR kind, pass, runtime concept, API, or adapter;
2193
+ the two core files grow by net 39 lines. Three source variants produce identical
2194
+ JavaScript (32,190 raw / 12,324 gzip B) and six unsafe neighboring forms remain
2195
+ source-located errors. Required Chrome verifies search/count/empty transitions,
2196
+ retained keys, fresh remounts, and static zero-JavaScript exclusion. Check, the
2197
+ project ownership gate plus 308 tests, package smoke, and both package dry-runs pass.
2198
+
2199
+ The full initial-source replay passes the unchanged content acceptance and emits
2200
+ the same 13 JavaScript files/bytes as the earlier retained inlined workaround
2201
+ (55,447 raw / 20,976 gzip B). This is a source replay, not a fresh model trial or
2202
+ replacement benchmark score; no timing or AI-cost improvement is claimed.
2203
+ [`PERFORMANCE.md`](../../PERFORMANCE.md#imported-search-source-replay-2026-09-07)
2204
+ records commands, provenance, accounting, and limits. Preserve the original r3
2205
+ evidence. The release also includes the reviewed r4 protocol/adapter lifecycle
2206
+ work: atomic partial traces, bounded incremental output, setup-inclusive deadlines,
2207
+ owned process-tree termination, and incomplete timeout attribution. Two Linux
2208
+ lifecycle regressions protect future-run integrity; Windows termination remains
2209
+ untested. The frozen r4 starters stay on published `0.16.23`; old raw records are
2210
+ not reinterpreted. This packet ships as `@kudzujs/core@0.16.24` with
2211
+ `create-kudzu@0.1.149`, without authorizing `1.0.0` or a new AI performance claim.
2212
+
2172
2213
  ## `1.0.0`: Stable Application Model
2173
2214
 
2174
2215
  `1.0.0` is authorized only after every required `0.21.x` gate passes. It means:
@@ -2314,4 +2355,4 @@ release transaction where possible or document and publish a forward-fix patch.
2314
2355
  | `0.21.1` | Released as `0.16.16` | Preserve 100/1,000/10,000-route phase, RSS, output, digest, incremental-equivalence, recovery, and bounded canonical-AST retention evidence. | Full 10,000-route report has one complete measured run after two measured-stage diagnostics; no projection used |
2315
2356
  | `0.21.2` | Closed by existing semantics | Preserve initial/session/lazy bytes, keyed/range/navigation median alarms, and bounded endurance ownership evidence. | Same-revision absolute gates; structural artifact bytes are not compressed network transfer |
2316
2357
  | `0.21.3` | Released as `0.16.17` | Preserve exact package, browser, benchmark, registry, and fresh-install evidence. | One initial navigation benchmark invocation stalled at the outer timeout; its clean seven-profile rerun passed all alarms |
2317
- | `0.21.4` | Measured gate failed; first blocker released as `0.16.22` | Preserve both raw batches, the corrected revision-2 protocols, the failed success/cost decision, and collection count reuse; keep `1.0.0` blocked. | Kudzu 11/25 versus React + Vite 24/25; next evidence work is conditional-branch reactive text |
2358
+ | `0.21.4` | Measured gate failed; selected blockers released through `0.16.24` | Preserve historical raw batches and failed success/cost decision; r4 lifecycle integrity applies only to future runs. Keep `1.0.0` blocked pending a complete rerun. | Published result remains Kudzu 11/25 versus React + Vite 24/25; commerce replay passes 5/5 and original imported search replay passes with unchanged JavaScript |
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.16.22` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The `0.21.4` AI delivery gate is measured and blocked; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
3
+ This maps the current `0.16.24` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The `0.21.4` AI delivery gate is measured and blocked; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
4
4
 
5
5
  ## Responsibility Map
6
6
 
@@ -66,7 +66,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
66
66
 
67
67
  ## Generator Versions
68
68
 
69
- `create-kudzu@0.1.147` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.22`.
69
+ `create-kudzu@0.1.149` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.24`.
70
70
 
71
71
  ## Release Boundary
72
72
 
@@ -73,7 +73,7 @@ export function analyzeCollectionPipeline(expression, options) {
73
73
  if (!source) return undefined
74
74
  const parameters = collectionParameters(value.arguments[0], "Rendered collection filter()", fail)
75
75
  const selectorStates = new Set(source.selectorStates)
76
- return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), { parameters, fail, stateNames, selectorStates })]], selectorStates }
76
+ return { ...source, selector: [...source.selector, ["filter", collectionExpression(unwrapExpression(value.arguments[0].body), { parameters, fail, stateNames, selectorStates, declarations })]], selectorStates }
77
77
  }
78
78
  if (method === "flatMap") {
79
79
  if (value.arguments.length !== 1) fail(value, "Rendered collection flatMap() requires one inline projector")
@@ -122,7 +122,7 @@ export function analyzeCollectionPipeline(expression, options) {
122
122
  }
123
123
  }
124
124
 
125
- export function collectionExpression(expression, { parameters = {}, fail, stateNames = new Set(), selectorStates = new Set() }) {
125
+ export function collectionExpression(expression, { parameters = {}, fail, stateNames = new Set(), selectorStates = new Set(), declarations, aliases = new Set() }) {
126
126
  const encode = node => {
127
127
  node = unwrapExpression(node)
128
128
  if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isNumericLiteral(node)) return ["value", ts.isNumericLiteral(node) ? Number(node.text) : node.text]
@@ -137,6 +137,19 @@ export function collectionExpression(expression, { parameters = {}, fail, stateN
137
137
  selectorStates.add(node.text)
138
138
  return ["state", node.text]
139
139
  }
140
+ const entries = declarations?.get(node.text)
141
+ if (entries?.length === 1 && entries[0].node.parent?.parent?.parent === nearestFunction(entries[0].node)?.body) {
142
+ if (aliases.has(node.text)) fail(node, `Rendered collection expression local cycle at "${node.text}"`)
143
+ aliases.add(node.text)
144
+ const dependencies = new Set()
145
+ const result = collectionExpression(entries[0].initializer, { fail, stateNames, selectorStates: dependencies, declarations, aliases })
146
+ for (const name of dependencies) {
147
+ if (name === parameters.item || name === parameters.index) fail(node, `Rendered collection expression local "${node.text}" cannot capture state shadowed by predicate parameters`)
148
+ selectorStates.add(name)
149
+ }
150
+ aliases.delete(node.text)
151
+ return result
152
+ }
140
153
  fail(node, `Rendered collection expression identifier "${node.text}" is not allowed`)
141
154
  }
142
155
  if (ts.isPropertyAccessExpression(node)) {
@@ -1149,7 +1149,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1149
1149
  if (!isUnshadowedGlobal(roundAccess.expression, sourceFile)) fail(roundAccess.expression, "Reactive JSX Intl.NumberFormat requires the unshadowed global Math object")
1150
1150
  collectionExpression(rounded.arguments[0], { fail: (node, message) => fail(node, message.replace("Rendered collection", "Reactive JSX local")), stateNames: allowedNames })
1151
1151
  }
1152
- const resolveReactiveJsxExpression = (expression, owner, setters) => {
1152
+ const resolveReactiveJsxExpression = (expression, owner, setters, buildDeclarations) => {
1153
1153
  const declarations = jsxLocalDeclarations.get(owner)
1154
1154
  if (!declarations) return expression
1155
1155
  const substitutions = new Map()
@@ -1187,6 +1187,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1187
1187
  const captures = captureNames(expanded, expanded, setters, bindingIndex)
1188
1188
  const allowedNames = new Set([...setters.values(), ...captures])
1189
1189
  validateReactiveJsxExpression(expanded, allowedNames, setters)
1190
+ if (buildDeclarations) for (const name of substitutions.keys()) buildDeclarations.add(declarations.get(name)[0].node)
1190
1191
  return expanded
1191
1192
  }
1192
1193
  const componentSpecializations = new WeakMap()
@@ -1696,6 +1697,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1696
1697
  const collectionAliasUses = rawRenderedLists.flatMap(({ parts }) => parts.aliasUses ?? [])
1697
1698
  const collectionAliasDeclarations = new Set(rawRenderedLists.flatMap(({ parts }) => parts.aliasDeclarations ?? []))
1698
1699
  const collectionLengthDeclarations = new Set()
1700
+ const collectionValueDeclarations = new Set()
1699
1701
  for (const declaration of collectionAliasDeclarations) {
1700
1702
  const owner = nearestFunction(declaration)
1701
1703
  const unsupported = identifierReferences(owner.body, declaration.name.text).find(reference => {
@@ -1713,6 +1715,19 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1713
1715
  })
1714
1716
  if (unsupported) fail(unsupported, `Rendered collection alias "${declaration.name.text}" may only be used as a rendered collection source`)
1715
1717
  }
1718
+ for (const [owner, declarations] of jsxLocalDeclarations) for (const entries of declarations.values()) {
1719
+ if (entries.length !== 1 || entries[0].node.parent?.parent?.parent !== owner?.body) continue
1720
+ const { node, initializer } = entries[0]
1721
+ const value = unwrapExpression(initializer)
1722
+ if (!ts.isPropertyAccessExpression(value) || value.name.text !== "length") continue
1723
+ const setters = settersForNode(node, settersByFunction)
1724
+ const expression = resolveReactiveJsxExpression(initializer, owner, setters, collectionValueDeclarations)
1725
+ if (!referencedStateNames(expression, setters).size) continue
1726
+ const collection = analyzeCollectionPipeline(unwrapExpression(expression).expression, { setters, declarations, fail, importedCollections, stateNames: new Set(setters.values()), importedCollectionTransforms })
1727
+ if (!collection?.selector.length) continue
1728
+ validateReactiveJsxExpression(expression, new Set(setters.values()), setters)
1729
+ collectionLengthDeclarations.add(node)
1730
+ }
1716
1731
  const rejectUnsupportedRenderControl = node => {
1717
1732
  if (ts.isIfStatement(node) && containsRenderControl(node, jsxLocalsByFunction.get(nearestFunction(node)) ?? new Set())) {
1718
1733
  const setters = settersForNode(node, settersByFunction)
@@ -1872,8 +1887,19 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1872
1887
 
1873
1888
  const compileRenderExpression = (expression, anchor) => {
1874
1889
  const parts = conditionalParts(expression)
1875
- if (!parts) return ts.visitNode(expression, visitor)
1876
1890
  const setters = settersForNode(anchor, settersByFunction)
1891
+ if (!parts) {
1892
+ if (containsJsx(expression)) return ts.visitNode(expression, visitor)
1893
+ const resolved = resolveReactiveJsxExpression(expression, nearestFunction(anchor), setters)
1894
+ const usedStates = referencedStateNames(resolved, setters, resolved, bindingIndex)
1895
+ const captures = captureNames(resolved, resolved, setters, bindingIndex)
1896
+ if ((usedStates.size || captures.size) && !ts.isIdentifier(resolved)) {
1897
+ usesBehavior = true
1898
+ usesBinding = true
1899
+ return descriptors.compileReactiveBinding(resolved, { setters, importBindings, derived: calculationBinding(expression, nearestFunction(anchor)) }).node
1900
+ }
1901
+ return ts.visitNode(expression, visitor)
1902
+ }
1877
1903
  const condition = resolveReactiveJsxExpression(parts.condition, nearestFunction(anchor), setters)
1878
1904
  const usedStates = referencedStateNames(condition, setters, condition, bindingIndex)
1879
1905
  const captures = captureNames(condition, condition, setters, bindingIndex)
@@ -2218,12 +2244,12 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2218
2244
  return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createIdentifier("undefined"))
2219
2245
  }
2220
2246
 
2221
- if (ts.isVariableDeclaration(node) && collectionLengthDeclarations.has(node) && node.initializer) {
2247
+ if (ts.isVariableDeclaration(node) && node.initializer && (collectionLengthDeclarations.has(node) || collectionValueDeclarations.has(node))) {
2222
2248
  const setters = settersForNode(node, settersByFunction)
2223
2249
  const expression = resolveReactiveJsxExpression(node.initializer, nearestFunction(node), setters)
2224
2250
  const stateNames = new Set(setters.values())
2225
2251
  const rewrite = current => {
2226
- if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current)) return factory.createPropertyAccessExpression(current, "value")
2252
+ if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current) && !isShadowedByParameter(current, expression)) return factory.createPropertyAccessExpression(current, "value")
2227
2253
  return ts.visitEachChild(current, rewrite, context)
2228
2254
  }
2229
2255
  return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, ts.visitNode(expression, rewrite))
@@ -3515,16 +3541,27 @@ function importedSerializableCollections(sourceFile, file, sourceFiles, sourceIn
3515
3541
  for (const statement of sourceFile.statements) if (ts.isImportDeclaration(statement) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
3516
3542
  for (const entry of statement.importClause.namedBindings.elements) if ((entry.propertyName ?? entry.name).text === "useMemo") memoNames.add(entry.name.text)
3517
3543
  }
3544
+ const visitedAliases = new Set()
3518
3545
  const collectRoot = node => {
3519
- if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map"].includes(node.name.text)) {
3546
+ if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map", "length"].includes(node.name.text)) {
3520
3547
  let expression = node.expression
3521
3548
  while (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) expression = expression.expression
3522
- if (ts.isIdentifier(expression)) mapped.add(expression.text)
3549
+ if (ts.isIdentifier(expression)) {
3550
+ if (!isShadowedIdentifier(expression, sourceFile)) mapped.add(expression.text)
3551
+ const owner = nearestFunction(node)
3552
+ if (owner?.body && ts.isBlock(owner.body)) for (const statement of owner.body.statements) {
3553
+ if (!ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const)) continue
3554
+ for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.name.text === expression.text && declaration.initializer && !visitedAliases.has(declaration)) {
3555
+ visitedAliases.add(declaration)
3556
+ collectRoot(declaration.initializer)
3557
+ }
3558
+ }
3559
+ }
3523
3560
  }
3524
3561
  ts.forEachChild(node, collectRoot)
3525
3562
  }
3526
3563
  const visit = node => {
3527
- if (ts.isPropertyAccessExpression(node) && node.name.text === "map") {
3564
+ if (ts.isPropertyAccessExpression(node) && ["map", "length"].includes(node.name.text)) {
3528
3565
  collectRoot(node)
3529
3566
  }
3530
3567
  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && memoNames.has(node.expression.text) && node.arguments[0]) collectRoot(node.arguments[0])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.22",
3
+ "version": "0.16.24",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",