fix(mobile): offer only the repos the desktop's New Workspace offers

The sheet now builds its lists from the desktop's own eligibility rule, so a server's
per-workspace-environment SSH repo never appears or wins a server's row. A connect reply that
arrives after the picked target changed no longer overwrites the newly shown target's state.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
Jinwoo-H
2026-10-11 22:52:22 -04:00
parent 1e7437a82d
commit 3b414b8842
6 changed files with 423 additions and 175 deletions
@@ -0,0 +1,414 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
const asyncStorage = vi.hoisted(() => ({
getItem: vi.fn().mockResolvedValue(null),
setItem: vi.fn().mockResolvedValue(undefined),
removeItem: vi.fn().mockResolvedValue(undefined)
}))
vi.mock('@react-native-async-storage/async-storage', () => ({ default: asyncStorage }))
vi.mock('react-native', () => ({
ActivityIndicator: 'ActivityIndicator',
AppState: { currentState: 'active' },
FlatList: 'FlatList',
Image: 'Image',
Keyboard: { dismiss: vi.fn() },
Linking: { openURL: vi.fn() },
Modal: 'Modal',
ScrollView: 'ScrollView',
Platform: { OS: 'ios', select: (options: { ios?: unknown }) => options.ios },
Pressable: 'Pressable',
StyleSheet: { create: <T,>(styles: T) => styles },
Switch: 'Switch',
Text: 'Text',
TextInput: 'TextInput',
View: 'View'
}))
// Every icon in the drawer tree renders as a host element named after itself.
vi.mock(
'lucide-react-native',
() =>
new Proxy(
{},
{
get: (_target, name) => (typeof name === 'string' ? name : undefined),
has: () => true
}
)
)
vi.mock('./BottomDrawer', () => ({ BottomDrawer: 'BottomDrawer' }))
vi.mock('./bottom-drawer-modal-host', () => ({ BottomDrawerModalHost: 'BottomDrawerModalHost' }))
vi.mock('./PickerListDrawer', () => ({ PickerListDrawer: 'PickerListDrawer' }))
vi.mock('./MobileAgentIcon', () => ({ MobileAgentIcon: 'MobileAgentIcon' }))
vi.mock('./TaskProviderLogo', () => ({ TaskProviderLogo: 'TaskProviderLogo' }))
import { recordHostDescriptor } from '../transport/host-descriptor-store'
import {
getLocalExecutionHostLabel,
type ExecutionHostId
} from '../../../src/shared/execution-host'
import { NewWorktreeModal } from './NewWorktreeModal'
const LOCAL_HOST_LABEL = getLocalExecutionHostLabel('darwin')
const repos = [
{
id: 'repo-1',
displayName: 'orca',
path: '/src/orca',
kind: 'git',
upstream: { owner: 'stablyai', repo: 'orca' }
}
]
function pickerItems(
renderer: ReactTestRenderer,
title: string
): { label: string; detail: string }[] {
const pickers = renderer.root.findAll((node) => isHost(node.type, 'PickerListDrawer'))
const picker = pickers.find((node) => node.props.title === title)
return picker?.props.items ?? []
}
function sourceInputs(renderer: ReactTestRenderer) {
return renderer.root.findAll(
(node) =>
isHost(node.type, 'TextInput') && node.props.placeholder === 'Type a name or search a source'
)
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the modal only sends requests.
const requestsOnly = (sendRequest: unknown) => ({ sendRequest }) as unknown as RpcClient
function runTargetPicker(renderer: ReactTestRenderer) {
return renderer.root
.findAll((node) => isHost(node.type, 'PickerListDrawer'))
.find((node) => node.props.title === 'Run on')!
}
/** Every element the mocks render is a host string, which `ElementType` does not admit as a literal. */
function isHost(type: unknown, name: string): boolean {
return type === name
}
function serversOf(client: RpcClient): ReadonlyMap<ExecutionHostId, RpcClient> {
return new Map([['runtime:env-1', client]])
}
async function flushUpdates(): Promise<void> {
await act(async () => {
await Promise.resolve()
await Promise.resolve()
await Promise.resolve()
})
}
describe('NewWorktreeModal on a server SSH repo', () => {
let renderer: ReactTestRenderer
afterEach(() => {
act(() => renderer?.unmount())
})
it("connects and creates on a server's own SSH repo through that server", async () => {
const serverSshRepo = {
...repos[0]!,
id: 'server-ssh',
path: '/home/dev/orca',
connectionId: 'devbox'
}
const sshState = (status: string) =>
Promise.resolve({
ok: true,
result: { state: { targetId: 'devbox', status, error: null, reconnectAttempt: 0 } }
})
const reply = (method: string, listed: unknown[], hostPlatform: string) => {
if (method === 'repo.list') {
return Promise.resolve({ ok: true, result: { repos: listed } })
}
if (method === 'status.get') {
return Promise.resolve({ ok: true, result: { hostPlatform } })
}
if (method === 'ssh.getState') {
return sshState('disconnected')
}
if (method === 'ssh.connect') {
return sshState('connected')
}
if (method === 'worktree.create') {
return Promise.resolve({
ok: true,
result: { worktree: { id: 'ssh-wt', displayName: 'ssh-wt', path: '/home/dev/ssh-wt' } }
})
}
if (method === 'settings.get') {
return Promise.resolve({ ok: true, result: { settings: {} } })
}
return Promise.resolve({ ok: true, result: {} })
}
const desktop = vi.fn((method: string) => reply(method, repos, 'darwin'))
const server = vi.fn((method: string) => reply(method, [serverSshRepo], 'linux'))
const onCreated = vi.fn()
recordHostDescriptor('host-1', { machineName: null, platform: 'darwin' })
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(desktop),
serverClients: serversOf(requestsOnly(server)),
hostId: 'host-1',
hostLabelById: new Map<ExecutionHostId, string>([['runtime:env-1', 'scratch-server']]),
onCreated,
openExternalUrl: () => {},
onClose: () => {}
})
)
})
await flushUpdates()
expect(pickerItems(renderer, 'Run on').map((item) => [item.label, item.detail])).toEqual([
[LOCAL_HOST_LABEL, '/src/orca'],
['scratch-server', '/home/dev/orca']
])
const picker = runTargetPicker(renderer)
await act(async () => picker.props.onSelect(picker.props.items[1]))
await flushUpdates()
expect(server).toHaveBeenCalledWith('ssh.getState', { targetId: 'devbox' })
const canCreate = () =>
renderer.root.find((node) => typeof node.props.onCreate === 'function').props.canCreate
act(() => sourceInputs(renderer)[0]!.props.onChangeText('ssh-wt'))
expect(canCreate()).toBe(false)
const connectable = renderer.root.find((node) => typeof node.props.onConnect === 'function')
await act(async () => connectable.props.onConnect())
await flushUpdates()
expect(canCreate()).toBe(true)
expect(server).toHaveBeenCalledWith(
'ssh.connect',
{ targetId: 'devbox' },
expect.objectContaining({ timeoutMs: 120_000 })
)
expect(server).toHaveBeenCalledWith('preflight.detectRemoteAgents', { connectionId: 'devbox' })
const form = renderer.root.find((node) => typeof node.props.onCreate === 'function')
await act(async () => form.props.onCreate())
await flushUpdates()
expect(server).toHaveBeenCalledWith(
'worktree.create',
expect.objectContaining({ repo: 'id:server-ssh' }),
expect.anything()
)
expect(onCreated).toHaveBeenCalledWith('ssh-wt', expect.any(String), undefined, 'runtime:env-1')
// The server holds this SSH target, so the desktop is never asked about it.
const desktopMethods = desktop.mock.calls.map(([method]) => method)
expect(desktopMethods.filter((method) => method.startsWith('ssh.'))).toEqual([])
expect(desktopMethods).not.toContain('preflight.detectRemoteAgents')
expect(desktopMethods).not.toContain('worktree.create')
})
it("never shows the desktop's SSH state for a server target with the same id", async () => {
const sshRepo = { ...repos[0]!, id: 'ssh-repo', path: '/home/dev/orca', connectionId: 'devbox' }
const desktop = vi.fn((method: string) => {
if (method === 'repo.list') {
return Promise.resolve({ ok: true, result: { repos: [sshRepo] } })
}
if (method === 'ssh.getState') {
return Promise.resolve({
ok: true,
result: {
state: { targetId: 'devbox', status: 'connected', error: null, reconnectAttempt: 0 }
}
})
}
if (method === 'preflight.detectRemoteAgents') {
return Promise.resolve({ ok: true, result: ['codex'] })
}
return new Promise(() => {})
})
// The server has not answered yet, so it has no state to show.
const server = vi.fn((method: string) =>
method === 'repo.list'
? Promise.resolve({ ok: true, result: { repos: [{ ...sshRepo, path: '/srv/orca' }] } })
: new Promise(() => {})
)
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(desktop),
serverClients: serversOf(requestsOnly(server)),
hostId: 'host-same-ssh-id',
onCreated: () => {},
openExternalUrl: () => {},
onClose: () => {}
})
)
})
await flushUpdates()
const gate = () => renderer.root.find((node) => node.props.sshGate !== undefined).props.sshGate
const picker = runTargetPicker(renderer)
expect(picker.props.items.map((item: { id: string }) => item.id)).toEqual([
'ssh:devbox',
'runtime:env-1'
])
await act(async () => picker.props.onSelect(picker.props.items[0]))
await flushUpdates()
expect(gate().status).toBe('connected')
await act(async () => runTargetPicker(renderer).props.onSelect(picker.props.items[1]))
await flushUpdates()
expect(runTargetPicker(renderer).props.selectedId).toBe('runtime:env-1')
expect(server).toHaveBeenCalledWith('ssh.getState', { targetId: 'devbox' })
expect(gate()).toMatchObject({ status: null, requiresConnection: true })
expect(server).not.toHaveBeenCalledWith('worktree.create', expect.anything(), expect.anything())
})
it("never offers a server's per-workspace-environment SSH repo, even last visited", async () => {
const vmRepo = {
...repos[0]!,
id: 'vm-repo',
path: '/home/dev/vm-orca',
connectionId: 'runtime-ssh-vm-1'
}
const serverRepo = { ...repos[0]!, id: 'server-repo', path: '/srv/orca' }
asyncStorage.getItem.mockResolvedValueOnce(
JSON.stringify({ hostId: 'host-vm', worktreeId: 'vm-repo::/home/dev/vm-orca/wt' })
)
const listing = (listed: unknown[]) =>
vi.fn((method: string) =>
method === 'repo.list'
? Promise.resolve({ ok: true, result: { repos: listed } })
: new Promise(() => {})
)
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(listing([])),
// Listed first, it would otherwise win the server's row.
serverClients: serversOf(requestsOnly(listing([vmRepo, serverRepo]))),
hostId: 'host-vm',
onCreated: () => {},
openExternalUrl: () => {},
onClose: () => {}
})
)
})
await flushUpdates()
expect(pickerItems(renderer, 'Run on').map((item) => item.detail)).toEqual(['/srv/orca'])
expect(
renderer.root.find((node) => node.props.runTarget !== undefined).props.runTarget?.detail
).toBe('/srv/orca')
})
it("gives a server's row to the first of its copies the server lists", async () => {
const sshCopy = { ...repos[0]!, id: 'ssh-copy', path: '/home/dev/orca', connectionId: 'devbox' }
const localCopy = { ...repos[0]!, id: 'local-copy', path: '/srv/orca' }
const listing = (listed: unknown[]) =>
vi.fn((method: string) =>
method === 'repo.list'
? Promise.resolve({ ok: true, result: { repos: listed } })
: new Promise(() => {})
)
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(listing(repos)),
serverClients: serversOf(requestsOnly(listing([sshCopy, localCopy]))),
hostId: 'host-1',
onCreated: () => {},
openExternalUrl: () => {},
onClose: () => {}
})
)
})
await flushUpdates()
expect(pickerItems(renderer, 'Run on').map((item) => item.detail)).toEqual([
'/src/orca',
'/home/dev/orca'
])
})
it("drops a connect reply that arrives after the server's target changed", async () => {
const sshRepo = (id: string, connectionId: string) => ({
...repos[0]!,
id,
displayName: id,
path: `/home/dev/${id}`,
upstream: { owner: 'stablyai', repo: id },
connectionId
})
const state = (targetId: string, status: string) => ({
ok: true,
result: { state: { targetId, status, error: null, reconnectAttempt: 0 } }
})
let finishConnect: ((reply: unknown) => void) | undefined
const server = vi.fn((method: string, params?: { targetId?: string }) => {
if (method === 'repo.list') {
return Promise.resolve({
ok: true,
result: { repos: [sshRepo('alpha', 'box-a'), sshRepo('beta', 'box-b')] }
})
}
if (method === 'ssh.getState') {
return Promise.resolve(state(params!.targetId!, 'disconnected'))
}
if (method === 'ssh.connect') {
return new Promise((resolve) => {
finishConnect = resolve
})
}
return new Promise(() => {})
})
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(vi.fn(() => new Promise(() => {}))),
serverClients: serversOf(requestsOnly(server)),
hostId: 'host-late-connect',
onCreated: () => {},
openExternalUrl: () => {},
onClose: () => {}
})
)
})
await flushUpdates()
const projects = () =>
renderer.root
.findAll((node) => isHost(node.type, 'PickerListDrawer'))
.find((node) => node.props.title === 'Project')!
const pickProject = async (label: string) => {
const picker = projects()
await act(async () =>
picker.props.onSelect(
picker.props.items.find((item: { label: string }) => item.label === label)
)
)
await flushUpdates()
}
const gate = () => renderer.root.find((node) => node.props.sshGate !== undefined).props.sshGate
await pickProject('alpha')
const connectable = renderer.root.find((node) => typeof node.props.onConnect === 'function')
await act(async () => connectable.props.onConnect())
await pickProject('beta')
expect(gate().status).toBe('disconnected')
await act(async () => finishConnect!(state('box-a', 'connected')))
await flushUpdates()
expect(gate()).toMatchObject({ status: 'disconnected', requiresConnection: true })
})
})
@@ -402,157 +402,6 @@ describe('NewWorktreeModal project targets', () => {
expect(onCreated).toHaveBeenCalledWith('srv-wt', expect.any(String), undefined, 'runtime:env-1')
})
it("connects and creates on a server's own SSH repo through that server", async () => {
const serverSshRepo = {
...repos[0]!,
id: 'server-ssh',
path: '/home/dev/orca',
connectionId: 'devbox'
}
const sshState = (status: string) =>
Promise.resolve({
ok: true,
result: { state: { targetId: 'devbox', status, error: null, reconnectAttempt: 0 } }
})
const reply = (method: string, listed: unknown[], hostPlatform: string) => {
if (method === 'repo.list') {
return Promise.resolve({ ok: true, result: { repos: listed } })
}
if (method === 'status.get') {
return Promise.resolve({ ok: true, result: { hostPlatform } })
}
if (method === 'ssh.getState') {
return sshState('disconnected')
}
if (method === 'ssh.connect') {
return sshState('connected')
}
if (method === 'worktree.create') {
return Promise.resolve({
ok: true,
result: { worktree: { id: 'ssh-wt', displayName: 'ssh-wt', path: '/home/dev/ssh-wt' } }
})
}
if (method === 'settings.get') {
return Promise.resolve({ ok: true, result: { settings: {} } })
}
return Promise.resolve({ ok: true, result: {} })
}
const desktop = vi.fn((method: string) => reply(method, repos, 'darwin'))
const server = vi.fn((method: string) => reply(method, [serverSshRepo], 'linux'))
const onCreated = vi.fn()
recordHostDescriptor('host-1', { machineName: null, platform: 'darwin' })
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(desktop),
serverClients: new Map([['runtime:env-1', requestsOnly(server)]]),
hostId: 'host-1',
hostLabelById: new Map([['runtime:env-1', 'scratch-server']]),
onCreated,
onClose: () => {}
})
)
})
await flushUpdates()
expect(pickerItems(renderer, 'Run on').map((item) => [item.label, item.detail])).toEqual([
[LOCAL_HOST_LABEL, '/src/orca'],
['scratch-server', '/home/dev/orca']
])
const picker = runTargetPicker(renderer)
await act(async () => picker.props.onSelect(picker.props.items[1]))
await flushUpdates()
expect(server).toHaveBeenCalledWith('ssh.getState', { targetId: 'devbox' })
const connectable = renderer.root.find((node) => typeof node.props.onConnect === 'function')
await act(async () => connectable.props.onConnect())
await flushUpdates()
expect(server).toHaveBeenCalledWith(
'ssh.connect',
{ targetId: 'devbox' },
expect.objectContaining({ timeoutMs: 120_000 })
)
expect(server).toHaveBeenCalledWith('preflight.detectRemoteAgents', { connectionId: 'devbox' })
act(() => sourceInputs(renderer)[0]!.props.onChangeText('ssh-wt'))
const form = renderer.root.find((node) => typeof node.props.onCreate === 'function')
await act(async () => form.props.onCreate())
await flushUpdates()
expect(server).toHaveBeenCalledWith(
'worktree.create',
expect.objectContaining({ repo: 'id:server-ssh' }),
expect.anything()
)
expect(onCreated).toHaveBeenCalledWith('ssh-wt', expect.any(String), undefined, 'runtime:env-1')
// The server holds this SSH target, so the desktop is never asked about it.
const desktopMethods = desktop.mock.calls.map(([method]) => method)
expect(desktopMethods.filter((method) => method.startsWith('ssh.'))).toEqual([])
expect(desktopMethods).not.toContain('preflight.detectRemoteAgents')
expect(desktopMethods).not.toContain('worktree.create')
})
it("never shows the desktop's SSH state for a server target with the same id", async () => {
const sshRepo = { ...repos[0]!, id: 'ssh-repo', path: '/home/dev/orca', connectionId: 'devbox' }
const desktop = vi.fn((method: string) => {
if (method === 'repo.list') {
return Promise.resolve({ ok: true, result: { repos: [sshRepo] } })
}
if (method === 'ssh.getState') {
return Promise.resolve({
ok: true,
result: {
state: { targetId: 'devbox', status: 'connected', error: null, reconnectAttempt: 0 }
}
})
}
if (method === 'preflight.detectRemoteAgents') {
return Promise.resolve({ ok: true, result: ['codex'] })
}
return new Promise(() => {})
})
// The server has not answered yet, so it has no state to show.
const server = vi.fn((method: string) =>
method === 'repo.list'
? Promise.resolve({ ok: true, result: { repos: [{ ...sshRepo, path: '/srv/orca' }] } })
: new Promise(() => {})
)
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(desktop),
serverClients: new Map([['runtime:env-1', requestsOnly(server)]]),
hostId: 'host-same-ssh-id',
onCreated: () => {},
onClose: () => {}
})
)
})
await flushUpdates()
const gate = () => renderer.root.find((node) => node.props.sshGate !== undefined).props.sshGate
const picker = runTargetPicker(renderer)
expect(picker.props.items.map((item: { id: string }) => item.id)).toEqual([
'ssh:devbox',
'runtime:env-1'
])
await act(async () => picker.props.onSelect(picker.props.items[0]))
await flushUpdates()
expect(gate().status).toBe('connected')
await act(async () => runTargetPicker(renderer).props.onSelect(picker.props.items[1]))
await flushUpdates()
expect(runTargetPicker(renderer).props.selectedId).toBe('runtime:env-1')
expect(server).toHaveBeenCalledWith('ssh.getState', { targetId: 'devbox' })
expect(gate()).toMatchObject({ status: null, requiresConnection: true })
expect(server).not.toHaveBeenCalledWith('worktree.create', expect.anything(), expect.anything())
})
it("offers the desktop's servers, by name, in a sheet opened before the list has them", async () => {
const serverRepo = { ...repos[0]!, id: 'server-repo', path: '/srv/orca' }
// One desktop: its own repos, its server list, and a server's repos relayed through it.
@@ -47,6 +47,9 @@ export function useNewWorkspaceExecutionTarget(args: {
const target = JSON.stringify([server ?? null, connectionId])
const [sshState, setSshEntry] = useState<SshStateEntry | null>(null)
const setSshState = (record: WorkspaceSshRecord) => setSshEntry({ target, record })
// A connect reply lands only while its target is still the one shown, so it never overwrites another's.
const settleSshState = (record: WorkspaceSshRecord) =>
setSshEntry((current) => (current?.target === target ? { target, record } : current))
const [connectingTarget, setConnectingTarget] = useState<string | null>(null)
const [detectedAgentIdsState, setDetectedAgentIdsState] = useState<DetectedAgentIdsState | null>(
null
@@ -134,9 +137,9 @@ export function useNewWorkspaceExecutionTarget(args: {
{ timeoutMs: 120_000 }
)
const state = sshRepoConnectRun.interpret(reply)
setSshState(state ?? fallbackSshState(connectionId, 'connected', null))
settleSshState(state ?? fallbackSshState(connectionId, 'connected', null))
} catch (error) {
setSshState(
settleSshState(
fallbackSshState(
connectionId,
'error',
@@ -1,11 +1,11 @@
import { useEffect, useMemo, useState } from 'react'
import type { ExecutionHostId } from '../../../src/shared/execution-host'
import { getNewWorkspaceDialogEligibleRepos } from '../../../src/shared/new-workspace-dialog-repo'
import type { RpcClient } from '../transport/rpc-client'
import { nativeChatRepoListRead } from '../session/mobile-session-read-operations'
import { getCachedRepos, setCachedRepos } from '../cache/repo-cache'
import { useLastVisitedWorktreeRepoId } from '../worktree/use-last-visited-worktree-repo'
import {
getMobileNewWorkspaceDialogEligibleRepos,
refreshMobileNewWorkspaceDialogSelectedRepo,
resolveMobileNewWorkspaceDialogRepoId
} from '../worktree/new-workspace-dialog-repo-selection'
@@ -58,11 +58,10 @@ export function useNewWorkspaceRepositories(args: {
const [pickedRepo, setSelectedRepo] = useState<MobileWorkspaceRepo | null>(null)
const [loading, setLoading] = useState(initialRepos == null)
const lastVisitedRepo = useLastVisitedWorktreeRepoId(hostId, visible)
// The desktop's rule: a per-workspace-environment SSH repo is plumbing, never a pick.
const repos = useMemo(
() =>
serverRepos.size === 0
? desktopRepos
: [...desktopRepos, ...[...serverRepos.values()].flat()],
getNewWorkspaceDialogEligibleRepos([...desktopRepos, ...[...serverRepos.values()].flat()]),
[desktopRepos, serverRepos]
)
// Why derived: the pick follows its repo's latest listing, and goes once its host stops listing it.
@@ -75,9 +74,8 @@ export function useNewWorkspaceRepositories(args: {
if (!visible || !lastVisitedRepo.loaded || selectedRepo || repos.length === 0) {
return
}
const eligibleRepos = getMobileNewWorkspaceDialogEligibleRepos(repos)
const preferredRepoId = resolveMobileNewWorkspaceDialogRepoId({
eligibleRepos,
eligibleRepos: repos,
activeRepoId: lastVisitedRepo.repoId
})
const preferredRepo = repos.find((repo) => repo.id === preferredRepoId) ?? null
@@ -1,6 +1,5 @@
import { describe, expect, it } from 'vitest'
import {
getMobileNewWorkspaceDialogEligibleRepos,
refreshMobileNewWorkspaceDialogSelectedRepo,
resolveMobileNewWorkspaceDialogRepoId
} from './new-workspace-dialog-repo-selection'
@@ -71,15 +70,6 @@ describe('mobile new workspace dialog repo selection', () => {
).toBe('local-repo')
})
it('excludes repos without paths from dialog defaults', () => {
expect(
getMobileNewWorkspaceDialogEligibleRepos([
makeRepo('missing-path', { path: '' }),
makeRepo('repo')
])
).toEqual([expect.objectContaining({ id: 'repo' })])
})
it('refreshes the selected repo from the fresh repo list', () => {
const staleSelectedRepo = makeRepo('repo', { path: '/cached/repo' })
const freshRepo = makeRepo('repo', { path: '/fresh/repo' })
@@ -7,12 +7,6 @@ type MobileNewWorkspaceDialogRepo = {
executionHostId?: 'local' | `ssh:${string}` | `runtime:${string}` | null
}
export function getMobileNewWorkspaceDialogEligibleRepos<T extends { path: string }>(
repos: readonly T[]
): T[] {
return repos.filter((repo) => Boolean(repo.path))
}
function getMobileRepoExecutionHostId(
repo: MobileNewWorkspaceDialogRepo
): Exclude<ExecutionHostScope, 'all'> {