fw-04 — Analysis
The generating constraint
A call stack cannot be paused, inspected, resumed, or duplicated.
Interruptible rendering needs all four, so the stack must become a heap data structure you own. The
return (parent) pointer in a Fiber is the direct artefact: a recursive traversal never needs one,
because the stack provides it.
Required invariants
- Render work is discardable, therefore side-effect free.
- Commit is atomic — exactly one continuation may touch the DOM.
- A new update makes in-flight work stale. Continuing it commits a tree where some components saw old state and some saw new: tearing.
- Two trees exist during render — current and work-in-progress.
- Higher priority abandons lower-priority in-progress work, which is only legal because of (1).
Invariants 1 and 2 are two statements of one requirement: discardable work must have done nothing, and output must be applied all at once.
The bug this module's spec caught
The reference implementation used to validate the spec failed the last test. Its scheduler
treated a same-priority update arriving mid-render as "keep going," so a render half-finished with
n = 1 continued after state became 2, and the commit mixed both.
The fix is one line of policy: any new update discards in-flight work and restarts. You are likely to write the same bug; the test will catch it.
This is the framework-level form of the defect useSyncExternalStore prevents at application level.
Does time-slicing help?
| Helps when | Does not help when |
|---|---|
| work is long (tens of ms) | the long task is an effect or layout read |
| work is in the render phase | total work is small; overhead dominates |
| there is higher-priority work to yield to | the real fix is doing less work |
"Concurrent React fixes our INP" is true only when input delay comes from long render phases.
The single diagnostic question is: is the long task a render, or something else? A LoAF
scripts[] breakdown answers it in one recording.