feat(desktop): the call video tile draws Luke's avatar connecting, live, and ended, with video off, restart, and hang up

Merged
CH
5d ago
+408-0

What

CallVideoTile (apps/desktop/src/renderer/call/call-video-tile.tsx), plan item #5 of the Keyframe video-call plan: <CallVideoTile video audio status speakerMuted onVideoOff onRestart onHangUp />.

  • Connecting (STARTING): Luke's face, "Connecting video", Video off and End call.
  • Live: plays the video track. With no video track it draws Luke's face as a still frame. That still is what a fixture or smoke run captures, since it reaches no avatar. It uses the pinned props as they are, with no extra fixture prop.
  • Ended: "Video ended" and a Restart video button, plus Video off and End call.
  • Off: no tile.
  • The <audio> element is drawn in every state, so the avatar's voice isn't cut when the tile changes state. Its muted property follows speakerMuted.
  • The tile drags the floating window like the call card does (buttons no-drag, a press takes no focus). It reuses the card's ground, border and corner, and the call bar's call-end button.
  • Adds a VideoOffIcon glyph to @sidecar/panel (a camera struck through, drawn the way SpeakerOffIcon is).

Status type: #1 is in flight in parallel, so the tile declares a local AVATAR_STATUS = {OFF, STARTING, LIVE, ENDED} in the shape #1 pins. #9b replaces it with #1's AVATAR_STATUS from #shared/messages/. If #1's string values differ, only that constant changes, because every check goes through the constant.

No caller yet

#9b (call window: run the session) mounts the tile in the call window and hands it the session's tracks. Knip doesn't flag the export: the tile's test imports it, and knip counts test files as entries. So no knip.json ignore was needed. If a --production knip run is ever added before #9b lands, the fix would be a temporary ignore entry for this file that #9b deletes.

Tests

call-video-tile.test.ts (jsdom, through the component's public export). The browser's MediaStream, which jsdom lacks, is stubbed with vi.stubGlobal at that browser boundary. The tests check:

  • each state's words and buttons;
  • the live video track plays, and a live tile with no track draws the still frame;
  • each button answers its own press;
  • the audio plays and its muted follows the speaker toggle both ways;
  • the audio element survives a change of state.

I checked that the mute test fails when the muted effect is removed.

./scripts/check.sh exits 0 (knip, lint, typecheck, discarded-effect, all vitest shards).

Evidence

CI could not verify this; Mac evidence pending. verify.sh can't run on the Linux cloud VM this was built on, and CI builds nothing for the Mac. The image below is only a Linux-side preview: the tile's three states rendered with renderToStaticMarkup over the built styles.css in headless Chrome. It is not app evidence. The tile isn't mounted anywhere yet (#9b), so a verify.sh capture would not show it until #9b lands.

Tile states: connecting, live (still frame), ended

šŸ¤– Generated with Claude Code