Phases: attach lifecycle + atomic publish batch

Every stream store exposes its attach machine as a read-only phase store (initialized -> attaching -> attached -> detached | failed) plus explicit attach() / detach() controls. And on the server, ctx.batch(fn) turns publishes into an all-or-nothing batch: a throw after an await retracts publishes issued before it.

Reading as Wacky Lobster (095e5e7e)

Subscription lifecycle

Feed hidden - the subscription is . Attach to render it.

attach() holds an internal retain: the stream stays subscribed with no UI subscriber and reattaches itself across outages (watch the badge cycle attaching -> attached after a reconnect). detach() is "done" - it releases the retain and tears the subscription down immediately, skipping the resume-grace window a normal component unmount gets.

Atomic publish batch

Both buttons run the same handler: ctx.batch(async () => { publish first; await; maybe throw; publish second }). The failing variant throws BETWEEN the two publishes, after a real await boundary - and neither entry appears, because the collector buffers publishes across awaits precisely so a later throw can retract them. Bare ctx.publish would already have flushed the first one at the await.