feat(desktop): Luke's avatar video moves to the top of the Call tab, and the call card goes back to audio only
Merged
CH
5d ago+502+502+012345678901234567890123456789-571-571-012345678901234567890123456789
Follow-up to the avatar feature: the floating call window's video tile was too small. Luke's avatar now plays as a large 16:9 video at the top of the panel's Call tab. The floating call card goes back to its audio-only 264x92 size.
What moves
- The Keyframe session runs in the panel's renderer.
callAvatarAtom(#9b's scopedrendererRuntime.atom), thebundleAtomload, the feed mapping, theAVATAR_REPORTreports and the autoplay retry are unchanged. They run in the panel now.- The session stays mounted at the renderer root, so it keeps running whatever tab is showing and while the panel is hidden.
- The avatar's
<audio>is split out of the tile asAvatarVoice.PanelAvatarVoicemounts it beside<App />inindex.tsx, so a tab change never cuts the voice.
- The tile is drawn only in the Call tab.
CallTabVideo(renderer/call/panel-avatar.tsx) drawsCallVideoTile, now video-only, through a newvideoslot onPlanCall.- It appears only on the call's own plan, and only while that call is in progress and
avatarTiledholds (callTabVideoStatusincall-card-model.ts). - The CSS is resized to the tab's width with the transcript's 16px gutter, and the drag region is gone.
- Main targets the panel.
PanelManagerholds the relay, renamedCallAvatarRelayāPanelAvatarRelay(main/window/panel-avatar.ts).- It sends
onAvatarSessionandonAvatarFeedto the primary panel's webContents, and only after that panel'sready-to-show. It re-hands the standing grant when the panel draws. AVATAR_REPORTandVOICE_SET_VIDEOare accepted from thepanelsender only.avatar-link.tsandavatar-acts.tstakepanel: Pick<PanelManager, ā¦>in place ofcallWindow.
- The call window is restored to its pre-#9a shape.
call-window.ts,call-window-presence.ts,call.html(back toconnect-src 'none') andCallWindowCardare back to what they were.CALL_VIDEO_BOUNDS,callWindowFrame,fitVideoandavatarTiledare gone from them. - CSP.
connect-src wss://*.livekit.cloud https://*.livekit.cloudmoves toindex.html.- The preview logs show one
avatar-startedline on 2026-10-14 withhost: keyframelabs-n1yiobta.livekit.cloud. - I kept the wildcard, which the task allowed when narrowing isn't tighter. livekit-client 2.22.4 (
RegionUrlProvider) fails a refused join over to regional hosts it gets from the project's/settings/regions. Those hosts are other*.livekit.cloudnames that no tighter CSP source can express. The HTML comment says so.
- The preview logs show one
- Sentry and session replay.
- The Sentry
beforeBreadcrumbandbeforeSendscrub already runs in every renderer, the panel included, and is unchanged. - The panel is the window that records session replay. So
SESSION_REPLAY_MASKINGnow setscapture_performance: falseandenable_recording_console_log: falseexplicitly. A refused LiveKit join's URL and its console line carry the token in the query string. Both were already off by default with the preloaded remote config; now they are asserted. Both names are added to the test that checks them against the installed bundle.
- The Sentry
- AGENTS.md. The Keyframe carve-out now names "the panel window" instead of "the floating call window". Nothing else in that sentence changed.
- Docs. PRIVACY.md,
apps/web/server/README.mdand thevoiceVideosetting's detail text now say where the video is shown.
Hidden panel and audio: every window, panels included, is built from hardenedWebPreferences, which already sets backgroundThrottling: false and autoplayPolicy: "no-user-gesture-required". That is the same setup the hidden voice window plays Luke's voice under today. No window settings changed.
Tests
avatar-acts.test.ts: reports and toggles are accepted from the panel. A report or toggle from the floating call window or the voice window is refused and changes nothing. The grant and the feed reach the panel's page.panel-avatar.test.ts(moved fromcall-window-avatar.test.ts): the grant and feed reach the panel's page and never the call window's or the voice window's. A panel that draws later gets the standing grant and not the stale feed.call-window-presence.test.ts: rewritten for the card alone. The card stands only while a call is in progress and no panel has the keyboard. A late blur changes nothing.call-video-tile.test.ts: the tile draws no audio.AvatarVoiceplays its track, follows the speaker toggle both ways, and releases the track once it is gone.call-card-model.test.ts:callTabVideoStatusdraws the tile only on the call's own plan, while the call is in progress, for STARTING, LIVE and restartable ENDED.
./scripts/check.sh exits 0.
Not verified
- Mac evidence pending. CI could not verify this, and this Linux VM can't run
verify.shorrun.sh --preview. The user will retest on the Mac: the tile's look at the top of the Call tab, the hidden panel still playing the avatar's audio, and the card back at 264x92. - No live Keyframe session was opened. Untested against a live session:
- the panel joining the room under the new CSP;
LIVEon the first frame while the panel is hidden or on another tab;- lip-synced audio from a hidden panel;
- the handover that mutes the voice window's element.
- Known limits:
- The grant goes to the primary panel only. Luke has one panel window, keyed by display.
- A panel that reloads mid-call is handed the standing grant again on
did-finish-load, as the call window re-handed it to a renderer it stood back up. A panel renderer that crashes and is not reloaded still leaves the avatar reading as up. The panel has no crash-recovery path of its own; that is out of scope here.
š¤ Generated with Claude Code