Files
pbakaus_impeccable/source
Paul BakausandClaude Opus 4.7 c7ee722472 fix(live): four HMR + React race bugs from Next 16 / Turbopack testing
Surfaced during hands-on testing against a real Next 16 + Turbopack app
(EACManagement). All four compound to produce unusable live iteration
for React users; fixed bottom-up because each one blocked testing the
next.

## 1. Picker bar snaps to (0,0) on first variant arrival

In startVariantObserver, `showVariantInDOM(sessionId, 1)` hides the
original via display:none but we never re-pointed selectedElement.
Next frame, getBoundingClientRect() on the hidden original returns a
zero rect and the bar positions at (0,0). Clicking Next masked the
bug because cycleVariant already calls updateSelectedElement.

Fix: after showVariantInDOM, re-point selectedElement via
pickVariantContent(wrapper, visibleVariant) — same call the no-HMR
fallback and updateSelectedElement already use.

## 2. React NotFoundError on accept/discard (Next 16 / Turbopack)

handleAccept and cleanup both called
`wrapper.parentElement.replaceChild(...)` eagerly, before the agent's
source rewrite had propagated through HMR. That yanks children out
from under React's reconciler; when React later tries to remove/replace
the wrapper, its fiber tree no longer matches the DOM and it throws.

Fix, both paths:
- cleanup (discard): `wrapper.style.display = 'none'` so variants
  disappear immediately, no structural DOM mutation.
- handleAccept: skip the eager replaceChild entirely. The accepted
  variant is already the only visible child of the wrapper thanks to
  the display: contents pattern; HMR cleans up the wrapper itself.
- Both paths schedule a 2s fallback replaceChild that runs only if
  HMR hasn't cleaned up — keeps static-server / no-HMR flows working.
- Capture sessionId + visibleVariant in closure variables before the
  1800ms cleanup timer zeros them, so the fallback still has context.

## 3. Server serves stale live.js forever

loadBrowserScripts() read live-browser.js once at startup into a
liveScript string. The /live.js handler served that cached string
with no cache headers. Every edit to the browser script was invisible
until a full server restart — silently broke the iteration loop on
fixes #1 and #2 for the user.

Fix:
- loadBrowserScripts returns { detectScript, livePath } — existence
  check only, no caching.
- /live.js handler re-reads livePath on every request and prepends
  __IMPECCABLE_TOKEN__ / __IMPECCABLE_PORT__ each time.
- Response headers: Cache-Control: no-store, no-cache, must-revalidate,
  max-age=0 + Pragma: no-cache.

detect.js stays cached — it rarely changes during a session.

## 4. Picker stuck in GENERATING when HMR doesn't fire

The only 'done' fallback fired when arrivedVariants === 0 and called
injectVariantsFromSource, which parses raw source via DOMParser. That
can't work for TSX/JSX/Vue/Svelte — JSX expressions aren't valid HTML.
If HMR flaked or was slow (500+ line inserts on Next 16 are prone to
this), state stayed in GENERATING and the spinner ran forever.

Fix: give HMR a 2s grace window, then `window.location.reload()`.
resumeSession already counts variants off the rendered DOM on load
and transitions straight to CYCLING — reload is the universal
recovery path that works for any framework, HTML, static server,
anything.

injectVariantsFromSource is now dead code on the 'done' path. Kept
for potential pure-HTML-no-HMR future use.

## Credit

Precise repro + root-cause diagnosis from the other agent in the
EACManagement session. #2 and #4 are the high-impact ones for Next 16
/ Turbopack; #3 is the meta-fix that made iterating on #1 and #2
possible at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 02:01:33 -07:00
..