Resolve host new-worktree route action before commit (#3242)

This commit is contained in:
Neil 2026-05-30 19:15:42 -07:00 committed by GitHub
parent 67f74a29a8
commit c082f21d2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 106 additions and 6 deletions

View File

@ -62,6 +62,11 @@ import {
loadPreferences,
savePreferences
} from '../../../src/storage/preferences'
import {
createInitialHostRouteActionState,
resolveHostRouteActionState,
setHostRouteNewWorktreeVisible
} from '../../../src/host-route-action-state'
// Why: locally-typed subset of the desktop's RuntimeStatus we read from
// `status.get`. Only the version fields matter to mobile today; everything
@ -340,7 +345,9 @@ export default function HostScreen() {
const [actionTarget, setActionTarget] = useState<Worktree | null>(null)
const [confirmDelete, setConfirmDelete] = useState<Worktree | null>(null)
const [confirmRemoveHost, setConfirmRemoveHost] = useState(false)
const [showNewWorktree, setShowNewWorktree] = useState(false)
const [routeActionState, setRouteActionState] = useState(() =>
createInitialHostRouteActionState(action)
)
const [sleptIds, setSleptIds] = useState<Set<string>>(new Set())
// Persisted pin state
@ -348,9 +355,16 @@ export default function HostScreen() {
const [_prefsLoaded, setPrefsLoaded] = useState(false)
const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set())
useEffect(() => {
if (action === 'newWorktree') setShowNewWorktree(true)
}, [action])
const resolvedRouteActionState = resolveHostRouteActionState(routeActionState, action)
// Why: `action=newWorktree` is a route-derived open edge. Resolve it before
// commit, but don't reopen after the user closes while the same URL remains.
if (resolvedRouteActionState !== routeActionState) {
setRouteActionState(resolvedRouteActionState)
}
const showNewWorktree = resolvedRouteActionState.showNewWorktree
const setShowNewWorktreeVisible = useCallback((visible: boolean) => {
setRouteActionState((current) => setHostRouteNewWorktreeVisible(current, visible))
}, [])
// Load persisted pins and preferences
useEffect(() => {
@ -871,7 +885,7 @@ export default function HostScreen() {
<Pressable
style={styles.newButton}
onPress={() => setShowNewWorktree(true)}
onPress={() => setShowNewWorktreeVisible(true)}
disabled={connState !== 'connected'}
>
<Plus
@ -1246,7 +1260,7 @@ export default function HostScreen() {
const params = new URLSearchParams({ name: worktreeName, created: '1' })
router.push(`/h/${hostId}/session/${encodeURIComponent(worktreeId)}?${params.toString()}`)
}}
onClose={() => setShowNewWorktree(false)}
onClose={() => setShowNewWorktreeVisible(false)}
/>
</SafeAreaView>
)

View File

@ -0,0 +1,50 @@
import { describe, expect, it } from 'vitest'
import {
createInitialHostRouteActionState,
resolveHostRouteActionState,
setHostRouteNewWorktreeVisible
} from './host-route-action-state'
describe('host route action state', () => {
it('opens new worktree modal on an initial newWorktree action', () => {
expect(createInitialHostRouteActionState('newWorktree')).toEqual({
routeAction: 'newWorktree',
showNewWorktree: true
})
})
it('keeps initial non-action routes closed', () => {
expect(createInitialHostRouteActionState(undefined)).toEqual({
routeAction: undefined,
showNewWorktree: false
})
})
it('opens once when route action changes to newWorktree', () => {
expect(
resolveHostRouteActionState({ routeAction: undefined, showNewWorktree: false }, 'newWorktree')
).toEqual({
routeAction: 'newWorktree',
showNewWorktree: true
})
})
it('does not reopen after user closes while route action is unchanged', () => {
const closed = setHostRouteNewWorktreeVisible(
{ routeAction: 'newWorktree', showNewWorktree: true },
false
)
expect(resolveHostRouteActionState(closed, 'newWorktree')).toBe(closed)
})
it('preserves an already-open modal when route action changes away', () => {
expect(
resolveHostRouteActionState({ routeAction: 'newWorktree', showNewWorktree: true }, undefined)
).toEqual({
routeAction: undefined,
showNewWorktree: true
})
})
})

View File

@ -0,0 +1,36 @@
export type HostRouteActionState = {
routeAction: string | undefined
showNewWorktree: boolean
}
export function createInitialHostRouteActionState(
routeAction: string | undefined
): HostRouteActionState {
return {
routeAction,
showNewWorktree: routeAction === 'newWorktree'
}
}
export function resolveHostRouteActionState(
current: HostRouteActionState,
routeAction: string | undefined
): HostRouteActionState {
if (current.routeAction === routeAction) {
return current
}
return {
routeAction,
showNewWorktree: current.showNewWorktree || routeAction === 'newWorktree'
}
}
export function setHostRouteNewWorktreeVisible(
current: HostRouteActionState,
showNewWorktree: boolean
): HostRouteActionState {
if (current.showNewWorktree === showNewWorktree) {
return current
}
return { ...current, showNewWorktree }
}