Restarting the dev server leaves the iframe holding a dead HMR socket. The page
keeps rendering and looks completely alive — it has simply stopped receiving
updates. So the agent edits a file, nothing moves, and there is no way to tell
whether the edit failed or the connection did. Reported after exactly that
sequence: stop, start, ask for a change, watch nothing happen.
A stale preview that is indistinguishable from a working one is worse than an
obviously broken one, because you debug the wrong thing.
── Which signal, and why not the obvious one ──
`preview:refresh` already exists and is bumped on every turn that touched a file.
Subscribing to it would fix this and ruin the feature: a reload throws away scroll
position, form state, and whatever route you had navigated to inside the app —
on every edit. HMR exists precisely so that does not happen.
So a new channel, bumped on `task:started` and on task completion. A job coming up
may be a dev server coming back; a job going down may be it going away. Both are
moments a preview pointed at it is now lying. Neither fires on an edit — this is a
few times a session, not once a keystroke.
Stated rather than hidden: it OVER-triggers. Any job starting reloads any preview,
not only the one serving it. That costs one reload on an unrelated watcher, which
is the cheaper of the two errors.
The parent process cannot see inside a cross-origin iframe, so there is no way to
ask whether its socket is alive. The job lifecycle is the closest observable
proxy, and it is exact for the case that prompted this.
tsgo clean, frontend builds, 851 pass / 7 fail unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>