# `provider/midi-context` ## Purpose Defines the two React contexts that wire `` to its descendant hooks. `MidiReactContext` carries the resolved `MidiContextValue` — status, `WebMIDIAccess`, any caught error, the `sysex` opt-in flag, and a monotonically-incrementing `stateVersion` counter bumped on each Web MIDI `statechange` event. The presence of `stateVersion` lets `useSyncExternalStore`-based hooks observe live device plug/unplug changes without each hook holding its own `statechange` listener. `MidiRequestContext` is a per-tree override slot for the `navigator.requestMIDIAccess` function. Tests provide a deterministic in-memory implementation here; production renders leave it unset and the provider falls back to the real global. This is a real injection seam — not a mock — so the integration path remains exercised verbatim under both real and fake transports. ## Features - Two distinct contexts so hooks that only need the resolved access re-render only on `stateVersion` bumps, not on injection changes. - `null` sentinel on `MidiReactContext` triggers `REACT_MIDI.NO_PROVIDER` in every public hook. - `MidiStatus` is the four-value lifecycle: `loading`, `granted`, `denied`, `unsupported`. ## Usage: Basic ```tsx import { MidiReactContext } from "@suaveplan/react-midi"; import { useContext } from "react"; function StatusBadge() { const ctx = useContext(MidiReactContext); return {ctx?.status ?? "no-provider"}; } ``` ## Usage: Advanced ```tsx import { MidiProvider, MidiRequestContext } from "@suaveplan/react-midi"; // Inject a deterministic fake in tests: function fakeRequest() { return Promise.resolve(fakeAccessShape); } {children} ; ``` ## API Reference - `MidiStatus = "loading" | "granted" | "denied" | "unsupported"`. - `MidiContextValue { status, access, error, sysex, stateVersion }`. - `MidiReactContext: React.Context`. - `MidiRequestContext: React.Context`. ## Implementation Notes `stateVersion` exists because the Web MIDI `statechange` event fires mutations on `MIDIAccess.inputs` / `outputs` in place — the underlying `Map` identity does not change. `useSyncExternalStore` therefore needs a sentinel value that *does* change to trigger a re-render; the provider increments `stateVersion` inside the `statechange` callback.