Separate explorer checkout from panel state

This commit is contained in:
Mohamed Boudra
2026-04-20 12:40:39 +07:00
parent 9ea75afd37
commit f8411d121e
11 changed files with 295 additions and 188 deletions
+11 -7
View File
@@ -388,9 +388,11 @@ function AppContainer({
const { theme } = useUnistyles();
const daemons = useHosts();
const { settings, updateSettings } = useAppSettings();
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
const toggleBothSidebars = usePanelStore((state) => state.toggleBothSidebars);
const toggleMobileAgentList = usePanelStore((state) => state.toggleMobileAgentList);
const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer);
const toggleDesktopAgentList = usePanelStore((state) => state.toggleDesktopAgentList);
const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer);
const toggleDesktopSidebars = usePanelStore((state) => state.toggleDesktopSidebars);
const toggleFocusMode = usePanelStore((state) => state.toggleFocusMode);
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
const agentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
@@ -405,6 +407,8 @@ function AppContainer({
const isCompactLayout = useIsCompactFormFactor();
const chromeEnabled = chromeEnabledOverride ?? daemons.length > 0;
const pathname = usePathname();
const toggleAgentList = isCompactLayout ? toggleMobileAgentList : toggleDesktopAgentList;
const toggleFileExplorer = isCompactLayout ? toggleMobileFileExplorer : toggleDesktopFileExplorer;
// TODO: stop matching pathname here as a branch. `chromeEnabled` should not
// conflate workspace/project-specific chrome (sidebar, mobile gesture) with
// global concerns like keyboard shortcuts. Split those out so settings (and
@@ -445,7 +449,7 @@ function AppContainer({
isMobile: isCompactLayout,
toggleAgentList,
toggleFileExplorer,
toggleBothSidebars,
toggleBothSidebars: toggleDesktopSidebars,
toggleFocusMode,
cycleTheme,
});
@@ -490,7 +494,7 @@ function MobileGestureWrapper({
chromeEnabled: boolean;
}) {
const mobileView = usePanelStore((state) => state.mobileView);
const openAgentList = usePanelStore((state) => state.openAgentList);
const showMobileAgentList = usePanelStore((state) => state.showMobileAgentList);
const horizontalScroll = useHorizontalScrollOptional();
const {
translateX,
@@ -507,8 +511,8 @@ function MobileGestureWrapper({
const handleGestureOpen = useCallback(() => {
gestureAnimatingRef.current = true;
openAgentList();
}, [openAgentList, gestureAnimatingRef]);
showMobileAgentList();
}, [showMobileAgentList, gestureAnimatingRef]);
const openGesture = useMemo(
() =>
@@ -125,7 +125,8 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
const [expandedInlineToolCallIds, setExpandedInlineToolCallIds] = useState<Set<string>>(
new Set(),
);
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer);
const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout);
// Get serverId (fallback to agent's serverId if not provided)
@@ -205,15 +206,21 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
isGit: agent.projectPlacement?.checkout?.isGit ?? true,
tab: "files",
});
openFileExplorer();
if (isMobile) {
showMobileFileExplorer();
return;
}
openDesktopFileExplorer();
},
[
agent.cwd,
openFileExplorer,
isMobile,
openDesktopFileExplorer,
requestDirectoryListing,
resolvedServerId,
router,
setExplorerTabForCheckout,
showMobileFileExplorer,
openWorkspaceFile,
workspaceId,
],
@@ -54,7 +54,8 @@ export function ExplorerSidebar({
const isMobile = useIsCompactFormFactor();
const mobileView = usePanelStore((state) => state.mobileView);
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
const closeToAgent = usePanelStore((state) => state.closeToAgent);
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer);
const explorerTab = usePanelStore((state) => state.explorerTab);
const explorerWidth = usePanelStore((state) => state.explorerWidth);
const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout);
@@ -107,15 +108,26 @@ export function ExplorerSidebar({
mobileView,
desktopFileExplorerOpen,
});
closeToAgent();
if (isMobile) {
showMobileAgent();
return;
}
closeDesktopFileExplorer();
},
[closeToAgent, desktopFileExplorerOpen, isOpen, mobileView],
[
closeDesktopFileExplorer,
desktopFileExplorerOpen,
isMobile,
isOpen,
mobileView,
showMobileAgent,
],
);
const handleCloseFromGesture = useCallback(() => {
gestureAnimatingRef.current = true;
closeToAgent();
}, [closeToAgent, gestureAnimatingRef]);
showMobileAgent();
}, [gestureAnimatingRef, showMobileAgent]);
const enableSidebarCloseGesture = isMobile && isOpen;
@@ -48,7 +48,8 @@ export function SidebarMenuToggle({
const isMobile = useIsCompactFormFactor();
const mobileView = usePanelStore((state) => state.mobileView);
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
const toggleAgentList = usePanelStore((state) => state.toggleAgentList);
const toggleMobileAgentList = usePanelStore((state) => state.toggleMobileAgentList);
const toggleDesktopAgentList = usePanelStore((state) => state.toggleDesktopAgentList);
const toggleShortcutKeys = getShortcutOs() === "mac" ? ["mod", "B"] : ["mod", "."];
const isOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen;
@@ -57,7 +58,7 @@ export function SidebarMenuToggle({
return (
<HeaderToggleButton
onPress={toggleAgentList}
onPress={isMobile ? toggleMobileAgentList : toggleDesktopAgentList}
tooltipLabel="Toggle sidebar"
tooltipKeys={toggleShortcutKeys}
tooltipSide={tooltipSide}
+6 -6
View File
@@ -118,7 +118,7 @@ export const LeftSidebar = memo(function LeftSidebar({
const isCompactLayout = useIsCompactFormFactor();
const mobileView = usePanelStore((state) => state.mobileView);
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
const closeToAgent = usePanelStore((state) => state.closeToAgent);
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
const pathname = usePathname();
const daemons = useHosts();
const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname]);
@@ -208,18 +208,18 @@ export const LeftSidebar = memo(function LeftSidebar({
const openProjectPicker = useOpenProjectPicker(activeServerId);
const handleOpenProjectMobile = useCallback(() => {
closeToAgent();
showMobileAgent();
void openProjectPicker();
}, [closeToAgent, openProjectPicker]);
}, [showMobileAgent, openProjectPicker]);
const handleOpenProjectDesktop = useCallback(() => {
void openProjectPicker();
}, [openProjectPicker]);
const handleSettingsMobile = useCallback(() => {
closeToAgent();
showMobileAgent();
router.push(buildSettingsRoute());
}, [closeToAgent]);
}, [showMobileAgent]);
const handleSettingsDesktop = useCallback(() => {
router.push(buildSettingsRoute());
@@ -272,7 +272,7 @@ export const LeftSidebar = memo(function LeftSidebar({
insetsTop={insets.top}
insetsBottom={insets.bottom}
isOpen={isOpen}
closeToAgent={closeToAgent}
closeToAgent={showMobileAgent}
handleOpenProject={handleOpenProjectMobile}
handleSettings={handleSettingsMobile}
handleViewMoreNavigate={handleViewMoreNavigate}
@@ -89,8 +89,8 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi
const xtermTheme = useMemo(() => toXtermTheme(theme.colors.terminal), [theme]);
const isMobile = useIsCompactFormFactor();
const mobileView = usePanelStore((state) => state.mobileView);
const openAgentList = usePanelStore((state) => state.openAgentList);
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
const showMobileAgentList = usePanelStore((state) => state.showMobileAgentList);
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
const swipeGesturesEnabled = isMobile && mobileView === "agent";
const { shift: keyboardShift, style: keyboardPaddingStyle } = useKeyboardShiftStyle({
mode: "padding",
@@ -573,13 +573,13 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi
if (!swipeGesturesEnabled) {
return;
}
openAgentList();
showMobileAgentList();
}}
onSwipeLeft={() => {
if (!swipeGesturesEnabled) {
return;
}
openFileExplorer();
showMobileFileExplorer();
}}
onInput={handleTerminalData}
onResize={handleTerminalResize}
@@ -36,7 +36,11 @@ import {
useHostRuntimeIsConnected,
} from "@/runtime/host-runtime";
import { ExplorerSidebarAnimationProvider } from "@/contexts/explorer-sidebar-animation-context";
import { usePanelStore, type ExplorerCheckoutContext } from "@/stores/panel-store";
import { usePanelStore } from "@/stores/panel-store";
import {
setActiveExplorerCheckout,
type ExplorerCheckoutContext,
} from "@/stores/explorer-checkout-context";
import { MAX_CONTENT_WIDTH, useIsCompactFormFactor } from "@/constants/layout";
import { WelcomeScreen } from "@/components/welcome-screen";
import type { Agent } from "@/contexts/session-context";
@@ -237,10 +241,12 @@ function DraftAgentScreenContent({
const isMobile = useIsCompactFormFactor();
const mobileView = usePanelStore((state) => state.mobileView);
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
const closeFileExplorer = usePanelStore((state) => state.closeFileExplorer);
const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout);
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer);
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer);
const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer);
const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer);
const activateExplorerTabForCheckout = usePanelStore(
(state) => state.activateExplorerTabForCheckout,
);
@@ -646,18 +652,39 @@ function DraftAgentScreenContent({
return;
}
activateExplorerTabForCheckout(draftExplorerCheckout);
openFileExplorer();
}, [activateExplorerTabForCheckout, draftExplorerCheckout, openFileExplorer]);
if (isMobile) {
showMobileFileExplorer();
return;
}
openDesktopFileExplorer();
}, [
activateExplorerTabForCheckout,
draftExplorerCheckout,
isMobile,
openDesktopFileExplorer,
showMobileFileExplorer,
]);
const handleToggleExplorer = useCallback(() => {
if (!canOpenExplorer) {
return;
}
if (isExplorerOpen) {
toggleFileExplorer();
if (isMobile) {
toggleMobileFileExplorer();
return;
}
toggleDesktopFileExplorer();
return;
}
openExplorerForDraftCheckout();
}, [canOpenExplorer, isExplorerOpen, openExplorerForDraftCheckout, toggleFileExplorer]);
}, [
canOpenExplorer,
isExplorerOpen,
isMobile,
openExplorerForDraftCheckout,
toggleDesktopFileExplorer,
toggleMobileFileExplorer,
]);
const explorerOpenGesture = useExplorerOpenGesture({
enabled: isMobile && mobileView === "agent" && canOpenExplorer,
onOpen: openExplorerForDraftCheckout,
@@ -956,8 +983,19 @@ function DraftAgentScreenContent({
if (!isFocused || canOpenExplorer || !isExplorerOpen) {
return;
}
closeFileExplorer();
}, [canOpenExplorer, closeFileExplorer, isExplorerOpen, isFocused]);
if (isMobile) {
showMobileAgent();
return;
}
closeDesktopFileExplorer();
}, [
canOpenExplorer,
closeDesktopFileExplorer,
isExplorerOpen,
isFocused,
isMobile,
showMobileAgent,
]);
useEffect(() => {
return () => {
setActiveExplorerCheckout(null);
@@ -20,7 +20,7 @@ import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
import { PairDeviceModal } from "@/desktop/components/pair-device-modal";
export function OpenProjectScreen({ serverId }: { serverId: string }) {
const openAgentList = usePanelStore((s) => s.openAgentList);
const openDesktopAgentList = usePanelStore((s) => s.openDesktopAgentList);
const openProjectPicker = useOpenProjectPicker(serverId);
const hasHydrated = useSessionStore((s) => s.sessions[serverId]?.hasHydratedWorkspaces ?? false);
const hasProjects = useHasWorkspaces(serverId);
@@ -31,9 +31,9 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) {
useEffect(() => {
if (!isCompactLayout) {
openAgentList();
openDesktopAgentList();
}
}, [isCompactLayout, openAgentList]);
}, [isCompactLayout, openDesktopAgentList]);
return (
<View style={styles.container}>
@@ -48,7 +48,11 @@ import { WorkspaceScriptsButton } from "@/screens/workspace/workspace-scripts-bu
import { ExplorerSidebarAnimationProvider } from "@/contexts/explorer-sidebar-animation-context";
import { useToast } from "@/contexts/toast-context";
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
import { usePanelStore, type ExplorerCheckoutContext } from "@/stores/panel-store";
import { usePanelStore } from "@/stores/panel-store";
import {
setActiveExplorerCheckout,
type ExplorerCheckoutContext,
} from "@/stores/explorer-checkout-context";
import { useSessionStore } from "@/stores/session-store";
import {
buildWorkspaceTabPersistenceKey,
@@ -843,13 +847,14 @@ function WorkspaceScreenContent({
const mobileView = usePanelStore((state) => state.mobileView);
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer);
const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer);
const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer);
const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer);
const activateExplorerTabForCheckout = usePanelStore(
(state) => state.activateExplorerTabForCheckout,
);
const closeToAgent = usePanelStore((state) => state.closeToAgent);
const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout);
const showMobileAgent = usePanelStore((state) => state.showMobileAgent);
const isExplorerOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
@@ -876,16 +881,36 @@ function WorkspaceScreenContent({
return;
}
activateExplorerTabForCheckout(activeExplorerCheckout);
openFileExplorer();
}, [activateExplorerTabForCheckout, activeExplorerCheckout, openFileExplorer]);
if (isMobile) {
showMobileFileExplorer();
return;
}
openDesktopFileExplorer();
}, [
activateExplorerTabForCheckout,
activeExplorerCheckout,
isMobile,
openDesktopFileExplorer,
showMobileFileExplorer,
]);
const handleToggleExplorer = useCallback(() => {
if (isExplorerOpen) {
toggleFileExplorer();
if (isMobile) {
toggleMobileFileExplorer();
return;
}
toggleDesktopFileExplorer();
return;
}
openExplorerForWorkspace();
}, [isExplorerOpen, openExplorerForWorkspace, toggleFileExplorer]);
}, [
isExplorerOpen,
isMobile,
openExplorerForWorkspace,
toggleDesktopFileExplorer,
toggleMobileFileExplorer,
]);
const explorerOpenGesture = useExplorerOpenGesture({
enabled: isMobile && mobileView === "agent",
@@ -899,14 +924,14 @@ function WorkspaceScreenContent({
const handler = BackHandler.addEventListener("hardwareBackPress", () => {
if (isExplorerOpen) {
closeToAgent();
showMobileAgent();
return true;
}
return false;
});
return () => handler.remove();
}, [closeToAgent, isExplorerOpen, isRouteFocused]);
}, [isExplorerOpen, isRouteFocused, showMobileAgent]);
const persistenceKey = useMemo(
() =>
@@ -1190,7 +1215,7 @@ function WorkspaceScreenContent({
const handleOpenFileFromExplorer = useCallback(
function handleOpenFileFromExplorer(filePath: string) {
if (isMobile) {
closeToAgent();
showMobileAgent();
}
if (!persistenceKey) {
return;
@@ -1200,7 +1225,7 @@ function WorkspaceScreenContent({
navigateToTabId(tabId);
}
},
[closeToAgent, isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey],
[isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey, showMobileAgent],
);
const handleOpenFileFromChat = useCallback(
@@ -0,0 +1,23 @@
export interface ExplorerCheckoutContext {
serverId: string;
cwd: string;
isGit: boolean;
}
let activeExplorerCheckout: ExplorerCheckoutContext | null = null;
export function getActiveExplorerCheckout(): ExplorerCheckoutContext | null {
return activeExplorerCheckout;
}
export function setActiveExplorerCheckout(checkout: ExplorerCheckoutContext | null) {
if (
activeExplorerCheckout?.serverId === checkout?.serverId &&
activeExplorerCheckout?.cwd === checkout?.cwd &&
activeExplorerCheckout?.isGit === checkout?.isGit
) {
return;
}
activeExplorerCheckout = checkout;
}
+131 -134
View File
@@ -8,8 +8,13 @@ import {
resolveExplorerTabForCheckout,
type ExplorerTab,
} from "./explorer-tab-memory";
import {
getActiveExplorerCheckout,
type ExplorerCheckoutContext,
} from "./explorer-checkout-context";
import { isWeb } from "@/constants/platform";
export type { ExplorerTab } from "./explorer-tab-memory";
export type { ExplorerCheckoutContext } from "./explorer-checkout-context";
/**
* Mobile panel state machine.
@@ -37,12 +42,6 @@ interface DesktopSidebarState {
}
export type SortOption = "name" | "modified" | "size";
export interface ExplorerCheckoutContext {
serverId: string;
cwd: string;
isGit: boolean;
}
export const DEFAULT_SIDEBAR_WIDTH = 320;
export const MIN_SIDEBAR_WIDTH = 200;
export const MAX_SIDEBAR_WIDTH = 600;
@@ -68,21 +67,25 @@ interface PanelState {
explorerTabByCheckout: Record<string, ExplorerTab>;
expandedPathsByWorkspace: Record<string, string[]>;
diffExpandedPathsByWorkspace: Record<string, string[]>;
activeExplorerCheckout: ExplorerCheckoutContext | null;
sidebarWidth: number;
explorerWidth: number;
explorerSortOption: SortOption;
explorerFilesSplitRatio: number;
// Actions
openAgentList: () => void;
openFileExplorer: () => void;
closeFileExplorer: () => void;
closeToAgent: () => void;
toggleAgentList: () => void;
toggleFileExplorer: () => void;
toggleBothSidebars: () => void;
toggleFocusMode: () => void;
showMobileAgent: () => void;
showMobileAgentList: () => void;
showMobileFileExplorer: () => void;
toggleMobileAgentList: () => void;
toggleMobileFileExplorer: () => void;
openDesktopAgentList: () => void;
closeDesktopAgentList: () => void;
toggleDesktopAgentList: () => void;
openDesktopFileExplorer: () => void;
closeDesktopFileExplorer: () => void;
toggleDesktopFileExplorer: () => void;
toggleDesktopSidebars: () => void;
// File explorer settings actions
setExplorerTab: (tab: ExplorerTab) => void;
@@ -90,7 +93,6 @@ interface PanelState {
setExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void;
setDiffExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void;
activateExplorerTabForCheckout: (checkout: ExplorerCheckoutContext) => void;
setActiveExplorerCheckout: (checkout: ExplorerCheckoutContext | null) => void;
setSidebarWidth: (width: number) => void;
setExplorerWidth: (width: number) => void;
setExplorerSortOption: (option: SortOption) => void;
@@ -116,14 +118,17 @@ function clampExplorerFilesSplitRatio(ratio: number): number {
return clampNumber(ratio, MIN_EXPLORER_FILES_SPLIT_RATIO, MAX_EXPLORER_FILES_SPLIT_RATIO);
}
function resolveExplorerTabFromActiveCheckout(state: PanelState): ExplorerTab | null {
if (!state.activeExplorerCheckout) {
function resolveExplorerTabFromCheckout(
state: PanelState,
checkout: ExplorerCheckoutContext | null,
): ExplorerTab | null {
if (!checkout) {
return null;
}
return resolveExplorerTabForCheckout({
serverId: state.activeExplorerCheckout.serverId,
cwd: state.activeExplorerCheckout.cwd,
isGit: state.activeExplorerCheckout.isGit,
serverId: checkout.serverId,
cwd: checkout.cwd,
isGit: checkout.isGit,
explorerTabByCheckout: state.explorerTabByCheckout,
});
}
@@ -148,103 +153,118 @@ export const usePanelStore = create<PanelState>()(
explorerTabByCheckout: {},
expandedPathsByWorkspace: {},
diffExpandedPathsByWorkspace: {},
activeExplorerCheckout: null,
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
explorerWidth: DEFAULT_EXPLORER_SIDEBAR_WIDTH,
explorerSortOption: "name",
explorerFilesSplitRatio: DEFAULT_EXPLORER_FILES_SPLIT_RATIO,
openAgentList: () =>
set((state) => ({
mobileView: "agent-list" as const,
desktop: { ...state.desktop, agentListOpen: true },
})),
openFileExplorer: () =>
set((state) => {
const resolvedTab = resolveExplorerTabFromActiveCheckout(state);
return {
mobileView: "file-explorer" as MobilePanelView,
desktop: { ...state.desktop, fileExplorerOpen: true },
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
};
}),
closeFileExplorer: () =>
set((state) => ({
mobileView: state.mobileView === "file-explorer" ? ("agent" as const) : state.mobileView,
desktop: {
...state.desktop,
fileExplorerOpen: false,
},
})),
closeToAgent: () =>
set((state) => ({
mobileView: "agent" as const,
// On desktop, closing depends on which panel triggered it
// This is called when closing via gesture/backdrop, so we close the currently active mobile panel
desktop: {
...state.desktop,
agentListOpen: state.mobileView === "agent-list" ? false : state.desktop.agentListOpen,
fileExplorerOpen:
state.mobileView === "file-explorer" ? false : state.desktop.fileExplorerOpen,
},
})),
toggleAgentList: () =>
set((state) => ({
// Mobile: toggle between agent and agent-list
mobileView: (state.mobileView === "agent-list"
? "agent"
: "agent-list") as MobilePanelView,
desktop: {
...state.desktop,
agentListOpen: !state.desktop.agentListOpen,
},
})),
toggleFileExplorer: () =>
set((state) => {
// Mobile: toggle between agent and file-explorer
const willOpenMobile = state.mobileView !== "file-explorer";
const willOpenDesktop = !state.desktop.fileExplorerOpen;
const nextMobileView: MobilePanelView = willOpenMobile ? "file-explorer" : "agent";
const nextDesktop = {
...state.desktop,
fileExplorerOpen: willOpenDesktop,
};
const nextState: Pick<PanelState, "mobileView" | "desktop"> &
Partial<Pick<PanelState, "explorerTab">> = {
mobileView: nextMobileView,
desktop: nextDesktop,
};
if (willOpenMobile || willOpenDesktop) {
const resolvedTab = resolveExplorerTabFromActiveCheckout(state);
if (resolvedTab) {
nextState.explorerTab = resolvedTab;
}
}
return nextState;
}),
toggleFocusMode: () =>
set((state) => ({
desktop: { ...state.desktop, focusModeEnabled: !state.desktop.focusModeEnabled },
})),
toggleBothSidebars: () =>
showMobileAgent: () =>
set((state) => {
if (state.mobileView === "agent") {
return state;
}
return { mobileView: "agent" as const };
}),
showMobileAgentList: () =>
set((state) => {
if (state.mobileView === "agent-list") {
return state;
}
return { mobileView: "agent-list" as const };
}),
showMobileFileExplorer: () =>
set((state) => {
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
return {
mobileView: "file-explorer" as const,
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
};
}),
toggleMobileAgentList: () =>
set((state) => ({
mobileView: state.mobileView === "agent-list" ? "agent" : "agent-list",
})),
toggleMobileFileExplorer: () =>
set((state) => {
if (state.mobileView === "file-explorer") {
return { mobileView: "agent" as const };
}
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
return {
mobileView: "file-explorer" as const,
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
};
}),
openDesktopAgentList: () =>
set((state) => {
if (state.desktop.agentListOpen) {
return state;
}
return { desktop: { ...state.desktop, agentListOpen: true } };
}),
closeDesktopAgentList: () =>
set((state) => {
if (!state.desktop.agentListOpen) {
return state;
}
return { desktop: { ...state.desktop, agentListOpen: false } };
}),
toggleDesktopAgentList: () =>
set((state) => ({
desktop: { ...state.desktop, agentListOpen: !state.desktop.agentListOpen },
})),
openDesktopFileExplorer: () =>
set((state) => {
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
return {
desktop: { ...state.desktop, fileExplorerOpen: true },
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
};
}),
closeDesktopFileExplorer: () =>
set((state) => {
if (!state.desktop.fileExplorerOpen) {
return state;
}
return { desktop: { ...state.desktop, fileExplorerOpen: false } };
}),
toggleDesktopFileExplorer: () =>
set((state) => {
const willOpen = !state.desktop.fileExplorerOpen;
const resolvedTab = willOpen
? resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout())
: null;
return {
desktop: { ...state.desktop, fileExplorerOpen: willOpen },
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
};
}),
toggleDesktopSidebars: () =>
set((state) => {
// If either sidebar is open, close both. Otherwise, open both.
const anyOpen = state.desktop.agentListOpen || state.desktop.fileExplorerOpen;
if (anyOpen) {
return {
mobileView: "agent" as MobilePanelView,
desktop: { ...state.desktop, agentListOpen: false, fileExplorerOpen: false },
};
}
const resolvedTab = resolveExplorerTabFromActiveCheckout(state);
const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout());
return {
mobileView: "agent" as MobilePanelView,
desktop: { ...state.desktop, agentListOpen: true, fileExplorerOpen: true },
...(resolvedTab ? { explorerTab: resolvedTab } : {}),
};
@@ -280,7 +300,6 @@ export const usePanelStore = create<PanelState>()(
})),
activateExplorerTabForCheckout: (checkout) =>
set((state) => ({
activeExplorerCheckout: checkout,
explorerTab: resolveExplorerTabForCheckout({
serverId: checkout.serverId,
cwd: checkout.cwd,
@@ -288,18 +307,6 @@ export const usePanelStore = create<PanelState>()(
explorerTabByCheckout: state.explorerTabByCheckout,
}),
})),
setActiveExplorerCheckout: (checkout) =>
set((state) => {
const current = state.activeExplorerCheckout;
if (
current?.serverId === checkout?.serverId &&
current?.cwd === checkout?.cwd &&
current?.isGit === checkout?.isGit
) {
return state;
}
return { activeExplorerCheckout: checkout };
}),
setSidebarWidth: (width) => set({ sidebarWidth: clampSidebarWidth(width) }),
setExplorerWidth: (width) => set({ explorerWidth: clampWidth(width) }),
setExplorerSortOption: (option) => set({ explorerSortOption: option }),
@@ -400,8 +407,6 @@ export const usePanelStore = create<PanelState>()(
state.diffExpandedPathsByWorkspace = {};
}
state.activeExplorerCheckout = null;
return state as PanelState;
},
partialize: (state) => ({
@@ -435,12 +440,12 @@ export function usePanelState(isMobile: boolean) {
return {
isAgentListOpen: store.mobileView === "agent-list",
isFileExplorerOpen: store.mobileView === "file-explorer",
openAgentList: store.openAgentList,
openFileExplorer: store.openFileExplorer,
closeAgentList: store.closeToAgent,
closeFileExplorer: store.closeToAgent,
toggleAgentList: store.toggleAgentList,
toggleFileExplorer: store.toggleFileExplorer,
openAgentList: store.showMobileAgentList,
openFileExplorer: store.showMobileFileExplorer,
closeAgentList: store.showMobileAgent,
closeFileExplorer: store.showMobileAgent,
toggleAgentList: store.toggleMobileAgentList,
toggleFileExplorer: store.toggleMobileFileExplorer,
// Explorer settings
explorerTab: store.explorerTab,
explorerTabByCheckout: store.explorerTabByCheckout,
@@ -450,7 +455,6 @@ export function usePanelState(isMobile: boolean) {
setExplorerTab: store.setExplorerTab,
setExplorerTabForCheckout: store.setExplorerTabForCheckout,
activateExplorerTabForCheckout: store.activateExplorerTabForCheckout,
setActiveExplorerCheckout: store.setActiveExplorerCheckout,
setExplorerWidth: store.setExplorerWidth,
setExplorerSortOption: store.setExplorerSortOption,
setExplorerFilesSplitRatio: store.setExplorerFilesSplitRatio,
@@ -461,18 +465,12 @@ export function usePanelState(isMobile: boolean) {
return {
isAgentListOpen: store.desktop.agentListOpen,
isFileExplorerOpen: store.desktop.fileExplorerOpen,
openAgentList: store.openAgentList,
openFileExplorer: store.openFileExplorer,
closeAgentList: () =>
usePanelStore.setState((state) => ({
desktop: { ...state.desktop, agentListOpen: false },
})),
closeFileExplorer: () =>
usePanelStore.setState((state) => ({
desktop: { ...state.desktop, fileExplorerOpen: false },
})),
toggleAgentList: store.toggleAgentList,
toggleFileExplorer: store.toggleFileExplorer,
openAgentList: store.openDesktopAgentList,
openFileExplorer: store.openDesktopFileExplorer,
closeAgentList: store.closeDesktopAgentList,
closeFileExplorer: store.closeDesktopFileExplorer,
toggleAgentList: store.toggleDesktopAgentList,
toggleFileExplorer: store.toggleDesktopFileExplorer,
// Explorer settings
explorerTab: store.explorerTab,
explorerTabByCheckout: store.explorerTabByCheckout,
@@ -482,7 +480,6 @@ export function usePanelState(isMobile: boolean) {
setExplorerTab: store.setExplorerTab,
setExplorerTabForCheckout: store.setExplorerTabForCheckout,
activateExplorerTabForCheckout: store.activateExplorerTabForCheckout,
setActiveExplorerCheckout: store.setActiveExplorerCheckout,
setExplorerWidth: store.setExplorerWidth,
setExplorerSortOption: store.setExplorerSortOption,
setExplorerFilesSplitRatio: store.setExplorerFilesSplitRatio,