Repository navigation
feat(huddle): add opt-in camera video sharing - #8373
DanielProkhorovSnap wants to merge 13 commits into
Conversation
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Agent Code ReviewReviewed the camera-only work against issue #6384, VISION.md, FindingsFixed blocker: unbounded camera enumeration feedback on affected WebKitGTK. No other blocking correctness, security, or agreed-contract issue found in Scores
ReadinessScreen prerequisite #8321 remains open. The user's Linux-to-Mac confirmation |
Camera UIThese are mock-bridge UI screenshots using generated H.264 video decoded by Camera OffThe participant keeps their avatar until video is explicitly enabled. Camera VideoTimed H.264 video replaces the participant avatar independently of screen sharing. Camera SettingsOpening the picker refreshes the available cameras without enabling capture. |
🔐 Codex Security Review
|
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Keep camera enumeration in the webview that owns capture permission and device IDs. Correlate companion requests, fence stale replies, and provide bounded retryable discovery. Cover cross-window discovery, startup ordering, failures, and retirement in provider tests, and model the external owner in companion-only browser fixtures. Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Camera Picker Follow-Up ReviewAdded The companion now requests camera discovery from the main capture window. Agent review of this follow-up found no blocking correctness, security, or Validation:
The documented WebKitGTK/GStreamer dependency workaround and remaining physical |
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Upstream Sync And Latency Follow-Up
Follow-up agent review against #6384, the relay-first product contract and the The PR is a draft because the latency behavior changed after the previous Verification On October 20
|
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
…cap (#8679) ## Summary Fixes the two CI workflow defects that turned `main` red most often in the 21–28 Oct 2026 audit: - **Desktop Tauri:** the lane now caches its apt `.deb`s. It no longer re-downloads 62 MB from a slow Ubuntu mirror on every run. - **Windows Rust:** the lane drops a duplicate `cargo check` and gets a 60 min cap, so a cold run can save its cache. It also updates #8613's `scripts/flake-check.test.mjs`, which breaks once the Tauri package list moves into an env var. There are no product code or test-behaviour changes. This is one of seven PRs split out of the `flakey-tests` branch (siblings below). ### Related issue None found. #8613 touched both workflows after the audit, but not these steps. No open PR changes how the Tauri packages are fetched, the Windows timeout, or its Check (workspace) step: - #8422, #8321 and #8373 only add packages to the list, and would now add them to `TAURI_APT_PACKAGES`. - #8414 adds two mesh-llm Tauri steps to the Windows job. If it lands, they must fit the cold budget below. ### CI failures fixed | Lane / test | Failing runs | Cause | Change | |---|---|---|---| | Desktop Tauri, "Install Tauri dependencies (Linux)" (`_ci-desktop.yml`) | 62 jobs in 51 run attempts, 22–27 Oct. On main: 39821769576, 39822883395, 39827234118, 40333705511, 40342468627, 40348069929, 40353619362, 40368315739, 40373404267, 40374519539, 40556742929 | All 3 `ci-apt-retry.sh` attempts hit their 420 s limit while `azure.archive.ubuntu.com` served the 62 MB at 20–65 KB/s. The 25.5 MB `libwebkit2gtk-4.1-0` alone takes a whole attempt, and each attempt loses its partial download. | The package list moves to a job-level `TAURI_APT_PACKAGES`. A new "Resolve Tauri apt archives" step builds the cache key from `apt-get install --print-uris -o Acquire::ForceHash=SHA256` (name, size and SHA256 per file). `actions/cache/restore` uses that key. The install step copies only the restored files whose SHA256 matches the signed index, because apt 2.8.3 reuses any same-size cached `.deb` without hashing it. A best-effort rebuild keeps exactly the listed files. `actions/cache/save` runs only on core-lane pushes after a successful rebuild; PR runs never save. | | Windows Rust (`_ci-rust.yml`) | All on main: 39796569342 (job 119885795086), 39821769576 (119975358772), 39827234118 (119994286695), 39865872891 (120128351939), 40402556064 (121852634685) | Every test passed. Cold steps take 41.3–43.5 min, so the 45 min cap cancelled the `Swatinem/rust-cache` post-run save 1.6–3.7 min in. No cache was written, and the next push was cold again. | Delete "Check (workspace)". The clippy step before it covers the same `--workspace --all-targets` set with `-D warnings`, which saves 2.0–2.8 min. Raise `timeout-minutes` from 45 to 60, with the measured breakdown in a comment: 39–41 min of steps, a ~4.5 min save and ~15 min of margin. | | `scripts/flake-check.test.mjs` "repeat invocations mirror the lanes they stand in for" | None in CI yet: it fails only with this change applied | #8613 added this test after `flakey-tests` branched. It checks that the advisory `flake-tauri` lane installs the same packages as the Desktop Tauri core lane, by regex-matching the inline `apt-get install -y --no-install-recommends \` list in `_ci-desktop.yml`. That list now lives in `TAURI_APT_PACKAGES`, so the regex matched nothing and the test failed with "Desktop Tauri apt packages". | Read `TAURI_APT_PACKAGES` from `_ci-desktop.yml` and compare package names (12 on each side) with `_ci-flake.yml`'s list. Renaming one package in `_ci-flake.yml` still fails the test. Giving the `flake-tauri` lane the same read-only `.deb` restore is a follow-up. | Local evidence is from the original `flakey-tests` commit (9aa768a3b): - The apt steps ran verbatim in Ubuntu 24.04 containers with apt 2.8.3, with the cache restore and the save step's `if:` emulated. - On a miss, the install fetches everything ("Need to get 254 MB"). On an exact hit with the network down, it fetches nothing ("Need to get 0 B/254 MB"). - On a fallback with one `.deb` missing and one corrupted at the same size, the hash check rejects the corrupt file, and apt re-fetches only the corrupt and missing files. - The key was identical in all four simulated jobs. - The Windows numbers come from the jobs API for the five runs above. On this branch, the split session ran the `scripts/` node tests: `ci-selection`, `ci-required-gates`, `ci-apt-retry` and `flake-check` on macOS gave 84 pass, 0 fail and 8 skipped (Linux-only). Before the `flake-check.test.mjs` fix, that test failed 1 case. The same session ran actionlint 1.7.12 with shellcheck, which was clean, and the rust-cache contract scripts, which passed. ### Verification This branch was pushed without further local re-verification, by the user's choice: local test runs were overloading the machine and failing for that reason. **Hosted CI is the check.** It was pushed with `git push --no-verify` over SSH. The pre-push hook runs tests, and the HTTPS token lacks the `workflow` scope. Only hosted CI can show: - The runner's real package list (about 62 MB) and the same key in all three Tauri lanes. - The `actions/cache` round trip. The first PR run misses, because `main` has no key yet. After merge, the first push to `main` saves it. - A cold Windows run saving its cache within the new cap. The next toolchain bump or cache eviction will show it. Remaining risk: after Ubuntu updates a large package, runs fetch it from the mirror until a push to `main` saves a new key. ### Readiness CI-only change. AGENTS.md's readiness steps (agent review, then human confirmation) are still outstanding, so `buzz-review-completed` is not set. ### Merge order Independent of the other six. It merges cleanly onto `main`, and with every sibling, in any order. ### Sibling PRs Split from `flakey-tests` (one PR per area): #8676 (PostgreSQL lease + lane), #8677 (relay E2E helper + Windows shell), #8678 (mobile), #8680 (desktop product races), #8681 (desktop E2E state gates), #8682 (desktop E2E composer + row actions). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>



Summary
Live video replaces the participant avatar; only self-preview is mirrored.
A client without a webcam can still receive video.
30 FPS at up to 720p. Publish signed, ephemeral kind
24812through theexisting relay, with the same H.264 validation, tenant/member/private-huddle
checks, and no-persistence guarantees as screen video.
transport queues, use background worker clocks, and fence stop, replacement,
leave, signer changes, and community changes.
keyframes immediately, flush sparse capture batches on a deadline, and pipeline
at most four acknowledged publications. Retire stale video dependencies and
recover fresh keyframes without reacquiring the source.
main webview. Companion pickers request that window's device IDs through
channel-scoped, correlated local events with a five-second deadline and
explicit retry, rather than enumerating a permission-redacted device list. Huddle entry,
opening Camera settings, explicit refresh, and successful acquisition refresh
the list; idle mounting and
devicechangestorms do not. This avoids addingthe WebKitGTK enumeration feedback loop reported in fix(desktop): make media device enumeration demand-driven to break WebKitGTK FD-leak loop #7866.
Dependency And Scope
Depends on #8321, the existing screen-sharing PR. This branch includes that
prerequisite commit until it merges; the
camera commit
and the picker follow-up
b3d0eedefare the original camera-only review scope.The latency follow-up
eb4d20f3falso changes shared screen playback andpublication. KaTeX/formula rendering is not included. The screen-sharing PR is
synced separately and does not include camera code.
Related to #6384. This adds the desktop camera portion, not mobile video,
recording, simulcast, or an external SFU. No new HTTP endpoint, production
deployment, database migration, or machine-specific runtime patch is included.
Upgrade both sending and receiving desktop clients. A screen-sharing-only
client cannot display camera video. Upgrade the relay for camera-specific
payload and huddle-admission validation; older relays can forward the
ephemeral kind without those guards.
Testing
just cipassed for the original camera feature, including formatting, both Tauri Clippy graphs,static/security checks, Rust and desktop/native/mobile tests, and
desktop/web builds. Linux checks used the pinned Hermit toolchain and native
library sysroot, with serialized Rust tests.
just test(all 103 runner steps) passed on the camera feature commit.After the earlier upstream sync, the complete integration lane, the real-Redis
ephemeral camera quota regression, and all four real-relay video E2E tests
passed. Coverage includes independent
camera/screen tracks, tenant and membership boundaries, zero stored frames,
and 130-second sustained screen-video delivery. The owned database and
media-store container were removed afterward.
They cover opt-in,
raw IPC geometry, decoded participant video, stop cleanup, unavailable
cameras, explicit picker refresh, and existing media flows. Their native
bridge and relay are mocked.
owner-only enumeration, consistent owner/companion IDs, startup handshakes,
deadlines, stale/foreign reply fencing, visible failure/retry, and preservation
of capture errors. No idle or devicechange-storm probing is introduced.
The original enumeration-loop guard was also mutation-tested previously.
b3d0eedef, normal pre-push checks passed:Rust, desktop lint/typecheck/unit tests, both Tauri Clippy feature graphs and
native tests, mobile analysis/tests, and the repository file-size gate.
System-default-only menu, then verified the fixed picker exposes the owner's
inventory, retains monitor selection, makes zero companion hardware probes,
and leaves capture off. Its inventories were injected fixtures; the actual
cross-window event bridge and controls were exercised, not OS permissions.
1280x720 frames in approximately 5.24 seconds and reached EOS cleanly. No
camera images were saved or sent to the relay.
binary IPC, OpenH264, a local relay, and a second native player. Physical
Linux capture was also exercised, including a hidden owner, with the
dependency workaround described below.
passed. On macOS 26.4, 187 native huddle tests and 35 focused JavaScript tests
passed before this cleanup. A separate native Tauri/WKWebView harness using
the production camera pipeline and controlled local transport sustained
generated 720p video at 25.5 presented FPS for a minute, followed by
stop/restart, stale-session, and simulated-disconnection cleanup.
upgrading the receiver. On October 16, 2026, the human also confirmed the
final owner-mediated picker detects and selects the monitor webcam in the
installed Linux build.
Platform Limits
WebKitGTK 2.52.6 with GStreamer 1.28 has an upstream first-frame freeze:
WebKit bug 324582 / WebKit PR 74373. Physical Linux testing on that affected
combination used an app-local compatibility workaround. This PR does not bundle
that workaround or claim the unpatched affected runtime works.
Physical Mac camera capture/permissions, Windows capture, physical camera
indicator release, and minimized-window behavior need further platform checks.
Generated canvas streams do not establish minimized physical-camera behavior.
Manual Check
audio, screen sharing, and leaving remain usable.
and close the picker. The camera must remain off until explicitly enabled.
upgraded client. Switch cameras, stop/restart, and share camera/screen together.
identity/community. Verify visible recovery, released tracks, and no stale
share revival.
Review Status
review rules; the enumeration-loop guard and companion-owner follow-up were
checked with no remaining blocking findings.
scopes described above.
October 16, 2026.
eb4d20f3f).The earlier picker confirmation remains recorded, but this PR is a draft until
the subsequent latency change has current human confirmation. The prerequisite
and remaining platform-coverage limits are documented above.
Architecture, compatibility, and the complete runbook:
docs/huddle-camera-sharing.md.Camera UI
These mock-bridge screenshots use generated H.264 video decoded by the browser.
They contain no physical webcam image and do not establish native permissions
or live-relay behavior.
Camera Off
The participant keeps their avatar until video is explicitly enabled.
Camera Video
Timed H.264 video replaces the participant avatar independently of screen sharing.
Camera Settings
Opening the picker refreshes the available cameras without enabling capture.
Latency Follow-Up
The native production pipeline was exercised with generated 720p/30 FPS video,
controlled local transport, and delayed acknowledgements. These probes do not
establish physical-camera permissions or production-network performance:
p95 and approximately 26 changing decoded frames per second.
decoded-pixel age, with 23-26 FPS. Rewind recovery and stop cleanup passed.
as passing evidence.
Manual retest: use upgraded sending and receiving clients, enable the webcam,
and compare local and remote motion. Sustain the call, switch cameras, stop and
restart, then share camera and screen together. Confirm playback does not drift
back to the previous approximately one-second delay and controls remain usable.