← The Kibitz Engine · deep dive
Pen/laser annotation over the shared screen on the call stage — now bound to the image it was drawn on, synced to everyone (including late joiners), drawable by an AI agent, and mirrorable to a TV.
<video> → captureStream → shareTrack + meta.stageImage.) Audio stages a "🎵 Now
playing" card on the stage while the sound rides the share-audio lane. Video sound to remote peers
rides a dedicated share-audio lane — a 2nd audio m-line parallel to the share-video lane (GateKind 'shareAudio' = audio[1]; publishShareAudio → replaceShareAudioTrack; received via remoteShareAudioTrack
→ a hidden <audio> sink). Default ON, with a per-device opt-OUT (localStorage 'kbz.shareAudio'='0') to
kill it if a peer/browser misbehaves with the extra m-line; old/opt-out peers just don't negotiate it (graceful,
the mic lane is untouched). Carries both a screen-share's tab/system audio and a staged clip's sound.
Gated by see-screen (video + audio together).ctl data-mesh channel
(sendCtl/sendCtlTo/onCtl, demuxed before onApp — not chat, not meta); the presenter applies a viewer's
toggle only when not suppressed.src/react/ink.ts, src/react/StageInk.tsx)StageInk overlays the stage video's content rect (letterbox-aware via containRect/toNorm), so a point
lands on the same spot on every viewer's screen whatever their panel size. Strokes are normalized 0..1.StageInk takes an imageKey prop = the id of the image currently on the stage. On a
change, switchDoodle(saved, current, fromKey, toKey) (pure, unit-tested) snapshots the leaving image's board
into a per-image store and restores the entering image's (or a clean board for a new image). imageKey comes
from presenter.meta.stageImage (the stager advertises which image is up). undefined (a live screen) →
the running board is left alone.{k:'restore'})InkEvent { k:'restore', image, strokes } (src/core/protocol.ts) — a bulk replay of an image's
doodle, keyed by the stage image.restore, StageInk writes it into the per-image store and paints it if it's the live image
— race-proof (whatever the arrival order, switchDoodle reads the store on the next switch).sendInk is exposed on the controls bridge (CallControls) so a headless agent can broadcast a replay.screen surface → shareTrack + presenting) and stamps
meta.stageImage with a content hash (FNV-1a) so doodles bind per image. It accumulates strokes per image
and replayDoodle()s them (sync).annotate tool (brain-callable on image-presenting agents): { shape: circle|box|point, x, y, r/w/h } in
0..1 image coords, placed from the agent's native vision of the image. Geometry is Node-side + tested
(agent/inkShapes.mjs); the driver relays the strokes via ctrl.sendInk (reusing this peer-ink rendering) and
self-accumulates them so they replay. See witbitz voice-stack.StageInk stage / Widget.tsx)requestFullscreen() on the stage (not the whole widget — the old "Full screen" was a CSS fill that still
mirrored the chrome). .kw-stage:fullscreen fills the screen black; the video letterboxes; the ink overlay
re-measures and stays aligned.<video> (no ink overlay), so the ⛶ button is hidden there —
on iOS use OS Screen Mirroring + the fill view instead.MediaStream (it's for media URLs/HLS), and transcoding would break E2EE. OS screen
mirroring is the supported path, which the fullscreen toggle is designed for.restore replaces the board with the authoritative set, so a per-stroke clear after a restore is coarser.