feat: dark mode fix, call wallpaper, setTheme error handling, Sentry filter
CI / Build & Quality Checks (push) Failing after 6m11s

- CallEmbed: inject :root { color-scheme } into iframe so EC respects Cinny
  theme regardless of OS preference (fixes white background in dark mode)
- CallEmbed: store themeKind, update color-scheme CSS on live setTheme() calls
- CallEmbed: catch transport.send() rejection in setTheme() to prevent
  unhandled promise rejection when widget not ready yet (fixes REACT-8)
- CallEmbed: html + body both set to background:none so wallpaper shows through
- CallEmbedProvider: apply chatBackground wallpaper style to call embed
  container in full-view mode (not PiP) -- wallpapers carry over to calls
- useCallEmbed: pass themeKind through to CallEmbed constructor
- index.tsx: ignoreErrors: [Request timed out] to suppress matrixRTC
  heartbeat timeouts (REACT-9) from Sentry noise
- README: document 0.19.4, positioning fix, dark mode fix, wallpaper,
  millify Rolldown interop fix, Sentry noise filter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lotus Bot
2026-05-23 00:28:37 -04:00
parent 95ac291a61
commit 9ce8ad58b1
5 changed files with 52 additions and 12 deletions
+13
View File
@@ -47,6 +47,10 @@ import { useMediaAuthentication } from '../hooks/useMediaAuthentication';
import { mxcUrlToHttp, getMxIdLocalPart } from '../utils/matrix';
import { RoomAvatar, RoomIcon } from './room-avatar';
import { useRoomNavigate } from '../hooks/useRoomNavigate';
import { getChatBg } from '../features/lotus/chatBackground';
import { useTheme, ThemeKind } from '../hooks/useTheme';
import { useSetting } from '../state/hooks/settings';
import { settingsAtom } from '../state/settings';
import { getStateEvent, getMemberDisplayName } from '../utils/room';
import { StateEvent } from '../../types/matrix/room';
import { getPowersLevelFromMatrixEvent } from '../hooks/usePowerLevels';
@@ -412,6 +416,14 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
const pipMode = callActive && !inCallRoom;
const { navigateRoom } = useRoomNavigate();
const theme = useTheme();
const isDark = theme.kind === ThemeKind.Dark;
const [chatBackground] = useSetting(settingsAtom, 'chatBackground');
const wallpaperStyle = React.useMemo(
() => getChatBg(chatBackground, isDark),
[chatBackground, isDark],
);
const pipDragRef = React.useRef<{
startX: number;
startY: number;
@@ -692,6 +704,7 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
left: 0,
width: '100%',
height: '50%',
...(callVisible && !pipMode ? wallpaperStyle : {}),
}}
ref={callEmbedRef}
>
+1 -1
View File
@@ -65,7 +65,7 @@ export const createCallEmbed = (
const controlState =
pref && new CallControlState(forceAudioOff ? false : pref.microphone, pref.video, pref.sound);
const embed = new CallEmbed(mx, room, widget, container, controlState);
const embed = new CallEmbed(mx, room, widget, container, controlState, themeKind);
return embed;
};
+27 -9
View File
@@ -52,6 +52,8 @@ export class CallEmbed {
private styleRetryObserver?: MutationObserver;
private themeKind: ElementCallThemeKind = 'dark';
// Arrow-function class fields so dispose() passes the exact same reference to mx.off()
private readonly boundOnEvent = (ev: MatrixEvent) => this.onEvent(ev);
@@ -175,6 +177,7 @@ export class CallEmbed {
widget: Widget,
container: HTMLElement,
initialControlState?: CallControlState,
themeKind: ElementCallThemeKind = 'dark',
) {
const iframe = CallEmbed.getIframe(
widget.getCompleteUrl({ currentUserId: mx.getSafeUserId() }),
@@ -189,6 +192,7 @@ export class CallEmbed {
this.room = room;
this.iframe = iframe;
this.container = container;
this.themeKind = themeKind;
const controlState = initialControlState ?? new CallControlState(true, false, true);
this.control = new CallControl(controlState, call, iframe);
@@ -218,9 +222,17 @@ export class CallEmbed {
}
public setTheme(theme: ElementCallThemeKind) {
return this.call.transport.send(WidgetApiToWidgetAction.ThemeChange, {
name: theme,
});
this.themeKind = theme;
const doc = this.document;
if (doc && this.joined) {
const styleEl = doc.getElementById('lotus-ec-styles');
if (styleEl) styleEl.textContent = this.buildStyleContent();
}
return this.call.transport
.send(WidgetApiToWidgetAction.ThemeChange, { name: theme })
.catch(() => {
// Widget transport not ready yet; theme URL param is the fallback
});
}
public hangup() {
@@ -304,22 +316,28 @@ export class CallEmbed {
this.control.forceState(this.initialState);
}
private buildStyleContent(): string {
return [
'html, body { background: none !important; }',
`:root { color-scheme: ${this.themeKind}; }`,
'[style*="height: 0"][style*="z-index: 1"][style*="align-self: center"] { display: none !important; }',
].join('\n');
}
private applyStyles(): void {
const doc = this.document;
if (!doc) return;
doc.body.style.setProperty('background', 'none', 'important');
// Inject CSS for things that can't be reliably caught by DOM timing
if (!doc.getElementById('lotus-ec-styles')) {
const style = doc.createElement('style');
style.id = 'lotus-ec-styles';
style.textContent = [
'body { background: none !important; }',
// Hide "using to Device key transport" status line
'[style*="height: 0"][style*="z-index: 1"][style*="align-self: center"] { display: none !important; }',
].join('\n');
style.textContent = this.buildStyleContent();
(doc.head ?? doc.body).appendChild(style);
} else {
const styleEl = doc.getElementById('lotus-ec-styles');
if (styleEl) styleEl.textContent = this.buildStyleContent();
}
// Hide EC built-in controls (we provide our own)