@kudzujs/core 0.16.21 → 0.16.23

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
@@ -65,6 +65,33 @@ cost, so this proof does not authorize `1.0.0`. This evidence packet changes no
65
65
  compiler, runtime, public API, dependency, generated application, or browser
66
66
  artifact.
67
67
 
68
+ The 2026-09-04 collection follow-up removes the first blocker for one ordinary
69
+ immutable filtered alias reused by keyed rendering, a top-level count, reactive
70
+ text, and a count-selected branch. It adds no primitive, pass, runtime concept,
71
+ API, dependency, or shared-runtime byte; net production compiler growth is 37
72
+ lines across two existing files. The maintained positive and negative fixtures
73
+ pass all 302 tests with Chrome required. Their exercised interactive route uses
74
+ the unchanged 36,219 B shared runtime closure and 37,870 B total JavaScript with
75
+ route handlers. Benchmark contract and budget deltas are zero; the production AI
76
+ comparison is not rerun until selected-branch reactive text is also addressed.
77
+ This follow-up ships as `@kudzujs/core@0.16.22`.
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
+
68
95
  ## 0.21.2 Browser Performance And Memory
69
96
 
70
97
  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.21 - AI evidence npm recovery.** The production-shaped `0.21.4` comparison measured Kudzu at 11/25 attributable successes and React + Vite at 24/25, so the `1.0.0` gate remains blocked while content, CRUD, and commerce compiler boundaries are investigated. This patch retries the unchanged evidence after npm rejected the `0.16.20` trusted-publishing certificate. Read the [release notes](./RELEASES.md#01621---ai-evidence-npm-recovery), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.21), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.16.23 - reactive conditional branch text.** Selected conditional branches now keep scalar text reactive while their condition remains true, using Kudzu's existing text-binding ownership. The retained commerce sources pass 5/5 on the current compiler; `1.0.0` remains blocked pending a complete equal-condition rerun. Read the [release notes](./RELEASES.md#01623---reactive-conditional-branch-text), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.23), 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,57 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.23 - Reactive Conditional Branch Text
4
+
5
+ Kudzu 0.16.23 removes the second commerce compiler blocker selected by the
6
+ corrected `0.21.4` AI delivery evidence: scalar text inside a selected
7
+ conditional branch remains reactive without restructuring ordinary TSX.
8
+
9
+ ### Changed in 0.16.23
10
+
11
+ - Lowers reactive scalar conditional branches through the existing text-binding
12
+ compiler path while preserving conditional ownership and static HTML.
13
+ - Verifies updates that remain in one branch and later replace that branch in
14
+ required Chrome.
15
+ - Passes all five retained commerce sources with `empty`, `below`, and `reached`
16
+ true while preserving their zero-JavaScript static siblings.
17
+ - Adds no semantic primitive, compiler pass, runtime concept, public API,
18
+ dependency, or shared-runtime source byte; `1.0.0` remains unauthorized pending
19
+ a complete equal-condition rerun.
20
+ - Updates `create-kudzu@0.1.148` to generate projects on
21
+ `@kudzujs/core@^0.16.23`.
22
+
23
+ ### Upgrade
24
+
25
+ ```sh
26
+ npm install @kudzujs/core@^0.16.23
27
+ ```
28
+
29
+ ## 0.16.22 - Reactive Collection Count Reuse
30
+
31
+ Kudzu 0.16.22 removes the first compiler blocker selected by the corrected
32
+ `0.21.4` AI delivery evidence: an ordinary filtered collection alias can now
33
+ feed keyed rendering, a top-level count, reactive text, and a count-selected
34
+ conditional branch without duplicating state or restructuring the component.
35
+
36
+ ### Changed in 0.16.22
37
+
38
+ - Normalizes supported collection alias `.length` reads through existing
39
+ collection analysis and reactive binding compilation.
40
+ - Expands count aliases before conditional compilation while preserving initial
41
+ server-rendered values and keyed DOM ownership across filter changes.
42
+ - Verifies reactive count updates, empty-branch unmount, keyed remount, and the
43
+ retained diagnostic for unsupported collection alias consumers in Chrome.
44
+ - Adds no semantic primitive, compiler pass, runtime concept, public API,
45
+ dependency, or shared-runtime byte; `1.0.0` remains unauthorized.
46
+ - Updates `create-kudzu@0.1.147` to generate projects on
47
+ `@kudzujs/core@^0.16.22`.
48
+
49
+ ### Upgrade
50
+
51
+ ```sh
52
+ npm install @kudzujs/core@^0.16.22
53
+ ```
54
+
3
55
  ## 0.16.21 - AI Evidence npm Recovery
4
56
 
5
57
  Kudzu 0.16.21 retries npm publication of the unchanged corrected `0.21.4` AI
@@ -2154,6 +2154,35 @@ and count reuse, then reactive text inside selected conditional branches.
2154
2154
  pass, runtime concept, public API, dependency, generated behavior, or browser
2155
2155
  byte.
2156
2156
 
2157
+ The first evidence-selected follow-up now normalizes one supported collection
2158
+ alias across a keyed map, a top-level `.length` count, reactive text, and a
2159
+ count-selected JSX branch. It reuses collection analysis plus existing binding,
2160
+ conditional, and keyed ownership: no semantic primitive, compiler pass, runtime
2161
+ concept, API, dependency, or shared-runtime byte was added. The implementation
2162
+ changes two existing compiler files by net 37 lines and extends one positive and
2163
+ one negative fixture. Required Chrome verifies count updates, empty-branch
2164
+ unmount, keyed remount, and retained source diagnostics; all 302 tests, package
2165
+ smoke, and `npm run check` pass. The exercised route ships the unchanged 36,219
2166
+ B shared runtime closure and 37,870 B total JavaScript including its existing
2167
+ route handlers. No benchmark contract or budget changes; the production AI
2168
+ comparison must be rerun only after the remaining selected-branch reactive-text
2169
+ blocker is addressed. This follow-up is released as `@kudzujs/core@0.16.22`
2170
+ with `create-kudzu@0.1.147`. `1.0.0` remains unauthorized.
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
+
2157
2186
  ## `1.0.0`: Stable Application Model
2158
2187
 
2159
2188
  `1.0.0` is authorized only after every required `0.21.x` gate passes. It means:
@@ -2299,4 +2328,4 @@ release transaction where possible or document and publish a forward-fix patch.
2299
2328
  | `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 |
2300
2329
  | `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 |
2301
2330
  | `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 |
2302
- | `0.21.4` | Measured gate failed; npm recovery released as `0.16.21` | Preserve both raw batches, the corrected revision-2 protocols, and the failed success/cost decision; keep `1.0.0` blocked. | Kudzu 11/25 versus React + Vite 24/25; next evidence work is reactive collection normalization/count reuse and conditional-branch reactive text |
2331
+ | `0.21.4` | Measured gate failed; selected blockers released through `0.16.23` | Preserve both raw batches, the corrected revision-2 protocols, the failed success/cost decision, collection count reuse, and selected-branch reactive text; keep `1.0.0` blocked pending a complete rerun. | Published result remains Kudzu 11/25 versus React + Vite 24/25; retained commerce sources now pass 5/5 on `0.16.23` |
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.16.21` 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.23` 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.146` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.21`.
69
+ `create-kudzu@0.1.148` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.23`.
70
70
 
71
71
  ## Release Boundary
72
72
 
@@ -102,11 +102,11 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
102
102
  }
103
103
  }
104
104
 
105
- function compileConditional(kind, expression, truthy, falsy, setters) {
105
+ function compileConditional(kind, expression, truthy, falsy, setters, importBindings = new Map()) {
106
106
  const state = directStateIdentifier(expression, setters, bindingIndex)
107
107
  const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
108
108
  if (state) return factory.createCallExpression(factory.createIdentifier("__kStateConditional"), undefined, [factory.createStringLiteral(kind), state, thunk(truthy), thunk(falsy)])
109
- const [initial, ...descriptor] = compileReactiveExpression(expression, setters)
109
+ const [initial, ...descriptor] = compileReactiveExpression(expression, setters, importBindings)
110
110
  return factory.createCallExpression(factory.createIdentifier("__kConditional"), undefined, [factory.createStringLiteral(kind), initial, thunk(truthy), thunk(falsy), ...descriptor])
111
111
  }
112
112
 
@@ -1107,7 +1107,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1107
1107
  visit(calculation.body)
1108
1108
  validatedEffectCalculations.add(calculation)
1109
1109
  }
1110
- const validateReactiveJsxExpression = (expression, allowedNames) => {
1110
+ const validateReactiveJsxExpression = (expression, allowedNames, setters) => {
1111
1111
  const value = unwrapExpression(expression)
1112
1112
  const formatAccess = ts.isCallExpression(value) && !value.questionDotToken && ts.isPropertyAccessExpression(value.expression) && !value.expression.questionDotToken && value.expression.name.text === "format" ? value.expression : undefined
1113
1113
  const formatter = formatAccess && unwrapExpression(formatAccess.expression)
@@ -1115,6 +1115,17 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1115
1115
  if (!constructor) {
1116
1116
  const validate = node => {
1117
1117
  const current = unwrapExpression(node)
1118
+ if (ts.isPropertyAccessExpression(current) && current.name.text === "length") {
1119
+ const collection = analyzeCollectionPipeline(current.expression, {
1120
+ setters,
1121
+ declarations: jsxLocalDeclarations.get(nearestFunction(expression)),
1122
+ fail,
1123
+ importedCollections,
1124
+ stateNames: allowedNames,
1125
+ importedCollectionTransforms
1126
+ })
1127
+ if (collection?.state || collection?.calculation) return factory.createNumericLiteral(0)
1128
+ }
1118
1129
  if (ts.isPropertyAccessExpression(current) && ts.isCallExpression(unwrapExpression(current.expression))) {
1119
1130
  const call = unwrapExpression(current.expression)
1120
1131
  if (ts.isIdentifier(call.expression) && importBindings.has(call.expression.text) && importBindings.get(call.expression.text).kind !== "namespace") {
@@ -1175,7 +1186,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1175
1186
  if (!usedStates.size) return expression
1176
1187
  const captures = captureNames(expanded, expanded, setters, bindingIndex)
1177
1188
  const allowedNames = new Set([...setters.values(), ...captures])
1178
- validateReactiveJsxExpression(expanded, allowedNames)
1189
+ validateReactiveJsxExpression(expanded, allowedNames, setters)
1179
1190
  return expanded
1180
1191
  }
1181
1192
  const componentSpecializations = new WeakMap()
@@ -1684,9 +1695,22 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1684
1695
  collectRenderedLists(sourceFile)
1685
1696
  const collectionAliasUses = rawRenderedLists.flatMap(({ parts }) => parts.aliasUses ?? [])
1686
1697
  const collectionAliasDeclarations = new Set(rawRenderedLists.flatMap(({ parts }) => parts.aliasDeclarations ?? []))
1698
+ const collectionLengthDeclarations = new Set()
1687
1699
  for (const declaration of collectionAliasDeclarations) {
1688
1700
  const owner = nearestFunction(declaration)
1689
- const unsupported = identifierReferences(owner.body, declaration.name.text).find(reference => !collectionAliasUses.includes(reference))
1701
+ const unsupported = identifierReferences(owner.body, declaration.name.text).find(reference => {
1702
+ if (collectionAliasUses.includes(reference)) return false
1703
+ if (!ts.isPropertyAccessExpression(reference.parent) || reference.parent.expression !== reference || reference.parent.name.text !== "length") return true
1704
+ for (let current = reference.parent; current && current !== owner; current = current.parent) {
1705
+ if (isFunctionLike(current)) return true
1706
+ if (ts.isVariableDeclaration(current) && current.initializer && current.parent?.parent?.parent === owner.body && isLocalConst(current)) {
1707
+ collectionLengthDeclarations.add(current)
1708
+ return false
1709
+ }
1710
+ if (ts.isJsxExpression(current) && current.initializer === undefined) return false
1711
+ }
1712
+ return true
1713
+ })
1690
1714
  if (unsupported) fail(unsupported, `Rendered collection alias "${declaration.name.text}" may only be used as a rendered collection source`)
1691
1715
  }
1692
1716
  const rejectUnsupportedRenderControl = node => {
@@ -1848,19 +1872,32 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1848
1872
 
1849
1873
  const compileRenderExpression = (expression, anchor) => {
1850
1874
  const parts = conditionalParts(expression)
1851
- if (!parts) return ts.visitNode(expression, visitor)
1852
1875
  const setters = settersForNode(anchor, settersByFunction)
1853
- const usedStates = referencedStateNames(parts.condition, setters, parts.condition, bindingIndex)
1854
- const captures = captureNames(parts.condition, parts.condition, setters, bindingIndex)
1876
+ if (!parts) {
1877
+ if (containsJsx(expression)) return ts.visitNode(expression, visitor)
1878
+ const resolved = resolveReactiveJsxExpression(expression, nearestFunction(anchor), setters)
1879
+ const usedStates = referencedStateNames(resolved, setters, resolved, bindingIndex)
1880
+ const captures = captureNames(resolved, resolved, setters, bindingIndex)
1881
+ if ((usedStates.size || captures.size) && !ts.isIdentifier(resolved)) {
1882
+ usesBehavior = true
1883
+ usesBinding = true
1884
+ return descriptors.compileReactiveBinding(resolved, { setters, importBindings, derived: calculationBinding(expression, nearestFunction(anchor)) }).node
1885
+ }
1886
+ return ts.visitNode(expression, visitor)
1887
+ }
1888
+ const condition = resolveReactiveJsxExpression(parts.condition, nearestFunction(anchor), setters)
1889
+ const usedStates = referencedStateNames(condition, setters, condition, bindingIndex)
1890
+ const captures = captureNames(condition, condition, setters, bindingIndex)
1855
1891
  if (!usedStates.size && !captures.size) return ts.visitEachChild(expression, visitor, context)
1856
1892
  usesBehavior = true
1857
1893
  usesConditional = true
1858
1894
  return descriptors.compileConditional(
1859
1895
  parts.kind,
1860
- parts.condition,
1896
+ condition,
1861
1897
  compileRenderExpression(parts.truthy, anchor),
1862
1898
  compileRenderExpression(parts.falsy, anchor),
1863
- setters
1899
+ setters,
1900
+ importBindings
1864
1901
  )
1865
1902
  }
1866
1903
 
@@ -2192,6 +2229,17 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2192
2229
  return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createIdentifier("undefined"))
2193
2230
  }
2194
2231
 
2232
+ if (ts.isVariableDeclaration(node) && collectionLengthDeclarations.has(node) && node.initializer) {
2233
+ const setters = settersForNode(node, settersByFunction)
2234
+ const expression = resolveReactiveJsxExpression(node.initializer, nearestFunction(node), setters)
2235
+ const stateNames = new Set(setters.values())
2236
+ const rewrite = current => {
2237
+ if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current)) return factory.createPropertyAccessExpression(current, "value")
2238
+ return ts.visitEachChild(current, rewrite, context)
2239
+ }
2240
+ return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, ts.visitNode(expression, rewrite))
2241
+ }
2242
+
2195
2243
  if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && importBindings.has(node.initializer.expression.text)) {
2196
2244
  if (parameterizedDebounceCalls.has(node.initializer)) return node
2197
2245
  const setters = settersForNode(node, settersByFunction)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.21",
3
+ "version": "0.16.23",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",