From 9cef5496eb88b7b04e91d86cd64d9b4b38bbd07c Mon Sep 17 00:00:00 2001 From: alelpoan Date: Mon, 27 Jul 2026 16:02:45 +0300 Subject: [PATCH] fix(desktop): front workspace pane when navigating sidebar routes Capabilities/Messaging/Artifacts (and other full-page workspace routes) rendered their content correctly on navigate(), but the workspace pane itself stayed behind an active session tile in the pane tree if one was focused. Clicking the same sidebar item again after switching to a session tile appeared to do nothing. Session switches already call revealTreePane('workspace') + noteActiveTreeGroup(null) to front the pane (store/session-states.ts), but sidebar/keybind/command-palette navigation to full-page routes did not have an equivalent. Add navigateToWorkspacePage() helper in routes.ts that wraps navigate() and fronts the workspace pane for non-overlay, non-chat views. Apply it at all 5 call sites that navigate to full-page workspace routes: selectSidebarItem, use-keybinds (nav.skills/messaging/artifacts), command palette 'go', Command Center's onNavigateRoute, and cold-start route restore. Fixes #72602 --- .../desktop/src/app/command-palette/index.tsx | 3 ++- .../contrib/hooks/use-desktop-integrations.ts | 4 +-- apps/desktop/src/app/contrib/wiring.tsx | 3 ++- apps/desktop/src/app/hooks/use-keybinds.ts | 7 +++--- apps/desktop/src/app/routes.ts | 25 +++++++++++++++++++ .../hooks/use-session-actions/index.ts | 4 +-- 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/app/command-palette/index.tsx b/apps/desktop/src/app/command-palette/index.tsx index 42bcef4e820a..e86bd2415e39 100644 --- a/apps/desktop/src/app/command-palette/index.tsx +++ b/apps/desktop/src/app/command-palette/index.tsx @@ -71,6 +71,7 @@ import { COMMAND_CENTER_ROUTE, CRON_ROUTE, MESSAGING_ROUTE, + navigateToWorkspacePage, NEW_CHAT_ROUTE, PROFILES_ROUTE, sessionRoute, @@ -351,7 +352,7 @@ export function CommandPalette() { } }, [open, pendingPage]) - const go = useCallback((path: string) => () => navigate(path), [navigate]) + const go = useCallback((path: string) => () => navigateToWorkspacePage(navigate, path), [navigate]) // Step up one nested page (or back to the root list), clearing the filter so // the parent page doesn't reopen mid-search. diff --git a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts index ed22e2cc7ebe..5e6c75dee23a 100644 --- a/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts +++ b/apps/desktop/src/app/contrib/hooks/use-desktop-integrations.ts @@ -17,7 +17,7 @@ import { openUpdatesWindow, startUpdatePoller, stopUpdatePoller } from '@/store/ import { isSecondaryWindow } from '@/store/windows' import { requestComposerFocus, requestComposerInsert } from '../../chat/composer/focus' -import { appViewForPath, isOverlayView, NEW_CHAT_ROUTE, sessionRoute } from '../../routes' +import { appViewForPath, isOverlayView, navigateToWorkspacePage, NEW_CHAT_ROUTE, sessionRoute } from '../../routes' interface DesktopIntegrationsParams { chatOpen: boolean @@ -99,7 +99,7 @@ export function useDesktopIntegrations({ const route = getRememberedRoute() if (route && route !== NEW_CHAT_ROUTE && !isOverlayView(appViewForPath(route))) { - navigate(route, { replace: true }) + navigateToWorkspacePage(navigate, route, { replace: true }) return } diff --git a/apps/desktop/src/app/contrib/wiring.tsx b/apps/desktop/src/app/contrib/wiring.tsx index de2e0becffdb..8e02554b67f5 100644 --- a/apps/desktop/src/app/contrib/wiring.tsx +++ b/apps/desktop/src/app/contrib/wiring.tsx @@ -78,6 +78,7 @@ import { closeAllTerminals } from '../right-sidebar/terminal/terminals' import { $workspaceIsPage, CRON_ROUTE, + navigateToWorkspacePage, routeSessionId, sessionRoute, SETTINGS_ROUTE, @@ -1015,7 +1016,7 @@ export function ContribWiring({ children }: { children: ReactNode }) { initialSection={commandCenterInitialSection} onClose={closeOverlayToPreviousRoute} onDeleteSession={removeSession} - onNavigateRoute={path => navigate(path)} + onNavigateRoute={path => navigateToWorkspacePage(navigate, path)} onOpenSession={sessionId => navigate(sessionRoute(sessionId))} /> diff --git a/apps/desktop/src/app/hooks/use-keybinds.ts b/apps/desktop/src/app/hooks/use-keybinds.ts index 8a1fe275478e..7e49029d1cea 100644 --- a/apps/desktop/src/app/hooks/use-keybinds.ts +++ b/apps/desktop/src/app/hooks/use-keybinds.ts @@ -57,6 +57,7 @@ import { ARTIFACTS_ROUTE, CRON_ROUTE, MESSAGING_ROUTE, + navigateToWorkspacePage, PROFILES_ROUTE, sessionRoute, SETTINGS_ROUTE, @@ -138,9 +139,9 @@ export function useKeybinds(deps: KeybindRuntimeDeps): void { 'nav.commandCenter': deps.toggleCommandCenter, 'nav.settings': () => navigate(SETTINGS_ROUTE), 'nav.profiles': () => navigate(PROFILES_ROUTE), - 'nav.skills': () => navigate(SKILLS_ROUTE), - 'nav.messaging': () => navigate(MESSAGING_ROUTE), - 'nav.artifacts': () => navigate(ARTIFACTS_ROUTE), + 'nav.skills': () => navigateToWorkspacePage(navigate, SKILLS_ROUTE), + 'nav.messaging': () => navigateToWorkspacePage(navigate, MESSAGING_ROUTE), + 'nav.artifacts': () => navigateToWorkspacePage(navigate, ARTIFACTS_ROUTE), 'nav.cron': () => navigate(CRON_ROUTE), 'nav.agents': () => navigate(AGENTS_ROUTE), diff --git a/apps/desktop/src/app/routes.ts b/apps/desktop/src/app/routes.ts index b3c28a0ae017..e834d26b1907 100644 --- a/apps/desktop/src/app/routes.ts +++ b/apps/desktop/src/app/routes.ts @@ -1,8 +1,11 @@ import { atom } from 'nanostores' import type { ReactNode } from 'react' +import { noteActiveTreeGroup, revealTreePane } from '@/components/pane-shell/tree/store' import { registry } from '@/contrib/registry' +type NavigateLike = (to: string, options?: { replace?: boolean }) => void + export const SESSION_ROUTE_PREFIX = '/' export const NEW_CHAT_ROUTE = '/' export const SETTINGS_ROUTE = '/settings' @@ -195,3 +198,25 @@ export function syncWorkspaceIsPage(pathname: string): void { $workspaceIsPage.set(isPage) } } + +/** + * Navigate to `path`, and if it lands on a full page rendered inside the + * `workspace` pane (skills/messaging/artifacts/contributed routes) also + * front the `workspace` pane in the pane tree. + * + * Without this, `navigate()` alone updates the route and the content inside + * `workspace` correctly, but if a session tile is currently focused in the + * same tab group, `workspace` stays behind it (issue #72602). This mirrors + * what `$selectedStoredSessionId.listen` already does for session switches + * in `store/session-states.ts`. + */ +export function navigateToWorkspacePage(navigate: NavigateLike, path: string, options?: { replace?: boolean }): void { + navigate(path, options) + + const view = appViewForPath(path) + + if (view !== 'chat' && !isOverlayView(view)) { + noteActiveTreeGroup(null) + revealTreePane('workspace') + } +} diff --git a/apps/desktop/src/app/session/hooks/use-session-actions/index.ts b/apps/desktop/src/app/session/hooks/use-session-actions/index.ts index fd490666d126..5d293c012b21 100644 --- a/apps/desktop/src/app/session/hooks/use-session-actions/index.ts +++ b/apps/desktop/src/app/session/hooks/use-session-actions/index.ts @@ -68,7 +68,7 @@ import { broadcastSessionsChanged } from '@/store/session-sync' import { isWatchWindow } from '@/store/windows' import type { SessionCreateResponse, SessionMessage, SessionResumeResponse, UsageStats } from '@/types/hermes' -import { NEW_CHAT_ROUTE, sessionRoute, SETTINGS_ROUTE } from '../../../routes' +import { navigateToWorkspacePage, NEW_CHAT_ROUTE, sessionRoute, SETTINGS_ROUTE } from '../../../routes' import type { ClientSessionState, SidebarNavItem } from '../../../types' import { sessionContextDrift } from '../session-context-drift' @@ -458,7 +458,7 @@ export function useSessionActions({ } if (item.route) { - navigate(item.route) + navigateToWorkspacePage(navigate, item.route) } }, [navigate, startFreshSessionDraft]