From 727a53a7769c8336f317207c3b9cc4b1739390eb Mon Sep 17 00:00:00 2001 From: heaven Date: Mon, 18 May 2026 15:14:58 +0300 Subject: [PATCH] fix(horseshoe): extend mobile DM and Channels wrappers up over the safe-top zone so the StreamHeader curtain paints the status-bar strip on drag-up --- .../settings/MobileSettingsHorseshoe.css.ts | 41 +++++++++++++++---- .../ChannelsWorkspaceHorseshoe.css.ts | 23 ++++++++++- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/app/features/settings/MobileSettingsHorseshoe.css.ts b/src/app/features/settings/MobileSettingsHorseshoe.css.ts index c7ab3862..aaec1647 100644 --- a/src/app/features/settings/MobileSettingsHorseshoe.css.ts +++ b/src/app/features/settings/MobileSettingsHorseshoe.css.ts @@ -17,6 +17,18 @@ export const HORSESHOE_GAP_PX = VOJO_HORSESHOE_GAP_PX; // // `flex: 1` so the container fills whatever flex slot it's mounted in // (PageNav's inner column for the Direct route). +// +// `marginTop: -var(--vojo-safe-top)` extends the container UP over the +// status-bar safe-top zone reserved by `PageNav` via `padding-top`. With +// this offset the wrapped StreamHeader's `curtain` (which positions +// `top: ` when dragged past `closed`) can paint into the +// status-bar zone — without it, both `overflow: hidden` here and the +// `clipPath` on `appBody` would clip those pixels at the bottom of +// the status-bar strip and the strip would stay uncovered, breaking +// parity with Bots / ChannelsRoot (where StreamHeader is a direct +// child of PageNav and no such wrapper clip exists). The compensating +// `padding-top` lives on `appBody` so the wrapped DM list / tabs row +// stay visually anchored at the same Y as before the shift. export const container = style({ position: 'relative', display: 'flex', @@ -25,6 +37,7 @@ export const container = style({ minWidth: 0, minHeight: 0, overflow: 'hidden', + marginTop: 'calc(-1 * var(--vojo-safe-top, 0px))', }); // === App body === Holds the wrapped children (the DM list — header, @@ -47,16 +60,25 @@ export const container = style({ // measured heights, and its top items in place; only the bottom edge // of what's visible gets carved into the void below. // -// `backgroundColor: Background.Container` is load-bearing: the -// container behind appBody is painted with the void colour (#090909) -// when the sheet is active, and without an opaque bg here the void -// would bleed through every transparent gap between DM list rows. -// The clip-path then carves away the bottom of this opaque pane, -// exposing the container's void colour only in the masked region — -// the only place we want the void to show. +// `backgroundColor: SurfaceVariant.Container` is load-bearing on two +// counts: (1) it must be OPAQUE so the container's void colour +// (painted inline when the sheet is active) doesn't bleed through gaps +// between DM list rows; (2) the safe-top padding region of THIS +// element is what paints the system-tray strip when the wrapper +// container is extended up over it (see `container.marginTop` above). +// Picking `SurfaceVariant.Container` (not `Background.Container`) +// matches the Bots / ChannelsRoot status-bar tone exactly — Bots +// renders `PageNav-inner.bg = SurfaceVariant.Container` in the safe- +// top zone, and the StreamHeader curtain (`Background.Container`) +// overpaints that lighter strip with the darker tone as it's dragged +// up. Mirroring the same two tones here gives Direct the same visible +// «curtain darkens the strip» transition the user expects. // // `flex: column` so the children (which expect a flex column parent -// — PageNav uses it) still stack naturally. +// — PageNav uses it) still stack naturally. `paddingTop: +// var(--vojo-safe-top)` reserves status-bar space INSIDE appBody so +// the wrapped StreamHeader.stage starts at the same Y as before the +// `container.marginTop` shift extended us upward. export const appBody = style({ position: 'absolute', top: 0, @@ -67,7 +89,8 @@ export const appBody = style({ flexDirection: 'column', minWidth: 0, minHeight: 0, - backgroundColor: color.Background.Container, + backgroundColor: color.SurfaceVariant.Container, + paddingTop: 'var(--vojo-safe-top, 0px)', willChange: 'clip-path', }); diff --git a/src/app/pages/client/channels/ChannelsWorkspaceHorseshoe.css.ts b/src/app/pages/client/channels/ChannelsWorkspaceHorseshoe.css.ts index 31c27f8d..085928f6 100644 --- a/src/app/pages/client/channels/ChannelsWorkspaceHorseshoe.css.ts +++ b/src/app/pages/client/channels/ChannelsWorkspaceHorseshoe.css.ts @@ -15,6 +15,16 @@ export const HORSESHOE_GAP_PX = VOJO_HORSESHOE_GAP_PX; // the sheet is active. See MobileSettingsHorseshoe.css.ts for the full // rationale on every property here — kept verbatim except for the file // header. +// +// `marginTop: -var(--vojo-safe-top)` extends the container UP over the +// status-bar safe-top zone. Mirror of the same property in +// `MobileSettingsHorseshoe.css.ts::container` — without it, the wrapped +// StreamHeader's curtain (which positions `top: ` when +// dragged past `closed`) is clipped by both `overflow: hidden` here +// and `appBody`'s clipPath at the bottom edge of the status bar, so +// the lighter `SurfaceVariant.Container` strip from `PageNav-inner` +// stays uncovered. The compensating `padding-top` lives on `appBody` +// so the wrapped channels list / tabs row stay visually anchored. export const container = style({ position: 'relative', display: 'flex', @@ -23,6 +33,7 @@ export const container = style({ minWidth: 0, minHeight: 0, overflow: 'hidden', + marginTop: 'calc(-1 * var(--vojo-safe-top, 0px))', }); // Wrapped children (StreamHeader → ChannelsList → ChannelCreateRow → @@ -32,6 +43,15 @@ export const container = style({ // sheet is active, so without an opaque bg the void would bleed through // every transparent gap between list rows. See canonical for the full // reasoning on clip-path vs translate vs flex-shrink. +// +// `paddingTop: var(--vojo-safe-top)` reserves status-bar space INSIDE +// appBody — compensates the `container.marginTop: -safe-top` shift so +// the wrapped flex children (StreamHeader.stage) stay anchored at the +// same visual Y as before. `backgroundColor` is `SurfaceVariant.Container` +// (not `Background.Container`) so the now-visible safe-top zone of +// appBody matches the tone `PageNav-inner` shows in Bots / Channels-root +// (which use `surface="surfaceVariant"`), giving the curtain's darker +// `Background.Container` tone a visible strip to over-paint on drag-up. export const appBody = style({ position: 'absolute', top: 0, @@ -42,7 +62,8 @@ export const appBody = style({ flexDirection: 'column', minWidth: 0, minHeight: 0, - backgroundColor: color.Background.Container, + backgroundColor: color.SurfaceVariant.Container, + paddingTop: 'var(--vojo-safe-top, 0px)', willChange: 'clip-path', });