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>
@suaveplan/react-audio-stream - Stream-tier React surface for the Suaveplan audio family — session-core foundation (chunk A). Provides StreamSession (FSM lifecycle), InterruptionClassifier, StreamSourcePlugin, error codes, and OTel instruments. Transports, hooks, components, and transport-share land in chunks B/C/D.
React 19 surface for live audio streaming sessions: a Provider, six hooks, and four
accessible components. Chunk C of the four-chunk enterprise-react-audio-stream split.
Chunks A (session-core) and B (transports) deliver the headless engine. Chunk D delivers
useTransportShareand its banner. This chunk delivers the React surface consumers actually touch.
Documentation tree
- shared-transport-indicator —
SharedTransportIndicatorrenders the spec-mandated "Shared transport active" badge whenever the local peer is in `'re…- shared-transport-indicator —
SharedTransportIndicatorrenders the spec-mandated "Shared transport active" badge whenever the local peer is in `'re…
- shared-transport-indicator —
- stream-mixer —
<StreamMixer>renders one strip per remote peer, each with a volume range slider, a mute toggle, a solo toggle, and a…- stream-mixer —
<StreamMixer>renders one strip per remote peer, each with a volume range slider, a mute toggle, a solo toggle, and a…
- stream-mixer —
- stream-permissions-banner —
<StreamPermissionsBanner>is the visible counterpart touseStreamPermissions(). It renders arole="status"ARIA-l…- stream-permissions-banner —
<StreamPermissionsBanner>is the visible counterpart touseStreamPermissions(). It renders arole="status"ARIA-l…
- stream-permissions-banner —
- stream-recorder —
<StreamRecorder>is a record/stop control with a live duration display. It delegates recording lifecycle to `useStrea…- stream-recorder —
<StreamRecorder>is a record/stop control with a live duration display. It delegates recording lifecycle to `useStrea…
- stream-recorder —
- stream-room —
<StreamRoom>is the headless layout primitive for the room scope. It renders no markup of its own — instead it expose…- stream-room —
<StreamRoom>is the headless layout primitive for the room scope. It renders no markup of its own — instead it expose…
- stream-room —
- error-codes — The error-codes module is the canonical source of truth for every failure mode published by `@suaveplan/react-audio-str…
- error-codes — The error-codes module is the canonical source of truth for every failure mode published by `@suaveplan/react-audio-str…
- internal — Internal helper consumed by every public chunk-C hook. It reads the
StreamReactContext, returns the non-null `{ sessi…- use-stream-context — Internal helper consumed by every public chunk-C hook. It reads the
StreamReactContext, returns the non-null `{ sessi…
- use-stream-context — Internal helper consumed by every public chunk-C hook. It reads the
- use-stream-levels —
useStreamLevelsprojects realtime peak/RMS samples out of the session. Called without an argument it returns the loca…- use-stream-levels —
useStreamLevelsprojects realtime peak/RMS samples out of the session. Called without an argument it returns the loca…
- use-stream-levels —
- use-stream-mixer —
useStreamMixerexposes per-peer mixer controls — level, mute, solo, and the per-peer FX slot count — and implements D…- use-stream-mixer —
useStreamMixerexposes per-peer mixer controls — level, mute, solo, and the per-peer FX slot count — and implements D…
- use-stream-mixer —
- use-stream-peers —
useStreamPeersreturns the live remote-peer roster as a stablereadonly Peer[]. Each peer entry carries the partici…- use-stream-peers —
useStreamPeersreturns the live remote-peer roster as a stablereadonly Peer[]. Each peer entry carries the partici…
- use-stream-peers —
- use-stream-permissions —
useStreamPermissionsexposes the microphone permission state with the typedMediaPermissionStateenum (`"granted" |…- use-stream-permissions —
useStreamPermissionsexposes the microphone permission state with the typedMediaPermissionStateenum (`"granted" |…
- use-stream-permissions —
- use-stream-recording —
useStreamRecordingexposes the start/stop controls for capturing a session as a single audio file. The hook delegates…- use-stream-recording —
useStreamRecordingexposes the start/stop controls for capturing a session as a single audio file. The hook delegates…
- use-stream-recording —
- use-stream-room —
useStreamRoomprojects the current room scope out of the session: the room id (ornullwhen the session is not conn…- use-stream-room —
useStreamRoomprojects the current room scope out of the session: the room id (ornullwhen the session is not conn…
- use-stream-room —
- use-stream-session —
useStreamSessionis the entry-point hook for chunk-C consumers. It returns the liveStreamSessioninstance, its cur…- use-stream-session —
useStreamSessionis the entry-point hook for chunk-C consumers. It returns the liveStreamSessioninstance, its cur…
- use-stream-session —
- use-transport-share —
useTransportShareis the React entry point to the transport-share subsystem. It composes a `TransportShareCoordinator…- use-transport-share —
useTransportShareis the React entry point to the transport-share subsystem. It composes a `TransportShareCoordinator…
- use-transport-share —
- stream-context —
StreamReactContextis the single React context that publishes the liveStreamSessionto every chunk-C hook and comp… - stream-provider —
<StreamProvider>is the singleton React surface that owns the liveStreamSessionfor a tree. It calls the caller-su… - session — The reconnect module owns the auto-reconnect lifecycle that
StreamSessionruns after a disconnect interruption. It wr…- interruption-classifier —
InterruptionClassifiermaps a structural disconnect cause to exactly one of `network | media-permission | signalling…- interruption-classifier —
InterruptionClassifiermaps a structural disconnect cause to exactly one of `network | media-permission | signalling…
- interruption-classifier —
- session-fsm — The
session-fsmmodule declares the canonical seven-state finite state machine that drives everyStreamSession. Bui…- session-fsm — The
session-fsmmodule declares the canonical seven-state finite state machine that drives everyStreamSession. Bui…
- session-fsm — The
- stream-session —
StreamSessionis the public face of the stream-tier session core. It wraps the FSM, the transport adapter, the interr…- stream-session —
StreamSessionis the public face of the stream-tier session core. It wraps the FSM, the transport adapter, the interr…
- stream-session —
- stream-source-plugin —
StreamSourcePluginis the bridge that lets a remote peer'sMediaStreamparticipate in the audio engine's plugin-cha…- stream-source-plugin —
StreamSourcePluginis the bridge that lets a remote peer'sMediaStreamparticipate in the audio engine's plugin-cha…
- stream-source-plugin —
- reconnect — The reconnect module owns the auto-reconnect lifecycle that
StreamSessionruns after a disconnect interruption. It wr…
- interruption-classifier —
- telemetry — The telemetry module owns every OpenTelemetry instrument published by
@suaveplan/react-audio-stream. Concentrating th…- telemetry — The telemetry module owns every OpenTelemetry instrument published by
@suaveplan/react-audio-stream. Concentrating th…
- telemetry — The telemetry module owns every OpenTelemetry instrument published by
- control-channel — The control-channel module is the wire-protocol heart of transport sharing. It defines the envelope shapes that flow be…
- share-state — The share-state module owns the role-machine that decides whether a peer is in
'local','driving', or'receiving'… - transports — The
transport.types.tsmodule declares the structural contract thatStreamSession(authored in chunk A of the `@sua…- trystero —
TrysteroTransportis the serverlessStreamTransportAdapterpowered by@suaveplan/network-discovery-trystero. It p…- trystero-transport —
TrysteroTransportis the serverlessStreamTransportAdapterpowered by@suaveplan/network-discovery-trystero. It p…
- trystero-transport —
- webrtc —
WebRtcTransportis the low-latency defaultStreamTransportAdapterfor@suaveplan/react-audio-stream. It wires a c…- webrtc-transport —
WebRtcTransportis the low-latency defaultStreamTransportAdapterfor@suaveplan/react-audio-stream. It wires a c…
- webrtc-transport —
- websocket —
WebSocketTransportis the fallbackStreamTransportAdapterconsumers choose when a corporate or carrier NAT blocks W…- websocket-transport —
WebSocketTransportis the fallbackStreamTransportAdapterconsumers choose when a corporate or carrier NAT blocks W…
- websocket-transport —
- transport — The
transport.types.tsmodule declares the structural contract thatStreamSession(authored in chunk A of the `@sua…
- trystero —
- test-setup —
test-setup.tsis the per-worker bootstrap file referenced byvitest.config.ts'ssetupFiles. It registers `@testin… - test-utils —
test-utils.tsxprovides the in-memoryFakeSessionand thewithProviderwrapper used by every chunk-C hook + compo…
Provider
<StreamProvider sessionFactory={…}>— StrictMode-safe singleton; injects aStreamSessionlazily; fans seven session event channels into a single render-bump counter; disconnects on unmount.StreamReactContext— exposed for advanced consumers that need raw context access.- Type-only mirrors (
SessionState,Peer,PeerMixerState,MediaPermissionState,LevelSample,RecordingResult,StreamSessionFactory,StreamSessionLike,SessionEventMap,ReactAudioStreamErrorLike) for downstream typing.
Hooks
useStreamSession()— session, FSM state, andconnect/join/leave/disconnect.useStreamRoom()—roomId,isJoined,joinedAt,participants.useStreamPeers()— livereadonly Peer[].useStreamMixer()— per-peersetVolume/setMute/setSolowith DAW solo semantics (when any peer is soloed, every non-soloed peer's track-volume → 0). Every mutation routes through the session'ssetTrackVolume/setTrackMute/setTrackSolo.useStreamLevels(peerId?)—{ peak, rms }for the local mic or named peer.useStreamRecording()—start(peerIds?)andstop()returning aRecordingResultblob.useStreamPermissions()— typedMediaPermissionStatewithrequest()callback.
Components
<StreamRoom>— headless render-prop wrapper exposing the room snapshot plus join / leave callbacks.<StreamMixer>— per-peer mixer strip with range slider,aria-pressedmute/solo toggles, FX-slot read-out; supportsrenderStriprender-prop.<StreamRecorder>— record/stop toggle withmm:ssduration display and ARIA-live read-out.<StreamPermissionsBanner>—role="status"aria-live="polite"banner; renders nothing when permission is granted.
Quick start
import { StreamProvider, useStreamSession } from "@suaveplan/react-audio-stream";
function Room() {
const { state, connect, join } = useStreamSession();
if (state === "idle") return <button onClick={() => connect("lobby").then(join)}>Join</button>;
return <p>Joined!</p>;
}
export function App() {
return (
<StreamProvider sessionFactory={() => createMyStreamSession()}>
<Room />
</StreamProvider>
);
}
Constraints (from the canonical spec)
- Zero browser audio APIs: no
AudioContext/AudioNode/AudioWorkletimports. - DAW solo semantics live in the mixer hook, not in each component.
- Every hook throws
REACT_AUDIO_STREAM.NO_PROVIDERoutside a<StreamProvider>.