Skip to content

Controller model

Everything in msw-panel orbits the controller interface exposed by msw-panel.

interface MswPanelController {
getSnapshot(): MswPanelSnapshot;
setAllEnabled(nextEnabled: boolean): void;
setEnabled(id: string, nextEnabled: boolean): void;
subscribe(listener: () => void): () => void;
sync(): void;
toggle(id: string): void;
}

That small surface area is intentional. The React panel uses it directly, and the bridge client emulates the same shape over a transport boundary.

The controller exposes a derived snapshot rather than leaking internal state. A snapshot includes:

  • the active handler count
  • the disabled handler count
  • one entry per handler with id, enabled, kind, label, method, and path

Because the snapshot is plain data, UI adapters and transports do not need to know anything about MSW internals.

Interactive example

Controller snapshot playground

Toggle handlers, add a runtime handler, and then call sync() to see how the snapshot catches up.

Snapshot active 0
Snapshot disabled 0
Runtime handlers 0

The snapshot starts aligned with the runtime.

    The host application still owns the runtime and the handler list:

    const controller = createMswPanelController({ handlers, runtime: worker });

    That means msw-panel does not replace your mocking setup. It consumes:

    • the runtime object that can list and reset handlers
    • the handlers your app already defined

    The controller starts from the supplied handler set and can rebuild from the runtime later with sync(). Enabled state is preserved where handler identities still match.

    That design is simple and predictable, but it also explains the current limitation: runtime-added handlers do not appear automatically unless the host calls controller.sync().