diff --git a/src/components/layout/__tests__/full-bleed.test.ts b/src/components/layout/__tests__/full-bleed.test.ts new file mode 100644 index 000000000..e7639e53d --- /dev/null +++ b/src/components/layout/__tests__/full-bleed.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; + +import { isFullBleedPage } from "../full-bleed"; + +const open = { outsideSharedRecord: false, moduleOff: false }; + +describe("isFullBleedPage", () => { + it("renders /coach edge to edge", () => { + expect(isFullBleedPage({ pathname: "/coach", ...open })).toBe(true); + }); + + it.each([ + "/coach/plans", + "/coach/conversations", + "/coach/", + "/coaching", + "/", + "/insights", + "/insights/coach", + "/settings/ai", + "/documents", + ])("keeps %s in the padded frame", (pathname) => { + expect(isFullBleedPage({ pathname, ...open })).toBe(false); + }); + + it("keeps the padded frame for a refusal on /coach", () => { + expect( + isFullBleedPage({ + pathname: "/coach", + outsideSharedRecord: true, + moduleOff: false, + }), + ).toBe(false); + expect( + isFullBleedPage({ + pathname: "/coach", + outsideSharedRecord: false, + moduleOff: true, + }), + ).toBe(false); + }); +}); diff --git a/src/components/layout/auth-shell.tsx b/src/components/layout/auth-shell.tsx index f6159f081..213d0a2e1 100644 --- a/src/components/layout/auth-shell.tsx +++ b/src/components/layout/auth-shell.tsx @@ -32,14 +32,12 @@ import { switchedOffModule, } from "@/components/layout/module-page-gate"; import { cn } from "@/lib/utils"; +import { isFullBleedPage } from "./full-bleed"; import { isOnboardingPathname } from "@/lib/onboarding/wizard-steps"; import { RecordScopeHydrationGate } from "./record-scope-hydration-gate"; import { SidebarNav } from "./sidebar-nav"; import { TopBar } from "./top-bar"; -/** The route that renders edge to edge inside the shell. */ -const COACH_FULL_BLEED_PATH = "/coach"; - // v1.4.27 MB6 — `/about` joins the public-path list so the GeoLite2 // CC BY-SA 4.0 attribution stays reachable for unauthenticated // visitors. The `/about` route is already registered in `proxy.ts` @@ -148,17 +146,11 @@ export function AuthShell({ inSharedRecord && !isDestinationInSharedRecord(pathname, sections) && !isRecordSettingsPath; - // The Coach page is the one full-bleed route: its conversations panel sits - // flush against the right edge of the viewport and the page owns every - // scroll area inside it. The content box drops the centred container and - // `
` drops its reserved scrollbar gutter, which would otherwise paint - // a dead strip to the right of the panel on classic-scrollbar platforms. - // A refusal (shared record, module off) renders a card, so it keeps the - // ordinary padded frame. - const fullBleed = - pathname === COACH_FULL_BLEED_PATH && - !outsideSharedRecord && - switchedOffModule(pathname, user?.modules) === undefined; + const fullBleed = isFullBleedPage({ + pathname, + outsideSharedRecord, + moduleOff: switchedOffModule(pathname, user?.modules) !== undefined, + }); // The whole setup flow, not only its front door: every screen lives under // `/onboarding/` (v1.39 C2), and an exact match here put the // questions inside the full app chrome, sidebar and all. diff --git a/src/components/layout/full-bleed.ts b/src/components/layout/full-bleed.ts new file mode 100644 index 000000000..069f35acb --- /dev/null +++ b/src/components/layout/full-bleed.ts @@ -0,0 +1,27 @@ +/** + * Whether the shell renders a page edge to edge. + * + * The Coach page is the one full-bleed route: its conversations panel sits + * flush against the right edge of the viewport and the page owns every + * scroll area inside it. The shell then drops its centred container and + * padding, and `
` drops its reserved scrollbar gutter, which would + * otherwise paint a dead strip to the right of the panel on + * classic-scrollbar platforms. + * + * Exactly `/coach`: its sub-pages (plans, conversations) are ordinary pages + * in the padded frame. A refusal on `/coach` (outside a shared record, or + * the module switched off) renders a card, so it keeps the padded frame too. + */ +const FULL_BLEED_PATH = "/coach"; + +export function isFullBleedPage(args: { + pathname: string; + outsideSharedRecord: boolean; + moduleOff: boolean; +}): boolean { + return ( + args.pathname === FULL_BLEED_PATH && + !args.outsideSharedRecord && + !args.moduleOff + ); +}