Fix workspace route state syncing

This commit is contained in:
Mohamed Boudra
2026-03-18 21:11:00 +07:00
parent 09fae62888
commit 82ab598426
5 changed files with 57 additions and 25 deletions
@@ -1,16 +1,22 @@
import { useGlobalSearchParams, usePathname } from 'expo-router' import { useLocalSearchParams } from 'expo-router'
import { WorkspaceScreen } from '@/screens/workspace/workspace-screen' import { WorkspaceScreen } from '@/screens/workspace/workspace-screen'
import { import {
parseHostWorkspaceRouteFromPathname, decodeWorkspaceIdFromPathSegment,
parseWorkspaceOpenIntent, parseWorkspaceOpenIntent,
} from '@/utils/host-routes' } from '@/utils/host-routes'
export default function HostWorkspaceLayout() { export default function HostWorkspaceLayout() {
const expoPathname = usePathname() const params = useLocalSearchParams<{
const params = useGlobalSearchParams<{ open?: string | string[] }>() serverId?: string | string[]
const activeRoute = parseHostWorkspaceRouteFromPathname(expoPathname) workspaceId?: string | string[]
const serverId = activeRoute?.serverId ?? '' open?: string | string[]
const workspaceId = activeRoute?.workspaceId ?? '' }>()
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 openValue = Array.isArray(params.open) ? params.open[0] : params.open
const openIntent = parseWorkspaceOpenIntent(openValue) const openIntent = parseWorkspaceOpenIntent(openValue)
@@ -23,6 +23,7 @@ import Animated, {
withRepeat, withRepeat,
withTiming, withTiming,
} from "react-native-reanimated"; } from "react-native-reanimated";
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
import { Fonts } from "@/constants/theme"; import { Fonts } from "@/constants/theme";
import * as Clipboard from "expo-clipboard"; import * as Clipboard from "expo-clipboard";
import { import {
@@ -581,13 +582,12 @@ export function FileExplorerPane({
style={({ hovered, pressed }) => [ style={({ hovered, pressed }) => [
styles.iconButton, styles.iconButton,
(hovered || pressed) && styles.iconButtonHovered, (hovered || pressed) && styles.iconButtonHovered,
pressed && styles.iconButtonPressed,
]} ]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel="Refresh files" accessibilityLabel="Refresh files"
> >
<Animated.View style={[styles.refreshIcon, refreshIconAnimatedStyle]}> <Animated.View style={[styles.refreshIcon, refreshIconAnimatedStyle]}>
<RotateCw size={16} color={theme.colors.foregroundMuted} /> <RotateCw size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
</Animated.View> </Animated.View>
</Pressable> </Pressable>
<Pressable style={styles.sortButton} onPress={handleSortCycle}> <Pressable style={styles.sortButton} onPress={handleSortCycle}>
@@ -875,7 +875,7 @@ const styles = StyleSheet.create((theme) => ({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
height: 32 + theme.spacing[2] * 2, height: WORKSPACE_SECONDARY_HEADER_HEIGHT,
paddingHorizontal: theme.spacing[3], paddingHorizontal: theme.spacing[3],
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: theme.colors.border, borderBottomColor: theme.colors.border,
@@ -898,7 +898,7 @@ const styles = StyleSheet.create((theme) => ({
flexShrink: 0, flexShrink: 0,
}, },
sortButton: { sortButton: {
height: 32, height: 28,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@@ -1025,8 +1025,8 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: theme.fontWeight.normal, fontWeight: theme.fontWeight.normal,
}, },
iconButton: { iconButton: {
width: 32, width: 22,
height: 32, height: 22,
borderRadius: theme.borderRadius.md, borderRadius: theme.borderRadius.md,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@@ -1034,10 +1034,6 @@ const styles = StyleSheet.create((theme) => ({
iconButtonHovered: { iconButtonHovered: {
backgroundColor: theme.colors.surface2, backgroundColor: theme.colors.surface2,
}, },
iconButtonPressed: {
opacity: 0.8,
transform: [{ scale: 0.96 }],
},
refreshIcon: { refreshIcon: {
width: 16, width: 16,
height: 16, height: 16,
@@ -37,6 +37,7 @@ import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query";
import { useCheckoutPrStatusQuery } from "@/hooks/use-checkout-pr-status-query"; import { useCheckoutPrStatusQuery } from "@/hooks/use-checkout-pr-status-query";
import { useHorizontalScrollOptional } from "@/contexts/horizontal-scroll-context"; import { useHorizontalScrollOptional } from "@/contexts/horizontal-scroll-context";
import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context"; import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context";
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
import { Fonts } from "@/constants/theme"; import { Fonts } from "@/constants/theme";
import { shouldAnchorHeaderBeforeCollapse } from "@/utils/git-diff-scroll"; import { shouldAnchorHeaderBeforeCollapse } from "@/utils/git-diff-scroll";
import { import {
@@ -1086,11 +1087,12 @@ const styles = StyleSheet.create((theme) => ({
flexShrink: 1, flexShrink: 1,
}, },
diffStatusContainer: { diffStatusContainer: {
paddingVertical: 1.5, height: WORKSPACE_SECONDARY_HEADER_HEIGHT,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: theme.colors.border, borderBottomColor: theme.colors.border,
}, },
diffStatusInner: { diffStatusInner: {
flex: 1,
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
@@ -20,7 +20,7 @@ import {
type ReactElement, type ReactElement,
type MutableRefObject, type MutableRefObject,
} from 'react' } from 'react'
import { router, usePathname } from 'expo-router' import { router, useGlobalSearchParams, usePathname } from 'expo-router'
import { navigateToWorkspace } from '@/hooks/use-workspace-navigation' import { navigateToWorkspace } from '@/hooks/use-workspace-navigation'
import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles' import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles'
import { type GestureType } from 'react-native-gesture-handler' import { type GestureType } from 'react-native-gesture-handler'
@@ -35,8 +35,10 @@ import { projectIconQueryKey } from '@/hooks/use-project-icon-query'
import { import {
buildHostNewAgentRoute, buildHostNewAgentRoute,
buildHostWorkspaceRoute, buildHostWorkspaceRoute,
decodeWorkspaceIdFromPathSegment,
parseHostWorkspaceRouteFromPathname, parseHostWorkspaceRouteFromPathname,
} from '@/utils/host-routes' } from '@/utils/host-routes'
import { normalizeWorkspaceIdentity } from '@/utils/workspace-identity'
import { import {
type SidebarProjectEntry, type SidebarProjectEntry,
type SidebarWorkspaceEntry, type SidebarWorkspaceEntry,
@@ -1210,6 +1212,10 @@ export function SidebarWorkspaceList({
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm' const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
const isNative = Platform.OS !== 'web' const isNative = Platform.OS !== 'web'
const pathname = usePathname() const pathname = usePathname()
const routeParams = useGlobalSearchParams<{
serverId?: string | string[]
workspaceId?: string | string[]
}>()
const isTauri = getIsTauri() const isTauri = getIsTauri()
const altDown = useKeyboardShortcutsStore((state) => state.altDown) const altDown = useKeyboardShortcutsStore((state) => state.altDown)
const cmdOrCtrlDown = useKeyboardShortcutsStore((state) => state.cmdOrCtrlDown) const cmdOrCtrlDown = useKeyboardShortcutsStore((state) => state.cmdOrCtrlDown)
@@ -1224,15 +1230,36 @@ export function SidebarWorkspaceList({
if (!pathname) { if (!pathname) {
return null return null
} }
const parsed = parseHostWorkspaceRouteFromPathname(pathname) if (!parseHostWorkspaceRouteFromPathname(pathname)) {
if (!parsed) {
return null return null
} }
return {
serverId: parsed.serverId, const serverValue = Array.isArray(routeParams.serverId)
workspaceId: parsed.workspaceId, ? 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(() => { const projectIconRequests = useMemo(() => {
if (!serverId) { if (!serverId) {
+1
View File
@@ -8,6 +8,7 @@ export const FOOTER_HEIGHT = 75;
// This ensures both headers have the same visual height // This ensures both headers have the same visual height
export const HEADER_INNER_HEIGHT = 48; export const HEADER_INNER_HEIGHT = 48;
export const HEADER_INNER_HEIGHT_MOBILE = 56; export const HEADER_INNER_HEIGHT_MOBILE = 56;
export const WORKSPACE_SECONDARY_HEADER_HEIGHT = 36;
export const HEADER_TOP_PADDING_MOBILE = 8; export const HEADER_TOP_PADDING_MOBILE = 8;
// Max width for chat content (stream view, input area, new agent form) // Max width for chat content (stream view, input area, new agent form)