Repository navigation
feat: add per-participant admin mute/unmute toggle - #750
Conversation
Make the admin-panel mute control functional: a per-participant mute/unmute toggle that issues the existing SMB EndpointMessage remote-mute directive (muteParticipant: 'mute' | 'unmute') plus the #678 global mute-state broadcast, and reflects each participant's current advisory admin-mute state from participantMuteState (#749). - Extract a reusable setParticipantMute(endpointId, muted) send helper in production-line.tsx; refactor the existing confirm-modal mute path to use it. - Replace the disabled mute placeholder in admin-panel.tsx with a state- reflecting toggle; kick stays a placeholder until #638. - Extend admin-panel tests to cover mute, unmute and state reflection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
Verdict: LGTM
Summary: A clean, well-scoped extension of the already-merged advisory mute model (#749) into a per-participant admin toggle. It correctly reuses the participantMuteState map, the UPDATE_PARTICIPANT_MUTE_STATE reducer action, and the existing data-channel send/broadcast pattern rather than reinventing them; the AdminPanel component is well tested. Two non-blocking warnings: the net-new setParticipantMute send-path logic is untested, and the panel toggle silently no-ops when the data channel is closed.
Blocking
None.
Warnings
src/components/ui/production-line/production-line.tsx:642-685— The net-new behavior for #676 lives insetParticipantMute(the"unmute"payload branch, the parameterized broadcast withparticipantId: endpointId, and thefalse/"data channel not open" return path) and has no unit or integration test.admin-panel.test.tsxonly asserts that the AdminPanel invokes theonToggleMutecallback with the right arguments; it does not exercise the actual directive/broadcast JSON construction or the closed-channel branch. Consider extracting the message-construction logic into a pure helper (likedata-channel-parser.tson the receive side) so it can be unit tested, or add coverage for the send path.src/components/ui/production-line/admin-panel.tsx:222— The toggle callsonToggleMute(p.endpointId, !isMuted)and discards the boolean it returns. When the data channel is closed,setParticipantMutereturnsfalse, dispatches nothing, and logs internally — so an admin clicking mute/unmute in the panel gets a silent no-op with no UI feedback and no change to the button state. The sibling UserList/confirmation path surfaces this viasetMuteError. The panel path should give equivalent feedback or at least not appear to succeed.
Suggestions
src/components/ui/production-line/admin-panel.tsx:84,224— The SVGfillis hardcoded to#f96c6c(error red) in both states, and an unmuted participant renders theMicMuted(crossed-mic) icon as the "click to mute" affordance. Combined with the always-red fill it can read as "this participant is muted" even when they are not. Consider differentiating the icon fill by state.
Domain Note
This change affects data channel parsing and the advisory mute directive/broadcast lifecycle. The send path is consistent with the receive side (data-channel-parser.ts maps "unmute" → muted: false), so both mute and unmute round-trip correctly. Advisory (non-server-enforced) mute semantics match the merged self-mute model.
Reviewed by eyevinn-pr-reviewer via daily-backlog-pr Phase 3.
Summary
{ muteParticipant: 'mute' }to the target endpoint plus the global broadcast{ muteState: 'mute', participantId }; unmute sends the'unmute'variants (payloads per the Define admin-mute vs self-mute state model and unmute semantics #677 model).callState.participantMuteState(theRecord<endpointId, boolean>map from Propagate global mute state to all participants (state sync + enforcement model) #678/feat: propagate global mute state to all participants via data channel #749) so it reflects current state for every admin, showing the unmute affordance witharia-pressedwhen muted.UPDATE_PARTICIPANT_MUTE_STATEreducer action from feat: propagate global mute state to all participants via data channel #749 — no new state model and no new dependencies.Test plan
npm test)npm run typecheck)npm run lint)What a human does next
Nothing to configure. To verify manually: join one SMB line from two+ browsers, open the admin panel, click a participant's mute button and confirm their input is silenced and the row shows the muted/unmute state in every connected client; click again to unmute and confirm it reverses (advisory — the target can still unmute locally, matching the merged self-mute model).
Closes #676
🤖 Generated with Claude Code