fix(profiles): keep the picker chrome-free
/profiles was unknown to the layout shell, so reaching it from Settings rendered the bottom nav and global header over the picker -- letting someone tab straight past the profile they were being asked to choose, and offering an in-app mini player as a route back into the previous profile's queue. Grouped with /login throughout layoutShell: it is a gate, not a page. OS lockscreen transport controls are unaffected, which is what keeps playing audio controllable while the app is locked.
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user