@askrjs/askr 0.0.42 → 0.0.43

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 (50) hide show
  1. package/dist/benchmark.js +2 -2
  2. package/dist/boot/index.d.ts.map +1 -1
  3. package/dist/boot/index.js +2 -0
  4. package/dist/boot/index.js.map +1 -1
  5. package/dist/common/route-activity.js +30 -0
  6. package/dist/common/route-activity.js.map +1 -0
  7. package/dist/common/router.d.ts +4 -2
  8. package/dist/common/router.d.ts.map +1 -1
  9. package/dist/data/index.d.ts +21 -2
  10. package/dist/data/index.d.ts.map +1 -1
  11. package/dist/data/index.js +56 -3
  12. package/dist/data/index.js.map +1 -1
  13. package/dist/data/invalidation-listeners.js +15 -0
  14. package/dist/data/invalidation-listeners.js.map +1 -0
  15. package/dist/renderer/dom.js +23 -15
  16. package/dist/renderer/dom.js.map +1 -1
  17. package/dist/renderer/evaluate.js +9 -13
  18. package/dist/renderer/evaluate.js.map +1 -1
  19. package/dist/renderer/for-commit.js +9 -0
  20. package/dist/renderer/for-commit.js.map +1 -1
  21. package/dist/resources/index.d.ts +2 -2
  22. package/dist/resources/index.js +2 -2
  23. package/dist/router/match.js +31 -6
  24. package/dist/router/match.js.map +1 -1
  25. package/dist/router/navigate.d.ts.map +1 -1
  26. package/dist/router/navigate.js +30 -4
  27. package/dist/router/navigate.js.map +1 -1
  28. package/dist/router/route.d.ts +6 -1
  29. package/dist/router/route.d.ts.map +1 -1
  30. package/dist/router/route.js +35 -8
  31. package/dist/router/route.js.map +1 -1
  32. package/dist/runtime/component.d.ts +7 -1
  33. package/dist/runtime/component.d.ts.map +1 -1
  34. package/dist/runtime/component.js +205 -44
  35. package/dist/runtime/component.js.map +1 -1
  36. package/dist/runtime/fastlane.js +6 -0
  37. package/dist/runtime/fastlane.js.map +1 -1
  38. package/dist/runtime/operations.d.ts +11 -3
  39. package/dist/runtime/operations.d.ts.map +1 -1
  40. package/dist/runtime/operations.js +161 -22
  41. package/dist/runtime/operations.js.map +1 -1
  42. package/dist/runtime/readable.d.ts +1 -0
  43. package/dist/runtime/readable.d.ts.map +1 -1
  44. package/dist/runtime/readable.js +7 -4
  45. package/dist/runtime/readable.js.map +1 -1
  46. package/dist/testing/index.d.ts +53 -0
  47. package/dist/testing/index.d.ts.map +1 -0
  48. package/dist/testing/index.js +172 -0
  49. package/dist/testing/index.js.map +1 -0
  50. package/package.json +11 -3
@@ -2,7 +2,7 @@ import { isDevelopmentEnvironment, isProductionEnvironment } from "../common/env
2
2
  import { logger } from "../dev/logger.js";
3
3
  import { globalScheduler } from "./scheduler.js";
4
4
  import { withContext } from "./context.js";
5
- import { cleanupReadableSubscriptions, finalizeReadableSubscriptions } from "./readable.js";
5
+ import { cleanupReadableSubscriptions, finalizeReadableSubscriptions, finalizeReadableSubscriptionsFromSnapshot } from "./readable.js";
6
6
  import { incDevCounter, setDevValue } from "./dev-namespace.js";
7
7
  import { isPromiseLike } from "../common/promise.js";
8
8
  import { isBulkCommitActive } from "./fastlane.js";
@@ -34,7 +34,9 @@ function createComponentInstance(id, fn, props, target) {
34
34
  expectedStateIndices: [],
35
35
  firstRenderComplete: false,
36
36
  mountOperations: [],
37
+ commitOperations: [],
37
38
  cleanupFns: [],
39
+ lifecycleSlots: [],
38
40
  lifecycleGeneration: 0,
39
41
  hasPendingUpdate: false,
40
42
  ownerFrame: null,
@@ -82,46 +84,166 @@ function setCurrentComponentInstance(instance) {
82
84
  function getCurrentPortalScope() {
83
85
  return currentInstance?.portalScope ?? currentPortalScope;
84
86
  }
85
- function registerMountOperation(operation) {
87
+ let currentLifecycleCommitBatch = null;
88
+ function beginLifecycleCommitBatch() {
89
+ const batch = {
90
+ parent: currentLifecycleCommitBatch,
91
+ entries: [],
92
+ entriesByInstance: /* @__PURE__ */ new Map(),
93
+ readCommits: [],
94
+ readCommitsByInstance: /* @__PURE__ */ new Map(),
95
+ renderSnapshots: [],
96
+ renderSnapshotsByInstance: /* @__PURE__ */ new Map(),
97
+ active: true
98
+ };
99
+ currentLifecycleCommitBatch = batch;
100
+ return batch;
101
+ }
102
+ function closeLifecycleCommitBatch(batch) {
103
+ if (!batch.active) return false;
104
+ batch.active = false;
105
+ currentLifecycleCommitBatch = batch.parent;
106
+ return true;
107
+ }
108
+ function enqueueLifecycleCommit(batch, instance, wasFirstMount) {
109
+ const existing = batch.entriesByInstance.get(instance);
110
+ if (existing) {
111
+ existing.wasFirstMount = existing.wasFirstMount || wasFirstMount;
112
+ return;
113
+ }
114
+ const entry = {
115
+ instance,
116
+ wasFirstMount
117
+ };
118
+ batch.entriesByInstance.set(instance, entry);
119
+ batch.entries.push(entry);
120
+ }
121
+ function enqueueReadSubscriptionCommit(batch, instance, token, pendingReadSources) {
122
+ const existing = batch.readCommitsByInstance.get(instance);
123
+ const commit = existing ?? {
124
+ instance,
125
+ token,
126
+ pendingReadSources
127
+ };
128
+ commit.token = token;
129
+ commit.pendingReadSources = pendingReadSources ? new Set(pendingReadSources) : void 0;
130
+ if (!existing) {
131
+ batch.readCommitsByInstance.set(instance, commit);
132
+ batch.readCommits.push(commit);
133
+ }
134
+ }
135
+ function enqueueInlineRenderSnapshot(batch, snapshot) {
136
+ if (batch.renderSnapshotsByInstance.has(snapshot.instance)) return;
137
+ batch.renderSnapshotsByInstance.set(snapshot.instance, snapshot);
138
+ batch.renderSnapshots.push(snapshot);
139
+ }
140
+ function captureInlineRenderSnapshot(instance) {
141
+ if (!currentLifecycleCommitBatch?.active) return;
142
+ enqueueInlineRenderSnapshot(currentLifecycleCommitBatch, {
143
+ instance,
144
+ props: instance.props,
145
+ ownerFrame: instance.ownerFrame,
146
+ portalScope: instance.portalScope,
147
+ parentInstance: instance.parentInstance,
148
+ isRoot: instance.isRoot
149
+ });
150
+ }
151
+ function finalizeInlineReadSubscriptions(instance, token, pendingReadSources) {
152
+ if (currentLifecycleCommitBatch?.active) {
153
+ enqueueReadSubscriptionCommit(currentLifecycleCommitBatch, instance, token, pendingReadSources);
154
+ return;
155
+ }
156
+ finalizeReadableSubscriptionsFromSnapshot(instance, token, pendingReadSources);
157
+ }
158
+ function flushLifecycleCommitBatch(batch) {
159
+ if (!closeLifecycleCommitBatch(batch)) return;
160
+ if (batch.parent?.active) {
161
+ for (const snapshot of batch.renderSnapshots) enqueueInlineRenderSnapshot(batch.parent, snapshot);
162
+ for (const commit of batch.readCommits) enqueueReadSubscriptionCommit(batch.parent, commit.instance, commit.token, commit.pendingReadSources);
163
+ for (const entry of batch.entries) enqueueLifecycleCommit(batch.parent, entry.instance, entry.wasFirstMount);
164
+ return;
165
+ }
166
+ for (const commit of batch.readCommits) finalizeReadableSubscriptionsFromSnapshot(commit.instance, commit.token, commit.pendingReadSources);
167
+ for (const entry of batch.entries) executeCommittedLifecycleOperations(entry.instance, entry.wasFirstMount);
168
+ }
169
+ function discardLifecycleCommitBatch(batch) {
170
+ if (!closeLifecycleCommitBatch(batch)) return;
171
+ for (let index = batch.renderSnapshots.length - 1; index >= 0; index -= 1) {
172
+ const snapshot = batch.renderSnapshots[index];
173
+ snapshot.instance.props = snapshot.props;
174
+ snapshot.instance.ownerFrame = snapshot.ownerFrame;
175
+ snapshot.instance.portalScope = snapshot.portalScope;
176
+ snapshot.instance.parentInstance = snapshot.parentInstance;
177
+ snapshot.instance.isRoot = snapshot.isRoot;
178
+ }
179
+ for (const entry of batch.entries) {
180
+ if (entry.wasFirstMount) entry.instance.mountOperations = [];
181
+ discardCommitOperations(entry.instance);
182
+ }
183
+ }
184
+ function registerCommitOperation(operation) {
86
185
  const instance = getCurrentComponentInstance();
87
186
  if (instance) {
88
187
  if (isBulkCommitActive()) {
89
- if (isDevelopmentEnvironment()) throw new Error("registerMountOperation called during bulk commit fast-lane");
188
+ if (isDevelopmentEnvironment()) throw new Error("registerCommitOperation called during bulk commit fast-lane");
90
189
  return;
91
190
  }
92
- instance.mountOperations.push(operation);
191
+ instance.commitOperations.push(operation);
93
192
  }
94
193
  }
194
+ function settleLifecycleOperationResult(instance, lifecycleGeneration, result) {
195
+ if (isPromiseLike(result)) Promise.resolve(result).then((cleanup) => {
196
+ if (typeof cleanup === "function") {
197
+ if (instance.lifecycleGeneration === lifecycleGeneration && instance.mounted) {
198
+ instance.cleanupFns.push(cleanup);
199
+ return;
200
+ }
201
+ try {
202
+ cleanup();
203
+ } catch (err) {
204
+ logger.error("[Askr] async mount cleanup failed:", err);
205
+ }
206
+ }
207
+ }, (err) => {
208
+ logger.error("[Askr] async mount operation failed:", err);
209
+ });
210
+ else if (typeof result === "function") instance.cleanupFns.push(result);
211
+ }
95
212
  /**
96
213
  * Execute all mount operations for a component
97
214
  * These run after the component is rendered and mounted to the DOM
98
215
  */
99
216
  function executeMountOperations(instance) {
100
- if (!instance.isRoot) return;
101
217
  const mountOperations = instance.mountOperations;
102
218
  if (mountOperations.length === 0) return;
103
219
  const lifecycleGeneration = instance.lifecycleGeneration;
104
- for (const operation of mountOperations) {
105
- const result = operation();
106
- if (isPromiseLike(result)) Promise.resolve(result).then((cleanup) => {
107
- if (typeof cleanup === "function") {
108
- if (instance.lifecycleGeneration === lifecycleGeneration && instance.mounted) {
109
- instance.cleanupFns.push(cleanup);
110
- return;
111
- }
112
- try {
113
- cleanup();
114
- } catch (err) {
115
- logger.error("[Askr] async mount cleanup failed:", err);
116
- }
117
- }
118
- }, (err) => {
119
- logger.error("[Askr] async mount operation failed:", err);
120
- });
121
- else if (typeof result === "function") instance.cleanupFns.push(result);
122
- }
220
+ for (const operation of mountOperations) settleLifecycleOperationResult(instance, lifecycleGeneration, operation());
123
221
  instance.mountOperations = [];
124
222
  }
223
+ function executeCommitOperations(instance) {
224
+ const commitOperations = instance.commitOperations;
225
+ if (commitOperations.length === 0) return;
226
+ instance.commitOperations = [];
227
+ const lifecycleGeneration = instance.lifecycleGeneration;
228
+ for (const operation of commitOperations) settleLifecycleOperationResult(instance, lifecycleGeneration, operation());
229
+ }
230
+ function discardCommitOperations(instance) {
231
+ instance.commitOperations = [];
232
+ }
233
+ function executeCommittedLifecycleOperations(instance, wasFirstMount) {
234
+ if (wasFirstMount && instance.mountOperations.length > 0) executeMountOperations(instance);
235
+ if (instance.commitOperations.length > 0) executeCommitOperations(instance);
236
+ }
237
+ function commitLifecycleForInstance(instance, wasFirstMount) {
238
+ if (currentLifecycleCommitBatch) {
239
+ enqueueLifecycleCommit(currentLifecycleCommitBatch, instance, wasFirstMount);
240
+ return;
241
+ }
242
+ executeCommittedLifecycleOperations(instance, wasFirstMount);
243
+ }
244
+ function commitRenderedComponent(instance) {
245
+ if (instance.mounted && instance.commitOperations.length > 0) commitLifecycleForInstance(instance, false);
246
+ }
125
247
  function mountInstanceInline(instance, target) {
126
248
  instance.target = target;
127
249
  try {
@@ -136,7 +258,7 @@ function mountInstanceInline(instance, target) {
136
258
  instance.notifyUpdate = instance._enqueueRun;
137
259
  const wasFirstMount = !instance.mounted;
138
260
  instance.mounted = true;
139
- if (wasFirstMount && instance.mountOperations.length > 0) executeMountOperations(instance);
261
+ commitLifecycleForInstance(instance, wasFirstMount);
140
262
  }
141
263
  /**
142
264
  * Run a component synchronously: execute function, handle result
@@ -187,7 +309,13 @@ function runComponent(instance) {
187
309
  instance._currentRenderToken = nextRenderToken();
188
310
  instance._pendingReadSources = void 0;
189
311
  const domSnapshot = instance.target ? instance.target.innerHTML : "";
190
- const result = executeComponentSync(instance);
312
+ let result;
313
+ try {
314
+ result = executeComponentSync(instance);
315
+ } catch (err) {
316
+ discardCommitOperations(instance);
317
+ throw err;
318
+ }
191
319
  if (isPromiseLike(result)) throw new Error("Async components are not supported. Components must be synchronous.");
192
320
  else {
193
321
  const fastlaneBridge = globalThis.__ASKR_FASTLANE;
@@ -204,6 +332,7 @@ function runComponent(instance) {
204
332
  if (result === null || result === void 0) {
205
333
  finalizeReadSubscriptions(instance);
206
334
  warnUnusedStateReads(instance);
335
+ commitRenderedComponent(instance);
207
336
  return;
208
337
  }
209
338
  const placeholder = instance._placeholder;
@@ -219,16 +348,24 @@ function runComponent(instance) {
219
348
  };
220
349
  const oldInstance = currentInstance;
221
350
  currentInstance = instance;
351
+ const lifecycleBatch = beginLifecycleCommitBatch();
222
352
  try {
223
- withContext(executionFrame, () => {
224
- evaluate(result, host);
225
- });
226
- parent.replaceChild(host, placeholder);
353
+ try {
354
+ withContext(executionFrame, () => {
355
+ evaluate(result, host);
356
+ });
357
+ parent.replaceChild(host, placeholder);
358
+ } catch (err) {
359
+ discardLifecycleCommitBatch(lifecycleBatch);
360
+ throw err;
361
+ }
227
362
  instance.target = host;
228
363
  instance._placeholder = void 0;
229
364
  host.__ASKR_INSTANCE = instance;
365
+ flushLifecycleCommitBatch(lifecycleBatch);
230
366
  finalizeReadSubscriptions(instance);
231
367
  warnUnusedStateReads(instance);
368
+ commitRenderedComponent(instance);
232
369
  } finally {
233
370
  currentInstance = oldInstance;
234
371
  }
@@ -236,6 +373,7 @@ function runComponent(instance) {
236
373
  }
237
374
  if (instance.target) {
238
375
  let oldChildren = [];
376
+ let restoredOldChildren = false;
239
377
  try {
240
378
  const wasFirstMount = !instance.mounted;
241
379
  const oldInstance = currentInstance;
@@ -245,17 +383,27 @@ function runComponent(instance) {
245
383
  values: null
246
384
  };
247
385
  oldChildren = Array.from(instance.target.childNodes);
386
+ const lifecycleBatch = beginLifecycleCommitBatch();
248
387
  try {
249
- withContext(executionFrame, () => {
250
- evaluate(result, instance.target, void 0, instance);
251
- });
388
+ try {
389
+ withContext(executionFrame, () => {
390
+ evaluate(result, instance.target, void 0, instance);
391
+ });
392
+ } catch (e) {
393
+ discardLifecycleCommitBatch(lifecycleBatch);
394
+ throw e;
395
+ }
252
396
  } catch (e) {
253
397
  try {
254
398
  const newChildren = Array.from(instance.target.childNodes);
255
- for (const n of newChildren) try {
256
- cleanupInstancesUnder(n);
257
- } catch (err) {
258
- logger.warn("[Askr] error cleaning up failed commit children:", err);
399
+ const preservedChildren = new Set(oldChildren);
400
+ for (const n of newChildren) {
401
+ if (preservedChildren.has(n)) continue;
402
+ try {
403
+ cleanupInstancesUnder(n);
404
+ } catch (err) {
405
+ logger.warn("[Askr] error cleaning up failed commit children:", err);
406
+ }
259
407
  }
260
408
  } catch (_err) {}
261
409
  try {
@@ -263,21 +411,28 @@ function runComponent(instance) {
263
411
  setDevValue("__LAST_DOM_REPLACE_STACK_COMPONENT_RESTORE", (/* @__PURE__ */ new Error()).stack);
264
412
  } catch (e) {}
265
413
  instance.target.replaceChildren(...oldChildren);
414
+ restoredOldChildren = true;
266
415
  throw e;
267
416
  } finally {
268
417
  currentInstance = oldInstance;
269
418
  }
419
+ flushLifecycleCommitBatch(lifecycleBatch);
270
420
  finalizeReadSubscriptions(instance);
271
421
  warnUnusedStateReads(instance);
272
422
  instance.mounted = true;
273
- if (wasFirstMount && instance.mountOperations.length > 0) executeMountOperations(instance);
423
+ executeCommittedLifecycleOperations(instance, wasFirstMount);
274
424
  } catch (renderError) {
425
+ discardCommitOperations(instance);
275
426
  try {
276
427
  const currentChildren = Array.from(instance.target.childNodes);
277
- for (const n of currentChildren) try {
278
- cleanupInstancesUnder(n);
279
- } catch (err) {
280
- logger.warn("[Askr] error cleaning up partial children during rollback:", err);
428
+ const preservedChildren = restoredOldChildren ? new Set(oldChildren) : null;
429
+ for (const n of currentChildren) {
430
+ if (preservedChildren?.has(n)) continue;
431
+ try {
432
+ cleanupInstancesUnder(n);
433
+ } catch (err) {
434
+ logger.warn("[Askr] error cleaning up partial children during rollback:", err);
435
+ }
281
436
  }
282
437
  } catch (_err) {}
283
438
  try {
@@ -311,7 +466,8 @@ function renderComponentInline(instance) {
311
466
  }
312
467
  try {
313
468
  const result = executeComponentSync(instance);
314
- if (!hadToken) finalizeReadSubscriptions(instance);
469
+ if (!hadToken) finalizeInlineReadSubscriptions(instance, instance._currentRenderToken, instance._pendingReadSources);
470
+ commitRenderedComponent(instance);
315
471
  return result;
316
472
  } finally {
317
473
  instance._currentRenderToken = prevToken;
@@ -340,6 +496,7 @@ function executeComponentSync(instance) {
340
496
  currentInstance = instance;
341
497
  currentPortalScope = instance.portalScope ?? savedPortalScope;
342
498
  stateIndex = 0;
499
+ let didComplete = false;
343
500
  try {
344
501
  const renderStartTime = isDevelopmentEnvironment() ? Date.now() : 0;
345
502
  const context = { get signal() {
@@ -352,8 +509,10 @@ function executeComponentSync(instance) {
352
509
  const renderTime = Date.now() - renderStartTime;
353
510
  if (renderTime > 5) warnInstanceOnce(instance, "slow-render", `[askr] Slow render detected: ${renderTime}ms. Consider optimizing component performance.`);
354
511
  if (!instance.firstRenderComplete) instance.firstRenderComplete = true;
512
+ didComplete = true;
355
513
  return result;
356
514
  } finally {
515
+ if (!didComplete) discardCommitOperations(instance);
357
516
  currentInstance = null;
358
517
  currentPortalScope = savedPortalScope;
359
518
  }
@@ -495,6 +654,8 @@ function cleanupComponent(instance) {
495
654
  instance.lifecycleGeneration++;
496
655
  instance.evaluationGeneration++;
497
656
  instance.mountOperations = [];
657
+ instance.commitOperations = [];
658
+ instance.lifecycleSlots = [];
498
659
  instance.hasPendingUpdate = false;
499
660
  instance.notifyUpdate = null;
500
661
  instance._placeholder = void 0;
@@ -519,6 +680,6 @@ function warnInstanceOnce(instance, key, message) {
519
680
  logger.warn(message);
520
681
  }
521
682
  //#endregion
522
- export { claimHookIndex, cleanupComponent, createComponentInstance, finalizeReadSubscriptions, getCurrentComponentInstance, getCurrentInstance, getCurrentPortalScope, getCurrentStateIndex, getSignal, mountComponent, mountInstanceInline, registerMountOperation, registerOwnedChildScope, renderComponentInline, renderScopedComponent, setCurrentComponentInstance, unregisterOwnedChildScope, warnUnusedStateReads };
683
+ export { captureInlineRenderSnapshot, claimHookIndex, cleanupComponent, createComponentInstance, finalizeReadSubscriptions, getCurrentComponentInstance, getCurrentInstance, getCurrentPortalScope, getCurrentStateIndex, getSignal, mountComponent, mountInstanceInline, registerCommitOperation, registerOwnedChildScope, renderComponentInline, renderScopedComponent, setCurrentComponentInstance, unregisterOwnedChildScope, warnUnusedStateReads };
523
684
 
524
685
  //# sourceMappingURL=component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","names":[],"sources":["../../src/runtime/component.ts"],"sourcesContent":["/**\n * Component instance lifecycle management\n * Internal only — users never see this\n */\n\nimport { type State } from './state';\nimport { globalScheduler } from './scheduler';\nimport type { Props } from '../common/props';\nimport type { ComponentFunction } from '../common/component';\nimport {\n // withContext is the sole primitive for context restoration\n withContext,\n type ContextFrame,\n} from './context';\nimport {\n type ReadableSource,\n finalizeReadableSubscriptions,\n cleanupReadableSubscriptions,\n} from './readable';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport { incDevCounter, setDevValue } from './dev-namespace';\nimport { isPromiseLike } from '../common/promise';\n\nexport type { ComponentFunction } from '../common/component';\n\nexport interface ComponentInstance {\n id: string;\n fn: ComponentFunction;\n props: Props;\n target: Element | null;\n parentInstance: ComponentInstance | null;\n portalScope: object | null;\n mounted: boolean;\n abortController: AbortController | null; // Lazily created per-component abort lifecycle\n ssr?: boolean; // Set to true for SSR temporary instances\n // Opt-in strict cleanup mode: when true cleanup errors are aggregated and re-thrown\n cleanupStrict?: boolean;\n stateValues: State<unknown>[]; // Persistent state storage across renders\n evaluationGeneration: number; // Prevents stale async evaluation completions\n notifyUpdate: (() => void) | null; // Callback for state updates (persisted on instance)\n // Internal: prebound helpers to avoid per-update closures (allocation hot-path)\n _pendingFlushTask?: () => void; // Clears hasPendingUpdate and triggers notifyUpdate\n _pendingRunTask?: () => void; // Clears hasPendingUpdate and runs component\n _enqueueRun?: () => void; // Batches run requests and enqueues _pendingRunTask\n stateIndexCheck: number; // Track state indices to catch conditional calls\n expectedStateIndices: number[]; // Expected sequence of render-scoped hook indices (frozen after first render)\n firstRenderComplete: boolean; // Flag to detect transition from first to subsequent renders\n mountOperations: Array<\n () => void | (() => void) | PromiseLike<void | (() => void)>\n >; // Operations to run when component mounts\n cleanupFns: Array<() => void>; // Cleanup functions to run on unmount\n lifecycleGeneration: number; // Invalidates async mount-operation settlement after disposal\n hasPendingUpdate: boolean; // Flag to batch state updates (coalescing)\n ownerFrame: ContextFrame | null; // Provider chain for this component (set by Scope, never overwritten)\n isRoot?: boolean;\n\n // Render-tracking for precise subscriptions (internal)\n _currentRenderToken?: number; // Token for the in-progress render (set before render)\n lastRenderToken?: number; // Token of the last *committed* render\n _pendingReadSources?: Set<ReadableSource<unknown>>; // Readables read during the in-progress render\n _lastReadSources?: Set<ReadableSource<unknown>>; // Readables read during the last committed render\n devWarningsEmitted?: Set<string>; // Dev-only warning dedupe for this instance\n\n // Placeholder for null-returning components. When a component initially returns\n // null, we create a comment placeholder so updates can replace it with content.\n _placeholder?: Comment;\n _ownedChildScopes?: Set<{\n key: string | number;\n dispose(): void;\n }>;\n errorBoundaryState?: {\n error: unknown | null;\n resetKey: unknown;\n notified: boolean;\n };\n}\n\nexport function createComponentInstance(\n id: string,\n fn: ComponentFunction,\n props: Props,\n target: Element | null\n): ComponentInstance {\n const instance: ComponentInstance = {\n id,\n fn,\n props,\n target,\n parentInstance: currentInstance,\n portalScope: currentInstance?.portalScope ?? currentPortalScope ?? null,\n mounted: false,\n abortController: null,\n stateValues: [],\n evaluationGeneration: 0,\n notifyUpdate: null,\n // Prebound helpers (initialized below) to avoid per-update allocations\n _pendingFlushTask: undefined,\n _pendingRunTask: undefined,\n _enqueueRun: undefined,\n stateIndexCheck: -1,\n expectedStateIndices: [],\n firstRenderComplete: false,\n mountOperations: [],\n cleanupFns: [],\n lifecycleGeneration: 0,\n hasPendingUpdate: false,\n ownerFrame: null, // Will be set by renderer when vnode is marked\n ssr: false,\n cleanupStrict: false,\n isRoot: false,\n\n // Render-tracking (for precise state subscriptions)\n _currentRenderToken: undefined,\n lastRenderToken: 0,\n _pendingReadSources: undefined,\n _lastReadSources: undefined,\n devWarningsEmitted: undefined,\n };\n\n // Initialize prebound helper tasks once per instance to avoid allocations\n instance._pendingRunTask = () => {\n // Clear pending flag when the run task executes\n instance.hasPendingUpdate = false;\n if (instance.notifyUpdate === null) {\n return;\n }\n // Execute component run (will set up notifyUpdate before render)\n runComponent(instance);\n };\n\n instance._enqueueRun = () => {\n if (!instance.hasPendingUpdate) {\n instance.hasPendingUpdate = true;\n // Enqueue single run task (coalesces multiple writes)\n globalScheduler.enqueue(instance._pendingRunTask!);\n }\n };\n\n // Default state-driven updates enqueue the run task directly. Specialized\n // runtimes (for example `For` item instances) can still override this hook.\n instance._pendingFlushTask = instance._pendingRunTask;\n\n return instance;\n}\n\nlet currentInstance: ComponentInstance | null = null;\nlet currentPortalScope: object | null = null;\nlet stateIndex = 0;\n\ntype OwnedChildScope = {\n key: string | number;\n dispose(): void;\n};\n\nfunction ensureAbortController(instance: ComponentInstance): AbortController {\n let controller = instance.abortController;\n if (!controller || controller.signal.aborted) {\n controller = new AbortController();\n instance.abortController = controller;\n }\n return controller;\n}\n\n// Export for state.ts to access\nexport function getCurrentComponentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n// Export for SSR to set temporary instance\nexport function setCurrentComponentInstance(\n instance: ComponentInstance | null\n): void {\n currentInstance = instance;\n currentPortalScope = instance?.portalScope ?? null;\n}\n\nexport function getCurrentPortalScope(): object | null {\n return currentInstance?.portalScope ?? currentPortalScope;\n}\n\n/**\n * Register a mount operation that will run after the component is mounted\n * Used by operations (task, on, timer, etc) to execute after render completes\n */\nimport { isBulkCommitActive } from './fastlane';\nimport { evaluate, cleanupInstancesUnder } from '../renderer';\n\nexport function registerMountOperation(\n operation: () => void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n const instance = getCurrentComponentInstance();\n if (instance) {\n // If we're in bulk-commit fast lane, registering mount operations is a\n // violation of the fast-lane preconditions. Throw in dev, otherwise ignore\n // silently in production (we must avoid scheduling work during bulk commit).\n if (isBulkCommitActive()) {\n if (isDevelopmentEnvironment()) {\n throw new Error(\n 'registerMountOperation called during bulk commit fast-lane'\n );\n }\n return;\n }\n instance.mountOperations.push(operation);\n }\n}\n\n/**\n * Execute all mount operations for a component\n * These run after the component is rendered and mounted to the DOM\n */\nfunction executeMountOperations(instance: ComponentInstance): void {\n // Only execute mount operations for root app instance. Child component\n // operations are currently registered but should not be executed (per\n // contract tests). They remain registered for cleanup purposes.\n if (!instance.isRoot) return;\n\n const mountOperations = instance.mountOperations;\n if (mountOperations.length === 0) {\n return;\n }\n\n const lifecycleGeneration = instance.lifecycleGeneration;\n\n for (const operation of mountOperations) {\n const result = operation();\n if (isPromiseLike(result)) {\n Promise.resolve(result).then(\n (cleanup) => {\n if (typeof cleanup === 'function') {\n if (\n instance.lifecycleGeneration === lifecycleGeneration &&\n instance.mounted\n ) {\n instance.cleanupFns.push(cleanup);\n return;\n }\n\n try {\n cleanup();\n } catch (err) {\n logger.error('[Askr] async mount cleanup failed:', err);\n }\n }\n },\n (err) => {\n logger.error('[Askr] async mount operation failed:', err);\n }\n );\n } else if (typeof result === 'function') {\n instance.cleanupFns.push(result);\n }\n }\n // Clear the operations array so they don't run again on subsequent renders\n instance.mountOperations = [];\n}\n\nexport function mountInstanceInline(\n instance: ComponentInstance,\n target: Element | null\n): void {\n instance.target = target;\n // Record backref on host element so renderer can clean up when the\n // node is removed. Avoids leaks if the node is detached or replaced.\n try {\n if (target instanceof Element) {\n const host = target as Element & {\n __ASKR_INSTANCE?: ComponentInstance;\n __ASKR_INSTANCES?: ComponentInstance[];\n };\n const instances = host.__ASKR_INSTANCES ?? [];\n const nextInstances = instances.filter((entry) => entry !== instance);\n nextInstances.push(instance);\n host.__ASKR_INSTANCES = nextInstances;\n host.__ASKR_INSTANCE = nextInstances[0] ?? instance;\n }\n } catch (err) {\n void err;\n }\n\n // Ensure notifyUpdate is available for async resource completions that may\n // try to trigger re-render. This mirrors the setup in executeComponent().\n // Use prebound enqueue helper to avoid allocating a new closure\n instance.notifyUpdate = instance._enqueueRun!;\n\n const wasFirstMount = !instance.mounted;\n instance.mounted = true;\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n}\n\n/**\n * Run a component synchronously: execute function, handle result\n * This is the internal workhorse that manages async continuations and generation tracking.\n * Must always be called through the scheduler.\n *\n * ACTOR INVARIANT: This function is enqueued as a task, never called directly.\n */\nlet _globalRenderCounter = 0;\n\nfunction resetRenderState(instance: ComponentInstance): void {\n instance.stateIndexCheck = -1;\n\n for (const state of instance.stateValues) {\n if (state) {\n state._hasBeenRead = false;\n }\n }\n\n instance._pendingReadSources = undefined;\n}\n\nfunction nextRenderToken(): number {\n return ++_globalRenderCounter;\n}\n\nexport function renderScopedComponent<T>(\n instance: ComponentInstance,\n startStateIndex: number,\n render: () => T\n): T {\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n const savedStateIndex = stateIndex;\n\n instance.notifyUpdate = instance._enqueueRun!;\n resetRenderState(instance);\n instance._currentRenderToken = nextRenderToken();\n\n currentInstance = instance;\n currentPortalScope = instance.portalScope ?? savedPortalScope;\n stateIndex = startStateIndex;\n\n let didComplete = false;\n\n try {\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, render);\n didComplete = true;\n return result;\n } finally {\n if (!didComplete) {\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n }\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n stateIndex = savedStateIndex;\n }\n}\n\nfunction runComponent(instance: ComponentInstance): void {\n // CRITICAL: Ensure notifyUpdate is available for state.set() calls during this render.\n // This must be set before executeComponentSync() runs, not after.\n // Use prebound enqueue helper to avoid allocating per-render closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Assign a token for this in-progress render and start a fresh pending-read set\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n const domSnapshot = instance.target ? instance.target.innerHTML : '';\n\n const result = executeComponentSync(instance);\n if (isPromiseLike(result)) {\n // Async components are not supported. Components must be synchronous and\n // must not return a Promise from their render function.\n throw new Error(\n 'Async components are not supported. Components must be synchronous.'\n );\n } else {\n // Try runtime fast-lane synchronously; if it activates we do not enqueue\n // follow-up work and the commit happens atomically in this task.\n // (Runtime fast-lane has conservative preconditions.)\n const fastlaneBridge = (\n globalThis as {\n __ASKR_FASTLANE?: {\n tryRuntimeFastLaneSync?: (\n instance: unknown,\n result: unknown\n ) => boolean;\n };\n }\n ).__ASKR_FASTLANE;\n try {\n const used = fastlaneBridge?.tryRuntimeFastLaneSync?.(instance, result);\n if (used) {\n warnUnusedStateReads(instance);\n return;\n }\n } catch (err) {\n // If invariant check failed in dev, surface the error; otherwise fall back\n if (isDevelopmentEnvironment()) throw err;\n }\n\n // Fallback: enqueue the render/commit normally\n globalScheduler.enqueue(() => {\n // Handle placeholder-based updates: when a component initially returned null,\n // we created a comment placeholder. If it now has content, we need to create\n // a host element and replace the placeholder.\n if (!instance.target && instance._placeholder) {\n // Component previously returned null (has placeholder), check if now has content\n if (result === null || result === undefined) {\n // Still null - nothing to do, keep placeholder\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n return;\n }\n\n // Has content now - need to create DOM and replace placeholder\n const placeholder = instance._placeholder;\n const parent = placeholder.parentNode;\n if (!parent) {\n // Placeholder was removed from DOM - can't render\n logger.warn(\n '[Askr] placeholder no longer in DOM, cannot render component'\n );\n return;\n }\n\n // Create a new host element for the content\n const host = document.createElement('div');\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n\n // Set up instance for normal updates\n const oldInstance = currentInstance;\n currentInstance = instance;\n try {\n withContext(executionFrame, () => {\n evaluate(result, host);\n });\n\n // Replace placeholder with host\n parent.replaceChild(host, placeholder);\n\n // Set up instance for future updates\n instance.target = host;\n instance._placeholder = undefined;\n (\n host as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = instance;\n\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n } finally {\n currentInstance = oldInstance;\n }\n return;\n }\n\n if (instance.target) {\n let oldChildren: Node[] = [];\n try {\n const wasFirstMount = !instance.mounted;\n // Ensure nested component executions during evaluation have access to\n // the current component instance. This allows nested components to\n // call `state()`, `resource()`, and other runtime helpers which\n // rely on `getCurrentComponentInstance()` being available.\n const oldInstance = currentInstance;\n currentInstance = instance;\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n // Capture snapshot of current children (by reference) so we can\n // restore them on render failure without losing event listeners or\n // instance attachments.\n oldChildren = Array.from(instance.target.childNodes);\n\n try {\n withContext(executionFrame, () => {\n evaluate(result, instance.target, undefined, instance);\n });\n } catch (e) {\n // If evaluation failed, attempt to cleanup any partially-added nodes\n // and restore the old children to preserve listeners and instances.\n try {\n const newChildren = Array.from(instance.target.childNodes);\n for (const n of newChildren) {\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up failed commit children:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n // Restore original children by re-inserting the old node references\n // this preserves attached listeners and instance backrefs.\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_RESTORE',\n new Error().stack\n );\n } catch (e) {\n void e;\n }\n instance.target.replaceChildren(...oldChildren);\n throw e;\n } finally {\n currentInstance = oldInstance;\n }\n\n // Commit succeeded — finalize recorded state reads so subscriptions reflect\n // the last *committed* render. This updates per-state reader maps\n // deterministically and synchronously with the commit.\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n\n instance.mounted = true;\n // Execute mount operations after first mount (do NOT run these with\n // currentInstance set - they may perform state mutations/registrations)\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n } catch (renderError) {\n // Atomic rendering: rollback on render error. Attempt non-lossy restore of\n // original child node references to preserve listeners/instances.\n try {\n const currentChildren = Array.from(instance.target.childNodes);\n for (const n of currentChildren) {\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up partial children during rollback:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_ROLLBACK',\n new Error().stack\n );\n instance.target.replaceChildren(...oldChildren);\n } catch {\n // Fallback to innerHTML restore if replaceChildren fails for some reason.\n instance.target.innerHTML = domSnapshot;\n }\n\n throw renderError;\n }\n }\n });\n }\n}\n\n/**\n * Execute a component's render function synchronously.\n * Returns either a vnode/promise immediately (does NOT render).\n * Rendering happens separately through runComponent.\n */\nexport function renderComponentInline(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n // Reused inline instances can cross renderer cleanup boundaries while their\n // host node is retained. Make sure state writes still enqueue this instance.\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Ensure inline executions (rendered during parent's evaluate) still\n // receive a render token and have their state reads finalized so\n // subscriptions are correctly recorded. If this function is called\n // as part of a scheduled run, the token will already be set by\n // runComponent and we should not overwrite it.\n const hadToken = instance._currentRenderToken !== undefined;\n const prevToken = instance._currentRenderToken;\n const prevPendingReads = instance._pendingReadSources;\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n if (!hadToken) {\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n }\n\n try {\n const result = executeComponentSync(instance);\n // If we set the token for inline execution, finalize subscriptions now\n // because the component is effectively committed as part of the parent's\n // synchronous evaluation.\n if (!hadToken) {\n finalizeReadSubscriptions(instance);\n }\n return result;\n } finally {\n // Restore previous token/read states for nested inline render scenarios\n instance._currentRenderToken = prevToken;\n instance._pendingReadSources = prevPendingReads;\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n }\n}\n\nexport function warnUnusedStateReads(instance: ComponentInstance): void {\n for (let i = 0; i < instance.stateValues.length; i++) {\n const state = instance.stateValues[i];\n const hasCommittedUsage =\n (state?._readers?.size ?? 0) > 0 ||\n ((state as { _derivedSubscribers?: Set<unknown> } | undefined)\n ?._derivedSubscribers?.size ?? 0) > 0;\n\n if (\n state &&\n !state._hasBeenRead &&\n !state._hasEverBeenRead &&\n !hasCommittedUsage\n ) {\n try {\n const name = instance.fn?.name || '<anonymous>';\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected in ${name} at index ${i}. State should be read during render or removed.`\n );\n } catch {\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected. State should be read during render or removed.`\n );\n }\n }\n }\n}\n\nfunction executeComponentSync(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n resetRenderState(instance);\n incDevCounter('componentRuns');\n incDevCounter('componentReruns');\n\n const savedPortalScope = currentPortalScope;\n currentInstance = instance;\n currentPortalScope = instance.portalScope ?? savedPortalScope;\n stateIndex = 0;\n\n try {\n // Track render time in dev mode\n const renderStartTime = isDevelopmentEnvironment() ? Date.now() : 0;\n\n // Create context object with abort signal\n const context = {\n get signal(): AbortSignal {\n return ensureAbortController(instance).signal;\n },\n };\n\n // Execute component within its owner frame (provider chain).\n // This ensures all context reads see the correct provider values.\n // We create a new execution frame whose parent is the ownerFrame. The\n // `values` map is lazily allocated to avoid per-render Map allocations\n // for components that do not use context.\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, () =>\n instance.fn(instance.props, context)\n );\n\n // Check render time\n const renderTime = Date.now() - renderStartTime;\n if (renderTime > 5) {\n warnInstanceOnce(\n instance,\n 'slow-render',\n `[askr] Slow render detected: ${renderTime}ms. Consider optimizing component performance.`\n );\n }\n\n // Mark first render complete after successful execution\n // This enables hook order validation on subsequent renders\n if (!instance.firstRenderComplete) {\n instance.firstRenderComplete = true;\n }\n\n return result;\n } finally {\n // Synchronous path: we did not push a fresh frame, so nothing to pop here.\n currentInstance = null;\n currentPortalScope = savedPortalScope;\n }\n}\n\n/**\n * Public entry point: Execute component with full lifecycle (execute + render)\n * Handles both initial mount and re-execution. Always enqueues through scheduler.\n * Single entry point to avoid lifecycle divergence.\n */\nexport function executeComponent(instance: ComponentInstance): void {\n // Lazily recreate abort controller only when signal is actually requested.\n instance.abortController = null;\n\n // Setup notifyUpdate callback using prebound helper to avoid per-call closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Enqueue the initial component run\n globalScheduler.enqueue(instance._pendingRunTask!);\n}\n\nexport function getCurrentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n/**\n * Get the abort signal for the current component\n * Used to cancel async operations on unmount/navigation\n *\n * The signal is guaranteed to be aborted when:\n * - Component unmounts\n * - Navigation occurs (different route)\n * - Parent is destroyed\n *\n * IMPORTANT: getSignal() must be called during component render execution.\n * It captures the current component instance from context.\n *\n * @returns AbortSignal that will be aborted when component unmounts\n * @throws Error if called outside component execution\n *\n * @example\n * ```ts\n * // ✅ Correct: called during render, used in async operation\n * export async function UserPage({ id }: { id: string }) {\n * const signal = getSignal();\n * const user = await fetch(`/api/users/${id}`, { signal });\n * return <div>{user.name}</div>;\n * }\n *\n * // ✅ Correct: passed to event handler\n * export function Button() {\n * const signal = getSignal();\n * return {\n * type: 'button',\n * props: {\n * onClick: async () => {\n * const data = await fetch(url, { signal });\n * }\n * }\n * };\n * }\n *\n * // ❌ Wrong: called outside component context\n * const signal = getSignal(); // Error: not in component\n * ```\n */\nexport function getSignal(): AbortSignal {\n if (!currentInstance) {\n throw new Error(\n 'getSignal() can only be called during component render execution. ' +\n 'Ensure you are calling this from inside your component function.'\n );\n }\n return ensureAbortController(currentInstance).signal;\n}\n\n/**\n * Finalize read subscriptions for an instance after a successful commit.\n * - Update per-state readers map to point to this instance's last committed token\n * - Remove this instance from states it no longer reads\n * This is deterministic and runs synchronously with commit to ensure\n * subscribers are only notified when they actually read a state in their\n * last committed render.\n */\nexport function finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\nexport function getNextStateIndex(): number {\n return stateIndex++;\n}\n\nexport function claimHookIndex(\n instance: ComponentInstance,\n hookName: string\n): number {\n const index = getNextStateIndex();\n\n if (index < instance.stateIndexCheck) {\n throw new Error(\n `Hook index violation: ${hookName}() call at index ${index}, ` +\n `but previously saw index ${instance.stateIndexCheck}. ` +\n `This happens when render-scoped hooks are called conditionally (inside if/for/etc). ` +\n `Move all ${hookName}() calls to the top level of your component function, ` +\n `before any conditionals.`\n );\n }\n\n instance.stateIndexCheck = index;\n\n if (instance.firstRenderComplete) {\n if (instance.expectedStateIndices[index] !== index) {\n throw new Error(\n `Hook order violation: ${hookName}() called at index ${index}, ` +\n `but this index was not in the first render's sequence [${instance.expectedStateIndices.join(', ')}]. ` +\n `This usually means ${hookName}() is inside a conditional or loop. ` +\n `Move all render-scoped hooks to the top level of your component function.`\n );\n }\n } else {\n instance.expectedStateIndices.push(index);\n }\n\n return index;\n}\n\nexport function getCurrentStateIndex(): number {\n return stateIndex;\n}\n\nexport function resetStateIndex(): void {\n stateIndex = 0;\n}\n\nexport function setStateIndex(value: number): void {\n stateIndex = value;\n}\n\n/**\n * Mount a component instance.\n * This is just an alias to executeComponent() to maintain API compatibility.\n * All lifecycle logic is unified in executeComponent().\n */\nexport function mountComponent(instance: ComponentInstance): void {\n executeComponent(instance);\n}\n\n/**\n * Clean up component — abort pending operations\n * Called on unmount or route change\n */\nexport function cleanupComponent(instance: ComponentInstance): void {\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n currentInstance = null;\n currentPortalScope = null;\n\n try {\n const cleanupErrors: unknown[] = [];\n const recordCleanupError = (message: string, err: unknown): void => {\n if (instance.cleanupStrict) {\n cleanupErrors.push(err);\n } else if (isDevelopmentEnvironment()) {\n logger.warn(message, err);\n }\n };\n\n const ownedChildScopes = instance._ownedChildScopes;\n if (ownedChildScopes && ownedChildScopes.size > 0) {\n instance._ownedChildScopes = new Set();\n for (const scope of ownedChildScopes) {\n try {\n scope.dispose();\n } catch (err) {\n recordCleanupError('[Askr] child scope cleanup threw:', err);\n }\n }\n }\n\n // Execute cleanup functions (from mount effects)\n const cleanupFns = instance.cleanupFns;\n instance.cleanupFns = [];\n for (const cleanup of cleanupFns) {\n try {\n cleanup();\n } catch (err) {\n recordCleanupError('[Askr] cleanup function threw:', err);\n }\n }\n\n // Remove deterministic state subscriptions for this instance\n try {\n cleanupReadableSubscriptions(instance);\n } catch (err) {\n recordCleanupError('[Askr] readable subscription cleanup threw:', err);\n }\n\n // Abort all pending operations\n try {\n if (\n instance.abortController &&\n !instance.abortController.signal.aborted\n ) {\n instance.abortController.abort();\n }\n } catch (err) {\n recordCleanupError('[Askr] abort controller cleanup threw:', err);\n }\n instance.abortController = null;\n\n // Clear update callback to prevent dangling references and stale updates\n instance.lifecycleGeneration++;\n instance.evaluationGeneration++;\n instance.mountOperations = [];\n instance.hasPendingUpdate = false;\n instance.notifyUpdate = null;\n instance._placeholder = undefined;\n\n // Mark instance as unmounted so external tracking (e.g., portal host lists)\n // can deterministically prune stale instances. Not marking this leads to\n // retained \"mounted\" flags across cleanup boundaries which breaks\n // owner selection in the portal fallback.\n instance.mounted = false;\n\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n cleanupErrors,\n `Cleanup failed for component ${instance.id}`\n );\n }\n } finally {\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n }\n}\n\nexport function registerOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n const scopes = (instance._ownedChildScopes ??= new Set());\n scopes.add(scope);\n}\n\nexport function unregisterOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n instance._ownedChildScopes?.delete(scope);\n}\n\nfunction warnInstanceOnce(\n instance: ComponentInstance,\n key: string,\n message: string\n): void {\n if (isProductionEnvironment()) return;\n const warnings = (instance.devWarningsEmitted ??= new Set());\n if (warnings.has(key)) return;\n warnings.add(key);\n logger.warn(message);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"component.js","names":[],"sources":["../../src/runtime/component.ts"],"sourcesContent":["/**\n * Component instance lifecycle management\n * Internal only — users never see this\n */\n\nimport { type State } from './state';\nimport { globalScheduler } from './scheduler';\nimport type { Props } from '../common/props';\nimport type { ComponentFunction } from '../common/component';\nimport {\n // withContext is the sole primitive for context restoration\n withContext,\n type ContextFrame,\n} from './context';\nimport {\n type ReadableSource,\n finalizeReadableSubscriptions,\n finalizeReadableSubscriptionsFromSnapshot,\n cleanupReadableSubscriptions,\n} from './readable';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport { incDevCounter, setDevValue } from './dev-namespace';\nimport { isPromiseLike } from '../common/promise';\n\nexport type { ComponentFunction } from '../common/component';\n\nexport interface ComponentInstance {\n id: string;\n fn: ComponentFunction;\n props: Props;\n target: Element | null;\n parentInstance: ComponentInstance | null;\n portalScope: object | null;\n mounted: boolean;\n abortController: AbortController | null; // Lazily created per-component abort lifecycle\n ssr?: boolean; // Set to true for SSR temporary instances\n // Opt-in strict cleanup mode: when true cleanup errors are aggregated and re-thrown\n cleanupStrict?: boolean;\n stateValues: State<unknown>[]; // Persistent state storage across renders\n evaluationGeneration: number; // Prevents stale async evaluation completions\n notifyUpdate: (() => void) | null; // Callback for state updates (persisted on instance)\n // Internal: prebound helpers to avoid per-update closures (allocation hot-path)\n _pendingFlushTask?: () => void; // Clears hasPendingUpdate and triggers notifyUpdate\n _pendingRunTask?: () => void; // Clears hasPendingUpdate and runs component\n _enqueueRun?: () => void; // Batches run requests and enqueues _pendingRunTask\n stateIndexCheck: number; // Track state indices to catch conditional calls\n expectedStateIndices: number[]; // Expected sequence of render-scoped hook indices (frozen after first render)\n firstRenderComplete: boolean; // Flag to detect transition from first to subsequent renders\n mountOperations: Array<\n () => void | (() => void) | PromiseLike<void | (() => void)>\n >; // Operations to run when component mounts\n commitOperations: Array<\n () => void | (() => void) | PromiseLike<void | (() => void)>\n >; // Operations to run after a successful committed render\n cleanupFns: Array<() => void>; // Cleanup functions to run on unmount\n lifecycleSlots: unknown[]; // Render-scoped lifecycle primitive storage\n lifecycleGeneration: number; // Invalidates async mount-operation settlement after disposal\n hasPendingUpdate: boolean; // Flag to batch state updates (coalescing)\n ownerFrame: ContextFrame | null; // Provider chain for this component (set by Scope, never overwritten)\n isRoot?: boolean;\n\n // Render-tracking for precise subscriptions (internal)\n _currentRenderToken?: number; // Token for the in-progress render (set before render)\n lastRenderToken?: number; // Token of the last *committed* render\n _pendingReadSources?: Set<ReadableSource<unknown>>; // Readables read during the in-progress render\n _lastReadSources?: Set<ReadableSource<unknown>>; // Readables read during the last committed render\n devWarningsEmitted?: Set<string>; // Dev-only warning dedupe for this instance\n\n // Placeholder for null-returning components. When a component initially returns\n // null, we create a comment placeholder so updates can replace it with content.\n _placeholder?: Comment;\n _ownedChildScopes?: Set<{\n key: string | number;\n dispose(): void;\n }>;\n errorBoundaryState?: {\n error: unknown | null;\n resetKey: unknown;\n notified: boolean;\n };\n}\n\nexport function createComponentInstance(\n id: string,\n fn: ComponentFunction,\n props: Props,\n target: Element | null\n): ComponentInstance {\n const instance: ComponentInstance = {\n id,\n fn,\n props,\n target,\n parentInstance: currentInstance,\n portalScope: currentInstance?.portalScope ?? currentPortalScope ?? null,\n mounted: false,\n abortController: null,\n stateValues: [],\n evaluationGeneration: 0,\n notifyUpdate: null,\n // Prebound helpers (initialized below) to avoid per-update allocations\n _pendingFlushTask: undefined,\n _pendingRunTask: undefined,\n _enqueueRun: undefined,\n stateIndexCheck: -1,\n expectedStateIndices: [],\n firstRenderComplete: false,\n mountOperations: [],\n commitOperations: [],\n cleanupFns: [],\n lifecycleSlots: [],\n lifecycleGeneration: 0,\n hasPendingUpdate: false,\n ownerFrame: null, // Will be set by renderer when vnode is marked\n ssr: false,\n cleanupStrict: false,\n isRoot: false,\n\n // Render-tracking (for precise state subscriptions)\n _currentRenderToken: undefined,\n lastRenderToken: 0,\n _pendingReadSources: undefined,\n _lastReadSources: undefined,\n devWarningsEmitted: undefined,\n };\n\n // Initialize prebound helper tasks once per instance to avoid allocations\n instance._pendingRunTask = () => {\n // Clear pending flag when the run task executes\n instance.hasPendingUpdate = false;\n if (instance.notifyUpdate === null) {\n return;\n }\n // Execute component run (will set up notifyUpdate before render)\n runComponent(instance);\n };\n\n instance._enqueueRun = () => {\n if (!instance.hasPendingUpdate) {\n instance.hasPendingUpdate = true;\n // Enqueue single run task (coalesces multiple writes)\n globalScheduler.enqueue(instance._pendingRunTask!);\n }\n };\n\n // Default state-driven updates enqueue the run task directly. Specialized\n // runtimes (for example `For` item instances) can still override this hook.\n instance._pendingFlushTask = instance._pendingRunTask;\n\n return instance;\n}\n\nlet currentInstance: ComponentInstance | null = null;\nlet currentPortalScope: object | null = null;\nlet stateIndex = 0;\n\ntype OwnedChildScope = {\n key: string | number;\n dispose(): void;\n};\n\nfunction ensureAbortController(instance: ComponentInstance): AbortController {\n let controller = instance.abortController;\n if (!controller || controller.signal.aborted) {\n controller = new AbortController();\n instance.abortController = controller;\n }\n return controller;\n}\n\n// Export for state.ts to access\nexport function getCurrentComponentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n// Export for SSR to set temporary instance\nexport function setCurrentComponentInstance(\n instance: ComponentInstance | null\n): void {\n currentInstance = instance;\n currentPortalScope = instance?.portalScope ?? null;\n}\n\nexport function getCurrentPortalScope(): object | null {\n return currentInstance?.portalScope ?? currentPortalScope;\n}\n\n/**\n * Register a mount operation that will run after the component is mounted\n * Used by operations (task, on, timer, etc) to execute after render completes\n */\nimport { isBulkCommitActive } from './fastlane';\nimport { evaluate, cleanupInstancesUnder } from '../renderer';\n\ntype LifecycleOperation = () =>\n | void\n | (() => void)\n | PromiseLike<void | (() => void)>;\n\ntype LifecycleCommitBatchEntry = {\n instance: ComponentInstance;\n wasFirstMount: boolean;\n};\n\ntype ReadSubscriptionCommit = {\n instance: ComponentInstance;\n token: number;\n pendingReadSources: Set<ReadableSource<unknown>> | undefined;\n};\n\ntype InlineRenderSnapshot = {\n instance: ComponentInstance;\n props: Props;\n ownerFrame: ContextFrame | null;\n portalScope: object | null;\n parentInstance: ComponentInstance | null;\n isRoot: boolean | undefined;\n};\n\ntype LifecycleCommitBatch = {\n parent: LifecycleCommitBatch | null;\n entries: LifecycleCommitBatchEntry[];\n entriesByInstance: Map<ComponentInstance, LifecycleCommitBatchEntry>;\n readCommits: ReadSubscriptionCommit[];\n readCommitsByInstance: Map<ComponentInstance, ReadSubscriptionCommit>;\n renderSnapshots: InlineRenderSnapshot[];\n renderSnapshotsByInstance: Map<ComponentInstance, InlineRenderSnapshot>;\n active: boolean;\n};\n\nlet currentLifecycleCommitBatch: LifecycleCommitBatch | null = null;\n\nfunction beginLifecycleCommitBatch(): LifecycleCommitBatch {\n const batch: LifecycleCommitBatch = {\n parent: currentLifecycleCommitBatch,\n entries: [],\n entriesByInstance: new Map(),\n readCommits: [],\n readCommitsByInstance: new Map(),\n renderSnapshots: [],\n renderSnapshotsByInstance: new Map(),\n active: true,\n };\n currentLifecycleCommitBatch = batch;\n return batch;\n}\n\nfunction closeLifecycleCommitBatch(batch: LifecycleCommitBatch): boolean {\n if (!batch.active) {\n return false;\n }\n\n batch.active = false;\n currentLifecycleCommitBatch = batch.parent;\n return true;\n}\n\nfunction enqueueLifecycleCommit(\n batch: LifecycleCommitBatch,\n instance: ComponentInstance,\n wasFirstMount: boolean\n): void {\n const existing = batch.entriesByInstance.get(instance);\n if (existing) {\n existing.wasFirstMount = existing.wasFirstMount || wasFirstMount;\n return;\n }\n\n const entry = { instance, wasFirstMount };\n batch.entriesByInstance.set(instance, entry);\n batch.entries.push(entry);\n}\n\nfunction enqueueReadSubscriptionCommit(\n batch: LifecycleCommitBatch,\n instance: ComponentInstance,\n token: number,\n pendingReadSources: Set<ReadableSource<unknown>> | undefined\n): void {\n const existing = batch.readCommitsByInstance.get(instance);\n const commit = existing ?? {\n instance,\n token,\n pendingReadSources,\n };\n\n commit.token = token;\n commit.pendingReadSources = pendingReadSources\n ? new Set(pendingReadSources)\n : undefined;\n\n if (!existing) {\n batch.readCommitsByInstance.set(instance, commit);\n batch.readCommits.push(commit);\n }\n}\n\nfunction enqueueInlineRenderSnapshot(\n batch: LifecycleCommitBatch,\n snapshot: InlineRenderSnapshot\n): void {\n if (batch.renderSnapshotsByInstance.has(snapshot.instance)) {\n return;\n }\n\n batch.renderSnapshotsByInstance.set(snapshot.instance, snapshot);\n batch.renderSnapshots.push(snapshot);\n}\n\nexport function captureInlineRenderSnapshot(instance: ComponentInstance): void {\n if (!currentLifecycleCommitBatch?.active) {\n return;\n }\n\n enqueueInlineRenderSnapshot(currentLifecycleCommitBatch, {\n instance,\n props: instance.props,\n ownerFrame: instance.ownerFrame,\n portalScope: instance.portalScope,\n parentInstance: instance.parentInstance,\n isRoot: instance.isRoot,\n });\n}\n\nfunction finalizeInlineReadSubscriptions(\n instance: ComponentInstance,\n token: number,\n pendingReadSources: Set<ReadableSource<unknown>> | undefined\n): void {\n if (currentLifecycleCommitBatch?.active) {\n enqueueReadSubscriptionCommit(\n currentLifecycleCommitBatch,\n instance,\n token,\n pendingReadSources\n );\n return;\n }\n\n finalizeReadableSubscriptionsFromSnapshot(\n instance,\n token,\n pendingReadSources\n );\n}\n\nfunction flushLifecycleCommitBatch(batch: LifecycleCommitBatch): void {\n if (!closeLifecycleCommitBatch(batch)) {\n return;\n }\n\n if (batch.parent?.active) {\n for (const snapshot of batch.renderSnapshots) {\n enqueueInlineRenderSnapshot(batch.parent, snapshot);\n }\n for (const commit of batch.readCommits) {\n enqueueReadSubscriptionCommit(\n batch.parent,\n commit.instance,\n commit.token,\n commit.pendingReadSources\n );\n }\n for (const entry of batch.entries) {\n enqueueLifecycleCommit(batch.parent, entry.instance, entry.wasFirstMount);\n }\n return;\n }\n\n for (const commit of batch.readCommits) {\n finalizeReadableSubscriptionsFromSnapshot(\n commit.instance,\n commit.token,\n commit.pendingReadSources\n );\n }\n\n for (const entry of batch.entries) {\n executeCommittedLifecycleOperations(entry.instance, entry.wasFirstMount);\n }\n}\n\nfunction discardLifecycleCommitBatch(batch: LifecycleCommitBatch): void {\n if (!closeLifecycleCommitBatch(batch)) {\n return;\n }\n\n for (let index = batch.renderSnapshots.length - 1; index >= 0; index -= 1) {\n const snapshot = batch.renderSnapshots[index]!;\n snapshot.instance.props = snapshot.props;\n snapshot.instance.ownerFrame = snapshot.ownerFrame;\n snapshot.instance.portalScope = snapshot.portalScope;\n snapshot.instance.parentInstance = snapshot.parentInstance;\n snapshot.instance.isRoot = snapshot.isRoot;\n }\n\n for (const entry of batch.entries) {\n if (entry.wasFirstMount) {\n entry.instance.mountOperations = [];\n }\n discardCommitOperations(entry.instance);\n }\n}\n\nexport function registerMountOperation(operation: LifecycleOperation): void {\n const instance = getCurrentComponentInstance();\n if (instance) {\n // If we're in bulk-commit fast lane, registering mount operations is a\n // violation of the fast-lane preconditions. Throw in dev, otherwise ignore\n // silently in production (we must avoid scheduling work during bulk commit).\n if (isBulkCommitActive()) {\n if (isDevelopmentEnvironment()) {\n throw new Error(\n 'registerMountOperation called during bulk commit fast-lane'\n );\n }\n return;\n }\n instance.mountOperations.push(operation);\n }\n}\n\nexport function registerCommitOperation(operation: LifecycleOperation): void {\n const instance = getCurrentComponentInstance();\n if (instance) {\n if (isBulkCommitActive()) {\n if (isDevelopmentEnvironment()) {\n throw new Error(\n 'registerCommitOperation called during bulk commit fast-lane'\n );\n }\n return;\n }\n instance.commitOperations.push(operation);\n }\n}\n\nfunction settleLifecycleOperationResult(\n instance: ComponentInstance,\n lifecycleGeneration: number,\n result: void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n if (isPromiseLike(result)) {\n Promise.resolve(result).then(\n (cleanup) => {\n if (typeof cleanup === 'function') {\n if (\n instance.lifecycleGeneration === lifecycleGeneration &&\n instance.mounted\n ) {\n instance.cleanupFns.push(cleanup);\n return;\n }\n\n try {\n cleanup();\n } catch (err) {\n logger.error('[Askr] async mount cleanup failed:', err);\n }\n }\n },\n (err) => {\n logger.error('[Askr] async mount operation failed:', err);\n }\n );\n } else if (typeof result === 'function') {\n instance.cleanupFns.push(result);\n }\n}\n\n/**\n * Execute all mount operations for a component\n * These run after the component is rendered and mounted to the DOM\n */\nfunction executeMountOperations(instance: ComponentInstance): void {\n const mountOperations = instance.mountOperations;\n if (mountOperations.length === 0) {\n return;\n }\n\n const lifecycleGeneration = instance.lifecycleGeneration;\n\n for (const operation of mountOperations) {\n settleLifecycleOperationResult(instance, lifecycleGeneration, operation());\n }\n // Clear the operations array so they don't run again on subsequent renders\n instance.mountOperations = [];\n}\n\nfunction executeCommitOperations(instance: ComponentInstance): void {\n const commitOperations = instance.commitOperations;\n if (commitOperations.length === 0) {\n return;\n }\n\n instance.commitOperations = [];\n const lifecycleGeneration = instance.lifecycleGeneration;\n\n for (const operation of commitOperations) {\n settleLifecycleOperationResult(instance, lifecycleGeneration, operation());\n }\n}\n\nfunction discardCommitOperations(instance: ComponentInstance): void {\n instance.commitOperations = [];\n}\n\nfunction executeCommittedLifecycleOperations(\n instance: ComponentInstance,\n wasFirstMount: boolean\n): void {\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n if (instance.commitOperations.length > 0) {\n executeCommitOperations(instance);\n }\n}\n\nfunction commitLifecycleForInstance(\n instance: ComponentInstance,\n wasFirstMount: boolean\n): void {\n if (currentLifecycleCommitBatch) {\n enqueueLifecycleCommit(\n currentLifecycleCommitBatch,\n instance,\n wasFirstMount\n );\n return;\n }\n\n executeCommittedLifecycleOperations(instance, wasFirstMount);\n}\n\nexport function commitRenderedComponent(instance: ComponentInstance): void {\n if (instance.mounted && instance.commitOperations.length > 0) {\n commitLifecycleForInstance(instance, false);\n }\n}\n\nexport function mountInstanceInline(\n instance: ComponentInstance,\n target: Element | null\n): void {\n instance.target = target;\n // Record backref on host element so renderer can clean up when the\n // node is removed. Avoids leaks if the node is detached or replaced.\n try {\n if (target instanceof Element) {\n const host = target as Element & {\n __ASKR_INSTANCE?: ComponentInstance;\n __ASKR_INSTANCES?: ComponentInstance[];\n };\n const instances = host.__ASKR_INSTANCES ?? [];\n const nextInstances = instances.filter((entry) => entry !== instance);\n nextInstances.push(instance);\n host.__ASKR_INSTANCES = nextInstances;\n host.__ASKR_INSTANCE = nextInstances[0] ?? instance;\n }\n } catch (err) {\n void err;\n }\n\n // Ensure notifyUpdate is available for async resource completions that may\n // try to trigger re-render. This mirrors the setup in executeComponent().\n // Use prebound enqueue helper to avoid allocating a new closure\n instance.notifyUpdate = instance._enqueueRun!;\n\n const wasFirstMount = !instance.mounted;\n instance.mounted = true;\n commitLifecycleForInstance(instance, wasFirstMount);\n}\n\n/**\n * Run a component synchronously: execute function, handle result\n * This is the internal workhorse that manages async continuations and generation tracking.\n * Must always be called through the scheduler.\n *\n * ACTOR INVARIANT: This function is enqueued as a task, never called directly.\n */\nlet _globalRenderCounter = 0;\n\nfunction resetRenderState(instance: ComponentInstance): void {\n instance.stateIndexCheck = -1;\n\n for (const state of instance.stateValues) {\n if (state) {\n state._hasBeenRead = false;\n }\n }\n\n instance._pendingReadSources = undefined;\n}\n\nfunction nextRenderToken(): number {\n return ++_globalRenderCounter;\n}\n\nexport function renderScopedComponent<T>(\n instance: ComponentInstance,\n startStateIndex: number,\n render: () => T\n): T {\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n const savedStateIndex = stateIndex;\n\n instance.notifyUpdate = instance._enqueueRun!;\n resetRenderState(instance);\n instance._currentRenderToken = nextRenderToken();\n\n currentInstance = instance;\n currentPortalScope = instance.portalScope ?? savedPortalScope;\n stateIndex = startStateIndex;\n\n let didComplete = false;\n\n try {\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, render);\n didComplete = true;\n return result;\n } finally {\n if (!didComplete) {\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n }\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n stateIndex = savedStateIndex;\n }\n}\n\nfunction runComponent(instance: ComponentInstance): void {\n // CRITICAL: Ensure notifyUpdate is available for state.set() calls during this render.\n // This must be set before executeComponentSync() runs, not after.\n // Use prebound enqueue helper to avoid allocating per-render closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Assign a token for this in-progress render and start a fresh pending-read set\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n const domSnapshot = instance.target ? instance.target.innerHTML : '';\n\n let result: unknown | Promise<unknown>;\n try {\n result = executeComponentSync(instance);\n } catch (err) {\n discardCommitOperations(instance);\n throw err;\n }\n if (isPromiseLike(result)) {\n // Async components are not supported. Components must be synchronous and\n // must not return a Promise from their render function.\n throw new Error(\n 'Async components are not supported. Components must be synchronous.'\n );\n } else {\n // Try runtime fast-lane synchronously; if it activates we do not enqueue\n // follow-up work and the commit happens atomically in this task.\n // (Runtime fast-lane has conservative preconditions.)\n const fastlaneBridge = (\n globalThis as {\n __ASKR_FASTLANE?: {\n tryRuntimeFastLaneSync?: (\n instance: unknown,\n result: unknown\n ) => boolean;\n };\n }\n ).__ASKR_FASTLANE;\n try {\n const used = fastlaneBridge?.tryRuntimeFastLaneSync?.(instance, result);\n if (used) {\n warnUnusedStateReads(instance);\n return;\n }\n } catch (err) {\n // If invariant check failed in dev, surface the error; otherwise fall back\n if (isDevelopmentEnvironment()) throw err;\n }\n\n // Fallback: enqueue the render/commit normally\n globalScheduler.enqueue(() => {\n // Handle placeholder-based updates: when a component initially returned null,\n // we created a comment placeholder. If it now has content, we need to create\n // a host element and replace the placeholder.\n if (!instance.target && instance._placeholder) {\n // Component previously returned null (has placeholder), check if now has content\n if (result === null || result === undefined) {\n // Still null - nothing to do, keep placeholder\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n commitRenderedComponent(instance);\n return;\n }\n\n // Has content now - need to create DOM and replace placeholder\n const placeholder = instance._placeholder;\n const parent = placeholder.parentNode;\n if (!parent) {\n // Placeholder was removed from DOM - can't render\n logger.warn(\n '[Askr] placeholder no longer in DOM, cannot render component'\n );\n return;\n }\n\n // Create a new host element for the content\n const host = document.createElement('div');\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n\n // Set up instance for normal updates\n const oldInstance = currentInstance;\n currentInstance = instance;\n const lifecycleBatch = beginLifecycleCommitBatch();\n try {\n try {\n withContext(executionFrame, () => {\n evaluate(result, host);\n });\n\n // Replace placeholder with host\n parent.replaceChild(host, placeholder);\n } catch (err) {\n discardLifecycleCommitBatch(lifecycleBatch);\n throw err;\n }\n\n // Set up instance for future updates\n instance.target = host;\n instance._placeholder = undefined;\n (\n host as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = instance;\n\n flushLifecycleCommitBatch(lifecycleBatch);\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n commitRenderedComponent(instance);\n } finally {\n currentInstance = oldInstance;\n }\n return;\n }\n\n if (instance.target) {\n let oldChildren: Node[] = [];\n let restoredOldChildren = false;\n try {\n const wasFirstMount = !instance.mounted;\n // Ensure nested component executions during evaluation have access to\n // the current component instance. This allows nested components to\n // call `state()`, `resource()`, and other runtime helpers which\n // rely on `getCurrentComponentInstance()` being available.\n const oldInstance = currentInstance;\n currentInstance = instance;\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n // Capture snapshot of current children (by reference) so we can\n // restore them on render failure without losing event listeners or\n // instance attachments.\n oldChildren = Array.from(instance.target.childNodes);\n\n const lifecycleBatch = beginLifecycleCommitBatch();\n try {\n try {\n withContext(executionFrame, () => {\n evaluate(result, instance.target, undefined, instance);\n });\n } catch (e) {\n discardLifecycleCommitBatch(lifecycleBatch);\n throw e;\n }\n } catch (e) {\n // If evaluation failed, attempt to cleanup any partially-added nodes\n // and restore the old children to preserve listeners and instances.\n try {\n const newChildren = Array.from(instance.target.childNodes);\n const preservedChildren = new Set(oldChildren);\n for (const n of newChildren) {\n if (preservedChildren.has(n)) {\n continue;\n }\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up failed commit children:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n // Restore original children by re-inserting the old node references\n // this preserves attached listeners and instance backrefs.\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_RESTORE',\n new Error().stack\n );\n } catch (e) {\n void e;\n }\n instance.target.replaceChildren(...oldChildren);\n restoredOldChildren = true;\n throw e;\n } finally {\n currentInstance = oldInstance;\n }\n\n flushLifecycleCommitBatch(lifecycleBatch);\n\n // Commit succeeded — finalize recorded state reads so subscriptions reflect\n // the last *committed* render. This updates per-state reader maps\n // deterministically and synchronously with the commit.\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n\n instance.mounted = true;\n // Execute mount operations after first mount (do NOT run these with\n // currentInstance set - they may perform state mutations/registrations)\n executeCommittedLifecycleOperations(instance, wasFirstMount);\n } catch (renderError) {\n discardCommitOperations(instance);\n // Atomic rendering: rollback on render error. Attempt non-lossy restore of\n // original child node references to preserve listeners/instances.\n try {\n const currentChildren = Array.from(instance.target.childNodes);\n const preservedChildren = restoredOldChildren\n ? new Set(oldChildren)\n : null;\n for (const n of currentChildren) {\n if (preservedChildren?.has(n)) {\n continue;\n }\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up partial children during rollback:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_ROLLBACK',\n new Error().stack\n );\n instance.target.replaceChildren(...oldChildren);\n } catch {\n // Fallback to innerHTML restore if replaceChildren fails for some reason.\n instance.target.innerHTML = domSnapshot;\n }\n\n throw renderError;\n }\n }\n });\n }\n}\n\n/**\n * Execute a component's render function synchronously.\n * Returns either a vnode/promise immediately (does NOT render).\n * Rendering happens separately through runComponent.\n */\nexport function renderComponentInline(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n // Reused inline instances can cross renderer cleanup boundaries while their\n // host node is retained. Make sure state writes still enqueue this instance.\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Ensure inline executions (rendered during parent's evaluate) still\n // receive a render token and have their state reads finalized so\n // subscriptions are correctly recorded. If this function is called\n // as part of a scheduled run, the token will already be set by\n // runComponent and we should not overwrite it.\n const hadToken = instance._currentRenderToken !== undefined;\n const prevToken = instance._currentRenderToken;\n const prevPendingReads = instance._pendingReadSources;\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n if (!hadToken) {\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n }\n\n try {\n const result = executeComponentSync(instance);\n // If we set the token for inline execution, finalize subscriptions now\n // unless the parent DOM commit is still provisional. Renderer commit\n // batches flush these reads only after DOM evaluation succeeds.\n if (!hadToken) {\n finalizeInlineReadSubscriptions(\n instance,\n instance._currentRenderToken!,\n instance._pendingReadSources\n );\n }\n commitRenderedComponent(instance);\n return result;\n } finally {\n // Restore previous token/read states for nested inline render scenarios\n instance._currentRenderToken = prevToken;\n instance._pendingReadSources = prevPendingReads;\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n }\n}\n\nexport function warnUnusedStateReads(instance: ComponentInstance): void {\n for (let i = 0; i < instance.stateValues.length; i++) {\n const state = instance.stateValues[i];\n const hasCommittedUsage =\n (state?._readers?.size ?? 0) > 0 ||\n ((state as { _derivedSubscribers?: Set<unknown> } | undefined)\n ?._derivedSubscribers?.size ?? 0) > 0;\n\n if (\n state &&\n !state._hasBeenRead &&\n !state._hasEverBeenRead &&\n !hasCommittedUsage\n ) {\n try {\n const name = instance.fn?.name || '<anonymous>';\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected in ${name} at index ${i}. State should be read during render or removed.`\n );\n } catch {\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected. State should be read during render or removed.`\n );\n }\n }\n }\n}\n\nfunction executeComponentSync(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n resetRenderState(instance);\n incDevCounter('componentRuns');\n incDevCounter('componentReruns');\n\n const savedPortalScope = currentPortalScope;\n currentInstance = instance;\n currentPortalScope = instance.portalScope ?? savedPortalScope;\n stateIndex = 0;\n\n let didComplete = false;\n\n try {\n // Track render time in dev mode\n const renderStartTime = isDevelopmentEnvironment() ? Date.now() : 0;\n\n // Create context object with abort signal\n const context = {\n get signal(): AbortSignal {\n return ensureAbortController(instance).signal;\n },\n };\n\n // Execute component within its owner frame (provider chain).\n // This ensures all context reads see the correct provider values.\n // We create a new execution frame whose parent is the ownerFrame. The\n // `values` map is lazily allocated to avoid per-render Map allocations\n // for components that do not use context.\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, () =>\n instance.fn(instance.props, context)\n );\n\n // Check render time\n const renderTime = Date.now() - renderStartTime;\n if (renderTime > 5) {\n warnInstanceOnce(\n instance,\n 'slow-render',\n `[askr] Slow render detected: ${renderTime}ms. Consider optimizing component performance.`\n );\n }\n\n // Mark first render complete after successful execution\n // This enables hook order validation on subsequent renders\n if (!instance.firstRenderComplete) {\n instance.firstRenderComplete = true;\n }\n\n didComplete = true;\n return result;\n } finally {\n if (!didComplete) {\n discardCommitOperations(instance);\n }\n // Synchronous path: we did not push a fresh frame, so nothing to pop here.\n currentInstance = null;\n currentPortalScope = savedPortalScope;\n }\n}\n\n/**\n * Public entry point: Execute component with full lifecycle (execute + render)\n * Handles both initial mount and re-execution. Always enqueues through scheduler.\n * Single entry point to avoid lifecycle divergence.\n */\nexport function executeComponent(instance: ComponentInstance): void {\n // Lazily recreate abort controller only when signal is actually requested.\n instance.abortController = null;\n\n // Setup notifyUpdate callback using prebound helper to avoid per-call closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Enqueue the initial component run\n globalScheduler.enqueue(instance._pendingRunTask!);\n}\n\nexport function getCurrentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n/**\n * Get the abort signal for the current component\n * Used to cancel async operations on unmount/navigation\n *\n * The signal is guaranteed to be aborted when:\n * - Component unmounts\n * - Navigation occurs (different route)\n * - Parent is destroyed\n *\n * IMPORTANT: getSignal() must be called during component render execution.\n * It captures the current component instance from context.\n *\n * @returns AbortSignal that will be aborted when component unmounts\n * @throws Error if called outside component execution\n *\n * @example\n * ```ts\n * // ✅ Correct: called during render, used in async operation\n * export async function UserPage({ id }: { id: string }) {\n * const signal = getSignal();\n * const user = await fetch(`/api/users/${id}`, { signal });\n * return <div>{user.name}</div>;\n * }\n *\n * // ✅ Correct: passed to event handler\n * export function Button() {\n * const signal = getSignal();\n * return {\n * type: 'button',\n * props: {\n * onClick: async () => {\n * const data = await fetch(url, { signal });\n * }\n * }\n * };\n * }\n *\n * // ❌ Wrong: called outside component context\n * const signal = getSignal(); // Error: not in component\n * ```\n */\nexport function getSignal(): AbortSignal {\n if (!currentInstance) {\n throw new Error(\n 'getSignal() can only be called during component render execution. ' +\n 'Ensure you are calling this from inside your component function.'\n );\n }\n return ensureAbortController(currentInstance).signal;\n}\n\n/**\n * Finalize read subscriptions for an instance after a successful commit.\n * - Update per-state readers map to point to this instance's last committed token\n * - Remove this instance from states it no longer reads\n * This is deterministic and runs synchronously with commit to ensure\n * subscribers are only notified when they actually read a state in their\n * last committed render.\n */\nexport function finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\nexport function getNextStateIndex(): number {\n return stateIndex++;\n}\n\nexport function claimHookIndex(\n instance: ComponentInstance,\n hookName: string\n): number {\n const index = getNextStateIndex();\n\n if (index < instance.stateIndexCheck) {\n throw new Error(\n `Hook index violation: ${hookName}() call at index ${index}, ` +\n `but previously saw index ${instance.stateIndexCheck}. ` +\n `This happens when render-scoped hooks are called conditionally (inside if/for/etc). ` +\n `Move all ${hookName}() calls to the top level of your component function, ` +\n `before any conditionals.`\n );\n }\n\n instance.stateIndexCheck = index;\n\n if (instance.firstRenderComplete) {\n if (instance.expectedStateIndices[index] !== index) {\n throw new Error(\n `Hook order violation: ${hookName}() called at index ${index}, ` +\n `but this index was not in the first render's sequence [${instance.expectedStateIndices.join(', ')}]. ` +\n `This usually means ${hookName}() is inside a conditional or loop. ` +\n `Move all render-scoped hooks to the top level of your component function.`\n );\n }\n } else {\n instance.expectedStateIndices.push(index);\n }\n\n return index;\n}\n\nexport function getCurrentStateIndex(): number {\n return stateIndex;\n}\n\nexport function resetStateIndex(): void {\n stateIndex = 0;\n}\n\nexport function setStateIndex(value: number): void {\n stateIndex = value;\n}\n\n/**\n * Mount a component instance.\n * This is just an alias to executeComponent() to maintain API compatibility.\n * All lifecycle logic is unified in executeComponent().\n */\nexport function mountComponent(instance: ComponentInstance): void {\n executeComponent(instance);\n}\n\n/**\n * Clean up component — abort pending operations\n * Called on unmount or route change\n */\nexport function cleanupComponent(instance: ComponentInstance): void {\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n currentInstance = null;\n currentPortalScope = null;\n\n try {\n const cleanupErrors: unknown[] = [];\n const recordCleanupError = (message: string, err: unknown): void => {\n if (instance.cleanupStrict) {\n cleanupErrors.push(err);\n } else if (isDevelopmentEnvironment()) {\n logger.warn(message, err);\n }\n };\n\n const ownedChildScopes = instance._ownedChildScopes;\n if (ownedChildScopes && ownedChildScopes.size > 0) {\n instance._ownedChildScopes = new Set();\n for (const scope of ownedChildScopes) {\n try {\n scope.dispose();\n } catch (err) {\n recordCleanupError('[Askr] child scope cleanup threw:', err);\n }\n }\n }\n\n // Execute cleanup functions (from mount effects)\n const cleanupFns = instance.cleanupFns;\n instance.cleanupFns = [];\n for (const cleanup of cleanupFns) {\n try {\n cleanup();\n } catch (err) {\n recordCleanupError('[Askr] cleanup function threw:', err);\n }\n }\n\n // Remove deterministic state subscriptions for this instance\n try {\n cleanupReadableSubscriptions(instance);\n } catch (err) {\n recordCleanupError('[Askr] readable subscription cleanup threw:', err);\n }\n\n // Abort all pending operations\n try {\n if (\n instance.abortController &&\n !instance.abortController.signal.aborted\n ) {\n instance.abortController.abort();\n }\n } catch (err) {\n recordCleanupError('[Askr] abort controller cleanup threw:', err);\n }\n instance.abortController = null;\n\n // Clear update callback to prevent dangling references and stale updates\n instance.lifecycleGeneration++;\n instance.evaluationGeneration++;\n instance.mountOperations = [];\n instance.commitOperations = [];\n instance.lifecycleSlots = [];\n instance.hasPendingUpdate = false;\n instance.notifyUpdate = null;\n instance._placeholder = undefined;\n\n // Mark instance as unmounted so external tracking (e.g., portal host lists)\n // can deterministically prune stale instances. Not marking this leads to\n // retained \"mounted\" flags across cleanup boundaries which breaks\n // owner selection in the portal fallback.\n instance.mounted = false;\n\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n cleanupErrors,\n `Cleanup failed for component ${instance.id}`\n );\n }\n } finally {\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n }\n}\n\nexport function registerOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n const scopes = (instance._ownedChildScopes ??= new Set());\n scopes.add(scope);\n}\n\nexport function unregisterOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n instance._ownedChildScopes?.delete(scope);\n}\n\nfunction warnInstanceOnce(\n instance: ComponentInstance,\n key: string,\n message: string\n): void {\n if (isProductionEnvironment()) return;\n const warnings = (instance.devWarningsEmitted ??= new Set());\n if (warnings.has(key)) return;\n warnings.add(key);\n logger.warn(message);\n}\n"],"mappings":"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"}
@@ -93,6 +93,10 @@ function classifyUpdate(instance, result) {
93
93
  useFastPath: false,
94
94
  reason: "pending-mounts"
95
95
  };
96
+ if ((instance.commitOperations?.length ?? 0) > 0) return {
97
+ useFastPath: false,
98
+ reason: "pending-lifecycle-commits"
99
+ };
96
100
  try {
97
101
  populateKeyMapForElement(firstChild);
98
102
  } catch {}
@@ -140,6 +144,7 @@ function validateFastLaneInvariants(instance, schedBefore) {
140
144
  const invariants = {
141
145
  commitCount,
142
146
  mountOps: instance.mountOperations?.length ?? 0,
147
+ commitOps: instance.commitOperations?.length ?? 0,
143
148
  cleanupFns: instance.cleanupFns?.length ?? 0
144
149
  };
145
150
  setDevValue("__LAST_FASTLANE_INVARIANTS", invariants);
@@ -148,6 +153,7 @@ function validateFastLaneInvariants(instance, schedBefore) {
148
153
  throw new Error("Fast-lane invariant violated: expected exactly one DOM commit during reorder-only commit");
149
154
  }
150
155
  if (invariants.mountOps > 0) throw new Error("Fast-lane invariant violated: mount operations were registered during bulk commit");
156
+ if (invariants.commitOps > 0) throw new Error("Fast-lane invariant violated: lifecycle commit operations were registered during bulk commit");
151
157
  if (invariants.cleanupFns > 0) throw new Error("Fast-lane invariant violated: cleanup functions were added during bulk commit");
152
158
  const schedAfter = globalScheduler.getState();
153
159
  if (schedBefore && schedAfter && schedAfter.taskCount > schedBefore.taskCount) {