@kudzujs/core 0.8.57 → 0.8.58

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.57` 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.58` 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
 
@@ -148,6 +148,7 @@ This queue orders the next investigations by general migration value. Start only
148
148
  - Context actions inline into existing route handler ESM and compile to concrete state operations. Private captures, dynamic Provider objects, and multiple Provider implementations remain diagnosed; no browser Context tree, callback registry, or shared runtime was added.
149
149
  - In `0.8.46`, the same Notes migration may omit action-only setters from its public Context type and authored Provider value when the corresponding state remains exposed. Kudzu restores those setters only in build scratch so existing concrete state operations, RouteIR signals, CRUD behavior, and zero-Context runtime output remain unchanged. Fully hidden action state and exposed setters without state remain diagnosed.
150
150
  - In `0.8.47`, a specialized setter-callback child may initialize local state directly from one primitive parent state prop. Build scratch reads the parent signal's build value while retaining the structural prop signal for handlers/effects. `0.8.57` extends that exact path to a parent state authored with one JSON-safe plain-object literal; independent child draft state and parent commit behavior pass in the FIRE-derived callback fixture. Array props, aliases, property paths, composed expressions, and object `.toString()` remain diagnosed.
151
+ - In `0.8.58`, a direct keyed item prop may initialize one row-owned object draft. Retained keys preserve that draft and DOM identity through reorder; removal releases it, and re-adding the key clones the current item into fresh state. Aliases, property paths, composed expressions, and indirect item props remain diagnosed.
151
152
  - In `0.8.48`, one setter-callback prop may be called once from each of multiple direct intrinsic event handlers in the specialized leaf. Each call lowers independently to the same parent signal; forwarding fan-out, aliases, non-handler uses, and repeated calls inside one handler remain diagnosed, and no callback registry or handler runtime is added.
152
153
  - In `0.8.49`, one component may forward the same destructured setter callback directly through multiple child component `on*` props. Each branch specializes independently to the original parent signal within the existing three-boundary limit; aliases, ordinary props, spreads, and a fourth boundary remain diagnosed.
153
154
 
package/PERFORMANCE.md CHANGED
@@ -6,7 +6,11 @@ Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm ru
6
6
 
7
7
  The maintained 2026-08-13 comparison used Node 24.14.0 and an Intel Core i5-9500 Linux x64 host, one warm-up, and seven alternating fresh-process samples against clean `v0.8.44`. A narrow fast path skips Kudzu semantic transformation for 450 plain `.ts` modules whose runtime edges are exclusively resolvable relative TypeScript imports or exports; all other modules retain the existing transformer. Compile median fell from 2,323.9 ms to 1,413.2 ms (39.2%) and clean-build median from 3,325.3 ms to 2,382.4 ms (28.4%); every paired sample improved. Compile peak-RSS median fell from 571.2 MiB to 552.6 MiB, while build peak RSS was 570.9 MiB versus 568.8 MiB. Compiler scratch fell from 7,328,390 to 1,971,061 bytes. Both targets emitted the same 50 static HTML files, 10,980 bytes, and deploy SHA-256 `e107d78a7f55bc8a1af0ea6e53efeffa19b3d44d21c892484d103fa346e7ba7b`. This is a source-scale compiler comparison, not a cross-framework result.
8
8
 
9
- ## Current 0.8.57 Release Snapshot
9
+ ## Current 0.8.58 Release Snapshot
10
+
11
+ Kudzu 0.8.58 adds one narrow keyed-item initializer descriptor to the existing complex row-state path. Routes without that descriptor, including static siblings, retain their existing capability output. This release makes no timing claim.
12
+
13
+ ## Maintained 0.8.57 Release Snapshot
10
14
 
11
15
  Kudzu 0.8.57 broadens an existing compiler proof from direct primitive parent state to direct JSON-safe plain-object parent state for specialized child draft initialization. It adds no browser runtime code and makes no timing claim.
12
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.57 - Plain-object prop draft state.** Specialized setter-callback children can initialize independent local draft state from one direct parent state prop authored with a JSON-safe plain-object literal, without new IR or runtime code. Read the [release notes](./RELEASES.md#0857---plain-object-prop-draft-state), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.57), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.8.58 - Keyed item draft state.** Keyed rows can initialize independent object draft state directly from their item prop, retaining it through reorder and recreating it after removal. Read the [release notes](./RELEASES.md#0858---keyed-item-draft-state), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.58), 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,36 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.58 - Keyed item draft state
4
+
5
+ Kudzu 0.8.58 lets a keyed row initialize independent local object draft state directly from its keyed item prop.
6
+
7
+ ### Changed in 0.8.58
8
+
9
+ - `const [draft, setDraft] = useState(todo)` compiles when `todo` is the direct item prop of a keyed row.
10
+ - RouteIR marks the state with a narrow `list-item` initializer descriptor.
11
+ - New keys clone the current item into existing key-scoped row-state ownership without a component runtime.
12
+ - Retained keys preserve their draft state and DOM identity through item updates and reorder.
13
+
14
+ ### Boundaries
15
+
16
+ - Removing a key releases its draft; re-adding that key creates a fresh draft from the current item.
17
+ - Aliases, property paths, composed expressions, and indirect item props remain rejected.
18
+ - This does not add prop-to-state synchronization, array prop drafts, a VDOM, hydration, or a retained component tree.
19
+ - Static sibling routes remain complete HTML with zero JavaScript.
20
+
21
+ ### Validation
22
+
23
+ - The Todo reducer fixture proves independent row drafts, retained draft and DOM identity through reorder, exact removal, and fresh remount initialization.
24
+ - A dedicated alias fixture remains fail-closed with a source-located diagnostic.
25
+ - RouteIR records the `list-item` initializer and capability planning emits the existing complex row-state path only where required.
26
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 224 tests and 173 generated pages.
27
+
28
+ ### Upgrade
29
+
30
+ ```sh
31
+ npm install @kudzujs/core@^0.8.58
32
+ ```
33
+
3
34
  ## 0.8.57 - Plain-object prop draft state
4
35
 
5
36
  Kudzu 0.8.57 lets a specialized setter-callback child initialize independent local draft state directly from one parent state prop authored with a JSON-safe plain-object literal.
@@ -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.57` 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, direct primitive and plain-object prop state initialization, repeated direct leaf-handler callback use, direct child callback fan-out, collision-free and private Context action setters, package-neutral shared-state/action IR, browser-only package imports and private mutable refs in owned effects, route/layout CSS closure, structural per-route capability/chunk reporting, signature-keyed runtime families, incremental source and affected-route development builds, measured route-entry output optimization, and the plain TypeScript source-scale fast path are complete. ResourceIR remains research for cross-owner transports and subscriptions; array prop drafts, range ownership, virtualization, and optimistic transactions still require independent fixtures. 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.58` 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, direct primitive, plain-object prop, and keyed-item draft state initialization, repeated direct leaf-handler callback use, direct child callback fan-out, collision-free and private Context action setters, package-neutral shared-state/action IR, browser-only package imports and private mutable refs in owned effects, route/layout CSS closure, structural per-route capability/chunk reporting, signature-keyed runtime families, incremental source and affected-route development builds, measured route-entry output optimization, and the plain TypeScript source-scale fast path are complete. ResourceIR remains research for cross-owner transports and subscriptions; array prop drafts, range ownership, virtualization, and optimistic transactions still require independent fixtures. 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.57` 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.58` 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
 
@@ -379,7 +379,7 @@ function increment(value) { setCount(value + 1) }; increment(count)
379
379
  After the relevant P0 foundations, investigate capabilities in this order:
380
380
 
381
381
  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.
382
- 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`; action-only Provider setter exposure removed in `0.8.46`; direct primitive prop state initialization completed in `0.8.47`; repeated direct leaf-handler callback use completed in `0.8.48`; direct child callback fan-out completed in `0.8.49`; direct plain-object prop state initialization completed in `0.8.57`:** forwarding preserves parent SignalIR, Context action lowering uses compiler-owned aliases when consumer locals reuse Provider state/setter names, action-required setters may remain compiler-only when their state is publicly exposed, specialized children may seed local state from a direct parent signal authored with a serializable primitive or plain-object literal, and one callback may branch through multiple component `on*` props and intrinsic handlers. The FIRE-derived object editor proves independent child draft updates and parent commit behavior without new IR or runtime code. A fourth callback boundary, callback aliases/non-handler uses, fully hidden Context state, array prop initializers, aliases, property paths, and composed expressions remain fail-closed. Broader prop, callback, ref, and Context graphs remain migration-led work.
382
+ 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`; action-only Provider setter exposure removed in `0.8.46`; direct primitive prop state initialization completed in `0.8.47`; repeated direct leaf-handler callback use completed in `0.8.48`; direct child callback fan-out completed in `0.8.49`; direct plain-object prop state initialization completed in `0.8.57`; direct keyed item draft initialization completed in `0.8.58`:** forwarding preserves parent SignalIR, Context action lowering uses compiler-owned aliases when consumer locals reuse Provider state/setter names, action-required setters may remain compiler-only when their state is publicly exposed, specialized children may seed local state from a direct parent signal authored with a serializable primitive or plain-object literal, keyed rows may seed object draft state from their direct item prop, and one callback may branch through multiple component `on*` props and intrinsic handlers. The Todo reducer fixture proves independent key-scoped drafts, retained identity through reorder, exact release, and fresh item initialization on remount. A fourth callback boundary, callback aliases/non-handler uses, fully hidden Context state, array prop initializers, keyed item aliases, property paths, and composed expressions remain fail-closed. Broader prop, callback, ref, and Context graphs remain migration-led work.
383
383
  3. Package-neutral shared state/actions and migration of current Zustand internals. **Completed in `0.8.50`:** Zustand source normalization produces one generic shared-state adapter descriptor; selectors and handlers register JSON-safe SharedStateIR/SharedActionIR records, handler lowering consumes package-neutral actions, and existing RouteIR, layout ownership, same-turn updates, navigation persistence, and browser output remain unchanged. Redux/RTK and public adapter APIs remain unsupported.
384
384
  4. Browser-only package imports in owned effect/resource modules. **Completed in `0.8.51` for effects:** direct package references in inline effect setup/cleanup callbacks use existing package import records and route-owned effect ESM bundling; build-time component modules and static siblings omit the package. Helper-indirect, render-time, dynamic-import, and ResourceIR package graphs remain unsupported.
385
385
  5. ResourceIR from at least two independent WebSocket/SSE/SDK fixtures with the same semantics. **Completed in `0.8.52` without ResourceIR for private ownership:** the E2B terminal and route-owned WebSocket fixtures lower refs used exclusively by one inline effect to invocation-private closure objects, while existing effect ownership supplies replacement, cleanup, stale setter invalidation, navigation, and BFCache disposal. ResourceIR remains unapproved and now requires independent cross-owner transport/subscription fixtures that cannot fit this narrower model.
@@ -481,4 +481,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
481
481
 
482
482
  ## Immediate Decision
483
483
 
484
- 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, the `0.8.46` action-only Provider setter slice, the `0.8.47` direct primitive and `0.8.57` plain-object prop initializer slices, the `0.8.48` repeated direct leaf-handler callback slice, the `0.8.49` direct child callback fan-out slice, the `0.8.50` package-neutral shared-state/action slice, the `0.8.51` owned-effect package import slice, the `0.8.52` effect-private mutable-ref slice, the `0.8.53` route/layout CSS closure slice, the `0.8.54` structural per-route capability/chunk report, the `0.8.55` signature-keyed runtime families, item 7 incremental source and affected-route builds, and the `0.8.42` measured route-output optimization are complete. Continue migration-led semantic generalization; keep array prop drafts and ResourceIR limited to independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
484
+ 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, the `0.8.46` action-only Provider setter slice, the `0.8.47` direct primitive, `0.8.57` plain-object prop, and `0.8.58` direct keyed item initializer slices, the `0.8.48` repeated direct leaf-handler callback slice, the `0.8.49` direct child callback fan-out slice, the `0.8.50` package-neutral shared-state/action slice, the `0.8.51` owned-effect package import slice, the `0.8.52` effect-private mutable-ref slice, the `0.8.53` route/layout CSS closure slice, the `0.8.54` structural per-route capability/chunk report, the `0.8.55` signature-keyed runtime families, item 7 incremental source and affected-route builds, and the `0.8.42` measured route-output optimization are complete. Continue migration-led semantic generalization; keep array prop drafts and ResourceIR limited to 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.57` are completed scopes represented by package/release records.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.58` 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
 
@@ -48,6 +48,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
48
48
  | `0.8.55` | Emit deduplicated signature-keyed runtime families while preserving navigation-group singleton ownership. | Equal standalone signatures share files, different signatures isolate output, grouped routes share one union family, and static routes emit no runtime. |
49
49
  | `0.8.56` | Recompile and rerender only source-affected routes during development. | A route-owned helper edit recompiles two of four modules, rerenders one of two pages, and emits byte-identical deploy output to a fresh full build. |
50
50
  | `0.8.57` | Initialize specialized child draft state from one direct parent plain-object state prop. | A FIRE-derived editor preserves independent child draft state and parent commit behavior; arrays, aliases, property paths, and composed expressions remain rejected without runtime changes. |
51
+ | `0.8.58` | Initialize keyed row object draft state from the direct keyed item prop. | Todo reducer rows retain independent drafts and DOM identity across reorder, release state on removal, recreate drafts from the current item on remount, reject aliases, and preserve a static zero-JavaScript sibling. |
51
52
 
52
53
  ## Sequence Rules
53
54
 
@@ -40,7 +40,7 @@ export function generateListRuntime(source, capabilityIR) {
40
40
  if (lists.rowHooks && !lists.generalRowHooks) runtime = replaceSequenceRequired(replaceRequired(runtime, /\/\* general-row-hooks \*\/[\s\S]*?\/\* general-row-hooks-end \*\/\n/, "", "general row hooks"), [
41
41
  ["initializeGeneralRowHooks", "initializeRowStates", "general row initializer", true],
42
42
  ["if (__KUDZU_LIST_ROW_HOOKS__) for (let index = 0; index < roots.length; index++) initializeRowStates(descriptor, descriptor.keys[index], roots[index], nested?.owner)", "if (__KUDZU_LIST_ROW_HOOKS__ && descriptor.rowStates) for (let index = 0; index < roots.length; index++) initializeRowStates(descriptor, descriptor.keys[index])", "flat row initialization"],
43
- ["if (__KUDZU_LIST_ROW_HOOKS__) initializeRowStates(list.descriptor, key, node, list.owner)", "if (__KUDZU_LIST_ROW_HOOKS__ && list.descriptor.rowStates) initializeRowStates(list.descriptor, key, node)", "flat row add", true],
43
+ ["if (__KUDZU_LIST_ROW_HOOKS__) initializeRowStates(list.descriptor, key, node, list.owner, item)", "if (__KUDZU_LIST_ROW_HOOKS__ && list.descriptor.rowStates) initializeRowStates(list.descriptor, key, node)", "flat row add", true],
44
44
  ["for (const node of registration.list.roots.values()) deleteRowStates(registration.list.descriptor, ownershipPaths.get(node))", "for (const token of registration.list.roots.keys()) deleteFlatRowStates(registration.list.descriptor, token)", "flat registration cleanup"],
45
45
  ["deleteRowStates(list.descriptor, ownershipPaths.get(node))", "deleteFlatRowStates(list.descriptor, token)", "flat row cleanup", true],
46
46
  [" if (__KUDZU_LIST_ROW_HOOKS__) replaceRowIds(root, rowReplacements.get(root))\n", "", "row ID replacement"],
@@ -70,7 +70,7 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
70
70
  lists.effects ||= Boolean(list.effects)
71
71
  lists.rowHooks ||= Boolean(list.rowStates?.length || list.rowRefs?.length)
72
72
  lists.rowRefs ||= Boolean(list.rowRefs?.length)
73
- lists.complexRowState ||= Boolean(list.rowStates?.some(state => state.initialValue !== null && typeof state.initialValue === "object"))
73
+ lists.complexRowState ||= Boolean(list.rowStates?.some(state => state.initializer === "list-item" || state.initialValue !== null && typeof state.initialValue === "object"))
74
74
  lists.nested ||= Boolean(list.ownerField)
75
75
  lists.selectors ||= Boolean(list.selector)
76
76
  lists.calculated ||= Boolean(list.source)
@@ -155,7 +155,7 @@ function assertList(list, index, ids, stateIds, listIds, parentByChild) {
155
155
  if (!isRecord(child) || !nonempty(child.id) || !nonempty(child.field) || typeof child.key !== "string" && child.key !== null || parentByChild.has(child.id)) throw new Error(`${label} has invalid or duplicate child ${JSON.stringify(child?.id)}`)
156
156
  parentByChild.set(child.id, list.id)
157
157
  }
158
- for (const row of list.rowStates ?? []) if (!isRecord(row) || !rowTemplate(row.id) || !Object.hasOwn(row, "initialValue")) throw new Error(`${label} has invalid row state`)
158
+ for (const row of list.rowStates ?? []) if (!isRecord(row) || !rowTemplate(row.id) || !Object.hasOwn(row, "initialValue") || row.initializer !== undefined && row.initializer !== "list-item") throw new Error(`${label} has invalid row state`)
159
159
  for (const row of [...(list.rowRefs ?? []), ...(list.rowConditions ?? [])]) if (!rowTemplate(row)) throw new Error(`${label} has invalid row ownership ID`)
160
160
  if (list.seed && list.valueSeed || list.seed !== undefined && !validSeed(list.seed) || list.valueSeed !== undefined && !validSeed(list.valueSeed)) throw new Error(`${label} has invalid seed`)
161
161
  }
@@ -2182,6 +2182,7 @@ function validateKeyedList(parts, sourceFile, setters, rowStates, componentSpeci
2182
2182
  const analysis = { values: [], conditions: [], nested: [] }
2183
2183
  const root = parts.root
2184
2184
  const item = parts.item
2185
+ for (const state of rowStates) if (state.initializer && state.initializer !== item) throw sourceNodeError(state.source.initializer, state.source.getSourceFile(), "Keyed row useState() item initializer must be the direct keyed item prop")
2185
2186
  const nestedDiagnostic = "Nested keyed list collections must be a direct property of the parent item"
2186
2187
  const validateElement = node => {
2187
2188
  const tag = ts.isJsxElement(node) ? node.openingElement.tagName : node.tagName
@@ -2492,13 +2493,15 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
2492
2493
  if (declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useState") {
2493
2494
  const hookLabel = ordinaryHooks ? "Setter-callback component" : "Keyed row"
2494
2495
  const initialArgument = declaration.initializer.arguments[0]
2495
- const propReceiver = ordinaryHooks && initialArgument && (ts.isIdentifier(initialArgument) ? initialArgument : ts.isCallExpression(initialArgument) && initialArgument.arguments.length === 0 && !initialArgument.questionDotToken && ts.isPropertyAccessExpression(initialArgument.expression) && !initialArgument.expression.questionDotToken && initialArgument.expression.name.text === "toString" && ts.isIdentifier(initialArgument.expression.expression) ? initialArgument.expression.expression : undefined)
2496
+ const propReceiver = initialArgument && (ordinaryHooks ? ts.isIdentifier(initialArgument) ? initialArgument : ts.isCallExpression(initialArgument) && initialArgument.arguments.length === 0 && !initialArgument.questionDotToken && ts.isPropertyAccessExpression(initialArgument.expression) && !initialArgument.expression.questionDotToken && initialArgument.expression.name.text === "toString" && ts.isIdentifier(initialArgument.expression.expression) ? initialArgument.expression.expression : undefined : ts.isIdentifier(initialArgument) ? initialArgument : undefined)
2496
2497
  const substitutedProp = propReceiver ? substitutions.get(propReceiver.text) : undefined
2497
2498
  const substitutedState = substitutedProp && ts.isIdentifier(unwrapExpression(substitutedProp)) ? unwrapExpression(substitutedProp).text : undefined
2498
2499
  const directProp = ts.isIdentifier(initialArgument)
2499
2500
  const parentInitializer = substitutedState ? directStateInitializer(call, substitutedState) : undefined
2500
- const propInitializer = substitutedState && ordinaryStateNames.has(substitutedState) && parentInitializer && (isPrimitiveDefaultLiteral(parentInitializer) || directProp && ts.isObjectLiteralExpression(parentInitializer))
2501
- if (declaration.initializer.arguments.length !== 1 || !isSerializableStateLiteral(initialArgument) && !propInitializer) throw sourceNodeError(declaration.initializer, component.getSourceFile(), `${hookLabel} useState() must use one directly serializable primitive, plain object, or array initial value${ordinaryHooks ? " or direct state prop initialized with a primitive or plain object; .toString() requires a primitive prop" : ""}; other dynamic initializers are not supported`)
2501
+ const propInitializer = ordinaryHooks && substitutedState && ordinaryStateNames.has(substitutedState) && parentInitializer && (isPrimitiveDefaultLiteral(parentInitializer) || directProp && ts.isObjectLiteralExpression(parentInitializer))
2502
+ const rowItemProp = propReceiver && elements.find(element => !element.dotDotDotToken && ts.isIdentifier(element.name) && element.name.text === propReceiver.text)
2503
+ const rowItemInitializer = !ordinaryHooks && directProp && substitutedState && rowItemProp && directProps.has((rowItemProp.propertyName ?? rowItemProp.name).text)
2504
+ if (declaration.initializer.arguments.length !== 1 || !isSerializableStateLiteral(initialArgument) && !propInitializer && !rowItemInitializer) throw sourceNodeError(declaration.initializer, component.getSourceFile(), `${hookLabel} useState() must use one directly serializable primitive, plain object, or array initial value${ordinaryHooks ? " or direct state prop initialized with a primitive or plain object; .toString() requires a primitive prop" : " or the direct keyed item prop"}; other dynamic initializers are not supported`)
2502
2505
  if (!ts.isArrayBindingPattern(declaration.name) || declaration.name.elements.length !== 2 || declaration.name.elements.some(element => !element || !ts.isBindingElement(element) || !ts.isIdentifier(element.name) || element.initializer || element.dotDotDotToken)) throw sourceNodeError(declaration.name, component.getSourceFile(), `${hookLabel} useState() must use [state, setter] identifier destructuring`)
2503
2506
  const suffix = `${Math.max(0, call.pos)}_${ordinaryHooks ? ordinaryStates.length : rowStates.length}`
2504
2507
  const state = ordinaryHooks ? `__kComponentState${suffix}` : `__kRowState${suffix}`
@@ -2509,16 +2512,18 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
2509
2512
  factory.createBindingElement(undefined, undefined, factory.createIdentifier(state)),
2510
2513
  factory.createBindingElement(undefined, undefined, factory.createIdentifier(setter))
2511
2514
  ])
2512
- const initialValue = propInitializer
2515
+ const initialValue = rowItemInitializer
2516
+ ? cloneAst(substitutedProp, factory, context)
2517
+ : propInitializer
2513
2518
  ? ts.isIdentifier(initialArgument)
2514
2519
  ? factory.createPropertyAccessExpression(cloneAst(substitutedProp, factory, context), "value")
2515
2520
  : substituteClone(initialArgument, substitutions, factory, context)
2516
2521
  : cloneAst(initialArgument, factory, context)
2517
2522
  synthesizeTree(initialValue)
2518
- const initializer = factory.createCallExpression(factory.createIdentifier(ordinaryHooks ? "__kComponentUseState" : "__kRowUseState"), undefined, [initialValue])
2523
+ const initializer = factory.createCallExpression(factory.createIdentifier(ordinaryHooks ? "__kComponentUseState" : "__kRowUseState"), undefined, rowItemInitializer ? [initialValue, factory.createStringLiteral(state), factory.createStringLiteral("list-item")] : [initialValue])
2519
2524
  hookDeclarations.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(binding, undefined, undefined, initializer)], ts.NodeFlags.Const)))
2520
2525
  if (ordinaryHooks) ordinaryStates.push({ state, setter, source: declaration })
2521
- else rowStates.push({ state, setter, source: declaration })
2526
+ else rowStates.push({ state, setter, source: declaration, ...(rowItemInitializer ? { initializer: substitutedState } : {}) })
2522
2527
  continue
2523
2528
  }
2524
2529
  if (declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useRef") {
@@ -125,7 +125,7 @@ export type ListDescriptor = {
125
125
  expressions?: true
126
126
  expressionAttributes?: true
127
127
  expressionStates?: string[]
128
- rowStates?: Array<{ id: string; initialValue: unknown }>
128
+ rowStates?: Array<{ id: string; initialValue: unknown; initializer?: "list-item" }>
129
129
  rowConditions?: string[]
130
130
  rowRefs?: string[]
131
131
  fastRelease?: true
@@ -53,12 +53,12 @@ export function useId() {
53
53
  return `k-${nextRenderId("i")}`
54
54
  }
55
55
 
56
- export function useState(initialValue, name) {
56
+ export function useState(initialValue, name, initializer) {
57
57
  if (!renderContext) {
58
58
  throw new Error("useState() can only run while rendering a Kudzu component")
59
59
  }
60
60
 
61
- const id = renderContext.listRoot || renderContext.listRowRoot ? nextRowRenderId("s", initialValue) : nextRenderId("s")
61
+ const id = renderContext.listRoot || renderContext.listRowRoot ? nextRowRenderId("s", initialValue, initializer) : nextRenderId("s")
62
62
  const signal = createSignal(id, initialValue)
63
63
 
64
64
  const setter = () => {
@@ -1005,7 +1005,7 @@ function nextRenderId(kind) {
1005
1005
  return `${kind}${renderContext[counters[kind]]++}`
1006
1006
  }
1007
1007
 
1008
- function nextRowRenderId(kind, initialValue) {
1008
+ function nextRowRenderId(kind, initialValue, initializer) {
1009
1009
  const root = renderContext.listRoot ?? renderContext.listRowRoot
1010
1010
  const index = root.rowIndexes[kind]++
1011
1011
  const entries = kind === "s" ? renderContext.listRowStates : kind === "r" ? renderContext.listRowRefs : renderContext.listRowConditions
@@ -1013,7 +1013,7 @@ function nextRowRenderId(kind, initialValue) {
1013
1013
  const entry = entries[index]
1014
1014
  if (entry) return entry.id
1015
1015
  const id = `${nextRenderId(kind)}:$k`
1016
- entries[index] = kind === "s" ? { id, initialValue } : { id }
1016
+ entries[index] = kind === "s" ? { id, initialValue, ...(initializer ? { initializer } : {}) } : { id }
1017
1017
  return id
1018
1018
  }
1019
1019
  const entry = entries[index]
@@ -237,7 +237,7 @@ function updateList(list) {
237
237
  if (!node) throw new Error("Keyed list template has no root element")
238
238
  node.removeAttribute("data-k-list-root")
239
239
  if (__KUDZU_NESTED_LISTS__ && list.childPrototypes) childPrototypes.set(node, list.childPrototypes)
240
- if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
240
+ if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner, item)
241
241
  if (staticRoot) listItems.set(node, item)
242
242
  else if (list.parts.directFill) {
243
243
  listItems.set(node, item)
@@ -401,7 +401,7 @@ function updateStableList(list, items) {
401
401
  if (node?.dataset.kListRoot !== list.descriptor.id) node = undefined
402
402
  if (!node) throw new Error("Keyed list template has no root element")
403
403
  node.removeAttribute("data-k-list-root")
404
- if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
404
+ if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner, item)
405
405
  if (list.parts.directFill) fillStructuralListParts(list.parts, node, item)
406
406
  else fillListItem(node, item, list.descriptor.nested, index, mapListItemParts(list.parts, node, list.descriptor.nested))
407
407
  fragment.append(node)
@@ -578,7 +578,7 @@ function addListRoot(list, { item, index = list.roots.size, key, token, value })
578
578
  if (!node) throw new Error("Keyed list template has no root element")
579
579
  node.removeAttribute("data-k-list-root")
580
580
  if (__KUDZU_NESTED_LISTS__ && list.childPrototypes) childPrototypes.set(node, list.childPrototypes)
581
- if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner)
581
+ if (__KUDZU_LIST_ROW_HOOKS__) initializeGeneralRowHooks(list.descriptor, key, node, list.owner, item)
582
582
  mapListItemParts(list.parts, node, list.descriptor.nested)
583
583
  fillListItem(node, item, list.descriptor.nested, index)
584
584
  const parent = list.container ?? list.start.parentNode
@@ -1046,7 +1046,7 @@ function keyToken(key) {
1046
1046
  }
1047
1047
 
1048
1048
  /* general-row-hooks */
1049
- function initializeGeneralRowHooks(descriptor, key, root, owner) {
1049
+ function initializeGeneralRowHooks(descriptor, key, root, owner, item) {
1050
1050
  const token = keyToken(key)
1051
1051
  const path = [...(ownershipPaths.get(owner) ?? []), `${descriptor.id}=${token}`]
1052
1052
  ownershipPaths.set(root, path)
@@ -1055,7 +1055,7 @@ function initializeGeneralRowHooks(descriptor, key, root, owner) {
1055
1055
  const replacements = new Map()
1056
1056
  for (const state of descriptor.rowStates ?? []) {
1057
1057
  const id = rowStateId(state.id, statePath)
1058
- if (!browserState.has(id)) browserState.set(id, __KUDZU_COMPLEX_LIST_ROW_STATE__ && state.initialValue !== null && typeof state.initialValue === "object" ? structuredClone(state.initialValue) : state.initialValue)
1058
+ if (!browserState.has(id)) browserState.set(id, state.initializer === "list-item" ? structuredClone(item) : __KUDZU_COMPLEX_LIST_ROW_STATE__ && state.initialValue !== null && typeof state.initialValue === "object" ? structuredClone(state.initialValue) : state.initialValue)
1059
1059
  replacements.set(state.id, id)
1060
1060
  }
1061
1061
  if (__KUDZU_LIST_ROW_REFS__) for (const ref of descriptor.rowRefs ?? []) replacements.set(ref, rowStateId(ref, statePath))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.57",
3
+ "version": "0.8.58",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",