@kudzujs/core 0.6.9 → 0.6.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  HTML-first TSX framework with synchronous state semantics and no virtual DOM.
8
8
 
9
- Kudzu keeps the familiar function-component, props, children, event-handler, `useState`, and mount-effect shape. Static components compile to HTML. Simple interactions compile to small behavior commands, while normal sync or async JavaScript handlers and mount effects compile to external ESM.
9
+ Kudzu keeps the familiar function-component, props, children, event-handler, `useState`, `useReducer`, and mount-effect shape. Static components compile to HTML. Simple interactions compile to small behavior commands, while normal sync or async JavaScript handlers and mount effects compile to external ESM.
10
10
 
11
11
  > Experimental `0.6.x`: the compiler API and supported TSX surface may change.
12
12
 
@@ -171,6 +171,17 @@ return <p>{weather.temperature}° {weather.label}</p>
171
171
 
172
172
  Derived text uses comment-bounded text nodes rather than wrapper elements, so table cells, options, SVG text, layout, and element selectors keep their authored structure.
173
173
 
174
+ Reducer state uses the same immediate logical updates and batched DOM commit:
175
+
176
+ ```tsx
177
+ import todoReducer from "../todoReducer"
178
+
179
+ const [todos, dispatch] = useReducer(todoReducer, [])
180
+ dispatch({ type: "add", title: "Ship" })
181
+ ```
182
+
183
+ The reduced migration form requires `[state, dispatch]`, exactly two hook arguments, and a synchronous two-parameter reducer exported as a default or named value from a relative TypeScript module. Dispatches in compiled handlers lower to functional state updates; lazy initializers, package, namespace, local, async, and generator reducers are not supported. Reducer dispatch functions cannot be passed through props or context.
184
+
174
185
  ## Reactive Attributes
175
186
 
176
187
  `className`, `disabled`, controlled `value`, and controlled `checked` accept normal state-dependent TSX expressions. The same `value` binding works for inputs and selects:
@@ -523,7 +534,7 @@ Supported:
523
534
  - Runtime bracket parameters with static fallback documents and host rewrite metadata
524
535
  - Static trusted `dangerouslySetInnerHTML`
525
536
  - Base-path deployments, multiple CSS files, and `afterBuild`
526
- - Primitive `useState` bindings
537
+ - `useState` and relative-imported `useReducer` bindings
527
538
  - Mount-only `useEffect(fn, [])` compiled to route-specific ESM
528
539
  - Relative TypeScript module Workers owned by inline effects
529
540
  - Conditional and keyed-row effect ownership with cleanup on DOM removal
@@ -23,6 +23,8 @@ Page `metadata` can emit description, canonical, favicon, manifest, Open Graph,
23
23
 
24
24
  Same-file and relative-imported components receiving a direct local-state array prop are specialized to intrinsic JSX before keyed-list analysis, so their component function is not retained in the browser. Handler modules are emitted only when a rendered descriptor references them, preventing specialized imported components from adding dead browser assets. Direct JSON-safe primitive keyed-item dependencies subscribe each row record to its owning list commit and compare selected fields after `list-runtime.js` synchronously refreshes the row marker. Only changed rows rerun with the complete latest item; reorder compares equal, unrelated fields do nothing, and key changes remain remove plus mount. Builds without item dependencies emit no item reader or list-state subscription code.
25
25
 
26
+ The reduced `useReducer` form reuses ordinary state slots. A direct dispatch in a compiled handler becomes a functional `set` whose reducer is bundled from a relative TypeScript module into that handler graph. Reducer-free routes and shared runtimes are unchanged; no reducer runtime or browser component instance exists.
27
+
26
28
  `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.
27
29
 
28
30
  The current matched commerce profile emits 35,355 deploy bytes and loads 7,334 B gzip of product-route JavaScript, including 2,425 B for navigation. These sizes are unchanged because its top-level-only navigation effects retain the smaller specialized path. Validated prefetch reduced the original 128.7 ms product-to-cart navigation to 5.6 ms in the current run. Seven interleaved artifact-clean builds after warm-up measured Kudzu at 486.8 ms and React at 545.4 ms, making Kudzu 10.7% faster. Cache-disabled output is byte-for-byte identical.
@@ -1611,6 +1611,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1611
1611
  return imported
1612
1612
  }
1613
1613
  const settersByFunction = new Map()
1614
+ const reducersByFunction = new Map()
1614
1615
  const functions = new Map()
1615
1616
  const components = new Map()
1616
1617
  const contexts = new Set()
@@ -1630,15 +1631,41 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1630
1631
  let usesListItem = false
1631
1632
 
1632
1633
  const collect = node => {
1633
- if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer)) {
1634
+ if (ts.isVariableDeclaration(node) && node.initializer && ts.isCallExpression(node.initializer)) {
1634
1635
  const callName = ts.isIdentifier(node.initializer.expression) ? node.initializer.expression.text : ""
1635
- const [stateElement, setterElement] = node.name.elements
1636
- if (callName === "useState" && stateElement && setterElement && ts.isBindingElement(stateElement) && ts.isBindingElement(setterElement) && ts.isIdentifier(stateElement.name) && ts.isIdentifier(setterElement.name)) {
1636
+ if (callName === "useReducer") {
1637
+ if (!ts.isArrayBindingPattern(node.name)) throw sourceNodeError(node.name, sourceFile, "useReducer() must use [state, dispatch] identifier destructuring")
1638
+ const [stateElement, dispatchElement] = node.name.elements
1639
+ if (node.name.elements.length !== 2 || !stateElement || !dispatchElement || !ts.isBindingElement(stateElement) || !ts.isBindingElement(dispatchElement) || !ts.isIdentifier(stateElement.name) || !ts.isIdentifier(dispatchElement.name)) throw sourceNodeError(node.name, sourceFile, "useReducer() must use [state, dispatch] identifier destructuring")
1640
+ if (node.initializer.arguments.length !== 2) throw sourceNodeError(node.initializer, sourceFile, "useReducer() requires exactly a reducer and initial value")
1641
+ const reducer = node.initializer.arguments[0]
1642
+ if (!ts.isIdentifier(reducer) || !importBindings.has(reducer.text) || importBindings.get(reducer.text).kind === "namespace") throw sourceNodeError(reducer, sourceFile, "useReducer() reducers must be default or named imports from relative TypeScript modules")
1643
+ const reducerImport = importBindings.get(reducer.text)
1644
+ let reducerDeclaration
1645
+ try {
1646
+ reducerDeclaration = resolveComponentExport(reducerImport.target, reducerImport.kind === "default" ? "default" : reducerImport.imported, importedSource, sourceFiles)
1647
+ } catch {
1648
+ throw sourceNodeError(reducer, sourceFile, "useReducer() imports must resolve to a statically analyzable reducer function")
1649
+ }
1650
+ if (reducerDeclaration.parameters.length !== 2 || reducerDeclaration.asteriskToken || reducerDeclaration.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) throw sourceNodeError(reducer, sourceFile, "useReducer() reducers must be synchronous functions with exactly state and action parameters")
1637
1651
  const owner = nearestFunction(node)
1638
- if (owner) {
1639
- const setters = settersByFunction.get(owner) ?? new Map()
1640
- setters.set(setterElement.name.text, stateElement.name.text)
1641
- settersByFunction.set(owner, setters)
1652
+ if (!owner) throw sourceNodeError(node, sourceFile, "useReducer() cannot be used outside a Kudzu component")
1653
+ const setters = settersByFunction.get(owner) ?? new Map()
1654
+ setters.set(dispatchElement.name.text, stateElement.name.text)
1655
+ settersByFunction.set(owner, setters)
1656
+ const reducers = reducersByFunction.get(owner) ?? new Map()
1657
+ reducers.set(dispatchElement.name.text, { state: stateElement.name.text, reducer: reducer.text, import: reducerImport })
1658
+ reducersByFunction.set(owner, reducers)
1659
+ }
1660
+ if (ts.isArrayBindingPattern(node.name)) {
1661
+ const [stateElement, setterElement] = node.name.elements
1662
+ if (callName === "useState" && stateElement && setterElement && ts.isBindingElement(stateElement) && ts.isBindingElement(setterElement) && ts.isIdentifier(stateElement.name) && ts.isIdentifier(setterElement.name)) {
1663
+ const owner = nearestFunction(node)
1664
+ if (owner) {
1665
+ const setters = settersByFunction.get(owner) ?? new Map()
1666
+ setters.set(setterElement.name.text, stateElement.name.text)
1667
+ settersByFunction.set(owner, setters)
1668
+ }
1642
1669
  }
1643
1670
  }
1644
1671
  }
@@ -1931,7 +1958,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1931
1958
  } else {
1932
1959
  compiledCallback = rewriteEffectWorkers(callback, callbackFile, callbackSource, sourceFiles, workerReferences, factory, context)
1933
1960
  }
1934
- const descriptor = compileNativeCallback(compiledCallback, setters, factory, effectHandlers, listEffect?.imports ?? importBindings, clientImports, "effect", dependencyItem, true, returns.cleanup)
1961
+ const descriptor = compileNativeCallback(compiledCallback, setters, reducersForNode(node, reducersByFunction), factory, effectHandlers, listEffect?.imports ?? importBindings, clientImports, "effect", dependencyItem, true, returns.cleanup)
1935
1962
  for (const reference of workerReferences.slice(workerStart)) Object.assign(reference, { module: handlerUrl, handler: descriptor.exportName })
1936
1963
  usesListItem ||= Boolean(itemDependencies.length && !listEffect)
1937
1964
  usesBehavior = true
@@ -1948,7 +1975,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
1948
1975
  ])
1949
1976
  }
1950
1977
 
1951
- if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text === "useState" && node.initializer.arguments.length === 1) {
1978
+ if (ts.isVariableDeclaration(node) && ts.isArrayBindingPattern(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && (node.initializer.expression.text === "useState" && node.initializer.arguments.length === 1 || node.initializer.expression.text === "useReducer" && node.initializer.arguments.length === 2)) {
1952
1979
  const stateElement = node.name.elements[0]
1953
1980
  if (!stateElement || !ts.isBindingElement(stateElement) || !ts.isIdentifier(stateElement.name)) return node
1954
1981
  const initializer = factory.updateCallExpression(node.initializer, node.initializer.expression, node.initializer.typeArguments, [
@@ -2030,7 +2057,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
2030
2057
 
2031
2058
  if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression && /^on[A-Z]/.test(node.name.text)) {
2032
2059
  const setters = settersForNode(node, settersByFunction)
2033
- const event = compileEvent(node.initializer.expression, setters, functions, factory, nativeHandlers, handlerUrl, listEventItems.get(node), importBindings, clientImports)
2060
+ const event = compileEvent(node.initializer.expression, setters, reducersForNode(node, reducersByFunction), functions, factory, nativeHandlers, handlerUrl, listEventItems.get(node), importBindings, clientImports)
2034
2061
  if (event) {
2035
2062
  usesBehavior = true
2036
2063
  return factory.updateJsxAttribute(node, node.name, factory.createJsxExpression(undefined, event))
@@ -2656,15 +2683,15 @@ function factoryNull() {
2656
2683
  return ts.factory.createNull()
2657
2684
  }
2658
2685
 
2659
- function compileEvent(expression, setters, functions, factory, nativeHandlers, handlerUrl, listItem, importBindings, clientImports) {
2686
+ function compileEvent(expression, setters, reducers, functions, factory, nativeHandlers, handlerUrl, listItem, importBindings, clientImports) {
2660
2687
  if (ts.isIdentifier(expression)) expression = functions.get(expression.text)
2661
2688
  if (!expression || (!ts.isArrowFunction(expression) && !ts.isFunctionExpression(expression) && !ts.isFunctionDeclaration(expression))) return undefined
2662
2689
 
2663
- const optimized = compileOptimizedEvent(expression, setters, factory)
2690
+ const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters, factory)
2664
2691
  if (optimized) return optimized
2665
2692
 
2666
2693
  rejectWorkerConstructions(expression, expression.getSourceFile(), "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback")
2667
- const descriptor = compileNativeCallback(expression, setters, factory, nativeHandlers, importBindings, clientImports, "handler", listItem)
2694
+ const descriptor = compileNativeCallback(expression, setters, reducers, factory, nativeHandlers, importBindings, clientImports, "handler", listItem)
2668
2695
  return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
2669
2696
  factory.createStringLiteral(handlerUrl),
2670
2697
  factory.createStringLiteral(descriptor.exportName),
@@ -2673,14 +2700,16 @@ function compileEvent(expression, setters, functions, factory, nativeHandlers, h
2673
2700
  ])
2674
2701
  }
2675
2702
 
2676
- function compileNativeCallback(expression, setters, factory, entries, importBindings, clientImports, prefix, listItem, deferValues = false, snapshotNested = false) {
2703
+ function compileNativeCallback(expression, setters, reducers, factory, entries, importBindings, clientImports, prefix, listItem, deferValues = false, snapshotNested = false) {
2677
2704
  const allCaptures = nativeCaptureNames(expression, setters)
2705
+ const usedReducers = referencedReducerDispatches(expression.body, reducers, expression)
2678
2706
  const imports = [...referencedImportedBindings(expression, importBindings)].map(name => importBindings.get(name))
2707
+ imports.push(...[...usedReducers].map(name => reducers.get(name).import))
2679
2708
  const captures = new Set([...allCaptures].filter(name => !importBindings.has(name)))
2680
2709
  for (const entry of imports) clientImports.add(entry.target)
2681
2710
  const usedStates = nativeStateNames(expression, setters)
2682
2711
  const exportName = `${prefix}${entries.length}`
2683
- entries.push({ exportName, expression, captures, imports, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), snapshotNested })
2712
+ entries.push({ exportName, expression, captures, imports, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), reducers: new Map([...reducers].filter(([name]) => usedReducers.has(name))), snapshotNested })
2684
2713
  const value = name => deferValues
2685
2714
  ? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
2686
2715
  : factory.createIdentifier(name)
@@ -2697,6 +2726,16 @@ function compileNativeCallback(expression, setters, factory, entries, importBind
2697
2726
  }
2698
2727
  }
2699
2728
 
2729
+ function referencedReducerDispatches(root, reducers, scopeRoot = root) {
2730
+ const used = new Set()
2731
+ const visit = node => {
2732
+ if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, scopeRoot)) used.add(node.text)
2733
+ ts.forEachChild(node, visit)
2734
+ }
2735
+ visit(root)
2736
+ return used
2737
+ }
2738
+
2700
2739
  function nativeStateNames(expression, setters) {
2701
2740
  return referencedStateNames(expression.body, setters, expression)
2702
2741
  }
@@ -2945,6 +2984,15 @@ function settersForNode(node, settersByFunction) {
2945
2984
  return new Map()
2946
2985
  }
2947
2986
 
2987
+ function reducersForNode(node, reducersByFunction) {
2988
+ for (let current = node.parent; current; current = current.parent) {
2989
+ if (!ts.isFunctionDeclaration(current) && !ts.isFunctionExpression(current) && !ts.isArrowFunction(current)) continue
2990
+ const reducers = reducersByFunction.get(current)
2991
+ if (reducers) return reducers
2992
+ }
2993
+ return new Map()
2994
+ }
2995
+
2948
2996
  function clientImportBindings(sourceFile, file, sourceFiles) {
2949
2997
  const bindings = new Map()
2950
2998
  for (const node of sourceFile.statements) {
@@ -3242,7 +3290,7 @@ function relativeModulePath(from, to) {
3242
3290
  return path.startsWith(".") ? path : `./${path}`
3243
3291
  }
3244
3292
 
3245
- function printNativeHandler({ exportName, expression, captures, setters, snapshotNested }) {
3293
+ function printNativeHandler({ exportName, expression, captures, setters, reducers = new Map(), snapshotNested }) {
3246
3294
  const factory = ts.factory
3247
3295
  const stateNames = new Set(setters.values())
3248
3296
  const snapshotNames = snapshotNested ? nestedStateNames(expression, setters) : new Set()
@@ -3251,6 +3299,16 @@ function printNativeHandler({ exportName, expression, captures, setters, snapsho
3251
3299
  const captureSnapshots = new Map([...captureSnapshotNames].map(name => [name, factory.createUniqueName("__kEffectCapture")]))
3252
3300
  const transformer = context => root => {
3253
3301
  const visitor = node => {
3302
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && reducers.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
3303
+ if (node.arguments.length !== 1) throw sourceNodeError(node, expression.getSourceFile(), "Reducer dispatches require exactly one action")
3304
+ return reducerDispatch(factory, reducers.get(node.expression.text), ts.visitNode(node.arguments[0], visitor))
3305
+ }
3306
+ if (ts.isShorthandPropertyAssignment(node) && reducers.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
3307
+ return factory.createPropertyAssignment(node.name, reducerReference(factory, reducers.get(node.name.text)))
3308
+ }
3309
+ if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
3310
+ return reducerReference(factory, reducers.get(node.text))
3311
+ }
3254
3312
  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
3255
3313
  return factory.createCallExpression(
3256
3314
  factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"),
@@ -3357,6 +3415,17 @@ function setterReference(factory, stateName) {
3357
3415
  )
3358
3416
  }
3359
3417
 
3418
+ function reducerReference(factory, reducer) {
3419
+ const action = factory.createUniqueName("__kAction")
3420
+ return factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, action)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), reducerDispatch(factory, reducer, action))
3421
+ }
3422
+
3423
+ function reducerDispatch(factory, reducer, action) {
3424
+ const previous = factory.createUniqueName("__kPrevious")
3425
+ const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier(reducer.reducer), undefined, [previous, action]))
3426
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
3427
+ }
3428
+
3360
3429
  function printReactiveBinding({ exportName, expression, captures, states }) {
3361
3430
  const factory = ts.factory
3362
3431
  const transformer = context => root => {
@@ -1,8 +1,11 @@
1
1
  export type StateSetter<T> = (value: T | ((previous: T) => T)) => void
2
+ export type Reducer<State, Action> = (state: State, action: Action) => State
3
+ export type Dispatch<Action> = (action: Action) => void
2
4
  export type EffectCleanup = () => void | Promise<void>
3
5
  export type EffectDependency = string | number | boolean | null
4
6
 
5
7
  export function useState<T>(initialValue: T): [T, StateSetter<T>]
8
+ export function useReducer<State, Action>(reducer: Reducer<State, Action>, initialValue: State): [State, Dispatch<Action>]
6
9
  export function useEffect(effect: () => void | EffectCleanup | Promise<void>, dependencies: readonly EffectDependency[]): void
7
10
  export function useParams<Params extends Record<string, string> = Record<string, string>>(): Readonly<Params>
8
11
 
@@ -2,6 +2,7 @@ import { serializeStyle } from "./style.js"
2
2
 
3
3
  const signalMarker = Symbol("kudzu.signal")
4
4
  const setterMarker = Symbol("kudzu.setter")
5
+ const reducerDispatchMarker = Symbol("kudzu.reducerDispatch")
5
6
  const behaviorMarker = Symbol("kudzu.behavior")
6
7
  const nativeBehaviorMarker = Symbol("kudzu.nativeBehavior")
7
8
  const bindingMarker = Symbol("kudzu.binding")
@@ -35,6 +36,16 @@ export function useState(initialValue, name) {
35
36
  return [signal, setter]
36
37
  }
37
38
 
39
+ export function useReducer(reducer, initialValue, name) {
40
+ if (typeof reducer !== "function") throw new Error("useReducer() requires a reducer function")
41
+ const [state] = useState(initialValue, name)
42
+ const dispatch = () => {
43
+ throw new Error("Reducer dispatches are compiled into browser handlers")
44
+ }
45
+ Object.defineProperty(dispatch, reducerDispatchMarker, { value: state.id })
46
+ return [state, dispatch]
47
+ }
48
+
38
49
  export function useParams() {
39
50
  if (renderContext?.renderScope === "layout") throw new Error("useParams() is only supported in route scope")
40
51
  if (!renderContext?.runtimeParamNames?.length) throw new Error("useParams() requires export const runtimeParams = true on a bracket page")
@@ -268,6 +279,7 @@ function serializeCapture(name, value, seen) {
268
279
  if (value?.[listItemMarker]) return { type: "list-item" }
269
280
  if (value?.[refMarker]) return { type: "ref", id: value.id }
270
281
  if (value?.[signalMarker]) return { type: "state", id: value.id }
282
+ if (typeof value === "function" && value[reducerDispatchMarker]) throw new Error(`Native capture "${name}" cannot contain a reducer dispatch`)
271
283
  if (typeof value === "function" && value[setterMarker]) return { type: "setter", id: value[setterMarker] }
272
284
  if (value === null || typeof value === "string" || typeof value === "boolean") return value
273
285
  if (typeof value === "number") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",