Expand description
MuxObjUpdate broadcast bridge.
Subscribes to srv_events_tx (the internal sidecar event bus that the
reducer publishes mutations to) and translates each event into one or
more MuxObjUpdate records, broadcast to all connected WS clients via
the existing event_bus.broadcast_event(...) plumbing — the same path
service/mod.rs’s run_service_call response broadcast ends in (via
EventBus::broadcast_mux_obj_updates, as one batched frame).
Why this exists: per-RPC handlers were responsible for attaching
MuxObjUpdates to their responses (success_with_updates(...)).
Forgetting that call left the frontend MOS cache stale (e.g. workspace
renames not propagating to the OS title or the InstancePanel — see
docs/specs/SPEC_REACTIVE_WORKSPACE_SYNC_2026-05-14.md).
With this bridge in place, any reducer event automatically reaches the frontend, so the per-handler convention becomes belt-and-suspenders instead of load-bearing.
Spec: docs/specs/SPEC_OBJ_UPDATE_BRIDGE_2026-05-14.md.
Phase 1 scope (this implementation): workspace events only —
immediately fixes the user-reported bug. Phase 2 expands to tabs /
blocks / windows / layouts; Phase 3 retires the per-handler
success_with_updates(...) calls now that the bridge covers them.
Functions§
- build_
update_ 🔒payload - JSON shape that gets broadcast as the
datapayload of awaveobj:updateWS event. Matches the shape of theMuxObjUpdatestruct incrates/srv/src/backend/obj.rsso the frontend’s existingupdateMuxObjecthandler accepts it without changes. - dispatch_
event 🔒 - Translate one reducer event into zero or more
waveobj:updatebroadcasts. - emit 🔒
- Push one
MuxObjUpdatepayload to all connected WS clients via the shared event_bus — the single-update counterpart of the batched response broadcast inservice/mod.rs’srun_service_call. - emit_
client_ 🔒singleton - Broadcast the singleton
ClientStoreObj. SrvWindowOpened / SrvWindowClosed mutateClient.windowids(perapply_srv_window_openedin persist_subscriber.rs) so renderers holding a pinned Client need to see the new windowids list — without this broadcast they’d render stale window membership until reload. Codex P2 on PR #861. - emit_
delete 🔒 - Broadcast a “delete”
waveobj:updatefor the given oid. No fetch needed — the frontend’supdateMuxObject(mos.ts) handles the delete arm with just the oid. - emit_
fetched 🔒 - Fetch one StoreObj by oid and broadcast it as a
waveobj:update. The SQLite read is offloaded to the blocking thread pool per ReAgent P1 on PR #852 (Store isstd::sync::Mutex<Connection>; brief in steady state but a long reducer transaction would block the tokio worker thread). Silently logs + skips on missing/error to satisfy the §8.15 idempotency contract — duplicate or stale events fold to no-op. - emit_
layout_ 🔒for_ tab - Layout events all reference a
tab_id; the affected StoreObj is theLayoutStatereferenced by the tab’slayoutstatefield. Two SQLite reads chained inside onespawn_blockingto keep the lock hold short. - run_
mux_ 🔒obj_ bridge - spawn_
mux_ obj_ bridge - Spawn the bridge task. Returns the
JoinHandleso callers can keep it alive (typically forever — the task lives for the lifetime of the srv process). Per ReAgent P1 on PR #852: the loop is panic-resilient — a panic insidedispatch_eventis caught and logged, and the loop continues processing subsequent events. Without this, a single malformed event could silently kill the entire bridge task and frontend MOS would stop seeing updates.