fix(app): retain Explorer tab state

This commit is contained in:
Mohamed Boudra
2026-08-17 21:15:16 +02:00
parent b97993f866
commit a54b6c2020
3 changed files with 89 additions and 32 deletions
@@ -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<ExplorerTab[]>(() => {
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 (
<View style={styles.sidebarContent} pointerEvents="auto">
@@ -391,31 +415,37 @@ function ExplorerSidebarContent({
{/* Content based on active tab */}
<View style={styles.contentArea} testID="explorer-content-area">
{resolvedTab === "changes" && (
<ChangedFilesPane
serverId={serverId}
workspaceId={workspaceId}
workspaceRoot={workspaceRoot}
isOpen={isOpen}
onOpenFile={onOpenFile}
/>
)}
{resolvedTab === "files" && (
<FilesPane
serverId={serverId}
workspaceId={workspaceId}
workspaceRoot={workspaceRoot}
onOpenFile={onOpenFile}
/>
)}
{resolvedTab === "pr" && (
<PrTabContent
serverId={serverId}
workspaceId={workspaceId}
cwd={workspaceRoot}
prPane={prPane}
/>
)}
{mountedTabIds.has("changes") ? (
<RetainedPanel active={resolvedTab === "changes"}>
<ChangedFilesPane
serverId={serverId}
workspaceId={workspaceId}
workspaceRoot={workspaceRoot}
isOpen={isOpen}
onOpenFile={onOpenFile}
/>
</RetainedPanel>
) : null}
{mountedTabIds.has("files") ? (
<RetainedPanel active={resolvedTab === "files"}>
<FilesPane
serverId={serverId}
workspaceId={workspaceId}
workspaceRoot={workspaceRoot}
onOpenFile={onOpenFile}
/>
</RetainedPanel>
) : null}
{mountedTabIds.has("pr") ? (
<RetainedPanel active={resolvedTab === "pr"}>
<PrTabContent
serverId={serverId}
workspaceId={workspaceId}
cwd={workspaceRoot}
prPane={prPane}
/>
</RetainedPanel>
) : null}
</View>
</View>
);
@@ -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",
@@ -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;
@@ -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,
);
});
});