Files
skills/plugins/document-navigator/docs/ui/react/react-midi
admin 9bf56a806b Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:10:00 +01:00
..

@suaveplan/react-midi - React 19 adapter for the Web MIDI API: , hooks (useMidi, useMidiInput, useMidiOutput, useMidiMessages, useMidiDevices, useNRPN, useRPN, useCC14), and small UI primitives (MidiDeviceList, MidiActivityIndicator).

React 19 adapter for the Web MIDI API. Provides a strict-mode-safe <MidiProvider> that requests MIDIAccess exactly once, seven typed hooks for common consumption patterns, and two small UI primitives (<MidiDeviceList>, <MidiActivityIndicator>).

Documentation tree

  • midi-activity-indicator — <MidiActivityIndicator> is a small visual primitive that "blinks" on inbound MIDI traffic. It subscribes to the selec…
    • midi-activity-indicator — <MidiActivityIndicator> is a small visual primitive that "blinks" on inbound MIDI traffic. It subscribes to the selec…
  • midi-device-list — <MidiDeviceList> is a small, accessible list of the available MIDI devices for a chosen kind (inputs or outputs). It…
    • midi-device-list — <MidiDeviceList> is a small, accessible list of the available MIDI devices for a chosen kind (inputs or outputs). It…
  • error-codes — Defines the stable REACT_MIDI.* error-code namespace published by @suaveplan/react-midi. Every code is generated vi…
    • error-codes — Defines the stable REACT_MIDI.* error-code namespace published by @suaveplan/react-midi. Every code is generated vi…
  • hooks — Internal helper that returns the live MidiContextValue for the nearest <MidiProvider> ancestor. It throws `ReactMid…
    • use-cc14 — useCC14(selector) exposes the consolidated 14-bit CC change for the selected input. Controllers that need finer resol…
      • use-cc14 — useCC14(selector) exposes the consolidated 14-bit CC change for the selected input. Controllers that need finer resol…
    • use-midi — useMidi() is the most general hook in @suaveplan/react-midi. It returns the live MidiContextValue surface as `{ s…
      • use-midi — useMidi() is the most general hook in @suaveplan/react-midi. It returns the live MidiContextValue surface as `{ s…
    • use-midi-devices — useMidiDevices() returns the live { inputs, outputs } device-list view, re-rendering whenever the provider's `state…
      • use-midi-devices — useMidiDevices() returns the live { inputs, outputs } device-list view, re-rendering whenever the provider's `state…
    • use-midi-input — useMidiInput(selector) returns a typed MidiPortLike adapter over a single Web MIDI input. The adapter parses raw by…
      • use-midi-input — useMidiInput(selector) returns a typed MidiPortLike adapter over a single Web MIDI input. The adapter parses raw by…
    • use-midi-messages — useMidiMessages(selector, options?) exposes a bounded ring buffer of the most recent parsed MIDI messages from the se…
      • use-midi-messages — useMidiMessages(selector, options?) exposes a bounded ring buffer of the most recent parsed MIDI messages from the se…
    • use-midi-output — useMidiOutput(selector) returns a typed handle for a single Web MIDI output. The handle exposes both send(bytes) (r…
      • use-midi-output — useMidiOutput(selector) returns a typed handle for a single Web MIDI output. The handle exposes both send(bytes) (r…
    • use-nrpn — useNRPN(selector) wraps the universal NRPN reducer from the midi-contract module and exposes the consolidated `NRPN…
      • use-nrpn — useNRPN(selector) wraps the universal NRPN reducer from the midi-contract module and exposes the consolidated `NRPN…
    • use-rpn — useRPN(selector) wraps the universal RPN reducer and exposes the consolidated RPNChange as React state. RPN ("Regis…
      • use-rpn — useRPN(selector) wraps the universal RPN reducer and exposes the consolidated RPNChange as React state. RPN ("Regis…
    • use-midi-context — Internal helper that returns the live MidiContextValue for the nearest <MidiProvider> ancestor. It throws `ReactMid…
  • codec — codec.ts provides minimal decodeMessage / encodeMessage helpers for the MIDI 1.0 wire format used internally by `…
  • midi-message — Defines the local mirror of the @suaveplan/midi MidiMessage discriminated union plus its peer types (Channel, `Mi…
  • reducers — Three small reducers that fold the four-CC NRPN, RPN, and 14-bit CC "dances" into single consolidated events. The Web M…
  • port — Bridges the native Web MIDI WebMIDIInput / WebMIDIOutput shapes into the structural MidiPortLike consumed by the…
    • web-midi-port — Bridges the native Web MIDI WebMIDIInput / WebMIDIOutput shapes into the structural MidiPortLike consumed by the…
  • midi-context — Defines the two React contexts that wire <MidiProvider> to its descendant hooks. MidiReactContext carries the resol…
  • midi-provider — <MidiProvider> is the root context provider for @suaveplan/react-midi. It calls `navigator.requestMIDIAccess({ syse…
  • telemetry — Owns the two OTel counters published by @suaveplan/react-midi: react_midi_messages_received_total for parsed inboun…
    • telemetry — Owns the two OTel counters published by @suaveplan/react-midi: react_midi_messages_received_total for parsed inboun…
  • test-setup — Per-package Vitest bootstrap loaded via setupFiles before the first test in a worker runs. Its only job is to registe…
  • test-utils — Provides the deterministic in-memory implementations of the Web MIDI shape used throughout the @suaveplan/react-midi…

Install

bun add @suaveplan/react-midi

Peer deps: react@^19, react-dom@^19.

Permission model

Web MIDI is gated behind navigator.requestMIDIAccess. <MidiProvider> calls it once on mount and surfaces the outcome as a status:

  • loading — request in flight.
  • granted — MIDIAccess resolved.
  • denied — user rejected, or the runtime threw (e.g. NotAllowedError).
  • unsupported — navigator.requestMIDIAccess is undefined (Firefox, sandboxed iframes, etc.).

SysEx access is opt-in via <MidiProvider sysex>. The provider is StrictMode-safe — it tracks an in-flight ref so React's double-mount never produces two parallel requests.

Quickstart

import {
    MidiProvider,
    useMidi,
    useMidiInput,
    MidiDeviceList,
} from "@suaveplan/react-midi";

function Workspace() {
    const { status } = useMidi();
    if (status === "loading") return <p>Connecting MIDI…</p>;
    if (status === "denied") return <p>MIDI permission denied.</p>;
    if (status === "unsupported")
        return <p>MIDI not available in this browser.</p>;
    return (
        <>
            <MidiDeviceList kind="input" />
            <NoteLogger />
        </>
    );
}

function NoteLogger() {
    const port = useMidiInput("first");
    useEffect(() => {
        if (port === null) return;
        return port.onMessage((msg) => {
            if (msg.type === "noteOn") console.log("note", msg.note);
        });
    }, [port]);
    return null;
}

export default function App() {
    return (
        <MidiProvider>
            <Workspace />
        </MidiProvider>
    );
}

Device-picker example

function DevicePicker() {
    const [id, setId] = useState<string | null>(null);
    return (
        <>
            <MidiDeviceList
                kind="input"
                selectedId={id}
                onSelect={setId}
            />
            {id !== null ? <NoteLogger portId={id} /> : null}
        </>
    );
}

Hooks reference

  • useMidi() — { status, access, inputs, outputs, error }.
  • useMidiInput(id | "first") — typed MidiPortLike with parsed MidiMessage events.
  • useMidiOutput(id | "first") — typed output handle with sendMessage(msg) (encoded) and raw send(bytes).
  • useMidiMessages(id, { capacity?, filter? }) — ring buffer of recent messages for log/debug UIs.
  • useMidiDevices() — { inputs, outputs }, re-renders on statechange.
  • useNRPN(id), useRPN(id), useCC14(id) — high-resolution reducers folded into React state.

Error codes

REACT_MIDI.{ACCESS_DENIED, UNSUPPORTED_PLATFORM, PORT_NOT_FOUND, SYSEX_NOT_PERMITTED, NO_PROVIDER}. Thrown as ReactMidiError instances; permission failures are surfaced as a status value rather than thrown so consumers can render friendly fallbacks.

Telemetry

Two OTel counters tagged with port: react_midi_messages_received_total{port} and react_midi_messages_sent_total{port}.

On the @suaveplan/midi dependency

This package ships a local mirror of the @suaveplan/midi MidiMessage contract in src/midi-contract/. Once @suaveplan/midi graduates to workspace:^, the local mirror is replaced by a re-export from that package without any shape change.