@kudzujs/core 0.8.26 → 0.8.28

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,6 +2,8 @@
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.28` 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
+
5
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.
6
8
 
7
9
  ## North Star
@@ -262,6 +264,18 @@ This queue orders the next investigations by general migration value. Start only
262
264
  - 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
265
  - Goal B records which build, output, keyed, Worker, integration, and heap evidence exists; no new optimization is authorized without another reproduced material loss.
264
266
 
267
+ ### Characterized In 0.8.27
268
+
269
+ - A reduced E2B Dashboard terminal fixture establishes callback-shared mutable browser resources, asynchronous generation invalidation, and BFCache retain/resume/discard behavior as a concrete Goal C boundary.
270
+ - Unsupported page-level mutable value refs fail during source analysis with a source location and the existing effect-owned animation-frame exception; no resource API or runtime is added.
271
+ - The large-application and AI-native execution plan orders symbol resolution, semantic operations, project/module analysis, IR authority, application foundations, ecosystem compatibility, AI tooling, and scale validation before broad feature work.
272
+
273
+ ### Completed In 0.8.28
274
+
275
+ - A source-local binding index assigns deterministic lexical identities to locals, parameters, imports, captures, known globals, and unresolved references after normalization.
276
+ - Reactive binding capture/import discovery and lowering distinguish outer application values from same-named browser globals and nested callback parameters without changing source syntax or adding browser bytes.
277
+ - Synthesized keyed expressions retain the existing fail-safe lowering path; native handler, effect, and remaining descriptor consumers stay ordered behind the next symbol-aware compiler patch.
278
+
265
279
  ## Cross-Cutting Performance Gates
266
280
 
267
281
  Every migration feature must preserve:
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.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).
17
+ **Latest release: 0.8.28 - Source-local binding index.** The first large-application compiler foundation classifies locals, parameters, imports, captures, globals, and unresolved references by lexical identity, then uses that index for reactive binding capture/import discovery and lowering. Read the [release notes](./RELEASES.md#0828---source-local-binding-index), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.28), 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.28 - Source-local binding index
4
+
5
+ Kudzu 0.8.28 completes the first large-application compiler foundation by resolving source-local lexical bindings before descriptor discovery and lowering.
6
+
7
+ ### Changed in 0.8.28
8
+
9
+ - A source-local binding index classifies value references as local declarations, parameters, imports, outer captures, known globals, or unresolved names with deterministic slots and source ranges.
10
+ - The index models module, function parameter/body, block, loop, catch, switch, class, computed method, namespace, import, destructuring, `var`, `let`, and `const` scopes without adding a TypeScript Program or cross-module graph.
11
+ - Reactive binding capture/import discovery uses lexical identity, so component locals named `document`, `location`, `history`, `navigator`, or `console` no longer become browser globals and shadowed callback parameters no longer become imports.
12
+ - Reactive binding lowering rewrites only the indexed occurrences that resolve to state or capture bindings. Same-named nested parameters remain local.
13
+ - Synthesized keyed expressions use the existing fail-safe path unless the complete expression can be indexed, preserving keyed row state and identity behavior.
14
+
15
+ ### Performance And Architecture
16
+
17
+ - A focused 1,000-reference guard verifies one index construction followed by direct indexed lookups and reports construction/lookup timing without making a cross-machine performance claim.
18
+ - The index is constructed after ordered normalization and parent repair, once per compiled source. It adds no public API, browser runtime, client metadata, or JavaScript bytes.
19
+ - Native handler, effect, and remaining descriptor discovery still use the existing analysis path; symbol-aware migration of those consumers remains the next P0.2 scope.
20
+
21
+ ### Validation
22
+
23
+ - `npm run check`, `npm run test:package`, and all 185 tests pass.
24
+ - Browser integration verifies shadowed browser-global names through initial HTML and reactive evaluator updates.
25
+ - React, Router, Zustand, keyed row, nested list, effect, Worker, navigation, and static-route zero-JavaScript behavior remain covered.
26
+
27
+ ### Upgrade
28
+
29
+ ```bash
30
+ npm install @kudzujs/core@^0.8.28
31
+ ```
32
+
33
+ ## 0.8.27 - Large-application compiler roadmap
34
+
35
+ Kudzu 0.8.27 records the evidence-backed path from the current static-first compiler to large production applications without adding a React runtime, generic client framework, or speculative resource API.
36
+
37
+ ### Changed in 0.8.27
38
+
39
+ - A reduced fixture from E2B Dashboard preserves a callback-shared terminal handle, asynchronous generation token, BFCache retention, resume, and discard cleanup as the first concrete Goal C resource-ownership boundary.
40
+ - Unsupported page-level mutable value refs now fail during source analysis with the source location and the existing effect-owned animation-frame exception instead of reaching the build-time DOM-ref runtime error.
41
+ - Goal C records independent WebSocket/SSE, shared request, and optimistic transaction candidates while explicitly withholding store, resource, query, cache, Provider, subscription, and scheduler APIs.
42
+ - The new large-application and AI-native execution plan audits the current compiler, IR, runtime, navigation, package, build, fixture, and benchmark boundaries and orders the next twelve independently verifiable PRs.
43
+ - The next implementation is fixed as a source-local binding index followed by symbol-aware descriptor discovery; ecosystem features cannot skip those semantic dependencies.
44
+
45
+ ### Architecture Boundary
46
+
47
+ - Existing ComponentAnalysis, ModuleIR, RouteIR, CapabilityIR, complete HTML, direct DOM ownership, native navigation, and capability runtimes remain the incremental foundation.
48
+ - The plan generalizes symbol identity, semantic state operations, component/module dataflow, resource/range ownership, artifact references, code splitting, diagnostics, migration analysis, and application-scale evidence without introducing a browser component tree.
49
+ - React ecosystem support is classified as Native, Compiled, Migrated, Adapter, Partial, or Unsupported so package-specific knowledge can move behind a compatibility boundary instead of accumulating in the core compiler.
50
+
51
+ ### Validation
52
+
53
+ - `npm run check`, `npm run test:package`, and all 176 tests pass.
54
+ - The E2B fixture remains an expected failure at its proven source boundary; no browser resource runtime or public API is emitted.
55
+ - Static-route zero JavaScript, existing animation-frame support, keyed-row diagnostics, setter-child diagnostics, and all current browser ownership behavior remain covered.
56
+
57
+ ### Upgrade
58
+
59
+ ```bash
60
+ npm install @kudzujs/core@^0.8.27
61
+ ```
62
+
3
63
  ## 0.8.26 - Goal B benchmark hardening
4
64
 
5
65
  Kudzu 0.8.26 makes the retained Goal B optimizations reproducible and directly regression-tested without changing generated runtime behavior or authoring support.
@@ -11,6 +11,8 @@ 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.28` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). It orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
15
+
14
16
  ## Required Invariants
15
17
 
16
18
  - Every route remains a complete static HTML document.
@@ -24,16 +26,18 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
24
26
  ## Reading Order
25
27
 
26
28
  1. [`compiler-current-architecture.md`](./compiler-current-architecture.md): exact current responsibilities and data flow.
27
- 2. [`goal-a-compiler-foundation.md`](./goal-a-compiler-foundation.md): approved extraction and generator work.
28
- 3. [`versioning.md`](./versioning.md): planned patch sequence and completion rules.
29
- 4. [`performance-gates.md`](./performance-gates.md): output, build, and browser gates.
30
- 5. [`goal-b-optimization-benchmarks.md`](./goal-b-optimization-benchmarks.md): active measurements, retained optimizations, and continuation rules.
31
- 6. [`goal-c-state-resource-research.md`](./goal-c-state-resource-research.md): research boundary only.
32
- 7. [`goal-d-routing-compatibility-decisions.md`](./goal-d-routing-compatibility-decisions.md): routing and islands decisions.
29
+ 2. [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md): active direction, priorities, PR queue, and completion evidence.
30
+ 3. [`goal-a-compiler-foundation.md`](./goal-a-compiler-foundation.md): completed extraction and generator foundation.
31
+ 4. [`versioning.md`](./versioning.md): completed patch sequence and future sequencing rules.
32
+ 5. [`performance-gates.md`](./performance-gates.md): output, build, and browser gates.
33
+ 6. [`goal-b-optimization-benchmarks.md`](./goal-b-optimization-benchmarks.md): active measurements, retained optimizations, and continuation rules.
34
+ 7. [`goal-c-state-resource-research.md`](./goal-c-state-resource-research.md): research boundary only.
35
+ 8. [`goal-d-routing-compatibility-decisions.md`](./goal-d-routing-compatibility-decisions.md): routing and islands decisions.
33
36
 
34
37
  ## Resume Checklist
35
38
 
36
39
  - [ ] Read [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) and this directory before planning migration work.
40
+ - [ ] Follow the PR dependency order in [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md); update it before broadening or reordering a patch.
37
41
  - [ ] Confirm `package.json` still reports the actual current version; never infer release status from this plan.
38
42
  - [ ] Inspect the worktree and preserve unrelated or uncommitted changes.
39
43
  - [ ] Start later architecture work only by an explicit decision from the completed Goal A baseline.
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
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.
3
+ This maps the current `0.8.28` 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
 
@@ -12,6 +12,7 @@ This maps the current `0.8.26` architecture built on the completed `0.8.23` Goal
12
12
  | 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. |
13
13
  | 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. |
14
14
  | Shared AST/scope helpers | [`framework/compiler/ast-helpers.mjs`](../../framework/compiler/ast-helpers.mjs) | Binding, scope, reference, effect-return, and source-location analysis. |
15
+ | 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. Reactive binding discovery/lowering consumes complete indexed expressions; synthesized expressions retain the existing fallback. |
15
16
  | 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
17
  | 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
18
  | 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. |
@@ -38,7 +39,9 @@ This maps the current `0.8.26` architecture built on the completed `0.8.23` Goal
38
39
  src/pages entries + config
39
40
  -> project discovery and reachable relative graph
40
41
  -> compileSource()
41
- -> ordered normalization and main transformer semantic analysis
42
+ -> ordered normalization and parent repair
43
+ -> source-local binding index
44
+ -> main transformer semantic analysis
42
45
  -> TypeScript transpilation
43
46
  -> JSON-safe build module, ModuleIR, handler module, and imported assets
44
47
  -> build orchestration writes .kudzu executable and handler modules
@@ -64,6 +67,7 @@ The browser consumes static HTML first. State seeds and descriptors in that HTML
64
67
  - `build()` still owns explicit artifact selection and filesystem writes after generator results are produced.
65
68
  - 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.
66
69
  - Source reachability and source compilation share one module because both consume the same normalization and import graph contracts.
70
+ - Native handler, effect, state/setter, list, Router, React, and Zustand discovery still contains name-based analysis. The binding index is intentionally adopted one consumer family at a time; P0.2 moves the next descriptor consumers without changing cross-module semantics.
67
71
 
68
72
  These are future simplification opportunities, not incomplete Goal A contracts. Goal A changed no source support, browser output semantics, or browser architecture.
69
73
 
@@ -36,6 +36,40 @@ Before any proposal, provide:
36
36
  - output and ownership costs;
37
37
  - an explanation of why existing async build rendering, state/effects, layout ownership, native platform storage, or effect-owned resources are insufficient.
38
38
 
39
+ ## First Application Survey
40
+
41
+ The first GitHub survey used active licensed React/TypeScript applications rather than library demos. It found four distinct pressure families:
42
+
43
+ | Application evidence | Ownership pressure | Research disposition |
44
+ |---|---|---|
45
+ | [E2B Dashboard terminal](https://github.com/e2b-dev/dashboard/blob/97164c9cbc8ced05b9039415318550f4e4df3874/src/features/dashboard/terminal/dashboard-terminal.tsx) | Mutable remote handle and generation tokens survive callbacks; BFCache entry retains the handle, real discard closes it, and restore resumes it | First reduced fixture because it is the smallest precise lifetime |
46
+ | [Mattermost WebSocket client](https://github.com/mattermost/mattermost/blob/53373e3752c6e8d7979b787f342fea4c56e68472/webapp/platform/client/src/websocket.ts) and [Twenty SSE provider](https://github.com/twentyhq/twenty/blob/cd288d84470503ba897c0823a9d98701fa9986ab/packages/twenty-front/src/modules/sse-db-event/components/SSEProvider.tsx) | One document/layout transport has many independently mounted route subscribers plus reconnect and replay state | Strong cross-owner subscription research; too broad for the first fixture |
47
+ | [Actual Budget queries](https://github.com/actualbudget/actual/blob/87e33e49eb4d666c33e0c0f172faf340d1b7aa0a/packages/desktop-client/src/payees/queries.ts) and [Plane workspace wrapper](https://github.com/makeplane/plane/blob/31853ab2b8b7810c59dc30d22e52c8f4b5a71a47/apps/web/core/layouts/auth-layout/workspace-wrapper.tsx) | Shared keyed request results outlive route consumers and are invalidated by external events or owner changes | Candidate for keyed layout/document ownership, not authorization for a query API |
48
+ | [InstantDB optimistic update](https://github.com/instantdb/instant/blob/30561b01ea850514dd4e0270bdd6ff3ed159bbb1/client/www/lib/auth.ts) and [Raven reaction update](https://github.com/The-Commit-Company/raven/blob/a29a274466b7a060754839b3c8e29bbee012a61d/packages/lib/hooks/useReactToMessage.ts) | Multiple consumers observe one optimistic snapshot that must commit or roll back atomically | Two independent examples of shared optimistic transactions; still research only |
49
+
50
+ ### E2B Reduced Fixture
51
+
52
+ The repository-owned fixture at `test/fixtures/goal-c-e2b-terminal` preserves the relevant conventional React shape without E2B's UI, SDK, or `useEffectEvent` dependency:
53
+
54
+ - one `null`-initialized remote handle ref;
55
+ - one numeric generation ref invalidating late asynchronous opens;
56
+ - `pagehide.persisted` retaining the handle for BFCache;
57
+ - non-persisted `pagehide` and effect cleanup closing it;
58
+ - `pageshow.persisted` resuming the retained handle;
59
+ - one unrelated static sibling route.
60
+
61
+ The build fails before BFCache behavior can be evaluated:
62
+
63
+ ```text
64
+ src/pages/index.tsx:7:25 Mutable value useRef() is unsupported except for an effect-owned useRef(0) animation-frame handle; otherwise keep resource-private mutable values inside the owning effect
65
+ ```
66
+
67
+ The failure is triggered by the ordinary `useRef(0)` generation token during source analysis. Existing animation-frame specialization cannot safely cover this shape: the generation token has different writes and callback ownership, while the remote handle is a browser-only non-serializable value rather than a DOM ref. Supporting only the token would expose incorrect DOM-ref semantics for the handle.
68
+
69
+ The remaining question before any state/resource API discussion is whether independent fixtures justify a narrow specialization for callback-shared effect resources with document and BFCache ownership. Until then, the source-located diagnostic preserves the current boundary.
70
+
71
+ An effect-local rewrite is a useful control, but requiring applications to restructure ordinary callback-shared refs is not automatically an acceptable migration solution. The fixture remains an expected failure until research answers that boundary.
72
+
39
73
  ## Decision Boundary
40
74
 
41
75
  Research may produce notes, fixtures, measurements, or a narrow compiler-specialization proposal. It must not publish `createStore`, `resource`, `query`, cache, Provider, subscription, or scheduler APIs. A public API requires separate approval after at least two independent migration fixtures establish the same semantics and after zero-cost exclusion is demonstrated.
@@ -0,0 +1,457 @@
1
+ # Large Application And AI-Native Development Plan
2
+
3
+ ## Status
4
+
5
+ Active execution plan after `0.8.28`. This document turns the current compiler audit into an ordered implementation program. It does not mark any planned capability as supported and does not authorize a React runtime, VDOM, hydration, retained browser component tree, generic rerenderer, public store/query/resource API, SPA router, or islands.
6
+
7
+ [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains authoritative for product invariants and fixture-driven feature selection. This plan is authoritative for the order and completion evidence of compiler generalization, large-application foundations, compatibility boundaries, AI tooling, and scale validation. If implementation evidence changes a boundary, update this document before broadening a patch.
8
+
9
+ ## Product Outcome
10
+
11
+ Kudzu must support ordinary React-shaped TypeScript and TSX from small static sites through production SaaS, dashboards, CRUD, commerce, authenticated applications, realtime collaboration, data-heavy interfaces, and large module graphs. The browser should receive complete HTML and only the capabilities required by each route or application lifetime.
12
+
13
+ The differentiating KPI is `cost per successful task`, not source line count alone. A successful task includes build, browser behavior, accessibility, output, and ownership checks. Track input/output/reasoning tokens, files read and modified, tool calls, compiler attempts, failed attempts, correction cycles, completion time, source retention, and final client bytes.
14
+
15
+ The governing principle is:
16
+
17
+ > Write what AI already knows. Compile away what AI should not need to reason about.
18
+
19
+ ## Audited Baseline
20
+
21
+ The current implementation already provides:
22
+
23
+ - complete route HTML and zero JavaScript for static routes;
24
+ - synchronous logical state with direct DOM commits;
25
+ - route, layout, conditional, keyed, effect, ref, and Worker ownership;
26
+ - stable keyed row identity with nested lists, state, refs, effects, and SVG;
27
+ - stale effect-invocation isolation and explicit cleanup;
28
+ - narrow React, React Router, and Zustand-shaped normalization;
29
+ - JSON-safe ComponentAnalysis, ModuleIR, RouteIR v1, and CapabilityIR v1;
30
+ - no-write source compilation and capability-specific runtime emission;
31
+ - source-located fail-closed diagnostics and broad reduced-fixture coverage.
32
+
33
+ The current limiting architecture is observable in these files:
34
+
35
+ | Concern | Current owner | Limitation to remove |
36
+ |---|---|---|
37
+ | Project graph | `framework/compiler/source-graph.mjs`, `framework/build.mjs` | Global root, repeated parsing, narrow export resolution, no incremental project session |
38
+ | Normalization | `framework/compiler/source-compiler.mjs`, focused passes | Pass order and package ownership are implicit; semantically equal source often follows different shape-specific paths |
39
+ | Component/state analysis | `framework/compiler/source-compiler.mjs`, `analysis/component-analysis.mjs` | AST identity, identifier text, source offsets, and caller-side AST specialization remain central |
40
+ | Handler/binding analysis | `framework/compiler/descriptor-session.mjs`, `handler-lowering.mjs` | Capture/import/state discovery is name-based and arbitrary handlers become code before IR finalization |
41
+ | Effect/resource analysis | `effect-analysis.mjs`, resource-specific passes | Selected browser APIs have exact-shape ownership checks; there is no package-neutral resource model |
42
+ | Lists/ranges | `source-compiler.mjs`, `list-runtime.js` | Strong keyed ownership, but syntax recognition and runtime branches are list-specific and not a general range/window primitive |
43
+ | ModuleIR | `compiler/ir/module-ir.mjs` | Numeric slots, names, export strings, and formatted owner strings coexist; several sections are descriptive rather than authoritative inputs |
44
+ | RouteIR/CapabilityIR | `route-capability-planner.mjs` | Validation is shallow and capability selection is site-wide across interactive routes |
45
+ | Build/codegen | `framework/build.mjs`, runtime generators | Full destructive builds, serialized-string handler reachability, source-text runtime surgery, no route chunk/CSS closure report |
46
+ | Browser lifetime | `native-runtime.js`, `effect-runtime.js`, `navigation-runtime.js` | Effects invalidate stale work, but pending native handlers can write after route unmount |
47
+ | Compatibility | React/Router/Zustand passes plus core branches | Package-specific knowledge is not contained behind one adapter boundary |
48
+ | AI interface | CLI and string diagnostics | No stable diagnostic codes, semantic index, explain, fix, or migration analysis output |
49
+
50
+ ## Architecture Direction
51
+
52
+ ```text
53
+ ProjectSession
54
+ -> parsed module and package graph
55
+ -> compatibility adapter registry
56
+ -> normalized modules with stable declaration/site identities
57
+ -> binding and symbol resolution
58
+ -> source semantic analysis
59
+ component and prop edges
60
+ state, derived, event, and operation edges
61
+ effect and resource ownership
62
+ collection and range ownership
63
+ -> strengthened existing ModuleIR
64
+ -> build-time render instantiation
65
+ -> RouteBuildRecord with RouteIR and artifact edges
66
+ -> per-route capability signature and chunk closure
67
+ -> HTML, CSS, and capability-specific ESM
68
+ ```
69
+
70
+ This is an incremental evolution of the current repository:
71
+
72
+ - Keep ComponentAnalysis and ModuleIR; make their references stable and their records authoritative.
73
+ - Keep `renderPage()` for build-known execution and concrete route ownership IDs; stop using it to rediscover source semantics already present in ModuleIR.
74
+ - Keep RouteIR and CapabilityIR; deepen validation and replace parallel route facts with one RouteBuildRecord.
75
+ - Keep existing browser runtimes; extract package-neutral semantic primitives only after fixtures prove repeated ownership needs.
76
+ - Do not introduce a general JavaScript bytecode VM or model the complete TypeScript language in a new IR.
77
+
78
+ ## Required Invariants
79
+
80
+ - Static routes remain zero JavaScript.
81
+ - Interactive routes add no capability unrelated to their source and route lifetime.
82
+ - Complete HTML remains the initial document.
83
+ - Native navigation remains the fallback and default.
84
+ - React and ecosystem package source may be accepted, normalized, migrated, or adapted without executing React.
85
+ - Package-specific knowledge must not leak beyond the compatibility boundary into generic ownership, dataflow, IR, or browser runtime code.
86
+ - New semantic support starts from a real or reduced failing application fixture.
87
+ - Every patch is independently reviewable, fail-closed, and measured against representative output.
88
+ - A compiler refactor must preserve accepted source, diagnostics, RouteIR, HTML, assets, and browser ownership unless its scope explicitly changes one contract.
89
+
90
+ ## Preserve, Refactor, Avoid
91
+
92
+ ### Preserve
93
+
94
+ - Build-known execution and complete static output.
95
+ - Direct DOM ownership and synchronous logical state.
96
+ - Route/layout/key-path lifetime semantics.
97
+ - Keyed identity and exact state/effect/ref release.
98
+ - Effect invalidation and cleanup.
99
+ - JSON-safe, versioned stage boundaries.
100
+ - Source-located diagnostics.
101
+ - Rendered Worker exclusion and content hashing.
102
+ - Capability-specific output with no React runtime.
103
+
104
+ ### Refactor
105
+
106
+ - Identifier text into resolved SymbolRef records.
107
+ - AST identity and `pos:end` keys into stable source-local declaration/site IDs.
108
+ - Exact setter syntax into semantic value and state-operation analysis.
109
+ - Caller-side component AST copying into a component/prop/callback graph.
110
+ - Manual export discovery into a project module-symbol graph.
111
+ - Primitive-only dependencies into property-path and derived dependencies.
112
+ - Resource-specific ownership passes into a package-neutral ResourceIR when evidence permits.
113
+ - Serialized handler URL searches into explicit artifact references.
114
+ - Site-wide capability unions into route capability signatures.
115
+ - Full destructive development builds into project-session invalidation and atomic output.
116
+ - String-only diagnostics into structured diagnostics suitable for machines and AI agents.
117
+
118
+ ### Avoid
119
+
120
+ - React runtime fallback, hydration, islands, or a browser component tree.
121
+ - Public Kudzu-specific router, query, store, form, or scheduler APIs before migration evidence.
122
+ - One core pass per ecosystem package.
123
+ - A generic rerender engine or general callback registry.
124
+ - Incomplete dynamic import support.
125
+ - Speculative cache, virtualization, portal, stream, or resource runtimes.
126
+ - File splitting that does not reduce semantic coupling.
127
+
128
+ ## Priority Program
129
+
130
+ ### Execution Status
131
+
132
+ - [x] P0.1 Source-local binding index is complete in `0.8.28`. Reactive binding capture/import discovery and lowering use the index only when the complete expression is indexed; synthesized keyed expressions retain the existing fail-safe path. Focused scope tests, a 1,000-reference guard, browser integration, all 185 tests, and packed-package smoke pass.
133
+ - [ ] P0.2 Symbol-aware descriptor discovery is next. It must migrate native handler, effect, and remaining binding discovery without broadening into cross-module symbols, semantic StateWrite IR, or ecosystem features.
134
+
135
+ ### P0: Semantic Correctness And Compiler Foundation
136
+
137
+ P0 creates the semantic base required by every later large-application capability. Do not begin broad ecosystem or application features before the relevant P0 dependencies are complete.
138
+
139
+ | Order | Objective | Dependency | Completion evidence |
140
+ |---|---|---|---|
141
+ | P0.1 | Source-local binding index | None | Local, parameter, import, capture, global, and unresolved references are classified consistently |
142
+ | P0.2 | Descriptor consumers use SymbolRef | P0.1 | Handler/effect/binding capture and import discovery no longer depends only on text names |
143
+ | P0.3 | Graph diagnostics | P0.1 | Relative dynamic imports and unresolved runtime edges fail at the source location |
144
+ | P0.4 | Async native handler ownership | None | Pending handlers cannot write after route or DOM ownership is released |
145
+ | P0.5 | Atomic collision-safe build | None | Failed builds preserve prior output and public files cannot overwrite generated artifacts |
146
+ | P0.6 | Explicit ProjectSession | P0.1 | Root and caches are build-scoped; two projects can compile safely in one process |
147
+ | P0.7 | Parsed module/export cache | P0.6 | Shared modules are parsed and summarized once per build |
148
+ | P0.8 | Stable ModuleSymbol and SiteId | P0.6-P0.7 | Imports, re-exports, aliases, owners, and call sites use stable identities outside a pass |
149
+ | P0.9 | Semantic state-operation analysis | P0.1, P0.8 | Equivalent setter/helper forms lower to the same state operation |
150
+ | P0.10 | ModuleIR reference unification | P0.8-P0.9 | Signals, bindings, effects, keyed blocks, handlers, and owners use validated slots/symbols |
151
+ | P0.11 | Explicit route artifact graph | P0.10 | Handler/Worker/CSS/chunk retention uses structural references, not serialized string searches |
152
+ | P0.12 | Deep RouteIR and CapabilityIR validation | P0.10-P0.11 | Invalid state, effect, binding, list, ownership, and artifact references fail before codegen |
153
+
154
+ ### P1: Large Application Foundations
155
+
156
+ | Objective | Required result |
157
+ |---|---|
158
+ | Cross-module component and prop dataflow | Deep composition, callbacks, setters, refs, context, and children retain semantic links without repeated AST inlining |
159
+ | Property-level derived dependencies | Object state can expose path-level dependencies without splitting application state into artificial primitives |
160
+ | Package-neutral shared state and actions | Existing Zustand support lowers through generic signal/action records; Redux-shaped research has a reusable target |
161
+ | Resource ownership | WebSocket, SSE, subscriptions, timers, observers, Workers, and imperative SDKs can share acquire/owner/dependency/cleanup semantics |
162
+ | Browser-only package modules | Native JS packages may be bundled only into effect/handler/resource chunks without build-time execution |
163
+ | Range ownership | Conditional, keyed, overlay, and future virtualized DOM ranges share release and identity primitives where semantics match |
164
+ | Route/layout graph | Nested persistent layouts and route lifetimes remain complete-document based and recoverable through native navigation |
165
+ | Route capability and chunk closure | Every route reports exact runtime, handler, package, Worker, and shared chunk edges |
166
+ | Route/layout CSS closure | Unrelated feature CSS is absent from a route unless configured global |
167
+ | Incremental development build | A source change recompiles and rerenders only affected modules/routes while preserving full reload correctness |
168
+ | Source-scale benchmark | At least 500 reachable modules, 50,000 TS/TSX lines, 50 routes, phase timings, and peak RSS |
169
+
170
+ ### P2: Compatibility And Migration
171
+
172
+ - Add an internal compatibility adapter registry.
173
+ - Consolidate React compatibility ownership behind one adapter contract.
174
+ - Register React Router ownership without changing native navigation semantics.
175
+ - Move Zustand-specific logic out of generic core, handler, and state paths onto package-neutral shared-state/action IR.
176
+ - Characterize Redux/RTK, TanStack Query, React Hook Form, Zod, Radix/Headless UI/MUI, CSS-in-JS, animation, chart, drag/drop, virtualization, auth, REST, and GraphQL against real application fixtures.
177
+ - Prefer native packages and Web APIs, then compilation, deterministic migration, adapters, partial support, or explicit unsupported status in that order.
178
+ - Publish no public adapter API until an external adapter requires independent versioning.
179
+
180
+ ### P3: AI Compiler Interface
181
+
182
+ - `kudzu check --json` with stable code, stage, file/range, symbols, module/component/ownership paths, compatibility status, suggestions, and safe fixes.
183
+ - `kudzu explain [route|symbol] --json` from the semantic and artifact graphs.
184
+ - `kudzu fix` only for deterministic changes whose preconditions the compiler proves.
185
+ - `kudzu migrate --analyze` for module, component, route, package, state, effect, resource, and blocker inventory.
186
+ - Migration readiness percentages must be calculated from actual classified modules/use sites, never estimated.
187
+
188
+ ### P4: Production And Scale Validation
189
+
190
+ - Maintain Tier 1 reduced fixtures, Tier 2 complete small/medium apps, Tier 3 large production slices, and Tier 4 whole existing React applications.
191
+ - Require Chrome, Firefox, and WebKit journeys for production candidates.
192
+ - Add source maps, accessibility automation, keyboard journeys, security checks, output manifests, long-navigation heap tests, and deployment/cache guidance.
193
+ - Compare Kudzu and React + Vite first with the same agent, model, tools, task, and acceptance checks; expand only after the protocol is stable.
194
+ - Report median and range for tokens, cost, file reads, tool calls, builds, failures, completion time, source retention, output bytes, and accepted behavior.
195
+
196
+ ## PR Execution Queue
197
+
198
+ ### PR 1: Source-Local Binding Index
199
+
200
+ **Objective:** establish one correct reference classifier before further compiler generalization.
201
+
202
+ **Current problem:** `descriptor-session.mjs`, migration passes, and source analysis use identifier text plus separate shadow walkers. A local named like a browser global or import can be classified incorrectly, and later cross-module work has no stable foundation.
203
+
204
+ **Files:**
205
+
206
+ - Add `framework/compiler/analysis/binding-index.mjs`.
207
+ - Add `test/binding-index.test.mjs`.
208
+ - Update `framework/compiler/ast-helpers.mjs` only for shared scope primitives proven necessary.
209
+ - Update a narrow capture/import consumer in `framework/compiler/descriptor-session.mjs`.
210
+
211
+ **Internal contract:**
212
+
213
+ ```text
214
+ resolveReference(identifier)
215
+ -> local declaration
216
+ -> parameter
217
+ -> import
218
+ -> outer lexical capture
219
+ -> known global
220
+ -> unresolved
221
+ ```
222
+
223
+ Every result carries a stable source-local binding slot, debug name, declaration range when present, and reference range. AST nodes remain source-session-local and do not enter JSON-safe IR.
224
+
225
+ **Not in scope:**
226
+
227
+ - Cross-module TypeScript Program.
228
+ - Component graph changes.
229
+ - New accepted React syntax.
230
+ - Handler operation IR.
231
+ - Public API or runtime changes.
232
+
233
+ **Tests:**
234
+
235
+ - Shadowed `document`, `location`, `history`, `navigator`, and `console`.
236
+ - Imported name shadowed by callback parameter or local declaration.
237
+ - Outer component capture versus callback-local binding.
238
+ - State/setter name collision across owners.
239
+ - Existing Router, React, binding, native handler, and effect fixtures.
240
+
241
+ **Performance check:** create a focused synthetic source with at least 1,000 references and record index construction and lookup time. The purpose is a regression guard, not an optimization claim.
242
+
243
+ **Done condition:** capture/import classification uses the binding index for the selected consumer, all existing tests pass, representative generated artifacts remain byte-identical except for an explicitly corrected shadowing case, and no browser bytes are added.
244
+
245
+ ### PR 2: Symbol-Aware Descriptor Discovery
246
+
247
+ **Objective:** move native handler, effect, and binding capture/import discovery onto the P0.1 binding index.
248
+
249
+ **Files:** `descriptor-session.mjs`, `handler-lowering.mjs`, `effect-analysis.mjs`, compiler tests.
250
+
251
+ **Tests:** shadowed imports/globals in native handlers, effects, bindings, and list evaluators; JSON-safe HandlerIR/BindingIR round trips.
252
+
253
+ **Done condition:** these analyses no longer decide identity from identifier text alone; output and diagnostics remain equivalent for existing accepted source.
254
+
255
+ ### PR 3: Graph Failure Diagnostics
256
+
257
+ **Objective:** fail early for runtime graph edges the compiler cannot emit safely.
258
+
259
+ **Files:** `source-graph.mjs`, `source-compiler.mjs`; add page/helper/re-export invalid fixtures.
260
+
261
+ **Done condition:** unresolved relative imports/re-exports and ordinary dynamic imports report importer, source range, and specifier; no generated `.kudzu` path is the primary diagnostic.
262
+
263
+ ### PR 4: Async Native Handler Invalidation
264
+
265
+ **Objective:** prevent late async event work from mutating released route/keyed ownership.
266
+
267
+ **Files:** `native-runtime.js`, navigation/list ownership integration, browser test fixture.
268
+
269
+ **Performance check:** compare synchronous event dispatch before and after; no material regression.
270
+
271
+ **Done condition:** a handler resolving after enhanced navigation or row removal cannot recreate or mutate released state, refs, or DOM.
272
+
273
+ ### PR 5: Atomic And Collision-Safe Output
274
+
275
+ **Objective:** make production builds safe before scaling the build graph.
276
+
277
+ **Files:** `build.mjs`, build integration tests.
278
+
279
+ **Tests:** public collisions with route HTML, core runtime, handler entry, chunk, Worker namespace, source CSS, and configured CSS; failed build preserves prior `dist`.
280
+
281
+ **Done condition:** output is staged, validated, and atomically promoted; public content cannot silently replace generated artifacts.
282
+
283
+ ### PR 6: ProjectSession And Explicit Root
284
+
285
+ **Objective:** remove import-time project globals and establish build-scoped graph/cache ownership.
286
+
287
+ **Files:** add `compiler/project-session.mjs`; update build, source graph, source compiler, and tests.
288
+
289
+ **Done condition:** two independent roots compile in one process, current CLI behavior and artifacts remain unchanged, and source caches cannot leak between projects.
290
+
291
+ ### PR 7: Parsed Module And Export Summary Cache
292
+
293
+ **Objective:** make work proportional to unique modules instead of importer edges.
294
+
295
+ **Dependency:** PR 6.
296
+
297
+ **Test fixture:** at least 100 importers sharing component/helper modules; expose parse/summary counters in tests without production logging.
298
+
299
+ **Done condition:** each unchanged module is parsed and summarized once per project session; transformed mutable AST is not shared across transformer contexts.
300
+
301
+ ### PR 8: Stable ModuleSymbol And SiteId
302
+
303
+ **Objective:** represent declarations, imports, re-exports, component calls, hooks, and ownership sites independently of transformed AST identity.
304
+
305
+ **Tests:** default/named exports, aliases, barrel chains, `export *`, cycles, and deterministic IDs after repeated compilation.
306
+
307
+ **Done condition:** cross-module semantic consumers use ModuleSymbol records and source-local SiteId values; readable names remain diagnostic metadata.
308
+
309
+ ### PR 9: Semantic State Operations
310
+
311
+ **Objective:** lower semantically equivalent state updates to the existing structured HandlerIR path.
312
+
313
+ **Required source equivalence:**
314
+
315
+ ```tsx
316
+ setCount(count + 1)
317
+ const next = count + 1; setCount(next)
318
+ const increment = () => setCount(count + 1); increment()
319
+ function increment(value) { setCount(value + 1) }; increment(count)
320
+ ```
321
+
322
+ **Internal scope:** extend existing command/value representation only for proven pure state operations. Keep arbitrary safe browser handlers as generated ESM; do not invent a general JavaScript IR.
323
+
324
+ **Done condition:** the equivalent forms produce the same StateWrite semantics, browser result, ownership references, and zero-unused-runtime behavior; recursion, escape, mutation, and dynamic dispatch fail with explicit diagnostics.
325
+
326
+ ### PR 10: ModuleIR Reference Unification
327
+
328
+ **Objective:** replace mixed state names, export strings, formatted owner strings, and slots with validated source-local slots and SymbolRef records.
329
+
330
+ **Tests:** malformed references, duplicate exports, parent/child reciprocity, cycles, version rejection, and JSON round trips.
331
+
332
+ **Done condition:** every HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and component specialization edge validates before build-module generation.
333
+
334
+ ### PR 11: RouteBuildRecord And Artifact Graph
335
+
336
+ **Objective:** make route artifact selection structural.
337
+
338
+ **Files:** add a focused route-artifact module; update `build.mjs` and capability planning.
339
+
340
+ **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.
341
+
342
+ ### PR 12: Deep RouteIR And CapabilityIR Validation
343
+
344
+ **Objective:** fail before codegen for invalid concrete route references and capability projections.
345
+
346
+ **Done condition:** duplicate/missing state IDs, invalid event/effect dependencies, broken list ownership, invalid binding descriptors, and artifact mismatches have focused tests and deterministic diagnostics.
347
+
348
+ ## Large Application Capability Order
349
+
350
+ After the relevant P0 foundations, investigate capabilities in this order:
351
+
352
+ 1. Property-level derived dependencies over ordinary object state.
353
+ 2. Multi-boundary component/prop/callback/ref/context dataflow.
354
+ 3. Package-neutral shared state/actions and migration of current Zustand internals.
355
+ 4. Browser-only package imports in owned effect/resource modules.
356
+ 5. ResourceIR from at least two independent WebSocket/SSE/SDK fixtures with the same semantics.
357
+ 6. Route/layout capability and CSS chunk closure.
358
+ 7. Incremental source and affected-route builds.
359
+ 8. Range ownership and virtualization only after a real data-heavy fixture establishes direct DOM limits.
360
+ 9. Optimistic shared transactions only after independent mutation fixtures establish commit/rollback semantics.
361
+
362
+ ## Compatibility Strategy
363
+
364
+ Classify every package or category as one of:
365
+
366
+ - Native: ordinary JS/TS/Web package; use it directly in an allowed build or browser module.
367
+ - Compiled: package-shaped source lowers to package-neutral semantics and the import is erased.
368
+ - Migrated: deterministic source transformation replaces a React runtime implementation with native/Kudzu source.
369
+ - Adapter: repeated package semantics justify an internal compatibility adapter.
370
+ - Partial: only an explicit API subset is supported.
371
+ - Unsupported: safe transformation is not proven; report the reason and migration path.
372
+
373
+ Current factual baseline:
374
+
375
+ | Ecosystem | Current classification |
376
+ |---|---|
377
+ | React source | Compiled, partial |
378
+ | React Router | Compiled, partial |
379
+ | Zustand | Compiled, partial, insufficiently isolated |
380
+ | Redux/RTK | Unsupported |
381
+ | TanStack Query | Migrated recipe, partial |
382
+ | React Hook Form | Migrated recipe, partial |
383
+ | Zod | Unsupported in ordinary shared schema placement; native-package candidate after module work |
384
+ | Radix dialog | Migrated to native dialog, partial |
385
+ | Headless UI/MUI | Unsupported |
386
+ | Plain CSS | Native |
387
+ | CSS Modules | Compiled, partial |
388
+ | Tailwind | Application-owned CSS adapter boundary |
389
+ | CSS-in-JS | Unsupported |
390
+ | Native SVG/canvas charts | Native/compiled |
391
+ | React chart, animation, drag/drop, virtualization packages | Unsupported or migration candidates |
392
+ | REST `fetch` | Native/compiled, partial async model |
393
+ | GraphQL/auth/browser SDKs | Unsupported until browser package/resource ownership exists |
394
+
395
+ ## Application Corpus
396
+
397
+ | Tier | Required evidence |
398
+ |---|---|
399
+ | Tier 1 | Existing reduced positive/negative fixtures with exact semantic boundaries |
400
+ | Tier 2 | Complete small/medium React applications such as CRUD, forms, commerce, admin, chat, and calendar |
401
+ | Tier 3 | Production slices with 100-500 modules, shared state, routing, packages, forms, server state, charts, auth, and resources |
402
+ | Tier 4 | At least two whole existing React applications, including one stateful/realtime app, with 500+ modules and 100,000+ reachable lines where available |
403
+
404
+ Every corpus entry records upstream URL/commit/license, acquisition hash, original source, migration patch, retained-source percentage, package classification, unsupported census, route/module/component counts, browser/a11y journeys, artifact bytes, build/rebuild/RSS metrics, and AI task traces.
405
+
406
+ ## AI Cost And Tooling Gates
407
+
408
+ Architecture work must connect to measurable AI cost reduction:
409
+
410
+ | Compiler capability | Expected cost reduction |
411
+ |---|---|
412
+ | Symbol graph | Fewer file searches, alias mistakes, and capture debugging cycles |
413
+ | Semantic operations | Fewer rewrites into exact compiler-recognized syntax |
414
+ | Component dataflow | Less component flattening and callback forwarding surgery |
415
+ | Property dependencies | Less artificial primitive-state decomposition |
416
+ | Compatibility registry | Less Kudzu-specific package knowledge and documentation lookup |
417
+ | Structured diagnostics | Fewer log-parsing and source-location tool calls |
418
+ | Explain graph | Fewer files read to understand a route or mutation |
419
+ | Deterministic fixes | Fewer repetitive source edits and retries |
420
+ | Incremental build | Lower feedback time per compiler attempt |
421
+ | Artifact graph | Faster runtime/chunk/debugging attribution |
422
+
423
+ The first comparison is Kudzu versus React + Vite using the same agent, model, tools, prompt, task, and acceptance suite. Record at least five attempts per task and report medians and ranges. A build-only result is not success.
424
+
425
+ ## Production Gates Before 1.0
426
+
427
+ - Async native and effect work cannot write after ownership release.
428
+ - Build output is atomic and collision-safe.
429
+ - Source maps connect generated route code to TS/TSX diagnostics.
430
+ - Chrome, Firefox, and WebKit pass required journeys.
431
+ - Accessibility automation and keyboard checks cover forms, navigation, dialogs, menus, charts, and errors.
432
+ - Route, module, package, CSS, Worker, and chunk closures are inspectable.
433
+ - Clean build, one-file rebuild, peak RSS, long-navigation heap, and mixed keyed ownership benchmarks have maintained baselines.
434
+ - Package compatibility status and version guarantees are public and machine-readable.
435
+ - Deployment cache, CSP, authentication, private-data, upload, and error-handling guidance is explicit.
436
+ - At least one complete content application and one complete stateful/realtime application pass production gates.
437
+ - AI benchmark results report cost per successful task and context surface area per successful task.
438
+
439
+ ## Per-PR Required Evidence
440
+
441
+ - [ ] State the exact producer and consumer boundary changed.
442
+ - [ ] Name the real fixture or correctness failure authorizing the patch.
443
+ - [ ] Add a focused check that fails before the implementation.
444
+ - [ ] Preserve unrelated source support and diagnostics.
445
+ - [ ] Compare representative ModuleIR, RouteIR, HTML, emitted paths, and bytes.
446
+ - [ ] Prove static sibling routes remain zero JavaScript.
447
+ - [ ] Prove identity, cleanup, cancellation, and late-completion behavior where ownership changes.
448
+ - [ ] Record build/runtime measurements for nontrivial paths.
449
+ - [ ] Run `npm run check`, `npm test`, and package smoke when package output changes.
450
+ - [ ] Update this plan before combining or reordering scopes.
451
+ - [ ] Update release notes only after the implementation is complete and published.
452
+
453
+ ## Immediate Decision
454
+
455
+ The first implementation PR is **PR 1: Source-Local Binding Index**. It is compile-time only, adds no browser bytes or public API, fixes an existing correctness class, and creates the stable semantic foundation required by component graphs, state operations, cross-module analysis, compatibility adapters, structured diagnostics, and AI explanation tooling.
456
+
457
+ The next PR is **PR 2: Symbol-Aware Descriptor Discovery**. 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.26` are completed scopes represented by package/release records.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.28` 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
 
@@ -17,6 +17,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
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
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
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. |
20
+ | `0.8.27` | Characterize the first Goal C browser-resource boundary and publish the ordered large-application and AI-native compiler execution plan. | The E2B-derived fixture fails with a source-located diagnostic, no resource runtime or API is added, and the next PR dependencies and completion evidence are explicit. |
21
+ | `0.8.28` | Add the source-local binding index and adopt it for reactive binding capture/import discovery and lowering. | Lexical shadowing is identity-based for complete indexed bindings, synthesized keyed expressions retain their fallback, the 1,000-reference guard passes, and no browser bytes or public API are added. |
20
22
 
21
23
  ## Sequence Rules
22
24
 
@@ -0,0 +1,273 @@
1
+ import ts from "typescript"
2
+
3
+ export const knownGlobalNames = new Set([
4
+ "Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "Infinity", "IntersectionObserver", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "cancelAnimationFrame", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "performance", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
5
+ ])
6
+
7
+ export function createBindingIndex(sourceFile) {
8
+ const scopeByNode = new WeakMap()
9
+ const referenceBindings = new WeakMap()
10
+ const originalReferenceBindings = new WeakMap()
11
+ const bindings = []
12
+ const rootScope = createScope(undefined, sourceFile, "module")
13
+ const pseudoBindings = new Map()
14
+
15
+ collect(sourceFile, rootScope)
16
+ resolveReferences(sourceFile)
17
+
18
+ function createScope(parent, node, kind) {
19
+ return { parent, node, kind, bindings: new Map() }
20
+ }
21
+
22
+ function mark(node, scope) {
23
+ scopeByNode.set(node, scope)
24
+ }
25
+
26
+ function declare(identifier, scope, declarationKind) {
27
+ mark(identifier, scope)
28
+ let binding = scope.bindings.get(identifier.text)
29
+ if (!binding) {
30
+ binding = { slot: bindings.length, debugName: identifier.text, declarationKind, declaration: identifier }
31
+ bindings.push(binding)
32
+ scope.bindings.set(identifier.text, binding)
33
+ }
34
+ return binding
35
+ }
36
+
37
+ function declareName(name, scope, declarationKind) {
38
+ mark(name, scope)
39
+ if (ts.isIdentifier(name)) {
40
+ declare(name, scope, declarationKind)
41
+ return
42
+ }
43
+ for (const element of name.elements) if (ts.isBindingElement(element)) declareName(element.name, scope, declarationKind)
44
+ }
45
+
46
+ function collectBindingExpressions(name, scope) {
47
+ if (ts.isIdentifier(name)) return
48
+ for (const element of name.elements) if (ts.isBindingElement(element)) {
49
+ if (element.propertyName && ts.isComputedPropertyName(element.propertyName)) collect(element.propertyName.expression, scope)
50
+ if (element.initializer) collect(element.initializer, scope)
51
+ collectBindingExpressions(element.name, scope)
52
+ }
53
+ }
54
+
55
+ function nearestVarScope(scope) {
56
+ for (let current = scope; current; current = current.parent) if (["function", "module"].includes(current.kind)) return current
57
+ return rootScope
58
+ }
59
+
60
+ function collectFunction(node, scope) {
61
+ if (ts.isFunctionDeclaration(node) && node.name) declare(node.name, scope, "function")
62
+ const parameterScope = createScope(scope, node, "parameters")
63
+ const functionScope = createScope(parameterScope, node.body ?? node, "function")
64
+ mark(node, parameterScope)
65
+ if (ts.isFunctionExpression(node) && node.name) declare(node.name, parameterScope, "function-name")
66
+ for (const parameter of node.parameters) {
67
+ mark(parameter, parameterScope)
68
+ declareName(parameter.name, parameterScope, "parameter")
69
+ collectBindingExpressions(parameter.name, parameterScope)
70
+ if (parameter.initializer) collect(parameter.initializer, parameterScope)
71
+ }
72
+ if (node.body) collect(node.body, functionScope)
73
+ }
74
+
75
+ function collectClass(node, scope) {
76
+ if (ts.isClassDeclaration(node) && node.name) declare(node.name, scope, "class")
77
+ const classScope = createScope(scope, node, "class")
78
+ mark(node, classScope)
79
+ if (ts.isClassExpression(node) && node.name) declare(node.name, classScope, "class-name")
80
+ for (const heritage of node.heritageClauses ?? []) for (const type of heritage.types) collect(type.expression, classScope)
81
+ for (const member of node.members) collect(member, classScope)
82
+ }
83
+
84
+ function collectLoop(node, scope) {
85
+ const loopScope = createScope(scope, node, "block")
86
+ mark(node, loopScope)
87
+ if (node.initializer) collect(node.initializer, loopScope)
88
+ if (ts.isForStatement(node)) {
89
+ if (node.condition) collect(node.condition, loopScope)
90
+ if (node.incrementor) collect(node.incrementor, loopScope)
91
+ } else collect(node.expression, loopScope)
92
+ collect(node.statement, loopScope)
93
+ }
94
+
95
+ function collect(node, scope) {
96
+ mark(node, scope)
97
+ if (ts.isTypeNode(node)) return
98
+ if (ts.isSourceFile(node)) {
99
+ for (const statement of node.statements) collect(statement, scope)
100
+ return
101
+ }
102
+ if (ts.isImportDeclaration(node)) {
103
+ const clause = node.importClause
104
+ if (!clause || clause.isTypeOnly) return
105
+ if (clause.name) declare(clause.name, scope, "import-default")
106
+ if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) declare(clause.namedBindings.name, scope, "import-namespace")
107
+ if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) for (const element of clause.namedBindings.elements) if (!element.isTypeOnly) declare(element.name, scope, "import-named")
108
+ return
109
+ }
110
+ if (ts.isImportEqualsDeclaration(node)) {
111
+ if (!node.isTypeOnly) declare(node.name, scope, "import-equals")
112
+ return
113
+ }
114
+ if (isFunctionLike(node)) {
115
+ if (node.name && ts.isComputedPropertyName(node.name)) collect(node.name.expression, scope)
116
+ collectFunction(node, scope)
117
+ return
118
+ }
119
+ if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
120
+ collectClass(node, scope)
121
+ return
122
+ }
123
+ if (ts.isBlock(node)) {
124
+ const blockScope = createScope(scope, node, "block")
125
+ mark(node, blockScope)
126
+ for (const statement of node.statements) collect(statement, blockScope)
127
+ return
128
+ }
129
+ if (ts.isCaseBlock(node)) {
130
+ const caseScope = createScope(scope, node, "block")
131
+ mark(node, caseScope)
132
+ for (const clause of node.clauses) collect(clause, caseScope)
133
+ return
134
+ }
135
+ if (ts.isCatchClause(node)) {
136
+ const catchScope = createScope(scope, node, "block")
137
+ mark(node, catchScope)
138
+ if (node.variableDeclaration) {
139
+ declareName(node.variableDeclaration.name, catchScope, "catch")
140
+ collectBindingExpressions(node.variableDeclaration.name, catchScope)
141
+ }
142
+ collect(node.block, catchScope)
143
+ return
144
+ }
145
+ if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) {
146
+ collectLoop(node, scope)
147
+ return
148
+ }
149
+ if (ts.isVariableDeclaration(node)) {
150
+ const list = node.parent
151
+ const declarationKind = ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.Const) ? "const" : ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.Let) ? "let" : "var"
152
+ const declarationScope = declarationKind === "var" ? nearestVarScope(scope) : scope
153
+ declareName(node.name, declarationScope, declarationKind)
154
+ collectBindingExpressions(node.name, scope)
155
+ if (node.initializer) collect(node.initializer, scope)
156
+ return
157
+ }
158
+ if (ts.isEnumDeclaration(node)) declare(node.name, scope, "enum")
159
+ if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) {
160
+ declare(node.name, scope, "namespace")
161
+ const moduleScope = createScope(scope, node, "module")
162
+ if (node.body && ts.isModuleBlock(node.body)) {
163
+ mark(node.body, moduleScope)
164
+ for (const statement of node.body.statements) collect(statement, moduleScope)
165
+ } else if (node.body) collect(node.body, moduleScope)
166
+ return
167
+ }
168
+ ts.forEachChild(node, child => collect(child, scope))
169
+ }
170
+
171
+ function lexicalBinding(scope, name) {
172
+ for (let current = scope; current; current = current.parent) {
173
+ const binding = current.bindings.get(name)
174
+ if (binding) return binding
175
+ }
176
+ }
177
+
178
+ function pseudoBinding(kind, name) {
179
+ const key = `${kind}:${name}`
180
+ let binding = pseudoBindings.get(key)
181
+ if (!binding) {
182
+ binding = { slot: bindings.length, debugName: name, declarationKind: kind }
183
+ bindings.push(binding)
184
+ pseudoBindings.set(key, binding)
185
+ }
186
+ return binding
187
+ }
188
+
189
+ function resolveReferences(node) {
190
+ if (ts.isTypeNode(node)) return
191
+ if (ts.isIdentifier(node) && isValueReference(node)) {
192
+ const lexical = lexicalBinding(scopeByNode.get(node) ?? rootScope, node.text)
193
+ const binding = lexical ?? pseudoBinding(knownGlobalNames.has(node.text) ? "global" : "unresolved", node.text)
194
+ referenceBindings.set(node, binding)
195
+ const original = ts.getOriginalNode(node)
196
+ if (original !== node) originalReferenceBindings.set(original, binding)
197
+ }
198
+ ts.forEachChild(node, resolveReferences)
199
+ }
200
+
201
+ function resolveReference(identifier, boundary = sourceFile) {
202
+ const original = ts.getOriginalNode(identifier)
203
+ const binding = referenceBindings.get(identifier) ?? referenceBindings.get(original) ?? originalReferenceBindings.get(original)
204
+ if (!binding) return undefined
205
+ let kind
206
+ if (binding.declarationKind.startsWith("import")) kind = "import"
207
+ else if (binding.declarationKind === "global") kind = "global"
208
+ else if (binding.declarationKind === "unresolved") kind = "unresolved"
209
+ else if (inside(binding.declaration, ts.getOriginalNode(boundary))) kind = binding.declarationKind === "parameter" ? "parameter" : "local"
210
+ else kind = "capture"
211
+ return {
212
+ kind,
213
+ slot: binding.slot,
214
+ debugName: binding.debugName,
215
+ declarationKind: binding.declarationKind,
216
+ ...(binding.declaration ? { declaration: binding.declaration, declarationRange: range(binding.declaration) } : {}),
217
+ referenceRange: range(original)
218
+ }
219
+ }
220
+
221
+ function references(root, boundary = root) {
222
+ const found = []
223
+ let complete = true
224
+ const visit = node => {
225
+ if (ts.isTypeNode(node)) return
226
+ if (ts.isIdentifier(node) && isValueReference(node)) {
227
+ const resolution = resolveReference(node, boundary)
228
+ if (resolution) found.push(resolution)
229
+ else complete = false
230
+ }
231
+ ts.forEachChild(node, visit)
232
+ }
233
+ visit(root)
234
+ return complete ? found : undefined
235
+ }
236
+
237
+ return { bindings: () => bindings.map(({ declaration, ...binding }) => ({ ...binding, ...(declaration ? { declarationRange: range(declaration) } : {}) })), references, resolveReference }
238
+ }
239
+
240
+ function inside(node, boundary) {
241
+ for (let current = node; current; current = current.parent) if (current === boundary) return true
242
+ return false
243
+ }
244
+
245
+ function range(node) {
246
+ return node.pos >= 0 && node.end >= 0 ? { start: node.getStart(), end: node.end } : undefined
247
+ }
248
+
249
+ function isFunctionLike(node) {
250
+ return ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node) || ts.isConstructorDeclaration(node)
251
+ }
252
+
253
+ function isValueReference(node) {
254
+ const parent = node.parent
255
+ if (!parent) return true
256
+ if (ts.isTypeNode(parent)) return false
257
+ if ((ts.isPropertyAccessExpression(parent) && parent.name === node)
258
+ || (ts.isPropertyAssignment(parent) && parent.name === node)
259
+ || ((ts.isMethodDeclaration(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isPropertySignature(parent) || ts.isMethodSignature(parent)) && parent.name === node)
260
+ || (ts.isVariableDeclaration(parent) && parent.name === node)
261
+ || (ts.isParameter(parent) && parent.name === node)
262
+ || ((ts.isFunctionDeclaration(parent) || ts.isFunctionExpression(parent) || ts.isClassDeclaration(parent) || ts.isClassExpression(parent) || ts.isEnumDeclaration(parent) || ts.isModuleDeclaration(parent)) && parent.name === node)
263
+ || (ts.isJsxAttribute(parent) && parent.name === node)
264
+ || (ts.isBindingElement(parent) && (parent.name === node || parent.propertyName === node))
265
+ || ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent) || ts.isImportEqualsDeclaration(parent)
266
+ || ts.isExportSpecifier(parent)
267
+ || ts.isLabeledStatement(parent) || ts.isBreakStatement(parent) || ts.isContinueStatement(parent)
268
+ || (ts.isEnumMember(parent) && parent.name === node)) return false
269
+ if ((ts.isTypeAliasDeclaration(parent) || ts.isInterfaceDeclaration(parent)) && parent.name === node) return false
270
+ if (ts.isJsxClosingElement(parent) && parent.tagName === node) return false
271
+ if ((ts.isJsxOpeningElement(parent) || ts.isJsxSelfClosingElement(parent)) && parent.tagName === node) return node.text[0] === node.text[0].toUpperCase()
272
+ return true
273
+ }
@@ -1,5 +1,6 @@
1
1
  import ts from "typescript"
2
2
  import { createComponentAnalysis } from "./analysis/component-analysis.mjs"
3
+ import { knownGlobalNames } from "./analysis/binding-index.mjs"
3
4
  import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
4
5
  import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
5
6
  import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
@@ -8,7 +9,7 @@ export function createSemanticArtifact(file) {
8
9
  return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
9
10
  }
10
11
 
11
- export function createDescriptorSession({ semantic, handlerUrl, factory, context, compileEventCommand, handlerLowering, isPrimitiveLiteral, rejectWorkerConstructions, sourceName = source => source.fileName }) {
12
+ export function createDescriptorSession({ semantic, handlerUrl, factory, context, bindingIndex, compileEventCommand, handlerLowering, isPrimitiveLiteral, rejectWorkerConstructions, sourceName = source => source.fileName }) {
12
13
  const { moduleIR } = semantic
13
14
  const nativeHandlers = []
14
15
  const effectHandlers = []
@@ -66,12 +67,16 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
66
67
 
67
68
  function compileReactiveExpression(expression, setters, importBindings = new Map(), keyedBlock) {
68
69
  const usedStates = referencedStateNames(expression, setters)
69
- const importedNames = referencedImportedBindings(expression, importBindings)
70
+ const indexed = bindingIndex?.references(expression, expression)
71
+ const importedNames = indexed ? new Set(indexed.filter(reference => reference.kind === "import" && importBindings.has(reference.debugName)).map(reference => reference.debugName)) : referencedImportedBindings(expression, importBindings)
70
72
  const imports = [...importedNames].map(name => importBindings.get(name))
71
73
  registerClientImports(imports)
72
- const captures = new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
74
+ const allStateNames = new Set(setters.values())
75
+ const captures = indexed
76
+ ? new Set(indexed.filter(reference => ["capture", "unresolved"].includes(reference.kind) && !setters.has(reference.debugName) && !allStateNames.has(reference.debugName) && !importedNames.has(reference.debugName)).map(reference => reference.debugName))
77
+ : new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
73
78
  const exportName = `binding${reactiveBindings.length}`
74
- reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports, role: "binding", keyedBlock })
79
+ reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports, role: "binding", keyedBlock, ...(indexed ? { bindingIndex } : {}) })
75
80
  const states = [...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
76
81
  const scope = [...captures].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
77
82
  const stateNames = new Set(usedStates)
@@ -303,14 +308,10 @@ export function captureNames(declarationRoot, referenceRoot, setters) {
303
308
  if (ts.isTypeNode(node)) return
304
309
  if (ts.isIdentifier(node)) {
305
310
  const declared = isFunctionLike(declarationRoot) ? isShadowedIdentifier(node, declarationRoot) : local.has(node.text)
306
- if (isReferenceIdentifier(node) && !declared && !setters.has(node.text) && !stateNames.has(node.text) && !nativeGlobals.has(node.text)) captures.add(node.text)
311
+ if (isReferenceIdentifier(node) && !declared && !setters.has(node.text) && !stateNames.has(node.text) && !knownGlobalNames.has(node.text)) captures.add(node.text)
307
312
  }
308
313
  ts.forEachChild(node, visit)
309
314
  }
310
315
  visit(referenceRoot)
311
316
  return captures
312
317
  }
313
-
314
- const nativeGlobals = new Set([
315
- "Array", "ArrayBuffer", "BigInt", "Blob", "Boolean", "Date", "Error", "Event", "FileReader", "FormData", "Infinity", "IntersectionObserver", "Intl", "JSON", "Map", "Math", "NaN", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "String", "Symbol", "TypeError", "URL", "URLSearchParams", "WeakMap", "WeakSet", "WebSocket", "Worker", "alert", "atob", "btoa", "cancelAnimationFrame", "clearInterval", "clearTimeout", "console", "crypto", "document", "fetch", "globalThis", "history", "isFinite", "isNaN", "localStorage", "location", "navigator", "parseFloat", "parseInt", "performance", "queueMicrotask", "requestAnimationFrame", "setInterval", "setTimeout", "structuredClone", "undefined", "window"
316
- ])
@@ -189,11 +189,15 @@ export function createHandlerLowering({ cloneAst, synthesizeTree }) {
189
189
  return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
190
190
  }
191
191
 
192
- function printReactiveBinding({ exportName, expression, captures, states }) {
192
+ function printReactiveBinding({ exportName, expression, captures, states, bindingIndex }) {
193
193
  const factory = ts.factory
194
194
  const transformer = context => root => {
195
195
  const visitor = node => {
196
- if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
196
+ const reference = ts.isShorthandPropertyAssignment(node) ? node.name : ts.isIdentifier(node) ? node : undefined
197
+ const resolution = reference ? bindingIndex?.resolveReference(reference, expression) : undefined
198
+ const indexedState = resolution?.kind === "capture" && states.has(resolution.debugName)
199
+ const indexedCapture = ["capture", "unresolved"].includes(resolution?.kind) && captures.has(resolution.debugName)
200
+ if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text) && (!bindingIndex || indexedState)) {
197
201
  return factory.createPropertyAssignment(
198
202
  node.name,
199
203
  factory.createCallExpression(
@@ -203,17 +207,17 @@ export function createHandlerLowering({ cloneAst, synthesizeTree }) {
203
207
  )
204
208
  )
205
209
  }
206
- if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
210
+ if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && (bindingIndex ? indexedState : !isShadowedByParameter(node, expression))) {
207
211
  return factory.createCallExpression(
208
212
  factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
209
213
  undefined,
210
214
  [factory.createStringLiteral(node.text)]
211
215
  )
212
216
  }
213
- if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) {
217
+ if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text) && (!bindingIndex || indexedCapture)) {
214
218
  return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
215
219
  }
216
- if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) {
220
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && (!bindingIndex || indexedCapture)) {
217
221
  return scopeRead(factory, node.text)
218
222
  }
219
223
  return ts.visitEachChild(node, visitor, context)
@@ -1,6 +1,7 @@
1
1
  import { readFile, realpath, stat } from "node:fs/promises"
2
2
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path"
3
3
  import ts from "typescript"
4
+ import { createBindingIndex } from "./analysis/binding-index.mjs"
4
5
  import { createComponentAnalysisSession } from "./analysis/component-analysis.mjs"
5
6
  import { normalizeEffectAnimationFrameRefs } from "./animation-frame-pass.mjs"
6
7
  import { bindingNames, containsJsx, effectReturns, functionVarDeclaresName, importDeclarationNames, isFunctionLike, isLocalConst, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, isUnshadowedGlobal, nearestFunction, referenceIdentifiers, referencesIdentifier, sourceLocation, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
@@ -248,6 +249,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
248
249
  const normalized = normalizeCompilerSource(sourceFile, { base, context, file, importedCollections, importedStaticCollections, sourceFiles, sourceIndex })
249
250
  sourceFile = normalized.sourceFile
250
251
  const { customHookTimerStates } = normalized
252
+ const bindingIndex = createBindingIndex(sourceFile)
251
253
  const factory = context.factory
252
254
  const sourceName = source => relative(root, source.fileName).replaceAll(sep, "/")
253
255
  const componentAnalysis = createComponentAnalysisSession(semantic.componentAnalysis)
@@ -256,6 +258,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
256
258
  handlerUrl,
257
259
  factory,
258
260
  context,
261
+ bindingIndex,
259
262
  compileEventCommand,
260
263
  handlerLowering,
261
264
  isPrimitiveLiteral: isPrimitiveDefaultLiteral,
@@ -629,9 +632,13 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
629
632
  }
630
633
  if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression)) {
631
634
  const owner = nearestFunction(node)
632
- if (owner && node.initializer.expression.text === "useRef" && node.initializer.arguments.length === 1 && node.initializer.arguments[0].kind === ts.SyntaxKind.NullKeyword) {
633
- ensureOwner(owner)
634
- componentAnalysis.registerRef(owner, { name: node.name.text, source: analysisSource(node) })
635
+ if (owner && node.initializer.expression.text === "useRef") {
636
+ const nullInitializer = node.initializer.arguments.length === 1 && node.initializer.arguments[0].kind === ts.SyntaxKind.NullKeyword
637
+ if (!nullInitializer && owner.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) throw sourceNodeError(node.initializer, sourceFile, "Mutable value useRef() is unsupported except for an effect-owned useRef(0) animation-frame handle; otherwise keep resource-private mutable values inside the owning effect")
638
+ if (nullInitializer) {
639
+ ensureOwner(owner)
640
+ componentAnalysis.registerRef(owner, { name: node.name.text, source: analysisSource(node) })
641
+ }
635
642
  }
636
643
  if (owner && node.initializer.expression.text === "useId" && node.initializer.arguments.length === 0) {
637
644
  ensureOwner(owner)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.26",
3
+ "version": "0.8.28",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",