9fa6f756ca
Adds typed role accessors to make participant-list filtering self-documenting: ParticipantTag.effectiveRole(): ROLE? — case-insensitive parse; null when the role string doesn't match any enum value (so an unknown future "director" role doesn't accidentally pass canSpeak). ParticipantTag.isHost() / isModerator() / isSpeaker() — single-role predicates for per-row UI gating. ParticipantTag.canSpeak() — true for HOST / MODERATOR / SPEAKER; the audio-room VM uses this to gate startBroadcast() so anyone who was promoted (not just the original host) can publish. 5 unit tests cover happy paths, case-insensitivity, the unknown-role → null contract, the canSpeak union, and the effectiveRole enum parse. Tier 1 #5 + #6 consume these — promote/demote and kick both need to render different rows depending on whether the local user is a host or moderator (allowed to manage roles) and the target participant's current role.