[QA] §D2 Element Call fork feature sweep: in-source denoise reconnect, call_state indicators, focus-participant, in-call decorations, transparent bg, soundboard, quality controls, server-enforced call permissions #174

Closed
opened 2026-09-17 23:24:06 -04:00 by jared · 2 comments
Owner

Migrated from LOTUS_TESTING.md §D2 on 2026-09-17 — the file is now reference-only.

How to report: tick each item as it passes; on FAIL comment with what you saw vs expected, browser/OS, web (chat.lotusguild.org) vs desktop (Tauri), theme, and any browser-console errors. Screenshots for anything visual.

Checklist

  • D2-1. Denoise in-source — survives reconnect (fixes A7) highest risk (everyone's mic)
  • D2-2. Speaking + mute indicators from widget events (#2)
  • D2-3. Focus camera during a screenshare (#4 / A5)
  • D2-4. In-call avatar decorations (#6) — NEW, beyond A6
  • D2-5. Native transparent background (#5)
  • D2-7. In-Call Soundboard (#3 / P5-15) — 👥 2 people — NEW
  • D2-8. Call Quality Controls (#7 / P5-31) — 👥 2 people — NEW
  • D2-9. Call Permissions — HARD server-side, cross-client (👥 2 people, admin) — NEW

The whole EC iframe is now our self-built fork (@lotusguild/element-call-embedded@0.20.1-lotus.1).
Five features are active (the host sets their flags / sends their actions); two ship dormant.
Confirm you're on the fork first: EC iframe console prints Element Call embedded-v0.20.1-lotus.1
(the old build prints embedded-v0.20.1). If it says the old version, the web deploy hasn't landed —
the fork features won't be present, so don't test D2 yet.
For non-dev testers, each item below also states the plain " good if / tell us if" outcome.

D2-1. Denoise in-source — survives reconnect (fixes A7) highest risk (everyone's mic)

Flag: cinny sets lotusDenoiseSource=1 when ML denoise is selected (the old build-time getUserMedia
shim is removed). This is the single change with the widest blast radius — test deliberately.

  • Audio flows, no silence with ML denoise on (baseline, also §D line 204).
  • Reconnect (the A7 fix): in a call with ML denoise on, kill network ~10 s (devtools → Offline)
    so EC shows "Connection lost / Reconnect", then restore. Mic still works AND still denoised
    afterward, without End+rejoin. (This is the exact bug that was reintroduced then fixed; if it
    regresses, mic dies on every reconnect.)
  • Mic device switch mid-call (Settings → change microphone): audio keeps working (same
    restart() path as reconnect).
  • Mute → unmute a few times: audio returns each time.
  • Each model if the picker offers them: rnnoise (default), speex, dtln, deepfilternet
    each loads + denoises, no silence. (All four are in-source now; DTLN runs at 16 kHz, others 48 kHz.)
  • No double-processing: audio isn't over-suppressed/artifacted (would mean the old shim is still
    injected alongside the in-source engine).
  • Rollback if bad for everyone: revert the cinny deploy commit (restores the shim + @element-hq parity).

D2-2. Speaking + mute indicators from widget events (#2)

Flag: lotusCallState=1. cinny now reads speaker/mute state from io.lotus.call_state events instead of
scraping EC's DOM (DOM fallback retained). Overlaps G1.

  • Speaking glow lights the correct person when they talk (you, then your friend).
  • PiP "All muted" / "You muted" badge points at the right person and updates on mute/unmute.

D2-3. Focus camera during a screenshare (#4 / A5)

Action: cinny sends io.lotus.focus_participant (the DOM .click() hack is gone). Overlaps A5 / G2.

  • Person A screenshares; Person B camera on; MemberGlance → Focus camera on B → B's camera is
    spotlighted alongside/over the shared screen (not ignored).
  • Camera-off target = graceful (no error, no kick out of the screenshare).

D2-4. In-call avatar decorations (#6) — NEW, beyond A6

Action: cinny pushes io.lotus.decorations. A6 only covered the lobby roster and called in-call EC
tiles out of scope — that's now in scope.

  • A participant with a Profile decoration joins camera off → the decoration ring renders on
    their in-call video-tile avatar (inside EC, not just the lobby), correctly sized/positioned.
  • Decoration tracks the right person across grid/spotlight layout changes; disappears when they leave.

D2-5. Native transparent background (#5)

Flag: lotusTransparent=1 (native, replacing the injected background:none !important).

  • Call background looks right — host wallpaper/surface shows through; no black box, bad
    see-through, or layout breakage (also covered loosely by §D2 "looks right").

D2-7. In-Call Soundboard (#3 / P5-15) — 👥 2 people — NEW

Flag: lotusAudioInject=1. A 🔔 Soundboard button now sits in the call controls bar (left group,
next to the chat button). Clips are user-uploadable and sync across your devices like emoji packs.
Prereq: Settings → General → Calls → Soundboard must be ON (default on).

  • Upload: open the soundboard popout → Upload → pick a short audio file (mp3/ogg/wav, ≤ 1 MB).
    It appears as a clip tile. (Too-big / too-many shows an error, doesn't crash.)
  • Plays into the call: with a second person in the call, click a clip. They hear it, and
    you hear it locally too. good if both hear it; tell us if only one side does.
  • Sync: the uploaded clip shows up on your other device/session (account-data sync).
  • Delete: the ✕ on a tile removes it (everywhere, after sync).
  • Off switch: turn Settings → Calls → Soundboard off → the call-bar button disappears.
  • Injecting a clip does not mute/interrupt your mic or anyone else's audio.

D2-8. Call Quality Controls (#7 / P5-31) — 👥 2 people — NEW

Action: io.lotus.set_quality. User settings in Settings → General → Calls (Microphone Bitrate,
Screenshare Bitrate, Screenshare Framerate; all default Auto). Admin caps in Room Settings →
General → Voice → Call Quality Caps
.

  • No regression at Auto: with everything on Auto, calls/screenshare work exactly as before.
  • User cap takes effect: set Microphone Bitrate to 32 kbps, rejoin/continue a call — audio
    still flows (thinner is fine). Set Screenshare Framerate to 15 fps and share your screen — it
    still shares. tell us if any setting kills audio/screenshare.
  • Applies mid-call: changing a setting during a call takes effect without End+rejoin.
  • Room-admin cap (admin needed): as a room admin, set Max Microphone Bitrate = 64 kbps in
    Room Settings → Voice. A member whose user setting is higher (e.g. 256) should be clamped to 64
    (best-effort/UX — this is client-side; hard server enforcement is a separate follow-up).
  • Resetting a setting back to Auto removes the cap for the rest of the call.

Soundboard + quality are no longer "dormant" — if either does nothing, grab the EC iframe console
and check for io.lotus.inject_audio / io.lotus.set_quality rejections.

D2-9. Call Permissions — HARD server-side, cross-client (👥 2 people, admin) — NEW

This is enforced by the voice-limit-guard on the server (re-signs the LiveKit JWT), so it applies to
every client, not just Lotus Chat. Set in Room Settings → General → Voice → Call Permissions.
(Requires the guard deployed on LXC 151 — auto-deploys on a matrix repo push.)

  • Disable screenshare: as admin, turn Allow Screen Sharing off. In a call, the
    screenshare button disappears in Lotus Chat. good if no one can screenshare.
  • Cross-client (the important one): have someone join the same room from stock Element / Element
    X
    and try to screenshare → the server refuses the track (it won't publish). This proves it's
    not just our client hiding a button.
  • Audio-only room: turn Allow Camera off too → the camera button disappears and cameras are
    server-blocked for all clients; microphones still work.
  • Live kill (mid-call): while someone is actively screensharing, an admin turns Allow
    Screen Sharing
    off. Within a few seconds their screenshare should stop for everyone on its own
    (no rejoin needed) — this is the server reconcile loop revoking it live. Works even if the sharer is
    on stock Element. good if the share drops within ~3–5 s; tell us if it keeps going.
  • Turning it back on restores the ability to screenshare/camera (start a new share).
  • No policy = no change: a room with Call Permissions left on defaults behaves exactly as before.

If any D2 item fails, grab the EC iframe console (right-click the call → inspect the iframe) — a
widget-action/payload mismatch shows up there as a io.lotus.* rejection or a MissingKey/transport log.


_Migrated from `LOTUS_TESTING.md` §D2 on 2026-09-17 — the file is now reference-only._ **How to report:** tick each item as it passes; on FAIL comment with what you saw vs expected, browser/OS, web (chat.lotusguild.org) vs desktop (Tauri), theme, and any browser-console errors. Screenshots for anything visual. **Checklist** - [ ] D2-1. Denoise **in-source** — survives reconnect (fixes A7) ⭐ highest risk (everyone's mic) - [ ] D2-2. Speaking + mute indicators from widget **events** (#2) - [ ] D2-3. Focus camera **during a screenshare** (#4 / A5) - [ ] D2-4. In-call avatar decorations (#6) — **NEW, beyond A6** - [ ] D2-5. Native transparent background (#5) - [ ] D2-7. In-Call Soundboard (#3 / P5-15) — 👥 2 people — **NEW** - [ ] D2-8. Call Quality Controls (#7 / P5-31) — 👥 2 people — **NEW** - [ ] D2-9. Call Permissions — HARD server-side, cross-client (👥 2 people, admin) — **NEW** --- > The whole EC iframe is now our **self-built fork** (`@lotusguild/element-call-embedded@0.20.1-lotus.1`). > Five features are **active** (the host sets their flags / sends their actions); two ship **dormant**. > **Confirm you're on the fork first:** EC iframe console prints `Element Call embedded-v0.20.1-lotus.1` > (the old build prints `embedded-v0.20.1`). If it says the old version, the web deploy hasn't landed — > the fork features won't be present, so don't test D2 yet. > For non-dev testers, each item below also states the plain "✅ good if / ❌ tell us if" outcome. ### D2-1. Denoise **in-source** — survives reconnect (fixes A7) ⭐ highest risk (everyone's mic) Flag: cinny sets `lotusDenoiseSource=1` when ML denoise is selected (the old build-time getUserMedia shim is **removed**). This is the single change with the widest blast radius — test deliberately. - [ ] **Audio flows, no silence** with ML denoise on (baseline, also §D line 204). - [ ] **Reconnect (the A7 fix):** in a call with ML denoise on, kill network ~10 s (devtools → Offline) so EC shows "Connection lost / Reconnect", then restore. **Mic still works AND still denoised** afterward, **without** End+rejoin. _(This is the exact bug that was reintroduced then fixed; if it regresses, mic dies on every reconnect.)_ - [ ] **Mic device switch mid-call** (Settings → change microphone): audio keeps working (same `restart()` path as reconnect). - [ ] **Mute → unmute** a few times: audio returns each time. - [ ] **Each model** if the picker offers them: `rnnoise` (default), `speex`, `dtln`, `deepfilternet` — each loads + denoises, no silence. (All four are in-source now; DTLN runs at 16 kHz, others 48 kHz.) - [ ] **No double-processing:** audio isn't over-suppressed/artifacted (would mean the old shim is still injected alongside the in-source engine). - **Rollback if bad for everyone:** revert the cinny deploy commit (restores the shim + `@element-hq` parity). ### D2-2. Speaking + mute indicators from widget **events** (#2) Flag: `lotusCallState=1`. cinny now reads speaker/mute state from `io.lotus.call_state` events instead of scraping EC's DOM (DOM fallback retained). Overlaps **G1**. - [ ] **Speaking glow** lights the **correct** person when they talk (you, then your friend). - [ ] **PiP "All muted" / "You muted" badge** points at the right person and updates on mute/unmute. ### D2-3. Focus camera **during a screenshare** (#4 / A5) Action: cinny sends `io.lotus.focus_participant` (the DOM `.click()` hack is gone). Overlaps **A5 / G2**. - [ ] Person A screenshares; Person B camera on; **MemberGlance → Focus camera** on B → B's camera is spotlighted **alongside/over** the shared screen (not ignored). - [ ] Camera-**off** target = graceful (no error, no kick out of the screenshare). ### D2-4. In-call avatar decorations (#6) — **NEW, beyond A6** Action: cinny pushes `io.lotus.decorations`. **A6 only covered the lobby roster** and called in-call EC tiles out of scope — that's now in scope. - [ ] A participant with a **Profile decoration** joins **camera off** → the decoration ring renders on their **in-call video-tile avatar** (inside EC, not just the lobby), correctly sized/positioned. - [ ] Decoration tracks the right person across grid/spotlight layout changes; disappears when they leave. ### D2-5. Native transparent background (#5) Flag: `lotusTransparent=1` (native, replacing the injected `background:none !important`). - [ ] Call background looks right — host wallpaper/surface shows through; **no** black box, bad see-through, or layout breakage (also covered loosely by §D2 "looks right"). ### D2-7. In-Call Soundboard (#3 / P5-15) — 👥 2 people — **NEW** Flag: `lotusAudioInject=1`. A 🔔 **Soundboard** button now sits in the call controls bar (left group, next to the chat button). Clips are user-uploadable and sync across your devices like emoji packs. _Prereq:_ Settings → General → Calls → **Soundboard** must be ON (default on). - [ ] **Upload:** open the soundboard popout → **Upload** → pick a short audio file (mp3/ogg/wav, ≤ 1 MB). It appears as a clip tile. (Too-big / too-many shows an error, doesn't crash.) - [ ] **Plays into the call:** with a second person in the call, click a clip. **They hear it**, and **you hear it locally** too. ✅ good if both hear it; ❌ tell us if only one side does. - [ ] **Sync:** the uploaded clip shows up on your **other device**/session (account-data sync). - [ ] **Delete:** the ✕ on a tile removes it (everywhere, after sync). - [ ] **Off switch:** turn Settings → Calls → **Soundboard** off → the call-bar button disappears. - [ ] Injecting a clip does **not** mute/interrupt your mic or anyone else's audio. ### D2-8. Call Quality Controls (#7 / P5-31) — 👥 2 people — **NEW** Action: `io.lotus.set_quality`. User settings in **Settings → General → Calls** (Microphone Bitrate, Screenshare Bitrate, Screenshare Framerate; all default **Auto**). Admin caps in **Room Settings → General → Voice → Call Quality Caps**. - [ ] **No regression at Auto:** with everything on **Auto**, calls/screenshare work exactly as before. - [ ] **User cap takes effect:** set Microphone Bitrate to **32 kbps**, rejoin/continue a call — audio still flows (thinner is fine). Set Screenshare Framerate to **15 fps** and share your screen — it still shares. ❌ tell us if any setting kills audio/screenshare. - [ ] **Applies mid-call:** changing a setting **during** a call takes effect without End+rejoin. - [ ] **Room-admin cap (admin needed):** as a room admin, set **Max Microphone Bitrate = 64 kbps** in Room Settings → Voice. A member whose user setting is higher (e.g. 256) should be **clamped to 64** (best-effort/UX — this is client-side; hard server enforcement is a separate follow-up). - [ ] Resetting a setting back to **Auto** removes the cap for the rest of the call. > Soundboard + quality are no longer "dormant" — if either does nothing, grab the **EC iframe console** > and check for `io.lotus.inject_audio` / `io.lotus.set_quality` rejections. ### D2-9. Call Permissions — HARD server-side, cross-client (👥 2 people, admin) — **NEW** This is enforced by the `voice-limit-guard` on the server (re-signs the LiveKit JWT), so it applies to **every** client, not just Lotus Chat. Set in **Room Settings → General → Voice → Call Permissions**. _(Requires the guard deployed on LXC 151 — auto-deploys on a `matrix` repo push.)_ - [ ] **Disable screenshare:** as admin, turn **Allow Screen Sharing** off. In a call, the **screenshare button disappears** in Lotus Chat. ✅ good if no one can screenshare. - [ ] **Cross-client (the important one):** have someone join the **same room from stock Element / Element X** and try to screenshare → the server **refuses** the track (it won't publish). This proves it's not just our client hiding a button. - [ ] **Audio-only room:** turn **Allow Camera** off too → the camera button disappears and cameras are server-blocked for all clients; **microphones still work**. - [ ] **⭐ Live kill (mid-call):** while someone is **actively screensharing**, an admin turns **Allow Screen Sharing** off. Within a few seconds their screenshare should **stop for everyone** on its own (no rejoin needed) — this is the server reconcile loop revoking it live. Works even if the sharer is on stock Element. ✅ good if the share drops within ~3–5 s; ❌ tell us if it keeps going. - [ ] **Turning it back on** restores the ability to screenshare/camera (start a new share). - [ ] **No policy = no change:** a room with Call Permissions left on defaults behaves exactly as before. > If any D2 item fails, grab the **EC iframe console** (right-click the call → inspect the iframe) — a > widget-action/payload mismatch shows up there as a `io.lotus.*` rejection or a `MissingKey`/transport log. ---
jared added this to the Manual QA backlog milestone 2026-09-17 23:24:06 -04:00
jared added the securityarea: callsqa labels 2026-09-17 23:24:06 -04:00
Author
Owner

Fork feature sweep on the local LiveKit stack (two real participants)

item result
D2-1 denoise in-source survives reconnect reconnect (offline 10 s) keeps the call and the fork's state; whether the audio is denoised cannot be judged headless
D2-2 speaking + mute from widget events io.lotus.call_state drives both; remote mute state correct both ways
D2-5 transparent background renders on the host surface (screenshots on #173)
D2-8 quality controls io.lotus.set_quality is sent once on join with the user's bitrate setting ✓ (effect on the encoder not measurable here)
D2-9 call permissions — server side Allow Screen Sharing off → bob's screenshare button gone ✓; Allow Camera off → camera button gone ✓; live kill did NOT work: found + fixed in matrix@ee5f78b — livekit-server's JSON uses can_publish_sources (snake_case) and the guard read canPublishSources, so it decided nobody publishes and never revoked. Now the share track is gone from the SFU 2.0 s after the policy flip. Follow-up #223: the sharer's own button stays lit ~30 s.
Voice limit max_users: 1 → second user sees "Channel Full (1/1)", Join disabled ✓, and a direct token request to the guard is refused 403 This voice channel is full ✓ — enforced for every client
D2-3 focus camera, D2-4 in-call decorations, D2-7 soundboard not exercised (need camera tiles / a decoration set / an uploaded clip — doable next)

Leaving open for D2-3/4/7.

## Fork feature sweep on the local LiveKit stack (two real participants) | item | result | |---|---| | D2-1 denoise in-source survives reconnect | reconnect (offline 10 s) keeps the call and the fork's state; *whether the audio is denoised* cannot be judged headless | | D2-2 speaking + mute from widget events | ✓ `io.lotus.call_state` drives both; remote mute state correct both ways | | D2-5 transparent background | renders on the host surface (screenshots on #173) | | D2-8 quality controls | `io.lotus.set_quality` is sent once on join with the user's bitrate setting ✓ (effect on the encoder not measurable here) | | **D2-9 call permissions — server side** | Allow Screen Sharing off → bob's screenshare button gone ✓; Allow Camera off → camera button gone ✓; **live kill did NOT work: found + fixed in `matrix@ee5f78b`** — livekit-server's JSON uses `can_publish_sources` (snake_case) and the guard read `canPublishSources`, so it decided nobody publishes and never revoked. Now the share track is gone from the SFU **2.0 s** after the policy flip. Follow-up #223: the sharer's own button stays lit ~30 s. | | **Voice limit** | `max_users: 1` → second user sees "Channel Full (1/1)", Join disabled ✓, and a direct token request to the guard is refused `403 This voice channel is full` ✓ — enforced for every client | | D2-3 focus camera, D2-4 in-call decorations, D2-7 soundboard | not exercised (need camera tiles / a decoration set / an uploaded clip — doable next) | Leaving open for D2-3/4/7.
Author
Owner

Verified end-to-end on the local calls stack (fork 0.25.0-lotus.4; two headless clients; widget messages recorded on both sides of the iframe, SFU state from ListParticipants).

Item Result Evidence
D2-1 Denoise in-source (automatable part) DeepFilterNet loads (df_bg.wasm, DeepFilterNet3_onnx.tar.gz, noiseGateWorklet.js), audio reaches the other side, speaking=true; after killing and restarting livekit-server mid-call both republish within ~2 s and audio flows again. Whether it sounds right is the human item below
D2-2 Speaking + mute from widget events io.lotus.call_state carries {id:"@u:hs:DEVICE:0", speaking, audioEnabled, videoEnabled} per participant; flips on mute/unmute and camera on/off, status bar shows "alice and bob are speaking…"
D2-3 Focus camera during a screenshare bob → member glance → "Focus camera" while alice shares: io.lotus.focus_participant {userId, id} sent, alice's camera spotlighted; menu flips to "Unfocus camera" and sends {userId:null} share
D2-4 In-call avatar decorations bob sets io.lotus.avatar_decoration=joystick (MSC4133); host pushes io.lotus.decorations {"@bob:localhost": …/joystick.png} to both ECs; the joystick renders on bob's tile focus
D2-5 Native transparent background EC body background-color: rgba(0,0,0,0), classes lotus-transparent lotus-theme, host #lotus-ec-styles injected, theme= param follows the app theme
D2-7 In-call soundboard personal pack from account data; "Play Tone" → io.lotus.inject_audio {url: blob:…, volume: 0.8} → SFU shows an extra AUDIO/UNKNOWN track on alice for the clip's duration; one-at-a-time guard disables the button; with the mic muted the host refuses with "Unmute your microphone to play a soundboard clip."
D2-8 Quality controls client side default payload {audioMaxBitrate:null,…}; setting io.lotus.room_quality {audio_max_kbps:32, screenshare_max_kbps:1000, screenshare_max_fps:15} mid-call re-sends io.lotus.set_quality {32000, 1000000, 15}; clearing it sends nulls again. Server-side enforcement remains the known open item
D2-9 Server-enforced call permissions verified in the earlier batch (buttons hidden + live kill via the guard, after the snake_case fix, matrix repo ee5f78b)

Also confirmed: join/leave chimes (now debounced, #145), camera-off default on join, "N Live" pill, call status bar, member glance, in-call chat (message lands in the room), second-call guard ("Already in another call — End the current call to join!"), call-button scoping (DM , private room without space parent , public room ✗, room inside a space ✗), DM ring dialog → Reject → caller sees "Declined your call".

Found & fixed while here: screenshare-audio mute undone by a re-share (fork lotus.4), SecurityError burst when EC fails to load (70620d4b). Found & filed: speaking indicator stuck after an SFU restart (element-call), see-through PiP (cinny).

Closing; by-ear items → the new human-QA issue.

Verified end-to-end on the local calls stack (fork `0.25.0-lotus.4`; two headless clients; widget messages recorded on both sides of the iframe, SFU state from `ListParticipants`). | Item | Result | Evidence | |---|---|---| | D2-1 Denoise in-source | ✅ (automatable part) | DeepFilterNet loads (`df_bg.wasm`, `DeepFilterNet3_onnx.tar.gz`, `noiseGateWorklet.js`), audio reaches the other side, `speaking=true`; after killing and restarting `livekit-server` mid-call both republish within ~2 s and audio flows again. Whether it *sounds* right is the human item below | | D2-2 Speaking + mute from widget events | ✅ | `io.lotus.call_state` carries `{id:"@u:hs:DEVICE:0", speaking, audioEnabled, videoEnabled}` per participant; flips on mute/unmute and camera on/off, status bar shows "alice and bob are speaking…" | | D2-3 Focus camera during a screenshare | ✅ | bob → member glance → "Focus camera" while alice shares: `io.lotus.focus_participant {userId, id}` sent, alice's camera spotlighted; menu flips to "Unfocus camera" and sends `{userId:null}` ![share](https://code.lotusguild.org/attachments/99bdb4fc-980d-41e5-b99d-65cdb9fed3fd) | | D2-4 In-call avatar decorations | ✅ | bob sets `io.lotus.avatar_decoration=joystick` (MSC4133); host pushes `io.lotus.decorations {"@bob:localhost": …/joystick.png}` to both ECs; the joystick renders on bob's tile ![focus](https://code.lotusguild.org/attachments/17e928f4-d9fe-4895-bafc-5cf5b586f1ee) | | D2-5 Native transparent background | ✅ | EC body `background-color: rgba(0,0,0,0)`, classes `lotus-transparent lotus-theme`, host `#lotus-ec-styles` injected, `theme=` param follows the app theme | | D2-7 In-call soundboard | ✅ | personal pack from account data; "Play Tone" → `io.lotus.inject_audio {url: blob:…, volume: 0.8}` → SFU shows an extra `AUDIO/UNKNOWN` track on alice for the clip's duration; one-at-a-time guard disables the button; with the mic muted the host refuses with "Unmute your microphone to play a soundboard clip." | | D2-8 Quality controls | ✅ client side | default payload `{audioMaxBitrate:null,…}`; setting `io.lotus.room_quality {audio_max_kbps:32, screenshare_max_kbps:1000, screenshare_max_fps:15}` mid-call re-sends `io.lotus.set_quality {32000, 1000000, 15}`; clearing it sends nulls again. Server-side enforcement remains the known open item | | D2-9 Server-enforced call permissions | ✅ | verified in the earlier batch (buttons hidden + live kill via the guard, after the snake_case fix, matrix repo `ee5f78b`) | Also confirmed: join/leave chimes (now debounced, #145), camera-off default on join, "N Live" pill, call status bar, member glance, in-call chat (message lands in the room), second-call guard ("Already in another call — End the current call to join!"), call-button scoping (DM ✅, private room without space parent ✅, public room ✗, room inside a space ✗), DM ring dialog → Reject → caller sees "Declined your call". Found & fixed while here: screenshare-audio mute undone by a re-share (fork `lotus.4`), SecurityError burst when EC fails to load (`70620d4b`). Found & filed: speaking indicator stuck after an SFU restart (element-call), see-through PiP (cinny). Closing; by-ear items → the new human-QA issue.
jared closed this issue 2026-09-19 00:36:11 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#174