feat(desktop): the call video tile draws Luke's avatar connecting, live, and ended, with video off, restart, and hang up
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
videotrack. 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. Itsmutedproperty followsspeakerMuted. - 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'scall-endbutton. - Adds a
VideoOffIconglyph to@sidecar/panel(a camera struck through, drawn the waySpeakerOffIconis).
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
mutedfollows 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.

š¤ Generated with Claude Code