SIGN IN SIGN UP

pbz: Chunk 29 — connection death is an event, not a timeout

Post-publish item 1. ws.onerror was wired inside the `opened` promise, so
once the socket was open, rejecting it again was a no-op and every
post-open error went on the floor. Nothing told the queue either, so a
parked waiter sat out its full timeout and then reported a timeout, and
the next send threw a bare InvalidStateError. The device got blamed for
ignoring a command nothing was listening to.

One onerror, two eras: before open it rejects `opened`; after open it
calls die(), which records the cause once, disarms the idle timer, and
fails the queue. onclose routes there too, so a silent hangup is caught.

queue.fail(err) rejects parked waiters now and later ones on arrival. A
slow device still resolves null, so death and slowness stay
distinguishable. Sends check first and throw the recorded cause.
collectFrames checks in its poll loop, so save() no longer burns its 6s
preview window against a dead socket. Our own closes are labelled so
they don't read as device faults.

12 new hermetic tests, 185 total, typecheck green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PZnUXRsLLnt2A7pR3X4Luj
T
Tarek Rached committed
56fe16a29a29ef4c974a65bc8b07736eaeb93dcd
Parent: 6e8049b