feat(calls): pin screen-share to a stage tile (LC-408) #421

Merged
longjacksonle merged 1 commit from feat/lc-408-screenshare-pinned-tile into main 2026-06-21 01:00:39 +02:00

Implements LC-408 (subtask of the LC-402 call redesign): pin an active screen share to a large stage tile instead of letting it sit as an equal grid cell.

What

While any participant is sharing their screen, the participant grid reshapes into a stage:

  • the sharing tile spans the full width, letterboxed (object-fit: contain on a neutral-dark background so the shared content is never cropped),
  • everyone else flows below as a centered filmstrip of small tiles,
  • a "Presenting" badge marks the sharer's tile.

When sharing ends it reverts to the even responsive grid. Multiple simultaneous shares stack as full-width stages with the filmstrip below.

How

A receiver cannot distinguish a screen track from a camera track, so the discrete share state is propagated over the WS exactly like mute (LC-407):

  • Server: new ClientFrame::voice_screen { room_id, sharing }; gated by is_in_voice_room, broadcasts ChatEvent::VoiceScreenChanged { room_id, user_id, sharing } to the room, rendered to #lc-voice-bus as kind="screen" (payload "1"/"0"). render_event returns None for it (handled per recipient).
  • Client (voice.js): announces on share start (toggleScreen) and stop (endScreenShare, which also fires for the browser's own "Stop sharing"), applies data-screen to the peer's tile, and re-announces on a late join so a newcomer pins correctly. selfSharing resets on join/leave.
  • Layout is pure CSS: a .lc-voice-grid:has(.lc-voice-tile[data-screen]) rule does the stage/filmstrip reshape - no JS layout code, and the DOM (flat tiles) is unchanged.

Scope

ws/events.rs (1 variant), routes/ws.rs (frame + handler + render + dispatch), views/ws_fragments.rs (None arm), assets/voice.js, assets/main.css, templates/base.html + locales/{en,es}/js.ftl (one "Presenting" string). No template-page or schema change.

Tests

just check (standalone + saas compile, clippy, fmt), just test, just test-saas all pass. voice.js syntax-checked; just build-css regenerated.

Implements LC-408 (subtask of the LC-402 call redesign): pin an active screen share to a large stage tile instead of letting it sit as an equal grid cell. ## What While any participant is sharing their screen, the participant grid reshapes into a stage: - the sharing tile spans the full width, letterboxed (`object-fit: contain` on a neutral-dark background so the shared content is never cropped), - everyone else flows below as a centered filmstrip of small tiles, - a "Presenting" badge marks the sharer's tile. When sharing ends it reverts to the even responsive grid. Multiple simultaneous shares stack as full-width stages with the filmstrip below. ## How A receiver cannot distinguish a screen track from a camera track, so the discrete share state is propagated over the WS exactly like mute (LC-407): - **Server:** new `ClientFrame::voice_screen { room_id, sharing }`; gated by `is_in_voice_room`, broadcasts `ChatEvent::VoiceScreenChanged { room_id, user_id, sharing }` to the room, rendered to `#lc-voice-bus` as `kind="screen"` (`payload` `"1"`/`"0"`). `render_event` returns `None` for it (handled per recipient). - **Client (voice.js):** announces on share start (`toggleScreen`) and stop (`endScreenShare`, which also fires for the browser's own "Stop sharing"), applies `data-screen` to the peer's tile, and re-announces on a late join so a newcomer pins correctly. `selfSharing` resets on join/leave. - **Layout is pure CSS:** a `.lc-voice-grid:has(.lc-voice-tile[data-screen])` rule does the stage/filmstrip reshape - no JS layout code, and the DOM (flat tiles) is unchanged. ## Scope `ws/events.rs` (1 variant), `routes/ws.rs` (frame + handler + render + dispatch), `views/ws_fragments.rs` (None arm), `assets/voice.js`, `assets/main.css`, `templates/base.html` + `locales/{en,es}/js.ftl` (one "Presenting" string). No template-page or schema change. ## Tests `just check` (standalone + saas compile, clippy, fmt), `just test`, `just test-saas` all pass. `voice.js` syntax-checked; `just build-css` regenerated.
feat(calls): pin screen-share to a stage tile (LC-408)
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 6s
check-secrets / Nosey parker (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Kingfisher (pull_request) Successful in 6s
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 3m51s
b209329228
A shared screen previously sat as an equal cell in the participant grid. Now, while anyone is sharing, the grid reshapes into a "stage": the sharing tile spans the full width (letterboxed, object-fit: contain on a neutral-dark background) and the other participants flow below as a centered filmstrip of small tiles. A "Presenting" badge marks the sharer's tile.

The receiver cannot tell a screen track from a camera track, so screen-share state is propagated over the WS exactly like mute (LC-407): a new voice_screen { room_id, sharing } client frame broadcasts ChatEvent::VoiceScreenChanged to the room (gated by is_in_voice_room), rendered to the voice bus as kind="screen". voice.js announces on share start (toggleScreen) and stop (endScreenShare, including the browser's own "Stop sharing"), applies data-screen to the peer's tile, and re-announces on a late join so newcomers pin correctly. The whole stage layout is a CSS :has([data-screen]) rule, so no JS layout code and it reverts to the even grid when sharing ends. selfSharing resets on join/leave.

Server: one event variant + frame + handler + render arm + the render_event None arm. Frontend: voice.js + main.css + one i18n string (en/es). No 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 feat/lc-408-screenshare-pinned-tile 2026-06-21 01:00:39 +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!421
No description provided.