@kudzujs/core 0.8.24 → 0.8.26

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.
@@ -250,6 +250,18 @@ This queue orders the next investigations by general migration value. Start only
250
250
  - No-op normalization passes skip redundant recursive parent repair, reducing the measured 1,000-product build median by 6.26% with equivalent output.
251
251
  - Identical generated route-entry transformation remains the next evidence-backed Goal B investigation; it is not implemented without an isolated benchmark.
252
252
 
253
+ ### Completed In 0.8.25
254
+
255
+ - Seven alternating builds of the 1,000-product, 1,011-page fixture authorized build-local exact-source reuse for identical generated native, parameter, and effect route-entry transforms, improving the median by 9.17% with identical deploy paths and bytes.
256
+ - Normalization pass results and finalized ModuleIR cross-slot references now fail closed at their compiler boundaries.
257
+ - Node 22 compatibility, required Chrome coverage, packed-package installation, version alignment, and post-publish registry checks are explicit release gates.
258
+
259
+ ### Completed In 0.8.26
260
+
261
+ - The maintained commerce runner requires byte-identical output by default while explicit expected deltas preserve historical benchmark reproduction.
262
+ - The standard suite counts exact route-entry transforms across repeated, distinct, and new-build sources and protects the safe keyed bulk-mount guard plus its per-root fallback.
263
+ - Goal B records which build, output, keyed, Worker, integration, and heap evidence exists; no new optimization is authorized without another reproduced material loss.
264
+
253
265
  ## Cross-Cutting Performance Gates
254
266
 
255
267
  Every migration feature must preserve:
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark` and `npm run benchmark:keyed` 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.8.26 Goal B Benchmark Hardening
6
+
7
+ The checked-in `benchmark:commerce` runner now requires byte-identical candidate output by default. `EXPECTED_CHANGES` is reserved for explicitly recorded historical comparisons; the route-entry reuse path also has a focused transform-count regression test in the ordinary suite.
8
+
9
+ After that runner update, seven alternating `v0.8.24` versus current-tree builds on the same Linux machine verified 1,011 pages, 3,056 files, and no changed deploy hash. The 14,766.5 ms and 12,202.4 ms medians validate the maintained runner and released-tree direction, but are not attributed to route-entry reuse alone because the compared revisions include all `0.8.25` compiler-boundary changes.
10
+
11
+ ```text
12
+ v0.8.24: [14311.1,14766.5,15294.2,14718.0,13440.6,15245.2,15007.0]
13
+ current: [12202.4,12243.2,11701.2,12320.8,11994.6,12461.4,12073.3]
14
+ ```
15
+
16
+ ## 0.8.25 Route Entry Transform Reuse
17
+
18
+ Measured UTC 2026-08-10 on an Intel Core i5-9500 with 6 cores, Linux 6.17.0-19-generic, Node 24.14.0, and npm 11.9.0. The public 1,000-product storefront fixture at `f2d5be1` generated 1,011 pages against the `0.8.24` tree plus the same compiler-boundary safety changes in both targets.
19
+
20
+ One warm-up followed by seven alternating clean builds compared repeated esbuild transformation with a build-local exact-source result map used only by generated native, parameter, and effect route entries. The median decreased from 13,851.0 ms to 12,581.4 ms, a 9.17% improvement. Every emitted path and SHA-256 hash matched. The catalog was generated once before timing; `dist` and `.kudzu` cleanup and manifest hashing remained outside timing.
21
+
22
+ ```text
23
+ repeated transform: [13437.4,13851.0,13844.9,13440.8,13906.1,14310.0,14901.0]
24
+ exact-source reuse: [12480.2,12826.9,12777.9,12615.9,11869.7,12121.3,12581.4]
25
+ ```
26
+
27
+ The map lasts for one build and keys the complete generated source after route-relative URLs are resolved. It is not a persistent or generalized JavaScript transform cache. The result measures the large repeated-route fixture on this Linux machine and is not directly comparable to the Apple M3 `0.8.24` build medians below.
28
+
5
29
  ## 0.8.24 Measured Goal B Optimizations
6
30
 
7
31
  Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, npm 11.18.0, and Chrome 151.0.7922.76. Baseline tag `v0.8.23` at `39a065b` and the `0.8.24` candidate used the same local volume and dependencies.
@@ -109,7 +133,7 @@ candidate reverse: [5.8,6.0,6.2,5.8,5.8,6.1,6.2,6.2,6.3,6.0,6.1,6.2,6.2,6.0,6.3,
109
133
 
110
134
  The public [`SimYunSup/kudzu-based-bench`](https://github.com/SimYunSup/kudzu-based-bench) commerce fixture at `f2d5be1` generated 1,000 deterministic products and 1,011 complete Kudzu pages. On the same Apple M3 / Node 25.6.1 machine, clean `0.8.23` and the Goal B candidate received one warm-up and 21 alternating clean builds. Output and `.kudzu` cleanup remained outside timing.
111
135
 
112
- The paired runner generates the catalog once, alternates the two compiler roots, cleans `dist` and `.kudzu` outside timing, compares relative output manifests and hashes, permits only the recorded `assets/kudzu-list.js` delta, and restores the external app's package symlink afterward:
136
+ The paired runner generates the catalog once, alternates the two compiler roots, cleans `dist` and `.kudzu` outside timing, compares relative output manifests and hashes, permits only the explicitly configured historical `assets/kudzu-list.js` delta, and restores the external app's package symlink afterward:
113
137
 
114
138
  ```bash
115
139
  git clone https://github.com/SimYunSup/kudzu-based-bench.git /tmp/kudzu-based-bench
@@ -121,6 +145,7 @@ ln -s "$PWD/node_modules" /tmp/kudzu-0.8.23/node_modules
121
145
  APP_ROOT=/tmp/kudzu-based-bench/apps/shop-kudzu \
122
146
  BASELINE_ROOT=/tmp/kudzu-0.8.23 \
123
147
  CANDIDATE_ROOT="$PWD" RUNS=21 CATALOG_SIZE=1000 \
148
+ EXPECTED_CHANGES=assets/kudzu-list.js \
124
149
  npm run benchmark:commerce
125
150
  ```
126
151
 
@@ -135,14 +160,16 @@ This external fixture is a candidate-finding and paired Kudzu regression benchma
135
160
 
136
161
  ## 0.8.22 Versioned Compiler Foundation
137
162
 
138
- Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.21` at `ff38092` and the current `0.8.22` compiler candidate used the same local volume and identical installed dependencies.
163
+ Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.21` at `ff38092` and the then-current `0.8.22` compiler candidate used the same local volume and identical installed dependencies.
139
164
 
140
- The candidate implementation patch over `v0.8.21` had SHA-256 `e1d89c97dd8dfb60d41ae9c14ac64dfbb50466e024f80076f430ae9e725fb28f`, produced by:
165
+ The final `v0.8.21` to `v0.8.22` tagged implementation patch has reproducible SHA-256 `5fbdc3658b8c0d4d568c7ccdbf89c2c1c20c3a275ff9d0fd20e439b479d60530`, produced by:
141
166
 
142
167
  ```bash
143
- git diff --binary v0.8.21 -- framework/build.mjs framework/compiler/list-runtime-codegen.mjs framework/compiler/param-codegen.mjs framework/compiler/route-capability-planner.mjs framework/compiler/runtime-codegen.mjs framework/core.mjs framework/core.d.ts | shasum -a 256
168
+ git diff --binary v0.8.21 v0.8.22 -- framework/build.mjs framework/compiler/list-runtime-codegen.mjs framework/compiler/param-codegen.mjs framework/compiler/route-capability-planner.mjs framework/compiler/runtime-codegen.mjs framework/core.mjs framework/core.d.ts | shasum -a 256
144
169
  ```
145
170
 
171
+ The previously recorded candidate hash does not match this tagged patch, and no intermediate commit exists between the two release tags. The measured candidate is therefore not independently identifiable as the final `v0.8.22` tree from repository history.
172
+
146
173
  Both targets received one warm-up followed by seven clean `worker-effects` production builds in alternating round-robin order. Cleanup remained outside timing. Both medians were exactly 250.1 ms; the distributions overlap and establish no material change.
147
174
 
148
175
  | Target | Build median | Worker raw / gzip | Window raw / gzip |
@@ -159,14 +186,16 @@ Before release-content updates, the complete 135-page site and the `bindings`, `
159
186
 
160
187
  ## 0.8.21 Explicit Effect Ownership
161
188
 
162
- Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.20` at `7fb6e37` and the current `0.8.21` compiler candidate used the same local volume and identical installed dependencies.
189
+ Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.20` at `7fb6e37` and the then-current `0.8.21` compiler candidate used the same local volume and identical installed dependencies.
163
190
 
164
- The candidate implementation patch over `v0.8.20` had SHA-256 `ac0b1921bbbfb72f45d9b53338bec96bf9ab3d1680446148dfd8548b871bcbe4`, produced by:
191
+ The final `v0.8.20` to `v0.8.21` tagged implementation patch has reproducible SHA-256 `c78159ccce1f88a5ed06445d5e0b113953576529a5845e7772ab386b8adf166a`, produced by:
165
192
 
166
193
  ```bash
167
- git diff --binary v0.8.20 -- framework/build.mjs framework/compiler/descriptor-session.mjs framework/compiler/effect-analysis.mjs framework/compiler/ir/module-ir.mjs framework/compiler/worker-compiler.mjs framework/core.d.ts | shasum -a 256
194
+ git diff --binary v0.8.20 v0.8.21 -- framework/build.mjs framework/compiler/descriptor-session.mjs framework/compiler/effect-analysis.mjs framework/compiler/ir/module-ir.mjs framework/compiler/worker-compiler.mjs framework/core.d.ts | shasum -a 256
168
195
  ```
169
196
 
197
+ The previously recorded candidate hash does not match this tagged patch, and no intermediate commit exists between the two release tags. The measured candidate is therefore not independently identifiable as the final `v0.8.21` tree from repository history.
198
+
170
199
  Both targets received one warm-up followed by seven clean `worker-effects` production builds in alternating round-robin order. Cleanup remained outside timing. The distributions overlap; the 1.02% lower candidate median does not establish a material change.
171
200
 
172
201
  | Target | Build median | Worker raw / gzip | Window raw / gzip |
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.8.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.8.24 - Measured Goal B optimizations.** Large keyed restoration batches mount connected flat-list additions once, and no-op normalization passes skip redundant full-AST parent repair. Maintained and external benchmarks measure 19.77% faster 2,000-row restoration and 6.26% faster 1,000-product builds. Read the [release notes](./RELEASES.md#0824---measured-goal-b-optimizations), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.24), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.8.26 - Goal B benchmark hardening.** Commerce comparisons now require byte-identical output by default, and the standard suite protects exact route-entry transform reuse plus safe keyed bulk-mount selection. Read the [release notes](./RELEASES.md#0826---goal-b-benchmark-hardening), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.26), 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,65 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.26 - Goal B benchmark hardening
4
+
5
+ Kudzu 0.8.26 makes the retained Goal B optimizations reproducible and directly regression-tested without changing generated runtime behavior or authoring support.
6
+
7
+ ### Changed in 0.8.26
8
+
9
+ - `benchmark:commerce` requires byte-identical candidate output by default; `EXPECTED_CHANGES` explicitly preserves historical comparisons with known artifact deltas.
10
+ - The standard suite verifies that exact route-entry sources transform once per build, distinct sources do not collide, and a new build owns a fresh transform map.
11
+ - List runtime generation tests retain the measured bulk-mount threshold, majority guards, nested/owner exclusions, and per-root fallback.
12
+ - The architecture packet now covers `0.8.25`, records Goal B baseline evidence and deliberate gaps, and closes the retained-experiment checklist.
13
+
14
+ ### Performance Validation
15
+
16
+ - Seven alternating `v0.8.24` and current-tree commerce builds emitted 1,011 pages and 3,056 files with no changed deploy hash.
17
+ - The measured medians were 14,766.5 ms and 12,202.4 ms. This validates the maintained runner and release direction but is not attributed to one optimization because the revisions include all `0.8.25` compiler-boundary changes.
18
+ - No new runtime bytes, scheduler, persistent cache, retained tree, or public browser capability was added.
19
+
20
+ ### Validation
21
+
22
+ - `npm run check`, `npm run test:package`, and all 175 tests pass.
23
+ - The exact-output commerce runner passed seven alternating 1,011-page builds.
24
+ - No accepted syntax, runtime behavior, DOM ownership, effect cleanup, or `create-kudzu` template changed.
25
+
26
+ ### Upgrade
27
+
28
+ ```bash
29
+ npm install @kudzujs/core@^0.8.26
30
+ ```
31
+
32
+ ## 0.8.25 - Exact route-entry reuse
33
+
34
+ Kudzu 0.8.25 removes measured repeated esbuild work from large multi-route builds, strengthens compiler boundary validation, and makes package publication gates explicit without changing generated deploy bytes or browser behavior.
35
+
36
+ ### Changed in 0.8.25
37
+
38
+ - Generated native, parameter, and effect route entries reuse one build-local esbuild result when their complete source is byte-identical after route-relative URLs are resolved.
39
+ - Normalization passes now declare an immutable-root contract and reject any result that is not a TypeScript `SourceFile`.
40
+ - Finalized ModuleIR rejects invalid local slots and dangling signal, handler, derived, keyed-parent, keyed-child, keyed-selector, and ownership references.
41
+ - CI covers the minimum Node 22 runtime and the Node 24 Chrome suite, while release jobs exercise the packed package and verify package, lockfile, tag, and registry versions.
42
+ - Missing `0.8.21`, `0.8.22`, and `0.8.23` release pages and their canonical sitemap entries are restored.
43
+
44
+ ### Performance
45
+
46
+ - Seven alternating clean builds of the public 1,000-product, 1,011-page fixture measured 12,581.4 ms with exact-source reuse versus 13,851.0 ms with repeated transformation, a 9.17% improvement.
47
+ - Every deploy path and SHA-256 hash matched. The map is scoped to one build and only the three generated route-entry families; no persistent or generalized transform cache was added.
48
+ - Raw arrays, environment, fixture revision, methodology, and limitations are recorded in `PERFORMANCE.md`.
49
+
50
+ ### Validation
51
+
52
+ - `npm run check`, `npm run test:package`, and all 174 tests pass.
53
+ - The packed-package smoke test installs the produced tarball into a temporary consumer, imports both public entry points, runs the packed CLI, and verifies generated HTML.
54
+ - The external 1,011-page benchmark produced byte-identical deploy manifests across both targets.
55
+ - No accepted syntax, public runtime API, VDOM, hydration, scheduler, retained browser component tree, or `create-kudzu` template change was added.
56
+
57
+ ### Upgrade
58
+
59
+ ```bash
60
+ npm install @kudzujs/core@^0.8.25
61
+ ```
62
+
3
63
  ## 0.8.24 - Measured Goal B optimizations
4
64
 
5
65
  Kudzu 0.8.24 starts the architecture optimization Goal B with two benchmark-proven optimizations for large keyed restoration and large multi-route builds while preserving complete HTML, direct DOM ownership, and existing compiler output. This is distinct from the completed historical Worker capability milestone in `GOAL_B.md`.
@@ -7,7 +7,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
7
7
  | Goal | Decision | Start condition |
8
8
  |---|---|---|
9
9
  | A: compiler foundation | Complete in `0.8.23` | The no-write source compiler, RouteIR v1, CapabilityIR v1, generator boundaries, and output baseline are recorded. |
10
- | B: optimization benchmarks | Active | The first measured bulk keyed restoration optimization is retained; continue one reproduced loss at a time. |
10
+ | B: optimization benchmarks | Active | Keyed restoration, no-op normalization, and exact route-entry transform reuse are retained; continue only when another current fixture reproduces a material loss. |
11
11
  | C: state/resource model | Research only | Reduced fixtures expose a limitation |
12
12
  | D: routing compatibility | Current behavior preserved | Revisit only with migration evidence and invariant review |
13
13
 
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.8.24` architecture built on the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
3
+ This maps the current `0.8.26` architecture built on the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
4
4
 
5
5
  ## Responsibility Map
6
6
 
@@ -15,7 +15,7 @@ This maps the current `0.8.24` architecture built on the completed `0.8.23` Goal
15
15
  | Pure collection language | [`framework/compiler/collection-analysis.mjs`](../../framework/compiler/collection-analysis.mjs) | Analyzes collection roots/selectors and serializes the allowed pure expression language used by lists and derived dependencies. |
16
16
  | Main semantic analysis | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `createKudzuTransformer()` | Produces transformed source plus explicit component, handler, binding, derived, keyed, and effect ownership results. |
17
17
  | Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Retains ordered JSON-safe owner and specialization records for state, setters, props, refs, IDs, direct signal links, and source provenance; AST identity remains private to its source-local session. |
18
- | Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes deterministic JSON-safe HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, imports, and client roots into ModuleIR. |
18
+ | Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes deterministic JSON-safe HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, imports, and client roots into ModuleIR and validates every local slot reference. |
19
19
  | Command IR and codegen | [`framework/compiler/optimize/command-specialization.mjs`](../../framework/compiler/optimize/command-specialization.mjs), [`framework/compiler/ir/module-ir.mjs`](../../framework/compiler/ir/module-ir.mjs), [`framework/compiler/codegen/command-codegen.mjs`](../../framework/compiler/codegen/command-codegen.mjs) | Supported command handlers specialize to JSON-safe ModuleIR, then emit the existing `__kBehavior` AST without changing route plans. |
20
20
  | Source compilation | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `compileSource()` | Runs TypeScript with the Kudzu transformer, rejects surviving React/Router references, and returns a JSON-safe project-relative build module, component analysis, ModuleIR, optional handler module, and imported assets without filesystem writes. |
21
21
  | Handler/evaluator lowering | [`framework/compiler/handler-lowering.mjs`](../../framework/compiler/handler-lowering.mjs) | Completes source-local callback/binding/list AST rewriting and diagnostics before the JSON-safe IR boundary. |
@@ -27,7 +27,7 @@ This maps the current `0.8.24` architecture built on the completed `0.8.23` Goal
27
27
  | Route capability projection | [`framework/compiler/route-capability-planner.mjs`](../../framework/compiler/route-capability-planner.mjs), `planRouteCapabilities()` | Validates RouteIR v1 and purely folds rendered plans and route facts into CapabilityIR v1. |
28
28
  | Effect entry generation | [`framework/compiler/effect-codegen.mjs`](../../framework/compiler/effect-codegen.mjs) | Generates ordinary, dependency, owned, and navigable effect entries from rendered descriptors. |
29
29
  | Runtime generation | [`framework/compiler/runtime-codegen.mjs`](../../framework/compiler/runtime-codegen.mjs), [`framework/compiler/list-runtime-codegen.mjs`](../../framework/compiler/list-runtime-codegen.mjs), [`framework/compiler/param-codegen.mjs`](../../framework/compiler/param-codegen.mjs) | Consumes versioned contracts, specializes authored capability sources with fail-closed anchors, and returns source/define results without filesystem ownership. |
30
- | Artifact emission | `framework/build.mjs` | Selects required files from CapabilityIR and writes or bundles generated sources with esbuild. |
30
+ | Artifact emission | `framework/build.mjs` | Selects required files from CapabilityIR and writes or bundles generated sources with esbuild. Byte-identical native, parameter, and effect route entries reuse one exact-source transform result within the current build only. |
31
31
  | Browser capabilities | [`framework/*.js`](../../framework/) | Small optional modules for commands, bindings, lists, effects, native handlers, serialization, parameters, and navigation; no component runtime. |
32
32
  | Opt-in navigation | [`framework/navigation-runtime.js`](../../framework/navigation-runtime.js) plus `framework/build.mjs` navigation configuration/emission | Fetches and validates complete same-origin documents, replaces only the marked route range, manages route/layout disposal, history, focus, finite prefetch retention, and native fallback. |
33
33
  | Development serving | [`framework/dev-server.mjs`](../../framework/dev-server.mjs) and [`framework/dev-state.js`](../../framework/dev-state.js) | Rebuild/watch/SSE and response-only short-lived state restoration; never changes production `dist/`. |
@@ -50,6 +50,7 @@ src/pages entries + config
50
50
  -> planRouteCapabilities(RouteIR records, route facts)
51
51
  -> CapabilityIR v1
52
52
  -> specialize and emit only selected runtime/capability ESM
53
+ -> reuse exact generated route-entry transforms within this build
53
54
  -> write route index.html, CSS/assets, Worker graphs, rewrites, and .kudzu/kudzu-plan.json
54
55
  -> optional afterBuild()
55
56
  ```
@@ -31,6 +31,21 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
31
31
 
32
32
  [`SimYunSup/kudzu-based-bench`](https://github.com/SimYunSup/kudzu-based-bench) then supplied a 1,000-product, 1,011-page build fixture. Twenty-one alternating clean builds measured 6,684.7 ms for clean `0.8.23` and 6,266.5 ms after skipping parent-pointer repair for normalization passes that return the unchanged AST, a 6.26% improvement. The output retained 3,056 files and only the separately optimized `kudzu-list.js` changed. Full raw arrays and limitations are recorded in [`PERFORMANCE.md`](../../PERFORMANCE.md).
33
33
 
34
+ The `0.8.25` investigation isolated repeated esbuild work by comparing ordinary transformation with build-local reuse keyed by the complete generated route-entry source. Seven alternating 1,011-page builds measured 13,851.0 ms and 12,581.4 ms medians, a 9.17% improvement, with identical emitted paths and hashes. The retained implementation applies only to native, parameter, and effect route entries and keeps no data beyond one build.
35
+
36
+ ### Baseline Coverage Status
37
+
38
+ | Area | Current evidence | Status |
39
+ |---|---|---|
40
+ | Build and artifact graph | Worker and commerce clean builds, emitted manifests, deterministic hashes, raw/gzip JavaScript and Worker bytes | Recorded |
41
+ | Static exclusion | Complete-site and representative fixture comparisons plus static zero-JavaScript assertions | Recorded |
42
+ | Keyed operations | Append, filter, bulk restoration, reverse, retained/released identity, state reset, and handler checks | Recorded |
43
+ | Worker ownership | Throughput, cadence, bounded history, stale writes, and repeated start/termination/listener ownership | Recorded |
44
+ | Command, binding, effect, and navigation semantics | Maintained integration and Chrome behavior tests | Correctness recorded; no standalone timing baseline because no material loss is currently reproduced |
45
+ | Heap trend | Repeated ownership counters and bounded fixture data | No general heap series; add one only when a reproduced ownership or memory loss requires it |
46
+
47
+ The broad baseline list is an investigation inventory, not a requirement to manufacture timings without a candidate. New timing or heap work begins only when a current fixture reproduces the corresponding loss.
48
+
34
49
  ## Candidate Order
35
50
 
36
51
  1. Profile the largest measured loss in a maintained fixture.
@@ -40,7 +55,7 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
40
55
 
41
56
  Known historical pressure points include large keyed-list removal/reconciliation and broad runtime specialization, but neither is authorized without a reproduced current loss.
42
57
 
43
- The next evidence-backed investigation is repeated esbuild transformation of byte-identical generated route entries. The 1,000-product fixture emitted identical effect entries and identical native entries across product routes, but their transformation cost has not yet been isolated from rendering and filesystem work. Reuse transformed source only if an interleaved benchmark establishes a material gain while every route file, URL, define, and byte remains unchanged. Do not add a generalized cache.
58
+ Repeated esbuild transformation of byte-identical generated route entries cleared the materiality and output gates. Further optimization is not authorized until another current fixture isolates a measured loss; do not broaden the route-entry map into a generalized cache.
44
59
 
45
60
  ## Benchmark Contract
46
61
 
@@ -68,4 +83,4 @@ Reject an optimization that:
68
83
  - [x] Select one measured loss, not a speculative hotspot.
69
84
  - [x] Add one minimal benchmark or reuse a maintained one.
70
85
  - [x] Record before/after raw arrays and artifacts.
71
- - [ ] Revert experiments that do not clear correctness and materiality gates.
86
+ - [x] Keep no experiment that fails correctness or materiality gates in the release tree.
@@ -1,6 +1,6 @@
1
1
  # Planned Version Sequence
2
2
 
3
- This is an execution sequence, not release history. `0.8.16` through `0.8.24` are completed scopes represented by package/release records.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.26` are completed scopes represented by package/release records.
4
4
 
5
5
  Keep each patch behavior-preserving and independently reviewable. If a boundary proves inseparable, revise this plan before combining releases; do not silently broaden a patch.
6
6
 
@@ -15,6 +15,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
15
15
  | `0.8.22` | Version the existing RouteIR and CapabilityIR, formalize numeric slots plus readable debug metadata, and extract focused runtime generators. | RouteIR and CapabilityIR reject unsupported versions; runtime generator contracts, output gates, and the recorded benchmark pass. |
16
16
  | `0.8.23` | Forward-fix Goal A by moving source normalization, semantic analysis, ModuleIR finalization, handler generation, and build-module generation behind one no-write source compiler result. | `build.mjs` contains no TSX feature analysis; source results are JSON-safe and project-relative; diagnostics, deploy bytes, tests, and build performance remain equivalent. |
17
17
  | `0.8.24` | Start Goal B with measured large keyed restoration and no-op normalization optimizations. | Fresh-profile keyed restoration improves materially; alternating 1,000-product builds improve materially; correctness, output, and byte deltas are recorded. |
18
+ | `0.8.25` | Reuse byte-identical generated native, parameter, and effect route-entry transforms within one build and strengthen compiler/release boundaries. | Alternating 1,011-page builds improve materially with identical deploy hashes; normalization and ModuleIR fail closed; Node, Chrome, package, and registry gates pass. |
19
+ | `0.8.26` | Harden Goal B benchmark reproducibility and ordinary regression coverage without changing runtime behavior. | Commerce comparison defaults to exact output; route transform counts and keyed bulk/fallback guards are protected by the standard suite; baseline coverage and limits are explicit. |
18
20
 
19
21
  ## Sequence Rules
20
22
 
@@ -6,7 +6,7 @@ import { build as bundle, transform } from "esbuild"
6
6
  import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
7
7
  import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
8
8
  import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
9
- import { clientModulePath, collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles } from "./compiler/source-compiler.mjs"
9
+ import { collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles } from "./compiler/source-compiler.mjs"
10
10
  import { createParamCodegen } from "./compiler/param-codegen.mjs"
11
11
  import { planRouteCapabilities, usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
12
12
  import { generateBindingRuntime, generateCoreRuntime, generateEffectRuntime, generateNativeRuntime, generateNavigationRuntime, specializeRuntime } from "./compiler/runtime-codegen.mjs"
@@ -95,6 +95,7 @@ export async function build({ quiet = false, minify = true } = {}) {
95
95
  const effectEntries = []
96
96
  const nativeEntries = []
97
97
  const paramEntries = []
98
+ const routeEntryTransforms = new Map()
98
99
  const rewrites = []
99
100
  const emittedRoutes = new Set()
100
101
  const emittedApplicationRoutes = new Set()
@@ -264,7 +265,7 @@ export async function build({ quiet = false, minify = true } = {}) {
264
265
  if (hasNativeHandlers) {
265
266
  const generated = generateNativeRuntime(await readFile(new URL("./native-runtime.js", import.meta.url), "utf8"), capabilityIR)
266
267
  await writeJavaScript(join(assetsDirectory, "kudzu-native.js"), generated.source, minify, generated.define)
267
- for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify)
268
+ for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify, routeEntryTransforms)
268
269
  }
269
270
  if (navigationGroups.length) {
270
271
  const navigationSource = await readFile(new URL("./navigation-runtime.js", import.meta.url), "utf8")
@@ -280,12 +281,12 @@ export async function build({ quiet = false, minify = true } = {}) {
280
281
  for (const entry of paramEntries) {
281
282
  const output = join(assetsDirectory, entry.path)
282
283
  await mkdir(dirname(output), { recursive: true })
283
- await writeJavaScript(output, printParamEntry(entry.schema, entry.params, entry.searchParams, entry.searchParamsWritable, output, assetsDirectory, base, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify)
284
+ await writeRouteEntry(output, printParamEntry(entry.schema, entry.params, entry.searchParams, entry.searchParamsWritable, output, assetsDirectory, base, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
284
285
  }
285
286
  for (const entry of effectEntries) {
286
287
  const output = join(assetsDirectory, entry.path)
287
288
  await mkdir(dirname(output), { recursive: true })
288
- await writeJavaScript(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify)
289
+ await writeRouteEntry(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
289
290
  }
290
291
  const clientModules = await collectClientModules(emittedHandlerModules.flatMap(module => module.clientImports).map(file => resolve(root, file)), sourceFileSet)
291
292
  for (const file of clientModules) {
@@ -358,13 +359,13 @@ function preloadModules(html) {
358
359
  return html.replace(scripts[0][0], `${links}${scripts[0][0]}`)
359
360
  }
360
361
 
361
- async function printNativeEntry(entry, assetsDirectory, base, minify) {
362
+ async function printNativeEntry(entry, assetsDirectory, base, minify, transforms) {
362
363
  const output = join(assetsDirectory, entry.path)
363
364
  await mkdir(dirname(output), { recursive: true })
364
365
  const imports = entry.modules.map((module, index) => `import * as __kNativeModule${index} from ${JSON.stringify(module)}`).join("\n")
365
366
  const registrations = entry.modules.map((module, index) => `[${JSON.stringify(module)}, __kNativeModule${index}]`).join(",")
366
367
  const runtime = assetPath(base, "assets/kudzu-native.js")
367
- await writeJavaScript(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify)
368
+ await writeRouteEntry(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify, transforms)
368
369
  }
369
370
 
370
371
  function runtimeEffects(effects, lifetimes = false) {
@@ -388,6 +389,15 @@ async function writeJavaScript(file, source, minify, define) {
388
389
  await writeFile(file, code)
389
390
  }
390
391
 
392
+ export async function writeRouteEntry(file, source, minify, transforms, transformSource = transform, write = writeFile) {
393
+ let code = transforms.get(source)
394
+ if (code === undefined) {
395
+ code = minify ? (await transformSource(source, { format: "esm", legalComments: "none", minify, target: "es2022" })).code : source
396
+ transforms.set(source, code)
397
+ }
398
+ await write(file, code)
399
+ }
400
+
391
401
  async function writeBundledJavaScript(file, source, minify, define) {
392
402
  const result = await bundle({
393
403
  stdin: { contents: source, resolveDir: dirname(file), sourcefile: file },
@@ -2,7 +2,7 @@ import ts from "typescript"
2
2
  import { createComponentAnalysis } from "./analysis/component-analysis.mjs"
3
3
  import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
4
4
  import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
5
- import { createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
5
+ import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
6
6
 
7
7
  export function createSemanticArtifact(file) {
8
8
  return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
@@ -222,6 +222,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
222
222
  const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
223
223
  moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()]
224
224
  moduleIR.clientModules = [...clientModules]
225
+ assertModuleIRReferences(moduleIR)
225
226
  }
226
227
 
227
228
  function source(node) {
@@ -2,6 +2,33 @@ export function createModuleIR(file) {
2
2
  return { version: 1, file, signals: [], handlers: [], bindings: [], derived: [], effects: [], keyedBlocks: [], imports: [], clientModules: [] }
3
3
  }
4
4
 
5
+ export function assertModuleIRReferences(moduleIR) {
6
+ const slot = (records, value, label) => {
7
+ if (!Number.isInteger(value) || value < 0 || value >= records.length) throw new Error(`ModuleIR ${label} references missing slot ${JSON.stringify(value)}`)
8
+ }
9
+ for (const [name, records] of [["signal", moduleIR.signals], ["handler", moduleIR.handlers], ["binding", moduleIR.bindings], ["derived", moduleIR.derived], ["effect", moduleIR.effects], ["keyed block", moduleIR.keyedBlocks]]) {
10
+ records.forEach((record, index) => {
11
+ if (record.slot !== index) throw new Error(`ModuleIR ${name} slot ${JSON.stringify(record.slot)} must equal its index ${index}`)
12
+ })
13
+ }
14
+ for (const handler of moduleIR.handlers) {
15
+ for (const command of handler.commands ?? []) slot(moduleIR.signals, command.signal, `handler ${handler.slot} command signal`)
16
+ if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `handler ${handler.slot} keyed block`)
17
+ }
18
+ for (const binding of moduleIR.bindings) if (binding.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, binding.keyedBlock, `binding ${binding.slot} keyed block`)
19
+ for (const effect of moduleIR.effects) {
20
+ slot(moduleIR.handlers, effect.setup?.handler, `effect ${effect.slot} setup handler`)
21
+ for (const dependency of effect.dependencies ?? []) if (dependency.kind === "derived") slot(moduleIR.derived, dependency.derived, `effect ${effect.slot} derived dependency`)
22
+ if (effect.ownership?.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, effect.ownership.keyedBlock, `effect ${effect.slot} keyed block`)
23
+ }
24
+ for (const block of moduleIR.keyedBlocks) {
25
+ if (block.parent !== undefined) slot(moduleIR.keyedBlocks, block.parent, `keyed block ${block.slot} parent`)
26
+ for (const child of block.children ?? []) slot(moduleIR.keyedBlocks, child, `keyed block ${block.slot} child`)
27
+ if (block.selector !== undefined) slot(moduleIR.derived, block.selector, `keyed block ${block.slot} selector`)
28
+ }
29
+ return moduleIR
30
+ }
31
+
5
32
  export function registerCommandHandler(moduleIR, commands, source, scope = "module") {
6
33
  const slots = new Map(moduleIR.signals.map(signal => [signal.key, signal.slot]))
7
34
  for (const { state, owner = scope } of commands) {
@@ -1,8 +1,10 @@
1
1
  import ts from "typescript"
2
2
 
3
3
  export function applyNormalizationPasses(sourceFile, passes) {
4
- for (const pass of passes) {
4
+ // Passes treat their input root as immutable and return the SourceFile used by the next pass.
5
+ for (const [index, pass] of passes.entries()) {
5
6
  const next = pass(sourceFile)
7
+ if (!next || !ts.isSourceFile(next)) throw new TypeError(`Normalization pass ${index + 1} must return a TypeScript SourceFile`)
6
8
  if (next !== sourceFile) ts.setParentRecursive(next, false)
7
9
  sourceFile = next
8
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.24",
3
+ "version": "0.8.26",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,6 +54,7 @@
54
54
  "dev": "node ./bin/kudzu.mjs dev",
55
55
  "check": "tsc --noEmit && tsc -p test/fixtures/tsconfig.json --noEmit && node ./bin/kudzu.mjs build",
56
56
  "test": "node --test test/*.test.mjs",
57
+ "test:package": "node test/package-smoke.mjs",
57
58
  "benchmark": "node test/performance.mjs",
58
59
  "benchmark:keyed": "node test/keyed-performance.mjs",
59
60
  "benchmark:commerce": "node test/commerce-build-performance.mjs",