0003 — The server is the single source of truth; surfaces are thin clients
Status: Accepted (built) Date: 2026-06-29
Context
A live mix is driven from several places at once: a tablet at front-of-house, a phone on stage, a laptop, and — designed for S3 — two or three physical X-Touch fader banks. They must all show the same values. If two surfaces each held their own authoritative state and synced peer-to-peer, they would drift, fight, and need conflict resolution. That is the failure mode of every "everybody is a master" design.
Decision
The server holds the canonical state. Every surface — web UI, future MCU bank, another desk routed in — is a bidirectional client of that one server and nothing else. Any edit (a finger on glass, a motor fader, a device-side change) is sent to the server; the server applies it through the engine and broadcasts the resulting value to every connected client, including the originator. Surfaces never talk to each other.
In the code: MixerServer.broadcast() fans every EngineEvent to all sockets; the
originating client also applies its change optimistically and reconciles against the
echoed authoritative value. A connecting client is greeted with the current topology and
can subscribe to have state (re)sent.
Consequences
- All surfaces converge on one truth. There is no surface-to-surface protocol to design, and no distributed-consensus problem.
- Adding a surface type is adding a client, not a new authority. The designed MCU/X-Touch layer is "one more client"; multi-window is "more clients of the same server".
- Self-echo is deliberate: the server echoes to the originator too, so optimistic UIs have an authoritative value to reconcile to. (Some consoles suppress echo of a client's own writes — e.g. the X32 — so adapters apply optimistically and poll on connect; that is a device quirk, not openmixer's policy.)
- The server is a single point of failure for control. It is stateless enough to restart and re-hydrate from device state + persisted sessions; clients reconnect and re-render.
- Built today: greet-on-connect,
subscribe, and full broadcast ofstate/meters/plugin.chain. The reconnect snapshot-on-connect for an arbitrary surface slice (so a reconnecting MCU bank repaints correctly) is part of the S3 design.
Note — 2026-07-29: the reconnect snapshot is built, and the X-Touch banks are real
Consequences says the reconnect snapshot-on-connect for an arbitrary surface
slice, "so a reconnecting MCU bank repaints correctly", "is part of the S3
design". It is built and pervasive, though not the way this ADR describes: every
?watch=1 stream's first event is a full snapshot of that row
(packages/server/src/resource-stream.ts), so a reconnecting client — including the
selection row at /surface/selection
(packages/server/src/surface-selection-row.ts) — is whole again the moment it
re-opens, with no separate replay path. The physical X-Touch banks the Context
anticipates are also real — packages/adapter-xtouch/.