@kudzujs/core 0.17.1 → 0.17.2

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
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.17.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Current release: [0.17.1](https://github.com/kudzujs/kudzu/releases/tag/v0.17.1).** Binding lifetime tracking uses one WeakMap instead of duplicate WeakMap/WeakSet membership, preserving state release and explicit DOM remount behavior. C2 saves 52 raw JavaScript bytes (7 gzip); the project application deploy saves 114 raw bytes. Generator `create-kudzu@0.1.162` targets core `^0.17.1`. This is a size and simplification improvement, not a demonstrated speed gain. Restoration latency and AI-cost/1.0 gates remain unresolved.
17
+ **Current release: [0.17.2](https://github.com/kudzujs/kudzu/releases/tag/v0.17.2).** Delayed evaluators cannot regain authority after DOM or source-state release, including same-node remount and state-ID reuse. Keyed conditions now settle correctly when row data and parent selection change in one turn. Native Chrome regressions cover both fixes; static siblings remain JavaScript-free. Generator `create-kudzu@0.1.163` targets core `^0.17.2`. Output sizes have mixed deltas; no speed improvement is claimed. Restoration latency and AI-cost/1.0 gates remain unresolved.
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
@@ -5,7 +5,6 @@ import { serializeStyle } from "./style.js"
5
5
  const imports = new Map()
6
6
  const bindingTargets = new Map()
7
7
  const conditionTargets = globalThis.__KUDZU_CONDITIONS__ && new Map()
8
- const mountedConditions = globalThis.__KUDZU_CONDITIONS__ && new WeakSet()
9
8
  const bindingRegistrations = new WeakMap()
10
9
  const conditionRegistrations = globalThis.__KUDZU_CONDITIONS__ && new WeakMap()
11
10
  const textDescriptors = globalThis.__KUDZU_TEXT_BINDINGS__ && typeof document !== "undefined" ? JSON.parse(document.body.dataset.kTextBindings ?? "[]") : []
@@ -43,7 +42,7 @@ function commitBindings(id) {
43
42
  if (bindings) {
44
43
  for (const binding of bindings) {
45
44
  if (!binding.node.isConnected) bindings.delete(binding)
46
- else patchBinding(binding.node, binding.target, binding.read())
45
+ else if (binding.read) patchBinding(binding.node, binding.target, binding.read())
47
46
  }
48
47
  }
49
48
  if (!globalThis.__KUDZU_CONDITIONS__) return
@@ -83,15 +82,7 @@ function mountBindings(root) {
83
82
  patchBinding(node, target, binding.read())
84
83
  continue
85
84
  }
86
- loadEvaluator(descriptor).then(evaluator => {
87
- if (!node.isConnected) return
88
- const binding = { node, target, read: evaluator.read }
89
- for (const id of evaluator.stateIds) {
90
- register(bindingTargets, id, binding)
91
- registrations.push([id, binding])
92
- }
93
- patchBinding(node, target, binding.read())
94
- }).catch(error => console.error(error))
85
+ mountBindingEvaluator(node, target, descriptor, registrations)
95
86
  }
96
87
  }
97
88
  if (globalThis.__KUDZU_TEXT_BINDINGS__) {
@@ -108,47 +99,56 @@ function mountBindings(root) {
108
99
  patchBinding(node, "text", binding.read())
109
100
  continue
110
101
  }
111
- loadEvaluator(descriptor).then(evaluator => {
112
- if (!node.isConnected) return
113
- const binding = { node, target: "text", read: evaluator.read }
114
- for (const id of evaluator.stateIds) {
115
- register(bindingTargets, id, binding)
116
- registrations.push([id, binding])
117
- }
118
- patchBinding(node, "text", binding.read())
119
- }).catch(error => console.error(error))
102
+ mountBindingEvaluator(node, "text", descriptor, registrations)
120
103
  }
121
104
  }
122
105
  }
123
106
 
107
+ function mountBindingEvaluator(node, target, descriptor, registrations) {
108
+ const binding = { node, target }
109
+ // Register dependencies before loading so state release also revokes pending work.
110
+ for (const id of bindingStateIds(descriptor)) {
111
+ register(bindingTargets, id, binding)
112
+ registrations.push([id, binding])
113
+ }
114
+ loadEvaluator(descriptor).then(evaluator => {
115
+ if (!node.isConnected || bindingRegistrations.get(node) !== registrations) return
116
+ binding.read = evaluator.read
117
+ patchBinding(node, target, binding.read())
118
+ }).catch(error => console.error(error))
119
+ }
120
+
124
121
  function mountConditions(root) {
125
122
  for (const start of matching(root, "template[data-k-if]")) {
126
- if (mountedConditions.has(start)) continue
127
- mountedConditions.add(start)
123
+ if (conditionRegistrations.has(start)) continue
124
+ // Reserve membership while the evaluator loads, before subscriptions exist.
125
+ conditionRegistrations.set(start, undefined)
128
126
  const descriptor = JSON.parse(start.dataset.kIf)
129
127
  const end = findEnd(start, descriptor.id)
130
128
  const truthy = globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg ? start.dataset.kSvgTrue : start.content.querySelector("template[data-k-true]")
131
129
  const falsy = globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg ? start.dataset.kSvgFalse : start.content.querySelector("template[data-k-false]")
132
130
  if (!end || !truthy || !falsy) continue
133
131
  const condition = { start, end, truthy, falsy, svg: globalThis.__KUDZU_SVG_CONDITIONS__ && descriptor.svg, kind: descriptor.kind, current: conditionKey(descriptor.kind, descriptor.initial), mount: globalThis.__KUDZU_CONDITION_MOUNTS__ !== false && descriptor.mount, owned: globalThis.__KUDZU_CONDITION_STATE__ !== false && descriptor.owned }
132
+ const registrations = []
133
+ const registration = { condition, registrations }
134
+ conditionRegistrations.set(start, registration)
135
+ for (const id of descriptor.state ? [descriptor.state] : bindingStateIds(descriptor)) {
136
+ register(conditionTargets, id, condition)
137
+ registrations.push([id, condition])
138
+ }
134
139
  if (globalThis.__KUDZU_CONDITION_STATE__ !== false) mountConditionStates(condition, Boolean(descriptor.initial), false)
135
140
  const mount = evaluator => {
136
- if (!start.isConnected) return
141
+ if (!start.isConnected || conditionRegistrations.get(start) !== registration) return
137
142
  condition.read = evaluator.read
138
- const registrations = []
139
- for (const id of evaluator.stateIds) {
140
- register(conditionTargets, id, condition)
141
- registrations.push([id, condition])
142
- }
143
- conditionRegistrations.set(start, { condition, registrations })
144
143
  updateCondition(condition)
145
144
  }
146
- if (descriptor.state) mount({ read: () => browserState.get(descriptor.state), stateIds: [descriptor.state] })
145
+ if (descriptor.state) mount({ read: () => browserState.get(descriptor.state) })
147
146
  else loadEvaluator(descriptor).then(mount).catch(error => console.error(error))
148
147
  }
149
148
  }
150
149
 
151
150
  function updateCondition(condition) {
151
+ if (!condition.read) return
152
152
  const value = condition.read()
153
153
  const next = conditionKey(condition.kind, value)
154
154
  if (next === condition.current) return
@@ -170,7 +170,7 @@ function updateCondition(condition) {
170
170
  const select = globalThis.__KUDZU_VALUE_BINDINGS__ !== false && condition.start.closest("select[data-k-bind-value]")
171
171
  if (select) {
172
172
  for (const binding of new Set((bindingRegistrations.get(select) ?? []).map(([, entry]) => entry))) {
173
- patchBinding(binding.node, binding.target, binding.read())
173
+ if (binding.read) patchBinding(binding.node, binding.target, binding.read())
174
174
  }
175
175
  }
176
176
  }
@@ -194,7 +194,6 @@ function unmountConditions(root) {
194
194
  for (const [id, condition] of registration?.registrations ?? []) conditionTargets.get(id)?.delete(condition)
195
195
  if (globalThis.__KUDZU_CONDITION_STATE__ !== false && registration?.condition) unmountConditionStates(registration.condition, registration.condition.current === "true")
196
196
  conditionRegistrations.delete(start)
197
- mountedConditions.delete(start)
198
197
  }
199
198
  }
200
199
 
@@ -210,7 +209,7 @@ function releaseBindings(id) {
210
209
  const registration = conditionRegistrations.get(condition.start)
211
210
  for (const [conditionId, entry] of registration?.registrations ?? []) conditionTargets.get(conditionId)?.delete(entry)
212
211
  if (globalThis.__KUDZU_CONDITION_STATE__ !== false) unmountConditionStates(condition, condition.current === "true")
213
- conditionRegistrations.delete(condition.start)
212
+ conditionRegistrations.set(condition.start, undefined)
214
213
  }
215
214
  conditionTargets.delete(id)
216
215
  }
@@ -650,13 +650,6 @@ function fillListParts(root, parts, item, revision, index = 0, previous) {
650
650
  }
651
651
  }
652
652
  fillListExpressions(root, parts, item, revision, index)
653
- if (__KUDZU_LIST_CONDITIONS__) {
654
- for (const [marker, descriptor] of parts.conditions) {
655
- evaluate(descriptor, item, index).then(value => {
656
- if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
657
- }).catch(error => console.error(error))
658
- }
659
- }
660
653
  }
661
654
 
662
655
  function fillListExpressions(root, parts, item, revision, index) {
@@ -676,6 +669,14 @@ function fillListExpressions(root, parts, item, revision, index) {
676
669
  }
677
670
  }
678
671
  }
672
+ // Parent-state commits invalidate the shared row revision, including pending conditions.
673
+ if (__KUDZU_LIST_CONDITIONS__) {
674
+ for (const [marker, descriptor] of parts.conditions) {
675
+ evaluate(descriptor, item, index).then(value => {
676
+ if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
677
+ }).catch(error => console.error(error))
678
+ }
679
+ }
679
680
  }
680
681
 
681
682
  function updateListExpressions(list) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",