@kudzujs/core 0.8.42 → 0.8.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This document is the source of truth for Kudzu's product direction, architecture invariants, and future development order. Read it before extending React-shaped syntax or browser capabilities.
4
4
 
5
- The executable post-`0.8.42` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
5
+ The executable post-`0.8.44` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
6
6
 
7
7
  [`GOAL_A.md`](./GOAL_A.md) and [`GOAL_B.md`](./GOAL_B.md) are completed capability-validation records. Their commerce and realtime dashboard fixtures prove general lifecycle, navigation, async-workflow, and Worker capabilities; they are not separate product verticals or future priority lists.
8
8
 
package/PERFORMANCE.md CHANGED
@@ -2,7 +2,15 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, and `npm run benchmark:module-cache` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
4
4
 
5
- ## Current 0.8.42 Release Snapshot
5
+ ## Current 0.8.44 Release Snapshot
6
+
7
+ Kudzu 0.8.44 changes compiler-only naming for action-private Context state and setters. The maintained Context browser fixture retains the same emitted concrete state operations and CRUD behavior while a same-named consumer local remains ordinary static content. No browser runtime module or public API changed, and no new performance claim is made.
8
+
9
+ ## Maintained 0.8.43 Release Snapshot
10
+
11
+ Kudzu 0.8.43 extends compiler-only callback/ref specialization from two to three direct component boundaries. The maintained browser fixture retains the same parent state operations, child state/effect/ref ownership, conditional cleanup, and static zero-JavaScript sibling while the added forwarding component is absent from emitted JavaScript. No browser runtime module or public API changed, and no new performance ranking is claimed.
12
+
13
+ ## Maintained 0.8.42 Optimization Snapshot
6
14
 
7
15
  Kudzu 0.8.42 retains the tracked 0.8.41 runtime and six-route commerce matrices below and adds two focused external-fixture optimizations. Those focused samples compare Kudzu before and after the patch; they do not establish a current cross-framework ranking.
8
16
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.8.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.8.42 - Large-route artifact sharing.** Byte-identical generated route entries now share one emitted file within a build, and a narrow read-only query form shape initializes hidden carry fields before deferred route capabilities arrive. Native document navigation remains the default after the measured static-catalog experiment lost on latency, transfer, and resilience. Read the [release notes](./RELEASES.md#0842---large-route-artifact-sharing), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.42), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.8.44 - Collision-free Context actions.** Context actions can now use Provider state and setters whose names also exist as ordinary consumer locals; Kudzu assigns compiler-owned aliases and still emits concrete state operations without a Context runtime. Read the [release notes](./RELEASES.md#0844---collision-free-context-actions), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.44), 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,67 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.44 - Collision-free Context actions
4
+
5
+ Kudzu 0.8.44 removes a compiler-only naming restriction from relative Context-hook actions: Provider state and setter fields needed privately by an action may now share names with ordinary consumer locals.
6
+
7
+ ### Changed in 0.8.44
8
+
9
+ - Action-required Provider state/setter fields that are not publicly selected receive compiler-owned collision-free aliases in the consumer destructuring.
10
+ - The specialized action AST and its state map use the same aliases before existing HandlerIR lowering.
11
+ - Alias selection checks component parameters and top-level declarations and increments the compiler-owned suffix when necessary.
12
+ - Consumer source keeps its chosen local names and public hook destructuring; no application rename or extra Context field selection is required.
13
+ - Provider state identity, direct state operations, and existing Context action diagnostics remain unchanged.
14
+
15
+ ### Output and performance
16
+
17
+ - Context action functions, Context objects, and Provider trees remain absent from browser output.
18
+ - Static siblings remain complete zero-JavaScript documents.
19
+ - No browser runtime module or public API changed. This release makes no new performance claim.
20
+
21
+ ### Validation
22
+
23
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 208 tests and 158 generated pages.
24
+ - The maintained Notes Context fixture now declares a consumer-local `setNotes` while create, rename, delete, select, list identity, and active-note updates continue to pass in Chrome.
25
+ - A focused counter fixture previously rejected for consumer `setCount` collision now builds successfully.
26
+ - Private Provider captures, hidden state pairs, and indirect action references remain source-diagnosed.
27
+
28
+ ### Upgrade
29
+
30
+ ```bash
31
+ npm install @kudzujs/core@^0.8.44
32
+ ```
33
+
34
+ ## 0.8.43 - Three-boundary callback ownership
35
+
36
+ Kudzu 0.8.43 extends ordinary React-shaped callback and ref composition through one more proven direct forwarding component without adding a callback registry, browser component tree, or runtime module.
37
+
38
+ ### Changed in 0.8.43
39
+
40
+ - A direct setter or inline/simple `const` callback may cross a child component and two additional direct forwarding components before one intrinsic handler invokes it.
41
+ - Each forwarding component must destructure the callback and pass it directly exactly once as an `on*` JSX prop.
42
+ - Callback substitution now occurs after recursive component expansion, so each authored forwarding boundary is validated before the original parent callback is specialized into the leaf handler.
43
+ - Parent SignalIR, child state, effects, deterministic IDs, and parent/child object refs retain their existing ownership through the deeper tree.
44
+ - A fourth callback boundary, aliases, spreads, intermediate adapters, repeated callback uses, and dynamic hook paths remain fail-closed.
45
+
46
+ ### Output and performance
47
+
48
+ - The forwarding components are specialized away and do not appear as retained browser functions.
49
+ - Static siblings remain complete zero-JavaScript documents.
50
+ - No browser runtime source or public API changed. This release makes no new build or cross-framework performance claim.
51
+
52
+ ### Validation
53
+
54
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 208 tests and 157 generated pages.
55
+ - Chrome coverage proves parent callback updates, leaf local state/effects/IDs, mounted refs, conditional cleanup, null refs after removal, fresh remount state, and prop synchronization across three boundaries.
56
+ - Compiler-result checks retain the original parent signal through imported specialization and prove every forwarding component is erased from emitted JavaScript.
57
+ - A dedicated four-boundary fixture fails at the authored source location.
58
+
59
+ ### Upgrade
60
+
61
+ ```bash
62
+ npm install @kudzujs/core@^0.8.43
63
+ ```
64
+
3
65
  ## 0.8.42 - Large-route artifact sharing
4
66
 
5
67
  Kudzu 0.8.42 reduces generated filesystem work for large static route sets and accelerates one proven hidden query form carry shape without adding a general runtime or changing the native-navigation default.
@@ -11,7 +11,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
11
11
  | C: state/resource model | Research only | Reduced fixtures expose a limitation |
12
12
  | D: routing compatibility | Current behavior preserved | Revisit only with migration evidence and invariant review |
13
13
 
14
- The active post-`0.8.42` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.12 validation, property-level object-state dependencies, the first direct two-boundary callback/ref slice, and the measured route-entry output optimization are complete; broader multi-boundary component/prop/callback/ref/context dataflow remains the next migration-backed investigation. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
14
+ The active post-`0.8.44` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.12 validation, property-level object-state dependencies, direct three-boundary callback/ref ownership, collision-free Context action-private state, and the measured route-entry output optimization are complete; broader prop, callback, ref, and Context dataflow remains migration-backed work. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
15
15
 
16
16
  ## Required Invariants
17
17
 
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.8.42` architecture, built on the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
3
+ This maps the current `0.8.44` architecture, built on the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
4
4
 
5
5
  ## Responsibility Map
6
6
 
@@ -17,7 +17,7 @@ This maps the current `0.8.42` architecture, built on the completed `0.8.23` Goa
17
17
  | Source-local binding index | [`framework/compiler/analysis/binding-index.mjs`](../../framework/compiler/analysis/binding-index.mjs) | After normalization, assigns deterministic lexical slots and classifies local, parameter, import, capture, global, and unresolved references. Native handler, effect, binding, list evaluator, optimized-command, and effect-resource consumers use complete index-owned AST; synthesized expressions retain the existing fallback. |
18
18
  | Pure collection language | [`framework/compiler/collection-analysis.mjs`](../../framework/compiler/collection-analysis.mjs) | Analyzes collection roots/selectors and serializes the allowed pure expression language used by lists and derived dependencies. |
19
19
  | Main semantic analysis | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `createKudzuTransformer()` | Produces transformed source plus explicit component, handler, binding, derived, keyed, and effect ownership results. |
20
- | Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Produces ComponentAnalysis v2 with ordered JSON-safe owner and specialization slots for state, setters, props, refs, IDs, direct SignalIR links, structural OwnerRefs, source-local SiteIds, and source provenance. One additional proven callback/ref forwarding component specializes into the same parent signal and intrinsic ownership; AST identity remains private to its source-local session. |
20
+ | Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Produces ComponentAnalysis v2 with ordered JSON-safe owner and specialization slots for state, setters, props, refs, IDs, direct SignalIR links, structural OwnerRefs, source-local SiteIds, and source provenance. Three direct callback/ref component boundaries specialize into the same parent signal and intrinsic ownership; Context action-private state fields receive collision-free consumer-local aliases; AST identity remains private to its source-local session. |
21
21
  | Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes ModuleIR v2 with deterministic SymbolRef, SignalIR, HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and ImportIR slots. One fail-closed boundary validates every source-local and component ownership edge before build-module generation. |
22
22
  | Route artifact graph | [`framework/compiler/route-build-record.mjs`](../../framework/compiler/route-build-record.mjs), `createRouteBuildRecord()`, `planRouteArtifacts()` | Validates each rendered route's RouteIR, capabilities, entry paths, styles, and exact handler/effect references. Handler modules, Workers, package-client modules, and bundle entry/chunk closure derive from structural edges without serialized output searches or formatted composite keys. |
23
23
  | Route contract validation | [`framework/compiler/route-ir.mjs`](../../framework/compiler/route-ir.mjs), `assertRouteIR()` | Fails before artifact selection for invalid state/parameter identity, commands, native/effect captures and dependencies, reactive descriptors, conditions, keyed-list identity/ownership, marker fields, or JSON safety. Immutable in-memory contracts validate once by identity. |
@@ -378,7 +378,7 @@ function increment(value) { setCount(value + 1) }; increment(count)
378
378
  After the relevant P0 foundations, investigate capabilities in this order:
379
379
 
380
380
  1. Property-level derived dependencies over ordinary object state. **Completed in `0.8.40`:** direct property paths and top-level immutable primitive locals over object state reuse tagged DerivedIR, subscribe to the source signal, and compare selected values with `Object.is`; whole-object and dynamic dependencies remain rejected.
381
- 2. Multi-boundary component/prop/callback/ref/context dataflow. **In progress after `0.8.42`:** one additional direct setter-callback/ref forwarding component preserves the parent SignalIR and final intrinsic ownership; `0.8.42` separately completed measured route-entry sharing and hidden query carry without broadening this dataflow scope. Broader prop, callback, ref, and Context graphs remain migration-led work.
381
+ 2. Multi-boundary component/prop/callback/ref/context dataflow. **Three-boundary callback/ref ownership completed in `0.8.43`; collision-free Context action-private state completed in `0.8.44`:** forwarding preserves parent SignalIR and Context action lowering uses compiler-owned aliases when consumer locals reuse Provider state/setter names. A fourth callback boundary remains fail-closed. Broader prop, callback, ref, and Context graphs remain migration-led work.
382
382
  3. Package-neutral shared state/actions and migration of current Zustand internals.
383
383
  4. Browser-only package imports in owned effect/resource modules.
384
384
  5. ResourceIR from at least two independent WebSocket/SSE/SDK fixtures with the same semantics.
@@ -480,4 +480,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
480
480
 
481
481
  ## Immediate Decision
482
482
 
483
- PR 1 through PR 12, the `0.8.40` property-dependency slice, the `0.8.41` direct multi-boundary callback/ref slice, and the `0.8.42` measured route-output optimization are complete. Continue **multi-boundary component/prop/callback/ref/context dataflow** from the next real migration blocker. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
483
+ PR 1 through PR 12, the `0.8.40` property-dependency slice, the `0.8.41` and `0.8.43` direct multi-boundary callback/ref slices, the `0.8.44` Context alias slice, and the `0.8.42` measured route-output optimization are complete. Continue broader **component/prop/callback/ref/context dataflow** only from the next real migration blocker. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
@@ -1,6 +1,6 @@
1
1
  # Planned Version Sequence
2
2
 
3
- This is an execution sequence, not release history. `0.8.16` through `0.8.42` are completed scopes represented by package/release records.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.44` are completed scopes represented by package/release records.
4
4
 
5
5
  Keep each patch behavior-preserving and independently reviewable. If a boundary proves inseparable, revise this plan before combining releases; do not silently broaden a patch.
6
6
 
@@ -33,6 +33,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
33
33
  | `0.8.40` | Add property-level derived effect dependencies over ordinary object state. | Direct property paths and immutable primitive locals reuse existing DerivedIR and `Object.is`; whole-object/dynamic dependencies fail, runtime JavaScript is unchanged, and measured build/RSS changes remain below 5%. |
34
34
  | `0.8.41` | Preserve callback, setter, and ref ownership through one additional direct presentation-component boundary. | Parent signals, child hooks, IDs, effects, refs, conditional cleanup, static zero-JavaScript output, and byte-identical deploy graphs pass; a third callback boundary fails closed. |
35
35
  | `0.8.42` | Share byte-identical generated route entries and accelerate one proven read-only query form carry shape. | A static-path fixture emits one native/effect file for three routes; hidden GET carry initializes before deferred parameter ESM; static siblings remain zero-JavaScript; the losing catalog navigation experiment is excluded. |
36
+ | `0.8.43` | Preserve callback, setter, and ref ownership through a third direct component boundary. | Parent SignalIR, child state/effects/IDs, object refs, conditional cleanup, fresh remount, and static zero-JavaScript output pass in Chrome; a fourth callback boundary fails closed. |
37
+ | `0.8.44` | Remove consumer-local name collisions from specialized Context actions. | Action-private Provider state/setter fields receive deterministic collision-free aliases; CRUD browser behavior, direct state operations, diagnostics, and zero Context runtime output pass. |
36
38
 
37
39
  ## Sequence Rules
38
40
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Kudzu specializes ordinary common React-shaped TSX so migrations need minimal source restructuring. Declarative components, collection pipelines, conditions, hooks, and handlers should be lowered at build time rather than replaced with application-owned imperative DOM code. This principle applies across migrations and is not Stay-specific; it does not imply a React package, VDOM, hydration, or ecosystem runtime.
4
4
 
5
- Migration source may retain conventional `react` imports for supported named or aliased hooks, direct members such as `React.useState`, same-file `memo`, inline `useCallback`, direct-state expression or analyzable collection-pipeline `useMemo`, direct intrinsic `forwardRef`, top-level `const` identifiers initialized by `useId()`, and default, namespace, or named `Fragment`. A named or default zero-argument custom hook imported from a relative TypeScript module may return direct shorthand state/setter pairs and callbacks that capture those states, including a callback that resets multiple states to direct primitive literals; callers use one top-level `const` object destructuring without aliases, defaults, or rest. Kudzu's JSX declarations accept ReactNode-shaped component returns and contextually type common intrinsic DOM events, so strict React component props do not need migration-only `unknown` or explicit event annotations. `forwardRef()` accepts one inline synchronous `(props, ref)` function and requires the object ref exactly once on its direct intrinsic root; the compiler removes `ref` from props/rest and erases the wrapper. `useId()` becomes a deterministic build-time HTML ID and emits no browser capability; keyed rows reject it because cloned row templates cannot safely duplicate HTML IDs. Collection memos may start from local array state or a named relative import of an exported JSON-safe `const` array, including type-only `as const` and `satisfies` wrappers, and may read direct local state declared in their dependency array. `compiler/react-migration-pass.mjs` canonicalizes those forms and rewrites module references to `@kudzujs/core` before build-time evaluation. Memo wrappers are erased or inlined into existing bindings and keyed-list selectors because no browser component rerender or memo cache exists. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports.
5
+ Migration source may retain conventional `react` imports for supported named or aliased hooks, direct members such as `React.useState`, same-file `memo`, inline `useCallback`, direct-state expression or analyzable collection-pipeline `useMemo`, direct intrinsic `forwardRef`, top-level `const` identifiers initialized by `useId()`, and default, namespace, or named `Fragment`. A named or default zero-argument custom hook imported from a relative TypeScript module may return direct shorthand state/setter pairs and callbacks that capture those states, including a callback that resets multiple states to direct primitive literals; callers use one top-level `const` object destructuring without aliases, defaults, or rest. Relative Context-hook actions may require exposed Provider state/setter fields without forcing those hidden fields into public consumer destructuring; compiler-owned collision-free aliases preserve same-named consumer locals. Kudzu's JSX declarations accept ReactNode-shaped component returns and contextually type common intrinsic DOM events, so strict React component props do not need migration-only `unknown` or explicit event annotations. `forwardRef()` accepts one inline synchronous `(props, ref)` function and requires the object ref exactly once on its direct intrinsic root; the compiler removes `ref` from props/rest and erases the wrapper. `useId()` becomes a deterministic build-time HTML ID and emits no browser capability; keyed rows reject it because cloned row templates cannot safely duplicate HTML IDs. Collection memos may start from local array state or a named relative import of an exported JSON-safe `const` array, including type-only `as const` and `satisfies` wrappers, and may read direct local state declared in their dependency array. `compiler/react-migration-pass.mjs` canonicalizes those forms and rewrites module references to `@kudzujs/core` before build-time evaluation. Memo wrappers are erased or inlined into existing bindings and keyed-list selectors because no browser component rerender or memo cache exists. Static routes remain JavaScript-free and emitted modules are checked for surviving React imports.
6
6
 
7
7
  Compilation begins from page entries and follows relative runtime imports, re-exports, and validated Worker references; unreachable TypeScript migration files are not transformed. Direct maps over imported immutable JSON-safe arrays fold to literals for zero-JavaScript static rows. Synchronous relative calculation functions may return objects whose direct static fields feed reactive JSX bindings; build rendering uses current signal values and route-specific binding ESM reevaluates the same helper after state commits. One direct array field may instead feed a keyed intrinsic map: its evaluator refreshes a compiler-owned array anchor before the existing list reconciler runs, preserving keyed DOM and SVG identity without a calculation runtime. That field must remain a JSON-safe array after every source-state commit. Package imports have a separate narrow boundary: direct references inside intrinsic JSX event callbacks are erased from build modules and bundled into route handler ESM, while render-time, effect, helper-indirect, and mixed package use fails.
8
8
 
@@ -86,7 +86,7 @@ Rendered collection selectors compile immutable local aliases and inline `(item)
86
86
 
87
87
  The reduced `useReducer` form reuses ordinary state slots and React's pure reducer contract. An optional inline, same-file, or relative-imported synchronous one-parameter initializer may derive a directly serializable literal only from its directly serializable initial argument; the compiler substitutes that argument and lowers the call to the ordinary two-argument ownership path. A direct dispatch in a compiled handler becomes a functional `set` whose reducer is bundled from a relative TypeScript module into that handler graph. Pure reducer-owned keyed lists reuse unchanged item identities for reorder, one removal, and append fast paths; ordinary `useState` lists retain full validation. One direct dispatch prop into a same-file or relative-imported synchronous component, including a direct keyed row, is specialized to intrinsic JSX at the call site, so its handler retains the parent reducer scope and no dispatch capture or child handler asset is emitted. A reducer row reads the latest item through the existing list scope and uses the same multiple serializable state, effect, condition, and object-ref specialization as other keyed rows. Relative TypeScript imports referenced inside that child handler receive collision-free call-site aliases and join the parent handler graph. One nested relative-imported intrinsic child may receive an inline or simple `const` callback containing dispatch; the compiler recursively substitutes that callback once and omits the nested child handler asset. Missing directly serializable literal defaults and direct intrinsic rest props in these reducer specializations are substituted at the same call site. Reducer-free routes and shared runtimes are unchanged; no reducer runtime or browser component instance exists.
88
88
 
89
- A direct setter or inline/simple `const` setter callback may cross one same-file or relative-imported component boundary and one additional direct forwarding component before an intrinsic handler invokes it exactly once. Value adapters such as `event => onValueChange(event.currentTarget.value)` specialize into the parent setter graph instead of serializing a function. The intermediate component must destructure and directly forward the callback once as an `on*` JSX prop; third-boundary forwarding, aliases, spreads, intermediate adapters, and repeated uses remain unsupported. Specialized children may own directly serializable `useState()` values or initialize string state with one direct primitive state prop's zero-argument `.toString()`, plus `useId()` values, supported effects, and object refs initialized with `null`. Same-file and relative-imported presentation components recursively specialize away; nested hooks join the same generated owner only on unconditional or statically truthy paths. A parent-owned `null`-initialized object ref may follow the proven tree to the direct intrinsic root. These remain compiler-owned descriptors: conditional removal drops the handler with the element, deletes child state, resolves refs to `null`, and runs effect cleanup, while remount creates fresh ownership and dependency effects synchronize prop-derived state without retaining a component instance.
89
+ A direct setter or inline/simple `const` setter callback may cross one same-file or relative-imported component boundary and two additional direct forwarding components before an intrinsic handler invokes it exactly once. Value adapters such as `event => onValueChange(event.currentTarget.value)` specialize into the parent setter graph instead of serializing a function. Each forwarding component must destructure and directly forward the callback once as an `on*` JSX prop; fourth-boundary forwarding, aliases, spreads, intermediate adapters, and repeated uses remain unsupported. Specialized children may own directly serializable `useState()` values or initialize string state with one direct primitive state prop's zero-argument `.toString()`, plus `useId()` values, supported effects, and object refs initialized with `null`. Same-file and relative-imported presentation components recursively specialize away; nested hooks join the same generated owner only on unconditional or statically truthy paths. A parent-owned `null`-initialized object ref may follow the proven tree to the direct intrinsic root. These remain compiler-owned descriptors: conditional removal drops the handler with the element, deletes child state, resolves refs to `null`, and runs effect cleanup, while remount creates fresh ownership and dependency effects synchronize prop-derived state without retaining a component instance.
90
90
 
91
91
  `kudzu.config` may opt one emitted shared-layout group into same-document navigation with legacy `navigation: { routes: ["/product", "/items/[id]"] }`, or multiple groups with `navigation: { groups: [{ routes: [...] }, { routes: [...] }] }`. The forms are mutually exclusive. Identities are globally unique emitted exact paths or `runtimeParams` patterns; each group uses one page-exported layout function identity. Runtime records securely match concrete pathnames under `base`, and their cache-safe parameter initializer runs before route DOM/effects mount on every transition. Each group receives a deterministic route-hashed asset specialized to only its records, pattern decoder, and effect/parameter lifecycle needs. Cross-group and ungrouped anchors remain native and are not prefetched; overlapping path domains across groups fail the build. Route effect entries export cache-safe layout and route mount functions: layout effects, including conditional/keyed DOM-owned effects, persist for the group session; route effects receive a fresh owner registry after each route insertion; and non-persisted page disposal cleans route before layout. Direct primitive state, runtime parameter, and keyed-item property dependencies and cleanup are supported. Fragment payloads and coordinated View Transitions are not implemented.
92
92
 
@@ -548,22 +548,33 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
548
548
  if (callback) for (const state of referencedStateNames(callback.body, hook.states, callback)) requiredContextStates.add(state)
549
549
  }
550
550
  }
551
+ const contextSubstitutions = new Map()
551
552
  for (const [setter, state] of hook.states) {
552
553
  if (hook.context) {
553
554
  if (names.has(setter) && !names.has(state)) throw sourceNodeError(node.name, sourceFile, `Relative Context setter ${JSON.stringify(setter)} requires state ${JSON.stringify(state)} to be destructured`)
554
555
  if (!names.has(state) && !requiredContextStates.has(state)) continue
555
- const localSetter = names.has(setter) || requiredContextStates.has(state) ? setter : `__kContextState_${state}`
556
- setters.set(localSetter, state)
557
- registerState(owner, state, localSetter, "context", node, { owner: hook.stateOwner, state: hook.stateSymbols.get(state) })
556
+ const privateFields = customHookPrivateFields.get(node) ?? []
557
+ const localName = field => {
558
+ if (names.has(field)) return field
559
+ const occupied = name => owner.parameters.some(parameter => bindingNames(parameter.name).includes(name)) || owner.body.statements.some(statement => statement !== node.parent.parent && statementDeclaresName(statement, name))
560
+ if (!occupied(field)) return field
561
+ let index = 0
562
+ let local
563
+ do local = `__kContext_${field}${index++ || ""}`
564
+ while (occupied(local))
565
+ return local
566
+ }
567
+ const localState = localName(state)
568
+ const localSetter = localName(setter)
569
+ setters.set(localSetter, localState)
570
+ registerState(owner, localState, localSetter, "context", node, { owner: hook.stateOwner, state: hook.stateSymbols.get(state) })
558
571
  if (requiredContextStates.has(state)) {
559
- const fields = customHookPrivateFields.get(node) ?? []
560
- for (const field of [state, setter]) {
561
- if (names.has(field) || fields.includes(field)) continue
562
- const conflict = owner.parameters.some(parameter => bindingNames(parameter.name).includes(field)) || owner.body.statements.some(statement => statement !== node.parent.parent && statementDeclaresName(statement, field))
563
- if (conflict) throw sourceNodeError(node.name, sourceFile, `Context action state field ${JSON.stringify(field)} conflicts with a consumer binding`)
564
- fields.push(field)
572
+ for (const [field, local] of [[state, localState], [setter, localSetter]]) {
573
+ if (names.has(field) || privateFields.some(entry => (typeof entry === "string" ? entry : entry.property) === field)) continue
574
+ privateFields.push({ property: field, local })
575
+ if (field !== local) contextSubstitutions.set(field, factory.createIdentifier(local))
565
576
  }
566
- customHookPrivateFields.set(node, fields)
577
+ customHookPrivateFields.set(node, privateFields)
567
578
  }
568
579
  continue
569
580
  }
@@ -584,12 +595,14 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
584
595
  settersByFunction.set(owner, setters)
585
596
  for (const name of names) {
586
597
  if (hook.callbacks.has(name)) {
598
+ const callback = contextSubstitutions.size ? substituteClone(hook.callbacks.get(name), contextSubstitutions, factory, context) : hook.callbacks.get(name)
587
599
  const callbacks = customHookFunctionsByOwner.get(owner) ?? new Map()
588
- callbacks.set(name, hook.callbacks.get(name))
600
+ callbacks.set(name, callback)
589
601
  customHookFunctionsByOwner.set(owner, callbacks)
590
602
  if (hook.context) {
591
603
  const reducers = reducersByFunction.get(owner) ?? new Map()
592
- reducers.set(name, { contextAction: hook.callbacks.get(name), states: hook.states })
604
+ const states = new Map([...hook.states].map(([setter, state]) => [contextSubstitutions.get(setter)?.text ?? setter, contextSubstitutions.get(state)?.text ?? state]))
605
+ reducers.set(name, { contextAction: callback, states })
593
606
  reducersByFunction.set(owner, reducers)
594
607
  }
595
608
  }
@@ -1027,7 +1040,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1027
1040
  if (references.length !== 1) fail(element, `Setter-callback prop ${JSON.stringify(prop)} must be used exactly once in the component`)
1028
1041
  }
1029
1042
  }
1030
- const expandSetterComponents = (root, componentSource, trail, aggregate, parentSetters, parentStateOwners, callbackDepth = 1) => {
1043
+ const expandSetterComponents = (root, componentSource, trail, aggregate, parentSetters, parentStateOwners, callbackDepth = 2) => {
1031
1044
  root = foldSetterStaticConditions(root)
1032
1045
  const replacements = new WeakMap()
1033
1046
  let count = 0
@@ -1066,24 +1079,24 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1066
1079
  const callbackProps = jsxSetterCallbackProps(node, setters, functionsForNode(node), reducersForNode(node, reducersByFunction))
1067
1080
  const callbackSubstitutions = new Map()
1068
1081
  if (callbackProps.length) {
1069
- if (!callbackDepth) fail(node, "Setter callbacks cannot cross more than two component boundaries")
1082
+ if (!callbackDepth) fail(node, "Setter callbacks cannot cross more than three component boundaries")
1070
1083
  const attributes = ts.isJsxElement(node) ? node.openingElement.attributes : node.attributes
1071
1084
  for (const prop of callbackProps) {
1072
1085
  const attribute = attributes.properties.find(entry => ts.isJsxAttribute(entry) && entry.name.text === prop)
1073
1086
  const value = attribute?.initializer && ts.isJsxExpression(attribute.initializer) ? unwrapExpression(attribute.initializer.expression) : undefined
1074
- if (!value || !ts.isIdentifier(value)) fail(attribute ?? node, "A second-boundary setter callback must be forwarded directly as one JSX event prop")
1087
+ if (!value || !ts.isIdentifier(value)) fail(attribute ?? node, "A nested setter callback must be forwarded directly as one JSX event prop")
1075
1088
  const callback = functionsForNode(value).get(value.text)
1076
1089
  if (callback) callbackSubstitutions.set(value.text, callback)
1077
1090
  }
1078
1091
  validateSetterCallbackProps(node, component, callbackProps)
1079
1092
  }
1080
1093
  const nested = specialize(node, component, "Nested setter-callback", true, true, new Set(setters.values()), { setters, stateOwners })
1094
+ if (dynamic && (nested.hookDeclarations.length || nested.effects.length)) fail(node, "Hookful nested setter-callback components require an unconditional or statically truthy render path")
1095
+ nested.root = expandSetterComponents(nested.root, component.getSourceFile(), [...trail, component], nested, setters, stateOwners, callbackDepth - Boolean(callbackProps.length))
1081
1096
  if (callbackSubstitutions.size) {
1082
1097
  nested.root = substituteClone(nested.root, callbackSubstitutions, factory, context)
1083
1098
  for (const effect of nested.effects) effect.call = substituteClone(effect.call, callbackSubstitutions, factory, context)
1084
1099
  }
1085
- if (dynamic && (nested.hookDeclarations.length || nested.effects.length)) fail(node, "Hookful nested setter-callback components require an unconditional or statically truthy render path")
1086
- nested.root = expandSetterComponents(nested.root, component.getSourceFile(), [...trail, component], nested, setters, stateOwners, callbackDepth - Boolean(callbackProps.length))
1087
1100
  if (imported) synthesizeTree(nested.root = mergeSpecializedImports(nested.root, component.getSourceFile(), node, nested.effects))
1088
1101
  aggregate.calculations.push(...nested.calculations)
1089
1102
  aggregate.effects.push(...nested.effects)
@@ -1549,7 +1562,9 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1549
1562
  const privateFields = customHookPrivateFields.get(node)
1550
1563
  return factory.updateVariableDeclaration(node, factory.updateObjectBindingPattern(node.name, [
1551
1564
  ...node.name.elements,
1552
- ...privateFields.map(name => factory.createBindingElement(undefined, undefined, name))
1565
+ ...privateFields.map(entry => typeof entry === "string"
1566
+ ? factory.createBindingElement(undefined, undefined, entry)
1567
+ : factory.createBindingElement(undefined, entry.property === entry.local ? undefined : entry.property, entry.local))
1553
1568
  ]), node.exclamationToken, node.type, node.initializer)
1554
1569
  }
1555
1570
  if (ts.isBlock(node) && setterHookHelpers.has(node)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.42",
3
+ "version": "0.8.44",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",