From a54b6c2020a0a2fc6ff86449425c1acc161cbd06 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 17 Aug 2026 21:01:23 +0200 Subject: [PATCH] fix(app): retain Explorer tab state --- .../app/src/components/explorer-sidebar.tsx | 95 +++++++++++++------ .../app/src/hooks/keyboard-shift-policy.ts | 7 ++ .../hooks/use-keyboard-shift-style.test.ts | 19 +++- 3 files changed, 89 insertions(+), 32 deletions(-) diff --git a/packages/app/src/components/explorer-sidebar.tsx b/packages/app/src/components/explorer-sidebar.tsx index 1bb0d6eb0..0fdd48e6e 100644 --- a/packages/app/src/components/explorer-sidebar.tsx +++ b/packages/app/src/components/explorer-sidebar.tsx @@ -17,13 +17,19 @@ import { formatPrTabLabel, PullRequestTabIcon } from "@/git/pull-request-panel"; import { usePanelStore, selectIsFileExplorerOpen, type ExplorerTab } from "@/stores/panel-store"; import { useCloseFileExplorerGesture } from "@/mobile-panels/gestures"; import { MobilePanelOverlay } from "@/mobile-panels/presentation"; -import { HEADER_INNER_HEIGHT } from "@/constants/layout"; +import { + HEADER_INNER_HEIGHT, + HEADER_INNER_HEIGHT_MOBILE, + HEADER_TOP_PADDING_MOBILE, +} from "@/constants/layout"; import { ChangesSurface } from "@/git/diff-pane"; import { FileExplorerPane } from "./file-explorer-pane"; import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style"; +import { shouldUseCompactExplorerKeyboardPadding } from "@/hooks/keyboard-shift-policy"; import { useHasOwnedWindowChromeObstruction, WindowChromeSafeArea } from "@/utils/desktop-window"; import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region"; -import { RetainedPanelActivity } from "@/components/retained-panel"; +import { RetainedPanel, RetainedPanelActivity } from "@/components/retained-panel"; +import { useMountedTabSet } from "@/screens/workspace/use-mounted-tab-set"; import { SidebarResizeHandle } from "@/components/sidebar-resize-handle"; import { resolveDesktopExplorerWidth } from "@/components/desktop-sidebar-layout"; import { @@ -82,9 +88,10 @@ export function CompactExplorerSidebar({ workspaceRoot, isGit, }); + const usePanelKeyboardPadding = shouldUseCompactExplorerKeyboardPadding({ isGit, explorerTab }); const { style: mobileKeyboardInsetStyle } = useKeyboardShiftStyle({ mode: "padding", - enabled: true, + enabled: usePanelKeyboardPadding, }); const { gesture: closeGesture } = useCloseFileExplorerGesture(); @@ -104,12 +111,19 @@ export function CompactExplorerSidebar({ const mobileSidebarStyle = useMemo( () => [ { - paddingTop: insets.top, + paddingTop: insets.top + HEADER_TOP_PADDING_MOBILE, + paddingBottom: usePanelKeyboardPadding ? 0 : insets.bottom, backgroundColor: theme.colors.surfaceSidebar, }, mobileKeyboardInsetStyle, ], - [insets.top, theme.colors.surfaceSidebar, mobileKeyboardInsetStyle], + [ + insets.bottom, + insets.top, + mobileKeyboardInsetStyle, + theme.colors.surfaceSidebar, + usePanelKeyboardPadding, + ], ); return ( @@ -318,6 +332,16 @@ function ExplorerSidebarContent({ !isGit && (activeTab === "changes" || activeTab === "pr") ? "files" : activeTab; const resolvedTab: ExplorerTab = requestedTab === "pr" && !showPrTab ? "changes" : requestedTab; const prTabLabel = formatPrTabLabel(prPane.prNumber); + const availableTabs = useMemo(() => { + const tabs: ExplorerTab[] = isGit ? ["changes", "files"] : ["files"]; + if (isGit && showPrTab) tabs.push("pr"); + return tabs; + }, [isGit, showPrTab]); + const { mountedTabIds } = useMountedTabSet({ + activeTabId: resolvedTab, + allTabIds: availableTabs, + cap: availableTabs.length, + }); return ( @@ -391,31 +415,37 @@ function ExplorerSidebarContent({ {/* Content based on active tab */} - {resolvedTab === "changes" && ( - - )} - {resolvedTab === "files" && ( - - )} - {resolvedTab === "pr" && ( - - )} + {mountedTabIds.has("changes") ? ( + + + + ) : null} + {mountedTabIds.has("files") ? ( + + + + ) : null} + {mountedTabIds.has("pr") ? ( + + + + ) : null} ); @@ -487,7 +517,10 @@ const styles = StyleSheet.create((theme) => ({ }, header: { position: "relative", - height: HEADER_INNER_HEIGHT, + height: { + xs: HEADER_INNER_HEIGHT_MOBILE, + md: HEADER_INNER_HEIGHT, + }, flexDirection: "row", alignItems: "center", justifyContent: "space-between", diff --git a/packages/app/src/hooks/keyboard-shift-policy.ts b/packages/app/src/hooks/keyboard-shift-policy.ts index 546bffa66..1923d7613 100644 --- a/packages/app/src/hooks/keyboard-shift-policy.ts +++ b/packages/app/src/hooks/keyboard-shift-policy.ts @@ -1,5 +1,12 @@ export const DEFAULT_IOS_KEYBOARD_INSET_MIN_HEIGHT = 120; +export function shouldUseCompactExplorerKeyboardPadding(input: { + isGit: boolean; + explorerTab: "changes" | "files" | "pr"; +}): boolean { + return !input.isGit || input.explorerTab !== "changes"; +} + export function resolveKeyboardShift(input: { rawKeyboardHeight: number; keyboardProgress: number; diff --git a/packages/app/src/hooks/use-keyboard-shift-style.test.ts b/packages/app/src/hooks/use-keyboard-shift-style.test.ts index 0f254d59c..031f09420 100644 --- a/packages/app/src/hooks/use-keyboard-shift-style.test.ts +++ b/packages/app/src/hooks/use-keyboard-shift-style.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { resolveKeyboardShift } from "./keyboard-shift-policy"; +import { + resolveKeyboardShift, + shouldUseCompactExplorerKeyboardPadding, +} from "./keyboard-shift-policy"; describe("resolveKeyboardShift", () => { it("keeps the existing open-keyboard offset behavior", () => { @@ -38,3 +41,17 @@ describe("resolveKeyboardShift", () => { ).toBe(0); }); }); + +describe("shouldUseCompactExplorerKeyboardPadding", () => { + it("keeps the changes viewport stable while preserving padding for other tabs", () => { + expect(shouldUseCompactExplorerKeyboardPadding({ isGit: true, explorerTab: "changes" })).toBe( + false, + ); + expect(shouldUseCompactExplorerKeyboardPadding({ isGit: true, explorerTab: "files" })).toBe( + true, + ); + expect(shouldUseCompactExplorerKeyboardPadding({ isGit: false, explorerTab: "changes" })).toBe( + true, + ); + }); +});