feat: singleton audio, transcript reflection, voice persistence
- audio.ts: true singleton player — only one audio at a time, with
URI-based tracking so each bubble knows its play state
- audio.ts: playSingle() for manual playback, playAudio() for
autoplay queue, explicit pause before remove to stop native audio
- MessageBubble: show transcript text below voice player, track
playing state via singleton URI
- VoiceButton: pass recording duration from recorder.currentTime
- ChatContext: handle incoming 'transcript' type to update voice
bubbles with transcribed text, send messageId with voice messages
- ChatContext: voice messages persist transcript text, empty chunks
are filtered on reload, transcribed voices become text bubbles
- ConnectionContext: pass messageId with voice messages to gateway
- wol.ts: add 5s timeout with settled guard to prevent hanging
- types: add WsIncomingTranscript, messageId on WsVoiceMessage