From fbf8e442769b2b1c1c4e5113f8dd71b33bfb65c3 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Mon, 19 Oct 2026 14:40:25 -0400 Subject: [PATCH] feat(lotus): keep your own tile on mobile with the camera off (cinny #265) 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 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- src/state/CallViewModel/CallViewModel.test.ts | 22 +++++++--------- src/state/CallViewModel/CallViewModel.ts | 25 +++++++++++-------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/state/CallViewModel/CallViewModel.test.ts b/src/state/CallViewModel/CallViewModel.test.ts index 18154917..58da1ee6 100644 --- a/src/state/CallViewModel/CallViewModel.test.ts +++ b/src/state/CallViewModel/CallViewModel.test.ts @@ -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: { diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index b852c627..45808b51 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -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 = 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 = spotlight$.pipe( -- 2.47.3