foldkit 0.156.0 → 0.157.0

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.
Files changed (201) hide show
  1. package/README.md +3 -3
  2. package/dist/asyncData/asyncData.d.ts +7 -7
  3. package/dist/asyncData/asyncData.d.ts.map +1 -1
  4. package/dist/asyncData/asyncData.js +12 -12
  5. package/dist/calendar/calendarDate.d.ts +15 -15
  6. package/dist/calendar/calendarDate.d.ts.map +1 -1
  7. package/dist/calendar/calendarDate.js +14 -14
  8. package/dist/calendar/comparison.d.ts +2 -2
  9. package/dist/calendar/comparison.d.ts.map +1 -1
  10. package/dist/calendar/comparison.js +5 -5
  11. package/dist/calendar/info.d.ts +2 -2
  12. package/dist/calendar/info.d.ts.map +1 -1
  13. package/dist/calendar/info.js +2 -2
  14. package/dist/calendar/locale.d.ts +7 -7
  15. package/dist/calendar/locale.d.ts.map +1 -1
  16. package/dist/calendar/locale.js +23 -23
  17. package/dist/canvas/shape.d.ts +89 -89
  18. package/dist/canvas/shape.d.ts.map +1 -1
  19. package/dist/canvas/shape.js +65 -53
  20. package/dist/command/index.d.ts +3 -3
  21. package/dist/customElement/index.d.ts +2 -2
  22. package/dist/customElement/index.js +2 -2
  23. package/dist/devTools/protocol.d.ts +278 -278
  24. package/dist/devTools/protocol.d.ts.map +1 -1
  25. package/dist/devTools/protocol.js +98 -92
  26. package/dist/devTools/schemaSummarize.d.ts.map +1 -1
  27. package/dist/devTools/schemaSummarize.js +24 -24
  28. package/dist/devTools/serialize.d.ts +1 -1
  29. package/dist/devTools/serialize.d.ts.map +1 -1
  30. package/dist/devTools/serialize.js +10 -10
  31. package/dist/devTools/store.d.ts.map +1 -1
  32. package/dist/devTools/store.js +52 -28
  33. package/dist/devTools/submodelPath.d.ts.map +1 -1
  34. package/dist/devTools/submodelPath.js +3 -3
  35. package/dist/devTools/summarize.d.ts +6 -6
  36. package/dist/devTools/summarize.d.ts.map +1 -1
  37. package/dist/devTools/summarize.js +13 -13
  38. package/dist/devTools/webSocketBridge.d.ts +3 -3
  39. package/dist/devTools/webSocketBridge.d.ts.map +1 -1
  40. package/dist/devTools/webSocketBridge.js +8 -8
  41. package/dist/dom/dom.d.ts +2 -0
  42. package/dist/dom/dom.d.ts.map +1 -1
  43. package/dist/dom/dom.js +47 -13
  44. package/dist/experimental/machine/machine.d.ts +29 -6
  45. package/dist/experimental/machine/machine.d.ts.map +1 -1
  46. package/dist/experimental/machine/machine.js +9 -8
  47. package/dist/experimental/server/entry.js +2 -2
  48. package/dist/experimental/server/serialize.js +2 -2
  49. package/dist/experimental/server/server.js +4 -4
  50. package/dist/fieldValidation/fieldValidation.d.ts +12 -12
  51. package/dist/fieldValidation/fieldValidation.d.ts.map +1 -1
  52. package/dist/fieldValidation/fieldValidation.js +9 -9
  53. package/dist/fieldValidation/rule.d.ts +2 -2
  54. package/dist/fieldValidation/rule.d.ts.map +1 -1
  55. package/dist/fieldValidation/rule.js +6 -6
  56. package/dist/file/file.d.ts +3 -3
  57. package/dist/file/file.d.ts.map +1 -1
  58. package/dist/file/file.js +3 -3
  59. package/dist/html/boundary.d.ts +93 -36
  60. package/dist/html/boundary.d.ts.map +1 -1
  61. package/dist/html/boundary.js +242 -75
  62. package/dist/html/childAttribute.d.ts +6 -4
  63. package/dist/html/childAttribute.d.ts.map +1 -1
  64. package/dist/html/childAttribute.js +7 -1
  65. package/dist/html/index.d.ts +31 -16
  66. package/dist/html/index.d.ts.map +1 -1
  67. package/dist/html/index.js +97 -40
  68. package/dist/html/lazy.d.ts +6 -6
  69. package/dist/html/lazy.d.ts.map +1 -1
  70. package/dist/html/lazy.js +37 -15
  71. package/dist/html/public.d.ts +1 -1
  72. package/dist/html/public.d.ts.map +1 -1
  73. package/dist/html/runtimeSingleton.d.ts +27 -6
  74. package/dist/html/runtimeSingleton.d.ts.map +1 -1
  75. package/dist/html/runtimeSingleton.js +46 -29
  76. package/dist/html/submodel.d.ts.map +1 -1
  77. package/dist/html/submodel.js +28 -39
  78. package/dist/http/http.d.ts +1 -1
  79. package/dist/http/http.js +1 -1
  80. package/dist/managedResource/managedResource.d.ts +5 -5
  81. package/dist/managedResource/managedResource.js +3 -3
  82. package/dist/mount/index.d.ts +92 -39
  83. package/dist/mount/index.d.ts.map +1 -1
  84. package/dist/mount/index.js +28 -8
  85. package/dist/mount/public.d.ts +1 -1
  86. package/dist/mount/public.d.ts.map +1 -1
  87. package/dist/mount/public.js +1 -1
  88. package/dist/navigation/urlRequest.d.ts +9 -9
  89. package/dist/navigation/urlRequest.d.ts.map +1 -1
  90. package/dist/navigation/urlRequest.js +2 -2
  91. package/dist/onUnmountModule.d.ts +19 -0
  92. package/dist/onUnmountModule.d.ts.map +1 -0
  93. package/dist/onUnmountModule.js +80 -0
  94. package/dist/port/port.d.ts +5 -5
  95. package/dist/port/port.js +5 -5
  96. package/dist/route/parser.d.ts +2 -2
  97. package/dist/route/parser.js +2 -2
  98. package/dist/runtime/hmrProtocol.d.ts +10 -10
  99. package/dist/runtime/hmrProtocol.d.ts.map +1 -1
  100. package/dist/runtime/hmrProtocol.js +10 -10
  101. package/dist/runtime/runtime.d.ts +4 -4
  102. package/dist/runtime/runtime.d.ts.map +1 -1
  103. package/dist/runtime/runtime.js +168 -51
  104. package/dist/schema/index.d.ts +41 -29
  105. package/dist/schema/index.d.ts.map +1 -1
  106. package/dist/schema/index.js +9 -9
  107. package/dist/snabbdom/init.d.ts +1 -1
  108. package/dist/snabbdom/init.d.ts.map +1 -1
  109. package/dist/snabbdom/init.js +5 -1
  110. package/dist/snabbdom/vnode.d.ts +1 -0
  111. package/dist/snabbdom/vnode.d.ts.map +1 -1
  112. package/dist/snabbdom/vnode.js +1 -0
  113. package/dist/subscription/animationFrame.d.ts +3 -3
  114. package/dist/subscription/animationFrame.d.ts.map +1 -1
  115. package/dist/subscription/animationFrame.js +2 -2
  116. package/dist/subscription/fromEvent.d.ts +2 -2
  117. package/dist/subscription/fromEvent.js +2 -2
  118. package/dist/subscription/subscription.d.ts +3 -3
  119. package/dist/subscription/subscription.js +1 -1
  120. package/dist/test/apps/bubbling.d.ts +6 -6
  121. package/dist/test/apps/bubbling.d.ts.map +1 -1
  122. package/dist/test/apps/bubbling.js +6 -6
  123. package/dist/test/apps/colorPicker.d.ts +7 -7
  124. package/dist/test/apps/colorPicker.d.ts.map +1 -1
  125. package/dist/test/apps/colorPicker.js +5 -5
  126. package/dist/test/apps/contextMenu.d.ts +5 -5
  127. package/dist/test/apps/contextMenu.d.ts.map +1 -1
  128. package/dist/test/apps/contextMenu.js +4 -4
  129. package/dist/test/apps/counter.d.ts +8 -8
  130. package/dist/test/apps/counter.d.ts.map +1 -1
  131. package/dist/test/apps/counter.js +8 -8
  132. package/dist/test/apps/crashOnRender.d.ts +18 -18
  133. package/dist/test/apps/crashOnRender.d.ts.map +1 -1
  134. package/dist/test/apps/crashOnRender.js +11 -11
  135. package/dist/test/apps/drafts.d.ts +9 -9
  136. package/dist/test/apps/drafts.d.ts.map +1 -1
  137. package/dist/test/apps/drafts.js +6 -6
  138. package/dist/test/apps/feedSocket.d.ts +6 -6
  139. package/dist/test/apps/feedSocket.d.ts.map +1 -1
  140. package/dist/test/apps/feedSocket.js +8 -8
  141. package/dist/test/apps/fileUpload.d.ts +2 -2
  142. package/dist/test/apps/fileUpload.d.ts.map +1 -1
  143. package/dist/test/apps/fileUpload.js +2 -2
  144. package/dist/test/apps/focusBoundary.d.ts +3 -3
  145. package/dist/test/apps/focusBoundary.d.ts.map +1 -1
  146. package/dist/test/apps/focusBoundary.js +3 -3
  147. package/dist/test/apps/formChild.d.ts +11 -11
  148. package/dist/test/apps/formChild.d.ts.map +1 -1
  149. package/dist/test/apps/formChild.js +13 -12
  150. package/dist/test/apps/interactions.d.ts +8 -8
  151. package/dist/test/apps/interactions.d.ts.map +1 -1
  152. package/dist/test/apps/interactions.js +8 -8
  153. package/dist/test/apps/keypress.d.ts +6 -6
  154. package/dist/test/apps/keypress.d.ts.map +1 -1
  155. package/dist/test/apps/keypress.js +6 -6
  156. package/dist/test/apps/login.d.ts +11 -11
  157. package/dist/test/apps/login.d.ts.map +1 -1
  158. package/dist/test/apps/login.js +15 -15
  159. package/dist/test/apps/logoutButton.d.ts +3 -3
  160. package/dist/test/apps/logoutButton.d.ts.map +1 -1
  161. package/dist/test/apps/logoutButton.js +2 -2
  162. package/dist/test/apps/mountPanel.d.ts +9 -9
  163. package/dist/test/apps/mountPanel.d.ts.map +1 -1
  164. package/dist/test/apps/mountPanel.js +9 -9
  165. package/dist/test/apps/multiRole.d.ts +3 -3
  166. package/dist/test/apps/multiRole.d.ts.map +1 -1
  167. package/dist/test/apps/multiRole.js +2 -2
  168. package/dist/test/apps/pointer.d.ts +7 -7
  169. package/dist/test/apps/pointer.d.ts.map +1 -1
  170. package/dist/test/apps/pointer.js +7 -7
  171. package/dist/test/apps/resumeUpload.d.ts +8 -8
  172. package/dist/test/apps/resumeUpload.d.ts.map +1 -1
  173. package/dist/test/apps/resumeUpload.js +7 -7
  174. package/dist/test/apps/scorePanel.d.ts +3 -3
  175. package/dist/test/apps/scorePanel.d.ts.map +1 -1
  176. package/dist/test/apps/scorePanel.js +2 -2
  177. package/dist/test/apps/selectiveKeys.d.ts +3 -3
  178. package/dist/test/apps/selectiveKeys.d.ts.map +1 -1
  179. package/dist/test/apps/selectiveKeys.js +3 -3
  180. package/dist/test/apps/uploads.d.ts +18 -18
  181. package/dist/test/apps/uploads.d.ts.map +1 -1
  182. package/dist/test/apps/uploads.js +13 -13
  183. package/dist/test/matchers.d.ts.map +1 -1
  184. package/dist/test/matchers.js +4 -4
  185. package/dist/test/query.d.ts.map +1 -1
  186. package/dist/test/query.js +28 -28
  187. package/dist/test/scene.d.ts +37 -8
  188. package/dist/test/scene.d.ts.map +1 -1
  189. package/dist/test/scene.js +58 -15
  190. package/dist/test/story.d.ts +35 -8
  191. package/dist/test/story.d.ts.map +1 -1
  192. package/dist/test/story.js +44 -22
  193. package/dist/update/update.d.ts +17 -20
  194. package/dist/update/update.d.ts.map +1 -1
  195. package/dist/url/index.d.ts +8 -8
  196. package/dist/url/index.d.ts.map +1 -1
  197. package/dist/url/index.js +19 -19
  198. package/dist/vdom.d.ts +3 -2
  199. package/dist/vdom.d.ts.map +1 -1
  200. package/dist/vdom.js +16 -6
  201. package/package.json +1 -1
@@ -13,8 +13,12 @@ const splitBoundary = (boundaryId) => boundaryId === ROOT_BOUNDARY ? [] : bounda
13
13
  export const createBoundaryRegistry = () => ({
14
14
  wraps: new Map(),
15
15
  boundaryDispatches: new WeakMap(),
16
+ mountWraps: new WeakMap(),
17
+ mountWrapOwners: new Map(),
16
18
  seenThisRender: new Map(),
17
19
  lazyTrackingStack: [],
20
+ boundaryWrapTransactionLog: [],
21
+ activeBoundaryWrapTransaction: undefined,
18
22
  dedupeSeen: new Set(),
19
23
  });
20
24
  const captureCallSite = () => {
@@ -22,10 +26,14 @@ const captureCallSite = () => {
22
26
  const lines = stack.split('\n');
23
27
  for (const line of lines) {
24
28
  const trimmed = line.trim();
29
+ const isLazyInternalFrame = /[/\\]html[/\\]lazy\.[cm]?[jt]s/.test(trimmed);
25
30
  if (trimmed.length === 0 ||
26
31
  trimmed.startsWith('Error') ||
27
32
  trimmed.includes('captureCallSite') ||
33
+ trimmed.includes('assertBoundaryNotSeen') ||
28
34
  trimmed.includes('registerBoundaryWrap') ||
35
+ trimmed.includes('restoreBoundaryWrapsForLazyHit') ||
36
+ isLazyInternalFrame ||
29
37
  trimmed.includes('at submodel')) {
30
38
  continue;
31
39
  }
@@ -33,7 +41,7 @@ const captureCallSite = () => {
33
41
  }
34
42
  return '(call site unavailable)';
35
43
  };
36
- export const registerBoundaryWrap = (registry, boundaryId, descriptor) => {
44
+ const assertBoundaryNotSeen = (registry, boundaryId) => {
37
45
  const existingCallSite = registry.seenThisRender.get(boundaryId);
38
46
  if (existingCallSite !== undefined) {
39
47
  const ownSlotId = boundaryId.includes(BOUNDARY_SEPARATOR)
@@ -49,16 +57,47 @@ export const registerBoundaryWrap = (registry, boundaryId, descriptor) => {
49
57
  `needs its own id (e.g. "desktop-foo", "mobile-foo"). For lists, use a stable ` +
50
58
  `per-item identifier.`);
51
59
  }
60
+ };
61
+ export const registerBoundaryWrap = (registry, boundaryId, descriptor, mountOuterDispatch) => {
62
+ assertBoundaryNotSeen(registry, boundaryId);
52
63
  // NOTE: compute the call site before writing either map. If
53
64
  // captureCallSite throws (e.g. hardened runtime without
54
65
  // Error.stack), neither map is mutated, so a later registration
55
66
  // with the same slotId throws the duplicate error correctly instead
56
67
  // of silently overwriting after a half-finished prior write.
57
68
  const callSite = captureCallSite();
69
+ writeBoundaryWrap(registry, boundaryId, descriptor, mountOuterDispatch, callSite);
70
+ };
71
+ const writeBoundaryWrap = (registry, boundaryId, descriptor, mountOuterDispatch, callSite) => {
72
+ const previousDescriptor = registry.wraps.get(boundaryId);
73
+ const existingMountWraps = registry.mountWraps.get(mountOuterDispatch);
74
+ const previousMountDescriptor = existingMountWraps?.get(boundaryId);
75
+ if (registry.activeBoundaryWrapTransaction !== undefined) {
76
+ registry.boundaryWrapTransactionLog.push({
77
+ _tag: 'Registration',
78
+ boundaryId,
79
+ descriptor,
80
+ mountOuterDispatch,
81
+ previousDescriptor,
82
+ previousMountDescriptor,
83
+ });
84
+ }
58
85
  registry.wraps.set(boundaryId, descriptor);
86
+ let mountWraps = existingMountWraps;
87
+ if (mountWraps === undefined) {
88
+ mountWraps = new Map();
89
+ registry.mountWraps.set(mountOuterDispatch, mountWraps);
90
+ }
91
+ mountWraps.set(boundaryId, descriptor);
92
+ let mountWrapOwners = registry.mountWrapOwners.get(boundaryId);
93
+ if (mountWrapOwners === undefined) {
94
+ mountWrapOwners = new Set();
95
+ registry.mountWrapOwners.set(boundaryId, mountWrapOwners);
96
+ }
97
+ mountWrapOwners.add(mountOuterDispatch);
59
98
  registry.seenThisRender.set(boundaryId, callSite);
60
99
  for (const tracked of registry.lazyTrackingStack) {
61
- tracked.set(boundaryId, callSite);
100
+ tracked.set(boundaryId, { callSite, descriptor, mountOuterDispatch });
62
101
  }
63
102
  };
64
103
  /** Starts capturing boundary registrations on a fresh set pushed onto
@@ -81,28 +120,37 @@ export const endLazyTracking = (registry) => {
81
120
  }
82
121
  registry.lazyTrackingStack.pop();
83
122
  };
84
- /** Replays a set of boundary ids captured during a previous lazy run
85
- * into `seenThisRender` so the duplicate-slotId guard sees them. Also
86
- * forwards them into any active tracking sets so an outer lazy
87
- * wrapping this cache hit captures the ids in its own snapshot.
123
+ /** Restores boundary registrations captured during a previous lazy run and
124
+ * replays their ids into `seenThisRender` so the duplicate-slotId guard sees
125
+ * them. Also forwards them into active tracking maps so an outer lazy
126
+ * wrapping this cache hit captures the registrations in its own snapshot.
88
127
  *
89
- * Skips ids already present in `seenThisRender` to preserve the
90
- * original call site of the live entry (the first registration this
91
- * render still wins the error message). */
92
- export const markSeenForLazyHit = (registry, trackedIds) => {
93
- for (const [boundaryId, callSite] of trackedIds) {
94
- if (!registry.seenThisRender.has(boundaryId)) {
95
- registry.seenThisRender.set(boundaryId, callSite);
96
- }
128
+ * A registration that still occupies both lookup tables needs only its seen
129
+ * marker restored. A registration evicted when the cached VNode left the DOM
130
+ * is written back transactionally, so a surrounding Submodel render that
131
+ * later fails can still roll the cache hit back. */
132
+ export const restoreBoundaryWrapsForLazyHit = (registry, trackedBoundaries) => {
133
+ for (const [boundaryId, trackedBoundary] of trackedBoundaries) {
134
+ const { callSite, descriptor, mountOuterDispatch } = trackedBoundary;
135
+ assertBoundaryNotSeen(registry, boundaryId);
136
+ registry.seenThisRender.set(boundaryId, callSite);
97
137
  for (const outerTracked of registry.lazyTrackingStack) {
98
138
  if (!outerTracked.has(boundaryId)) {
99
- outerTracked.set(boundaryId, callSite);
139
+ outerTracked.set(boundaryId, trackedBoundary);
100
140
  }
101
141
  }
142
+ if (registry.wraps.get(boundaryId) === descriptor &&
143
+ registry.mountWraps.get(mountOuterDispatch)?.get(boundaryId) ===
144
+ descriptor) {
145
+ continue;
146
+ }
147
+ writeBoundaryWrap(registry, boundaryId, descriptor, mountOuterDispatch, callSite);
102
148
  }
103
149
  };
104
- /** Removes a boundary's wrap. Called by `h.submodel`'s destroy hook when
105
- * the corresponding vnode leaves the DOM.
150
+ /** Removes a boundary's wrap when it still matches the descriptor owned by the
151
+ * VNode being destroyed. A replacement registered earlier in the same patch
152
+ * is left intact. Called by `h.submodel`'s destroy hook when the corresponding
153
+ * VNode leaves the DOM.
106
154
  *
107
155
  * Does not touch `boundaryDispatches`: it is a WeakMap keyed by
108
156
  * outerDispatch, so per-outerDispatch inner Maps become unreachable and
@@ -111,8 +159,108 @@ export const markSeenForLazyHit = (registry, trackedIds) => {
111
159
  * when it cannot find an ancestor wrap, which surfaces a clear error
112
160
  * rather than letting events from a destroyed boundary silently
113
161
  * misroute. */
114
- export const deregisterBoundaryWrap = (registry, boundaryId) => {
115
- registry.wraps.delete(boundaryId);
162
+ export const deregisterBoundaryWrap = (registry, boundaryId, descriptor, mountOuterDispatch) => {
163
+ if (registry.wraps.get(boundaryId) === descriptor) {
164
+ registry.wraps.delete(boundaryId);
165
+ const mountWrapOwners = registry.mountWrapOwners.get(boundaryId);
166
+ if (mountWrapOwners !== undefined) {
167
+ for (const owner of mountWrapOwners) {
168
+ registry.mountWraps.get(owner)?.delete(boundaryId);
169
+ }
170
+ registry.mountWrapOwners.delete(boundaryId);
171
+ }
172
+ return;
173
+ }
174
+ const mountWraps = registry.mountWraps.get(mountOuterDispatch);
175
+ if (mountWraps?.get(boundaryId) === descriptor) {
176
+ mountWraps.delete(boundaryId);
177
+ const mountWrapOwners = registry.mountWrapOwners.get(boundaryId);
178
+ mountWrapOwners?.delete(mountOuterDispatch);
179
+ if (mountWrapOwners?.size === 0) {
180
+ registry.mountWrapOwners.delete(boundaryId);
181
+ }
182
+ }
183
+ };
184
+ /** Restores the registry entries replaced by one boundary registration that
185
+ * did not produce a VNode. Used when a Submodel view throws or returns
186
+ * `null`, where no destroy hook will run and cleanup must undo only that
187
+ * render owner's write without evicting descriptors retained by the
188
+ * currently rendered tree. */
189
+ const rollbackBoundaryWrapRegistration = (registry, boundaryId, descriptor, mountOuterDispatch, previousDescriptor, previousMountDescriptor) => {
190
+ if (registry.wraps.get(boundaryId) === descriptor) {
191
+ if (previousDescriptor === undefined) {
192
+ registry.wraps.delete(boundaryId);
193
+ }
194
+ else {
195
+ registry.wraps.set(boundaryId, previousDescriptor);
196
+ }
197
+ }
198
+ const mountWraps = registry.mountWraps.get(mountOuterDispatch);
199
+ if (mountWraps?.get(boundaryId) !== descriptor) {
200
+ return;
201
+ }
202
+ if (previousMountDescriptor === undefined) {
203
+ mountWraps.delete(boundaryId);
204
+ const mountWrapOwners = registry.mountWrapOwners.get(boundaryId);
205
+ mountWrapOwners?.delete(mountOuterDispatch);
206
+ if (mountWrapOwners?.size === 0) {
207
+ registry.mountWrapOwners.delete(boundaryId);
208
+ }
209
+ }
210
+ else {
211
+ mountWraps.set(boundaryId, previousMountDescriptor);
212
+ }
213
+ };
214
+ /** Starts a nested boundary-registration transaction for one Submodel view. */
215
+ export const beginBoundaryWrapTransaction = (registry) => {
216
+ const transaction = {
217
+ parent: registry.activeBoundaryWrapTransaction,
218
+ startIndex: registry.boundaryWrapTransactionLog.length,
219
+ };
220
+ registry.activeBoundaryWrapTransaction = transaction;
221
+ return transaction;
222
+ };
223
+ /** Commits a Submodel view's boundary registrations. Nested registrations stay
224
+ * in the parent transaction until that parent also produces a VNode. */
225
+ export const commitBoundaryWrapTransaction = (registry, transaction) => {
226
+ if (registry.activeBoundaryWrapTransaction !== transaction) {
227
+ throw new Error('Foldkit: boundary wrap transactions must commit in order.');
228
+ }
229
+ registry.activeBoundaryWrapTransaction = transaction.parent;
230
+ if (transaction.parent === undefined) {
231
+ registry.boundaryWrapTransactionLog.length = 0;
232
+ }
233
+ };
234
+ /** Registers an internal cache restoration with the active Submodel boundary
235
+ * transaction. Calls outside a Submodel view need no rollback and are ignored. */
236
+ export const registerBoundaryWrapTransactionRollback = (registry, value, restore) => {
237
+ if (registry.activeBoundaryWrapTransaction !== undefined) {
238
+ registry.boundaryWrapTransactionLog.push({
239
+ _tag: 'Rollback',
240
+ rollback: () => {
241
+ restore(value);
242
+ },
243
+ });
244
+ }
245
+ };
246
+ /** Restores every boundary registration performed since a Submodel view began,
247
+ * including registrations from nested Submodels whose VNodes never reached
248
+ * snabbdom. */
249
+ export const rollbackBoundaryWrapTransaction = (registry, transaction) => {
250
+ if (registry.activeBoundaryWrapTransaction !== transaction) {
251
+ throw new Error('Foldkit: boundary wrap transactions must roll back in order.');
252
+ }
253
+ const entries = registry.boundaryWrapTransactionLog.splice(transaction.startIndex);
254
+ entries.reverse();
255
+ for (const entry of entries) {
256
+ if (entry._tag === 'Registration') {
257
+ rollbackBoundaryWrapRegistration(registry, entry.boundaryId, entry.descriptor, entry.mountOuterDispatch, entry.previousDescriptor, entry.previousMountDescriptor);
258
+ }
259
+ else {
260
+ entry.rollback();
261
+ }
262
+ }
263
+ registry.activeBoundaryWrapTransaction = transaction.parent;
116
264
  };
117
265
  // NOTE: reading `_tag` can itself throw, through a getter or a Proxy trap. This
118
266
  // runs while reporting another failure, so an escape here would replace the
@@ -160,6 +308,55 @@ const liftAcrossBoundary = (descriptor, boundaryId, message) => {
160
308
  `parent's boundary.`, { cause });
161
309
  }
162
310
  };
311
+ const missingBoundaryWrapError = (mode, ancestorBoundary, boundaryId) => {
312
+ if (mode === 'Dispatch') {
313
+ return new Error(`Foldkit: dispatchAcrossBoundary missing wrap for ancestor ` +
314
+ `"${ancestorBoundary}" of boundary "${boundaryId}". The Submodel's ` +
315
+ `wrap was absent from the registry at dispatch time. A known cause: ` +
316
+ `a slot callback (an h.submodel \`viewInputs\` function value) was ` +
317
+ `invoked from a deferred context (setTimeout, Promise.then, a ` +
318
+ `stored callback) after the parent Submodel unmounted. Slot ` +
319
+ `callbacks must be invoked synchronously inside the render that ` +
320
+ `created them. It can also mean foldkit was loaded as more than ` +
321
+ `one instance (a bundler split foldkit and @foldkit/ui), so the ` +
322
+ `wrap was registered in one copy and read from another.`);
323
+ }
324
+ else if (mode === 'OnUnmount') {
325
+ return new Error(`Foldkit: resolveBoundaryDispatchThunk missing wrap for ancestor ` +
326
+ `"${ancestorBoundary}" of boundary "${boundaryId}" while resolving an ` +
327
+ `OnUnmount message. The Submodel's wrap was absent from the registry ` +
328
+ `at resolve time, which should not happen during a live render.`);
329
+ }
330
+ else if (mode === 'Scene') {
331
+ return new Error(`Foldkit: boundaryMappers missing wrap for ancestor ` +
332
+ `"${ancestorBoundary}" of boundary "${boundaryId}" while snapshotting ` +
333
+ `an OnMount lift. The Submodel's wrap was absent from the registry ` +
334
+ `during render, which should not happen for a live boundary.`);
335
+ }
336
+ else {
337
+ return new Error(`Foldkit: Mount dispatch missing wrap for ancestor ` +
338
+ `"${ancestorBoundary}" of boundary "${boundaryId}". The ` +
339
+ `Submodel's wrap was absent from the Mount's render owner.`);
340
+ }
341
+ };
342
+ const foldBoundaryChain = (wraps, boundaryId, mode, initial, fold) => {
343
+ let result = initial;
344
+ const parts = splitBoundary(boundaryId);
345
+ for (let depth = parts.length; depth > 0; depth--) {
346
+ const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
347
+ const descriptor = wraps.get(ancestorBoundary);
348
+ if (descriptor === undefined) {
349
+ throw missingBoundaryWrapError(mode, ancestorBoundary, boundaryId);
350
+ }
351
+ result = fold(result, descriptor, ancestorBoundary);
352
+ }
353
+ return result;
354
+ };
355
+ const liftBoundaryMessage = (message, descriptor, ancestorBoundary) => liftAcrossBoundary(descriptor, ancestorBoundary, message);
356
+ const collectBoundaryMapper = (mappers, descriptor) => {
357
+ mappers.push(descriptor.toParentMessage);
358
+ return mappers;
359
+ };
163
360
  /** Applies the wrapping chain for `boundaryId` from innermost to
164
361
  * outermost, then dispatches the fully-wrapped message via
165
362
  * `outerDispatch`. Called at event-fire time by the dispatcher closure
@@ -175,25 +372,7 @@ const liftAcrossBoundary = (descriptor, boundaryId, message) => {
175
372
  * wraps would produce a malformed Message that the outermost
176
373
  * `Match.tagsExhaustive` would then crash on with no useful trace. */
177
374
  const dispatchAcrossBoundary = (registry, outerDispatch, boundaryId, message) => {
178
- let wrapped = message;
179
- const parts = splitBoundary(boundaryId);
180
- for (let depth = parts.length; depth > 0; depth--) {
181
- const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
182
- const descriptor = registry.wraps.get(ancestorBoundary);
183
- if (descriptor === undefined) {
184
- throw new Error(`Foldkit: dispatchAcrossBoundary missing wrap for ancestor ` +
185
- `"${ancestorBoundary}" of boundary "${boundaryId}". The Submodel's ` +
186
- `wrap was absent from the registry at dispatch time. A known cause: ` +
187
- `a slot callback (an h.submodel \`viewInputs\` function value) was ` +
188
- `invoked from a deferred context (setTimeout, Promise.then, a ` +
189
- `stored callback) after the parent Submodel unmounted. Slot ` +
190
- `callbacks must be invoked synchronously inside the render that ` +
191
- `created them. It can also mean foldkit was loaded as more than ` +
192
- `one instance (a bundler split foldkit and @foldkit/ui), so the ` +
193
- `wrap was registered in one copy and read from another.`);
194
- }
195
- wrapped = liftAcrossBoundary(descriptor, ancestorBoundary, wrapped);
196
- }
375
+ const wrapped = foldBoundaryChain(registry.wraps, boundaryId, 'Dispatch', message, liftBoundaryMessage);
197
376
  outerDispatch(wrapped);
198
377
  };
199
378
  /** Resolves a message through `boundaryId`'s wrapping chain immediately,
@@ -213,20 +392,7 @@ export const resolveBoundaryDispatchThunk = (registry, outerDispatch, boundaryId
213
392
  if (boundaryId === ROOT_BOUNDARY) {
214
393
  return () => outerDispatch(message);
215
394
  }
216
- let wrapped = message;
217
- const parts = splitBoundary(boundaryId);
218
- for (let depth = parts.length; depth > 0; depth--) {
219
- const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
220
- const descriptor = registry.wraps.get(ancestorBoundary);
221
- if (descriptor === undefined) {
222
- throw new Error(`Foldkit: resolveBoundaryDispatchThunk missing wrap for ancestor ` +
223
- `"${ancestorBoundary}" of boundary "${boundaryId}" while resolving an ` +
224
- `OnUnmount message. The Submodel's wrap was absent from the registry ` +
225
- `at resolve time, which should not happen during a live render.`);
226
- }
227
- wrapped = liftAcrossBoundary(descriptor, ancestorBoundary, wrapped);
228
- }
229
- const rootMessage = wrapped;
395
+ const rootMessage = foldBoundaryChain(registry.wraps, boundaryId, 'OnUnmount', message, liftBoundaryMessage);
230
396
  return () => outerDispatch(rootMessage);
231
397
  };
232
398
  /** Collects the `toParentMessage` wrapping chain for `boundaryId`, innermost
@@ -238,20 +404,7 @@ export const resolveBoundaryDispatchThunk = (registry, outerDispatch, boundaryId
238
404
  * it when the mount is resolved. Throws when an ancestor wrap is missing
239
405
  * (boundary alive at call time), matching {@link dispatchAcrossBoundary}. */
240
406
  export const boundaryMappers = (registry, boundaryId) => {
241
- const parts = splitBoundary(boundaryId);
242
- const mappers = [];
243
- for (let depth = parts.length; depth > 0; depth--) {
244
- const ancestorBoundary = parts.slice(0, depth).join(BOUNDARY_SEPARATOR);
245
- const descriptor = registry.wraps.get(ancestorBoundary);
246
- if (descriptor === undefined) {
247
- throw new Error(`Foldkit: boundaryMappers missing wrap for ancestor ` +
248
- `"${ancestorBoundary}" of boundary "${boundaryId}" while snapshotting ` +
249
- `an OnMount lift. The Submodel's wrap was absent from the registry ` +
250
- `during render, which should not happen for a live boundary.`);
251
- }
252
- mappers.push(descriptor.toParentMessage);
253
- }
254
- return mappers;
407
+ return foldBoundaryChain(registry.wraps, boundaryId, 'Scene', [], collectBoundaryMapper);
255
408
  };
256
409
  export const getOrCreateBoundaryDispatch = (registry, outerDispatch, boundaryId) => {
257
410
  if (boundaryId === ROOT_BOUNDARY) {
@@ -272,17 +425,31 @@ export const getOrCreateBoundaryDispatch = (registry, outerDispatch, boundaryId)
272
425
  perOuterDispatch.set(boundaryId, dispatch);
273
426
  return dispatch;
274
427
  };
428
+ /** Returns a dispatcher for a Mount acquired inside `boundaryId`.
429
+ * The root dispatcher identifies the render owner: live and replay renders
430
+ * therefore consult separate wrap tables. Within that owner, the dispatcher
431
+ * resolves the current wrapping chain at emission time so a surviving Mount
432
+ * follows later live `toParentMessage` closures just like an event handler. */
433
+ export const resolveMountBoundaryDispatch = (registry, mountOuterDispatch, boundaryId) => {
434
+ if (boundaryId === ROOT_BOUNDARY) {
435
+ return mountOuterDispatch;
436
+ }
437
+ const dispatch = message => {
438
+ const mountWraps = registry.mountWraps.get(mountOuterDispatch);
439
+ if (mountWraps === undefined) {
440
+ throw new Error(`Foldkit: Mount dispatch missing wraps for boundary "${boundaryId}". ` +
441
+ `The Mount's render owner has no registered Submodel boundaries.`);
442
+ }
443
+ const wrapped = foldBoundaryChain(mountWraps, boundaryId, 'Mount', message, liftBoundaryMessage);
444
+ mountOuterDispatch(wrapped);
445
+ };
446
+ return dispatch;
447
+ };
275
448
  /** Called at the start of each top-level render. Clears the
276
449
  * per-render duplicate-slotId tracking map so siblings inside the
277
- * same parent boundary can be re-validated. Does NOT touch `wraps`
278
- * or `boundaryDispatches`. Those persist across renders and are
279
- * evicted by vnode destroy hooks instead.
280
- *
281
- * Clear `seenThisRender` only here, at the start of a cycle, never
282
- * between the view and patch phases. The Submodel destroy hook reads
283
- * it during patch to tell a same-cycle remount (a keyed root whose
284
- * key changed) from a true unmount; clearing it mid-cycle would
285
- * resurrect the `dispatchAcrossBoundary missing wrap` crash. */
450
+ * same parent boundary can be re-validated. Does not touch the wrap
451
+ * or dispatcher tables. Those persist across renders and are evicted
452
+ * by VNode destroy hooks instead. */
286
453
  export const beginRender = (registry) => {
287
454
  registry.seenThisRender.clear();
288
455
  registry.dedupeSeen.clear();
@@ -1,4 +1,4 @@
1
- import { type DispatchSync } from './runtimeSingleton.js';
1
+ import { type DispatchSync, type MountDispatchResolver } from './runtimeSingleton.js';
2
2
  declare const BRAND = "__childAttribute";
3
3
  /** An attribute carrying a handler that dispatches through a Submodel
4
4
  * boundary's wrapping chain. Published by Submodels (typically Foldkit's
@@ -10,9 +10,10 @@ declare const BRAND = "__childAttribute";
10
10
  * `resolveUnmount` snapshots the boundary's wrapping chain at the time the
11
11
  * group was published (child boundary alive) so `OnUnmount` can dispatch a
12
12
  * root message from a destroy hook that fires after the boundary has been
13
- * torn down. `boundaryMappers` snapshots the same chain as a pure list of
14
- * `toParentMessage` lifts (innermost first) so `OnMount` can stamp it on the
15
- * mount marker; the Scene test harness folds it to replay the lift.
13
+ * torn down. `boundaryMappers` snapshots the `toParentMessage` lifts
14
+ * (innermost first) for the Scene test harness. Groups containing `OnMount`
15
+ * also carry `resolveMountDispatch`, which binds the Mount to the acquiring
16
+ * render's dispatch owner while following that owner's current live wrappers.
16
17
  *
17
18
  * Created via {@link childAttributes}. Element constructors accept
18
19
  * `ChildAttribute` alongside `Attribute<Message>` in their attribute
@@ -23,6 +24,7 @@ export type ChildAttribute = Readonly<{
23
24
  readonly dispatch: DispatchSync;
24
25
  readonly resolveUnmount: (message: unknown) => () => void;
25
26
  readonly boundaryMappers: ReadonlyArray<(message: unknown) => unknown>;
27
+ readonly resolveMountDispatch?: MountDispatchResolver;
26
28
  }>;
27
29
  export declare const isChildAttribute: (value: unknown) => value is ChildAttribute;
28
30
  /** Captures the current boundary's dispatcher and wraps each attribute
@@ -1 +1 @@
1
- {"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAIlB,MAAM,uBAAuB,CAAA;AAE9B,QAAA,MAAM,KAAK,qBAAqB,CAAA;AAEhC;;;;;;;;;;;;;;;;cAgBc;AACd,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;IACzD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,CAAA;CACvE,CAAC,CAAA;AAEF,eAAO,MAAM,gBAAgB,UAAW,OAAO,KAAG,KAAK,IAAI,cACI,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;oCAsBoC;AACpC,eAAO,MAAM,eAAe,GAAI,SAAS,cAC3B,aAAa,CAAC,SAAS,CAAC,KACnC,aAAa,CAAC,cAAc,CAW9B,CAAA"}
1
+ {"version":3,"file":"childAttribute.d.ts","sourceRoot":"","sources":["../../src/html/childAttribute.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAK3B,MAAM,uBAAuB,CAAA;AAE9B,QAAA,MAAM,KAAK,qBAAqB,CAAA;AAKhC;;;;;;;;;;;;;;;;;cAiBc;AACd,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;IACzD,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,CAAA;IACtE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,CAAA;CACtD,CAAC,CAAA;AAEF,eAAO,MAAM,gBAAgB,UAAW,OAAO,KAAG,KAAK,IAAI,cACI,CAAA;AAE/D;;;;;;;;;;;;;;;;;;;;;;oCAsBoC;AACpC,eAAO,MAAM,eAAe,GAAI,SAAS,cAC3B,aAAa,CAAC,SAAS,CAAC,KACnC,aAAa,CAAC,cAAc,CAe9B,CAAA"}
@@ -1,5 +1,7 @@
1
- import { requireBoundaryMappers, requireDispatch, requireUnmountResolver, } from './runtimeSingleton.js';
1
+ import { Predicate } from 'effect';
2
+ import { requireBoundaryMappers, requireDispatch, requireMountDispatchResolver, requireUnmountResolver, } from './runtimeSingleton.js';
2
3
  const BRAND = '__childAttribute';
4
+ const isOnMountAttribute = (attribute) => Predicate.isTagged(attribute, 'OnMount');
3
5
  export const isChildAttribute = (value) => typeof value === 'object' && value !== null && BRAND in value;
4
6
  /** Captures the current boundary's dispatcher and wraps each attribute
5
7
  * so handlers inside it route through that boundary's wrapping chain at
@@ -28,11 +30,15 @@ export const childAttributes = (attributes) => {
28
30
  const dispatch = requireDispatch();
29
31
  const resolveUnmount = requireUnmountResolver();
30
32
  const boundaryMappers = requireBoundaryMappers();
33
+ const resolveMountDispatch = attributes.some(isOnMountAttribute)
34
+ ? requireMountDispatchResolver()
35
+ : undefined;
31
36
  return attributes.map(attribute => ({
32
37
  [BRAND]: true,
33
38
  attribute,
34
39
  dispatch,
35
40
  resolveUnmount,
36
41
  boundaryMappers,
42
+ ...(resolveMountDispatch !== undefined && { resolveMountDispatch }),
37
43
  }));
38
44
  };
@@ -1,6 +1,7 @@
1
- import { Data, Option, Schema as S } from 'effect';
1
+ import { Data, Option, Schema } from 'effect';
2
2
  import type { File } from '../file/index.js';
3
3
  import type { MountAction } from '../mount/index.js';
4
+ import { beginReplayUnmountRender, endReplayUnmountRender, flushReplayUnmountsAfterPatchFailure } from '../onUnmountModule.js';
4
5
  import { VNode } from '../vdom.js';
5
6
  import { type ChildAttribute } from './childAttribute.js';
6
7
  import { clearRuntime, requireDispatch, setRuntime } from './runtimeSingleton.js';
@@ -61,23 +62,23 @@ export type Html = VNode | null;
61
62
  export type Child = Html | string;
62
63
  /** Whether an event handler leaves the browser's default action in place or
63
64
  * prevents it synchronously. */
64
- export declare const DefaultAction: S.Literals<readonly ["Allow", "Prevent"]>;
65
+ export declare const DefaultAction: Schema.Literals<readonly ["Allow", "Prevent"]>;
65
66
  /** Whether an event handler leaves the browser's default action in place or
66
67
  * prevents it synchronously. */
67
68
  export type DefaultAction = typeof DefaultAction.Type;
68
69
  /** Whether an event continues through its DOM propagation path or stops after
69
70
  * the handlers on its current element have run. */
70
- export declare const EventPropagation: S.Literals<readonly ["Bubble", "Stop"]>;
71
+ export declare const EventPropagation: Schema.Literals<readonly ["Bubble", "Stop"]>;
71
72
  /** Whether an event continues through its DOM propagation path or stops after
72
73
  * the handlers on its current element have run. */
73
74
  export type EventPropagation = typeof EventPropagation.Type;
74
75
  /** Declarative controls for an `OnClick` handler. Omitted
75
76
  * fields keep the browser default, allow the click to bubble, and leave focus
76
77
  * unchanged. */
77
- export declare const ClickOptions: S.Struct<{
78
- readonly defaultAction: S.optional<S.Literals<readonly ["Allow", "Prevent"]>>;
79
- readonly propagation: S.optional<S.Literals<readonly ["Bubble", "Stop"]>>;
80
- readonly focusSelector: S.optional<S.String>;
78
+ export declare const ClickOptions: Schema.Struct<{
79
+ readonly defaultAction: Schema.optional<Schema.Literals<readonly ["Allow", "Prevent"]>>;
80
+ readonly propagation: Schema.optional<Schema.Literals<readonly ["Bubble", "Stop"]>>;
81
+ readonly focusSelector: Schema.optional<Schema.String>;
81
82
  }>;
82
83
  /** Declarative controls for an `OnClick` handler. Omitted
83
84
  * fields keep the browser default, allow the click to bubble, and leave focus
@@ -85,7 +86,7 @@ export declare const ClickOptions: S.Struct<{
85
86
  export type ClickOptions = typeof ClickOptions.Type;
86
87
  /** Text direction for the document root, applied to `dir` on the `<html>`
87
88
  * element. `Auto` defers to the browser's first-strong-character heuristic. */
88
- export declare const TextDirection: S.Literals<readonly ["Ltr", "Rtl", "Auto"]>;
89
+ export declare const TextDirection: Schema.Literals<readonly ["Ltr", "Rtl", "Auto"]>;
89
90
  /** Text direction for the document root, applied to `dir` on the `<html>`
90
91
  * element. `Auto` defers to the browser's first-strong-character heuristic. */
91
92
  export type TextDirection = typeof TextDirection.Type;
@@ -125,13 +126,16 @@ export type Document = Readonly<{
125
126
  export type TagName = 'a' | 'abbr' | 'address' | 'area' | 'article' | 'aside' | 'audio' | 'b' | 'base' | 'bdi' | 'bdo' | 'blockquote' | 'body' | 'br' | 'button' | 'canvas' | 'caption' | 'cite' | 'code' | 'col' | 'colgroup' | 'data' | 'datalist' | 'dd' | 'del' | 'details' | 'dfn' | 'dialog' | 'div' | 'dl' | 'dt' | 'em' | 'embed' | 'fieldset' | 'figcaption' | 'figure' | 'footer' | 'form' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'head' | 'header' | 'hgroup' | 'hr' | 'html' | 'i' | 'iframe' | 'img' | 'input' | 'ins' | 'kbd' | 'label' | 'legend' | 'li' | 'link' | 'main' | 'map' | 'mark' | 'menu' | 'meta' | 'meter' | 'nav' | 'noscript' | 'object' | 'ol' | 'optgroup' | 'option' | 'output' | 'p' | 'picture' | 'portal' | 'pre' | 'progress' | 'q' | 'rp' | 'rt' | 'ruby' | 's' | 'samp' | 'script' | 'search' | 'section' | 'select' | 'slot' | 'small' | 'source' | 'span' | 'strong' | 'style' | 'sub' | 'summary' | 'sup' | 'table' | 'tbody' | 'td' | 'template' | 'textarea' | 'tfoot' | 'th' | 'thead' | 'time' | 'title' | 'tr' | 'track' | 'u' | 'ul' | 'var' | 'video' | 'wbr' | 'animate' | 'animateMotion' | 'animateTransform' | 'circle' | 'clipPath' | 'defs' | 'desc' | 'ellipse' | 'feBlend' | 'feColorMatrix' | 'feComponentTransfer' | 'feComposite' | 'feConvolveMatrix' | 'feDiffuseLighting' | 'feDisplacementMap' | 'feDistantLight' | 'feDropShadow' | 'feFlood' | 'feFuncA' | 'feFuncB' | 'feFuncG' | 'feFuncR' | 'feGaussianBlur' | 'feImage' | 'feMerge' | 'feMergeNode' | 'feMorphology' | 'feOffset' | 'fePointLight' | 'feSpecularLighting' | 'feSpotLight' | 'feTile' | 'feTurbulence' | 'filter' | 'foreignObject' | 'g' | 'image' | 'line' | 'linearGradient' | 'marker' | 'mask' | 'metadata' | 'mpath' | 'path' | 'pattern' | 'polygon' | 'polyline' | 'radialGradient' | 'rect' | 'set' | 'stop' | 'svg' | 'switch' | 'symbol' | 'text' | 'textPath' | 'tspan' | 'use' | 'view' | 'annotation' | 'annotation-xml' | 'math' | 'maction' | 'menclose' | 'merror' | 'mfenced' | 'mfrac' | 'mglyph' | 'mi' | 'mlabeledtr' | 'mlongdiv' | 'mmultiscripts' | 'mn' | 'mo' | 'mover' | 'mpadded' | 'mphantom' | 'mprescripts' | 'mroot' | 'mrow' | 'ms' | 'mscarries' | 'mscarry' | 'msgroup' | 'msline' | 'mspace' | 'msqrt' | 'msrow' | 'mstack' | 'mstyle' | 'msub' | 'msubsup' | 'msup' | 'mtable' | 'mtd' | 'mtext' | 'mtr' | 'munder' | 'munderover' | 'semantics';
126
127
  /** Opens a replay-render window. The runtime calls this immediately before a
127
128
  * DevTools time-travel render and closes it with {@link __endReplayRender}
128
- * afterward, so any `OnUnmount` destroy hook that fires while the replayed
129
- * tree is patched in skips its dispatch. Without the gate the hook would
129
+ * afterward, so the `OnUnmount` module can defer live callbacks while the
130
+ * replayed tree is patched in. Without the gate the callback would
130
131
  * enqueue a hygiene Message into live history during mere inspection of past
131
132
  * state. */
132
- export declare const __beginReplayRender: () => void;
133
+ export declare const __beginReplayRender: typeof beginReplayUnmountRender;
133
134
  /** Closes the replay-render window opened by {@link __beginReplayRender}. */
134
- export declare const __endReplayRender: () => void;
135
+ export declare const __endReplayRender: typeof endReplayUnmountRender;
136
+ /** Dispatches the live `OnUnmount` callbacks deferred by a replay patch that
137
+ * failed and forced the runtime to discard the damaged DOM. */
138
+ export declare const __flushReplayUnmountsAfterPatchFailure: typeof flushReplayUnmountsAfterPatchFailure;
135
139
  /** Key under which the OnMount attribute stamps a `{ name }` marker on the
136
140
  * snabbdom `VNodeData`. Snabbdom passes unknown data fields through without
137
141
  * rendering them to the DOM, so the marker is invisible at runtime but
@@ -142,8 +146,9 @@ export declare const FOLDKIT_MOUNT_KEY: 'foldkitMount';
142
146
  * introspection can identify pending mounts. When the mount lives inside a
143
147
  * Submodel boundary, it also carries that boundary's `toParentMessage` chain
144
148
  * (innermost first), snapshotted at render time, so `Scene.Mount.resolve` can
145
- * replay the lift the result travels through in production. The chain is read
146
- * only by the Scene harness; production dispatch lifts via `ctx.dispatch`. */
149
+ * replay the lift the result travels through in production. Production keeps
150
+ * the Mount bound to the dispatcher owned by its acquiring render, then
151
+ * resolves that owner's latest chain when the Mount emits. */
147
152
  export type FoldkitMountMarker = Readonly<{
148
153
  name: string;
149
154
  args?: Record<string, unknown>;
@@ -1116,6 +1121,16 @@ export { Prop, OnCustomEvent };
1116
1121
  export declare const customElement: <Message>() => (tagName: string) => (attributes?: ReadonlyArray<Attribute<Message> | ChildAttribute>, children?: ReadonlyArray<Child>) => Html;
1117
1122
  type ElementFunction<Message> = (attributes: ReadonlyArray<Attribute<Message> | ChildAttribute>, children?: ReadonlyArray<Child>) => Html;
1118
1123
  type VoidElementFunction<Message> = (attributes: ReadonlyArray<Attribute<Message> | ChildAttribute>) => Html;
1124
+ /** An attribute accepted by textarea builders. Textarea content must use
1125
+ * `h.Value`; innerHTML does not keep the live value tracking the Model after
1126
+ * the field is dirty. */
1127
+ export type TextareaAttribute<Message> = Exclude<Attribute<Message>, Readonly<{
1128
+ _tag: 'InnerHTML';
1129
+ }>>;
1130
+ type TextValueElementFunction<Message> = (attributes: ReadonlyArray<TextareaAttribute<Message> | ChildAttribute>) => Html;
1131
+ type KeyedElementFunction<Message> = (key: PropertyKey, attributes?: ReadonlyArray<Attribute<Message> | ChildAttribute>, children?: ReadonlyArray<Child>) => Html;
1132
+ type KeyedTextValueElementFunction<Message> = (key: PropertyKey, attributes?: ReadonlyArray<TextareaAttribute<Message> | ChildAttribute>) => Html;
1133
+ type KeyedFunction<Message> = <Name extends TagName>(tagName: Name) => Name extends 'textarea' ? KeyedTextValueElementFunction<Message> : KeyedElementFunction<Message>;
1119
1134
  type HtmlElements<Message> = {
1120
1135
  a: ElementFunction<Message>;
1121
1136
  abbr: ElementFunction<Message>;
@@ -1217,7 +1232,7 @@ type HtmlElements<Message> = {
1217
1232
  tbody: ElementFunction<Message>;
1218
1233
  td: ElementFunction<Message>;
1219
1234
  template: ElementFunction<Message>;
1220
- textarea: ElementFunction<Message>;
1235
+ textarea: TextValueElementFunction<Message>;
1221
1236
  tfoot: ElementFunction<Message>;
1222
1237
  th: ElementFunction<Message>;
1223
1238
  thead: ElementFunction<Message>;
@@ -2653,7 +2668,7 @@ type MessageUniverse<Message> = Readonly<{
2653
2668
  */
2654
2669
  export type HtmlBuilder<Message> = MessageUniverse<Message> & HtmlElements<Message> & HtmlAttributes<Message> & Readonly<{
2655
2670
  empty: null;
2656
- keyed: (tagName: TagName) => (key: PropertyKey, attributes?: ReadonlyArray<Attribute<Message> | ChildAttribute>, children?: ReadonlyArray<Child>) => Html;
2671
+ keyed: KeyedFunction<Message>;
2657
2672
  submodel: <View extends AnySubmodelView>(config: SubmodelConfig<View, Message>) => Html;
2658
2673
  }>;
2659
2674
  /** @internal Returns the process-wide builder singleton retyped to a frame's