From e68fc4def2baaa38e49b04c5646bee35167bc21d Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 3 Jun 2026 22:30:47 -0500 Subject: [PATCH] feat(desktop): titlebar toggle to flip sidebar sides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a top-left swap button (replacing the search icon) that mirrors the layout: sessions sidebar ↔ file browser + preview rail. Persisted via $panesFlipped. The left/right sidebar toggles, content inset, and pane borders all follow the active side so the buttons stay accurate after a flip. --- apps/desktop/src/app/chat/sidebar/index.tsx | 5 +- apps/desktop/src/app/desktop-controller.tsx | 102 ++++++++++++------ apps/desktop/src/app/right-sidebar/index.tsx | 9 +- apps/desktop/src/app/shell/app-shell.tsx | 20 ++-- .../src/app/shell/titlebar-controls.tsx | 54 ++++++---- apps/desktop/src/store/layout.ts | 9 ++ 6 files changed, 131 insertions(+), 68 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx index 2bffdd8d2..bee1feb6b 100644 --- a/apps/desktop/src/app/chat/sidebar/index.tsx +++ b/apps/desktop/src/app/chat/sidebar/index.tsx @@ -36,6 +36,7 @@ import { Skeleton } from '@/components/ui/skeleton' import { searchSessions, type SessionInfo, type SessionSearchResult } from '@/hermes' import { cn } from '@/lib/utils' import { + $panesFlipped, $pinnedSessionIds, $sidebarAgentsGrouped, $sidebarOpen, @@ -214,6 +215,7 @@ export function ChatSidebar({ onNewSessionInWorkspace }: ChatSidebarProps) { const sidebarOpen = useStore($sidebarOpen) + const panesFlipped = useStore($panesFlipped) const agentsGrouped = useStore($sidebarAgentsGrouped) const pinnedSessionIds = useStore($pinnedSessionIds) const pinsOpen = useStore($sidebarPinsOpen) @@ -406,7 +408,8 @@ export function ChatSidebar({ return ( { + const onKeyDown = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === 'k') { + event.preventDefault() + toggleCommandPalette() + } + } + + window.addEventListener('keydown', onKeyDown) + + return () => window.removeEventListener('keydown', onKeyDown) + }, []) + const refreshSessions = useCallback(async () => { const requestId = refreshSessionsRequestRef.current + 1 refreshSessionsRequestRef.current = requestId @@ -564,6 +583,7 @@ export function DesktopController() { + {settingsOpen && ( @@ -641,12 +661,52 @@ export function DesktopController() { ) + // Flipped layout mirrors the default: sessions sidebar → right, file + // browser + preview rail → left. Same panes, swapped sides. + const sidebarSide = panesFlipped ? 'right' : 'left' + const railSide = panesFlipped ? 'left' : 'right' + + const previewPane = ( + + {chatOpen ? ( + + ) : null} + + ) + + const fileBrowserPane = ( + + + + ) + return ( openCommandCenterSection('sessions')} onOpenSettings={openSettings} overlays={overlays} statusbarItems={statusbarItems} @@ -658,7 +718,7 @@ export function DesktopController() { maxWidth={SIDEBAR_MAX_WIDTH} minWidth={SIDEBAR_DEFAULT_WIDTH} resizable - side="left" + side={sidebarSide} width={`${SIDEBAR_DEFAULT_WIDTH}px`} > {sidebar} @@ -718,35 +778,13 @@ export function DesktopController() { } path="*" /> - - {chatOpen ? ( - - ) : null} - - - - + {/* + Order within a side maps to column order. Default (rail on the right): + main | preview | file-browser. Flipped (rail on the left): mirror it to + file-browser | preview | main so preview stays adjacent to the chat. + */} + {panesFlipped ? fileBrowserPane : previewPane} + {panesFlipped ? previewPane : fileBrowserPane} ) } diff --git a/apps/desktop/src/app/right-sidebar/index.tsx b/apps/desktop/src/app/right-sidebar/index.tsx index 8e46aeb67..9acb93e35 100644 --- a/apps/desktop/src/app/right-sidebar/index.tsx +++ b/apps/desktop/src/app/right-sidebar/index.tsx @@ -7,6 +7,7 @@ import { Codicon } from '@/components/ui/codicon' import { Loader } from '@/components/ui/loader' import { normalizeOrLocalPreviewTarget } from '@/lib/local-preview' import { cn } from '@/lib/utils' +import { $panesFlipped } from '@/store/layout' import { notifyError } from '@/store/notifications' import { setCurrentSessionPreviewTarget } from '@/store/preview' import { $currentBranch, $currentCwd } from '@/store/session' @@ -38,6 +39,7 @@ const RIGHT_SIDEBAR_TABS: readonly RightSidebarTab[] = [ export function RightSidebarPane({ onActivateFile, onActivateFolder, onChangeCwd }: RightSidebarPaneProps) { const activeTab = useStore($rightSidebarTab) const terminalTakeover = useStore($terminalTakeover) + const panesFlipped = useStore($panesFlipped) const currentBranch = useStore($currentBranch).trim() const currentCwd = useStore($currentCwd).trim() const hasCwd = currentCwd.length > 0 @@ -96,7 +98,12 @@ export function RightSidebarPane({ onActivateFile, onActivateFolder, onChangeCwd return (