Skip to main content
Mission Control keeps client-side state in Zustand. There are more than 100 stores in src/stores/, each a focused slice of state with its own actions. Some larger stores are split into a directory of slices (agent-connection/, agent-capabilities/, settings/, ground-station/).

Why Zustand

A Zustand store is a plain function that returns state and actions. There are no context providers and no reducers. Components subscribe to the fields they read and re-render only when those fields change. That matters for a ground control station. A MAVLink stream delivers hundreds of decoded frames a second. A React context that re-rendered on every frame would freeze the page; fine-grained subscriptions plus coalesced notifications (below) keep it smooth.

Store map

These are representative stores, not the full list. Browse src/stores/ for the rest.

Ring buffers for telemetry

telemetry-store holds one fixed-capacity ring buffer per telemetry message (RingBuffer in src/lib/ring-buffer.ts). When a buffer is full the oldest sample is overwritten, so memory stays flat however long the flight is. Most buffers are sized for about 60 seconds at the message’s usual rate: The full list is in src/stores/telemetry-store.ts. A buffer offers push, latest, get, last(n), nearest(ts) and toArray.

Coalesced notifications

Pushing a sample mutates the buffer in place and then calls scheduleVersionBump() from src/stores/coalesced-version.ts. All the pushes inside one animation frame collapse into a single increment of the store’s _version field. Components select _version and re-read the buffers, so the UI updates at most once per display frame however fast the wire runs. Any store fed by live telemetry follows the same rule.

The drone manager bridge

drone-manager (src/stores/drone-manager.ts) owns the connected vehicles, their protocol adapters and the selected vehicle. When a vehicle connects, bridgeTelemetry() in src/stores/drone-manager-bridge.ts subscribes to the adapter’s callbacks and routes each one to the stores:
The telemetry rings are global, so only the selected vehicle writes to them; otherwise two connected vehicles would interleave their streams in one buffer. Every frame is still recorded for its own vehicle. bridgeTelemetry() returns the unsubscribe functions, and they all run when the connection closes.

Parameters and configuration panels

Every flight controller configuration panel reads and writes parameters through the usePanelParams hook (src/hooks/use-panel-params.ts). It takes an options object and returns the values, load state and write actions:
It handles MAVLink parameters (ArduPilot and PX4) and MSP virtual parameters (Betaflight and iNav) the same way, so panel code never touches the protocol. Writes go to RAM first; commitToFlash makes them permanent on the flight controller.

Demo mode

Demo mode runs a simulated fleet entirely in the browser with no backend. MockFlightEngine (src/mock/engine.ts) writes synthetic telemetry into the same stores a real link would, and registers a MockProtocol per simulated vehicle with the drone manager, so configuration panels, flight logs and sensor health work too. The fleet itself is described on Dashboard. The first-run welcome modal has a Demo Mode switch, on by default. You can also turn Demo Mode on or off in Settings, or with Toggle Demo Mode in the command palette (⌘K).

Persistence

Stores that must survive a reload use Zustand’s persist middleware with a version number and a migration function. When the stored shape changes, the version goes up in the same change and the migration converts old data. Persisted stores include: Live telemetry stores never persist. They start empty on every page load.

Selectors

Components subscribe to specific fields:
When a component needs several fields at once, wrap the selector in useShallow so a new object with equal values does not trigger a render.

Next steps

MAVLink protocol

The adapters that feed the stores.

Code recipes

Add a store, a panel or a decoder.
Last modified on October 11, 2026