Files
suaveplan/openspec/archive/2026-05-23-enterprise-react-audio-stream-session-core/proposal.md
2026-05-27 23:29:53 +01:00

43 lines
4.0 KiB
Markdown

# @suaveplan/react-audio-stream — session core (chunk A)
- **Target path**: `packages/ui/react/react-audio-stream/`
- **Tier**: browser/react
- **Date**: 2026-05-23
- **Status**: PROPOSED — splits `enterprise-react-audio-stream` into 4 ship-sized changes; this is chunk A (foundation).
## Why
The original `enterprise-react-audio-stream` change folder is ~80-120 files at the project quality bar — too large to ship in a single agent session at 100/100/100/100. Splitting into four disjoint-subtree chunks (session-core, transports, hooks-components, transport-share) lets each land independently while keeping the public surface contract preserved.
This chunk ships the session foundation: error codes, FSM lifecycle, source-plugin bridge, interruption classification, OTel instruments, and the package's structural shell (package.json with subpath exports, vitest/vite/tsconfig configs, root barrel). Every downstream chunk imports from here.
## What changes
1. **NEW package shell** `@suaveplan/react-audio-stream` at `packages/ui/react/react-audio-stream/` with `package.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md`, root `src/index.ts`, all required subpath exports per the original spec's Requirement 9 (gated to those whose targets exist after this chunk).
2. **Error codes** — `src/error-codes/` declares `REACT_AUDIO_STREAM.{NO_PROVIDER, INVALID_STATE, SIGNALLING_FAILED, PEER_NOT_FOUND, MEDIA_PERMISSION_DENIED, RECONNECT_EXHAUSTED, INVALID_OPTIONS}` via `defineCodes` plus `ReactAudioStreamError extends SuaveplanError`.
3. **FSM** — `src/session/session-fsm/` builds the 7-state FSM (`idle` → `connecting` → `connected` → `joining` → `joined` → `disconnecting` → `disconnected`) using `@suaveplan/fsm`. Exposes `transition(method)`, emits `'state-changed'` events.
4. **StreamSession** — `src/session/stream-session/` is the public class wrapping the FSM. `connect`, `join`, `leave`, `disconnect`; emits `'state-changed'`, `'interruption'`, `'peer-stream-added'`, `'peer-stream-removed'`. Transport adapter is injected via constructor — concrete adapters land in chunk B.
5. **InterruptionClassifier** — `src/session/interruption-classifier/` deterministic rule mapping to `network | media-permission | signalling | peer | unknown`.
6. **StreamSourcePlugin** — `src/session/stream-source-plugin/` implements `AudioFxPlugin` from `@suaveplan/audio-fx-plugin-base`; `setSourceStream(MediaStream)`; consumed by `StreamSession` to materialise peer streams onto `peer:<participantId>` tracks.
7. **Telemetry** — `src/telemetry/` registers 6 OTel instruments per the original Requirement 7 (peer connect/disconnect counters, active-sessions gauge, reconnect counters, RTT histogram).
8. **Purity test** — `tests/purity.test.ts` asserts the built `dist/` references no `AudioContext`/`OfflineAudioContext`/`AudioNode`/`AudioWorklet`.
## Requirements landed by this chunk
From the original spec at `openspec/changes/enterprise-react-audio-stream/specs/react-audio-stream/spec.md`:
- Stream-tier purity (full)
- StreamSession FSM lifecycle (full)
- Peer streams materialise as source plugins (full)
- Interruption classification (full)
- OpenTelemetry instrumentation (full)
- Structured error codes (full)
- Subpath exports — partial: only the subpaths whose targets exist after this chunk (`.`, `./session`, `./error-codes`); transports/hooks/components subpaths come in their chunks.
**Deferred to follow-up chunks:** Auto-reconnect (chunk D), per-peer stream mixer (chunk C), transport-share mode (chunk D), WebRTC/WebSocket/Trystero transports (chunk B), 7 hooks + 4 components + Provider (chunk C), bundle-size test (chunk B).
## Impact
- New package shell with the FSM foundation. ~25 files (src + tests + docs + configs).
- Unblocks chunks B, C, D — each imports `StreamSession`, `ReactAudioStreamError`, `InterruptionClassifier`, `StreamSourcePlugin` from here.
- No `dist/` archives until all four chunks merge. Spec fold + change archival happens after chunk D lands.