@kudzujs/core 0.16.6 → 0.16.8

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.
@@ -51,7 +51,7 @@ Syntax compatibility does not mean reproducing React wholesale. Kudzu accepts th
51
51
  - stronger JavaScript failure resilience;
52
52
  - no remaining material large-route build-scaling weakness.
53
53
 
54
- The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.18.2` in the capability release plan below. Benchmark-only feature omission, unmatched accessibility, weighted scores that hide losses, and unrecorded environment differences do not count as proof.
54
+ The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.19.0` in the capability release plan below. Benchmark-only feature omission, unmatched accessibility, weighted scores that hide losses, and unrecorded environment differences do not count as proof.
55
55
 
56
56
  ### 0.10.0 Through 0.21.x: Application Capability Release Train
57
57
 
@@ -128,7 +128,7 @@ The following are available building blocks, not future vertical roadmaps:
128
128
  - `useState`, independent repeated non-keyed child state with conditional mount ownership, reduced relative-imported `useReducer`, direct dispatch specialization, and reduced Zustand-shaped shared state lowered through package-neutral shared-state/action IR and proven by migration fixtures.
129
129
  - Mount and dependency effects with cleanup, route/layout lifetimes, conditional/keyed ownership, stale-write isolation, and relative TypeScript Workers.
130
130
  - Direct browser-only package references in inline effect setup/cleanup callbacks bundle into route-owned effect ESM without entering build-time component execution or static sibling output.
131
- - A CodeMirror-class editor may retain one package instance across one mount effect and later dependency update effects through a null-initialized ref with one direct setup assignment and null-reset cleanup. The ref lowers to existing owner-scoped object state; package DOM, bidirectional updates, errors, accessibility, conditional disposal, and fresh remount require no widget runtime or React island.
131
+ - A CodeMirror-class editor may retain one package instance across one mount or guarded lazy activation effect and later dependency update effects through a null-initialized ref with one direct setup assignment and null-reset cleanup. The lazy form assigns only inside the literal package import's direct `.then()` callback. The ref lowers to existing owner-scoped object state; package DOM, bidirectional updates, errors, accessibility, conditional disposal, and fresh remount require no loader, widget runtime, or React island.
132
132
  - A Chart.js-class canvas instance uses the same retained-handle contract. One mount effect owns construction, resize listeners, and destroy/reset cleanup; later data effects read the instance without replacing it. Enhanced route disposal and fresh remount require no chart runtime or package-specific IR.
133
133
  - A SortableJS-class drag engine may temporarily move keyed DOM during a gesture, but Kudzu state remains the durable order owner. The package callback restores authored DOM order before one immutable state reorder; existing keyed reconciliation then preserves row identity. Keyboard controls use the same state operation, package disposal is effect-owned, and static siblings exclude the package.
134
134
  - Keyed local-state and imported static collections, pure selectors, nested direct-property lists, recursive and directly exported row specialization, static/keyed component reuse, analyzable specialized prop spreads, serializable literal defaults, direct intrinsic rest forwarding, forwarded JSX children, latest-item handlers, row state/effects/refs, and stable keyed identity.
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, `npm run benchmark:module-cache`, `npm run benchmark:project-navigation`, `npm run benchmark:project-state`, and `npm run benchmark:source-scale` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
4
4
 
5
+ ## 0.16.8 Shared Lazy Capability Graph
6
+
7
+ Measured 2026-08-31 on Linux x64 with Node 24.14.0 and Chrome. Two route owners
8
+ share one deferred CodeMirror chunk measuring 250,086 raw / 80,890 aggregate
9
+ gzip B. Their eager graphs are 21,599 raw / 9,466 gzip B and 21,609 raw / 9,468
10
+ gzip B. The static sibling remains complete HTML with zero JavaScript.
11
+
12
+ Required Chrome proves zero deferred requests before interaction, one request on
13
+ the first owner, no navigation-triggered prefetch, no duplicate request for the
14
+ second owner, and exact owner/document cleanup. Native ESM supplies the
15
+ document-lifetime cache; no loader, prefetch registry, cache, or runtime concept
16
+ was added. The maintained Worker benchmark remains 907 raw / 477 gzip B for the
17
+ Worker graph and 14,456 raw / 6,160 gzip B for the window graph. Seven clean
18
+ builds record a 696.6 ms median on this loaded host; timing is not compared.
19
+
20
+ ## 0.16.7 Lazy Retained Editor Lifecycle
21
+
22
+ Measured 2026-08-30 on macOS arm64 with Node 24.14.0 and Chrome. The existing
23
+ eager real CodeMirror editor owns 214,968 raw / 71,607 aggregate gzip B. The
24
+ lazy retained journey initially owns 15,896 raw / 7,230 gzip B and defers
25
+ 250,086 raw / 80,890 gzip B, reducing initial gzip by 64,377 B (89.9%). Its
26
+ static sibling remains complete HTML with zero JavaScript.
27
+
28
+ Required Chrome proves zero initial deferred requests, one activation request,
29
+ bidirectional updates, accessible update-error recovery, retained editor DOM,
30
+ conditional cleanup, cached fresh remount, and document disposal. The maintained
31
+ Worker benchmark remains 907 raw / 477 gzip B for the Worker graph and 14,456
32
+ raw / 6,160 gzip B for the window graph. Seven clean builds record a 230.5 ms
33
+ median. No browser runtime file or concept was added.
34
+
5
35
  ## 0.16.6 Owner-Triggered Capability Imports
6
36
 
7
37
  Measured 2026-08-29 on macOS arm64 with Node 24.14.0 and Chrome. The existing
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.16.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.16.6 - Owner-triggered capability imports.** One literal package dependency may stay out of the initial route graph until its guarded effect owner activates, while static siblings remain JavaScript-free. Read the [release notes](./RELEASES.md#0166---owner-triggered-capability-imports), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.6), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.16.8 - Shared lazy capability graphs.** Multiple route owners now share one interaction-triggered CodeMirror graph, with lexical ownership validation and no loader, cache, or eager prefetch. Read the [release notes](./RELEASES.md#0168---shared-lazy-capability-graphs), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.8), 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,82 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.8 - Shared Lazy Capability Graphs
4
+
5
+ Kudzu 0.16.8 shares one deferred package graph across route owners while
6
+ keeping loading interaction-triggered and ownership validation lexical.
7
+
8
+ ### Changed in 0.16.8
9
+
10
+ - Records one deferred CodeMirror chunk as shared by both route owners without
11
+ duplicating its package graph.
12
+ - Keeps the shared chunk out of route HTML, module preload, navigation prefetch,
13
+ and static siblings until an owner activates.
14
+ - Uses the native browser module map for document-scoped deduplication and adds
15
+ no loader, prefetch registry, cache, or browser runtime.
16
+ - Resolves lazy `useEffect` ownership through lexical imports so a local
17
+ same-named function cannot bypass ordinary dynamic-import diagnostics.
18
+ - Preserves exact owner cleanup; native in-flight imports remain non-cancellable
19
+ and late callbacks cannot mount after disposal.
20
+ - Updates `create-kudzu@0.1.133` to generate projects on
21
+ `@kudzujs/core@^0.16.8`.
22
+
23
+ ### Output And Browser Evidence
24
+
25
+ - Both owners share one 250,086 raw / 80,890 gzip byte deferred chunk. Their
26
+ eager graphs are 21,599 raw / 9,466 gzip and 21,609 raw / 9,468 gzip bytes.
27
+ - Required Chrome proves zero initial requests, one first-owner request, no
28
+ route-prefetch request, no second-owner duplicate, and exact cleanup.
29
+ - The static sibling emits zero JavaScript. The maintained Worker and window
30
+ graphs remain 907 raw / 477 gzip bytes and 14,456 raw / 6,160 gzip bytes.
31
+
32
+ ### Upgrade
33
+
34
+ ```sh
35
+ npm install @kudzujs/core@^0.16.8
36
+ ```
37
+
38
+ ## 0.16.7 - Lazy Retained Editor Lifecycle
39
+
40
+ Kudzu 0.16.7 applies owner-triggered package loading to the real retained
41
+ CodeMirror editor journey without losing update, error, identity, or cleanup
42
+ behavior.
43
+
44
+ ### Changed in 0.16.7
45
+
46
+ - Lets one retained `useRef(null)` package handle receive its single setup
47
+ assignment inside the direct `.then()` callback of a guarded literal package
48
+ import.
49
+ - Reuses existing owner-scoped retained state across the lazy activation effect
50
+ and later read-only update effects.
51
+ - Keeps indirect promise callbacks, multiple assignments, update-effect writes,
52
+ attached refs, missing null-reset cleanup, and broader dynamic imports
53
+ unsupported.
54
+ - Preserves application-to-editor and editor-to-application updates, accessible
55
+ package errors and recovery, retained DOM identity, conditional disposal, and
56
+ fresh state on remount.
57
+ - Uses the native browser module map for cached remount and adds no loader,
58
+ resource registry, widget runtime, React, VDOM, hydration, or retained browser
59
+ component tree.
60
+ - Updates `create-kudzu@0.1.132` to generate projects on
61
+ `@kudzujs/core@^0.16.7`.
62
+
63
+ ### Output And Browser Evidence
64
+
65
+ - The eager real editor owns 214,968 raw / 71,607 gzip bytes. The lazy journey
66
+ initially owns 15,896 raw / 7,230 gzip bytes and defers 250,086 raw / 80,890
67
+ gzip bytes, reducing initial gzip by 64,377 bytes (89.9%).
68
+ - Required Chrome proves zero initial chunk requests, one on-demand request,
69
+ bidirectional updates, accessible failure recovery, retained identity, exact
70
+ conditional cleanup, cached fresh remount, and document cleanup.
71
+ - The static sibling emits zero JavaScript. The maintained Worker and window
72
+ graphs remain 907 raw / 477 gzip bytes and 14,456 raw / 6,160 gzip bytes.
73
+
74
+ ### Upgrade
75
+
76
+ ```sh
77
+ npm install @kudzujs/core@^0.16.7
78
+ ```
79
+
3
80
  ## 0.16.6 - Owner-Triggered Capability Imports
4
81
 
5
82
  Kudzu 0.16.6 lets one guarded effect load a literal package dependency only
@@ -15,7 +15,7 @@ The completed `0.9.0` milestone is recorded in [`0.9-semantic-compression.md`](.
15
15
 
16
16
  [`1.0-large-application-compatibility-audit.md`](./1.0-large-application-compatibility-audit.md) records the first post-0.9 probes against Memos, Apache Answer, and Actual Budget. The audit finds that reduced slices build but whole-application source retention and behavior parity do not yet pass.
17
17
 
18
- [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.18.2`. It assigns one application-capability section to each minor release and one independently accepted evidence packet to each patch release from `0.10.0` through the `1.0.0` gate. It supersedes the provisional 0.10/0.11/0.12 tool-first ordering in the completed 0.9 handoff without rewriting that historical record.
18
+ [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.19.0`. It assigns one application-capability section to each minor release and one independently accepted evidence packet to each patch release from `0.10.0` through the `1.0.0` gate. It supersedes the provisional 0.10/0.11/0.12 tool-first ordering in the completed 0.9 handoff without rewriting that historical record.
19
19
 
20
20
  ## Required Invariants
21
21
 
@@ -1531,6 +1531,36 @@ as the actual `0.16.6` patch to retain the public `0.16.x` version line.
1531
1531
  - **Done condition:** initial route bytes fall by the exact deferred package
1532
1532
  graph without losing behavior.
1533
1533
 
1534
+ **Completion evidence:** a real retained CodeMirror editor now begins closed and
1535
+ uses the `0.18.1` guarded literal package edge for its mount. The existing
1536
+ effect-private-ref normalization recognizes its one direct `.then()` assignment
1537
+ as the retained setup write, lowers the handle to owner-scoped state, and leaves
1538
+ later value effects read-only. Indirect callbacks, repeated writes, attached
1539
+ refs, update-effect writes, missing null-reset cleanup, and broader dynamic
1540
+ imports retain diagnostics. Package evaluation remains entirely in browser
1541
+ effect ESM.
1542
+
1543
+ Required Chrome records zero chunk requests before activation and one afterward.
1544
+ It proves initial content and accessible name, application-to-editor and
1545
+ editor-to-application updates, retained DOM identity, authored update failure
1546
+ and recovery, exact conditional disposal, fresh `Alpha` state on remount, no
1547
+ second package request, and final document disposal. Native ESM evaluation
1548
+ failure retry remains outside this packet as recorded in `0.18.1`.
1549
+
1550
+ The eager editor owns 214,968 raw / 71,607 gzip bytes. The lazy retained editor
1551
+ initially owns 15,896 raw / 7,230 gzip bytes and defers 250,086 raw / 80,890 gzip
1552
+ bytes, reducing initial gzip by 64,377 bytes (89.9%). Its static sibling remains
1553
+ 0 B JavaScript. The artifact collector covers nine fixtures and 29 routes with
1554
+ no ownerless JavaScript or preload mismatch.
1555
+
1556
+ The packet adds no semantic primitive, IR kind, compiler pass, public API,
1557
+ browser runtime concept/file, loader, resource registry, or widget runtime. One
1558
+ existing normalization pass changes by 15 added and four removed production
1559
+ lines. `npm run check`, 285 required-Chrome tests, package smoke, and the
1560
+ maintained benchmark pass. Worker and window graphs remain 907 raw / 477 gzip
1561
+ and 14,456 raw / 6,160 gzip bytes; the seven-build median is 230.5 ms. This
1562
+ packet ships as the actual `0.16.7` patch to retain the public `0.16.x` line.
1563
+
1534
1564
  ### `0.18.3`: Shared Chunks And Prefetch Policy
1535
1565
 
1536
1566
  - **Purpose:** avoid duplicate large feature graphs and define optional preload.
@@ -1540,6 +1570,37 @@ as the actual `0.16.6` patch to retain the public `0.16.x` version line.
1540
1570
  - **Done condition:** policy minimizes unused transfer while meeting accepted
1541
1571
  interaction latency.
1542
1572
 
1573
+ **Completion evidence:** two enhanced-navigation route owners now use one
1574
+ deferred CodeMirror graph. Existing multi-entry esbuild splitting emits one
1575
+ 250,086 raw / 80,890 gzip byte chunk, and the existing artifact report records
1576
+ that exact lazy path as shared by `/` and `/second`. Their eager graphs remain
1577
+ 21,599 raw / 9,466 gzip and 21,609 raw / 9,468 gzip bytes. Neither route HTML
1578
+ preloads the deferred chunk, navigation document prefetch does not request it,
1579
+ and the static sibling remains complete HTML with 0 B JavaScript.
1580
+
1581
+ Required Chrome proves zero requests before interaction, one request when the
1582
+ first owner activates, exact disposal, no route-triggered request, and no second
1583
+ request when the second owner activates. The selected policy is therefore no
1584
+ optional package preload: interaction calls native `import()` immediately, the
1585
+ browser module map deduplicates it for the document lifetime, and Kudzu owns no
1586
+ additional cache. Native ESM imports cannot be cancelled after request; owner
1587
+ cleanup invalidates the callback and prevents a late mount rather than claiming
1588
+ network cancellation.
1589
+
1590
+ The released lazy-import gate is also hardened before closing the packet.
1591
+ `ownedLazyPackageImport()` now resolves the callback callee through the existing
1592
+ lexical binding index and accepts only named `useEffect` imports from React or
1593
+ Kudzu. A local function with the same text can no longer bypass the ordinary
1594
+ dynamic-import diagnostic. The packet adds no semantic primitive, IR kind,
1595
+ compiler pass, public API, runtime concept/file, loader, prefetch registry, or
1596
+ cache. Four existing compiler files change by 17 added and eight removed lines.
1597
+ The artifact collector covers ten fixtures and 32 routes. `npm run check`, 288
1598
+ required-Chrome tests, package smoke, and the maintained benchmark pass. Worker
1599
+ and window graphs remain 907 raw / 477 gzip and 14,456 raw / 6,160 gzip bytes;
1600
+ the loaded Linux host records a 696.6 ms seven-build median without a timing
1601
+ comparison. This packet ships as the actual `0.16.8` patch to retain the public
1602
+ `0.16.x` line.
1603
+
1543
1604
  ## `0.19.x`: React Ecosystem Migration
1544
1605
 
1545
1606
  ### `0.19.0`: Compatibility Boundary And Inventory
@@ -1837,4 +1898,5 @@ release transaction where possible or document and publish a forward-fix patch.
1837
1898
  | `0.17.2` | Released as `0.16.5` | Preserve stale-prefetch rejection and bounded document, state, resource, listener, DOM, and external-instance ownership. | Patch release retained the `0.16.x` public version line |
1838
1899
  | `0.18.0` | Closed by existing artifact reporting | Preserve exact route/source ownership, bytes, hashes, preload equality, zero-JavaScript controls, and bounded prefetch policy. | No production change; no release consumed |
1839
1900
  | `0.18.1` | Released as `0.16.6` | Preserve one guarded literal package edge, native module-map deduplication, exact owner cleanup, deferred artifact ownership, and static exclusion. | Patch release retained the `0.16.x` public version line |
1840
- | `0.18.2` | Active | Apply the bounded edge to the retained editor mount/update/dispose journey without broadening dynamic imports. | None |
1901
+ | `0.18.2` | Released as `0.16.7` | Preserve on-demand retained editor mount, bidirectional updates, failure recovery, identity, exact cleanup, cached fresh remount, and static exclusion. | Patch release retained the `0.16.x` public version line |
1902
+ | `0.18.3` | Released as `0.16.8` | Preserve shared deferred ownership, interaction-only loading, native document module-map deduplication, exact owner cleanup, binding-aware import validation, and static exclusion. | Patch release retained the `0.16.x` public version line |
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.16.6` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The active application packet is `0.18.2`; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
3
+ This maps the current `0.16.8` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The active application packet is `0.19.0`; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
4
4
 
5
5
  ## Responsibility Map
6
6
 
@@ -10,7 +10,7 @@ This maps the current `0.16.6` architecture, built on the completed `0.9.0` sema
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. Production builds use one session; development retains it across rebuilds. 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
12
  | Build orchestration | [`framework/build.mjs`](../../framework/build.mjs), `build()`, `buildWithSession()` | Coordinates config, discovery, source compilation, RouteBuildRecord collection, CapabilityIR projection, generator invocation, artifact emission, and `afterBuild`. A retained session caches source results and pre-family route renders by page graph; successful builds alone replace that cache. |
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. |
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. A lazy package edge must resolve lexically to a named React or Kudzu `useEffect` import. |
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. |
16
16
  | Shared AST/scope helpers | [`framework/compiler/ast-helpers.mjs`](../../framework/compiler/ast-helpers.mjs) | Binding, scope, reference, inclusive ancestry, effect-return, and source-location analysis. |
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Completed compiler-foundation record and longer-term plan after `0.8.35`. The [`0.9-semantic-compression.md`](./0.9-semantic-compression.md) execution queue is complete. The active queue for current work is [`application-capability-release-plan.md`](./application-capability-release-plan.md) at packet `0.18.2`. This document does not mark any remaining 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.
5
+ Completed compiler-foundation record and longer-term plan after `0.8.35`. The [`0.9-semantic-compression.md`](./0.9-semantic-compression.md) execution queue is complete. The active queue for current work is [`application-capability-release-plan.md`](./application-capability-release-plan.md) at packet `0.19.0`. This document does not mark any remaining 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
6
 
7
- [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains the product authority for product invariants and fixture-driven feature selection. [`application-capability-release-plan.md`](./application-capability-release-plan.md) is authoritative for current work order and evidence at packet `0.18.2`; this plan retains the completed foundation, deferred program, and long-term production gates. If implementation evidence changes either boundary, update the relevant document before broadening a patch.
7
+ [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains the product authority for product invariants and fixture-driven feature selection. [`application-capability-release-plan.md`](./application-capability-release-plan.md) is authoritative for current work order and evidence at packet `0.19.0`; this plan retains the completed foundation, deferred program, and long-term production gates. If implementation evidence changes either boundary, update the relevant document before broadening a patch.
8
8
 
9
9
  ## Product Outcome
10
10
 
@@ -483,4 +483,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
483
483
 
484
484
  ## Immediate Decision
485
485
 
486
- All listed foundation and `0.9` slices are complete. Current work continues at `0.18.2` Lazy Editor Journey under the [`application-capability-release-plan.md`](./application-capability-release-plan.md); keep ResourceIR limited to qualifying independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
486
+ All listed foundation and `0.9` slices are complete. Current work continues at `0.19.0` Compatibility Boundary And Inventory under the [`application-capability-release-plan.md`](./application-capability-release-plan.md); keep ResourceIR limited to qualifying independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
@@ -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.62` are completed scopes represented by package/release records. The `0.10.0` through `1.0.0` minor/patch sequence is maintained in [`application-capability-release-plan.md`](./application-capability-release-plan.md), currently at `0.18.2`; that plan supersedes the provisional tool-first 0.10/0.11/0.12 ordering in the completed 0.9 handoff.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.62` are completed scopes represented by package/release records. The `0.10.0` through `1.0.0` minor/patch sequence is maintained in [`application-capability-release-plan.md`](./application-capability-release-plan.md), currently at `0.19.0`; that plan supersedes the provisional tool-first 0.10/0.11/0.12 ordering in the completed 0.9 handoff.
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
 
@@ -66,7 +66,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
66
66
 
67
67
  ## Generator Versions
68
68
 
69
- `create-kudzu@0.1.131` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.6`.
69
+ `create-kudzu@0.1.133` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.8`.
70
70
 
71
71
  ## Release Boundary
72
72
 
@@ -172,7 +172,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
172
172
  const dynamicImports = []
173
173
  const visitDynamicImports = node => {
174
174
  if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
175
- if (role !== "effect" || !ownedLazyPackageImport(node)) throw new Error("Dynamic package imports require one literal import() directly inside an inline owned effect")
175
+ if (role !== "effect" || !ownedLazyPackageImport(node, bindingIndex)) throw new Error("Dynamic package imports require one literal import() directly inside an inline owned effect")
176
176
  dynamicImports.push({ kind: "dynamic", local: node.arguments[0].text, target: node.arguments[0].text, package: true })
177
177
  }
178
178
  ts.forEachChild(node, visitDynamicImports)
@@ -1,7 +1,10 @@
1
1
  import ts from "typescript"
2
+ import { createBindingIndex } from "./analysis/binding-index.mjs"
2
3
  import { effectReturns, importDeclarationNames, isNodeWithin, isShadowedIdentifier, nearestFunction, referenceIdentifiers, referencesIdentifier, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
4
+ import { ownedLazyPackageImport } from "./source-graph.mjs"
3
5
 
4
6
  export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
7
+ const bindingIndex = createBindingIndex(sourceFile)
5
8
  const frameCall = (node, name) => ts.isCallExpression(node) && (
6
9
  ts.isIdentifier(node.expression) && node.expression.text === name ||
7
10
  ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "window" && node.expression.name.text === name
@@ -22,6 +25,16 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
22
25
  if (isNegated) condition = unwrapExpression(condition.operand)
23
26
  return isNegated === negated && currentAccess(condition, name)
24
27
  }
28
+ const containsLazyImport = node => {
29
+ if (ownedLazyPackageImport(node, bindingIndex)) return true
30
+ let found = false
31
+ ts.forEachChild(node, child => { if (!found) found = containsLazyImport(child) })
32
+ return found
33
+ }
34
+ const directLazyImportCallback = (callback, effectCallback) => {
35
+ const call = callback?.parent
36
+ return (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && ts.isCallExpression(call) && call.arguments[0] === callback && ts.isPropertyAccessExpression(call.expression) && call.expression.name.text === "then" && ownedLazyPackageImport(unwrapExpression(call.expression.expression), bindingIndex) && isNodeWithin(callback, effectCallback)
37
+ }
25
38
  const hasUseRefImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useRef"))
26
39
  const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
27
40
  const hasUseStateImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && ["react", "@kudzujs/core"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useState"))
@@ -73,8 +86,8 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
73
86
  const callbacks = owners.map(effect => effect.arguments[0])
74
87
  const directCallbacks = callbacks.every(callback => (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && ts.isBlock(callback.body))
75
88
  const everyAccessOwned = accesses.every(access => callbacks.filter(callback => isNodeWithin(access, callback)).length === 1)
76
- const mountEffects = owners.filter(effect => ts.isArrayLiteralExpression(effect.arguments[1]) && !effect.arguments[1].elements.length)
77
- const updateEffects = owners.filter(effect => ts.isArrayLiteralExpression(effect.arguments[1]) && effect.arguments[1].elements.length)
89
+ const mountEffects = owners.filter(effect => ts.isArrayLiteralExpression(effect.arguments[1]) && (!effect.arguments[1].elements.length || containsLazyImport(effect.arguments[0])))
90
+ const updateEffects = owners.filter(effect => !mountEffects.includes(effect))
78
91
  if (owners.length >= 2 && directCallbacks && everyAccessOwned && mountEffects.length === 1 && updateEffects.length === owners.length - 1 && owners[0] === mountEffects[0]) {
79
92
  const mount = mountEffects[0]
80
93
  const callback = mount.arguments[0]
@@ -85,7 +98,7 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
85
98
  const cleanupWrites = cleanup ? writes.filter(access => isNodeWithin(access, cleanup)) : []
86
99
  const setupStatement = setupWrites[0]?.parent.parent
87
100
  const cleanupStatement = cleanupWrites[0]?.parent.parent
88
- const directSetup = setupWrites.length === 1 && ts.isExpressionStatement(setupStatement) && setupStatement.parent === callback.body && unwrapExpression(setupWrites[0].parent.right).kind !== ts.SyntaxKind.NullKeyword
101
+ const directSetup = setupWrites.length === 1 && ts.isExpressionStatement(setupStatement) && (setupStatement.parent === callback.body || directLazyImportCallback(nearestFunction(setupWrites[0]), callback)) && unwrapExpression(setupWrites[0].parent.right).kind !== ts.SyntaxKind.NullKeyword
89
102
  const directCleanup = cleanup && ts.isBlock(cleanup.body) && cleanupWrites.length === 1 && ts.isExpressionStatement(cleanupStatement) && cleanupStatement.parent === cleanup.body && unwrapExpression(cleanupWrites[0].parent.right).kind === ts.SyntaxKind.NullKeyword
90
103
  const updateWrites = mutations.some(access => updateEffects.some(effect => isNodeWithin(access, effect.arguments[0])))
91
104
  if (directSetup && directCleanup && !updateWrites) {
@@ -96,7 +109,7 @@ export function normalizeEffectPrivateRefs(sourceFile, factory, context) {
96
109
  return
97
110
  }
98
111
  }
99
- if (owners.length >= 2) throw sourceNodeError(node, sourceFile, "Retained instance refs require one empty-dependency mount effect with one direct assignment and null-reset cleanup followed by read-only dependency effects")
112
+ if (owners.length >= 2) throw sourceNodeError(node, sourceFile, "Retained instance refs require one empty-dependency mount effect or guarded lazy activation effect with one direct assignment and null-reset cleanup followed by read-only dependency effects")
100
113
  }
101
114
  if (!frameAssignments.length) {
102
115
  const callback = effects.length === 1 ? effects[0].arguments[0] : undefined
@@ -2032,7 +2032,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2032
2032
  if (!ts.isBlock(callback.body)) effectFail(callback, "useEffect() callback must use a block body")
2033
2033
  const lazyPackageImports = []
2034
2034
  const collectLazyPackageImports = current => {
2035
- if (ownedLazyPackageImport(current)) lazyPackageImports.push(current)
2035
+ if (ownedLazyPackageImport(current, bindingIndex)) lazyPackageImports.push(current)
2036
2036
  else ts.forEachChild(current, collectLazyPackageImports)
2037
2037
  }
2038
2038
  collectLazyPackageImports(callback)
@@ -2136,7 +2136,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
2136
2136
  ]) : factory.createNull()) : []
2137
2137
  let hasLazyPackageImport = false
2138
2138
  const findLazyPackageImport = current => {
2139
- if (ownedLazyPackageImport(current)) hasLazyPackageImport = true
2139
+ if (ownedLazyPackageImport(current, bindingIndex)) hasLazyPackageImport = true
2140
2140
  else ts.forEachChild(current, findLazyPackageImport)
2141
2141
  }
2142
2142
  findLazyPackageImport(callback)
@@ -1,5 +1,6 @@
1
1
  import { dirname, extname, join, relative, resolve } from "node:path"
2
2
  import ts from "typescript"
3
+ import { createBindingIndex } from "./analysis/binding-index.mjs"
3
4
  import { sourceNodeError } from "./ast-helpers.mjs"
4
5
 
5
6
  export function createSourceGraph(root) {
@@ -17,11 +18,12 @@ export function createSourceGraph(root) {
17
18
 
18
19
  const ordinaryRuntimeDependencies = (file, sourceFile, sourceFiles, isStaticImport) => {
19
20
  const dependencies = []
21
+ const bindingIndex = createBindingIndex(sourceFile)
20
22
  const rejectDynamicImports = node => {
21
23
  if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
22
24
  const argument = node.arguments[0]
23
25
  const specifier = node.arguments.length === 1 && ts.isStringLiteralLike(argument) ? JSON.stringify(argument.text) : argument?.getText(sourceFile) ?? "<missing>"
24
- if (ownedLazyPackageImport(node)) return
26
+ if (ownedLazyPackageImport(node, bindingIndex)) return
25
27
  throw sourceNodeError(node, sourceFile, `Dynamic import ${specifier} is not supported in ordinary source modules`)
26
28
  }
27
29
  ts.forEachChild(node, rejectDynamicImports)
@@ -45,14 +47,19 @@ export function createSourceGraph(root) {
45
47
  return { ordinaryRuntimeDependencies, parseSourceFile, resolveSourceImport, runtimeModuleReference }
46
48
  }
47
49
 
48
- export function ownedLazyPackageImport(node) {
50
+ export function ownedLazyPackageImport(node, bindingIndex) {
49
51
  if (!ts.isCallExpression(node) || node.expression.kind !== ts.SyntaxKind.ImportKeyword || node.arguments.length !== 1 || !ts.isStringLiteral(node.arguments[0])) return false
50
52
  const target = node.arguments[0].text
51
53
  if (!target || target.startsWith(".") || target.startsWith("/") || /^[a-z][a-z\d+.-]*:/i.test(target)) return false
52
54
  for (let current = node.parent; current; current = current.parent) {
53
55
  if (!ts.isArrowFunction(current) && !ts.isFunctionExpression(current)) continue
54
56
  const call = current.parent
55
- return ts.isCallExpression(call) && call.arguments[0] === current && ts.isIdentifier(call.expression) && call.expression.text === "useEffect" && !current.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)
57
+ if (!ts.isCallExpression(call) || call.arguments[0] !== current || !ts.isIdentifier(call.expression) || current.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) return false
58
+ const binding = bindingIndex.resolveReference(call.expression, node.getSourceFile())
59
+ const declaration = binding?.kind === "import" ? binding.declaration : undefined
60
+ const specifier = declaration?.parent
61
+ const importDeclaration = specifier?.parent?.parent?.parent
62
+ return Boolean(specifier && ts.isImportSpecifier(specifier) && (specifier.propertyName ?? specifier.name).text === "useEffect" && importDeclaration && ts.isImportDeclaration(importDeclaration) && ts.isStringLiteral(importDeclaration.moduleSpecifier) && ["react", "@kudzujs/core"].includes(importDeclaration.moduleSpecifier.text))
56
63
  }
57
64
  return false
58
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.6",
3
+ "version": "0.16.8",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",