feat(desktop): the call's video tile, Luke's picture or his still frame with video off, restart, and hang up
Merged
CH
6d ago+394+394+012345678901234567890123456789-0-0-0123456789
What
PR #6 of the video-calls rollout: <CallVideoTile video audio status onVideoOff onRestart onHangUp /> in apps/desktop/src/renderer/call/, driven only by props.
call-video-tile-model.ts: the tile's own status vocabularyCALL_VIDEO_STATUS = {CONNECTING, LIVE, ENDED}, andcallVideoTile({status, picture}), which decides what is drawn and offered:- connecting: Luke's still frame with "Starting video", plus video off (the way to abandon a start) and hang up;
- live: the picture once a track is handed, plus video off and hang up. A live call with no track handed draws the still frame with nothing said over it. That covers both the beat before the room's first track and the smoke fixture, which has no room to hand one;
- ended: the still frame with "Video ended", plus a "Restart video" pill and hang up.
call-video-tile.tsx: a 16:9 tile. Both tracks are played through always-mounted<video muted>and<audio>elements, each track wrapped in its ownMediaStream. Hang up is the call bar's own.call-enddisc and label.call.css: the tile uses the call card's edge, ground and window shadow, so it reads as the card grown. The whole tile drags the window and its buttons are no-drag, as the card does, and it answersdata-pointer-stalethe same way. Over a picture the controls sit on a scrim in light colours, in both appearances.VideoIconin@sidecar/panel's glyphs (Tablervideo, MIT).knip.json: the tile is listed as an entry, because nothing mounts it yet. #11 removes that line when it mounts the tile.
video and audio are typed as MediaStreamTrack | undefined, the narrowest browser type. #7's AvatarRoom hands its LiveKit tracks' mediaStreamTrack here.
Consumer: #11 (room delivery to the call window) mounts the tile and maps AVATAR_STATUS to it: STARTING ā CONNECTING, LIVE ā LIVE, ENDED ā ENDED. #13 binds the buttons to VOICE_SET_VIDEO and the call's end. The speaker mute is not one of the pinned props, so #11/#13 apply it by handing audio={undefined} while the speaker is muted.
Verification
./scripts/check.shexits 0.call-video-tile-model.test.ts: what each state draws and offers.- CI could not verify this on the Mac. CI builds nothing for the Mac, and this PR came from a Linux cloud workspace.
./scripts/verify.shPNG evidence is still owed by a human on a Mac. Nothing renders the tile in the app until #11, so that pass belongs with #11's. - The screenshots below come from the built
styles.cssand the real component, rendered in headless Chrome on Linux. Each shows four tiles: connecting, live with no track (the still frame), live with a canvas-captured picture, and ended. They are not Mac evidence.
Dark:

Light:

š¤ Generated with Claude Code