diff --git a/src/lib/utils/layoutShell.test.ts b/src/lib/utils/layoutShell.test.ts index c226e8fc..8316da85 100644 --- a/src/lib/utils/layoutShell.test.ts +++ b/src/lib/utils/layoutShell.test.ts @@ -202,3 +202,37 @@ describe("showHeaderSearch", () => { expect(showHeaderSearch({ pathname: "/settings" })).toBe(false); }); }); + +/** + * The profile picker is a gate, not a page. It must be chrome-free for the same + * reason /login is: bottom nav over a "who's watching" screen lets someone tab + * straight past the profile they were being asked to choose, and an in-app mini + * player on it would offer a route into the previous profile's queue. OS + * lockscreen transport controls are unaffected — those are what keep playing + * audio controllable while the app is locked (DR-275). + * + * TRACES: UR-082 | DR-276 + */ +describe("profile picker chrome", () => { + const pathname = "/profiles"; + + it("shows no bottom nav, even authenticated", () => { + expect(showBottomNav({ pathname, isAuthenticated: true })).toBe(false); + }); + + it("shows no global mini player", () => { + expect(showGlobalMiniPlayer({ pathname })).toBe(false); + }); + + it("shows no global header", () => { + expect(showGlobalHeader({ pathname, isAuthenticated: true })).toBe(false); + }); + + it("owns its own layout", () => { + expect(routeOwnsLayout({ pathname })).toBe(true); + }); + + it("lets the shell reserve the bottom inset, since nothing else will", () => { + expect(shellReservesBottomInset({ pathname, isAuthenticated: true })).toBe(true); + }); +}); diff --git a/src/lib/utils/layoutShell.ts b/src/lib/utils/layoutShell.ts index 05b95752..2e99be39 100644 --- a/src/lib/utils/layoutShell.ts +++ b/src/lib/utils/layoutShell.ts @@ -28,10 +28,19 @@ export interface BottomUiVisibilityInput { /** * The bottom nav is shown on every authenticated route except the full-screen - * player and the login route. + * player, the login route, and the profile picker. + * + * `/profiles` is grouped with `/login` throughout this module because it is a + * gate rather than a page: a nav bar over "who's watching" lets someone tab + * straight past the choice they were being asked to make. */ export function showBottomNav({ pathname, isAuthenticated }: BottomUiVisibilityInput): boolean { - return isAuthenticated && !pathname.startsWith("/player/") && !pathname.startsWith("/login"); + return ( + isAuthenticated && + !pathname.startsWith("/player/") && + !pathname.startsWith("/login") && + !pathname.startsWith("/profiles") + ); } /** @@ -44,6 +53,7 @@ export function showGlobalMiniPlayer({ pathname }: { pathname: string }): boolea return ( !pathname.startsWith("/player/") && !pathname.startsWith("/login") && + !pathname.startsWith("/profiles") && !pathname.startsWith("/settings") ); } @@ -58,7 +68,8 @@ export function routeOwnsLayout({ pathname }: { pathname: string }): boolean { return ( pathname.startsWith("/library") || pathname.startsWith("/player/") || - pathname.startsWith("/login") + pathname.startsWith("/login") || + pathname.startsWith("/profiles") ); } @@ -69,7 +80,7 @@ export function routeOwnsLayout({ pathname }: { pathname: string }): boolean { * Routes that own their layout (library) render their own AppHeader, so the * root must not double it up. `/settings` owns its content but deliberately has * no account menu (the user is already there). `/player/*` and `/login` are - * immersive/chrome-free. Everything else authenticated (`/`, `/search`, + * immersive/chrome-free, as is `/profiles`. Everything else authenticated (`/`, `/search`, * `/downloads`) gets the header from the root — the whole point of UR-054. * * TRACES: UR-054 | DR-076 @@ -109,7 +120,8 @@ export function showBottomUi(input: BottomUiVisibilityInput): boolean { * Exactly one element may reserve it. BottomUi owns it whenever it renders, * because the padding belongs *inside* its surface box so the colour extends * behind the bar rather than leaving a strip of page background. On routes with - * no bottom UI at all (login, the full-screen player) nothing else would, so + * no bottom UI at all (login, the profile picker, the full-screen player) + * nothing else would, so * the shell takes it. * * TRACES: UR-066 | DR-112