Skip to content

RnD sync#883

Open
testradav wants to merge 2270 commits into
security-union:RnDfrom
testradav:RnD
Open

RnD sync#883
testradav wants to merge 2270 commits into
security-union:RnDfrom
testradav:RnD

Conversation

@testradav

@testradav testradav commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

July 10

Realtime-first video playout and honest painted-fps tile metrics

Realtime-first frame presentation

  • fix: a burst of late video frames (e.g. WS/TCP delivery after congestion) used to be decoded AND painted frame-by-frame — a fast-forward "catch-up flash" that left video trailing real time while audio (neteq time-compression) stayed live, i.e. desync in the audio-leads-video direction viewers are most sensitive to. Decoded frames now flow through a latest-wins presentation mailbox: each arriving frame displaces (and closes) any still-unpainted predecessor and a single requestAnimationFrame presents only the newest, so a burst becomes one jump-to-live paint. Steady state (≤1 frame pending per display refresh) is untouched — the mechanism self-gates with no thresholds — and the delta reference chain is preserved (every frame still decodes in order; coalescing is strictly presentational). Camera and screen share intentionally share the path; painted/backpressure accounting is unaffected. Ships mutation-sensitive unit tests on the production mailbox type.

Per-tile media-metrics overlay: measured-at-the-paint fps + reliable audio kbps

  • fix: the overlay's "↓ fps" was a network arrival-rate bucket that legitimately spiked above the sender's encode rate under bursty delivery (field report: 31-60 shown on a ≤30fps sender). It is now painted-fps — counted at the sole canvas paint site, so coalesced-away and hidden-tile frames never inflate it — smoothed by an EMA with an asymmetric snap-down (a stopped video reverts to the em-dash immediately instead of a phantom decay tail). Emission is event-driven: the 1 Hz sampler broadcasts while painting plus two trailing zeros, then stays silent until the next real paint re-arms it (~10× less idle churn in mostly-muted meetings), and a freshly-constructed decoder emits its snap-down zeros so a mid-call decoder swap can never pin a stale reading. The diagnostics drawer's arrival-rate series is unchanged (it remains the network-burstiness signal).
  • fix: the overlay's audio field could stick at "—k"; it now derives audio kbps from the same peer-status heartbeat flag that drives the mic icon (consistent by construction) via a single-source base-audio-layer accessor. Also fixes a latent bug where the drawer's received-audio-bitrate series was silently always 0.

Meeting-roster hardening

  • fix: the host-roster clobber-prevention guard (which discards a stale mid-fetch roster read when a host-change event lands during the fetch) is extracted to a pure, testable boundary with a regression test — behavior unchanged, removing the guard now fails the test.

July 9

Keyboard-accessible action-bar customization, lighter simulcast ladders with a per-tile metrics overlay, and notification-preference splits

Accessibility — the action-bar customize mode is now fully keyboard-usable

  • feat: the action-bar position menu (Bottom / Left / Right / auto-hide / Customize / Reset) is fully operable with Tab, Arrow keys, Home/End, Enter/Space, and Escape; in customize mode, Arrow/Home/End reorder the focused slot with screen-reader live-region announcements ("Camera moved to position 2 of 8"), OS key-repeat is ignored (one press = one step) and modifier+Arrow combos are left to the browser.
  • feat: Tab reaches every action-bar button exactly once (including a previously Tab-skipped disabled Screen Share) and flows left-to-right ending on Done → Hang Up; exiting customize mode restores focus, and both Done and Escape exit. Ships wasm unit tests for the reorder helpers plus extensive keyboard-driven Playwright coverage.

Lighter simulcast ladders + real-time-first frame policy

  • feat: the simulcast video ladder becomes a dedicated single-source-of-truth table — 320×180 @ 7fps / 120 kbps · 640×360 @ 15fps / 350 kbps · 1280×720 @ 30fps / 1500 kbps — decoupled from the (unchanged) single-stream adaptive ladder. Each layer's encoder gets a per-layer framerate hint and a drop-never-queue gate: frames above the rung's rate are dropped so the current frame stays as close to real time as possible; keyframes bypass the gate so all layers keyframe on the same source frame.
  • feat: audio tiers become 48 / 24 / 12 kbps (with an 8 kbps publisher-only network-rescue rung); receiver selection ladder updated to match.
  • perf: total encode pixel-rate −37% (the two auxiliary layer encodes −83%); base-rung uplink 400 → 120 kbps; receiver size-cap boundaries re-derived (198/396/792 px) so tiny grid tiles decode a true 180p stream.

New: per-tile media-metrics overlay

  • feat: a "Display options" checkbox in the diagnostics drawer overlays, at the bottom of each remote tile, the received resolution · fps · audio kbps ("↓ 640×360 · 15fps · 24k") and the sending metrics on your own tile ("↑ …"). Directional glyphs distinguish send/receive (not color alone), text is white-on-scrim for contrast, the readout is pointer-transparent and costs nothing when disabled; the setting persists.

Notification preferences

  • feat: the unified join/leave notification and sound toggles split into four independent toggles (entry message, exit message, entry sound, exit sound), each with screen-reader helper text, persisted as plain-text keys. Note: the legacy unified-key migration was deliberately dropped — existing users will see these preferences reset once, to all-enabled.

UI fixes

  • fix: in a 1:1 meeting, the full-bleed peer tile's cropped video now crops from the video's center instead of anchoring to the top of the frame (most visible on wide windows).

Tests

  • test: keyboard-driven action-bar customize specs; plain-text-seeded notification-preference specs covering suppression, independence, and persistence; simulcast specs re-anchored to the new ladder; overlay toggle/persistence spec.

Antonio Estrada and others added 30 commits June 12, 2026 19:43
…kpressure

fix(codecs): release-side WebCodecs decode-queue backpressure (#1024)
- Action bar: set BOTH left+right longhands in every dock arm (and a
  non-empty 'left:auto; right:auto' on mobile) so dioxus-web 0.7.3's
  omit-and-restore inline-style behavior can't resurrect a stale opposite
  anchor on a dock switch or desktop->mobile transition (controls bar was
  stretching full-width / staying offset). Pure bottom-dock pin/unpin was
  unaffected; this fixes the dock-switch + mobile paths.
- Resize grip: rest color var(--border) -> var(--border-emphasis) (~1.2:1
  was invisible until hover; now perceptible at rest). Hover/focus/active
  var(--accent) unchanged.
- a11y: add aria_label "Resize panel" to both resize separators.
- e2e: add a revert-sensitive test for the pinned Bottom->Right dock-switch
  stale-anchor resurrection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ard (#1295)

Joining with the camera ON showed a persistent dark square (no video feed)
until a manual camera OFF→ON. Root cause: the acquire-phase and per-frame
supersede guards added in #1295 read `switching`. On initial join a
post-permission `devicechange` raises `switching` (via select() while
enabled) on the very loop that should bind the capture stream to
<video id=webcam>; that loop then bailed BEFORE set_src_object, and with
device churn re-raising `switching` no loop ever bound — a persistent dark
square (a manual toggle's stop()+start() clears `switching` and rebinds).

Fix: extract `loop_is_superseded(enabled, loop_epoch, my_epoch)` — epoch +
enabled ONLY, no `switching` — and use it in both supersede guards. The
epoch is the sole supersede authority: a real switch tears the old loop
down and bumps the epoch (caught by loop_epoch != my_epoch), while the
newest loop IS the switch's response and must own it, not self-abort on the
request flag — so it binds. `start()` now lowers `switching` at the epoch
commit (the request is satisfied by the new generation); the now-dead
task-exit `switching` resets are removed. The wrong-device protection
(#1295's purpose) is unchanged — a stale loop still bails on epoch mismatch
before binding, so it can never bind the wrong device.

Test: native unit test `loop_is_not_superseded_when_only_switching_is_raised`
pins the predicate (enabled + matching epoch must NOT be superseded);
mutation-verified — re-adding a `switching` term forces a signature change
that breaks the test. (A JS-only e2e cannot deterministically separate
fixed-vs-buggy here because the restart darkens both sides; the kept e2e
convergence test's comments are re-pointed at this Rust pin.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oin-bind

# Conflicts:
#	videocall-client/src/encode/camera_encoder.rs
…#1283)

The #1170 demand gauge (relay_layer_preference_sessions) was unit-tested at the
pure-classifier level, but the actor-driven sweep loop (sweep_layer_preference_gauge:
room iteration, the per-session has_any() skip, and the explicit-zero SET) had no
test that seeds state and asserts the resulting gauge values.

Add an integration test (NATS-backed, like the other chat_server tests) that seeds
room_members + session_layer_prefs for two rooms — one active with a known
distribution across all 4 buckets x {video,screen}, one with a member but no
recorded prefs (has_any() false) — drives sweep_layer_preference_gauge directly
(it is &self, no actor context), and asserts the per-cell gauge.

Mutation-proofing: a Prometheus gauge reads 0 whether SET to 0 or never touched, so
the test PRE-POISONS every asserted cell to 99 before the sweep; each assertion then
proves the sweep actually SET the cell. Verified the test fails if the has_any() skip
is inverted (active counts collapse to 0) or the explicit-zero SET is dropped (the
poisoned 99 survives where 0 is expected).

Closes #1283.
fix(ui): drawer follow-ups — action-bar reflow, both-open, robust resize + visible grip (#1296)
…-map-bound

fix(relay): bound KeyframeRequestLimiter map, gate new buckets on global cap (#1303)
fix(client): bind camera on join — drop switching from supersede guard (#1295)
recompute_layer_hints_for_source scanned all three media kinds (VIDEO=1,
AUDIO=2, SCREEN=3) via LAYER_HINT_MEDIA_KINDS, but AUDIO has no simulcast
ladder and the publisher's LAYER_HINT dispatch arm ignores AUDIO/UNSPECIFIED
entries (video_call_client.rs, the _ => {} fail-open arm). So the AUDIO union
scan and any emitted AUDIO entry are pure wasted work on the relay.

Drop AUDIO from LAYER_HINT_MEDIA_KINDS ([1,2,3] -> [1,3]) so the relay
computes/emits hints only for VIDEO + SCREEN. Verified safe on both ends:
the const is used only in the recompute scan loop, and the client already
ignores AUDIO hint entries.

Test pins the scan list to {VIDEO, SCREEN} (re-adding AUDIO fails it) and ties
the scanned kinds to laddered wire kinds. Full chat_server layer-hint/recompute
suite (incl. #1203 coalescing) green against NATS.

Note: #1118 N1 (cancel orphaned suppress-lazy re-check timers) is NOT in this
commit — see the issue; the per-source timer vs per-kind window interaction
needs careful per-deadline handling, deferred to avoid a subtle regression.

Addresses #1118 (N3).
Code review note: the layer_hint_media_kind doc still said LAYER_HINT_MEDIA_KINDS
"only contains 1/2/3" — stale after N3 dropped AUDIO. Correct it to "1 and 3"
and note the AUDIO(2) match arm is retained for completeness. Doc-only.
…8 N1)

The suppress-lazy re-check armed by `recompute_layer_hints_for_source`
discarded its `notify_later` `SpawnHandle`, so rapid drop→restore→drop
churn at the ~200 ms preference cadence accumulated orphaned timers
(bounded ~10 per `(source, kind)`, self-draining after the 2000 ms
window, and safe — a fired re-check hits the `!is_member` /
`SkipClearPending` no-op — but wasteful).

Arm one timer per `(room, source, kind)` and track its handle in a new
`layer_hint_recheck_handles` map, cancelling it the instant the downgrade
is resolved: on `SkipClearPending` (demand restored), on `Emit` (matured
or superseded by an eager restore), in `forget_layer_hint_state_for_source`
(publisher left), and wholesale in `Actor::stopping`. Mirrors the #1203 /
pending-departure `cancel_future` pattern.

Mutation-proof test drives drop→restore churn through the real actor and
asserts both (1) the tracked timer count arms-to-1 / cancels-to-0 each
cycle and (2) no orphaned actix timer fires after the full window — the
latter catches a `remove` that forgets `cancel_future` (dropping a
`SpawnHandle` does NOT cancel an actix timer).
- Correct the test's mutation-A prediction: neutering the SkipClearPending
  cancel trips proof (1) at cycle 0's `after_drop` (reads 3, not 1) — the
  un-cancelled timers, including the solo-join (union=0) ones the restore
  normally reaps, accumulate. (Prior text said `after_restore`.)
- Document why the never-activated-session expiry branch deliberately does
  NOT reap either LAYER_HINT map: both are keyed by source publisher, and an
  RTT-election loser never published, so it holds no entry to reap and the
  per-(source,kind) recheck invariant is not stranded.

No logic change.
…intln-to-log

perf(codecs): convert jitter_buffer.rs println! tracing to log:: (#1023)
test(relay): cover the layer-preference gauge sweep loop end-to-end (#1283)
…3-followups

perf(relay): skip AUDIO layer-hint union scan (#1118 N3)
Per request: drop the drawer pin/overlay-toggle entirely; drawers are now
overlay-only but still resizable and still both-openable.

Removed (drawer pin only):
- pin toggle buttons on both drawers + their pinned/on_toggle_pin props
  (peer_list.rs, diagnostics.rs) and the .pin-button / .pinned CSS.
- left_pinned/right_pinned signals + the vc_drawer_*_pinned localStorage
  load/save.
- the inset/reflow math (left_inset/right_inset, 60%-cap, *_render_w),
  grid-container inline left/right insets (grid + screen-share now full-bleed
  left:0;right:0), and the action-bar (controls nav) inline carve-out — the
  tile grid + controls return to the original full-width/overlay layout.
- the now-dead `mobile` local that only fed the pin paths.

Kept:
- drag-resize: .drawer-resize-handle + visible ::before grip, aria-label
  "Resize panel", rAF-throttled pointer handlers, robust drag-end on
  pointerup/pointercancel/lostpointercapture; width applied to the overlay
  drawer; vc_drawer_left_width/vc_drawer_right_width persistence (load_f64
  clamp + save_f64 on drag-end), DRAWER_MIN_WIDTH/max_for_side clamp.
- both drawers open simultaneously (independent open state, mobile z-index
  stacking).

NOT touched: the separate peer-TILE pin (pinned_peer_id / on_toggle_pin on
canvas tiles / grid-item-pinned) is a different feature and is left intact
(canvas_generator.rs zero diff).

e2e: renamed drawer-pin-resize.spec.ts -> drawer-resize.spec.ts; dropped the
pin tests (toggle, reflow, action-bar carve-out, both-pinned, dock-switch
stale-anchor, mobile-ignores-pin); kept resize-clamp, width-persist-across-
reload, no-hover-latch, lost-capture end, visible grip, mobile-handle-hidden,
both-open; added a "left drawer overlays, no grid reflow" guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(ui): remove drawer pin capability, keep drag-resize (#1296)
…r-hint

fix(relay): drop client-sent LAYER_HINT at ingest, never reflect to room (#1119)
Sync HCL PR-staging into the public RnD branch. Brings the latest HCL work
(pre-join auto-show devices, transport-pin override, lobby device passthrough
+ camera-bind fix, drawer resize, relay/decode hardening, etc.). OSS
`.github/workflows` restored from upstream/main (HCL-specific workflows
stripped). HCL-only blocked paths verified absent. upstream/RnD (security-union#863)
incorporated. Build-verified: meeting-api + wasm videocall-client + wasm
videocall-ui compile; fmt + clippy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ed-recheck-timers

fix(relay): cancel orphaned layer-hint suppress re-check timers (#1118 N1)
A relay layer-union-cap shed is demand-driven (every receiver asked for
fewer layers), NOT a flap, so it is deliberately excluded from the anti-flap
penalty box (#1141). But it was also SILENT — field logs could not tell a
union-driven shed from a backpressure/congestion one, so an incident like the
observed 11-shed/11-restore churn could not be attributed to a shed path.

Per the #1294 decision (observability-first; do NOT damp the union path — it
is correct demand-driven behavior, and the churn, if real, originates upstream
in the relay union aggregation [#1288/#1202], not the sender AQ): emit an
`AQ_LAYER_SHED: cause=union_cap` log line plus a monotonic `union_cap_shed_total`
counter on the union-cap shed branch. Observability ONLY — no control decision
changes; the penalty-box exclusion is untouched.

Mutation-proof test asserts (1) a union-cap shed increments the counter and
(2) a backpressure shed does NOT — the two halves independently guard against
counting on the wrong branch and counting on every shed.
Per the #1201 decision: keep audio rungs always-published (no runtime shed),
do NOT act on relay AUDIO hints.

- Fix the stale "AUDIO has no simulcast ladder" comments in the LAYER_HINT
  dispatch arm — audio HAS had a 3-rung ladder since #1086; the accurate reason
  AUDIO hints are ignored is that the publisher does not act on them and the
  relay computes no AUDIO union (#1118 N3).
- Document on `audio_layer_is_published` that the user SEND ceiling is the ONLY
  runtime audio gate, so the full 24/32/50 kbps ladder (~106 kbps) is published
  unconditionally as an accepted cost. A publisher-side shed was rejected because
  today's only signal is the receiver's VIDEO-downlink proxy; shedding audio on a
  video-congestion proxy would risk degrading the highest-priority/lowest-cost
  stream while audio is fine. Revisit only if a real audio-downlink-health signal
  lands (#1290).

Comment/doc only — no behavior change.
The relay-side 'computes no AUDIO union' is only true once #1118 N3 (PR #1330)
merges; on current PR-staging the relay still computes it. Reword so the claims
are accurate regardless of N3 merge order: the client-side truth (publisher never
acts on AUDIO hints; audio always published) holds now, and the relay change is
cited as the coordinated #1330 follow-up rather than asserted as already-live.
…ility log

Review fix: active is capped to `union_requested_layer_cap.min(user_layer_ceiling_cap)`,
so a top-side cap shed can be driven by the relay union OR the user "layers
published" ceiling. The first cut hardcoded `cause=union_cap` and incremented
`union_cap_shed_total` for either — so a user lowering their perf-panel thumb
(union fail-open) emitted the self-contradictory `cause=union_cap ... cap=uncapped`
and corrupted the very signal #1294 adds.

Attribute the shed to whichever cap is the lower count (the one that determined
`cap` via `.min()`): `cause=union_cap` (+ counter) when the union binds,
`cause=user_cap` (no counter) when the user ceiling binds. New test
`test_user_ceiling_shed_is_not_counted_as_union_cap_1294` guards it; verified to
fail if every cap shed is attributed to the union counter. Still observability
only — no control change.
Metric-only (no governor) signal for how far behind live a receiver's
decoded video is — making the #1252 audio-ahead-of-video lag observable.
The 5-6s lag read 94-97 on the quality score during the stall, and
#1024's release-side gate may have RELOCATED the backlog from stage-2
into stage-1 rather than removing it; neither was visible server-side.

Estimator (worker-computed, read-only, spans both receive stages):
  playout_latency_ms = stage1_span_ms (newest - next-to-release buffered
  arrival-time span, the dominant term) + decode_queue_depth() *
  source_frame_interval_ms (stage-2, bounded by #1024's HWM).
source_frame_interval_ms tracks SOURCE cadence via an EWMA over
released-frame inter-arrival deltas, not the decoder drain rate.

- proto: VideoStats.playout_latency_ms (5) + playout_stage1_span_ms (6)
- jitter_buffer: read-only buffered_span_ms / source_frame_interval_ms /
  playout_latency_ms + release-cadence EWMA; 3 unit tests
- worker/messages/decoder: carry both metrics across the worker->main bus
- health_reporter: fold into the health packet only when fps_received > 0
  (a paused/hidden tile's stale frame is not latency)
- metrics(server): two per-peer GaugeVec mirroring video_seq_loss_per_sec,
  set unconditionally (recover to 0) + per-peer label cleanup

stage1_span is emitted separately so a dashboard can tell whether #1024
removed the backlog (total drops with stage1) or relocated it (total
stays high while stage1 drops).
Adds a per-peer observability metric `videocall_video_playout_paint_lag_ms`:
the decoded-but-unpainted backlog sitting in the worker->main postMessage
queue + main-thread paint task queue — a region `decode_queue_size()`
(stage 2) structurally cannot see. Complements the #1337 playout-latency
metric and is the signal that disambiguates the open #1252 question: is the
5-6s lag in stage-1 (the jitter-buffer no-keyframe hole, which #1337 already
surfaces) or downstream of the jitter buffer in the paint path (which #1337
is blind to)? GPU-15%-while-lagging fits the latter.

Estimator (worker-computed, read-only):
  playout_paint_lag_ms = (frames_emitted - frames_painted)
                         * source_frame_interval_ms
frames_emitted is incremented in the WebCodecs `on_output` closure; the main
thread ACKs its cumulative frames_painted (every drained VideoFrame, painted
or hidden-dropped) back via WorkerMessage::PaintProgress, throttled to <=2/s.
Computed in the worker because the worker's 1Hz stats message rides the SAME
postMessage FIFO as the frames — comparing emitted-vs-painted at stats-receipt
on the main thread would be delayed by the very backlog it measures and read ~0.

The emitted/painted counters are deliberately lifetime-monotonic on BOTH sides
and are NOT reset on an in-place jitter-buffer reset: the main-thread painted
counter survives the worker's internal decoder reset, so zeroing only the
worker side would desync it from the next monotonic ACK and floor the metric
to a false 0 for minutes. Frames dropped by destroy_decoder() were never
emitted; already-posted frames still drain and count — so emitted-painted stays
the true in-flight backlog across resets without any reset.

- proto: VideoStats.playout_paint_lag_ms (7)
- jitter_buffer: pure `paint_lag_ms` helper + 2 mutation-resistant unit tests
- worker/messages/decoder: FRAMES_EMITTED/PAINTED counters + PaintProgress ACK
- health_reporter: fold into the health packet only when fps_received > 0
- metrics(server): one per-peer GaugeVec on the same 6-label set as #1337
  (no new Prometheus dimension), set unconditionally + per-peer cleanup
anhelinakrutaya-ops and others added 28 commits June 17, 2026 20:43
The keyframe-less freshness-eviction path (#1025) fired a proactive
keyframe request at a fixed ~1/s for the entire duration of a stall.
Field data from two consecutive infra meetings showed this 1:1
(freshness_skip == proactive_kf, e.g. 79/79) as the receiver-side
amplifier of the PLI keyframe-storm freeze loop: every receiver pokes
the active speaker once per second across a meeting-long sequence of
freezes, and the relay's delivery-unaware KEYFRAME_REQUEST limiter
(#1297 residual) then delays the very recovery keyframe those pokes
ask for.

Damp the loop: the first request still fires at the relay window
(1000ms base), but each subsequent request within the backoff window
doubles the interval (capped at 8s via
PROACTIVE_KEYFRAME_REQUEST_MAX_BACKOFF_EXP=3).

Crucially the backoff exponent is keyed to *request cadence*, not to
frame release. The #1479 field shape is many short (1-5s) freezes
interleaved with smooth playout, NOT one long stall — so resetting on
every frame release would zero the exponent during each smooth gap and
the backoff would never accumulate across the storm (a premise the
pre-submit backend reviewer correctly flagged on the first cut). The
exponent therefore persists across the storm's closely-spaced
re-freezes and only resets after a genuinely quiet interval
(PROACTIVE_KEYFRAME_REQUEST_BACKOFF_RESET_MS=12s, comfortably above the
8s cap) with no proactive request — a true sustained recovery.

Going quiet is safe: the independent reactive gap-driven request path
(peer_decode_manager::should_request_keyframe, 1s base + its own backoff
+ uncapped slow-retry) remains the binding lower bound on freeze
recovery; this proactive path is only an accelerator on top of it.
Introduces zero new keyframe-request sources, preserving the
#1287/#1297/security-union#814 keyframe-storm-avoidance invariant.

Adds proactive_keyframe_request_backs_off_and_resets_after_quiet_interval
(mutation-checked: killing the backoff or the quiet-interval reset both
fail it) and updates the existing throttle test to assert the grown
interval.
…--tests

The flush precondition assert used packet_buffer.len() > 0, which trips
clippy::len_zero. The failing CI job runs a target combination the local
checks missed: cargo clippy -p neteq --no-default-features --features web
--tests -- -D warnings. Verified the fix against that exact command.
CI's cargo clippy job runs five commands (.github/workflows/
pr-check-rust-hcl.yaml), not one: --all plus per-crate --tests runs with
crate-specific feature flags (neteq --no-default-features --features web
--tests). A local 'cargo clippy' / 'cargo clippy --all' lints only
library/binary targets, so deny-by-default lints inside #[test] code and
behind feature flags slip through locally and fail in CI on an
already-pushed PR (PR #1491 shipped a clippy::len_zero in a neteq
--features web test for exactly this reason).

Add a 'make clippy-ci' target that mirrors those five commands
byte-for-byte, and point the CLAUDE.md linter rules at it. Keep the
target in sync if the workflow changes.
…fix banner count in SS mode

PR #1467 follow-up. The screen-share panel inlined a duplicate of the
user-requested decode-promotion loop; route it through the pure
promote_requested_into_decoded helper (already used by the normal grid)
so the two can't drift. SS renders all tiles (vertical scroll, no +N
badge), so it passes displayed_tile_count = ss_all.len() — every
off-budget tile is renderable, so the helper's true-overflow bound
(#1470) never excludes an SS peer, preserving the prior inline behaviour.
The pinned_slot resolution keeps the ss_budget < ss_all.len() bound
(mirroring the helper's own early-return) so the get_peer_user_id scan is
skipped when the budget already covers every tile.

Also fix DecodeBudgetBanner avatar_count during screen share: it
reflected the normal-grid avatar_tiles.len() even when the SS panel was
the active layout. Derive it from ss_avatar_tiles.len() when
has_screen_share so the 'N videos paused' count matches what the user
sees. Cosmetic/pre-existing (the old avatar_tile_count had the same
property), no live bug.

Adds promote_with_displayed_eq_len_admits_all_offbudget_requests, a host
test pinning the displayed_tile_count == all_tiles.len() no-op-take
equivalence the SS path relies on (mutation-checked: tightening the
.take() bound fails it).
videocall-ui runs native libtest, so its #[test] code was invisible to
'cargo clippy --all' (which lints only lib/bin targets) — the same gap
that let PR #1491 ship a clippy::len_zero in a neteq --features web test.
A pre-existing clippy::assertions_on_constants was sitting unlinted in
decode_budget.rs as a result.

- Add 'clippy (videocall-ui test targets)' step to pr-check-rust-hcl.yaml
- Add the matching line to the Makefile clippy-ci target (kept in sync)
- Fix the surfaced lint: assert! over consts -> const { assert!(..) }
- CLAUDE.md: when adding a crate with test code, add a --tests clippy
  step to BOTH the workflow and clippy-ci.
…li-backoff

fix(#1479): exponential backoff on proactive-PLI keyframe requests
fix(#1402): wire WorkerMsg::Flush to actually flush the NetEq buffer (no-op flush left hiss after peer audio-off)
…target

ci: add make clippy-ci + lint videocall-ui test targets (#1498)
…udget-dry

fix(#1472): share decode-budget promotion helper with screen-share + fix SS banner count
…ess-log-instrumentation

feat(#1486 + #1384): log relay downlink-relief epoch staleness + host-testable freshness_skip formatter
enable protocol badge
feat(#1483): server-gated WT/WS transport badge on peer tiles
# Conflicts:
#	.github/workflows/ci-host-janitor-hcl.yaml
#	.github/workflows/daily-deploy-ascend.yaml
#	.github/workflows/daily-deploy-hcl.yaml
#	.github/workflows/pr-check-backend-hcl.yaml
#	.github/workflows/pr-check-rust-hcl.yaml
#	docs/hcl-daily-operations.md
Squashed public sync of the latest PR-staging R&D batch (decode-budget
tile fixes, simulcast/diagnostics observability, audio/NetEq + transport
resilience, perf, prometheus-ingress TLS). The HCL-internal Smatter/JMAP
in-meeting chat integration is EXCLUDED from the public fork (depends on
an internal backend); its tree is fully removed AND its commit history is
intentionally not carried onto this branch (squashed to avoid exposing
internal docs/secrets that lived in that branch's history).

Verified: cargo check green (meeting-api, wasm videocall-client, wasm videocall-ui).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Syncs HCL PR-staging fixes into the public RnD branch. HCL-internal
Smatter/JMAP chat integration, HCL workflows, and blocked paths are
excluded; internal commit history is collapsed to this single commit.
…aging-derived tree

security-union#864 was an earlier HCL->public RnD sync. This branch (testradav/RnD) already
carries the newer, equivalent content for every file: all 14 conflicting files
are byte-identical to labs/PR-staging here (attendants.rs == PR-staging minus the
intended Smatter-chat strip), while security-union#864's versions are the older snapshot. No
file exists in security-union#864 that is absent here. Resolved with -s ours so the public tree
stays exactly the vetted, HCL-stripped current state; security-union#864 is recorded as merged
to clear the PR conflict.
…nion#866 conflicts (newer HCL code wins; upstream owns config/helm/OSS-workflow)
…conciliation; trees already identical, no content change
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

👋 Thanks for your contribution!

A maintainer will review your changes shortly.

✅ Automated Checks

CI/CD tests will run automatically. Please ensure all checks pass before requesting review.

🚀 Preview Deployments

Maintainers can test this PR in a live environment using these commands:

  • /build-images — Build Docker images for this PR (~10-15 min)
  • /deploy — Deploy preview environment (~3-5 min)
  • /build-and-deploy — Build images and deploy in one step (~15-20 min)
  • /undeploy — Tear down preview environment

Preview URLs will be posted automatically once deployed.

📚 PR Deployment Documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants