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-0

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 vocabulary CALL_VIDEO_STATUS = {CONNECTING, LIVE, ENDED}, and callVideoTile({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 own MediaStream. Hang up is the call bar's own .call-end disc 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 answers data-pointer-stale the same way. Over a picture the controls sit on a scrim in light colours, in both appearances.
  • VideoIcon in @sidecar/panel's glyphs (Tabler video, 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.sh exits 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.sh PNG 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.css and 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:

dark

Light:

light

šŸ¤– Generated with Claude Code