From 82ab5984262f413823889a295eeea666da0a3f3c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 18 Mar 2026 21:11:00 +0700 Subject: [PATCH] Fix workspace route state syncing --- .../workspace/[workspaceId]/_layout.tsx | 20 +++++---- .../app/src/components/file-explorer-pane.tsx | 16 +++----- packages/app/src/components/git-diff-pane.tsx | 4 +- .../src/components/sidebar-workspace-list.tsx | 41 +++++++++++++++---- packages/app/src/constants/layout.ts | 1 + 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx b/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx index 8b763cffb..d2c06c8c7 100644 --- a/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx +++ b/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx @@ -1,16 +1,22 @@ -import { useGlobalSearchParams, usePathname } from 'expo-router' +import { useLocalSearchParams } from 'expo-router' import { WorkspaceScreen } from '@/screens/workspace/workspace-screen' import { - parseHostWorkspaceRouteFromPathname, + decodeWorkspaceIdFromPathSegment, parseWorkspaceOpenIntent, } from '@/utils/host-routes' export default function HostWorkspaceLayout() { - const expoPathname = usePathname() - const params = useGlobalSearchParams<{ open?: string | string[] }>() - const activeRoute = parseHostWorkspaceRouteFromPathname(expoPathname) - const serverId = activeRoute?.serverId ?? '' - const workspaceId = activeRoute?.workspaceId ?? '' + const params = useLocalSearchParams<{ + serverId?: string | string[] + workspaceId?: string | string[] + open?: string | string[] + }>() + const serverValue = Array.isArray(params.serverId) ? params.serverId[0] : params.serverId + const workspaceValue = Array.isArray(params.workspaceId) + ? params.workspaceId[0] + : params.workspaceId + const serverId = serverValue?.trim() ?? '' + const workspaceId = workspaceValue ? (decodeWorkspaceIdFromPathSegment(workspaceValue) ?? '') : '' const openValue = Array.isArray(params.open) ? params.open[0] : params.open const openIntent = parseWorkspaceOpenIntent(openValue) diff --git a/packages/app/src/components/file-explorer-pane.tsx b/packages/app/src/components/file-explorer-pane.tsx index 2f6b4f870..14d15282f 100644 --- a/packages/app/src/components/file-explorer-pane.tsx +++ b/packages/app/src/components/file-explorer-pane.tsx @@ -23,6 +23,7 @@ import Animated, { withRepeat, withTiming, } from "react-native-reanimated"; +import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout"; import { Fonts } from "@/constants/theme"; import * as Clipboard from "expo-clipboard"; import { @@ -581,13 +582,12 @@ export function FileExplorerPane({ style={({ hovered, pressed }) => [ styles.iconButton, (hovered || pressed) && styles.iconButtonHovered, - pressed && styles.iconButtonPressed, ]} accessibilityRole="button" accessibilityLabel="Refresh files" > - + @@ -875,7 +875,7 @@ const styles = StyleSheet.create((theme) => ({ flexDirection: "row", alignItems: "center", justifyContent: "space-between", - height: 32 + theme.spacing[2] * 2, + height: WORKSPACE_SECONDARY_HEADER_HEIGHT, paddingHorizontal: theme.spacing[3], borderBottomWidth: 1, borderBottomColor: theme.colors.border, @@ -898,7 +898,7 @@ const styles = StyleSheet.create((theme) => ({ flexShrink: 0, }, sortButton: { - height: 32, + height: 28, flexDirection: "row", alignItems: "center", justifyContent: "center", @@ -1025,8 +1025,8 @@ const styles = StyleSheet.create((theme) => ({ fontWeight: theme.fontWeight.normal, }, iconButton: { - width: 32, - height: 32, + width: 22, + height: 22, borderRadius: theme.borderRadius.md, alignItems: "center", justifyContent: "center", @@ -1034,10 +1034,6 @@ const styles = StyleSheet.create((theme) => ({ iconButtonHovered: { backgroundColor: theme.colors.surface2, }, - iconButtonPressed: { - opacity: 0.8, - transform: [{ scale: 0.96 }], - }, refreshIcon: { width: 16, height: 16, diff --git a/packages/app/src/components/git-diff-pane.tsx b/packages/app/src/components/git-diff-pane.tsx index e157ca86b..ecd1154cc 100644 --- a/packages/app/src/components/git-diff-pane.tsx +++ b/packages/app/src/components/git-diff-pane.tsx @@ -37,6 +37,7 @@ import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query"; import { useCheckoutPrStatusQuery } from "@/hooks/use-checkout-pr-status-query"; import { useHorizontalScrollOptional } from "@/contexts/horizontal-scroll-context"; import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context"; +import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout"; import { Fonts } from "@/constants/theme"; import { shouldAnchorHeaderBeforeCollapse } from "@/utils/git-diff-scroll"; import { @@ -1086,11 +1087,12 @@ const styles = StyleSheet.create((theme) => ({ flexShrink: 1, }, diffStatusContainer: { - paddingVertical: 1.5, + height: WORKSPACE_SECONDARY_HEADER_HEIGHT, borderBottomWidth: 1, borderBottomColor: theme.colors.border, }, diffStatusInner: { + flex: 1, flexDirection: "row", alignItems: "center", justifyContent: "space-between", diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx index 4cefdf9f6..a128b0136 100644 --- a/packages/app/src/components/sidebar-workspace-list.tsx +++ b/packages/app/src/components/sidebar-workspace-list.tsx @@ -20,7 +20,7 @@ import { type ReactElement, type MutableRefObject, } from 'react' -import { router, usePathname } from 'expo-router' +import { router, useGlobalSearchParams, usePathname } from 'expo-router' import { navigateToWorkspace } from '@/hooks/use-workspace-navigation' import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles' import { type GestureType } from 'react-native-gesture-handler' @@ -35,8 +35,10 @@ import { projectIconQueryKey } from '@/hooks/use-project-icon-query' import { buildHostNewAgentRoute, buildHostWorkspaceRoute, + decodeWorkspaceIdFromPathSegment, parseHostWorkspaceRouteFromPathname, } from '@/utils/host-routes' +import { normalizeWorkspaceIdentity } from '@/utils/workspace-identity' import { type SidebarProjectEntry, type SidebarWorkspaceEntry, @@ -1210,6 +1212,10 @@ export function SidebarWorkspaceList({ const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm' const isNative = Platform.OS !== 'web' const pathname = usePathname() + const routeParams = useGlobalSearchParams<{ + serverId?: string | string[] + workspaceId?: string | string[] + }>() const isTauri = getIsTauri() const altDown = useKeyboardShortcutsStore((state) => state.altDown) const cmdOrCtrlDown = useKeyboardShortcutsStore((state) => state.cmdOrCtrlDown) @@ -1224,15 +1230,36 @@ export function SidebarWorkspaceList({ if (!pathname) { return null } - const parsed = parseHostWorkspaceRouteFromPathname(pathname) - if (!parsed) { + if (!parseHostWorkspaceRouteFromPathname(pathname)) { return null } - return { - serverId: parsed.serverId, - workspaceId: parsed.workspaceId, + + const serverValue = Array.isArray(routeParams.serverId) + ? routeParams.serverId[0] + : routeParams.serverId + const workspaceValue = Array.isArray(routeParams.workspaceId) + ? routeParams.workspaceId[0] + : routeParams.workspaceId + const serverId = serverValue?.trim() ?? '' + const workspaceId = normalizeWorkspaceIdentity( + workspaceValue ? decodeWorkspaceIdFromPathSegment(workspaceValue) : null + ) + if (!serverId || !workspaceId) { + const parsed = parseHostWorkspaceRouteFromPathname(pathname) + if (!parsed) { + return null + } + return { + serverId: parsed.serverId, + workspaceId: parsed.workspaceId, + } } - }, [pathname]) + + return { + serverId, + workspaceId, + } + }, [pathname, routeParams.serverId, routeParams.workspaceId]) const projectIconRequests = useMemo(() => { if (!serverId) { diff --git a/packages/app/src/constants/layout.ts b/packages/app/src/constants/layout.ts index aca0be463..7a8ac7833 100644 --- a/packages/app/src/constants/layout.ts +++ b/packages/app/src/constants/layout.ts @@ -8,6 +8,7 @@ export const FOOTER_HEIGHT = 75; // This ensures both headers have the same visual height export const HEADER_INNER_HEIGHT = 48; export const HEADER_INNER_HEIGHT_MOBILE = 56; +export const WORKSPACE_SECONDARY_HEADER_HEIGHT = 36; export const HEADER_TOP_PADDING_MOBILE = 8; // Max width for chat content (stream view, input area, new agent form)