Files
element-call/LOTUS.md
Lotus CIandClaude Opus 5.5 2ef80cef2c
CI / Build embedded bundle (pull_request) Successful in 2m1s
CI / Publish to Gitea npm registry (pull_request) Skipped
docs(lotus): LOTUS.md lists io.lotus.call_error and io.lotus.reconnect (#50)
Regenerate the action table from lotusActions.ts: add the two actions
from #50, and note which cinny PR handles each one still in flight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 00:01:18 -04:00

82 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lotus fork of Element Call
This fork is embedded by [Lotus Chat](https://code.lotusguild.org/LotusGuild/cinny)
(cinny) as the in-app call frame. Everything it adds is opt-in: without the
URL flags and widget actions below it behaves like upstream Element Call.
The source of truth is `src/lotus/` (the `io.lotus.*` actions are declared in
`src/lotus/lotusActions.ts`). Keep this file in step with it: when the host and
the fork drift, features fail silently (element-call#43, cinny#260).
## URL flags
The host sets these on the widget URL (cinny: `CallEmbed.getWidget`).
| Flag | Effect |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `lotusCallState=true` | Stream call state to the host: `io.lotus.call_state` (participants, speaking, mute) and `io.lotus.mic_level`. |
| `lotusTheme=true` | Apply the host theme; the frame's root `color-scheme` follows it. |
| `lotusTransparent=true` | Transparent canvas so the host's wallpaper shows through. Only meaningful with `lotusTheme` (the fork applies it anyway and warns). |
| `lotusHostControls=true` | The host draws the call bar: hide Element Call's footer and enable the host-control actions. |
| `lotusFrameScreenshare=true` | With `lotusHostControls`: show the frame's own screenshare button, for engines without Capability Delegation (Firefox, Safari) where the host can't start a share. |
| `lotusAudioInject=true` | Accept `io.lotus.inject_audio` (soundboard). |
| `lotusDenoiseSource=true` | Run ML noise suppression in-source as a LiveKit audio TrackProcessor. |
| `lotusModel=rnnoise\|speex\|dtln\|deepfilternet` | Denoise model (default `rnnoise`; falls back to `rnnoise` once if the chosen model fails). |
| `lotusGate=true`, `lotusGateThreshold=<dB>` | Noise gate after denoise (default −45 dB). |
| `lotusDenoiseFloor=<0..1>` | Denoise mix floor. |
| `lotusDenoiseBase=<url>` | Where the denoise worklets / wasm / models are served (same-origin by default: `./denoise/`). |
| `sendNotificationType=none` | Upstream param, extended: `none` sends no call-start notification at all (cinny#295; voice channels shouldn't @room-ping). |
## Widget actions (`io.lotus.*`)
"host → call" actions must be allow-listed in `LOTUS_TO_WIDGET_ACTIONS`.
The table is generated from `src/lotus/lotusActions.ts`; the last column was
checked against cinny's `lotus` branch.
| Action | Direction | Payload / meaning | Handled by cinny (`lotus`) |
| -------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| `io.lotus.call_state` | call → host | in-call per-participant speaking / mute state. NOTE: matrix-widget-api `transport.send` is request/response — the host MUST register a handler that replies/acks each one (cinny's `listenAction` does, replying `{}`). If the host has no handler, ClientWidgetApi immediately error-replies "unsupported from-widget action", so the data is silently dropped and each throttled send rejects (caught) — functional miss + log churn, not a hang. | yes |
| `io.lotus.focus_participant` | host → call | pin/spotlight (or clear, with userId=null) a participant. | yes |
| `io.lotus.inject_audio` | host → call | play an audio clip into the call as a separate published track. | yes |
| `io.lotus.set_quality` | host → call | set audio/screenshare encoding quality (bitrate/framerate). | yes |
| `io.lotus.decorations` | host → call | per-user avatar-decoration image URLs for in-call tiles. | yes |
| `io.lotus.set_deafen` | host → call | deafen remote audio (and optionally mute screenshare audio). | yes |
| `io.lotus.request_state` | call → host | sent on (re)registration of a lotus toWidget handler (e.g. decorations) so the host can re-push state that would otherwise only be sent on change (decorations roster, the current focus pin) after an EC-side reconnect/remount. cinny's `resendForkState()` should respond to this the same way it responds to a fresh join. | yes |
| `io.lotus.denoise_state` | call → host | real state of the in-source denoise engine — `{ active: boolean, model: string, error?: string }` — sent once the processor attaches (or after the rnnoise fallback also fails), so the host toggle can reflect reality rather than the requested state. | yes |
| `io.lotus.call_summary` | call → host | one-shot end-of-call readout for the local participant — `{ durationMs, reconnects, poorMs, verdict }` (#143) — sent on SFU disconnect or in-call teardown, whichever comes first. | yes |
| `io.lotus.set_audio_output` | host → call | select the audio output device `{ deviceId }` (#119). | yes |
| `io.lotus.audio_output_state` | call → host | the currently selected output `{ deviceId }` (#119). | cinny #417 |
| `io.lotus.screenshare_notice` | call → host | local screenshare reminder `{ kind: "ended" \| "no-frames" \| "alone" }` — window closed, no frames for 15 s, or 30 min of sharing with nobody else in the call (#39). Each fires at most once per share. | yes |
| `io.lotus.set_layout` | host → call | switch the call layout `{ layout: "grid" \| "spotlight" }` (cinny #43 — replaces the host clicking EC's hidden layout radio). | yes |
| `io.lotus.open_settings` | host → call | open EC's settings modal, or close it with `{ open: false }`; omit `open` to toggle (cinny #43). | yes |
| `io.lotus.toggle_reactions` | host → call | toggle the reactions / raise-hand menu (cinny #43). | yes |
| `io.lotus.set_screenshare` | host → call | start/stop sharing `{ on?: boolean }` (omit to toggle; cinny #43). Starting calls getDisplayMedia, which needs the user's click: the host sends this with Capability Delegation (`delegate: "display-capture"`), and without it the browser rejects the share. | yes |
| `io.lotus.set_hotkeys` | host → call | `{ codes: string[] }` — KeyboardEvent.code values (PTT, deafen) to report while focus is inside this frame (cinny #43). `[]` stops. | yes |
| `io.lotus.set_frame_screenshare` | host → call | `{ visible: boolean }` — whether the in-frame screenshare button (`lotusFrameScreenshare`) may show, e.g. false while the room's call policy forbids screensharing (cinny #43). Defaults to visible. | yes |
| `io.lotus.prompt_screenshare` | host → call | `{}` — show the "Share your screen?" prompt inside this frame (cinny #43). For hosts that can't delegate the user's click: the prompt's Share button is clicked here, so getDisplayMedia gets its activation. | yes |
| `io.lotus.hotkey` | call → host | a watched key went down/up here, or this window's focus changed; see `LotusHotkeyReport` (cinny #43). | yes |
| `io.lotus.controls_state` | call → host | `{ screensharing: boolean, layout: "grid" \| "spotlight" \| null, screenshareAction: true, hotkeys: true, frameScreenshare: false, screensharePrompt: boolean, screensharePromptOpen: boolean }` whenever either changes (cinny #43), so the host stops reading EC's DOM for them. Its arrival also tells the host this fork supports the actions above. | yes |
| `io.lotus.mic_level` | call → host | local mic level `{ bars: 0 \| 1 \| 2 \| 3 }` (cinny #146), sent only when it changes (≤ 10 Hz); 0 while muted or with no mic. | yes |
| `io.lotus.call_error` | call → host | the call's error page is up (`{ code, recoverable }`) or cleared (`{ code: null }`) (cinny #318), so the host stops showing the call as joined after "Connection lost". | cinny #418 |
| `io.lotus.reconnect` | host → call | press the error page's Reconnect / Try again (cinny #318). | cinny #418 |
Entries naming a cinny PR are handled once that PR merges.
## Fork-only behaviour (no flag needed)
Per-person volume memory, screenshare notices (window closed / no frames /
sharing alone), mono mic capture, an SFU refusal reason instead of "Something
went wrong", "Try again" for failed connects, and support for running on its
own origin (cinny#43).
## Releasing
1. Merge the change into `lotus`.
2. Bump `version` in `embedded/web/package.json` (`0.25.0-lotus.N`), commit
`chore(lotus): 0.25.0-lotus.N` on `lotus`, and push a tag `v0.25.0-lotus.N`.
3. CI builds the embedded bundle and, for `v*` tags, publishes
`@lotusguild/element-call-embedded` to the Gitea npm registry
(`https://code.lotusguild.org/api/packages/LotusGuild/npm/`).
4. In cinny, pin the exact version in `package.json` (`npm install --save-exact
@lotusguild/element-call-embedded@0.25.0-lotus.N`) and open a PR.