Compare commits

...
Author SHA1 Message Date
Lotus CIandClaude Opus 5.5 c4f6193888 chore(lotus): 0.25.0-lotus.18
CI / Build embedded bundle (push) Successful in 3m9s
CI / Publish to Gitea npm registry (push) Successful in 1m17s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 13:39:18 -04:00
Lotus CIandClaude Opus 5.5 7855a0c22f feat(lotus): hide the footer + root color-scheme natively (cinny #43)
- `lotusHostControls=1`: body.lotus-host-controls hides EC's footer
  (position absolute + visibility hidden, still in the DOM so a host on an
  engine without Capability Delegation can click its screenshare button).
- With lotusTheme/lotusTransparent, `:root` color-scheme follows the
  theme, so the transparent frame gets no opaque backdrop.
Together these replace the CSS the host injected into this frame.

Also fixes unreadable tile name tags in the light theme: the Lotus
near-black canvas override now applies to dark themes only (it sat behind
light-theme dark text).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 13:39:18 -04:00
Lotus CIandClaude Opus 5.5 aeb72c22b9 chore(lotus): 0.25.0-lotus.17
CI / Build embedded bundle (push) Successful in 2m45s
CI / Publish to Gitea npm registry (push) Successful in 1m17s
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 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
7 changed files with 87 additions and 8 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@lotusguild/element-call-embedded",
"version": "0.25.0-lotus.16",
"version": "0.25.0-lotus.18",
"files": [
"README.md",
"LICENSE-AGPL-3.0",
+14 -1
View File
@@ -90,16 +90,29 @@ body.lotus-transparent[data-background="gradient"]::before {
display: none !important;
}
/* [cinny #43] lotusHostControls=1: the host renders its own call bar, so EC's
footer is hidden but stays in the DOM and in layout-independent position. */
body.lotus-host-controls [data-testid="footer-container"] {
position: absolute !important;
visibility: hidden !important;
}
/* [lotus] Native Lotus/TDS theme, applied when lotusTheme=1, instead of the
host injecting CSS into the iframe after load. Overrides Compound design tokens
with Lotus values at the source so theming is complete and flash-free. Extend
this block with the full Lotus token map from the design system; the canvas
override below is a safe starting point that matches the Lotus dark surface. */
body.lotus-theme {
--cpd-color-bg-canvas-default: #0c0d10;
--video-tile-background: var(--cpd-color-bg-subtle-secondary);
}
/* Dark themes only: in the light theme this near-black canvas sat behind
light-theme (dark) text, e.g. the tile name tags were unreadable. */
body.lotus-theme.cpd-theme-dark,
body.lotus-theme.cpd-theme-dark-hc {
--cpd-color-bg-canvas-default: #0c0d10;
}
/* [lotus #21] Subtle contrast guard for elements that sit directly on the
transparent canvas (no opaque tile background behind them) when
lotusTransparent is set: the host's real wallpaper is unknown to us, so a
+1
View File
@@ -30,6 +30,7 @@ describe("LotusWidgetActions", () => {
LotusWidgetActions.SetLayout,
LotusWidgetActions.OpenSettings,
LotusWidgetActions.ToggleReactions,
LotusWidgetActions.SetScreenshare,
];
expect(new Set(LOTUS_TO_WIDGET_ACTIONS)).toEqual(new Set(expectedToWidget));
+10 -1
View File
@@ -75,7 +75,15 @@ export enum LotusWidgetActions {
/** toWidget: toggle the reactions / raise-hand menu (cinny #43). */
ToggleReactions = "io.lotus.toggle_reactions",
/**
* fromWidget: `{ screensharing: boolean, layout: "grid" | "spotlight" | null }`
* toWidget: 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.
*/
SetScreenshare = "io.lotus.set_screenshare",
/**
* fromWidget: `{ screensharing: boolean, layout: "grid" | "spotlight" | null,
* screenshareAction: true }`
* 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.
*/
@@ -98,4 +106,5 @@ export const LOTUS_TO_WIDGET_ACTIONS: LotusWidgetActions[] = [
LotusWidgetActions.SetLayout,
LotusWidgetActions.OpenSettings,
LotusWidgetActions.ToggleReactions,
LotusWidgetActions.SetScreenshare,
];
+19 -1
View File
@@ -7,7 +7,11 @@ Please see LICENSE in the repository root for full details.
import { describe, expect, it } from "vitest";
import { parseLayoutPayload, parseSettingsPayload } from "./lotusControls";
import {
parseLayoutPayload,
parseSettingsPayload,
shouldToggleScreenshare,
} from "./lotusControls";
describe("parseLayoutPayload", () => {
it("accepts grid and spotlight", () => {
@@ -33,3 +37,17 @@ describe("parseSettingsPayload", () => {
expect(parseSettingsPayload({ open: "yes" }, true)).toBe(false);
});
});
describe("shouldToggleScreenshare", () => {
it("toggles only when an explicit on differs from the current state", () => {
expect(shouldToggleScreenshare({ on: true }, false)).toBe(true);
expect(shouldToggleScreenshare({ on: true }, true)).toBe(false);
expect(shouldToggleScreenshare({ on: false }, true)).toBe(true);
expect(shouldToggleScreenshare({ on: false }, false)).toBe(false);
});
it("toggles when on is missing or not a boolean", () => {
expect(shouldToggleScreenshare({}, false)).toBe(true);
expect(shouldToggleScreenshare({ on: "yes" }, true)).toBe(true);
expect(shouldToggleScreenshare(null, true)).toBe(true);
});
});
+31 -3
View File
@@ -21,6 +21,8 @@ export interface LotusControlsState {
screensharing: boolean;
/** Null while EC offers no layout switch (e.g. PiP or a 1:1 layout). */
layout: LayoutMode | null;
/** This fork handles `io.lotus.set_screenshare` (always true). */
screenshareAction: true;
}
/** `{ layout }` payload → a layout mode, or undefined if invalid. Exported for tests. */
@@ -39,13 +41,28 @@ export function parseSettingsPayload(data: unknown, current: boolean): boolean {
return !current;
}
/**
* `{ on? }` payload + current sharing state → whether to call the toggle.
* Exported for tests.
*/
export function shouldToggleScreenshare(
data: unknown,
sharing: boolean,
): boolean {
if (typeof data === "object" && data !== null && "on" in data) {
const { on } = data as { on?: unknown };
if (typeof on === "boolean") return on !== sharing;
}
return true;
}
/**
* [cinny #43] Widget-API replacements for the host's DOM access to EC's
* controls: layout switch, settings modal and reactions menu, plus a
* `controls_state` report (screensharing + layout) so the host no longer reads
* EC's DOM for them. Screensharing itself stays host-DOM driven for now:
* `getDisplayMedia` needs the user's click to reach this frame (Capability
* Delegation), which a plain widget message doesn't carry.
* EC's DOM for them. Screenshare start/stop too: `getDisplayMedia` needs the
* user's click in this frame, so the host sends `set_screenshare` with
* Capability Delegation (Chromium). Elsewhere it keeps clicking EC's button.
*
* No effect unless the host sends the actions; registering is safe whenever
* we're a widget. Returns a teardown function.
@@ -69,10 +86,19 @@ export function startLotusControls(vm: CallViewModel): () => void {
w.api.transport.reply(ev.detail, {});
window.dispatchEvent(new Event(LOTUS_TOGGLE_REACTIONS_EVENT));
};
const onSetScreenshare = (ev: CustomEvent<IWidgetApiRequest>): void => {
w.api.transport.reply(ev.detail, {});
// Call synchronously: the delegated activation is only good for a few
// seconds after the message arrived.
if (shouldToggleScreenshare(ev.detail.data, vm.sharingScreen$.value)) {
vm.toggleScreenSharing?.();
}
};
w.lazyActions.on(LotusWidgetActions.SetLayout, onSetLayout);
w.lazyActions.on(LotusWidgetActions.OpenSettings, onOpenSettings);
w.lazyActions.on(LotusWidgetActions.ToggleReactions, onToggleReactions);
w.lazyActions.on(LotusWidgetActions.SetScreenshare, onSetScreenshare);
const sub: Subscription = combineLatest([
vm.sharingScreen$,
@@ -85,6 +111,7 @@ export function startLotusControls(vm: CallViewModel): () => void {
([screensharing, layout]): LotusControlsState => ({
screensharing,
layout,
screenshareAction: true,
}),
),
distinctUntilChanged(
@@ -100,5 +127,6 @@ export function startLotusControls(vm: CallViewModel): () => void {
w.lazyActions.off(LotusWidgetActions.SetLayout, onSetLayout);
w.lazyActions.off(LotusWidgetActions.OpenSettings, onOpenSettings);
w.lazyActions.off(LotusWidgetActions.ToggleReactions, onToggleReactions);
w.lazyActions.off(LotusWidgetActions.SetScreenshare, onSetScreenshare);
};
}
+11 -1
View File
@@ -76,7 +76,17 @@ export const useTheme = (): void => {
"lotusTheme anyway since the two flags are only meaningful together.",
);
}
if (lotusTheme || lotusTransparent)
if (lotusTheme || lotusTransparent) {
document.body.classList.add("lotus-theme");
// [cinny #43] The frame's root colour scheme must match the host's, or
// the browser paints an opaque backdrop behind the transparent frame.
// (The host used to inject `:root { color-scheme }` for this.)
document.documentElement.style.colorScheme = theme;
}
// [cinny #43] The host draws its own call bar: hide EC's footer, but keep
// it in the DOM (the host still clicks its screenshare button on engines
// without Capability Delegation). Replaces the host's injected styles.
if (lotusFlag("lotusHostControls"))
document.body.classList.add("lotus-host-controls");
}, [previousTheme, requestedTheme]);
};