Commit Graph
8 Commits
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 bb675fa50e feat(lotus): in-frame "Share your screen?" prompt replaces the corner button (cinny #43)
CI / Publish to Gitea npm registry (pull_request) Skipped
CI / Build embedded bundle (pull_request) Successful in 3m25s
Where the host can't delegate the user's click (Firefox, Safari, WebKitGTK),
lotus.20 showed EC's own screenshare button floating in the frame's corner,
apart from the host's call bar. Now the host keeps its bar button and sends
the new toWidget io.lotus.prompt_screenshare; the fork shows a small
"Share your screen?" card inside the frame, just above the bar, and its
Share button starts the share with the frame's own click. Same two clicks as
the host's confirm on Chromium.

- The card focuses Share; Cancel and Escape close it; it closes if a share
  starts some other way or the room's call policy stops allowing sharing,
  and never opens where the policy forbids it (set_frame_screenshare).
- Picture-in-picture (~158 px tall): compact card without the description.
- controls_state: frameScreenshare is now always false (the corner button is
  gone), screensharePrompt says the host should use the prompt, and
  screensharePromptOpen lets the host stop covering the frame meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-27 12:48:12 -04:00
Lotus CIandClaude Opus 5.5 0f93368a7e feat(lotus): in-frame screenshare button where the host can't delegate (cinny #43)
Firefox, Safari and WebKitGTK have no Capability Delegation, so the host
can't start a share from its call bar: getDisplayMedia needs the click in
this frame. Until now the host clicked EC's hidden footer button through
the DOM, which only works while the frame is same-origin.

With lotusHostControls + lotusFrameScreenshare the fork now shows EC's own
screenshare button (floating bottom-right, footer still hidden), so the
click is native. controls_state reports frameScreenshare so the host hides
its own button. New toWidget io.lotus.set_frame_screenshare { visible }
lets the host hide it while the room's call policy forbids screensharing
(never while a share is live, so it can still be stopped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 21:48:22 -04:00
Lotus CIandClaude Opus 5.5 e0eecc5271 feat(lotus): report host call hotkeys from inside the frame (cinny #43)
The host used to add PTT/deafen key listeners to this frame's window,
which needs same-origin access. Now it sends `io.lotus.set_hotkeys
{ codes }` and the fork reports those keys as `io.lotus.hotkey`
(keydown/keyup with modifiers + whether the target is a text field or a
button), plus window focus changes so a held PTT is released. The
default action is cancelled here with the host's rules (not while typing,
not on a focused button). `controls_state` carries `hotkeys: true`.

Fixes a live bug: EC's own shortcuts (M = toggle mic, Space = PTT) also
fired for the host's keys, so with focus in the call and the default
deafen key M, the first press turned the MIC ON instead of deafening
(even in push-to-talk mode). EC's shortcuts now ignore keys the host owns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 13:59:34 -04:00
Lotus CIandClaude Opus 5.5 6fa34911ef feat(lotus): io.lotus.set_screenshare for the host call bar (cinny #43)
toWidget `io.lotus.set_screenshare { on?: boolean }` (omit to toggle)
starts/stops sharing via the view model instead of the host clicking EC's
hidden button. The host sends it with Capability Delegation
(`delegate: "display-capture"`) where supported, so getDisplayMedia keeps
the user's click on engines that require it. `controls_state` now carries
`screenshareAction: true` so the host only uses it on a fork that has it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 12:49:58 -04:00
Lotus CIandClaude Opus 5.5 cee6bab9a6 feat(lotus): report the local mic level to the host (cinny #146)
The host had only a boolean `speaking` for yourself, throttled to 500 ms, so
nothing on the call bar showed that the mic was actually picking you up.

- lotusMicLevel.ts: one shared local mic sampler (a clone of the published
  track, ~10 Hz RMS, while a mic is published, muted or not), shared per
  call view model so there is one AudioContext.
- MicLevelQuantizer: 0–3 bars (≈ −46/−36/−26 dBFS), rising at once and
  falling one bar per sample so it doesn't flicker between words.
- fromWidget io.lotus.mic_level { bars } only when the value changes; 0 while
  muted or with no mic. Opt-in with the host state stream (lotusCallState=1).
- "Talking while muted" (#37) now reads the same sampler instead of running
  its own; same gate and behaviour.

Verified in a local call with a fake-tone mic: the host receives 0–3 as the
tone rises/falls (about 4 messages/s while it changes), 0 on mute and levels
again on unmute; muted + tone still raises speakingWhileMuted and the host's
"You're muted" notice. Lotus/room suites pass on Node 22 (148 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-25 12:08:57 -04:00
Lotus CIandClaude Opus 5.5 6beebd3ea7 feat(lotus): layout, settings and reactions over the widget API (cinny #43)
The Lotus host drove these by clicking EC's hidden controls in our DOM and
read screenshare/layout state from it, which only works while the frame is
same-origin and breaks whenever a data-testid changes. New actions:

- toWidget io.lotus.set_layout { layout: "grid" | "spotlight" } → the layout
  switch view model's setLayout.
- toWidget io.lotus.open_settings { open?: boolean } → setSettingsOpen
  (toggle when `open` is omitted).
- toWidget io.lotus.toggle_reactions → a window event the reactions button
  listens for.
- fromWidget io.lotus.controls_state { screensharing, layout } on change; its
  arrival tells the host the fork supports the actions above.

Screensharing stays host-driven for now: getDisplayMedia needs the user's
click to reach this frame (Capability Delegation), which a widget message
doesn't carry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-24 22:16:24 -04:00
Lotus CIandClaude Opus 5 4a48ec98d0 feat(lotus): io.lotus.set_audio_output — host-driven output device switch (#119)
The embed hides Element Call's footer, so its own output picker is out of
reach; the host's call bar now sends io.lotus.set_audio_output { deviceId }
and the fork applies it via mediaDevices.audioOutput.select(), answering
with io.lotus.audio_output_state { deviceId } on every change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-20 15:31:33 -04:00
Lotus CIandClaude Opus 5 501e3fb5ac fix(lotus): reply immediately to lotus actions with no mounted handler; new fromWidget actions
Lotus toWidget actions with no handler used to sit in the LazyEventEmitter
backlog forever (host timed out; stale replay on remount). They now get
an immediate {} reply. Adds RequestState and DenoiseState to the enum
(fromWidget) with a test pinning the toWidget/fromWidget split.

Fixes #18

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-13 01:22:20 -04:00