@kudzujs/core 0.16.8 → 0.16.10

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.
@@ -18,7 +18,7 @@ Apache Answer's `useTranslation()` cannot be folded safely because its language
18
18
 
19
19
  Apache Answer's SWR/axios/qs question reads now have an executable migration path through one native fetch effect with application-owned loading/error/data state. Derived `page` and `order` query dependencies restart the effect, stale responses lose setter authority, recommendation endpoints and HTTP recovery remain explicit, nested keyed tags retain list ownership, and a static sibling remains JavaScript-free. No cache, Provider, retry scheduler, SWR, axios, or query runtime is added.
20
20
 
21
- Apache Answer authentication now has a reduced layout-owned session path: native login form submission persists a token, a mount effect restores and validates it, shared header/settings consumers read one package-neutral state record, and a 401 clears storage/state before native replacement navigation. The public sibling remains JavaScript-free. Full browser acceptance and the broader server-fed settings stores remain subsequent gates.
21
+ Apache Answer authentication now has a browser-proven layout-owned session path: native login form submission persists a token, a mount effect restores and validates it, shared header/settings consumers read one package-neutral state record, and a 401 clears storage/state before native replacement navigation. A deterministic server verifies anonymous, invalid login, valid login, reload restoration, shared consumers, and expiry while the public sibling remains JavaScript-free. Broader server-fed settings stores remain a subsequent gate.
22
22
 
23
23
  Apache Answer's first route-shell gate replaces `RouterProvider` and nested `Outlet` ownership with seven directly addressable Kudzu file routes, one ordinary shared shell, native anchors, and runtime question/tag parameters. Questions, tags, admin dashboard, and unwrapped legal output build without a browser router; the two bracket routes emit only pathname capability and rewrite metadata.
24
24
 
@@ -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.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.
54
+ The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.19.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.
55
55
 
56
56
  ### 0.10.0 Through 0.21.x: Application Capability Release Train
57
57
 
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,29 @@
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.10 Apache Answer Authentication Journey
6
+
7
+ Measured 2026-08-31 on Linux x64 with Node 24.14.0 and required Chrome. The
8
+ existing authentication fixture remains 17 deploy files and 36,441 raw / 15,598
9
+ aggregate gzip B. The login route owns 25,684 raw / 11,372 gzip B, settings owns
10
+ 26,818 raw / 11,319 gzip B, and the public sibling owns zero JavaScript. Required
11
+ Chrome verifies anonymous, invalid and valid login, token restore, shared
12
+ header/settings state, 401 clearing, and replacement navigation against a
13
+ deterministic server. No production source or deploy artifact changed. The
14
+ maintained Worker graph remains 907 raw / 477 gzip B and the window graph remains
15
+ 14,456 raw / 6,160 gzip B. Seven clean builds record a 674.7 ms median on this
16
+ loaded host; timing is not compared.
17
+
18
+ ## 0.16.9 Compatibility Boundary And Inventory
19
+
20
+ Measured 2026-08-31 on Linux x64 with Node 24.14.0 and required Chrome. The
21
+ compatibility inventory is compiler scratch generated from reachable original
22
+ source before normalization; it emits no deploy file or browser capability.
23
+ Existing project-output baselines and package-specific diagnostics pass without
24
+ change. The maintained Worker graph remains 907 raw / 477 gzip B and the window
25
+ graph remains 14,456 raw / 6,160 gzip B. Seven clean builds record a 532.6 ms
26
+ median on this loaded host; timing is not compared.
27
+
5
28
  ## 0.16.8 Shared Lazy Capability Graph
6
29
 
7
30
  Measured 2026-08-31 on Linux x64 with Node 24.14.0 and Chrome. Two route owners
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.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).
17
+ **Latest release: 0.16.10 - Apache Answer authentication journey.** The existing layout-owned session reduction now passes anonymous, invalid and valid login, reload restoration, shared settings, and 401 replacement navigation against a deterministic server without compiler or browser-output changes. Read the [release notes](./RELEASES.md#01610---apache-answer-authentication-journey), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.10), 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,59 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.10 - Apache Answer Authentication Journey
4
+
5
+ Kudzu 0.16.10 promotes the existing Apache Answer authentication reduction from
6
+ build-only evidence to a complete deterministic browser journey.
7
+
8
+ ### Changed in 0.16.10
9
+
10
+ - Proves anonymous startup, invalid login, valid login, token persistence, and
11
+ token restoration after a full document reload.
12
+ - Proves one layout-owned package-neutral session record updates the shared
13
+ header and settings route without a browser component tree.
14
+ - Proves a server 401 clears storage and shared state before native replacement
15
+ navigation returns to the anonymous login document.
16
+ - Runs the journey against a deterministic native test server while preserving
17
+ the public sibling as complete HTML with zero JavaScript.
18
+ - Adds no compiler behavior, semantic primitive, IR kind, runtime concept,
19
+ package adapter, public API, or browser bytes.
20
+ - Updates `create-kudzu@0.1.135` to generate projects on
21
+ `@kudzujs/core@^0.16.10`.
22
+
23
+ ### Upgrade
24
+
25
+ ```sh
26
+ npm install @kudzujs/core@^0.16.10
27
+ ```
28
+
29
+ ## 0.16.9 - Compatibility Boundary And Inventory
30
+
31
+ Kudzu 0.16.9 records deterministic package compatibility provenance from the
32
+ reachable authored source graph without changing semantic IR or browser output.
33
+
34
+ ### Changed in 0.16.9
35
+
36
+ - Centralizes package identities and compatibility classifications used by
37
+ React, React Router, React Bootstrap, Zustand, `clsx`, and browser-signal
38
+ normalization.
39
+ - Writes `.kudzu/kudzu-compatibility.json` from original, pre-normalization
40
+ source with package/API identity and exact line/column ranges.
41
+ - Classifies sites as Native, Compiled, Normalized, Adapter, Owned External UI,
42
+ Partial, or Unsupported with stable rule names and deterministic ordering.
43
+ - Includes only modules reachable from page entries; unused migration source is
44
+ excluded from the report just as it is from compilation.
45
+ - Keeps package origin out of ComponentAnalysis, ModuleIR, RouteIR, codegen,
46
+ runtime modules, and deploy output. No plugin API or browser capability is
47
+ added.
48
+ - Updates `create-kudzu@0.1.134` to generate projects on
49
+ `@kudzujs/core@^0.16.9`.
50
+
51
+ ### Upgrade
52
+
53
+ ```sh
54
+ npm install @kudzujs/core@^0.16.9
55
+ ```
56
+
3
57
  ## 0.16.8 - Shared Lazy Capability Graphs
4
58
 
5
59
  Kudzu 0.16.8 shares one deferred package graph across route owners while
@@ -338,8 +338,10 @@ navigation compile without Zustand, axios, React Router, or React in output; a
338
338
  public sibling remains static. One compiler correction prevents shared-state
339
339
  identifiers such as `session.token` from being misclassified as imported
340
340
  calculation results during effect dependency analysis. This adds no IR kind,
341
- pass, or runtime concept. Browser login/reload/401 acceptance remains required
342
- before this intake can be marked a completed authentication reduction.
341
+ pass, or runtime concept. Required Chrome now proves anonymous, invalid and
342
+ valid login, token restore after reload, shared header/settings reads, 401
343
+ storage/state clearing, and replacement navigation against a deterministic
344
+ server. The authentication reduction is complete without a production change.
343
345
 
344
346
  ## Route Shell Intake
345
347
 
@@ -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.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.
18
+ [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.19.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.
19
19
 
20
20
  ## Required Invariants
21
21
 
@@ -1615,6 +1615,34 @@ comparison. This packet ships as the actual `0.16.8` patch to retain the public
1615
1615
  - **Done condition:** existing React Router, Bootstrap, Zustand, and browser
1616
1616
  normalizations preserve output and diagnostics.
1617
1617
 
1618
+ Implementation and release-candidate evidence complete. One internal registry
1619
+ now owns package identities and classifies original reachable import, re-export,
1620
+ and literal dynamic-import sites before normalization. The deterministic
1621
+ `.kudzu/kudzu-compatibility.json` report records package/API identity, stable
1622
+ rule, Native, Compiled, Normalized, Adapter, Owned External UI, Partial, or
1623
+ Unsupported treatment, and exact authored line/column range. Type-only,
1624
+ relative, Node, Kudzu-first-party, and unreachable source stays outside the
1625
+ inventory.
1626
+
1627
+ React Router, React Bootstrap, Zustand, React migration, browser-signal, `clsx`,
1628
+ and i18next recognition reuse the registry's package identities. Focused tests
1629
+ cover all seven classes, aliases, exact ranges, deterministic ordering, and a
1630
+ real build where an unreachable unsupported module is excluded. Existing
1631
+ project output baselines and package diagnostics remain green. Package origin
1632
+ does not enter SourceResult, ComponentAnalysis, ModuleIR, RouteIR, codegen,
1633
+ runtime files, `afterBuild()`, or deploy output.
1634
+
1635
+ The slice adds zero semantic primitives, IR kinds, compiler passes,
1636
+ normalization entries, runtime concepts/files, browser bytes, or public APIs.
1637
+ Production compiler/orchestration changes are 161 added and 13 removed lines,
1638
+ including the 141-line registry, for +148 net compatibility/build lines and zero
1639
+ core semantic LOC. `npm run check`, 290 required-Chrome tests, package smoke,
1640
+ and the maintained benchmark pass. Worker and window graphs remain 907 raw /
1641
+ 477 gzip and 14,456 raw / 6,160 gzip bytes; seven clean Linux x64 builds record
1642
+ a 532.6 ms median without a timing comparison. This packet ships as the actual
1643
+ `0.16.9` patch, with `create-kudzu@0.1.134`, to retain the public
1644
+ `0.16.x` line.
1645
+
1618
1646
  ### `0.19.1`: Apache Answer Authentication Journey
1619
1647
 
1620
1648
  - **Purpose:** promote the existing build-only auth fixture to browser evidence.
@@ -1623,6 +1651,24 @@ comparison. This packet ships as the actual `0.16.8` patch to retain the public
1623
1651
  - **Stop condition:** client UI is treated as authorization.
1624
1652
  - **Done condition:** the complete journey passes against a deterministic server.
1625
1653
 
1654
+ Complete. The existing Apache Answer reduction now runs one required-Chrome
1655
+ journey against a deterministic native server. It proves anonymous startup,
1656
+ invalid and valid native form submission, token persistence and reload restore,
1657
+ layout-owned header/settings reads, a server 401 clearing storage and shared
1658
+ state, and replacement navigation back to the anonymous login document. The
1659
+ public sibling remains complete HTML with zero JavaScript.
1660
+
1661
+ No authored fixture, compiler, IR, runtime, or deploy artifact changed. The
1662
+ packet adds one browser harness only: zero semantic primitives, ModuleIR kinds,
1663
+ passes, normalization entries, runtime concepts, public APIs, and browser bytes.
1664
+ The authentication fixture remains 17 deploy files and 36,441 raw / 15,598 gzip
1665
+ bytes; `/` owns 25,684 raw / 11,372 gzip bytes, `/settings` owns 26,818 raw /
1666
+ 11,319 gzip bytes, and `/public` owns 0 JavaScript bytes. Worker and window
1667
+ graphs remain 907 raw / 477 gzip and 14,456 raw / 6,160 gzip bytes; seven clean
1668
+ Linux x64 builds record a 674.7 ms median without a timing comparison. This
1669
+ packet ships as the actual `0.16.10` patch, with `create-kudzu@0.1.135`, to
1670
+ retain the public `0.16.x` line.
1671
+
1626
1672
  ### `0.19.2`: Apache Answer Connected Authoring Journey
1627
1673
 
1628
1674
  - **Purpose:** connect query, route, detail, create/edit, validation, mutation,
@@ -1900,3 +1946,5 @@ release transaction where possible or document and publish a forward-fix patch.
1900
1946
  | `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 |
1901
1947
  | `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
1948
  | `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 |
1949
+ | `0.19.0` | Released as `0.16.9` | Preserve the compatibility registry, reachable source inventory, exact ranges, and package-neutral output boundary. | Patch release retained the `0.16.x` public version line |
1950
+ | `0.19.1` | Released as `0.16.10` | Preserve anonymous, invalid/valid login, token restore, layout-shared session reads, 401 clear, replacement navigation, and public zero-JavaScript output. | 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.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.
3
+ This maps the current `0.16.10` 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.2`; 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
 
@@ -11,6 +11,7 @@ This maps the current `0.16.8` architecture, built on the completed `0.9.0` sema
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
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
+ | Compatibility inventory | [`framework/compiler/compatibility-registry.mjs`](../../framework/compiler/compatibility-registry.mjs), `createCompatibilityReport()` | Classifies package/API sites in reachable original source as Native, Compiled, Normalized, Adapter, Owned External UI, Partial, or Unsupported and writes deterministic source ranges to `.kudzu/kudzu-compatibility.json`. Package provenance ends here and never enters semantic IR or browser output. |
14
15
  | 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
16
  | 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
17
  | 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. |
@@ -43,6 +44,7 @@ This maps the current `0.16.8` architecture, built on the completed `0.9.0` sema
43
44
  src/pages entries + config
44
45
  -> ProjectSession(root) with project paths, source records, parsed/symbol caches, graph, and Worker compiler
45
46
  -> project discovery and reachable relative graph
47
+ -> classify original package sites into the scratch compatibility report
46
48
  -> in development, intersect changed paths with current and prior per-page graphs
47
49
  -> reuse unaffected SourceResult and pre-family route render records
48
50
  -> compileSource()
@@ -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.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.
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.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.
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.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.
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.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.
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.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.
486
+ All listed foundation and `0.9` slices are complete. Current work continues at `0.19.2` Apache Answer Connected Authoring 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.
@@ -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.19.0`; 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.2`; 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.133` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.8`.
69
+ `create-kudzu@0.1.135` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.10`.
70
70
 
71
71
  ## Release Boundary
72
72
 
@@ -78,7 +78,7 @@ Reduced Zustand migration stores lower through package-neutral `SharedStateIR` a
78
78
 
79
79
  Compiler ownership follows explicit stages. `build.mjs` coordinates project discovery, source compilation, RouteIR rendering, RouteBuildRecord collection, CapabilityIR planning, generator calls, and artifact emission. `compiler/normalization-pipeline.mjs` owns pass order and parent repair. The main transformer analyzes normalized source while `compiler/descriptor-session.mjs` owns one per-source semantic artifact containing HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, and client imports. `core.mjs` emits complete HTML, RouteIR v1, and exact handler references: route-local state `slot` is an internal array reference, `id` remains the browser/DOM identity, and `name` remains readable development metadata. `compiler/route-build-record.mjs` validates structural artifact edges, and `compiler/route-capability-planner.mjs` projects CapabilityIR v1 from those records. Focused codegen modules consume IR/descriptors and return route-specific source without source analysis or filesystem ownership. The completed architecture record lives in `docs/next-architecture`; it adds no runtime or accepted syntax by itself.
80
80
 
81
- `.kudzu/kudzu-artifacts.json` records each route's exact CapabilityIR signature, assigned emitted runtime family, handler, Worker, stylesheet, and transitive chunk edges. Equal standalone signatures reuse `dist/assets/runtime/<family>/`; every enhanced-navigation group intentionally shares one union family. The same JSON-safe contract is available to `afterBuild()` as `artifacts`.
81
+ `.kudzu/kudzu-artifacts.json` records each route's exact CapabilityIR signature, assigned emitted runtime family, handler, Worker, stylesheet, and transitive chunk edges. Equal standalone signatures reuse `dist/assets/runtime/<family>/`; every enhanced-navigation group intentionally shares one union family. The same JSON-safe contract is available to `afterBuild()` as `artifacts`. `.kudzu/kudzu-compatibility.json` separately inventories reachable authored package sites before normalization with deterministic compatibility classes and source ranges; that provenance does not enter semantic IR, codegen, runtime files, or deploy output.
82
82
 
83
83
  New syntax support belongs in an existing pass or a focused new pass only when a reduced migration fixture proves it. Passes must preserve source-located diagnostics, avoid module-global analysis state, and expose metadata through return values rather than AST-identity side channels. Build orchestration stays in `build.mjs`; feature-specific graph validation or code generation moves under `compiler/` when it has a stable input/output boundary.
84
84
 
@@ -4,6 +4,7 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
4
4
  import { pathToFileURL } from "node:url"
5
5
  import { build as bundle, transform } from "esbuild"
6
6
  import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
7
+ import { createCompatibilityReport } from "./compiler/compatibility-registry.mjs"
7
8
  import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
8
9
  import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
9
10
  import { createProjectSession } from "./compiler/project-session.mjs"
@@ -103,6 +104,7 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
103
104
  if (!pageFiles.length) throw new Error("No pages found in src/pages/")
104
105
  const pageSources = new Map(pageFiles.map(file => [file, new Set(reachableSourceFiles([file], allSourceFileSet, sourceIndex))]))
105
106
  const sourceFiles = [...new Set([...pageSources.values()].flatMap(files => [...files]))].sort()
107
+ await writePrettyJson(join(workDirectory, "kudzu-compatibility.json"), createCompatibilityReport(sourceFiles.map(file => ({ file: relative(root, file).replaceAll(sep, "/"), source: sourceIndex.get(file) }))))
106
108
  const sourceFileSet = project.sourceFiles
107
109
  sourceFileSet.clear()
108
110
  for (const file of sourceFiles) sourceFileSet.add(file)
@@ -1,8 +1,9 @@
1
1
  import ts from "typescript"
2
2
  import { bindingNames, containsJsx, functionVarDeclaresName, importDeclarationNames, isNodeWithin, isShadowedIdentifier, isUnshadowedGlobal, nearestFunction, referenceIdentifiers, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
3
+ import { compatibilityPackages } from "./compatibility-registry.mjs"
3
4
 
4
5
  export function normalizeMediaQueryExternalStores(sourceFile, factory, context) {
5
- const imports = sourceFile.statements.filter(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react" && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings))
6
+ const imports = sourceFile.statements.filter(statement => ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === compatibilityPackages.react && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings))
6
7
  const externalStoreImport = imports.flatMap(statement => statement.importClause.namedBindings.elements.map(entry => ({ entry, statement }))).find(({ entry }) => !entry.isTypeOnly && !entry.propertyName && entry.name.text === "useSyncExternalStore")
7
8
  if (!externalStoreImport) return sourceFile
8
9
  const returnedExpression = callback => {
@@ -0,0 +1,141 @@
1
+ import ts from "typescript"
2
+
3
+ export const compatibilityClasses = Object.freeze([
4
+ "Native",
5
+ "Compiled",
6
+ "Normalized",
7
+ "Adapter",
8
+ "Owned External UI",
9
+ "Partial",
10
+ "Unsupported"
11
+ ])
12
+
13
+ export const compatibilityPackages = Object.freeze({
14
+ clsx: "clsx",
15
+ react: "react",
16
+ reactBootstrap: "react-bootstrap",
17
+ reactI18next: "react-i18next",
18
+ reactRouter: "react-router-dom",
19
+ zustand: "zustand"
20
+ })
21
+
22
+ const compiledReact = new Set(["default", "*", "Fragment", "createContext", "useContext", "useEffect", "useId", "useReducer", "useRef", "useState"])
23
+ const normalizedReact = new Set(["createRef", "forwardRef", "memo", "useCallback", "useMemo", "useSyncExternalStore"])
24
+ const normalizedRouter = new Set(["useMatch", "useParams", "useSearchParams"])
25
+ const ownedExternalUi = new Set(["@codemirror/view", "chart.js", "gsap", "sortablejs", "typed.js"])
26
+
27
+ export function createCompatibilityReport(sources) {
28
+ const sites = sources.flatMap(({ file, source }) => compatibilitySites(file, source)).sort(compareSites)
29
+ const counts = Object.fromEntries(compatibilityClasses.map(name => [name, 0]))
30
+ const packages = new Map()
31
+ for (const site of sites) {
32
+ counts[site.classification]++
33
+ const key = `${site.package}\0${site.classification}`
34
+ packages.set(key, { package: site.package, classification: site.classification, sites: (packages.get(key)?.sites ?? 0) + 1 })
35
+ }
36
+ return {
37
+ version: 1,
38
+ summary: counts,
39
+ packages: [...packages.values()].sort((left, right) => compareText(left.package, right.package) || compareText(left.classification, right.classification)),
40
+ sites
41
+ }
42
+ }
43
+
44
+ function compatibilitySites(file, source) {
45
+ const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS)
46
+ const sites = []
47
+ const add = (target, imported, local, kind, node) => {
48
+ if (!isBarePackage(target) || target === "@kudzujs/core" || target.startsWith("@kudzujs/core/")) return
49
+ const rule = classify(target, imported, kind)
50
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
51
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd())
52
+ sites.push({
53
+ package: target,
54
+ imported,
55
+ ...(local && local !== imported ? { local } : {}),
56
+ kind,
57
+ classification: rule.classification,
58
+ rule: rule.id,
59
+ file,
60
+ location: { line: start.line + 1, column: start.character + 1, endLine: end.line + 1, endColumn: end.character + 1 }
61
+ })
62
+ }
63
+ const visit = node => {
64
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
65
+ const target = node.moduleSpecifier.text
66
+ const clause = node.importClause
67
+ if (!clause?.isTypeOnly) {
68
+ if (!clause) add(target, null, null, "side-effect-import", node.moduleSpecifier)
69
+ if (clause?.name) add(target, "default", clause.name.text, "import", clause.name)
70
+ const bindings = clause?.namedBindings
71
+ if (bindings && ts.isNamespaceImport(bindings)) add(target, "*", bindings.name.text, "import", bindings.name)
72
+ if (bindings && ts.isNamedImports(bindings)) for (const entry of bindings.elements) {
73
+ if (!entry.isTypeOnly) add(target, (entry.propertyName ?? entry.name).text, entry.name.text, "import", entry)
74
+ }
75
+ }
76
+ return
77
+ }
78
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
79
+ const target = node.moduleSpecifier.text
80
+ const clause = node.exportClause
81
+ if (!node.isTypeOnly) {
82
+ if (!clause || ts.isNamespaceExport(clause)) add(target, "*", null, "re-export", clause ?? node.moduleSpecifier)
83
+ else for (const entry of clause.elements) if (!entry.isTypeOnly) add(target, (entry.propertyName ?? entry.name).text, entry.name.text, "re-export", entry)
84
+ }
85
+ return
86
+ }
87
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
88
+ add(node.arguments[0].text, "*", null, "dynamic-import", node.arguments[0])
89
+ return
90
+ }
91
+ ts.forEachChild(node, visit)
92
+ }
93
+ visit(sourceFile)
94
+ return sites
95
+ }
96
+
97
+ function classify(target, imported, kind) {
98
+ if (kind === "re-export") return { id: "package.re-export.unsupported", classification: "Unsupported" }
99
+ if (kind === "side-effect-import") return { id: "package.side-effect.unsupported", classification: "Unsupported" }
100
+ const root = packageRoot(target)
101
+ if (kind === "dynamic-import") return { id: "package.dynamic.partial", classification: "Partial" }
102
+ if (target === compatibilityPackages.react) {
103
+ if (compiledReact.has(imported)) return { id: "react.compiled", classification: "Compiled" }
104
+ if (normalizedReact.has(imported)) return { id: "react.normalized", classification: "Normalized" }
105
+ return { id: "react.partial", classification: "Partial" }
106
+ }
107
+ if (target === compatibilityPackages.reactRouter) {
108
+ if (imported === "Link" || imported === "useNavigate") return { id: "react-router-dom.native", classification: "Native" }
109
+ if (normalizedRouter.has(imported)) return { id: "react-router-dom.normalized", classification: "Normalized" }
110
+ return { id: "react-router-dom.partial", classification: "Partial" }
111
+ }
112
+ if (target === compatibilityPackages.reactBootstrap) return ["Row", "Col"].includes(imported)
113
+ ? { id: "react-bootstrap.normalized", classification: "Normalized" }
114
+ : { id: "react-bootstrap.partial", classification: "Partial" }
115
+ if (target === compatibilityPackages.zustand) return imported === "create"
116
+ ? { id: "zustand.adapter", classification: "Adapter" }
117
+ : { id: "zustand.partial", classification: "Partial" }
118
+ if (target === compatibilityPackages.clsx) return ["default", "clsx"].includes(imported)
119
+ ? { id: "clsx.normalized", classification: "Normalized" }
120
+ : { id: "clsx.partial", classification: "Partial" }
121
+ if (target === compatibilityPackages.reactI18next) return { id: "react-i18next.unsupported", classification: "Unsupported" }
122
+ if (ownedExternalUi.has(root)) return { id: "owned-external-ui", classification: "Owned External UI" }
123
+ return { id: "package.contextual", classification: "Partial" }
124
+ }
125
+
126
+ function packageRoot(target) {
127
+ const parts = target.split("/")
128
+ return target.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0]
129
+ }
130
+
131
+ function isBarePackage(target) {
132
+ return !target.startsWith(".") && !target.startsWith("/") && !target.startsWith("node:") && !/^[a-z][a-z+.-]*:/i.test(target)
133
+ }
134
+
135
+ function compareSites(left, right) {
136
+ return compareText(left.file, right.file) || left.location.line - right.location.line || left.location.column - right.location.column || compareText(left.package, right.package) || compareText(String(left.imported), String(right.imported))
137
+ }
138
+
139
+ function compareText(left, right) {
140
+ return left < right ? -1 : left > right ? 1 : 0
141
+ }
@@ -1,12 +1,13 @@
1
1
  import ts from "typescript"
2
2
  import { bindingNames, importDeclarationNames, isFunctionLike, isLocalConst, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, loopDeclaresName, nearestFunction, nearestFunctionLike, referenceIdentifiers, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
3
3
  import { analyzeCollectionPipeline, isArrayFromCall } from "./collection-analysis.mjs"
4
+ import { compatibilityPackages } from "./compatibility-registry.mjs"
4
5
 
5
6
  export function createReactMigrationPass({ cloneAst, jsxTagName }) {
6
7
  function normalizeReactBootstrapLayout(sourceFile, factory, context) {
7
8
  const layouts = new Map()
8
9
  for (const statement of sourceFile.statements) {
9
- if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "react-bootstrap") continue
10
+ if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== compatibilityPackages.reactBootstrap) continue
10
11
  const clause = statement.importClause
11
12
  if (!clause || clause.name || !clause.namedBindings || ts.isNamespaceImport(clause.namedBindings)) throw sourceNodeError(statement, sourceFile, "React Bootstrap layout migration requires named Row or Col imports")
12
13
  for (const entry of clause.namedBindings.elements) {
@@ -52,7 +53,7 @@ export function createReactMigrationPass({ cloneAst, jsxTagName }) {
52
53
  }
53
54
  if (ts.isJsxSelfClosingElement(node) && layout(node.tagName)) return factory.updateJsxSelfClosingElement(node, factory.createIdentifier("div"), node.typeArguments, attributes(node.attributes))
54
55
  if (ts.isIdentifier(node) && layouts.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Bootstrap Row and Col imports may only be used as direct JSX elements")
55
- if (ts.isImportDeclaration(node) && !node.importClause?.isTypeOnly && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react-bootstrap") {
56
+ if (ts.isImportDeclaration(node) && !node.importClause?.isTypeOnly && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === compatibilityPackages.reactBootstrap) {
56
57
  const clause = node.importClause
57
58
  const bindings = clause?.namedBindings
58
59
  if (!clause || !bindings || !ts.isNamedImports(bindings)) return node
@@ -71,7 +72,7 @@ export function createReactMigrationPass({ cloneAst, jsxTagName }) {
71
72
  const aliases = new Map()
72
73
  const reactObjects = new Set()
73
74
  for (const statement of sourceFile.statements) {
74
- if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "react") continue
75
+ if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== compatibilityPackages.react) continue
75
76
  if (statement.importClause?.name) reactObjects.add(statement.importClause.name.text)
76
77
  const bindings = statement.importClause?.namedBindings
77
78
  if (bindings && ts.isNamespaceImport(bindings)) reactObjects.add(bindings.name.text)
@@ -257,7 +258,7 @@ export function createReactMigrationPass({ cloneAst, jsxTagName }) {
257
258
  const missing = [...required].filter(name => !imported.has(name)).sort()
258
259
  if (!missing.length) return normalizeReactBootstrapLayout(normalized, factory, context)
259
260
  for (const name of missing) {
260
- const collision = sourceFile.statements.some(statement => statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name) && statement.moduleSpecifier.text !== "react")
261
+ const collision = sourceFile.statements.some(statement => statementDeclaresName(statement, name) || ts.isImportDeclaration(statement) && importDeclarationNames(statement).includes(name) && statement.moduleSpecifier.text !== compatibilityPackages.react)
261
262
  if (collision) throw sourceNodeError(sourceFile, sourceFile, `React.${name} cannot be normalized because ${JSON.stringify(name)} is already declared`)
262
263
  }
263
264
  const declaration = factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports(missing.map(name => factory.createImportSpecifier(false, undefined, factory.createIdentifier(name))))), factory.createStringLiteral("react"))
@@ -1,5 +1,6 @@
1
1
  import ts from "typescript"
2
2
  import { bindingNames, functionVarDeclaresName, isFunctionLike, isLocalConst, isReferenceIdentifier, isShadowedIdentifier, loopDeclaresName, nearestFunction, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
3
+ import { compatibilityPackages } from "./compatibility-registry.mjs"
3
4
 
4
5
  export function createRouterPass({ withBase }) {
5
6
  return function normalizeReactRouterSyntax(sourceFile, factory, context, base, importedCollections = new Map()) {
@@ -9,7 +10,7 @@ export function createRouterPass({ withBase }) {
9
10
  const searchHooks = new Set()
10
11
  const navigateHooks = new Set()
11
12
  for (const statement of sourceFile.statements) {
12
- if ((ts.isExportDeclaration(statement) || ts.isImportDeclaration(statement)) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react-router-dom") {
13
+ if ((ts.isExportDeclaration(statement) || ts.isImportDeclaration(statement)) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === compatibilityPackages.reactRouter) {
13
14
  if (ts.isExportDeclaration(statement)) throw sourceNodeError(statement, sourceFile, "React Router exports are not supported; import Link directly where it renders")
14
15
  const clause = statement.importClause
15
16
  if (clause?.isTypeOnly) continue
@@ -306,7 +307,7 @@ export function createRouterPass({ withBase }) {
306
307
  if (ts.isIdentifier(node) && matchHooks.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useMatch imports may only initialize the supported top-level const binding")
307
308
  if (ts.isIdentifier(node) && searchHooks.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useSearchParams imports may only initialize the supported top-level tuple binding")
308
309
  if (ts.isIdentifier(node) && navigateHooks.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile)) throw sourceNodeError(node, sourceFile, "React Router useNavigate imports may only initialize the supported top-level navigate binding")
309
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "react-router-dom") {
310
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === compatibilityPackages.reactRouter) {
310
311
  const clause = node.importClause
311
312
  if (!clause || clause.isTypeOnly) return node
312
313
  const bindings = clause.namedBindings
@@ -8,6 +8,7 @@ import { normalizeEffectPrivateRefs } from "./effect-private-ref-pass.mjs"
8
8
  import { bindingNames, containsJsx, effectReturns, functionVarDeclaresName, importDeclarationNames, isFunctionLike, isLocalConst, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, isUnshadowedGlobal, nearestFunction, referenceIdentifiers, referencesIdentifier, sourceLocation, sourceNodeError, statementDeclaresName, unwrapExpression } from "./ast-helpers.mjs"
9
9
  import { normalizeMediaQueryExternalStores, normalizeNavigatorCapabilityConditions } from "./browser-signal-passes.mjs"
10
10
  import { analyzeCollectionPipeline, collectionExpression, collectionParameters, isArrayFromCall, mutatingCollectionMethods as mutatingListMethods, pureCollectionMathMethods as pureMathMethods, pureCollectionMethods as pureListMethods } from "./collection-analysis.mjs"
11
+ import { compatibilityPackages } from "./compatibility-registry.mjs"
11
12
  import { normalizeCustomHookTimerRefs } from "./custom-hook-timer-pass.mjs"
12
13
  import { captureNames, createDescriptorSession, createSemanticArtifact, nativeCaptureNames, referencedReducerDispatches, referencedStateNames } from "./descriptor-session.mjs"
13
14
  import { analyzeEffectDependencies, validateEffectOwnedBrowserResources } from "./effect-analysis.mjs"
@@ -170,7 +171,7 @@ function reachableSourceFiles(entries, sourceFiles, sourceIndex) {
170
171
  function normalizeClsxSyntax(sourceFile, factory, context) {
171
172
  const names = new Set()
172
173
  for (const statement of sourceFile.statements) {
173
- if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "clsx") continue
174
+ if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== compatibilityPackages.clsx) continue
174
175
  if (statement.importClause?.name) names.add(statement.importClause.name.text)
175
176
  const bindings = statement.importClause?.namedBindings
176
177
  if (bindings && ts.isNamedImports(bindings)) for (const entry of bindings.elements) if (!entry.isTypeOnly && (entry.propertyName ?? entry.name).text === "clsx") names.add(entry.name.text)
@@ -197,7 +198,7 @@ function normalizeClsxSyntax(sourceFile, factory, context) {
197
198
  const visitor = node => {
198
199
  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text) && !isShadowedIdentifier(node.expression, sourceFile)) return combine(node.arguments.map(lower))
199
200
  if (ts.isIdentifier(node) && names.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, sourceFile) && !(ts.isCallExpression(node.parent) && node.parent.expression === node)) throw sourceNodeError(node, sourceFile, "clsx imports may only be called directly")
200
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "clsx") {
201
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === compatibilityPackages.clsx) {
201
202
  const clause = node.importClause
202
203
  if (!clause || clause.isTypeOnly) return node
203
204
  let bindings = clause.namedBindings
@@ -403,7 +404,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
403
404
  for (const [name, binding] of packageBindings) {
404
405
  const references = referenceIdentifiers(sourceFile, name)
405
406
  const invalid = references.find(reference => !insideJsxEventHandler(reference, sourceFile) && !insideOwnedEffectCallback(reference, sourceFile))
406
- if (invalid && binding.target === "react-i18next" && binding.imported === "useTranslation") throw sourceNodeError(invalid, sourceFile, "React i18next useTranslation() depends on runtime locale resources; migrate build-known locales through getStaticPaths() and props, or browser-only locale reads through an owned effect")
407
+ if (invalid && binding.target === compatibilityPackages.reactI18next && binding.imported === "useTranslation") throw sourceNodeError(invalid, sourceFile, "React i18next useTranslation() depends on runtime locale resources; migrate build-known locales through getStaticPaths() and props, or browser-only locale reads through an owned effect")
407
408
  if (invalid) throw sourceNodeError(invalid, sourceFile, `Package import ${JSON.stringify(name)} may only be referenced directly inside JSX event handlers or owned effect setup/cleanup callbacks`)
408
409
  }
409
410
  const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && ["@kudzujs/core", "react"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
@@ -1,12 +1,13 @@
1
1
  import { relative, sep } from "node:path"
2
2
  import ts from "typescript"
3
3
  import { isReferenceIdentifier, isShadowedIdentifier, nearestFunction, sourceNodeError, unwrapExpression } from "./ast-helpers.mjs"
4
+ import { compatibilityPackages } from "./compatibility-registry.mjs"
4
5
 
5
6
  export function createZustandPass({ isSerializableStateLiteral, nativeCaptureNames, sourceDirectory }) {
6
7
  function analyzeZustandStores(sourceFile) {
7
8
  const createNames = new Set()
8
9
  for (const statement of sourceFile.statements) {
9
- if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== "zustand") continue
10
+ if (!ts.isImportDeclaration(statement) || statement.importClause?.isTypeOnly || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== compatibilityPackages.zustand) continue
10
11
  const bindings = statement.importClause?.namedBindings
11
12
  if (statement.importClause?.name || !bindings || !ts.isNamedImports(bindings)) throw sourceNodeError(statement, sourceFile, "Zustand migration input requires a named create import")
12
13
  for (const entry of bindings.elements) {
@@ -77,7 +78,7 @@ export function createZustandPass({ isSerializableStateLiteral, nativeCaptureNam
77
78
  function normalizeZustandMigrationSyntax(sourceFile, factory, context) {
78
79
  const stores = analyzeZustandStores(sourceFile)
79
80
  if (!stores.size) {
80
- const declaration = sourceFile.statements.find(statement => ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "zustand" && !statement.importClause?.isTypeOnly)
81
+ const declaration = sourceFile.statements.find(statement => ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === compatibilityPackages.zustand && !statement.importClause?.isTypeOnly)
81
82
  if (declaration) throw sourceNodeError(declaration, sourceFile, "Zustand create must directly initialize an exported const store")
82
83
  return sourceFile
83
84
  }
@@ -92,7 +93,7 @@ export function createZustandPass({ isSerializableStateLiteral, nativeCaptureNam
92
93
  factory.createStringLiteral(store.sourceKind)
93
94
  ]))
94
95
  }
95
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === "zustand") return undefined
96
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === compatibilityPackages.zustand) return undefined
96
97
  return ts.visitEachChild(node, visitor, context)
97
98
  }
98
99
  const normalized = ts.visitNode(sourceFile, visitor)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.8",
3
+ "version": "0.16.10",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",