feat(huddle): ad-hoc audio huddles in text channels (LC-493) #490

Merged
longjacksonle merged 3 commits from feat/LC-493-huddles into main 2026-06-29 02:46:17 +02:00

LC-493 - Huddles: lightweight ad-hoc audio in any text channel

One-click ephemeral audio in a group text room, reusing the entire enclave-voice WebRTC stack (full-mesh, presence, mute/screen, device picker, ICE) rather than provisioning a voice channel.

How it reuses the voice stack

The mesh is already room-id-generic: the hub keys voice_rooms/voice_conn on any room id, the signaling relays are generic, and voice.js binds any [data-lc-voice-root] and drives join/leave/mute/camera/screen + the participant grid from its data-lc-voice-* hooks. So a huddle is just that root mounted in a text room.

  • Server: handle_voice_join previously required is_voice; it now accepts any accessible non-DM room. DMs keep their dedicated 1:1 call path. The ephemeral lifecycle is free - the hub already drops a room from voice_rooms when the last participant leaves, so a huddle exists only while someone is on the line.
  • In-room bar: a compact [data-lc-voice-root][data-lc-huddle] above the composer (group rooms only) with the mute/camera/screen/leave/devices controls, a count chip, a pre-join lobby, and the tile grid - all the existing voice hooks/CSS. RoomPage seeds huddle_participants from the hub so a fresh page load already shows an in-progress huddle.
  • Start/join: a header "Huddle" button (data-lc-voice-join) starts or joins. The bar stays hidden until the viewer joins or someone else is on the line (applyHuddleVisibility in voice.js, called on join-state and participant changes), so an idle room shows only the header button. Voice-channel roots are unaffected (no data-lc-huddle).

Tests

just check, just test, just test-saas all green; i18n parity covers the new huddle-* keys (en+es); Tailwind rebuilt. The audio path itself is the existing, shipped voice mesh (presence/signaling already covered); this PR adds the room attachment + in-room surface. The WebRTC media path is browser-driven and not exercised by the Rust test suite (same as the existing voice channels).

Not operator-visible (no env/config/contract change; reuses LETS_CHAT_ICE_SERVERS).

Follow-ups (not in this slice)

A persistent "huddle active" indicator in the sidebar/room list, transcription in huddles (omitted to keep the bar light), and a per-room/per-enclave disable toggle if operators want to gate it.

🤖 Generated with Claude Code

## LC-493 - Huddles: lightweight ad-hoc audio in any text channel One-click ephemeral audio in a group text room, reusing the entire enclave-voice WebRTC stack (full-mesh, presence, mute/screen, device picker, ICE) rather than provisioning a voice channel. ### How it reuses the voice stack The mesh is already room-id-generic: the hub keys `voice_rooms`/`voice_conn` on any room id, the signaling relays are generic, and `voice.js` binds any `[data-lc-voice-root]` and drives join/leave/mute/camera/screen + the participant grid from its `data-lc-voice-*` hooks. So a huddle is just that root mounted in a text room. - **Server**: `handle_voice_join` previously required `is_voice`; it now accepts any accessible non-DM room. DMs keep their dedicated 1:1 call path. The ephemeral lifecycle is free - the hub already drops a room from `voice_rooms` when the last participant leaves, so a huddle exists only while someone is on the line. - **In-room bar**: a compact `[data-lc-voice-root][data-lc-huddle]` above the composer (group rooms only) with the mute/camera/screen/leave/devices controls, a count chip, a pre-join lobby, and the tile grid - all the existing voice hooks/CSS. `RoomPage` seeds `huddle_participants` from the hub so a fresh page load already shows an in-progress huddle. - **Start/join**: a header "Huddle" button (`data-lc-voice-join`) starts or joins. The bar stays hidden until the viewer joins or someone else is on the line (`applyHuddleVisibility` in voice.js, called on join-state and participant changes), so an idle room shows only the header button. Voice-channel roots are unaffected (no `data-lc-huddle`). ### Tests `just check`, `just test`, `just test-saas` all green; i18n parity covers the new `huddle-*` keys (en+es); Tailwind rebuilt. The audio path itself is the existing, shipped voice mesh (presence/signaling already covered); this PR adds the room attachment + in-room surface. The WebRTC media path is browser-driven and not exercised by the Rust test suite (same as the existing voice channels). Not operator-visible (no env/config/contract change; reuses `LETS_CHAT_ICE_SERVERS`). ### Follow-ups (not in this slice) A persistent "huddle active" indicator in the sidebar/room list, transcription in huddles (omitted to keep the bar light), and a per-room/per-enclave disable toggle if operators want to gate it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
handle_voice_join previously required is_voice; relax it to any accessible non-DM room so the same WebRTC mesh (presence, signaling, mute/screen) backs an ad-hoc huddle in a text channel. DMs keep their 1:1 call path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RoomPage gains huddle_enabled/ice_servers/huddle_participants (seeded from the hub so a fresh load shows an in-progress huddle). A compact [data-lc-voice-root][data-lc-huddle] bar above the composer reuses the voice control/grid/lobby hooks; a header "Huddle" button (data-lc-voice-join) starts/joins one. Both gated to non-DM, non-voice rooms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(huddle): voice.js compact-visibility for huddle roots + i18n (LC-493)
All checks were successful
check-secrets / Nosey parker (push) Successful in 7s
check-secrets / Kingfisher (push) Successful in 12s
check-secrets / TruffleHog (push) Successful in 12s
check-secrets / Kingfisher (pull_request) Successful in 5s
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 6s
Check / clippy + fmt + tests (pull_request) Successful in 4m3s
Create release / Create release from merged PR (pull_request) Has been skipped
1b08396727
A [data-lc-huddle] root stays hidden until the viewer joins or someone else is on the line (applyHuddleVisibility, called on join state + participant changes), so an idle room shows only the header button. Voice-channel roots are unaffected. New huddle-* i18n (en+es).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/LC-493-huddles 2026-06-29 02:46:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!490
No description provided.