@kudzujs/core 0.17.1 → 0.17.3

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.3](https://github.com/kudzujs/kudzu/releases/tag/v0.17.3).** Calculated lists release row state even while their evaluator module is loading, so explicit remount starts fresh. Initial evaluation also updates visible row text from the latest source data. Native Chrome regressions cover both fixes; static siblings remain JavaScript-free. Generator `create-kudzu@0.1.164` targets core `^0.17.3`. The fixes add a small measured byte cost; 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
  }
@@ -97,6 +97,7 @@ function mountLists(root) {
97
97
  ownedLists.set(list.owner, lists)
98
98
  listRegistrations.set(start, { list, owner: list.owner })
99
99
  } else if (descriptor.source) {
100
+ listRegistrations.set(start, { states: [], list })
100
101
  if (descriptor.children) updateList(list)
101
102
  const load = {}
102
103
  listLoads.set(start, load)
@@ -180,7 +181,7 @@ function updateList(list) {
180
181
  keys.add(token)
181
182
  entries.push({ item, index, key, token, value: referenceOnly ? item : seededValue === undefined ? JSON.stringify(item) : seededValue })
182
183
  }
183
- if (list.items === undefined && list.parts.directFill && entries.length === list.roots.size) {
184
+ if (list.items === undefined && !list.sourceEvaluator && list.parts.directFill && entries.length === list.roots.size) {
184
185
  const roots = __KUDZU_LIST_STABLE_FAST_PATHS__ ? list.orderedRoots : [...list.roots.values()]
185
186
  if (entries.every((entry, index) => list.roots.get(entry.token) === roots[index])) {
186
187
  for (let index = 0; index < entries.length; index++) {
@@ -650,13 +651,6 @@ function fillListParts(root, parts, item, revision, index = 0, previous) {
650
651
  }
651
652
  }
652
653
  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
654
  }
661
655
 
662
656
  function fillListExpressions(root, parts, item, revision, index) {
@@ -676,6 +670,14 @@ function fillListExpressions(root, parts, item, revision, index) {
676
670
  }
677
671
  }
678
672
  }
673
+ // Parent-state commits invalidate the shared row revision, including pending conditions.
674
+ if (__KUDZU_LIST_CONDITIONS__) {
675
+ for (const [marker, descriptor] of parts.conditions) {
676
+ evaluate(descriptor, item, index).then(value => {
677
+ if (revisions.get(root) === revision && marker.isConnected) updateListCondition(marker, descriptor.kind, value, item, index)
678
+ }).catch(error => console.error(error))
679
+ }
680
+ }
679
681
  }
680
682
 
681
683
  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.3",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",