feat(lotus): keep your own tile on mobile with the camera off (cinny #265)
CI / Build embedded bundle (pull_request) Successful in 1m57s
CI / Publish to Gitea npm registry (pull_request) Skipped

Upstream drops the local PiP in the one-on-one mobile layout whenever the
local camera is off. In Lotus voice rooms that is nearly always, so on a
phone you never saw yourself or your own speaking ring. Show it as the
desktop layout does; the tile falls back to your avatar. Tests updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
Lotus CI
2026-10-19 14:40:25 -04:00
co-authored by Claude Opus 5.5
parent 5693e81bb9
commit fbf8e44276
2 changed files with 23 additions and 24 deletions
+9 -13
View File
@@ -451,14 +451,16 @@ describe.each(modes)("CallViewModel (%s mode)", (mode) => {
});
});
test("one-on-one mobile layout shows local tile when video is enabled", () => {
// [lotus / cinny #265] The local tile stays on mobile with the camera off
// (upstream hid it), so you can see yourself in a voice call.
test("one-on-one mobile layout shows the local tile whether or not video is enabled", () => {
withTestScheduler(({ behavior, schedule, expectObservable }) => {
// Local participant enables their video, then disables it
const videoInputMarbles = " ny--n";
// While tile is shown, tap the screen twice
// Tap the screen twice
const tapScreenInputMarbles = "--aa-";
// Layout should show local tile, make it small, enlarge it again, then hide it
const expectedLayoutMarbles = "abcba";
// The local tile is shown throughout; taps make it small, then large again
const expectedLayoutMarbles = "b-cb";
withCallViewModel(
{
@@ -477,12 +479,6 @@ describe.each(modes)("CallViewModel (%s mode)", (mode) => {
expectObservable(summarizeLayout$(vm.layout$)).toBe(
expectedLayoutMarbles,
{
a: {
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pipSize: "lg",
},
b: {
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
@@ -518,7 +514,7 @@ describe.each(modes)("CallViewModel (%s mode)", (mode) => {
a: {
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pip: `${localId}:0`,
pipSize: "lg",
},
});
@@ -566,7 +562,7 @@ describe.each(modes)("CallViewModel (%s mode)", (mode) => {
a: {
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pip: `${localId}:0`,
pipSize: "sm",
},
b: {
@@ -765,7 +761,7 @@ describe.each(modes)("CallViewModel (%s mode)", (mode) => {
// This is the expected one-on-one layout for a narrow window
type: "one-on-one-mobile",
spotlight: [`${aliceId}:0`],
pip: undefined,
pip: `${localId}:0`,
pipSize: "lg",
},
b: {
+14 -11
View File
@@ -1220,19 +1220,22 @@ export function createCallViewModel$(
}),
);
// [lotus / cinny #265] Upstream drops the local PiP on mobile while your
// camera is off; in Lotus voice rooms that is almost always, so on a phone
// you never saw yourself (or your own speaking ring). Show it as desktop
// does: the tile falls back to your avatar.
const oneOnOneMobileLayoutMedia$: Observable<OneOnOneMobileLayoutMedia | null> =
oneOnOneLayoutMedia$.pipe(
switchMap((media) => {
if (media === null) return of(null);
return media.local.videoEnabled$.pipe(
map((videoEnabled) => ({
type: "one-on-one-mobile" as const,
edgeToEdge: true as const,
spotlight: media.remote,
pip: videoEnabled ? media.local : undefined,
})),
);
}),
map((media) =>
media === null
? null
: {
type: "one-on-one-mobile" as const,
edgeToEdge: true as const,
spotlight: media.remote,
pip: media.local,
},
),
);
const pipLayoutMedia$: Observable<LayoutMedia> = spotlight$.pipe(