feat(calls): redesign the in-call voice UI (LC-402) #419

Merged
longjacksonle merged 1 commit from feat/lc-402-voice-call-redesign into main 2026-06-21 00:34:40 +02:00

Implements the core of the LC-402 epic (subtasks LC-403/404/405/406). See data/liveT.png for the before state. Stack unchanged: Askama + HTMX + Tailwind, server-rendered, WebSocket-driven, no client framework.

What changed

Layout regions + theme (LC-403). The enclave voice page now has clear regions: control bar / participant grid (primary) / transcript drawer (secondary). The voice page, the shared recording pill, and the transcript panel are migrated off hardcoded slate/green/red/indigo onto the semantic theme tokens (--surface, --content, --accent, --danger, --success, --border), so the call UI follows light/dark/high-contrast. New .lc-call* / .lc-voice* / .lc-transcript* design system in main.css.

Participant grid (LC-404). Responsive CSS grid for 1 / 2 / many (a lone tile is sized instead of ballooning; "Waiting for others to join" shows when alone). Tiles rebuilt in voice.js: video-vs-avatar, name + YOU badge, a self mute indicator, and an active-speaker ring driven by a self-contained, feature-detected Web Audio analyser loop. Participant-count chip in the header.

Control bar (LC-405). Two clusters - media controls (Mute / Start video / Share screen) and session actions (Transcribe / Transcript / Leave) - split by a divider. Mute / video / screen show aria-pressed on/off state; Leave is danger-styled; Transcribe is the danger-tinted "recording" toggle with a pulsing dot tied to the pill + drawer.

Transcript panel (LC-406). The bottom scrim becomes a themed right-hand drawer (toggle + close, own scroll). Lines render as speaker + timestamp + text, grouped by consecutive speaker; auto-scroll to latest with a "Jump to live" affordance when scrolled up. The pill, toggle, and drawer header share one obvious recording state. On the voice page the root reserves space for the drawer so tiles are never covered; over the 1:1 modal it overlays.

Safety

Every data-lc-* attribute and element id the JS relies on is preserved, so the WebRTC mesh + signaling state machines are untouched - only tile structure, control state, and caption presentation changed. The 1:1 DM call modal shares the new transcript drawer + tokened pill. CSS .hidden overrides are added where main.css (loaded after tailwind-built.css) would otherwise beat the .hidden toggle the JS uses (the LC-391 gotcha).

Brainstorm captured as follow-ups (sub-issues on LC-402)

Remote mute/speaking propagation over WS (only local mute is known today), screen-share pinned tile, full mobile pass, connection-quality indicator, transcript consent/export-link, and 1:1 modal control-bar parity (it gets the drawer but no panel-toggle button yet). Noted in the epic.

Scope

Frontend only: templates/voice/page.html, templates/layout.html (caption/drawer), templates/ws/transcript_segment.html, templates/base.html (2 JS strings), assets/voice.js, assets/transcribe.js, assets/main.css, locales/{en,es}/{misc,js}.ftl. No Rust, route, or schema change.

Tests

just check (standalone + saas compile, clippy, fmt), just test, just test-saas all pass. voice.js + transcribe.js syntax-checked (node + bun). just build-css regenerated. prefers-reduced-motion respected.

Implements the core of the LC-402 epic (subtasks LC-403/404/405/406). See data/liveT.png for the before state. Stack unchanged: Askama + HTMX + Tailwind, server-rendered, WebSocket-driven, no client framework. ## What changed **Layout regions + theme (LC-403).** The enclave voice page now has clear regions: control bar / participant grid (primary) / transcript drawer (secondary). The voice page, the shared recording pill, and the transcript panel are migrated off hardcoded slate/green/red/indigo onto the semantic theme tokens (`--surface`, `--content`, `--accent`, `--danger`, `--success`, `--border`), so the call UI follows light/dark/high-contrast. New `.lc-call* / .lc-voice* / .lc-transcript*` design system in `main.css`. **Participant grid (LC-404).** Responsive CSS grid for 1 / 2 / many (a lone tile is sized instead of ballooning; "Waiting for others to join" shows when alone). Tiles rebuilt in `voice.js`: video-vs-avatar, name + YOU badge, a self mute indicator, and an active-speaker ring driven by a self-contained, feature-detected Web Audio analyser loop. Participant-count chip in the header. **Control bar (LC-405).** Two clusters - media controls (Mute / Start video / Share screen) and session actions (Transcribe / Transcript / Leave) - split by a divider. Mute / video / screen show aria-pressed on/off state; Leave is danger-styled; Transcribe is the danger-tinted "recording" toggle with a pulsing dot tied to the pill + drawer. **Transcript panel (LC-406).** The bottom scrim becomes a themed right-hand drawer (toggle + close, own scroll). Lines render as speaker + timestamp + text, grouped by consecutive speaker; auto-scroll to latest with a "Jump to live" affordance when scrolled up. The pill, toggle, and drawer header share one obvious recording state. On the voice page the root reserves space for the drawer so tiles are never covered; over the 1:1 modal it overlays. ## Safety Every `data-lc-*` attribute and element id the JS relies on is preserved, so the WebRTC mesh + signaling state machines are untouched - only tile structure, control state, and caption presentation changed. The 1:1 DM call modal shares the new transcript drawer + tokened pill. CSS `.hidden` overrides are added where `main.css` (loaded after tailwind-built.css) would otherwise beat the `.hidden` toggle the JS uses (the LC-391 gotcha). ## Brainstorm captured as follow-ups (sub-issues on LC-402) Remote mute/speaking propagation over WS (only local mute is known today), screen-share pinned tile, full mobile pass, connection-quality indicator, transcript consent/export-link, and 1:1 modal control-bar parity (it gets the drawer but no panel-toggle button yet). Noted in the epic. ## Scope Frontend only: `templates/voice/page.html`, `templates/layout.html` (caption/drawer), `templates/ws/transcript_segment.html`, `templates/base.html` (2 JS strings), `assets/voice.js`, `assets/transcribe.js`, `assets/main.css`, `locales/{en,es}/{misc,js}.ftl`. No Rust, route, or schema change. ## Tests `just check` (standalone + saas compile, clippy, fmt), `just test`, `just test-saas` all pass. `voice.js` + `transcribe.js` syntax-checked (node + bun). `just build-css` regenerated. prefers-reduced-motion respected.
feat(calls): redesign the in-call voice UI (LC-402)
All checks were successful
check-secrets / TruffleHog (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 7s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 3m10s
4f3a37fd54
Reworks the enclave voice channel into a coherent call UI on the existing Askama + HTMX + Tailwind, WebSocket-driven stack. Previously the page was a flat slate/green/red control bar, one ballooned participant tile in empty space, and a live transcript dumped as "username phrase" lines pinned to the bottom scrim, half cut off and fading under the sidebar. The whole call surface also hardcoded palette colours and ignored the app's themes.

Layout + theme (LC-403): clear regions - control bar / participant grid (primary) / transcript drawer (secondary). The voice page, the shared caption banner, and the transcript panel are migrated off hardcoded slate/green/red/indigo onto the semantic theme tokens, so the call UI follows light/dark/high-contrast like the rest of the app. New `.lc-call*` / `.lc-voice*` / `.lc-transcript*` design system in main.css.

Participant grid (LC-404): responsive CSS grid that handles 1 / 2 / many (lone tile is sized, not ballooned; a "Waiting for others" placeholder shows when alone). Tiles redesigned in voice.js: video-vs-avatar, name with a YOU badge, a mute indicator (self), and an active-speaker highlight driven by a self-contained, feature-detected Web Audio analyser loop. A participant-count chip lands in the header.

Control bar (LC-405): two logical clusters - media controls (Mute / Start video / Share screen) and session actions (Transcribe / Transcript panel / Leave) - split by a divider. Mute / video / screen carry clear aria-pressed on/off states; Leave is danger-styled; Transcribe is the danger-tinted "recording" toggle with a pulsing dot that pairs with the live pill and the drawer header.

Transcript panel (LC-406): the bottom scrim becomes a deliberate, themed right-hand drawer (toggle button + close), with its own scroll. Lines render as speaker + timestamp + text, grouped by consecutive speaker (transcribe.js stamps the local time and collapses repeats). Auto-scroll to the latest line, with a "Jump to live" affordance when the user scrolls up. The recording pill, the toggle, and the drawer header now share one obvious state.

JS keeps every data-lc-*/id hook, so the WebRTC mesh + signaling state machines are untouched; only tile structure, control state, and caption presentation changed. New en + es strings; prefers-reduced-motion respected.

Frontend only (templates / CSS / JS / i18n), no Rust change. just check, just test, just test-saas all pass; voice.js + transcribe.js syntax-checked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/lc-402-voice-call-redesign 2026-06-21 00:34:40 +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!419
No description provided.