Easy Gen had no equivalent of the Cutting Room's reconnect-after-reload: reloading
mid-run left Generate re-enabled with no Interrupt and no progress, while the run
carried on server-side.
Nothing was missing on the backend. Easy Gen queues through the Editor's own
/generate route, so its prompts already carry the editor client_id + funpack run
identity, already come back from GET /api/active, and api.js already exposed
active(). Easy Gen simply never asked.
app.js gains reattachRunning(), run after the project is restored on boot:
- Run targets the loaded project -> full re-attach through the existing
preview.watch(), so the result still lands in the preview when it finishes.
- Run targets something else (typically a montage queued from the Cutting Room)
-> preview.monitor(), a new queue watcher over /api/active that shows progress
and Interrupt without claiming the output. It deliberately does NOT switch
projects out from under the user to take ownership of a foreign run.
- Loops while further runs are queued behind the running one, so Generate stays
blocked until the queue drains.
Also fixes a live-run bug found on the way: render() ran on every store notify
(health poll every 15s, every prompt keystroke) and unconditionally set
generateBtn.disabled = !project — re-enabling Generate underneath a generation
that was still going. Both paths now go through one setGenerating() flag, which
render() respects, and a project switch mid-run no longer tears down the poll.
preview.js: watch() takes an optional `note` for the initial label so a re-attach
says "Reconnected after reload" instead of "Starting…"; the Interrupt wiring is
shared between watch() and monitor() as _armStopButton().
Verified in the local ComfyUI: page boots clean (only ComfyUI's own pre-existing
console errors), /api/active is polled on boot, monitor() stays pending with an
armed Interrupt while a job reports running and resolves + hides when it leaves
the queue, and Generate survives a store notify. The full-reattach branch reuses
the already-validated preview.watch() path but could not be exercised end to end
locally, since a real run needs the rental GPU.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>