0012 — Menus and config files are two front-ends to one AdapterManager
Status: Accepted (built — 2026-07-29 note below) Date: 2026-06-29
Context
An operator needs to plug in I/O endpoints and consoles/surfaces (a PipeWire device, a REAC stagebox, an X32, an MCU) two ways: from a GUI menu at the gig ("found a stagebox — add it"), and from a config file checked into the rig setup ("these are my endpoints, start them on boot"). The dangerous design is to build those as two separate code paths that can disagree — the menu adds an adapter the file does not know about, or editing the file does something subtly different from using the menu.
Decision
One AdapterManager is the single source of truth, and menus and files are two
front-ends to it and to the same persisted config.
- On start, the manager reads
config/adapters.{yaml,json}, instantiates eachenabledadapter via atype → factoryregistry, and tracks status. - The web UI add/edit/remove menu calls WS verbs (
adapter.add/update/remove/list/types); the manager applies the change and persists it back to the same config file. - Editing the file and restarting yields the identical end state. There is no second code path.
adapter.typesreturns a per-typesettingsSchemaso the menu form is rendered generically — the same metadata→widget idea as the plugin editor (0010), so there is no per-adapter-type UI code.- Discovery feeds the same manager: a found device →
discovery.addAsAdapter→AdapterManager.add→ persisted → started. Same end state as hand-editing the file.
Today the server loads a single adapter chosen by environment variables
(OPENMIXER_ADAPTER), via a lazy package loader. The multi-adapter AdapterManager, the
config file, and the adapter.* / discovery.* WS verbs are designed and not yet built.
Consequences
- The menu and the file can never drift, because they are the same state and the same persistence — "no second code path" is the whole point.
- Reproducible rigs: the gig setup is a file you can version, diff, and restore; the menu is just a live editor for it.
- A new adapter type is a factory registration plus a settings schema; the menu form and the file format both pick it up with no UI work.
- Probes must degrade (a missing NIC disables that probe with a logged reason, never crashes discovery), so the manager always starts even on a partial rig.
Note — 2026-07-29: built, all three parts
The Context paragraph "The multi-adapter AdapterManager, the config file, and
the adapter.* / discovery.* WS verbs are designed and not yet built" is no
longer true of any of the three, though the WS verbs it names were later deleted
outright rather than built — the REST entity map replaced them:
AdapterManager— the class inpackages/server/src/adapter-manager.ts.- the config file —
startConsoleinpackages/server/src/console-rig.tsresolvesOPENMIXER_ADAPTERS_CONFIGor<stateDir>/adapters.yaml; an example ships atpackages/server/config/adapters.example.yaml. - the verbs —
buildAdapterAdminRows(packages/server/src/adapter-admin-rows.ts) andbuildDiscoveryRows(packages/server/src/discovery-rows.ts) build the/adapter/*and/discovery/*REST rows;packages/server/src/server.tsis what registers them into the resource registry.
The per-type settingsSchema this ADR predicted is real too
(xtouchRegistration in packages/adapter-xtouch/src/control-surface.ts,
and AdapterManager in packages/server/src/adapter-manager.ts). The single-adapter
OPENMIXER_ADAPTER env path still exists alongside it.