diff --git a/docs/superpowers/specs/2026-08-18-react-islands-design.md b/docs/superpowers/specs/2026-08-18-react-islands-design.md index 8d2777ea..6d81a1f2 100644 --- a/docs/superpowers/specs/2026-08-18-react-islands-design.md +++ b/docs/superpowers/specs/2026-08-18-react-islands-design.md @@ -127,9 +127,9 @@ record that subscribers and devtools depend on. Write → action → store notifies → snapshot changes → island re-renders. This terminates cleanly **provided writes never occur during render**. Writes belong in event handlers or effects. -This rule is enforced in dev (see Error handling), not merely documented. It is also the reason -this shape was chosen over generated `bind:` sugar: the cycle stays visible in the author's own -code rather than being hidden in generated glue. +This rule is documented rather than machine-enforced — see "Dropped: the write-during-render +guard" below. It is also the reason this shape was chosen over generated `bind:` sugar: the +cycle stays visible in the author's own code rather than being hidden in generated glue. ## Compiler and bundler changes @@ -189,12 +189,38 @@ point. | Island bundle fails to load | Placeholder remains, warning logged; page stays functional because everything else was server-rendered | | Non-serializable props | Compile-time `WRN-ISLAND-PROPS` | | Unknown store name | Dev: throw, listing available store names. Prod: warn, return undefined | -| Action fired during render | Dev: throw with a targeted message pointing at the handler/effect rule (React's own warning is too generic to diagnose quickly) | +| Action fired during render | Left to React. See "Dropped: the write-during-render guard" below. | | Cleanup throws on unmount | Caught and logged; navigation must not break | Islands failing **locally** is the most valuable property of this model: a crashed chart leaves the rest of the page working. +## Dropped: the write-during-render guard + +The design originally called for a dev-only guard that threw when an island +called a store action during render. It was implemented, then removed: the +mechanism is unreliable in both directions. + +- **False positives.** React runs effects before a queued microtask drains, so a + render-phase flag cleared on a microtask is still set inside `useEffect`. An + island writing from an effect — the documented correct pattern — would throw. +- **False negatives.** The flag can only be set from the error boundary`s + render. When an island updates its own state, only the island re-renders, so + the flag is never set and a genuine write-during-render passes silently. + +There is no reliable public API for detecting React`s render phase; doing it +properly requires React internals, which is not acceptable in a shipped +framework. + +React already covers the real hazard: writing during render that notifies +subscribers produces "Cannot update a component while rendering a different +component", and the infinite-loop case is caught by the `getSnapshot` caching +requirement handled in the store bridge. The custom guard added false positives +without covering anything React misses. + +The author-facing rule still stands and is still documented — it is simply not +machine-enforced. + ## Testing ### Compiler unit tests