lotus(#6): render decoration in MediaView; fix store lifecycle
Review found in-call tiles use MediaView->Avatar, not TileAvatar, so the decoration never rendered in-call (CRITICAL). Move the overlay into MediaView, gated on the avatar's own visibility (!(video && videoEnabled)) so it never floats over live video; revert the TileAvatar changes. Also ref-count the io.lotus.decorations registration (one shared handler, no double-reply) and stop clearing the map on teardown so a transient remount doesn't drop decorations (HIGH/MED). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
70358d442b
commit
33d0e98eb0
@@ -47,34 +47,51 @@ function safeImageUrl(raw: unknown): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
// Ref-counted single registration: the decoration roster is app-wide (keyed by
|
||||
// userId), so multiple tile/InCallView mounts must share ONE handler — otherwise
|
||||
// each would reply to the same widget request (double-reply) and a transient
|
||||
// remount would tear it down.
|
||||
let registrations = 0;
|
||||
let unregister: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* Register the `io.lotus.decorations` handler. No effect unless the host sends
|
||||
* the action. Returns a teardown function.
|
||||
* Register the `io.lotus.decorations` handler (ref-counted). No effect unless
|
||||
* the host sends the action. Returns a teardown function.
|
||||
*/
|
||||
export function startLotusDecorations(): () => void {
|
||||
const w = widget;
|
||||
if (!w) return () => undefined;
|
||||
|
||||
const handler = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
void w.api.transport.reply(ev.detail, {});
|
||||
const data = ev.detail.data as
|
||||
| { decorations?: Record<string, unknown> }
|
||||
| undefined;
|
||||
const next: Record<string, string> = {};
|
||||
if (data?.decorations && typeof data.decorations === "object") {
|
||||
for (const [userId, url] of Object.entries(data.decorations)) {
|
||||
const safe = safeImageUrl(url);
|
||||
if (safe) next[userId] = safe;
|
||||
if (registrations === 0) {
|
||||
const handler = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
void w.api.transport.reply(ev.detail, {});
|
||||
const data = ev.detail.data as
|
||||
| { decorations?: Record<string, unknown> }
|
||||
| undefined;
|
||||
const next: Record<string, string> = {};
|
||||
if (data?.decorations && typeof data.decorations === "object") {
|
||||
for (const [userId, url] of Object.entries(data.decorations)) {
|
||||
const safe = safeImageUrl(url);
|
||||
if (safe) next[userId] = safe;
|
||||
}
|
||||
}
|
||||
}
|
||||
decorations = next;
|
||||
emit();
|
||||
};
|
||||
decorations = next;
|
||||
emit();
|
||||
};
|
||||
w.lazyActions.on(LotusWidgetActions.Decorations, handler);
|
||||
unregister = () =>
|
||||
w.lazyActions.off(LotusWidgetActions.Decorations, handler);
|
||||
}
|
||||
registrations += 1;
|
||||
|
||||
w.lazyActions.on(LotusWidgetActions.Decorations, handler);
|
||||
return () => {
|
||||
w.lazyActions.off(LotusWidgetActions.Decorations, handler);
|
||||
decorations = {};
|
||||
emit();
|
||||
registrations -= 1;
|
||||
if (registrations <= 0) {
|
||||
registrations = 0;
|
||||
unregister?.();
|
||||
unregister = null;
|
||||
// Intentionally keep the last decorations map: a transient remount must
|
||||
// not drop decorations until the host next pushes an update.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user