@kudzujs/core 0.16.23 → 0.16.25

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
@@ -2,6 +2,284 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, `npm run benchmark:module-cache`, `npm run benchmark:project-navigation`, `npm run benchmark:project-state`, `npm run benchmark:source-scale`, `npm run benchmark:ai-delivery`, and `npm run benchmark:ai-delivery-production` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
4
4
 
5
+ ## 0.16.25 Release Scope
6
+
7
+ The following r5 compiler follow-up and future-only r6 acceptance alignment ship
8
+ as `@kudzujs/core@0.16.25` with `create-kudzu@0.1.150`. The session records below
9
+ retain their original pre-release scope and verification counts. Publication
10
+ does not rerun a model, rescore r5, upload its local raw archive, or establish a
11
+ timing/cost improvement. Frozen r6 starters remain pinned to released 0.16.24;
12
+ benchmarking the new compiler requires a separately frozen revision and full run.
13
+
14
+ ## r5 Compiler Follow-Up (2026-09-07)
15
+
16
+ This is compiler regression work and deterministic source replay against
17
+ `5ed0f9bc875691e4e86e2fab832491c78c2f0c69` (`0.16.24`), not a model rerun,
18
+ release, or replacement benchmark score. The dirty r5 results and separately
19
+ prepared r6 acceptance/protocol changes remain intact. Historical r5 stays
20
+ Kudzu 18/25 versus React + Vite 23/25, with two partial traces; `1.0.0` remains
21
+ blocked. No production prompt, protocol, contract, or acceptance script was
22
+ edited by this compiler follow-up.
23
+
24
+ ### Evidence And Reduction
25
+
26
+ Authorizing batch:
27
+ `test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r5-kudzu-0.16.24-20260907-01/`.
28
+ The reduced checks were run red before their respective compiler edits.
29
+
30
+ | Evidence | Confirmed problem | Existing path used |
31
+ |---|---|---|
32
+ | `content-kudzu-1/adapter.stdout:35`, first patch; build failure at line 42 | `normalizedQuery = query.trim().toLowerCase()` survives in build scratch when `filteredArticles.length` is read directly in JSX rather than through a count local. It calls a string method on the signal object. | Collection-alias count validation now registers its substituted dependencies in the existing build-value declaration set. Existing binding/conditional/list consumers still subscribe to `query`; build scratch reads `query.value`. |
33
+ | Unchanged content starter `src/pages/topics/performance.tsx:13`, including historically accepted attempts 0/1/4 | A direct imported static `filter().map()` unnecessarily creates list state, prototypes, handler ESM, and a runtime family. | After existing collection/predicate/key analysis, direct imported pipelines with no selector states and no collection aliases remain ordinary build-time map execution. Local state and reactive selectors retain keyed ownership. |
34
+ | `realtime-kudzu-0` and `realtime-kudzu-3`, `src/pages/index.tsx:16` | Zero-initialized version refs have no cleanup reset and are intended to survive `[paused]` effect replacements. | Existing effect-private cleanup diagnostic is correct and remains unchanged. Making the ref invocation-private would reset the version and violate the authored lifetime. No persistent-ref support is added. |
35
+ | `realtime-kudzu-1`, `src/pages/index.tsx:16-17` | An unattached null ref is assigned `1` during render, then mutated by snapshot callbacks. Compilation previously accepted it as a DOM ref. `core.mjs` serializes ref captures by ID, not by numeric `.current`; this does not provide a retained version cell. | Existing ref normalization now rejects render-time writes at the authored source, recommending component state for retained values or cleanup-owned private refs for invocation-local values. This closes a diagnostic gap, not a resource-lifetime extension. |
36
+
37
+ The historical realtime timeout did not record an exact browser exception, so
38
+ none is asserted retroactively. The new negative reduction proves the null-ref
39
+ shape compiled before the guard and is diagnosed afterward. Replays of original
40
+ realtime sources 0/3 retain the cleanup diagnostic, source 1 receives the new
41
+ render-write diagnostic, and unchanged state-based sources 2/4 still build and
42
+ pass the realtime browser journey. Their authoring compromises remain explicit;
43
+ this is not a claim of persistent React ref compatibility.
44
+
45
+ ### Output And Browser Proof
46
+
47
+ The new direct-alias-count case uses the existing imported article fixture and
48
+ Chrome journey. All four source variants emit byte-identical JavaScript:
49
+ 9 files, 32,190 raw / 12,324 aggregate gzip B. Search by title/topic, whitespace
50
+ and case normalization, singular/plural status, empty branches, retained keys,
51
+ and fresh restored rows pass. The new static topic reduction emits matching
52
+ HTML, no excluded article seed, no state markers, and zero JavaScript files.
53
+ The six existing unsafe collection forms remain source-located diagnostics.
54
+
55
+ The original failing article source was recovered from the first patch event,
56
+ not reconstructed from the agent's final workaround. Recovered page SHA-256:
57
+ `915d825d15d1799b2a6fc891e12e1e84ca366df5a00a7721bd83d1b0f33db4d2`.
58
+ It and all five final content sources pass the separately prepared r6 acceptance
59
+ with all ten static siblings checked. The two combined polite-region sources
60
+ also pass its corrected semantic count check. These source-only results do not
61
+ erase historical budget failures or change model success/cost denominators.
62
+
63
+ | Output | Before raw / gzip B | After raw / gzip B | Delta raw / gzip B |
64
+ |---|---:|---:|---:|
65
+ | Complete content JavaScript, attempts 0/1/2/4 | 55,447 / 20,976 | 34,908 / 13,677 | -20,539 / -7,299 |
66
+ | Complete content JavaScript, attempt 3 | 55,440 / 20,987 | 34,901 / 13,688 | -20,539 / -7,299 |
67
+ | Content runtime files only, all five attempts | 51,357 / 19,100 | 30,895 / 11,896 | -20,462 / -7,204 |
68
+ | `/topics/performance/` JavaScript graph | 20,539 / 7,299 | 0 / 0 | -20,539 / -7,299 |
69
+ | Existing reduced search graph | 32,190 / 12,324 | 32,190 / 12,324 | 0 / 0 |
70
+
71
+ Each content build changes from 11 pages / 2 interactive / 13 JavaScript files
72
+ to 11 pages / 1 interactive / 9 JavaScript files. Removed paths are
73
+ `assets/handlers/pages/topics/performance.js` and the `kudzu.js`, `kudzu-list.js`,
74
+ and `kudzu-collection-selector.js` files in runtime family `432dc692688d09a1`.
75
+ Every retained JavaScript path and byte is identical to its archived r5 output.
76
+ The recovered source emits the same graph as final attempts 0/1/2/4, SHA-256
77
+ `34a34339db48a3da01f67b41fa5a5b0f50ea4d1854e3c30a1569e8e32b2c5270`
78
+ (sorted relative JavaScript paths followed by their bytes). Gzip is summed per
79
+ artifact with Node `gzipSync`, not a compressed archive or network measurement.
80
+
81
+ ### Accounting And Verification
82
+
83
+ | Metric | Delta |
84
+ |---|---:|
85
+ | Semantic primitives / ModuleIR kinds / core passes | 0 / 0 / 0 |
86
+ | Core semantic LOC (`source-compiler.mjs`, including comment) | +5 |
87
+ | Focused normalization LOC (`effect-private-ref-pass.mjs`) | +2 |
88
+ | Production compiler LOC total | +7 |
89
+ | Ordered normalization entries / transform rules / adapters | 0 / 0 / 0 |
90
+ | Source diagnostic guards | +1 |
91
+ | Runtime concepts / runtime source LOC / dependencies / public APIs | 0 / 0 / 0 / 0 |
92
+ | Positive reduced cases / negative reduced cases | +2 / +2 |
93
+ | AI score, build-speed, interaction-timing, CPU, heap claims | None |
94
+
95
+ The positive cases extend the existing isolated fixture generator; the two
96
+ negative source fixtures live in `test/realtime-version-ref.test.mjs`. No new
97
+ runtime or generic compiler abstraction was introduced. Static collection
98
+ aliases and cross-invocation mutable refs are not broadened by this change.
99
+
100
+ ```bash
101
+ KUDZU_REQUIRE_CHROME=1 node --test test/imported-article-search.test.mjs
102
+ node --test test/realtime-version-ref.test.mjs
103
+ node /tmp/opencode/r5-compiler-followup.mjs
104
+ node /tmp/opencode/verify-r5-compiler-output.mjs
105
+ npm run check
106
+ KUDZU_REQUIRE_CHROME=1 npm test
107
+ npm run test:package
108
+ ```
109
+
110
+ All pass on the same Linux x64 / Node 24.14.0 / Chrome 152.0.7977.64 host as r5.
111
+ Check builds 224 pages, two interactive. The final full run passes the standalone
112
+ project ownership gate plus 315/315 tests, including the preserved r6 acceptance
113
+ tests; package smoke installs four packages and builds one zero-JavaScript page.
114
+ Full commands use a 1,200,000 ms timeout. Local replay sources, artifacts, full
115
+ acceptance JSON, errors, path lists, and raw/gzip totals are retained separately
116
+ at `/tmp/opencode/r5-compiler-followup-EQOrpR/report.json` and adjacent directories.
117
+ No model, commit, version bump, publication, or release was run. Build/browser
118
+ timings were not sampled under an interleaved performance protocol, so no timing
119
+ improvement is claimed.
120
+
121
+ ## 0.21.4 Released 0.16.24 Rerun (2026-09-07)
122
+
123
+ All 50 scheduled attempts executed, without selective retries, from
124
+ `2026-09-07T04:18:00.678Z` through `2026-09-07T05:36:06.037Z` (78 minutes
125
+ 5 seconds). The frozen scorer reports **Kudzu 18/25 (72%) versus React + Vite
126
+ 23/25 (92%)**. Execution finished, but `suite.json` correctly says `incomplete`:
127
+ two Kudzu realtime attempts retain partial attribution after their deadlines.
128
+ This is a new local r5 observation, not a rewrite of the published r2 result,
129
+ proof of a statistically significant improvement, or authorization for `1.0.0`.
130
+ The acceptance/prompt and output-contract gaps below also prevent treating this
131
+ score as a clean production-framework ranking.
132
+
133
+ ### Protocol And Execution
134
+
135
+ Base revision: `5ed0f9bc875691e4e86e2fab832491c78c2f0c69`, released
136
+ `@kudzujs/core@0.16.24`; no compiler changes during this packet. Although no r4
137
+ execution was found in the local evidence inventory, r4's `0.16.23` pins were
138
+ already release-documented. Revision 5 therefore explicitly changes all five
139
+ Kudzu manifests, lock root dependencies, resolved core versions/tarball URLs,
140
+ registry integrity values, and canonical starter digests. Historical r2/r3 raw
141
+ directories and release records remain untouched. The r4 lifecycle adapter is
142
+ unchanged, SHA-256
143
+ `91c0db691c2f7a7d5c40122e86b2ac7b928e3016a80a202f8c079c2013b3eb0e`.
144
+
145
+ Model remains `openai/gpt-5.6-sol`; exact OpenCode `1.18.27` is selected with
146
+ `OPENCODE_BIN` rather than the host default `1.18.29`. Node `24.14.0`, npm
147
+ `11.9.0`, Chrome `152.0.7977.64`, Linux x64 `7.0.0-30-generic`, Intel i5-9500
148
+ (6 logical CPUs), and 33,491,050,496 bytes RAM were recorded. OAuth presence was
149
+ checked without reading credentials. A separate tool-free model preflight
150
+ returned `READY`; its 6,755 tokens are excluded from task accounting.
151
+
152
+ The model, budgets, tools, prompts, public context, executable acceptance,
153
+ written contracts, all React starters, and alternating schedules are identical
154
+ to r4. Each adapter has a setup-inclusive 300,000 ms deadline and limits of
155
+ 400,000 input, 20,000 output, 20,000 reasoning tokens, 40 tool calls, 20 files
156
+ read, 8 files modified, and 5 builds. Tasks execute serially in content, forms,
157
+ CRUD, commerce, realtime order; model processes never run in parallel.
158
+
159
+ ```bash
160
+ OPENCODE_BIN=/home/kft/.npm/_npx/3fe6b69ccd55ed58/node_modules/.bin/opencode CHROME_BIN=/usr/bin/google-chrome npm run benchmark:ai-delivery-production -- --out test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r5-kudzu-0.16.24-20260907-01 > test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r5-kudzu-0.16.24-20260907-01/suite.stdout 2> test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r5-kudzu-0.16.24-20260907-01/suite.stderr
161
+ ```
162
+
163
+ The command exits 0 after all schedules finish; that is not a passing benchmark
164
+ gate. The output directory contains `suite.json`, all five `run.json` and copied
165
+ protocols, 50 attempt results, raw adapter output/traces, build/acceptance logs,
166
+ retained source and deploy artifacts, preflight logs, test logs, and `audit.json`
167
+ (all source diffs, raw-trace review extracts, and artifact/token aggregates).
168
+ The complete local archive is
169
+ `test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r5-kudzu-0.16.24-20260907-01.tar.gz`,
170
+ SHA-256 `d0190c9c4d6da0bd9676365037cf1a1def72e808010e3b03a94a701138a31205`.
171
+ Gzip integrity passes; all 50 schedule entries, 50 pinned-model attribution
172
+ traces, and 300 recorded command-stream SHA-256 values were verified. The archive
173
+ is local evidence only and was not uploaded or released.
174
+
175
+ | Task | r5 protocol SHA-256 | Kudzu starter SHA-256 |
176
+ |---|---|---|
177
+ | Content | `f63c11e20e21659c25ef99498e13085054565382af9a500e7aa6e6520968b1ec` | `6b56448eb0b9179183aeb433bd3e52538749f1a2e609c37c5a8bdf8e479ccd92` |
178
+ | Forms | `967335af955b23c23e8ce81eed35b3a9426c08047b4286a5328e144f30b6e9aa` | `0a762afe93c77cec2137e956796f47a944b9222e3dcff1178462665cf995291b` |
179
+ | CRUD | `063625fd48442c639a059c240d1edaef63f54176f921dcd297c8daf97086430d` | `1e349e2deb163b8bc52fb8844a989e8f29f1ed12a2857da86119bf89ffa3e5c2` |
180
+ | Commerce | `c35cb1801f5a4b98a644f6f48f7c02b2168ebc9ce901b0b8e74fcf792c3d2e0b` | `d2b6e8d926b3e55d0e3e47c0647fdd75f01b46e3dc4ac705108974c7d3e186b3` |
181
+ | Realtime | `bda2526c95862b8d55045018da70370b9dd9a36d4e7ccfe56b3af56f06dbad10` | `f5adc4cf163a8e9cb838173d718fd21302a474fd70920167c12d64518890b218` |
182
+
183
+ The starter digest is SHA-256 over recursively locale-sorted file names, each
184
+ relative slash-normalized path followed by NUL and file bytes, matching the
185
+ integrity test. Every core lock entry uses registry integrity
186
+ `sha512-qLcVItXkI7vI5ktOC16+87uQ4BqQTQOVrFqR4AK7gz4eMN4imL7o+ocUBc7znuVsMML98ChKLRgMSsUot7GUhg==`.
187
+
188
+ ### Scores And Attribution
189
+
190
+ | Task | Kudzu success | React success | Kudzu tokens / success | React tokens / success |
191
+ |---|---:|---:|---:|---:|
192
+ | Content | 3/5 | 5/5 | 591,903 | 77,522 |
193
+ | Forms | 5/5 | 5/5 | 92,087 | 81,111 |
194
+ | CRUD | 3/5 | 3/5 | 250,028 | 197,556 |
195
+ | Commerce | 5/5 | 5/5 | 68,746 | 57,756 |
196
+ | Realtime | 2/5 | 5/5 | unavailable | 142,071 |
197
+ | Overall | 18/25 | 23/25 | unavailable | task median 81,111 |
198
+
199
+ Tokens per success include failed attempts, cache-read input, output, and
200
+ reasoning, following the existing runner's accounting. Recorded totals are
201
+ 4,279,911 Kudzu tokens (a lower bound because two traces are partial) and
202
+ 2,384,962 React tokens. There are 48 complete and 2 incomplete traces; no raw
203
+ model error event was found. Both timed-out model sessions had already produced
204
+ source and a compiler diagnostic, so they are not model-unavailability failures.
205
+ Provider-reported subscription cost is zero, not a market-price cost comparison.
206
+ Kudzu's aggregate failure-inclusive task cost remains unavailable, not zero.
207
+ Successful-attempt elapsed medians are 69,363/65,918 ms, tool calls 12/10, files
208
+ read 5/4, files modified 2/2, builds 1/1, and correction cycles 0/0 for
209
+ Kudzu/React. These are descriptive scorer subsets, not speed or cost wins.
210
+
211
+ Final builds passed 23/25 Kudzu and 25/25 React; complete executable acceptance
212
+ passed 18/25 and 23/25. Against historical r2's 11/25 and 24/25, the observed
213
+ counts change by +7 and -1, with content and commerce now having scored Kudzu
214
+ successes. Different stochastic runs, compiler pins, lifecycle accounting, and
215
+ the limitations below prevent attributing that entire change to the compiler.
216
+
217
+ ### Every Failure
218
+
219
+ | Attempts | Observed failure | Attribution / limitation |
220
+ |---|---|---|
221
+ | `content-kudzu-2`, `content-kudzu-3` | Empty-state live-region text is `0 articlesNo articles match your search.` rather than exactly `0 articles`; all filtering/count transitions otherwise match. Attempt 2 also uses 497,474 input tokens, over 400,000. | Both place separate count and empty-message paragraphs inside one polite region. The grader assumes the entire first live region is the count. Preserve failures, but do not call this broken filtering; the prompt does not explicitly require a count-only region. |
222
+ | `crud-kudzu-1`, `crud-kudzu-3`, `crud-react-vite-2`, `crud-react-vite-4` | Only `initial.group` fails; keyed retention, filters, create/edit/delete, counts, focus, and empty state pass. | All use `role="group" aria-label="Filter memos by status"`. The grader requires exact `Filter memos`. That name exists in the written contract and starter, but the supplied prompt asks only for a named group. The adapter does not supply the written contract. This is a prompt/acceptance alignment gap, not a compiler or missing-group failure. |
223
+ | `realtime-kudzu-0`, `realtime-kudzu-3` | Adapter SIGKILL at about 300,011 ms; final build rejects `src/pages/index.tsx:16:9`: effect-private refs require cleanup that resets or invalidates `.current`. No deploy output. | Version refs initialized with zero are intended to survive pause/resume; current effect-private ownership cannot provide that lifetime. Atomic partial traces retain 117,447 and 175,039 recorded tokens, respectively. No retry or synthetic completion. |
224
+ | `realtime-kudzu-1` | Build passes after changing `useRef(1)` to an unattached `useRef(null)` plus render-time initialization; acceptance times out waiting for the first four-row snapshot. | Generated handler still reads `scope("version").current`. The runtime-ref workaround is not proven safe by its successful build. The frozen timeout path does not serialize browser exception details; do not invent an exact exception or claim a diagnosed compiler fix. |
225
+
226
+ Raw content traces also retain a residual `query.trim is not a function`
227
+ build failure in attempts 1 and 3 when a normalized query alias feeds a filtered
228
+ array whose `.length` is consumed directly. Both agents inline the query
229
+ expression to recover. Attempts 0/2/4 retain the normalized query with a separate
230
+ count local and build. These exact sources, plus the realtime ref cases, are
231
+ future reduced-fixture inputs; this rerun makes no speculative compiler change.
232
+
233
+ ### Output And Source
234
+
235
+ | Executable-accepted subset | Kudzu median artifact raw / gzip B | React median artifact raw / gzip B | Kudzu / React median transferred JS B |
236
+ |---|---:|---:|---:|
237
+ | Content (3 / 5 attempts) | 93,274 / 35,226 | 206,504 / 66,425 | 37,608 / 199,132 |
238
+ | Forms (5 / 5) | 23,832 / 9,771 | 202,704 / 64,305 | 15,147 / 198,469 |
239
+ | CRUD (3 / 3) | 48,490 / 16,727 | 203,383 / 64,471 | 35,542 / 198,476 |
240
+ | Commerce (5 / 5) | 53,886 / 21,871 | 201,055 / 64,188 | 30,214 / 197,976 |
241
+ | Realtime (2 / 5) | 45,555 / 16,150 | 202,524 / 64,244 | 36,012 / 199,043 |
242
+
243
+ Artifact totals include every deploy file; gzip is summed per file. Transferred
244
+ JS sums `.js` resource entries from the frozen journey, including protocol
245
+ overhead, not CPU time or a complete multi-route session. All 18 scored Kudzu
246
+ successes have the designated complete, script/marker-free static sibling.
247
+ Content emits 11 HTML pages but only nine are script/marker-free: besides the
248
+ search route, `/topics/performance/` loads a list capability. Its source was not
249
+ edited by the agents, but this violates the broader written content static-route
250
+ expectation and is not checked by executable acceptance. React's
251
+ `staticZeroJavaScript: true` is a bypass in the checker, not evidence of zero
252
+ React JavaScript. No complete 0.9 performance, memory, resilience, or all-route
253
+ accessibility parity claim follows from these artifact observations.
254
+
255
+ All 50 retained source inventories were compared with their starters. Only
256
+ authored TSX/CSS changed (one to three files); manifests, locks, seeded data,
257
+ fake transport, and test infrastructure remain unchanged. Search preserves
258
+ `ArticleCard`/`Shell` composition and imported data; CRUD preserves its shared
259
+ Provider and keyed declarative rows. No imperative list/DOM replacement was
260
+ found. Native form refs, focus, and validity calls are ordinary form behavior.
261
+ Maintainability is not uniformly equivalent: commerce Kudzu attempt 4 adds
262
+ derived display state after a formatting diagnostic; realtime Kudzu attempt 2
263
+ calls `setMemos` inside a version-state updater, while attempt 4 carries version
264
+ through state plus an effect-local copy instead of React's persistent ref.
265
+ These are explicit authoring compromises, not proof of abstraction parity.
266
+ The runner's byte-identical-file retention metric includes lockfile bytes and
267
+ is not a semantic source-retention percentage.
268
+
269
+ Next prerequisite is an acceptance/prompt alignment review, preserving this raw
270
+ batch and changing no scores retroactively. The retained direct-count query and
271
+ cross-invocation version-ref failures then provide concrete compiler intake;
272
+ the static topic output also needs a separately scoped check. `1.0.0` remains
273
+ blocked. This packet adds zero semantic primitives, compiler passes/LOC, runtime
274
+ concepts, or dependencies, and makes no release, speed, price, or superiority claim.
275
+
276
+ Verification: six protocol/runner/lifecycle tests pass; before execution and
277
+ again after the results documentation, `npm run check` builds 224 pages, two
278
+ interactive, and `npm test` passes the standalone project ownership test plus
279
+ 308/308 tests. Final logs are `final-check.log` and `final-test.log` in the raw
280
+ directory. `git diff --check` passes. The final archive/checksum metadata update
281
+ does not change model inputs or any recorded artifact.
282
+
5
283
  ## 0.21.4 Corrected AI Delivery Proof
6
284
 
7
285
  Measured 2026-09-03 on Linux x64 with Node 24.14.0, Chrome 152.0.7977.64,
@@ -92,6 +370,64 @@ not a new equal-condition model run, so the published 11/25 versus 24/25 result
92
370
  and the `1.0.0` block remain unchanged pending the complete production rerun.
93
371
  This follow-up ships as `@kudzujs/core@0.16.23`.
94
372
 
373
+ ### Imported Search Source Replay (2026-09-07)
374
+
375
+ Compiler follow-up shipping as `0.16.24` to `f88279024e68c875ea35592a1ab363bde1cec9d0`
376
+ (`0.16.23`), on Linux x64, Node 24.14.0 and Chrome 152.0.7977.64. Authorizing
377
+ evidence is the initial aliased search patch at line 50 of
378
+ `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`.
379
+ The reduced fixture first reproduced both the source-located generic expression
380
+ diagnostic and the direct-filter-count `query.trim is not a function` build error.
381
+
382
+ Imported collection discovery now follows top-level const selector aliases from
383
+ map/count uses. Filter predicates encode proven pure query locals using the
384
+ existing expression language, and count dependencies use signal build values in
385
+ scratch rather than invoking string methods on signal objects. Existing binding,
386
+ selector, keyed, and conditional consumers remain authoritative.
387
+
388
+ | Metric | Result |
389
+ |---|---|
390
+ | Semantic primitives / IR kinds / runtime concepts / APIs | +0 / +0 / +0 / +0 |
391
+ | Core passes / ordered normalization entries / adapters | +0 / +0 / +0 |
392
+ | Existing normalization/proof boundaries extended | 3: import roots, pure filter locals, count build values |
393
+ | Core semantic LOC, unchanged 0.9 file set | +39, 5,466 -> 5,505; source compiler +26, collection analysis +13 |
394
+ | Regression evidence | 1 reduced fixture; 3 positive source variants, 6 negative forms, 4 tests |
395
+ | Reduced route JS | 9 files; 32,190 raw / 12,324 aggregate gzip B in every variant |
396
+ | Delta from fully inlined control | 0 raw / 0 gzip B; identical JS paths and contents |
397
+ | Reduced static sibling | Complete HTML, 0 B JavaScript |
398
+ | Full retained application replay | 11 pages, 2 interactive; build and existing content acceptance pass |
399
+ | Full application JS vs earlier retained inlined artifacts | 13 files; 55,447 raw / 20,976 aggregate gzip B; all paths/bytes identical |
400
+ | Build/browser timing and AI-cost delta | Not measured; no fresh model benchmark or ranking claim |
401
+
402
+ The full replay restores the initial page and stylesheet patch over the content
403
+ starter while preserving the retained application's other source. It uses the
404
+ current workspace compiler and the unchanged existing content acceptance runner,
405
+ not a new adapter invocation. It passes search/count/empty/restore, accessibility,
406
+ zero-JavaScript static output, and no browser exceptions or failed requests.
407
+ Raw build/acceptance output and `replay.json` are retained separately under
408
+ `test-results/ai-delivery-production/imported-article-search-source-replay-20260907/`. Its sorted
409
+ HTML/CSS/JS path-plus-NUL-plus-content SHA-256 is
410
+ `92d96f7cfdd4535de28ff9b7665aa241815ed2649e6db14fc22c5eadc8258904`.
411
+ The original attempt and earlier benchmark results are not rewritten.
412
+
413
+ Release verification: `npm run check` (224 pages, 2 interactive),
414
+ `KUDZU_REQUIRE_CHROME=1 npm test` (standalone project ownership gate, then
415
+ 308/308 tests), `npm run test:package` (4 installed packages, one static page),
416
+ and both package dry-runs pass. The release gates ran sequentially with a
417
+ 1,200-second tool limit, avoiding overlapping fixture tests.
418
+ Chrome regression checks also prove retained rows and fresh remounts; they are
419
+ correctness checks, not latency samples. Mutable aliases, impure calls, cycles,
420
+ collection escapes, mutating sort, and predicate-parameter state collisions remain
421
+ diagnosed. No general local/callback graph or effect cleanup claim is added.
422
+ The same release includes separate r4 benchmark lifecycle integrity changes:
423
+ atomic partial-usage checkpoints, bounded incremental stdout/stderr evidence,
424
+ setup-inclusive deadlines, owned process-tree termination, and incomplete timeout
425
+ attribution. Two Linux lifecycle regressions cover setup and model timeouts,
426
+ partial usage, descendant termination, and passing acceptance after adapter timeout.
427
+ The Windows `taskkill` path remains untested. Future r4 protocols pin published
428
+ `0.16.23` starters; historical raw evidence remains untouched. The published
429
+ 11/25 versus 24/25 comparison and `1.0.0` block remain unchanged.
430
+
95
431
  ## 0.21.2 Browser Performance And Memory
96
432
 
97
433
  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.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).
17
+ **Latest release: 0.16.25 - static content and acceptance alignment.** Direct imported static selectors emit HTML without list JavaScript, and original query aliases support direct JSX counts. Content source replay removes four files and 20,539 raw / 7,299 aggregate gzip JavaScript bytes. Future-only r6 acceptance covers named groups, combined polite regions, and all ten static siblings. Historical r5 remains 18/25 versus 23/25 with two partial traces; no fresh model rerun or replacement score is claimed, and `1.0.0` remains blocked. Read the [release notes](./RELEASES.md#01625---static-content-and-acceptance-alignment), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.25), 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,71 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.25 - Static Content And Acceptance Alignment
4
+
5
+ Kudzu 0.16.25 closes the r5 static-topic and direct-count compiler gaps and
6
+ aligns future acceptance with communicated requirements. This release includes
7
+ historical r5 observations, not a fresh model rerun or replacement score.
8
+
9
+ ### Changed in 0.16.25
10
+
11
+ - Keeps proven direct imported static selector maps at build time and registers
12
+ normalized query build values for collection counts consumed directly in JSX.
13
+ - Removes four topic JavaScript files from each replayed content build: 13 to 9
14
+ files, saving 20,539 raw / 7,299 aggregate gzip bytes. Retained JavaScript is
15
+ byte-identical, and all ten static siblings pass source-replay acceptance.
16
+ - Rejects unsafe render-written mutable refs at authored source locations.
17
+ Persistent realtime version refs across effect replacements remain unsupported;
18
+ existing cleanup diagnostics and state-based realtime sources are preserved.
19
+ - Adds seven production compiler lines across two existing files, no semantic
20
+ primitive, pass, IR kind, runtime concept, public API, or dependency.
21
+ - Aligns named CRUD groups and combined polite count/empty regions, enforces
22
+ static exclusion on all ten content siblings, and freezes r6 protocol hashes.
23
+ Those future-only protocols retain published 0.16.24 starters; no r6 model run
24
+ has been performed.
25
+ - Preserves historical r5's 18/25 Kudzu versus 23/25 React + Vite, with two partial
26
+ Kudzu traces and acceptance limitations. Published r2 remains 11/25 versus
27
+ 24/25. Neither is rescored; `1.0.0` remains blocked. No timing or cost win is claimed.
28
+ - Updates `create-kudzu@0.1.150` to generate `@kudzujs/core@^0.16.25` projects.
29
+
30
+ ### Upgrade
31
+
32
+ ```sh
33
+ npm install @kudzujs/core@^0.16.25
34
+ ```
35
+
36
+ ## 0.16.24 - Imported Article Search And Benchmark Integrity
37
+
38
+ Kudzu 0.16.24 compiles the original authored imported-article search shape
39
+ without an inlined workaround and hardens evidence lifecycle integrity for
40
+ future AI delivery runs. This is source replay evidence, not a new AI score.
41
+
42
+ ### Changed in 0.16.24
43
+
44
+ - Follows imported collection selector aliases, normalizes proven pure query
45
+ locals, and evaluates count dependencies from signal build values.
46
+ - Adds 39 net core semantic lines across two existing compiler files, with no
47
+ new semantic primitive, pass, IR kind, runtime concept, API, or dependency.
48
+ - Covers three byte-identical search variants, six rejected unsafe forms,
49
+ retained keyed identity, fresh remounts, and a zero-JavaScript static sibling.
50
+ - Replays the original authored application search successfully through the
51
+ unchanged content acceptance: 13 JavaScript files, 55,447 raw / 20,976 gzip
52
+ bytes, identical to the retained inlined application's JavaScript.
53
+ - Checkpoints adapter usage atomically, streams bounded output evidence, shares
54
+ the deadline with setup, kills owned process trees on timeout, and prevents
55
+ incomplete or timed-out attempts from becoming successful attribution.
56
+ - Freezes r4 protocols with published 0.16.23 starters for future runs only.
57
+ Historical raw evidence is unchanged. Linux lifecycle regressions cover the
58
+ termination paths; the Windows `taskkill` path remains untested.
59
+ - Makes no fresh timing, cost, or AI performance claim. The published 11/25
60
+ versus 24/25 comparison and the `1.0.0` block remain unchanged.
61
+ - Updates `create-kudzu@0.1.149` to generate `@kudzujs/core@^0.16.24` projects.
62
+
63
+ ### Upgrade
64
+
65
+ ```sh
66
+ npm install @kudzujs/core@^0.16.24
67
+ ```
68
+
3
69
  ## 0.16.23 - Reactive Conditional Branch Text
4
70
 
5
71
  Kudzu 0.16.23 removes the second commerce compiler blocker selected by the
@@ -2183,6 +2183,120 @@ This does not revise the published equal-condition score without a complete
2183
2183
  rerun, so `1.0.0` remains unauthorized. This follow-up is released as
2184
2184
  `@kudzujs/core@0.16.23` with `create-kudzu@0.1.148`.
2185
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
+
2213
+ The post-release 2026-09-07 r5 rerun now executes all 50 predeclared attempts with
2214
+ published `0.16.24` core pins, registry lock integrity, and canonical hashes in
2215
+ all five Kudzu starters. Revision 5 preserves the release-documented r4 pins as
2216
+ history rather than relabeling them; model `openai/gpt-5.6-sol`, OpenCode 1.18.27,
2217
+ budgets, prompts, public context, acceptance, React starters, schedules, and the
2218
+ r4 lifecycle adapter are unchanged. No compiler or runtime change is made.
2219
+
2220
+ The frozen scorer records Kudzu 18/25 versus React + Vite 23/25, with per-task
2221
+ counts 3/5, 5/5, 3/5, 5/5, 2/5 versus 5/5, 5/5, 3/5, 5/5, 5/5. All tasks finish,
2222
+ but two Kudzu realtime deadline failures retain partial attribution, so suite
2223
+ status and Kudzu aggregate token cost remain incomplete. React's failure-inclusive
2224
+ task-token median is 81,111. Historical r2's published 11/25 versus 24/25 and all
2225
+ r3 raw evidence remain unchanged; no selective retries or replacement scores.
2226
+
2227
+ This is not a clean ranking or `1.0.0` authorization. Four CRUD failures are
2228
+ exact accessible-name mismatches against a requirement absent from the supplied
2229
+ prompt; two content failures arise from the grader reading count and empty copy
2230
+ as one live-region string, one also exceeding the input budget. The next
2231
+ prerequisite is acceptance/prompt alignment review without retroactive rescoring.
2232
+ Real compiler intake remains in raw `query.trim` failures for direct count uses
2233
+ of normalized-query filters and realtime refs intended to survive pause/resume.
2234
+ One null-ref workaround builds but fails the first snapshot; two zero-ref
2235
+ attempts fail cleanup proof and time out. Accepted source retains declarative
2236
+ composition but some Kudzu attempts add derived/version state workarounds.
2237
+ Designated static siblings remain script-free; content's unchanged topic source
2238
+ emits list scripts, leaving the broader static-route contract unproven.
2239
+
2240
+ Exact commands, nine-failure census, hashes, artifact medians, and scope limits:
2241
+ [`PERFORMANCE.md`](../../PERFORMANCE.md#0214-released-01624-rerun-2026-09-07).
2242
+ Local raw evidence is
2243
+ `test-results/ai-delivery-production/0.21.4-gpt-5.6-sol-r5-kudzu-0.16.24-20260907-01/`.
2244
+ Six integrity/lifecycle tests, `npm run check`, and the standalone ownership gate
2245
+ plus 308 tests pass. No commit, version bump, publication, or release is part of
2246
+ this continuation.
2247
+
2248
+ ### r5 Compiler Follow-Up
2249
+
2250
+ The evidence-backed follow-up is implemented without changing the dirty r5/r6
2251
+ protocol, prompt, contract, or acceptance work. The two content reductions were
2252
+ confirmed red before compiler changes: direct JSX `.length` over a normalized
2253
+ query filter threw `query.trim is not a function`, and the unchanged static topic
2254
+ filter emitted list state/scripts. Existing count dependency registration now
2255
+ selects signal build values, while direct imported pipelines without selector
2256
+ states or collection aliases stay in ordinary build-time map execution.
2257
+ BindingIR, DerivedIR, KeyedBlockIR, and route/keyed/conditional ownership remain
2258
+ unchanged; reactive selectors retain the existing list path and negative checks.
2259
+
2260
+ Realtime source tracing closes the proposed lifetime extension negatively.
2261
+ Attempts 0/3 correctly reject version refs without cleanup reset; moving them
2262
+ into invocation-private closures would lose the version on pause/resume. Attempt
2263
+ 1 exposes a separate diagnostic gap: render-written null refs were accepted as
2264
+ DOM refs, which serialize an ID rather than the retained numeric value. Existing
2265
+ ref normalization now rejects those writes at source. No cross-invocation ref,
2266
+ resource primitive, runtime, or arbitrary callback support was added. Original
2267
+ state-based attempts 2/4 still pass the realtime browser journey.
2268
+
2269
+ Accounting: semantic primitives, IR kinds, core passes, normalization entries,
2270
+ transform/adapter rules, runtime concepts/source, and APIs all +0; core semantic
2271
+ LOC +5, focused normalization LOC +2, one diagnostic guard, two positive and two
2272
+ negative reduced cases. The four search variants emit identical JavaScript at
2273
+ 32,190 raw / 12,324 gzip B. All five final content sources plus the recovered
2274
+ first attempt-1 patch pass the prepared r6 acceptance, including every one of the
2275
+ ten static siblings and combined polite regions. Each deploy removes four topic
2276
+ JavaScript files totaling 20,539 raw / 7,299 gzip B, including 20,462 / 7,204 B of
2277
+ runtime files; every retained JavaScript file is byte-identical to r5.
2278
+
2279
+ `npm run check`, the standalone ownership gate plus 315 required-Chrome tests,
2280
+ and package smoke pass with 1,200,000 ms command timeouts. Exact source locations,
2281
+ replay hashes, commands, output tables, diagnostic boundaries, and local evidence
2282
+ are in [`PERFORMANCE.md`](../../PERFORMANCE.md#r5-compiler-follow-up-2026-09-07).
2283
+ This is source replay, not AI rescoring: r5 remains 18/25 versus 23/25 with partial
2284
+ traces, and published r2 remains 11/25 versus 24/25. No model rerun, timing claim,
2285
+ commit, release, or `1.0.0` authorization follows. The next benchmark decision
2286
+ must use the aligned r6 protocol in a separately authorized complete run, not
2287
+ selective retries or broader compiler semantics without new evidence.
2288
+
2289
+ ### 0.16.25 Release Transaction
2290
+
2291
+ The user-authorized patch packages the preceding r5 records, compiler fixes,
2292
+ render-ref diagnostic, and future-only r6 acceptance alignment as core 0.16.25
2293
+ and create-kudzu 0.1.150. The pre-release session records above remain historical.
2294
+ R6 accepts semantically named CRUD groups and combined polite count/empty regions,
2295
+ and checks all ten content static siblings. Its hashes and published 0.16.24
2296
+ starter pins remain frozen; testing 0.16.25 in a model run requires a new frozen
2297
+ revision. No fresh model run, score replacement, persistent-ref support, or
2298
+ `1.0.0` authorization is part of this release.
2299
+
2186
2300
  ## `1.0.0`: Stable Application Model
2187
2301
 
2188
2302
  `1.0.0` is authorized only after every required `0.21.x` gate passes. It means:
@@ -2328,4 +2442,4 @@ release transaction where possible or document and publish a forward-fix patch.
2328
2442
  | `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 |
2329
2443
  | `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 |
2330
2444
  | `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 |
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` |
2445
+ | `0.21.4` | r5 executed all 50 attempts; compiler follow-up verified; gate remains blocked | Preserve raw scores and partial traces. Direct-count/static-topic fixes and the render-ref diagnostic are verified; persistent version refs remain unsupported. Use aligned r6 only in a separately authorized complete run. | Local historical r5 scorer: Kudzu 18/25 versus React + Vite 23/25, with two partial Kudzu traces. Source replay passes all ten content static siblings but does not replace scores. Published r2 remains 11/25 versus 24/25. |
@@ -1,6 +1,8 @@
1
1
  # Current Compiler Architecture
2
2
 
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.
3
+ This maps the current `0.16.25` 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
+
5
+ The r5 follow-up registers direct collection-count query build values and leaves proven direct imported static selectors in build-time map execution. Existing ref normalization rejects render-written mutable refs; persistent cross-effect version refs remain unsupported. These changes add seven compiler lines, no pass, IR kind, or runtime concept.
4
6
 
5
7
  ## Responsibility Map
6
8
 
@@ -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.148` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.23`.
69
+ `create-kudzu@0.1.150` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.25`.
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)) {
@@ -72,6 +72,8 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
72
72
  if (frameAssignments.length && invalidReference) throw sourceNodeError(invalidReference, sourceFile, "Animation frame refs may only use direct .current reads and assignments")
73
73
  if (frameAssignments.length && !topLevelConst) throw sourceNodeError(node, sourceFile, "Animation frame refs must be one top-level component const")
74
74
  if (attachedToJsx && mutations.length) throw sourceNodeError(mutations[0], sourceFile, "JSX object refs may not assign to ref.current")
75
+ const renderWrite = mutations.find(access => nearestFunction(access) === owner)
76
+ if (renderWrite) throw sourceNodeError(renderWrite, sourceFile, "Mutable refs cannot be assigned during render; use component state for values retained across effect replacements, or an effect-private ref with cleanup for invocation-local values")
75
77
  if (!topLevelConst || invalidReference || !accesses.length) return
76
78
  const effectCalls = owner.body.statements.flatMap(statement => hasUseEffectImport && ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isIdentifier(statement.expression.expression) && statement.expression.expression.text === "useEffect" && !isShadowedIdentifier(statement.expression.expression, sourceFile) ? [statement.expression] : [])
77
79
  const effects = effectCalls.filter(effect => {
@@ -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 => {
@@ -1707,12 +1709,28 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1707
1709
  collectionLengthDeclarations.add(current)
1708
1710
  return false
1709
1711
  }
1710
- if (ts.isJsxExpression(current) && current.initializer === undefined) return false
1712
+ if (ts.isJsxExpression(current) && current.initializer === undefined) {
1713
+ resolveReactiveJsxExpression(reference.parent, owner, settersForNode(reference, settersByFunction), collectionValueDeclarations)
1714
+ return false
1715
+ }
1711
1716
  }
1712
1717
  return true
1713
1718
  })
1714
1719
  if (unsupported) fail(unsupported, `Rendered collection alias "${declaration.name.text}" may only be used as a rendered collection source`)
1715
1720
  }
1721
+ for (const [owner, declarations] of jsxLocalDeclarations) for (const entries of declarations.values()) {
1722
+ if (entries.length !== 1 || entries[0].node.parent?.parent?.parent !== owner?.body) continue
1723
+ const { node, initializer } = entries[0]
1724
+ const value = unwrapExpression(initializer)
1725
+ if (!ts.isPropertyAccessExpression(value) || value.name.text !== "length") continue
1726
+ const setters = settersForNode(node, settersByFunction)
1727
+ const expression = resolveReactiveJsxExpression(initializer, owner, setters, collectionValueDeclarations)
1728
+ if (!referencedStateNames(expression, setters).size) continue
1729
+ const collection = analyzeCollectionPipeline(unwrapExpression(expression).expression, { setters, declarations, fail, importedCollections, stateNames: new Set(setters.values()), importedCollectionTransforms })
1730
+ if (!collection?.selector.length) continue
1731
+ validateReactiveJsxExpression(expression, new Set(setters.values()), setters)
1732
+ collectionLengthDeclarations.add(node)
1733
+ }
1716
1734
  const rejectUnsupportedRenderControl = node => {
1717
1735
  if (ts.isIfStatement(node) && containsRenderControl(node, jsxLocalsByFunction.get(nearestFunction(node)) ?? new Set())) {
1718
1736
  const setters = settersForNode(node, settersByFunction)
@@ -2229,12 +2247,12 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2229
2247
  return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createIdentifier("undefined"))
2230
2248
  }
2231
2249
 
2232
- if (ts.isVariableDeclaration(node) && collectionLengthDeclarations.has(node) && node.initializer) {
2250
+ if (ts.isVariableDeclaration(node) && node.initializer && (collectionLengthDeclarations.has(node) || collectionValueDeclarations.has(node))) {
2233
2251
  const setters = settersForNode(node, settersByFunction)
2234
2252
  const expression = resolveReactiveJsxExpression(node.initializer, nearestFunction(node), setters)
2235
2253
  const stateNames = new Set(setters.values())
2236
2254
  const rewrite = current => {
2237
- if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current)) return factory.createPropertyAccessExpression(current, "value")
2255
+ if (ts.isIdentifier(current) && stateNames.has(current.text) && isReferenceIdentifier(current) && !isShadowedByParameter(current, expression)) return factory.createPropertyAccessExpression(current, "value")
2238
2256
  return ts.visitEachChild(current, rewrite, context)
2239
2257
  }
2240
2258
  return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, ts.visitNode(expression, rewrite))
@@ -2449,6 +2467,8 @@ function keyedListParts(expression, setters, declarations, fail, aliases = new S
2449
2467
  const field = keyExpression && directProperty(keyExpression, parameters.item)
2450
2468
  const positional = Boolean(keyExpression && parameters.index && ts.isIdentifier(unwrapExpression(keyExpression)) && unwrapExpression(keyExpression).text === parameters.index)
2451
2469
  if (!field && !positional) fail(key ?? root, `Keyed list root must have key={${parameters.item}.<field>} or key={${parameters.index ?? "index"}}`)
2470
+ // Direct build-known imports can execute their map without keyed browser ownership.
2471
+ if (collection.static && !collection.localStatic && !collection.selectorStates.size && !collection.aliasDeclarations?.length) return undefined
2452
2472
  return { ...collection, static: collection.static && (!collection.localStatic || collection.selector.length > 0), callback, root, item: parameters.item, index: parameters.index, indexed: Boolean(parameters.index), keyField: positional ? null : field }
2453
2473
  }
2454
2474
 
@@ -3526,16 +3546,27 @@ function importedSerializableCollections(sourceFile, file, sourceFiles, sourceIn
3526
3546
  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)) {
3527
3547
  for (const entry of statement.importClause.namedBindings.elements) if ((entry.propertyName ?? entry.name).text === "useMemo") memoNames.add(entry.name.text)
3528
3548
  }
3549
+ const visitedAliases = new Set()
3529
3550
  const collectRoot = node => {
3530
- if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map"].includes(node.name.text)) {
3551
+ if (ts.isPropertyAccessExpression(node) && ["filter", "flatMap", "slice", "toSorted", "map", "length"].includes(node.name.text)) {
3531
3552
  let expression = node.expression
3532
3553
  while (ts.isCallExpression(expression) || ts.isPropertyAccessExpression(expression)) expression = expression.expression
3533
- if (ts.isIdentifier(expression)) mapped.add(expression.text)
3554
+ if (ts.isIdentifier(expression)) {
3555
+ if (!isShadowedIdentifier(expression, sourceFile)) mapped.add(expression.text)
3556
+ const owner = nearestFunction(node)
3557
+ if (owner?.body && ts.isBlock(owner.body)) for (const statement of owner.body.statements) {
3558
+ if (!ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const)) continue
3559
+ for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.name.text === expression.text && declaration.initializer && !visitedAliases.has(declaration)) {
3560
+ visitedAliases.add(declaration)
3561
+ collectRoot(declaration.initializer)
3562
+ }
3563
+ }
3564
+ }
3534
3565
  }
3535
3566
  ts.forEachChild(node, collectRoot)
3536
3567
  }
3537
3568
  const visit = node => {
3538
- if (ts.isPropertyAccessExpression(node) && node.name.text === "map") {
3569
+ if (ts.isPropertyAccessExpression(node) && ["map", "length"].includes(node.name.text)) {
3539
3570
  collectRoot(node)
3540
3571
  }
3541
3572
  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.23",
3
+ "version": "0.16.25",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",