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
This commit is contained in:
Lotus CI
2026-09-24 22:16:24 -04:00
co-authored by Claude Opus 5.5
parent d8880daca5
commit 6beebd3ea7
6 changed files with 180 additions and 0 deletions
+10
View File
@@ -26,6 +26,7 @@ import { logger } from "matrix-js-sdk/lib/logger";
import classNames from "classnames";
import { useReactionsSender } from "../reactions/useReactionsSender";
import { LOTUS_TOGGLE_REACTIONS_EVENT } from "../lotus/lotusControls";
import styles from "./ReactionToggleButton.module.css";
import {
type RaisedHandInfo,
@@ -196,6 +197,15 @@ export function ReactionToggleButton({
setErrorText(undefined);
}, [showReactionsMenu]);
// [cinny #43] The Lotus host's call bar toggles this menu over the widget
// API (lotusControls.ts) instead of clicking this button in our DOM.
useEffect(() => {
const toggle = (): void => setShowReactionsMenu((open) => !open);
window.addEventListener(LOTUS_TOGGLE_REACTIONS_EVENT, toggle);
return (): void =>
window.removeEventListener(LOTUS_TOGGLE_REACTIONS_EVENT, toggle);
}, []);
const sendRelation = useCallback(
async (reaction: ReactionOption) => {
try {