From 674616f3989380cce0a0af8df1cdd50001005e8c Mon Sep 17 00:00:00 2001 From: heaven Date: Sun, 7 Jun 2026 01:46:40 +0300 Subject: [PATCH] fix(nav): stop the native chat list overscrolling when its content fits the window --- src/app/components/page/Page.tsx | 27 ++++++++++++++++++++++++++- src/app/components/page/style.css.ts | 11 ++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/app/components/page/Page.tsx b/src/app/components/page/Page.tsx index f72b924f..56affa88 100644 --- a/src/app/components/page/Page.tsx +++ b/src/app/components/page/Page.tsx @@ -395,8 +395,33 @@ export function PageNavContent({ size="300" hideTrack visibility="Hover" + // folds `direction="Vertical"` sets `overflow-y: scroll`, which makes + // this a *user-scrollable* container even when the list fits (scroll + // range 0, permanently pinned at its boundary). On Android any + // touch-drag on such a boundary-pinned scroller fires the elastic + // overscroll stretch — the list "bounces" though it has nothing to + // scroll. `overflow-y: auto` is user-scrollable only when content + // actually overflows: short lists become inert (no overscroll), long + // lists still scroll normally. Inline style wins over the recipe class. + style={{ overflowY: 'auto' }} > -
{children}
+ {/* `css.PageNavContent` carries a 32px (`S700`) bottom padding for + breathing room below the last row. That padding is part of the + scroll content, so once the list grows to within 32px of the + viewport it overflows by however much of the padding doesn't fit — + the list visually "fits" (a gap shows below it) yet you can still + scroll a few px into the leftover padding. On native the curtain + list already has its own bottom boundary (the pinned DirectSelfRow / + WorkspaceFooter, or the screen edge), so the in-scroll breather is + redundant; dropping it there means a list that fits has zero scroll + range. Desktop keeps the breather (mouse-scrolling into it is + harmless and the last row wants the air at the panel bottom). */} +
+ {children} +
); diff --git a/src/app/components/page/style.css.ts b/src/app/components/page/style.css.ts index 7c35d4ab..5dea3d8c 100644 --- a/src/app/components/page/style.css.ts +++ b/src/app/components/page/style.css.ts @@ -143,7 +143,16 @@ export const PageNavHeader = recipe({ export type PageNavHeaderVariants = RecipeVariants; export const PageNavContent = style({ - minHeight: '100%', + // No `min-height: 100%`. It used to force this padded wrapper to at least + // the scroll viewport's height, but the div is transparent (the curtain on + // native / the PageNav inner column on web paints the background) so the + // fill was invisible — its only real effect was a ~1px scroll overflow: + // `100%` resolves against the fractional flex-parent height and rounds UP + // while the viewport's clientHeight rounds DOWN, leaving the list + // permanently scrollable by a hair even when it fits. That hair is the + // "useless" overscroll on native. Letting the wrapper size to its content + // means a short list has no scroll range at all (paired with the + // `overflow-y: auto` override on the Scroll in Page.tsx). padding: config.space.S200, paddingRight: 0, paddingBottom: config.space.S700,