From 8645a5576952c1f326abc69883cb51bdb71e342c Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Tue, 20 Oct 2026 19:45:11 -0400 Subject: [PATCH 1/2] feat(call): Details card for the call-ended toast (#253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The call-ended toast gets a Details button (and stays 10 s) that opens a card with who was in the call and the peak, connection verdict and reconnects, how long your / each person's / everyone's connection was poor (likely the server when everyone), RTT, packet loss and jitter, and your talk, mute, deafen and screenshare time, in technical wording. Deafened time is tracked by CallEmbed; the rest comes from the fork's new io.lotus.call_summary `details` (lotus.29). Settings → Calls gets "Summary After Calls" (on by default). Fixes #253 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- .../call-summary/CallSummaryDialog.tsx | 84 ++++++++++++ src/app/features/settings/general/General.tsx | 8 ++ .../features/toast/LotusToastContainer.tsx | 32 ++++- src/app/hooks/useCallEndedToast.ts | 20 ++- src/app/pages/App.tsx | 2 + src/app/plugins/call/CallEmbed.ts | 28 +++- src/app/state/callSummary.ts | 6 + src/app/state/settings.ts | 3 + src/app/state/toast.ts | 2 + src/app/utils/callSummary.test.ts | 101 ++++++++++++++- src/app/utils/callSummary.ts | 120 ++++++++++++++++++ 11 files changed, 397 insertions(+), 9 deletions(-) create mode 100644 src/app/features/call-summary/CallSummaryDialog.tsx create mode 100644 src/app/state/callSummary.ts diff --git a/src/app/features/call-summary/CallSummaryDialog.tsx b/src/app/features/call-summary/CallSummaryDialog.tsx new file mode 100644 index 000000000..05835f7dc --- /dev/null +++ b/src/app/features/call-summary/CallSummaryDialog.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import FocusTrap from 'focus-trap-react'; +import { + Box, + config, + Dialog, + Header, + Icon, + IconButton, + Icons, + Overlay, + OverlayBackdrop, + OverlayCenter, + Text, +} from 'folds'; +import { useAtom } from 'jotai'; +import { callSummaryViewAtom } from '../../state/callSummary'; +import { useModalStyle } from '../../hooks/useModalStyle'; +import { stopPropagation } from '../../utils/keyboard'; + +/** + * [Gitea #253] The post-call "Details" card: opened from the call-ended toast, + * shows who was in the call, connection quality and whose side it was on, + * network numbers and your own activity (technical wording). + */ +export function CallSummaryDialog() { + const [view, setView] = useAtom(callSummaryViewAtom); + const modalStyle = useModalStyle(400); + if (!view) return null; + const close = () => setView(undefined); + return ( + }> + + + +
+ + + + Call summary · {view.roomName} + + + + + +
+ + {view.sections.map((section) => ( + + {section.title} + {section.lines.map((line) => ( + + {line} + + ))} + + ))} + +
+
+
+
+ ); +} diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 3b30475b0..d1c9be22d 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -1749,6 +1749,7 @@ function Calls() { ); const [pttMode, setPttMode] = useSetting(settingsAtom, 'pttMode'); + const [callSummaryToast, setCallSummaryToast] = useSetting(settingsAtom, 'callSummaryToast'); const [callRejoin, setCallRejoin] = useSetting(settingsAtom, 'callRejoinAfterRestart'); const [haptics, setHaptics] = useSetting(settingsAtom, 'hapticFeedback'); const [pttKey, setPttKey] = useSetting(settingsAtom, 'pttKey'); @@ -2062,6 +2063,13 @@ function Calls() { /> } /> + + } + /> {hapticsSupported() && ( { if (toast.sticky) return; - timerRef.current = setTimeout(() => { - dismiss(toast.id); - }, 4000); + timerRef.current = setTimeout( + () => { + dismiss(toast.id); + }, + // [Gitea #253] A toast with a button stays long enough to use it. + toast.actionLabel ? 10_000 : 4000, + ); return () => { if (timerRef.current !== null) clearTimeout(timerRef.current); }; - }, [dismiss, toast.id, toast.sticky]); + }, [dismiss, toast.id, toast.sticky, toast.actionLabel]); const handleCardClick = () => { if (toast.onClick) { @@ -212,6 +216,24 @@ function ToastCard({ toast }: ToastCardProps) {
{toast.body}
{toast.roomName &&
{toast.roomName}
} + {toast.actionLabel && toast.onAction && ( +
+ +
+ )} ); } diff --git a/src/app/hooks/useCallEndedToast.ts b/src/app/hooks/useCallEndedToast.ts index 62f1b710e..5973ecfd9 100644 --- a/src/app/hooks/useCallEndedToast.ts +++ b/src/app/hooks/useCallEndedToast.ts @@ -3,7 +3,10 @@ import { useSetAtom } from 'jotai'; import { CallEmbed } from '../plugins/call'; import { useCallHangupEvent } from './useCallEmbed'; import { toastQueueAtom } from '../state/toast'; -import { describeCallEnd } from '../utils/callSummary'; +import { describeCallDetails, describeCallEnd } from '../utils/callSummary'; +import { callSummaryViewAtom } from '../state/callSummary'; +import { useSetting } from '../state/hooks/settings'; +import { settingsAtom } from '../state/settings'; // The fork sends io.lotus.call_summary on SFU disconnect, which normally lands // before Element Call's hangup echo; give a late one this long before toasting. @@ -16,6 +19,8 @@ const SUMMARY_GRACE_MS = 400; */ export function useCallEndedToast(embed: CallEmbed): void { const setToast = useSetAtom(toastQueueAtom); + const setSummaryView = useSetAtom(callSummaryViewAtom); + const [callSummaryToast] = useSetting(settingsAtom, 'callSummaryToast'); const fired = useRef(false); useEffect(() => { @@ -25,14 +30,25 @@ export function useCallEndedToast(embed: CallEmbed): void { useCallHangupEvent(embed, () => { if (fired.current || !embed.joined || embed.joinedAt === undefined) return; fired.current = true; + if (!callSummaryToast) return; const durationMs = Date.now() - embed.joinedAt; + const deafenedMs = embed.deafenedMs(); + const roomName = embed.room.name ?? 'Voice call'; + const nameOf = (userId: string) => embed.room.getMember(userId)?.name ?? userId; const toast = () => setToast({ id: `call-ended-${Date.now()}`, displayName: 'Lotus Chat', body: describeCallEnd(durationMs, embed.lastSummary), - roomName: embed.room.name ?? 'Voice call', + roomName, roomId: embed.roomId, + // [Gitea #253] The full breakdown, snapshotted at hangup. + actionLabel: 'Details', + onAction: () => + setSummaryView({ + roomName, + sections: describeCallDetails(embed.lastSummary, durationMs, deafenedMs, nameOf), + }), }); if (embed.lastSummary) toast(); else setTimeout(toast, SUMMARY_GRACE_MS); diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx index 9c9488996..c005066b3 100644 --- a/src/app/pages/App.tsx +++ b/src/app/pages/App.tsx @@ -25,6 +25,7 @@ import { useCompositionEndTracking } from '../hooks/useComposingCheck'; import { settingsAtom } from '../state/settings'; import { isWithinTimeWindow } from '../utils/timeWindow'; import { LotusToastContainer } from '../features/toast/LotusToastContainer'; +import { CallSummaryDialog } from '../features/call-summary/CallSummaryDialog'; import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge'; import { useSessionAppeared } from '../hooks/useSessionAppeared'; import { useTauriWindowChrome } from '../hooks/useTauriWindowChrome'; @@ -248,6 +249,7 @@ function App() { + diff --git a/src/app/plugins/call/CallEmbed.ts b/src/app/plugins/call/CallEmbed.ts index 0ca525df0..7eb023032 100644 --- a/src/app/plugins/call/CallEmbed.ts +++ b/src/app/plugins/call/CallEmbed.ts @@ -26,13 +26,14 @@ import { ElementMediaStateDetail, ElementWidgetActions, } from './types'; -import { CallControl } from './CallControl'; +import { CallControl, CallControlEvent } from './CallControl'; import { CallControlState } from './CallControlState'; import { verifyDenoiseAssets } from './denoiseSmokeCheck'; import { canDelegateCapability } from './utils'; import { getCallPageUrl } from './callPageUrl'; import { restrictWidgetMessages } from '../widgetTransport'; import { isRingableRoom } from '../../utils/callRing'; +import { parseCallSummaryDetails, type LotusCallSummaryDetails } from '../../utils/callSummary'; // Maximum time to wait for the embedded Element Call iframe to progress from // initial load to a ready/joined state. If it hasn't by then, we assume the @@ -59,6 +60,8 @@ export interface LotusCallSummary { reconnects: number; poorMs: number; verdict: 'good' | 'fair' | 'poor' | 'unknown'; + /** [Gitea #253] Breakdown for the Details card (fork lotus.29+). */ + details?: LotusCallSummaryDetails; } export class CallEmbed { @@ -78,6 +81,25 @@ export class CallEmbed { /** [Gitea #143] The fork's end-of-call summary, once it arrives. */ public lastSummary: LotusCallSummary | undefined; + // [Gitea #253] Time spent deafened (sound off), for the post-call Details. + private deafenedSince: number | undefined; + + private deafenedTotal = 0; + + public deafenedMs(now = Date.now()): number { + return this.deafenedTotal + (this.deafenedSince === undefined ? 0 : now - this.deafenedSince); + } + + private trackDeafened = (): void => { + if (!this.joined) return; + const deaf = !this.control.sound; + if (deaf && this.deafenedSince === undefined) this.deafenedSince = Date.now(); + if (!deaf && this.deafenedSince !== undefined) { + this.deafenedTotal += Date.now() - this.deafenedSince; + this.deafenedSince = undefined; + } + }; + // C-M4: set once dispose() has run so the hangup fallback timer can tell // whether the embed was already torn down by the normal Close/Hangup echo. public disposed = false; @@ -339,6 +361,8 @@ export class CallEmbed { const controlState = initialControlState ?? new CallControlState(true, false, true); this.control = new CallControl(controlState, call, iframe); this.initialState = controlState; + this.control.on(CallControlEvent.StateUpdate, this.trackDeafened); + this.disposables.push(() => this.control.off(CallControlEvent.StateUpdate, this.trackDeafened)); // If the iframe document itself fails to load, fail fast. iframe.onerror = () => { this.settleLoad('iframe'); @@ -434,6 +458,7 @@ export class CallEmbed { reconnects: data.reconnects ?? 0, poorMs: data.poorMs ?? 0, verdict: data.verdict ?? 'unknown', + details: parseCallSummaryDetails((data as { details?: unknown }).details), }; } }), @@ -623,6 +648,7 @@ export class CallEmbed { } this.joined = true; this.joinedAt = Date.now(); + this.trackDeafened(); // EC ignores io.element.device_mute before join; re-apply desired state now that EC is live this.control.forceState(this.initialState); } diff --git a/src/app/state/callSummary.ts b/src/app/state/callSummary.ts new file mode 100644 index 000000000..c7be36a7f --- /dev/null +++ b/src/app/state/callSummary.ts @@ -0,0 +1,6 @@ +import { atom } from 'jotai'; +import type { CallSummarySection } from '../utils/callSummary'; + +/** [Gitea #253] The post-call Details card currently open (undefined = closed). */ +export type CallSummaryView = { roomName: string; sections: CallSummarySection[] }; +export const callSummaryViewAtom = atom(undefined); diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts index 575be7fae..6f6631140 100644 --- a/src/app/state/settings.ts +++ b/src/app/state/settings.ts @@ -310,6 +310,8 @@ export interface Settings { screenshareFramerate: ScreenshareFramerate; // [P5-15] In-call soundboard soundboardEnabled: boolean; + // [Gitea #253] The call-ended toast (with its Details card). + callSummaryToast: boolean; soundboardVolume: number; // 0–100 seasonalThemeOverride: SeasonalThemeOverride; @@ -429,6 +431,7 @@ const defaultSettings: Settings = { screenshareBitrate: 'auto', screenshareFramerate: 'auto', soundboardEnabled: true, + callSummaryToast: true, soundboardVolume: 80, seasonalThemeOverride: 'auto', diff --git a/src/app/state/toast.ts b/src/app/state/toast.ts index 4a228d03c..2d8bb2f62 100644 --- a/src/app/state/toast.ts +++ b/src/app/state/toast.ts @@ -13,6 +13,8 @@ export type ToastNotif = { onClick?: () => void; // custom click handler; skips hash navigation when set sticky?: boolean; // when true, does not auto-dismiss — use for action toasts that require a click onDismiss?: () => void; // called when the user closes the toast with its X (not on click/eviction) + actionLabel?: string; // [Gitea #253] a small button on the toast, e.g. "Details" + onAction?: () => void; createdAt?: number; // set by toastQueueAtom when enqueued; used for #80 eviction ordering }; diff --git a/src/app/utils/callSummary.test.ts b/src/app/utils/callSummary.test.ts index c01783c96..33652a47a 100644 --- a/src/app/utils/callSummary.test.ts +++ b/src/app/utils/callSummary.test.ts @@ -1,6 +1,11 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import { describeCallDuration, describeCallEnd } from './callSummary'; +import { + describeCallDuration, + describeCallEnd, + describeCallDetails, + parseCallSummaryDetails, +} from './callSummary'; describe('describeCallDuration', () => { it('picks the unit', () => { @@ -65,3 +70,97 @@ describe('describeCallEnd', () => { ); }); }); + +it('[#253] parseCallSummaryDetails keeps well-formed fields and drops the rest', () => { + const d = parseCallSummaryDetails({ + participants: ['@bob:x', 7, '@carol:x'], + peakParticipants: 3, + quality: { + youPoorMs: 1000, + othersPoorMs: { '@bob:x': 500, evil: 9, '@c:x': -1 }, + everyonePoorMs: 0, + }, + network: { rttMedianMs: 38, rttP95Ms: 'fast', inboundLossPct: 0.5 }, + activity: { speakingMs: 60_000, mutedMs: 'x', screenshareMs: 0 }, + }); + assert.deepEqual(d, { + participants: ['@bob:x', '@carol:x'], + peakParticipants: 3, + quality: { youPoorMs: 1000, othersPoorMs: { '@bob:x': 500 }, everyonePoorMs: 0 }, + network: { rttMedianMs: 38, inboundLossPct: 0.5 }, + activity: { speakingMs: 60_000, mutedMs: 0, screenshareMs: 0 }, + }); + assert.equal(parseCallSummaryDetails(undefined), undefined); + assert.equal(parseCallSummaryDetails('details'), undefined); +}); + +it('[#253] describeCallDetails: technical sections with names, blame and numbers', () => { + const names: Record = { '@bob:x': 'Bob', '@carol:x': 'Carol' }; + const sections = describeCallDetails( + { + durationMs: 2_460_000, + reconnects: 2, + poorMs: 120_000, + verdict: 'fair', + details: { + participants: ['@bob:x', '@carol:x'], + peakParticipants: 3, + quality: { + youPoorMs: 120_000, + othersPoorMs: { '@carol:x': 30_000 }, + everyonePoorMs: 10_000, + }, + network: { + rttMedianMs: 38, + rttP95Ms: 310, + outboundLossPct: 4.1, + inboundLossPct: 0.5, + jitterMs: 6, + }, + activity: { speakingMs: 600_000, mutedMs: 1_200_000, screenshareMs: 300_000 }, + }, + }, + 2_460_000, + 90_000, + (u) => names[u] ?? u, + ); + assert.deepEqual(sections, [ + { title: 'Call', lines: ['Duration 41 min · peak 3 in the call', 'With Bob, Carol'] }, + { + title: 'Connection', + lines: [ + 'Verdict: fair · 2 reconnects', + 'Your connection poor/lost: 2 min', + 'Carol poor/lost: 30 s', + 'Everyone poor at once: 10 s (likely server or SFU side)', + ], + }, + { + title: 'Network', + lines: [ + 'RTT median 38 ms · p95 310 ms', + 'Outbound packet loss 4.1%', + 'Inbound packet loss 0.5%', + 'Jitter 6 ms', + ], + }, + { + title: 'You', + lines: ['Talking 10 min · muted 20 min · deafened 2 min', 'Screensharing 5 min'], + }, + ]); +}); + +it('[#253] describeCallDetails without fork details (older fork) still says something useful', () => { + const sections = describeCallDetails( + { durationMs: 60_000, reconnects: 0, poorMs: 0, verdict: 'good' }, + 60_000, + 0, + (u) => u, + ); + assert.deepEqual( + sections.map((s) => s.title), + ['Call', 'Connection'], + ); + assert.deepEqual(sections[0].lines, ['Duration 1 min', 'Participants unknown']); +}); diff --git a/src/app/utils/callSummary.ts b/src/app/utils/callSummary.ts index f6dd47367..c6c17834d 100644 --- a/src/app/utils/callSummary.ts +++ b/src/app/utils/callSummary.ts @@ -32,3 +32,123 @@ export function describeCallEnd(durationMs: number, summary?: LotusCallSummary): } return parts.join(' · '); } + +/** [Gitea #253] The fork's `details` in io.lotus.call_summary (lotus.29+). */ +export type LotusCallSummaryDetails = { + participants: string[]; + peakParticipants: number; + quality: { youPoorMs: number; othersPoorMs: Record; everyonePoorMs: number }; + network: { + rttMedianMs?: number; + rttP95Ms?: number; + outboundLossPct?: number; + inboundLossPct?: number; + jitterMs?: number; + }; + activity: { speakingMs: number; mutedMs: number; screenshareMs: number }; +}; + +const num = (v: unknown): number | undefined => + typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined; +const obj = (v: unknown): Record => + typeof v === 'object' && v !== null && !Array.isArray(v) ? (v as Record) : {}; + +/** Defensive: the frame is another origin; take only well-formed fields. */ +export function parseCallSummaryDetails(raw: unknown): LotusCallSummaryDetails | undefined { + if (typeof raw !== 'object' || raw === null) return undefined; + const r = obj(raw); + const quality = obj(r.quality); + const network = obj(r.network); + const activity = obj(r.activity); + const othersPoorMs: Record = {}; + Object.entries(obj(quality.othersPoorMs)).forEach(([user, ms]) => { + const n = num(ms); + if (n !== undefined && user.startsWith('@')) othersPoorMs[user] = n; + }); + const net: LotusCallSummaryDetails['network'] = {}; + (['rttMedianMs', 'rttP95Ms', 'outboundLossPct', 'inboundLossPct', 'jitterMs'] as const).forEach( + (k) => { + const n = num(network[k]); + if (n !== undefined) net[k] = n; + }, + ); + return { + participants: Array.isArray(r.participants) + ? r.participants.filter((p): p is string => typeof p === 'string').slice(0, 200) + : [], + peakParticipants: num(r.peakParticipants) ?? 1, + quality: { + youPoorMs: num(quality.youPoorMs) ?? 0, + othersPoorMs, + everyonePoorMs: num(quality.everyonePoorMs) ?? 0, + }, + network: net, + activity: { + speakingMs: num(activity.speakingMs) ?? 0, + mutedMs: num(activity.mutedMs) ?? 0, + screenshareMs: num(activity.screenshareMs) ?? 0, + }, + }; +} + +export type CallSummarySection = { title: string; lines: string[] }; + +/** + * [Gitea #253] The Details card, in technical wording (the owner's choice): + * who was there, connection quality and whose side it was on, network + * numbers, and your own activity. `nameOf` turns a user id into a display name. + */ +export function describeCallDetails( + summary: LotusCallSummary | undefined, + durationMs: number, + deafenedMs: number, + nameOf: (userId: string) => string, +): CallSummarySection[] { + const sections: CallSummarySection[] = []; + const d = summary?.details; + const dur = describeCallDuration; + const people = d?.participants ?? []; + sections.push({ + title: 'Call', + lines: [ + `Duration ${dur(durationMs)}${d ? ` · peak ${d.peakParticipants} in the call` : ''}`, + people.length + ? `With ${people.map(nameOf).join(', ')}` + : d + ? 'Nobody else joined' + : 'Participants unknown', + ], + }); + if (summary) { + const quality: string[] = [ + `Verdict: ${summary.verdict} · ${summary.reconnects} reconnect${summary.reconnects === 1 ? '' : 's'}`, + ]; + if (d) { + quality.push(`Your connection poor/lost: ${dur(d.quality.youPoorMs)}`); + Object.entries(d.quality.othersPoorMs) + .sort((a, b) => b[1] - a[1]) + .forEach(([user, ms]) => quality.push(`${nameOf(user)} poor/lost: ${dur(ms)}`)); + if (d.quality.everyonePoorMs > 0) + quality.push( + `Everyone poor at once: ${dur(d.quality.everyonePoorMs)} (likely server or SFU side)`, + ); + } else quality.push(`Your connection poor/lost: ${dur(summary.poorMs)}`); + sections.push({ title: 'Connection', lines: quality }); + } + if (d) { + const n = d.network; + const net: string[] = []; + if (n.rttMedianMs !== undefined) + net.push(`RTT median ${n.rttMedianMs} ms · p95 ${n.rttP95Ms ?? n.rttMedianMs} ms`); + if (n.outboundLossPct !== undefined) net.push(`Outbound packet loss ${n.outboundLossPct}%`); + if (n.inboundLossPct !== undefined) net.push(`Inbound packet loss ${n.inboundLossPct}%`); + if (n.jitterMs !== undefined) net.push(`Jitter ${n.jitterMs} ms`); + if (net.length) sections.push({ title: 'Network', lines: net }); + const act = [ + `Talking ${dur(d.activity.speakingMs)} · muted ${dur(d.activity.mutedMs)} · deafened ${dur(deafenedMs)}`, + ]; + if (d.activity.screenshareMs > 0) act.push(`Screensharing ${dur(d.activity.screenshareMs)}`); + sections.push({ title: 'You', lines: act }); + } + return sections; +} From ac1a1c9a48e21806ed4192690fddfe0865bc0b42 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Tue, 20 Oct 2026 20:33:13 -0400 Subject: [PATCH 2/2] chore(deps): pin @lotusguild/element-call-embedded 0.25.0-lotus.29 (#253) lotus.29 adds the call summary `details` (element-call#60). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index a79b834bd..1e00fac47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,7 @@ }, "devDependencies": { "@axe-core/playwright": "4.13.0", - "@lotusguild/element-call-embedded": "0.25.0-lotus.28", + "@lotusguild/element-call-embedded": "0.25.0-lotus.29", "@playwright/test": "1.63.0", "@rollup/plugin-inject": "5.0.5", "@rollup/plugin-wasm": "6.2.2", @@ -2695,9 +2695,9 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, "node_modules/@lotusguild/element-call-embedded": { - "version": "0.25.0-lotus.28", - "resolved": "https://code.lotusguild.org/api/packages/LotusGuild/npm/%40lotusguild%2Felement-call-embedded/-/0.25.0-lotus.28/element-call-embedded-0.25.0-lotus.28.tgz", - "integrity": "sha512-IFBHHSVw0CzrrLgx3oIIcJmBB4YMb8at4oVuSKXifZ7MmyHydxg9U3sTlZGgLUjMVrrAXj4gFzCksGHet4LSQw==", + "version": "0.25.0-lotus.29", + "resolved": "https://code.lotusguild.org/api/packages/LotusGuild/npm/%40lotusguild%2Felement-call-embedded/-/0.25.0-lotus.29/element-call-embedded-0.25.0-lotus.29.tgz", + "integrity": "sha512-IAIEyiOFfkfb1k2JfBNOkX4LOVaGF8LzeKVQYCqKoyKDVao6rYEYA71LON8ntTaxkredXOK4CeLFsnkCiJHuGg==", "dev": true }, "node_modules/@matrix-org/matrix-sdk-crypto-wasm": { diff --git a/package.json b/package.json index cecbbaeec..472baf480 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ }, "devDependencies": { "@axe-core/playwright": "4.13.0", - "@lotusguild/element-call-embedded": "0.25.0-lotus.28", + "@lotusguild/element-call-embedded": "0.25.0-lotus.29", "@playwright/test": "1.63.0", "@rollup/plugin-inject": "5.0.5", "@rollup/plugin-wasm": "6.2.2",