@kudzujs/core 0.8.36 → 0.8.38

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This document is the source of truth for Kudzu's product direction, architecture invariants, and future development order. Read it before extending React-shaped syntax or browser capabilities.
4
4
 
5
- The executable post-`0.8.36` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
5
+ The executable post-`0.8.38` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
6
6
 
7
7
  [`GOAL_A.md`](./GOAL_A.md) and [`GOAL_B.md`](./GOAL_B.md) are completed capability-validation records. Their commerce and realtime dashboard fixtures prove general lifecycle, navigation, async-workflow, and Worker capabilities; they are not separate product verticals or future priority lists.
8
8
 
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, and `npm run benchmark:module-cache` 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
+ ## P0.11 Structural Route Artifact Graph
6
+
7
+ Measured UTC 2026-08-11 on the Intel Core i5-9500 Linux x64 host with 6 physical cores, Node 24.14.0, and npm 11.9.0. The baseline was clean tag `v0.8.37` at `8b4e8850c40f2856216e43f23ad02ada8434eb37`. The focused implementation files had SHA-256 `1a93b7fff5d80f271c588f89486a47dc262427195a6a768fe392be949eb99b5e`, produced by:
8
+
9
+ ```bash
10
+ sha256sum framework/build.mjs framework/compiler/route-build-record.mjs framework/compiler/route-capability-planner.mjs framework/core.mjs framework/core.d.ts test/compiler-passes.test.mjs | sha256sum
11
+ ```
12
+
13
+ The repository's 152-page build used one warm-up and seven alternating clean fresh-process samples. Every sample produced the same 173-file deploy graph, 3,835,970 raw bytes, 1,979,875 aggregate gzip bytes, and SHA-256 `c1f95f25d43589b61d1abe7ecf256e5b5e9dddd5647fbcf6368c08cc5ae3ee20`. Baseline and candidate `kudzu-plan.json` files are byte-identical.
14
+
15
+ | Target | Clean build median | Range | Peak RSS median | Deploy bytes |
16
+ |---|---:|---:|---:|---:|
17
+ | `v0.8.37` | 1,820.186 ms | 1,769.592-1,910.670 ms | 351.6 MiB | 3,835,970 B |
18
+ | P0.11 candidate | 1,804.568 ms | 1,756.849-1,843.941 ms | 351.4 MiB | 3,835,970 B |
19
+
20
+ The candidate's unpaired median is 0.86% lower. Round-paired candidate-minus-baseline differences had a -20.933 ms median, with the candidate faster in five of seven pairs and the baseline faster in two. Timing ranges overlap, so no material improvement is claimed; peak-RSS medians differ by 0.3 MiB.
21
+
22
+ ```text
23
+ v0.8.37: [1786.548,1834.792,1910.670,1853.493,1820.186,1769.592,1778.201]
24
+ candidate: [1756.849,1804.568,1843.941,1832.560,1811.390,1798.756,1782.132]
25
+ paired candidate-baseline: [-29.698,-30.225,-66.729,-20.933,-8.796,29.164,3.930]
26
+ ```
27
+
28
+ RouteBuildRecord now owns each rendered route's RouteIR, capabilities, entry paths, styles, and exact handler/effect edges. Handler, Worker, package-client, and chunk closure starts from those structural edges instead of serialized HTML/plan searches or formatted effect keys. This changes build-scratch orchestration only; browser runtime source, RouteIR v1, CapabilityIR v1, emitted files, and deploy bytes remain unchanged.
29
+
30
+ ## P0.10 Structural ModuleIR References
31
+
32
+ Measured UTC 2026-08-11 on an Apple M4 macOS arm64 host with 10 logical CPUs, 16 GiB RAM, Node 24.14.0, and npm 11.9.0. The baseline was clean tag `v0.8.36` at `268cd9023c9f47a912601f298963a9ffe9c00da2`. The compiler and focused-check patch had SHA-256 `f684c79027b290bc8c7d549667ef0d7f21a9d0057b5d6291fcce18b91947eff2`, produced by:
33
+
34
+ ```bash
35
+ git diff --binary v0.8.36 -- framework/compiler/analysis/binding-index.mjs framework/compiler/analysis/component-analysis.mjs framework/compiler/descriptor-session.mjs framework/compiler/ir/module-ir.mjs framework/compiler/source-compiler.mjs test/compiler-passes.test.mjs test/framework.test.mjs | shasum -a 256
36
+ ```
37
+
38
+ The maintained 100-importer fixture used one warm-up and seven alternating fresh-process samples. Both targets retain 103 parse misses, 103 export-summary misses, and 100 importer-local clones. ModuleIR and ComponentAnalysis v2 intentionally change the serialized compiler graph, so the previous equal-digest gate correctly rejects direct v1/v2 comparison: normalized result size changes from 382,603 B to 447,977 B and source-result size changes from 395,346 B to 460,720 B. The additional 65,374 B is deterministic structural slot, signal, symbol, and ownership metadata in compiler scratch; it is not deployed browser JavaScript.
39
+
40
+ | Target | Compiler median | Range | Peak RSS median | Source-result bytes |
41
+ |---|---:|---:|---:|---:|
42
+ | `v0.8.36` | 224.506 ms | 220.888-313.782 ms | 281.6 MiB | 395,346 B |
43
+ | `0.8.37` | 227.158 ms | 224.744-272.594 ms | 282.9 MiB | 460,720 B |
44
+
45
+ The candidate's unpaired median is 1.18% higher. Round-paired candidate-minus-baseline differences had a +6.270 ms median, with the candidate faster in two of seven pairs and the baseline faster in five. Timing ranges overlap, peak RSS differs by 1.3 MiB, and neither result crosses the 5% material-regression threshold.
46
+
47
+ ```text
48
+ v0.8.36: [220.888,222.461,313.782,224.506,249.086,222.746,242.721]
49
+ candidate: [227.158,224.744,225.098,236.027,225.158,239.672,272.594]
50
+ paired candidate-baseline: [6.270,2.283,-88.684,11.521,-23.928,16.926,29.873]
51
+ ```
52
+
53
+ ModuleIR and ComponentAnalysis are build-scratch contracts. Runtime-facing state names and export spellings remain only where generated module and browser ABIs require them. The standard suite verifies static zero-JavaScript output, capability exclusion, keyed identity, effect cleanup, Workers, navigation, and migration behavior; no browser runtime source was added for P0.10.
54
+
5
55
  ## P0.9 Semantic State Operations
6
56
 
7
57
  Measured UTC 2026-08-11 on the Intel Core i5-9500 Linux x64 host with Node 24.14.0. The baseline was clean tag `v0.8.35` at `f25700d9d2b247c01db19f0e8c95f16cb1fa81a5`. The compiler and focused-check patch had SHA-256 `4c3c8a3de18b1e792ea84cf7608a89971850195036a58f37dd76e359bfc8a58d`, produced by:
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.36 - Semantic state operations.** Equivalent direct, aliased, and local-helper state updates now lower to the same command IR without adding handler JavaScript. Read the [release notes](./RELEASES.md#0836---semantic-state-operations), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.36), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.8.38 - Structural route artifacts.** Handler, effect, Worker, CSS, package-client, and chunk retention now follows validated per-route artifact edges instead of serialized output searches. Read the [release notes](./RELEASES.md#0838---structural-route-artifacts), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.38), 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,69 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.38 - Structural route artifacts
4
+
5
+ Kudzu 0.8.38 completes P0.11 by replacing serialized route-output searches and parallel route facts with one validated RouteBuildRecord artifact graph.
6
+
7
+ ### Changed in 0.8.38
8
+
9
+ - Each emitted route records its RouteIR, capability facts, route-entry paths, styles, and exact handler/effect references.
10
+ - Final event, effect, binding, conditional, and keyed-list descriptors retain handler references only when they are rendered.
11
+ - Handler modules resolve by exact module URL, and Worker references resolve by structural module/handler pairs instead of formatted keys.
12
+ - CSS output, package-client compilation, and bundle entry/chunk closure derive from retained route edges.
13
+ - CapabilityIR now folds RouteBuildRecord values directly; serialized HTML/plan `includes()` searches and parallel route capability/entry arrays are removed.
14
+
15
+ ### Performance
16
+
17
+ - Seven alternating clean 152-page builds measured `v0.8.37` and candidate medians of 1,820.186 ms and 1,804.568 ms. The -20.933 ms paired median and overlapping ranges establish no material speedup or regression.
18
+ - Peak RSS medians were 351.6 MiB and 351.4 MiB.
19
+ - Both targets emit the same 173 files, 3,835,970 raw bytes, 1,979,875 aggregate gzip bytes, deploy digest, and byte-identical `kudzu-plan.json`.
20
+
21
+ ### Validation
22
+
23
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 200 tests and 153 generated pages.
24
+ - Focused checks reject unsupported versions, malformed and duplicate references, missing handler modules, inconsistent route entries, and invalid JSON round trips.
25
+ - Existing static, command, binding, keyed, effect, Worker, package-import, navigation, and migration fixtures retain their browser behavior and zero-unused-runtime checks.
26
+ - P0.12 deep RouteIR and CapabilityIR validation is next.
27
+
28
+ ### Upgrade
29
+
30
+ ```bash
31
+ npm install @kudzujs/core@^0.8.38
32
+ ```
33
+
34
+ ## 0.8.37 - Structural ModuleIR references
35
+
36
+ Kudzu 0.8.37 completes P0.10 by replacing mixed state names, export strings, formatted owner keys, and partial slots with one validated structural reference graph.
37
+
38
+ ### Changed in 0.8.37
39
+
40
+ - ModuleIR v2 assigns deterministic slots to symbols, signals, handlers, bindings, derived values, effects, keyed blocks, and imports.
41
+ - ComponentAnalysis v2 assigns deterministic owner, specialization, state, ref, and ID slots.
42
+ - StateRef, OwnerRef, source-local SymbolRef, and stable ModuleSymbol records connect state, capture, import, effect, collection, specialization, prop-signal, and row ownership edges.
43
+ - Descriptor finalization resolves effect handlers and imports structurally instead of searching by export spelling.
44
+ - Readable state and export names remain only as codegen, runtime ABI, and diagnostic metadata.
45
+ - One fail-closed pre-codegen validator rejects unsupported versions, malformed slots, duplicate exports, invalid effect handlers, broken keyed parent-child reciprocity, ownership cycles, and dangling specialization/ref edges.
46
+
47
+ ### Performance
48
+
49
+ - The maintained 100-importer fixture retains 103 parse misses, 103 export-summary misses, and 100 importer-local clones.
50
+ - Seven alternating fresh-process samples measured `v0.8.36` and 0.8.37 compiler medians of 224.506 ms and 227.158 ms. The +6.270 ms paired median and overlapping timing/RSS ranges establish no material regression.
51
+ - Source-result size increases from 395,346 B to 460,720 B because ModuleIR and ComponentAnalysis now serialize structural symbol, signal, owner, and slot metadata. This is compiler scratch data, not deployed browser JavaScript.
52
+ - Existing static, command, binding, keyed, effect, Worker, navigation, and migration fixtures retain their browser behavior and zero-unused-runtime checks.
53
+
54
+ ### Validation
55
+
56
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 198 tests and 152 generated pages.
57
+ - Focused checks cover unsupported versions, malformed references, duplicate exports, parent-child reciprocity, cycles, effect-handler roles, specialization ownership, and JSON round trips.
58
+ - Package smoke installation passes from the packed tarball.
59
+ - P0.11 explicit route artifact graph is next.
60
+
61
+ ### Upgrade
62
+
63
+ ```bash
64
+ npm install @kudzujs/core@^0.8.37
65
+ ```
66
+
3
67
  ## 0.8.36 - Semantic state operations
4
68
 
5
69
  Kudzu 0.8.36 completes P0.9 by proving equivalent direct, aliased, and local-helper state updates and lowering them through the existing command HandlerIR path.
@@ -11,7 +11,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
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
 
14
- The active post-`0.8.36` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.9 semantic state operations is complete; P0.10 ModuleIR reference unification is next. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
14
+ The active post-`0.8.38` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.11 explicit route artifact graph is complete; P0.12 deep RouteIR and CapabilityIR validation is next. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
15
15
 
16
16
  ## Required Invariants
17
17
 
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.8.36` 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.38` 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
 
@@ -9,7 +9,7 @@ This maps the current `0.8.36` architecture, built on the completed `0.8.23` Goa
9
9
  | CLI entry | [`bin/kudzu.mjs`](../../bin/kudzu.mjs) | Dispatches build and development commands. |
10
10
  | Project session | [`framework/compiler/project-session.mjs`](../../framework/compiler/project-session.mjs), `createProjectSession()` | Owns one absolute root, standard project paths, source records, bound graph operations, and Worker compiler for a build. Omitted roots resolve from call-time CWD. |
11
11
  | Parsed module cache and symbols | [`framework/compiler/project-session.mjs`](../../framework/compiler/project-session.mjs) | Parses each unchanged source module once per ProjectSession and records source-local declaration/import/re-export sites. Stable ModuleSymbol records resolve direct, aliased, barrel, and `export *` exports with cycle and ambiguity checks; repeated resolutions are cached against their source dependencies, and normalization consumers locate the resolved SiteId in a fresh clone with independent parent links. |
12
- | Build orchestration | [`framework/build.mjs`](../../framework/build.mjs), `build()` | Coordinates config, discovery, source compilation, RouteIR rendering, CapabilityIR projection, generator invocation, artifact emission, and `afterBuild`. |
12
+ | Build orchestration | [`framework/build.mjs`](../../framework/build.mjs), `build()` | Coordinates config, discovery, source compilation, RouteBuildRecord collection, CapabilityIR projection, generator invocation, artifact emission, and `afterBuild`. |
13
13
  | Reachability/import resolution | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `reachableSourceFiles()`; [`framework/compiler/source-graph.mjs`](../../framework/compiler/source-graph.mjs), `ordinaryRuntimeDependencies()`, `resolveSourceImport()` | Starts from page entries, follows relative runtime imports/re-exports and validated Worker references, excludes unreachable migration source, and fails unresolved ordinary edges or dynamic imports at the importer source location before code generation. |
14
14
  | Ordered normalization | [`framework/compiler/normalization-pipeline.mjs`](../../framework/compiler/normalization-pipeline.mjs), `applyNormalizationPasses()`; [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `normalizeCompilerSource()` | Applies migration/resource passes in order and repairs TypeScript parent pointers after every structural change. Imported source uses the same pipeline. |
15
15
  | Focused normalization passes | [`framework/compiler/`](../../framework/compiler/) | React, Router, browser signals, animation-frame refs, custom-hook timers, Zustand, and render control each validate and lower a narrow source shape. |
@@ -17,8 +17,9 @@ This maps the current `0.8.36` architecture, built on the completed `0.8.23` Goa
17
17
  | Source-local binding index | [`framework/compiler/analysis/binding-index.mjs`](../../framework/compiler/analysis/binding-index.mjs) | After normalization, assigns deterministic lexical slots and classifies local, parameter, import, capture, global, and unresolved references. Native handler, effect, binding, list evaluator, optimized-command, and effect-resource consumers use complete index-owned AST; synthesized expressions retain the existing fallback. |
18
18
  | 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. |
19
19
  | 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. |
20
- | 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, source-local SiteIds, and source provenance; AST identity remains private to its source-local session. |
21
- | 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. |
20
+ | Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Produces ComponentAnalysis v2 with ordered JSON-safe owner and specialization slots for state, setters, props, refs, IDs, direct SignalIR links, structural OwnerRefs, source-local SiteIds, and source provenance; AST identity remains private to its source-local session. |
21
+ | Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes ModuleIR v2 with deterministic SymbolRef, SignalIR, HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and ImportIR slots. One fail-closed boundary validates every source-local and component ownership edge before build-module generation. |
22
+ | Route artifact graph | [`framework/compiler/route-build-record.mjs`](../../framework/compiler/route-build-record.mjs), `createRouteBuildRecord()`, `planRouteArtifacts()` | Validates each rendered route's RouteIR, capabilities, entry paths, styles, and exact handler/effect references. Handler modules, Workers, package-client modules, and bundle entry/chunk closure derive from structural edges without serialized output searches or formatted composite keys. |
22
23
  | 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) | Direct commands use the existing fast path; proven immutable state aliases and one-call local helpers specialize to the same JSON-safe command ModuleIR. Recursion, escape, mutation, and dynamic helper dispatch fail explicitly, while unrelated handlers retain native ESM. Codegen emits the existing `__kBehavior` AST and command ABI. |
23
24
  | 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. |
24
25
  | 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. |
@@ -26,11 +27,11 @@ This maps the current `0.8.36` architecture, built on the completed `0.8.23` Goa
26
27
  | Effect analysis | [`framework/compiler/effect-analysis.mjs`](../../framework/compiler/effect-analysis.mjs) | Classifies ordered signal, derived, and keyed-item dependencies and validates cleanup-owned browser resources before EffectIR registration. |
27
28
  | Worker graph | [`framework/compiler/worker-compiler.mjs`](../../framework/compiler/worker-compiler.mjs) | Returns functional Worker rewrite results and JSON-safe EffectIR edges, validates relative graphs, emits content-hashed ESM, and resolves placeholders only for rendered effects. |
28
29
  | Shared path conversion | [`framework/compiler/path-helpers.mjs`](../../framework/compiler/path-helpers.mjs) | Converts project-relative module, browser, asset, and base paths for build and development serving. |
29
- | Build-time JSX execution | [`framework/core.mjs`](../../framework/core.mjs), `renderPage()` | Executes compiled pages/layouts, allocates deterministic route/layout ownership IDs, emits complete HTML, and returns RouteIR v1 plus capability facts. |
30
- | 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. |
30
+ | Build-time JSX execution | [`framework/core.mjs`](../../framework/core.mjs), `renderPage()` | Executes compiled pages/layouts, allocates deterministic route/layout ownership IDs, emits complete HTML, and returns RouteIR v1 plus capability facts and exact retained handler references. |
31
+ | Route capability projection | [`framework/compiler/route-capability-planner.mjs`](../../framework/compiler/route-capability-planner.mjs), `planRouteCapabilities()` | Validates RouteBuildRecord and RouteIR v1, then purely folds their plans and capability facts into CapabilityIR v1. |
31
32
  | Effect entry generation | [`framework/compiler/effect-codegen.mjs`](../../framework/compiler/effect-codegen.mjs) | Generates ordinary, dependency, owned, and navigable effect entries from rendered descriptors. |
32
33
  | 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. |
33
- | Artifact emission | `framework/build.mjs` | Selects required files from CapabilityIR, writes route HTML in bounded batches, writes and bundles the complete generation in a project-local staging sibling, copies public subtrees without replacing generated paths, runs `afterBuild`, then promotes with rollback so failed builds preserve the prior `dist`. Byte-identical native, parameter, and effect route entries reuse one exact-source transform result within the current build only. |
34
+ | Artifact emission | `framework/build.mjs` | Selects route artifacts from RouteBuildRecord edges and shared runtimes from CapabilityIR, writes route HTML in bounded batches, writes and bundles the complete generation in a project-local staging sibling, copies public subtrees without replacing generated paths, runs `afterBuild`, then promotes with rollback so failed builds preserve the prior `dist`. Byte-identical native, parameter, and effect route entries reuse one exact-source transform result within the current build only. |
34
35
  | Browser capabilities | [`framework/*.js`](../../framework/) | Small optional modules for commands, bindings, lists, effects, native handlers, serialization, parameters, and navigation; native contexts invalidate writes and refs at DOM ownership release, with no component runtime. |
35
36
  | 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. |
36
37
  | 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; failed rebuilds show the existing error overlay while preserving the previous on-disk output. |
@@ -50,10 +51,11 @@ src/pages entries + config
50
51
  -> build orchestration writes .kudzu executable and handler modules
51
52
  -> import page modules and execute renderPage()
52
53
  -> complete HTML
53
- -> RouteIR v1
54
- -> referenced handler URLs and route facts
54
+ -> RouteIR v1 and exact handler references
55
+ -> validated RouteBuildRecord per emitted route
56
+ -> capability facts, route entries, styles, handler/effect artifact edges
55
57
  -> remove unrendered handlers/effects/Workers
56
- -> planRouteCapabilities(RouteIR records, route facts)
58
+ -> planRouteCapabilities(RouteBuildRecord[])
57
59
  -> CapabilityIR v1
58
60
  -> specialize and emit only selected runtime/capability ESM
59
61
  -> reuse exact generated route-entry transforms within this build
@@ -68,8 +70,8 @@ The browser consumes static HTML first. State seeds and descriptors in that HTML
68
70
  ## Residual Coupling
69
71
 
70
72
  - `createKudzuTransformer()` remains one large source-local analysis unit combining validation, specialization, descriptor registration, and transformed-source emission.
71
- - Transient component rewrite indexes remain source-local AST indexes; handler, binding, derived, keyed, effect, and component ownership now have explicit JSON-safe source results.
72
- - `build()` still owns explicit artifact selection and filesystem writes after generator results are produced.
73
+ - Transient component rewrite indexes remain source-local AST indexes; finalized handler, binding, derived, keyed, effect, signal, import, and component ownership edges use explicit JSON-safe slots or stable symbols.
74
+ - `build()` still owns filesystem writes after structural route artifact selection and generator results are produced.
73
75
  - Runtime generators intentionally specialize readable authored sources through exact anchors; every required anchor fails closed, but a future generator format may remove this transitional dependency.
74
76
  - Source reachability and source compilation remain in one session-bound compiler factory because both consume the same normalization and import graph contracts. ModuleSymbol resolution is stable across canonical and cloned trees; unsupported export syntax remains intentionally narrow.
75
77
  - Imported declarations resolve by ModuleSymbol and source-local SiteId. Specialized and compiler-synthesized trees still use conservative name/scope fallback where the source-local binding index does not own the complete AST.
@@ -138,6 +138,8 @@ This is an incremental evolution of the current repository:
138
138
  - [x] P0.7 Parsed module and export summary caching is complete in `0.8.34`. Canonical read-only source trees and narrow export summaries are invalidated together by source text and remain ProjectSession-local; every normalization context receives a deep clone with independent parent links. A 100-importer fixture parses and summarizes 103 unique page/barrel/component/helper modules exactly once and creates 200 importer-local clones. The maintained paired benchmark preserves the complete source-result digest and establishes no material timing or peak-RSS conclusion; all 193 tests and package checks pass without broadening exports, source syntax, or browser output.
139
139
  - [x] P0.8 Stable ModuleSymbol and SiteId is complete in `0.8.35`. ProjectSession records source-local declaration, import, and re-export sites and resolves stable ModuleSymbol records through default/named exports, aliases, barrel chains, `export *`, ambiguity, and cycles. Cross-module compiler consumers locate resolved declarations by SiteId in their private normalized clones, while component calls, hooks, keyed blocks, effects, and ownership records expose deterministic source-local SiteIds. Repeated sessions and compilations preserve IDs, and symbol-only barrel traversal avoids cloning intermediate modules without changing browser artifacts or source syntax.
140
140
  - [x] P0.9 Semantic State Operations is complete in `0.8.36`. Direct setters, one immutable state-value alias, one synchronous zero-argument arrow helper, and one synchronous one-parameter function helper lower to identical existing command HandlerIR. Binding identity proves state/setter/helper/parameter ownership; recursion, escape, mutation, and dynamic helper dispatch fail at authored source locations. Existing direct command specialization remains first, unrelated safe handlers retain native ESM, and no command ABI, runtime, JavaScript VM, or general expression IR is added.
141
+ - [x] P0.10 ModuleIR Reference Unification is complete in `0.8.37`. ModuleIR and ComponentAnalysis v2 assign deterministic slots to symbols, signals, handlers, bindings, derived values, effects, keyed blocks, imports, owners, specializations, states, refs, and IDs. State, capture, import, effect, collection, parent/child, specialization, and row edges use structural slots or ModuleSymbol records while readable names remain codegen/debug metadata. A fail-closed pre-codegen validator rejects malformed slots, unsupported versions, duplicate exports, broken reciprocity, and ownership cycles; focused JSON round-trip checks and all 198 tests pass without changing browser runtime behavior.
142
+ - [x] P0.11 RouteBuildRecord and Artifact Graph is complete in `0.8.38`. Each rendered route records RouteIR, capability facts, route-entry paths, styles, and exact handler/effect references. Build orchestration derives Handler ESM, Worker, package-client, and chunk closure from those edges; serialized HTML/plan `includes()` searches, formatted effect keys, and parallel route-fact/entry arrays are removed. Focused malformed-reference and JSON round-trip checks plus the standard suite preserve the exact 173-file deploy digest and bytes.
141
143
 
142
144
  ### P0: Semantic Correctness And Compiler Foundation
143
145
 
@@ -350,6 +352,8 @@ function increment(value) { setCount(value + 1) }; increment(count)
350
352
 
351
353
  **Done condition:** every HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and component specialization edge validates before build-module generation.
352
354
 
355
+ **Completed in `0.8.37`:** ModuleIR and ComponentAnalysis v2 replace formatted owner keys and cross-record export/name lookups with source-local slots, StateRef/OwnerRef records, SymbolRef slots, and stable ModuleSymbol records. Descriptor finalization resolves imports, handlers, bindings, effects, derived dependencies, keyed collections, row ownership, and component prop signals before one fail-closed validation boundary. The validator checks slot/index integrity, state/setter ownership, captures/imports, duplicate exports, effect-handler roles, keyed parent/child reciprocity and cycles, specialization/ref edges, and JSON-safe round trips. Existing export names and state spellings remain only where emitted module/runtime ABIs require them.
356
+
353
357
  ### PR 11: RouteBuildRecord And Artifact Graph
354
358
 
355
359
  **Objective:** make route artifact selection structural.
@@ -358,6 +362,8 @@ function increment(value) { setCount(value + 1) }; increment(count)
358
362
 
359
363
  **Done condition:** handler, effect, Worker, CSS, package, and chunk reachability derives from explicit route edges; serialized `includes()` searches and parallel route-fact maps are removed.
360
364
 
365
+ **Completed in `0.8.38`:** `renderPage()` retains exact handler references only when final event, effect, binding, conditional, or keyed-list descriptors are emitted. RouteBuildRecord validates those references with RouteIR, capability facts, route-entry paths, and styles before `build.mjs` projects runtime entries and artifact closure. Handler modules resolve by exact URL, Worker references resolve by structural module/handler pairs, and package-client/chunk roots derive from retained modules. The clean 152-page baseline and candidate builds produce byte-identical `kudzu-plan.json`, 173 deploy files, raw/gzip bytes, and deploy digest with no material build-time regression.
366
+
361
367
  ### PR 12: Deep RouteIR And CapabilityIR Validation
362
368
 
363
369
  **Objective:** fail before codegen for invalid concrete route references and capability projections.
@@ -471,4 +477,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
471
477
 
472
478
  ## Immediate Decision
473
479
 
474
- PR 1 through PR 9 are complete. The next PR is **PR 10: ModuleIR Reference Unification**. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
480
+ PR 1 through PR 11 are complete. The next PR is **PR 12: Deep RouteIR And CapabilityIR Validation**. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
@@ -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.36` are completed scopes represented by package/release records.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.38` 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
 
@@ -27,6 +27,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
27
27
  | `0.8.34` | Cache canonical parsed modules and narrow export summaries within one ProjectSession while cloning transformer input. | A 100-importer fixture parses and summarizes 103 unique modules once, creates independent normalization trees, preserves the complete source-result digest, and leaks no cache state across projects. |
28
28
  | `0.8.35` | Resolve cross-module declarations through stable ModuleSymbol and source-local SiteId records. | Default/named exports, aliases, barrel chains, `export *`, cycles, ambiguity, repeated compilation, source invalidation, private clones, and measured build behavior remain deterministic. |
29
29
  | `0.8.36` | Lower equivalent direct, aliased, and local-helper state updates through existing command HandlerIR. | The four required forms emit identical commands and no native handler module; recursion, escape, mutation, and dynamic dispatch fail at authored source locations. |
30
+ | `0.8.37` | Unify ModuleIR and ComponentAnalysis references through deterministic source-local slots and stable symbols. | Every handler, binding, derived, effect, keyed, specialization, state, ref, capture, and import edge validates before build-module generation. |
31
+ | `0.8.38` | Replace serialized route artifact discovery with validated RouteBuildRecord edges. | Handler, effect, Worker, CSS, package-client, and chunk retention is structural while deploy files, bytes, plans, and browser behavior remain unchanged. |
30
32
 
31
33
  ## Sequence Rules
32
34
 
@@ -68,7 +68,7 @@ Reduced Zustand migration stores lower to one ordinary layout-lifetime state slo
68
68
  - `dev-state.js`: dev-only, short-lived logical-state snapshot validation and restoration.
69
69
  - `*.d.ts`: public TypeScript and JSX declarations.
70
70
 
71
- Compiler ownership follows explicit stages. `build.mjs` coordinates project discovery, source compilation, RouteIR rendering, CapabilityIR planning, generator calls, and artifact emission. `compiler/normalization-pipeline.mjs` owns pass order and parent repair. The main transformer analyzes normalized source while `compiler/descriptor-session.mjs` owns one per-source semantic artifact containing HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, and client imports. `core.mjs` emits complete HTML and RouteIR v1: route-local state `slot` is an internal array reference, `id` remains the browser/DOM identity, and `name` remains readable development metadata. `compiler/route-capability-planner.mjs` validates those plans and projects CapabilityIR v1. Focused codegen modules consume IR/descriptors and return route-specific source without source analysis or filesystem ownership. The completed architecture record lives in `docs/next-architecture`; it adds no runtime or accepted syntax by itself.
71
+ Compiler ownership follows explicit stages. `build.mjs` coordinates project discovery, source compilation, RouteIR rendering, RouteBuildRecord collection, CapabilityIR planning, generator calls, and artifact emission. `compiler/normalization-pipeline.mjs` owns pass order and parent repair. The main transformer analyzes normalized source while `compiler/descriptor-session.mjs` owns one per-source semantic artifact containing HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, and client imports. `core.mjs` emits complete HTML, RouteIR v1, and exact handler references: route-local state `slot` is an internal array reference, `id` remains the browser/DOM identity, and `name` remains readable development metadata. `compiler/route-build-record.mjs` validates structural artifact edges, and `compiler/route-capability-planner.mjs` projects CapabilityIR v1 from those records. Focused codegen modules consume IR/descriptors and return route-specific source without source analysis or filesystem ownership. The completed architecture record lives in `docs/next-architecture`; it adds no runtime or accepted syntax by itself.
72
72
 
73
73
  New syntax support belongs in an existing pass or a focused new pass only when a reduced migration fixture proves it. Passes must preserve source-located diagnostics, avoid module-global analysis state, and expose metadata through return values rather than AST-identity side channels. Build orchestration stays in `build.mjs`; feature-specific graph validation or code generation moves under `compiler/` when it has a stable input/output boundary.
74
74
 
@@ -7,6 +7,7 @@ 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
9
  import { createProjectSession } from "./compiler/project-session.mjs"
10
+ import { createRouteBuildRecord, planRouteArtifacts } from "./compiler/route-build-record.mjs"
10
11
  import { createSourceCompiler } from "./compiler/source-compiler.mjs"
11
12
  import { createParamCodegen } from "./compiler/param-codegen.mjs"
12
13
  import { planRouteCapabilities, usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
@@ -113,18 +114,12 @@ async function buildInto(project, outputDirectory, { minify }) {
113
114
  return effect.workers.map(worker => ({ ...worker, module: assetPath(base, `assets/${result.handlerModule.path}`), handler: handler.exportName }))
114
115
  }))
115
116
 
116
- const plans = []
117
- const routeCapabilities = new Map()
118
- const pageEntries = []
119
- const effectEntries = []
120
- const nativeEntries = []
121
- const paramEntries = []
117
+ const routeRecords = []
122
118
  const routeEntryTransforms = new Map()
123
119
  const rewrites = []
124
120
  const emittedRoutes = new Set()
125
121
  const emittedApplicationRoutes = new Set()
126
122
  const emittedNavigationRecords = []
127
- const renderedHandlerUrls = new Set()
128
123
  const styleUrls = [...new Set([
129
124
  ...cssFiles.map(file => assetPath(base, `assets/${relative(sourceDirectory, file).replaceAll(sep, "/")}`)),
130
125
  ...configuredStyles.urls
@@ -190,30 +185,37 @@ async function buildInto(project, outputDirectory, { minify }) {
190
185
  runtimeParams: runtimeSchema?.params,
191
186
  ...(navigable ? { navigationAsset: navigationGroup.assetPath, applicationId: navigationGroup.applicationId, layoutId: navigationGroup.layoutId, routeId: applicationRoute } : {})
192
187
  }, props, module.layout)
193
- const renderedOutput = `${JSON.stringify(result.plan)}\n${result.html}`
194
- for (const url of result.handlerModules) if (renderedOutput.includes(JSON.stringify(url))) renderedHandlerUrls.add(url)
195
188
  if (navigationGroup) {
196
189
  navigationGroup.hasEffects ||= result.hasEffects
197
190
  navigationGroup.hasParams ||= result.hasParams
198
191
  }
199
192
  const usesDependencyRuntime = usesRouteDependencyRuntime({ plan: result.plan, navigable, hasBindings: result.hasBindings, hasLists: result.hasLists })
200
- pageEntries.push({ route, html: result.html, usesDependencyRuntime })
201
- plans.push({ route: routePath, ...result.plan })
202
- routeCapabilities.set(routePath, {
203
- navigable,
204
- usesDependencyRuntime,
205
- hasBehaviors: result.hasBehaviors,
206
- hasBindings: result.hasBindings,
207
- hasLists: result.hasLists,
208
- hasListStyles: result.hasListStyles,
209
- hasStateSeed: result.hasStateSeed
210
- })
211
- if (result.hasParams) paramEntries.push({ path: paramPath, schema: runtimeSchema, params: result.plan.params, searchParams: result.plan.searchParams, searchParamsWritable: result.plan.searchParamsWritable, usesDependencyRuntime, navigable })
212
- if (result.hasEffects) effectEntries.push({ path: effectPath, effects: runtimeEffects(result.plan.effects, navigable), paramPath: result.hasParams ? paramPath : undefined, usesDependencyRuntime, navigable })
213
- if (result.plan.events.some(event => event.native)) nativeEntries.push({
214
- path: nativePath,
215
- modules: [...new Set(result.plan.events.filter(event => event.native).map(event => event.native.module))]
216
- })
193
+ const plan = { route: routePath, ...result.plan }
194
+ routeRecords.push(createRouteBuildRecord({
195
+ route: routePath,
196
+ output: route,
197
+ html: result.html,
198
+ plan,
199
+ handlerReferences: result.handlerReferences,
200
+ styles: styleUrls,
201
+ capabilities: {
202
+ navigable,
203
+ usesDependencyRuntime,
204
+ hasBehaviors: result.hasBehaviors,
205
+ hasBindings: result.hasBindings,
206
+ hasLists: result.hasLists,
207
+ hasListStyles: result.hasListStyles,
208
+ hasStateSeed: result.hasStateSeed,
209
+ hasParams: result.hasParams,
210
+ hasEffects: result.hasEffects
211
+ },
212
+ entries: {
213
+ ...(result.hasParams ? { param: paramPath } : {}),
214
+ ...(result.hasEffects ? { effect: effectPath } : {}),
215
+ ...(result.plan.events.some(event => event.native) ? { native: nativePath } : {})
216
+ },
217
+ runtimeSchema
218
+ }))
217
219
  }
218
220
  }
219
221
 
@@ -227,9 +229,8 @@ async function buildInto(project, outputDirectory, { minify }) {
227
229
 
228
230
  const assetsDirectory = join(outputDirectory, "assets")
229
231
  await mkdir(assetsDirectory, { recursive: true })
230
- const emittedHandlerModules = handlerModules.filter(module => renderedHandlerUrls.has(assetPath(base, `assets/${module.path}`)))
231
- const renderedEffects = new Set(plans.flatMap(plan => plan.effects.map(effect => `${effect.module}:${effect.handler}`)))
232
- const renderedWorkerReferences = workerReferences.filter(reference => renderedEffects.has(`${reference.module}:${reference.handler}`))
232
+ const { handlerModules: emittedHandlerModules, workerReferences: renderedWorkerReferences, styles: renderedStyles } = planRouteArtifacts(routeRecords, handlerModules, workerReferences, module => assetPath(base, `assets/${module.path}`))
233
+ const renderedStyleUrls = new Set(renderedStyles)
233
234
  if (renderedWorkerReferences.length && await exists(join(publicDirectory, "assets", "workers"))) throw new Error("public/assets/workers collides with Kudzu's generated Worker asset namespace")
234
235
  const workerAssets = await project.workerCompiler.emit(renderedWorkerReferences, sourceFileSet, assetsDirectory, base, minify)
235
236
  for (const module of emittedHandlerModules) {
@@ -240,7 +241,8 @@ async function buildInto(project, outputDirectory, { minify }) {
240
241
  }
241
242
  if (module.code.includes("/__kudzu_worker_")) throw new Error(`Worker URL placeholder survived in ${module.path}`)
242
243
  }
243
- const capabilityIR = planRouteCapabilities(plans, { routes: routeCapabilities, navigationRouteCount: navigationRoutes.length })
244
+ const plans = routeRecords.map(record => record.plan)
245
+ const capabilityIR = planRouteCapabilities(routeRecords, { navigationRouteCount: navigationRoutes.length })
244
246
  const {
245
247
  routes: { behaviors: behaviorCount, regularBehaviors: regularBehaviorCount, dependencyStateSeeds: dependencyStateSeedCount },
246
248
  events: { command: commandEvents, hasNativeHandlers },
@@ -251,11 +253,11 @@ async function buildInto(project, outputDirectory, { minify }) {
251
253
  runtime: { shared: hasSharedRuntime, dependency: hasDependencyRuntime }
252
254
  } = capabilityIR
253
255
  const runtimeName = usesDependencyRuntime => usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js"
254
- for (let offset = 0; offset < pageEntries.length; offset += 64) {
255
- await Promise.all(pageEntries.slice(offset, offset + 64).map(async entry => {
256
- const routeDirectory = join(outputDirectory, entry.route)
256
+ for (let offset = 0; offset < routeRecords.length; offset += 64) {
257
+ await Promise.all(routeRecords.slice(offset, offset + 64).map(async record => {
258
+ const routeDirectory = join(outputDirectory, record.output)
257
259
  await mkdir(routeDirectory, { recursive: true })
258
- const html = preloadModules(entry.html.replace(runtimePlaceholder, escapeAttribute(assetPath(base, `assets/${runtimeName(entry.usesDependencyRuntime)}`))))
260
+ const html = preloadModules(record.html.replace(runtimePlaceholder, escapeAttribute(assetPath(base, `assets/${runtimeName(record.capabilities.usesDependencyRuntime)}`))))
259
261
  await writeFile(join(routeDirectory, "index.html"), html)
260
262
  }))
261
263
  }
@@ -291,7 +293,10 @@ async function buildInto(project, outputDirectory, { minify }) {
291
293
  if (hasNativeHandlers) {
292
294
  const generated = generateNativeRuntime(await readFile(new URL("./native-runtime.js", import.meta.url), "utf8"), capabilityIR)
293
295
  await writeJavaScript(join(assetsDirectory, "kudzu-native.js"), generated.source, minify, generated.define)
294
- for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify, routeEntryTransforms)
296
+ for (const record of routeRecords) if (record.entries.native) await printNativeEntry({
297
+ path: record.entries.native,
298
+ modules: [...new Set(record.plan.events.filter(event => event.native).map(event => event.native.module))]
299
+ }, assetsDirectory, base, minify, routeEntryTransforms)
295
300
  }
296
301
  if (navigationGroups.length) {
297
302
  const navigationSource = await readFile(new URL("./navigation-runtime.js", import.meta.url), "utf8")
@@ -304,15 +309,15 @@ async function buildInto(project, outputDirectory, { minify }) {
304
309
  await mkdir(resolve(output, ".."), { recursive: true })
305
310
  await writeJavaScript(output, handlerModule.code, minify)
306
311
  }
307
- for (const entry of paramEntries) {
308
- const output = join(assetsDirectory, entry.path)
312
+ for (const record of routeRecords) if (record.entries.param) {
313
+ const output = join(assetsDirectory, record.entries.param)
309
314
  await mkdir(dirname(output), { recursive: true })
310
- await writeRouteEntry(output, printParamEntry(entry.schema, entry.params, entry.searchParams, entry.searchParamsWritable, output, assetsDirectory, base, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
315
+ await writeRouteEntry(output, printParamEntry(record.runtimeSchema, record.plan.params, record.plan.searchParams, record.plan.searchParamsWritable, output, assetsDirectory, base, runtimeName(record.capabilities.usesDependencyRuntime), record.capabilities.navigable), minify, routeEntryTransforms)
311
316
  }
312
- for (const entry of effectEntries) {
313
- const output = join(assetsDirectory, entry.path)
317
+ for (const record of routeRecords) if (record.entries.effect) {
318
+ const output = join(assetsDirectory, record.entries.effect)
314
319
  await mkdir(dirname(output), { recursive: true })
315
- await writeRouteEntry(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
320
+ await writeRouteEntry(output, printEffectEntry(runtimeEffects(record.plan.effects, record.capabilities.navigable), output, emittedHandlerModules, assetsDirectory, base, record.entries.param, runtimeName(record.capabilities.usesDependencyRuntime), record.capabilities.navigable), minify, routeEntryTransforms)
316
321
  }
317
322
  const clientModules = await collectClientModules(emittedHandlerModules.flatMap(module => module.clientImports).map(file => resolve(root, file)), sourceFileSet)
318
323
  for (const file of clientModules) {
@@ -342,7 +347,7 @@ async function buildInto(project, outputDirectory, { minify }) {
342
347
  }
343
348
  const sortedRewrites = rewrites.sort((left, right) => runtimeSpecificity(right) - runtimeSpecificity(left) || left.pattern.localeCompare(right.pattern))
344
349
  await writeFile(join(workDirectory, "kudzu-plan.json"), JSON.stringify({ routes: plans, rewrites: sortedRewrites }, null, 2))
345
- for (const file of cssFiles) {
350
+ for (const file of cssFiles.filter(file => renderedStyleUrls.has(assetPath(base, `assets/${relative(sourceDirectory, file).replaceAll(sep, "/")}`)))) {
346
351
  const output = join(assetsDirectory, relative(sourceDirectory, file))
347
352
  await mkdir(dirname(output), { recursive: true })
348
353
  await writeFile(output, cssOutputs.get(file))
@@ -353,7 +358,7 @@ async function buildInto(project, outputDirectory, { minify }) {
353
358
  await mkdir(dirname(output), { recursive: true })
354
359
  await writeFile(output, await readFile(file))
355
360
  }
356
- for (const style of configuredStyles.sources) {
361
+ for (const style of configuredStyles.sources.filter(style => renderedStyleUrls.has(withBase(base, style.output)))) {
357
362
  let css = await readFile(style.source, "utf8")
358
363
  if (style.transform) {
359
364
  const result = await style.transform(css, { source: style.source, output: style.output })
@@ -8,6 +8,7 @@ export function createBindingIndex(sourceFile) {
8
8
  const scopeByNode = new WeakMap()
9
9
  const referenceBindings = new WeakMap()
10
10
  const originalReferenceBindings = new WeakMap()
11
+ const declarationBindings = new WeakMap()
11
12
  const bindings = []
12
13
  const rootScope = createScope(undefined, sourceFile, "module")
13
14
  const pseudoBindings = new Map()
@@ -31,6 +32,8 @@ export function createBindingIndex(sourceFile) {
31
32
  bindings.push(binding)
32
33
  scope.bindings.set(identifier.text, binding)
33
34
  }
35
+ declarationBindings.set(identifier, binding)
36
+ declarationBindings.set(ts.getOriginalNode(identifier), binding)
34
37
  return binding
35
38
  }
36
39
 
@@ -218,6 +221,17 @@ export function createBindingIndex(sourceFile) {
218
221
  }
219
222
  }
220
223
 
224
+ function resolveBinding(identifier) {
225
+ const binding = declarationBindings.get(identifier) ?? declarationBindings.get(ts.getOriginalNode(identifier)) ?? lexicalBinding(scopeByNode.get(identifier) ?? rootScope, identifier.text)
226
+ if (!binding) return undefined
227
+ return {
228
+ slot: binding.slot,
229
+ debugName: binding.debugName,
230
+ declarationKind: binding.declarationKind,
231
+ ...(binding.declaration ? { declarationRange: range(binding.declaration) } : {})
232
+ }
233
+ }
234
+
221
235
  function references(root, boundary = root) {
222
236
  const found = []
223
237
  let complete = true
@@ -234,7 +248,7 @@ export function createBindingIndex(sourceFile) {
234
248
  return complete ? found : undefined
235
249
  }
236
250
 
237
- return { bindings: () => bindings.map(({ declaration, ...binding }) => ({ ...binding, ...(declaration ? { declarationRange: range(declaration) } : {}) })), hasNode: node => scopeByNode.has(node), references, resolveReference }
251
+ return { bindings: () => bindings.map(({ declaration, ...binding }) => ({ ...binding, ...(declaration ? { declarationRange: range(declaration) } : {}) })), hasNode: node => scopeByNode.has(node), references, resolveBinding, resolveReference }
238
252
  }
239
253
 
240
254
  function inside(node, boundary) {
@@ -1,5 +1,5 @@
1
1
  export function createComponentAnalysis(file) {
2
- return { version: 1, file, owners: [], specializations: [] }
2
+ return { version: 2, file, owners: [], specializations: [] }
3
3
  }
4
4
 
5
5
  export function createComponentAnalysisSession(analysis) {
@@ -40,7 +40,13 @@ export function createComponentAnalysisSession(analysis) {
40
40
  }
41
41
 
42
42
  function registerSpecialization(descriptor) {
43
- const specialization = { slot: analysis.specializations.length, ...descriptor }
43
+ const specialization = {
44
+ slot: analysis.specializations.length,
45
+ ...descriptor,
46
+ states: (descriptor.states ?? []).map((state, slot) => ({ slot, ...state })),
47
+ refs: (descriptor.refs ?? []).map((ref, slot) => ({ slot, ...ref })),
48
+ ids: (descriptor.ids ?? []).map((id, slot) => ({ slot, ...id }))
49
+ }
44
50
  analysis.specializations.push(specialization)
45
51
  return specialization
46
52
  }