Expand description
WaveObjUpdate 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 WaveObjUpdate records, broadcast to all connected WS clients via
the existing event_bus.broadcast_event(...) plumbing — the same path
that service.rs:39-52’s response-broadcast loop uses.
Why this exists: per-RPC handlers were responsible for attaching
WaveObjUpdates to their responses (success_with_updates(...)).
Forgetting that call left the frontend WOS 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 ofWaveObjUpdateinagentmux-srv/src/backend/obj.rs:465-474so the frontend’s existingupdateWaveObjecthandler accepts it without changes. - dispatch_
event 🔒 - Translate one reducer event into zero or more
waveobj:updatebroadcasts. - emit 🔒
- Push one
WaveObjUpdatepayload to all connected WS clients via the shared event_bus. Mirrors the response-broadcast loop inservice.rs:39-52. - emit_
client_ 🔒singleton - Broadcast the singleton
ClientWaveObj. SrvWindowOpened / SrvWindowClosed mutateClient.windowids(perapply_srv_window_openedin persist_subscriber.rs:518) 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’supdateWaveObject(wos.ts:263-265) handles the delete arm with just the oid. - emit_
fetched 🔒 - Fetch one WaveObj 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 (WaveStore 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 WaveObj is theLayoutStatereferenced by the tab’slayoutstatefield. Two SQLite reads chained inside onespawn_blockingto keep the lock hold short. - run_
wave_ 🔒obj_ bridge - spawn_
wave_ 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 WOS would stop seeing updates.