docs(spec): baseline 0.9.5 — v1 audio + PTT lifecycle redesign

Add SysRS-303/304, SysDes-149/150/151, SRS-204/205/206/207,
SAD-081/082/083, SDD-094/095/096/097, DEC-029/030.

Captures the v1 lifecycle redesign:
- Drop manual Start-audio button; audio engine is bound to voice-channel
  join/leave (ensure_running on first join, shutdown_if_idle on last
  leave). Output stream opens regardless of mic-permission state so
  listen-only is a first-class flow.
- TransmitMode enum (Ptt / Continuous / VoiceActivity-reserved).
  Default Ptt on fresh install. Persisted per identity.
- PTT release tail: 200 ms default (0-500 ms configurable) before
  transmit gate closes, avoiding clipped trailing syllables.
- Hard-mute toggle overrides transmit gate regardless of mode/PTT.
- Bridge surface: drop start_audio/stop_audio; add
  voice_join(channel_id) / voice_leave() and BridgeEvent::VoiceState.

DEC-029 rejects Flutter global-hotkey packages (hotkey_manager,
super_hot_key) for PTT: they wrap RegisterHotKey/RegisterEventHotKey
which consume the key and don't fire key-up, wrong primitive for PTT.
Native Rust DesktopPttBackend (SDD-083/084/085) stays authoritative.

DEC-030 defers Voice Activity Detection to P1. RMS / WebRTC VAD /
Silero VAD trade-off review (binary-size, dependency-surface, CPU
profile) postponed; TransmitMode::VoiceActivity reserved on the enum
surface so a P1 increment is non-breaking.

Validator clean: 304/151/207/83/97 IDs, strict layered sourcing
preserved, no new warnings beyond the pre-existing 35 old-package-name
filenames.
This commit is contained in:
EdisonJwa
2026-05-15 22:46:51 +08:00
parent f9d20d8585
commit dfa84ee7bb
9 changed files with 232 additions and 2 deletions
+44
View File
@@ -1122,6 +1122,42 @@ The registry supports consistency between architecture, detailed design, impleme
- Source SAD: SAD-080
- Verification method: Unit Test, Platform Test (Windows / macOS / Linux), UI Review
**SDD-094**: `chanora_bridge::voice::voice_join(channel_id: ChannelId) -> Result<(), VoiceError>` and `chanora_bridge::voice::voice_leave() -> Result<(), VoiceError>` shall be the only public-bridge voice lifecycle calls. Internally `chanora_audio::engine::AudioEngine::ensure_running()` shall be idempotent and shall open the cpal input and output streams on the first call of the session; `chanora_audio::engine::AudioEngine::shutdown_if_idle()` shall be called from `voice_leave` and shall close both streams when no voice channels remain. The Flutter widget tree shall subscribe to `BridgeEvent::VoiceState { in_channel, transmit_mode, mute, release_tail_ms }` through the existing FRB stream and shall render the Voice Bar from that stream. The pre-existing `start_audio` and `stop_audio` FRB symbols shall be deleted from `crates/chanora_bridge/src/api.rs` together with their generated bindings; the corresponding caller in `apps/chanora_flutter/lib/main.dart` shall be removed.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `chanora_bridge::voice`, `chanora_audio::engine::AudioEngine`, `BridgeEvent::VoiceState`, `apps/chanora_flutter/lib/main.dart`
- Source SAD: SAD-081
- Verification method: Integration Test, UI Review
**SDD-095**: `chanora_audio::TransmitMode` shall be a `#[repr(u8)]` Rust enum with variants `Ptt = 0`, `Continuous = 1`, `VoiceActivity = 2`. Persistence shall be provided by `chanora_storage::IdentityStore::set_transmit_mode(&self, mode: TransmitMode)` and `chanora_storage::IdentityStore::get_transmit_mode(&self) -> TransmitMode`, the latter returning `TransmitMode::Ptt` on first read for a fresh install. The bridge surface shall carry the enum across the FFI boundary via `mirror` codegen as `BridgeTransmitMode`. The Flutter settings page in `apps/chanora_flutter/lib/widgets/voice_settings.dart` shall render three radio rows; the `VoiceActivity` row shall be rendered with `enabled: false` and a "Coming soon" trailing label until DEC-030 is resolved and an implementation is allocated.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `chanora_audio::TransmitMode`, `chanora_storage::IdentityStore::{set_transmit_mode, get_transmit_mode}`, `BridgeTransmitMode`, `apps/chanora_flutter/lib/widgets/voice_settings.dart`
- Source SAD: SAD-083
- Verification method: Unit Test, UI Review
**SDD-096**: `chanora_audio::release_tail::ReleaseTailTimer` shall be a tokio-task-owning struct exposing `arm(&self, gate: AudioTransmitGate, tail_ms: u32)`, `key_down(&self)`, `key_up(&self)`, and `cancel(&self)`. Internally the struct shall hold a `tokio::sync::watch::Sender<bool>` plus a `JoinHandle<()>` for the pending close. `key_down` shall abort any pending close handle and shall set `transmit_active = true` via the gate; `key_up` shall abort the prior close handle and shall spawn a new task that sleeps for `tail_ms` milliseconds and then sets `transmit_active = false`. The struct shall use no `unsafe`, no mutex, and shall rely only on atomic refcounts on the watch handle. The configuration value `release_tail_ms` shall live in the identity store next to `transmit_mode`, with a default of 200 and validated range 0500 inclusive on every write.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `chanora_audio::release_tail::ReleaseTailTimer`, `chanora_storage::IdentityStore` (`release_tail_ms`)
- Source SAD: SAD-082
- Verification method: Unit Test, Integration Test
**SDD-097**: `apps/chanora_flutter/lib/widgets/voice_bar.dart` shall replace the existing `_AudioControls` widget in `apps/chanora_flutter/lib/main.dart` as the rendering surface for voice state. Its input shall be the `BridgeEvent::VoiceState` stream; its children, in render order, shall be: the channel-name pill (visible only when `in_channel` is true); the mode badge (`PTT: <key_label>` / `Continuous` / `Voice activity (coming soon)`); the hard-mute toggle button; the release-tail and threshold disclosure; the level meter; and the PTT capability badge already specified in SDD-091. The "Start audio" button shall be removed from the widget tree. The settings dialog in `apps/chanora_flutter/lib/widgets/voice_settings.dart` shall render the `TransmitMode` radio group, the bind-key button, and a release-tail slider over the range 0500 ms with step 25 ms; the dialog shall never expose a "Start audio" affordance.
- Status: Draft
- Type: Software Detailed Design Item
- Stage: P0 / MVP
- Software unit: `apps/chanora_flutter/lib/widgets/voice_bar.dart`, `apps/chanora_flutter/lib/widgets/voice_settings.dart`, `apps/chanora_flutter/lib/main.dart`
- Source SAD: SAD-081, SAD-083
- Verification method: UI Review, Widget Test
## 11. Updated SAD-to-SDD Coverage Matrix
| SAD Range | SDD Coverage |
@@ -1130,6 +1166,7 @@ The registry supports consistency between architecture, detailed design, impleme
| SAD-061 through SAD-070 | Covered by `SDD-071` through `SDD-080` |
| SAD-071 through SAD-079 | Covered by `SDD-081` through `SDD-092` |
| SAD-080 | Covered by `SDD-093` |
| SAD-081 through SAD-083 | Covered by `SDD-094` through `SDD-097` |
## Baseline Candidate 0.9.1 Update
@@ -1158,3 +1195,10 @@ The registry supports consistency between architecture, detailed design, impleme
| Version | Date | Description |
|---|---|---|
| 0.9.4 | 2026-05-15 | Added `SDD-093` covering `SAD-080`. `SDD-093` fixes the `PttInputClass` enum surface, the cross-backend rebind contract for `MouseSideButton`, and the Flutter side-button capture path against the `PointerEvent.buttons` bitmask. Strict layered sourcing preserved (`SDD -> SAD` only). |
## Baseline Candidate 0.9.5 Update
| Version | Date | Description |
|---|---|---|
| 0.9.5 | 2026-05-15 | Added v1 audio + PTT lifecycle detailed design SDD-094 through SDD-097 sourced from SAD-081..083: bridge surface drops `start_audio` / `stop_audio` and exposes `chanora_bridge::voice::voice_join` / `voice_leave` with idempotent `AudioEngine::ensure_running` and `shutdown_if_idle`, `chanora_audio::TransmitMode` as `#[repr(u8)]` enum persisted via `chanora_storage::IdentityStore::{set,get}_transmit_mode` and mirrored across FRB as `BridgeTransmitMode`, `chanora_audio::release_tail::ReleaseTailTimer` as a tokio-task-owning adapter on the watch handle (no `unsafe`, no mutex; configurable 0500 ms via `release_tail_ms`), and the new `apps/chanora_flutter/lib/widgets/voice_bar.dart` + `voice_settings.dart` surfaces replacing the legacy `_AudioControls` widget. Strict layered sourcing preserved (`SDD -> SAD` only). |