fix(calls): screen-share stage never applied + could stick on peers (LC-408) #423

Merged
longjacksonle merged 1 commit from fix/lc-408-screenshare-stage into main 2026-06-21 01:23:59 +02:00

Fixes two bugs in the LC-408 screen-share pin reported after #421.

1. The stage layout never applied (sharing tile didn't span; no filmstrip)

voice.js showed the participant grid with an inline style.display = 'grid'. An inline style beats the stylesheet, so the .lc-voice-grid:has(.lc-voice-tile[data-screen]) rule that switches the container to a flex "stage" was silently overridden - the grid stayed an even grid and nothing pinned.

Fix: toggle a .lc-voice-grid--hidden class for the shown/hidden state instead of an inline display. With no inline override, the stage rule wins by specificity, so the sharing tile spans full width (letterboxed) and the others drop to a filmstrip below.

2. A peer could stay "sharing" after the presenter stopped

The explicit voice_screen=false does clear data-screen, but when the presenter has no camera the peer's <video> could freeze on the last screen frame (a missed track onmute/onremovetrack), so it still looked like sharing.

Belt-and-suspenders, both client-side:

  • the screen event now re-runs updateTileMedia to revert the camera-vs-avatar swap when the signal arrives, and
  • updateTileMedia clears a stale data-screen on any remote tile that no longer has a live video track (a screen share always carries one), so the stage can never get stuck pinned even if the explicit signal is missed.

Scope

Frontend only: assets/voice.js (class toggle + two robustness nudges), templates/voice/page.html (grid hidden via class, not inline display), assets/main.css (the --hidden rule). No Rust, route, or schema change.

Tests

just check, just test, just test-saas all pass. voice.js syntax-checked; just build-css regenerated.

Fixes two bugs in the LC-408 screen-share pin reported after #421. ## 1. The stage layout never applied (sharing tile didn't span; no filmstrip) `voice.js` showed the participant grid with an inline `style.display = 'grid'`. An inline style beats the stylesheet, so the `.lc-voice-grid:has(.lc-voice-tile[data-screen])` rule that switches the container to a flex "stage" was silently overridden - the grid stayed an even grid and nothing pinned. Fix: toggle a `.lc-voice-grid--hidden` class for the shown/hidden state instead of an inline `display`. With no inline override, the stage rule wins by specificity, so the sharing tile spans full width (letterboxed) and the others drop to a filmstrip below. ## 2. A peer could stay "sharing" after the presenter stopped The explicit `voice_screen=false` does clear `data-screen`, but when the presenter has no camera the peer's `<video>` could freeze on the last screen frame (a missed track `onmute`/`onremovetrack`), so it still looked like sharing. Belt-and-suspenders, both client-side: - the `screen` event now re-runs `updateTileMedia` to revert the camera-vs-avatar swap when the signal arrives, and - `updateTileMedia` clears a stale `data-screen` on any remote tile that no longer has a live video track (a screen share always carries one), so the stage can never get stuck pinned even if the explicit signal is missed. ## Scope Frontend only: `assets/voice.js` (class toggle + two robustness nudges), `templates/voice/page.html` (grid hidden via class, not inline display), `assets/main.css` (the `--hidden` rule). No Rust, route, or schema change. ## Tests `just check`, `just test`, `just test-saas` all pass. `voice.js` syntax-checked; `just build-css` regenerated.
fix(calls): screen-share stage never applied + could stick on peers (LC-408)
All checks were successful
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / Nosey parker (push) Successful in 5s
check-secrets / TruffleHog (push) Successful in 7s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Kingfisher (pull_request) Successful in 5s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 3m13s
fe07580177
Two bugs in the screen-share pin:

1. The stage layout never took effect. voice.js set the grid's shown state with an inline `style.display = 'grid'`, and an inline style beats the stylesheet - so the `.lc-voice-grid:has([data-screen])` rule that switches the container to a flex "stage" (full-width sharing tile + filmstrip) was overridden and the grid stayed an even grid. Fixed by toggling a `.lc-voice-grid--hidden` class instead of an inline display, so the stage rule wins by specificity. The sharing tile now spans full width with the rest below as a filmstrip.

2. A peer could stay "sharing" after the presenter stopped. The explicit voice_screen=false does clear data-screen, but if the presenter had no camera the peer's video could freeze on the last screen frame (a missed track onmute/onremovetrack). Belt-and-suspenders: the screen event now re-runs updateTileMedia to revert the camera-vs-avatar swap, and updateTileMedia clears a stale data-screen on any remote tile that no longer has a live video track (a screen share always carries one), so the stage can never get stuck pinned.

Frontend only: voice.js (class toggle + two robustness nudges), voice/page.html (grid hidden via class), main.css (the `--hidden` rule). No Rust/schema change. just check, just test, just test-saas all pass; voice.js syntax-checked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch fix/lc-408-screenshare-stage 2026-06-21 01:23:59 +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!423
No description provided.