|
| 1 | +# Scheduler And Commit Phase |
| 2 | + |
| 3 | +Reference file for [Async Framework](../framework.md). This file owns |
| 4 | +scheduler timing, visual commit ordering, background work, and |
| 5 | +`Async.loader.swap(...)` promise completion. |
| 6 | + |
| 7 | +## Purpose |
| 8 | + |
| 9 | +Async needs deterministic ordering for signal effects, lifecycle callbacks, |
| 10 | +route partials, and streamed boundary patches without turning the runtime into a |
| 11 | +component renderer. The scheduler orders explicit work that the protocol |
| 12 | +already names. It is not React Fiber, a virtual DOM reconciler, or a hidden |
| 13 | +renderer loop. |
| 14 | + |
| 15 | +## Responsibilities |
| 16 | + |
| 17 | +- Keep registration and validation synchronous. |
| 18 | +- Resolve signal effects, async-signal state, and lifecycle work in microtasks. |
| 19 | +- Commit visual DOM replacement and morph work in the commit phase. |
| 20 | +- Resolve post-commit promises only after inserted DOM has been scanned and the |
| 21 | + post flush has completed. |
| 22 | +- Run non-critical background work only after required visible work is done. |
| 23 | +- Provide deterministic fallbacks for server, command-line, and test runtimes |
| 24 | + that do not expose browser frame or idle callbacks. |
| 25 | + |
| 26 | +## Timing Model |
| 27 | + |
| 28 | +Scheduler timing is phase based: |
| 29 | + |
| 30 | +1. Sync and registration: `Async.use(...)`, registry adoption, option |
| 31 | + validation, boundary lookup, signal reads, signal writes, and direct handler |
| 32 | + invocation remain ordinary synchronous JavaScript. |
| 33 | +2. Microtask resolve and effects: binding updates, lifecycle callbacks, signal |
| 34 | + effects, and async-signal work flush from the microtask scheduler unless a |
| 35 | + manual scheduler is supplied. |
| 36 | +3. Animation-frame commit: visual boundary replacement, morph work, route |
| 37 | + partial DOM commits, and streamed HTML swaps run in the commit phase. Browser |
| 38 | + runtimes use `requestAnimationFrame` for this phase when it is available. |
| 39 | +4. Post flush: post callbacks and completion promises settle after commit work |
| 40 | + has run, inserted DOM has been scanned and bound, and earlier phase work |
| 41 | + created by that scan has drained. |
| 42 | +5. Idle and background: optional non-critical work runs after the post flush. |
| 43 | + Browser runtimes may use `requestIdleCallback` for this phase. |
| 44 | + |
| 45 | +`requestAnimationFrame` is only for visual commit work. `requestIdleCallback` is |
| 46 | +only for non-critical background work. Neither callback is required to run |
| 47 | +registration, signal writes, event dispatch, server result parsing, or ordinary |
| 48 | +effect scheduling. |
| 49 | + |
| 50 | +## Public Contract |
| 51 | + |
| 52 | +`createScheduler(...)` exposes a small deterministic scheduler with named |
| 53 | +phases, `enqueue(...)`, `afterFlush(...)`, `commit(...)`, `flush(...)`, |
| 54 | +`flushScope(...)`, scope cancellation, and inspection. |
| 55 | + |
| 56 | +`Async.loader.swap(...)` is the app-level promise-returning swap API. It queues |
| 57 | +until a concrete browser loader exists, schedules DOM mutation in the scheduler |
| 58 | +commit phase, and resolves only after: |
| 59 | + |
| 60 | +- the target boundary has been replaced or morphed |
| 61 | +- inserted protocol attributes have been scanned |
| 62 | +- signal, class, event, component, and lifecycle bindings created by that scan |
| 63 | + have had their required flush opportunity |
| 64 | +- post-commit scheduler callbacks have completed |
| 65 | + |
| 66 | +The concrete runtime loader still validates synchronously, returns the same |
| 67 | +boundary, boundary array, or cleanup function shape as before, and is used by |
| 68 | +routers, server-result application, and streaming receivers. Integrations that |
| 69 | +need completion semantics must wait for the scheduled commit before reporting |
| 70 | +success, redirecting, or resolving app-level promises. |
| 71 | + |
| 72 | +## Fallbacks |
| 73 | + |
| 74 | +When `requestAnimationFrame` is unavailable, the commit phase falls back to a |
| 75 | +synchronous deterministic commit. This keeps server and test runtimes stable |
| 76 | +while preserving the same post-flush promise boundary. |
| 77 | + |
| 78 | +When `requestIdleCallback` is unavailable, background work remains a normal |
| 79 | +scheduler phase after post flush. It must not be required for visible DOM |
| 80 | +correctness. |
| 81 | + |
| 82 | +Manual schedulers do not auto-flush. Tests and custom runtimes that choose a |
| 83 | +manual scheduler must call `flush(...)` or `flushScope(...)` to advance queued |
| 84 | +work. |
| 85 | + |
| 86 | +## Subsystem Boundaries |
| 87 | + |
| 88 | +- The scheduler orders work; it does not discover DOM, diff components, own a |
| 89 | + render tree, or decide what HTML should exist. |
| 90 | +- The loader owns boundary lookup, cleanup, replacement, morphing, scanning, |
| 91 | + and binding. |
| 92 | +- The router owns navigation state and route partial selection, then waits for |
| 93 | + loader commit completion before finishing DOM-changing navigation. |
| 94 | +- The boundary receiver owns patch sequence and retry state, then waits for |
| 95 | + loader commit completion before consuming a successful sequence number. |
| 96 | +- Server-result application owns envelope effect ordering, then waits for |
| 97 | + loader commit completion before following redirects that depend on committed |
| 98 | + HTML. |
| 99 | + |
| 100 | +## Invariants |
| 101 | + |
| 102 | +- Sync validation errors surface before commit scheduling. |
| 103 | +- Commit jobs for the same boundary serialize when frame-backed commits are |
| 104 | + deferred. |
| 105 | +- Independent boundaries may prepare or commit independently unless a caller |
| 106 | + explicitly batches them. |
| 107 | +- Post callbacks do not resolve before earlier phase work created by a commit |
| 108 | + has had a chance to drain. |
| 109 | +- Background work never gates boundary correctness. |
| 110 | + |
| 111 | +## Failure Modes |
| 112 | + |
| 113 | +- A missing boundary fails before a swap is scheduled. |
| 114 | +- A scan or binding failure during commit rejects the app-level swap promise and |
| 115 | + lets streaming receivers retry the same sequence number. |
| 116 | +- Destroyed scopes cancel their pending scheduler work. |
| 117 | +- A destroyed runtime rejects queued loader work and prevents later commit |
| 118 | + attempts from targeting detached roots. |
| 119 | + |
| 120 | +## Acceptance Criteria |
| 121 | + |
| 122 | +- A frame-backed app-level `Async.loader.swap(...)` does not mutate the DOM |
| 123 | + before the animation-frame commit. |
| 124 | +- The same promise resolves only after replacement, scan, inserted bindings, |
| 125 | + lifecycle flush, and post flush complete. |
| 126 | +- Non-browser runtimes without `requestAnimationFrame` keep deterministic |
| 127 | + synchronous commit fallback behavior. |
| 128 | +- Same-boundary frame commits serialize so a later swap cannot commit before an |
| 129 | + earlier in-flight swap finishes. |
| 130 | +- Streamed boundary patches report success only after the scheduled loader |
| 131 | + commit finishes. |
0 commit comments