fix(ui): make overlay scrollbar persistent on desktop shells (#2581)
PR #2520 fixed #2475 in the wrong layer (overflow classes on SettingsView.tsx wrapper divs that aren't the actual scroll containers), so the "no scrollbar in Settings" bug shipped in v1.17.2 unchanged, and the sessions feed has the same underlying issue (#2402). The actual scroll containers render through ScrollableOverlay -> OverlayScrollbar, which hides the native scrollbar and draws its own JS thumb that auto-hides ~1s after scroll activity stops, with a second CSS rule fully hiding that thumb inside Settings specifically. Fix both at the shared component/CSS layer so every consumer (Settings, sessions feed, git panels, chat, etc.) gets a persistent scrollbar on desktop shells (Electron + VS Code webview) in one change: - OverlayScrollbar.tsx: skip the auto-hide timer and show the thumb immediately on mount when content overflows, on desktop runtimes. userIntentOnly consumers (chat auto-follow scroll, reasoning blocks) keep today's intent-gated behavior unchanged. - index.css: scope the Settings-specific hide rule to non-desktop, so mobile/web keep existing behavior and desktop gets the thumb back.
This commit is contained in:
@@ -25,6 +25,12 @@ const isSameThumbMetrics = (a: ThumbMetrics, b: ThumbMetrics): boolean => {
|
||||
return Math.abs(a.length - b.length) < METRIC_EPSILON && Math.abs(a.offset - b.offset) < METRIC_EPSILON;
|
||||
};
|
||||
|
||||
// Desktop shells (Electron, VS Code webview) use persistent, not
|
||||
// auto-hiding, scrollbars. Reads the same `desktop-runtime` root class that
|
||||
// index.css keys off, so JS and CSS never disagree on what counts as desktop.
|
||||
const isDesktopScrollbarRuntime = (): boolean =>
|
||||
typeof document !== "undefined" && document.documentElement.classList.contains("desktop-runtime");
|
||||
|
||||
const OverlayScrollbarComponent: React.FC<OverlayScrollbarProps> = ({
|
||||
containerRef,
|
||||
minThumbSize = 32,
|
||||
@@ -128,8 +134,15 @@ const OverlayScrollbarComponent: React.FC<OverlayScrollbarProps> = ({
|
||||
if (isHoveringRef.current) {
|
||||
return;
|
||||
}
|
||||
// Desktop shells keep the thumb visible once shown instead of
|
||||
// auto-hiding it after a delay. userIntentOnly callers (e.g. chat
|
||||
// auto-follow scroll) opt out of persistence on purpose, so they keep
|
||||
// the existing auto-hide behavior even on desktop.
|
||||
if (isDesktopScrollbarRuntime() && !userIntentOnly) {
|
||||
return;
|
||||
}
|
||||
hideTimeoutRef.current = setTimeout(() => setVisible(false), hideDelayMs);
|
||||
}, [hideDelayMs]);
|
||||
}, [hideDelayMs, userIntentOnly]);
|
||||
|
||||
const markUserIntent = React.useCallback(() => {
|
||||
lastUserIntentAtRef.current = Date.now();
|
||||
@@ -162,7 +175,10 @@ const OverlayScrollbarComponent: React.FC<OverlayScrollbarProps> = ({
|
||||
if (!container) return;
|
||||
|
||||
updateMetrics();
|
||||
setVisible(false);
|
||||
// On desktop shells, show the thumb immediately if content overflows
|
||||
// instead of waiting for the first scroll event (persistent affordance,
|
||||
// matching native desktop scrollbar conventions).
|
||||
setVisible(isDesktopScrollbarRuntime() && !userIntentOnly);
|
||||
|
||||
const onScroll = () => handleScroll();
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
|
||||
@@ -1804,8 +1804,10 @@ input[aria-label="Terminal input"] {
|
||||
|
||||
}
|
||||
|
||||
/* Settings dialog: hide the overlay scrollbar; wheel/keyboard scroll still works. */
|
||||
[data-settings-view="true"] .overlay-scrollbar {
|
||||
/* Settings dialog: hide the overlay scrollbar on mobile/web; wheel/keyboard
|
||||
scroll still works. Desktop shells keep the persistent scrollbar so the
|
||||
Settings sub-panels aren't left with no scroll affordance at all. */
|
||||
html:not(.desktop-runtime) [data-settings-view="true"] .overlay-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user