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 Breezy Turnip (85a0077d)
Feed hidden - the subscription is . Attach to render it.
This page attached for you on load. Press Detach,
then Attach - every hop the machine takes lands
in the log above, including the attaching that
usually resolves too fast to catch on the badge. attach() holds an internal retain: the stream stays
subscribed with no UI subscriber and reattaches itself
across outages; failed is the hop you get when the
server refuses the attach. detach() is "done" - it
releases the retain and tears the subscription down immediately,
skipping the resume-grace window a normal component unmount gets
(the short hold that lets a quick remount resume the same
subscription - see
it in the counter-resume demo).
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.