Skip to content

feat: propagate global mute state to all participants via data channel - #749

Merged
eyevinn-developer[bot] merged 1 commit into
mainfrom
frontend/678-global-mute-state-sync
Nov 5, 2026
Merged

eyevinn-developer[bot] merged 1 commit into
mainfrom
frontend/678-global-mute-state-sync

Conversation

@eyevinn-developer

Copy link
Copy Markdown
Contributor

Summary

  • Broadcast an EndpointMessage { muteState, participantId } over the existing data channel whenever an admin mutes/unmutes a participant.
  • Every client parses the message and stores per-endpointId mute state in callState.participantMuteState.
  • Render a muted indicator next to the affected participant in the participant list for all connected clients.
  • Enforcement is advisory (mirrors the already-merged self-mute directive); authoritative/server-enforced mute + backend persistence (option b) is deferred as a documented follow-up product decision.

Test plan

  • Tests pass (npm test)
  • TypeScript compiles (npm run typecheck)
  • Lint clean (npm run lint)
  • Join one SMB line from two+ browsers, have an admin mute a participant, and confirm the muted indicator appears in every client's list.

What a human does next

Nothing to configure. To verify manually: join one SMB line from two+ browsers, have an admin mute a participant from the participant list, and confirm a muted indicator appears next to that participant in every connected client's list within the normal refresh interval (advisory — the muted user can still unmute themselves locally).

Closes #678

🤖 Generated with Claude Code

…el (#678)

Extend the existing advisory remote-mute directive so an admin's mute is
reflected in every participant's list, not just the mute target.

When an admin mutes a participant, in addition to the existing targeted
self-mute directive we broadcast an EndpointMessage mute-state message
(muteState + participantId) to all endpoints. Each client parses it and
stores per-endpointId advisory mute state in call state
(participantMuteState), which the participant list renders as a muted
indicator.

Enforcement stays advisory, matching the already-merged behaviour: the
broadcast only mirrors what an admin asked for and is not server-enforced.
Authoritative/server-enforced mute with backend persistence and self-unmute
prevention (option (b) in #678) is a deferred follow-up product decision and
is documented as out of scope in the code.

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

@eyevinn-pr-reviewer eyevinn-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Verdict: LGTM

Summary: Clean, well-tested extension of the advisory data-channel mute model. The new broadcast message is correctly distinguished from the existing targeted self-mute/kick directives: getMuteStateBroadcast is evaluated first in the receive chain and keys only off muteState+participantId, so a broadcast can never flip the receiver's own isRemotelyMuted or be read as a force-disconnect (asserted by the new use-rtc-connection test). Reducer action is wired into the TGlobalStateAction union and switch, all CallState construction sites gained participantMuteState, no new dependencies, style consistent. One non-blocking scope note and two minor suggestions.

Blocking

  • None.

Warnings

  • src/components/ui/production-line/production-line.tsx (muteParticipant sender) — The only sender currently emits muteState: "mute"; nothing broadcasts "unmute" yet, so the advisory muted badge, once set in everyone's list, cannot be cleared from the UI (a participant unmuting their own mic locally does not broadcast). This is expected scope separation: the remote-unmute action belongs to the dependent admin-panel issue #676, and #678's job is the state-propagation mechanism (the receive/reducer side already handles both mute and unmute payloads). Flagging so the limitation is explicit; it is documented in-code.

Suggestions

  • src/global-state/global-state-reducer.ts (UPDATE_PARTICIPANT_MUTE_STATE) — participantMuteState entries keyed by endpointId are not pruned when a participant leaves; low impact (badge only renders for current participants and REMOVE_CALL drops the whole map) but a long-lived, high-churn call accumulates dead keys. Optional cleanup on participant removal.
  • src/components/ui/production-line/data-channel-parser.ts (getMuteStateBroadcast) — Re-validates muteState/participantId as strings already guaranteed upstream; harmless/defensive given the loose EndpointMessage payload type. Fine to keep.

Domain Note

The feature depends on SMB relaying an EndpointMessage with empty to: "" to all other endpoints without echoing to the sender (hence the acting admin's local self-dispatch). That broadcast semantic can't be verified from the frontend repo. If SMB does not fan out empty-to endpoint messages, the badge simply never appears on remote clients (silent no-op, not a crash) — worth a quick manual confirmation against a live SMB conference.

Reviewed by code-reviewer (separate invocation from the implementer) via daily-backlog-pr Phase 3.

@eyevinn-developer
eyevinn-developer Bot merged commit 4ad8d16 into main Nov 5, 2026
6 checks passed
@eyevinn-developer
eyevinn-developer Bot deleted the frontend/678-global-mute-state-sync branch November 5, 2026 10:52
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.

Propagate global mute state to all participants (state sync + enforcement model)

1 participant