mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 03:57:35 +00:00
refactor(mobile): list server rows through one desktop fetch, with no side effect on listing
The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the desktop's own fetch is both the live source and the cache, and stamping stays in main. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -148,7 +148,7 @@ describe('mobile relay hosts: phone -> desktop lists the servers it shows', () =
|
||||
return { box, contact, phoneCall }
|
||||
}
|
||||
|
||||
it('lists cached rows for an unreachable server and live rows for an update-needed one', async () => {
|
||||
it('lists rows live through the desktop, then its last rows as stale once a server is unreachable', async () => {
|
||||
const { box, contact, phoneCall } = await startTopology()
|
||||
|
||||
await expect(phoneCall('mobileRelay.hosts.list')).resolves.toEqual({
|
||||
|
||||
@@ -89,7 +89,7 @@ function catalog(hosts: MobileDesktopRelayHosts, labels = new Map()) {
|
||||
}
|
||||
|
||||
describe('mobile relay host catalog', () => {
|
||||
it("lists every configured server with the desktop's health and whether it can relay", () => {
|
||||
it("lists every configured server with the desktop's health and whether it can relay", async () => {
|
||||
const { hosts, call } = fakeHosts([
|
||||
{ id: 'ready', name: 'Box', snapshot: snapshot('ready', 'live') },
|
||||
{ id: 'old', name: 'ThinkPad', snapshot: snapshot('old', 'live', false) },
|
||||
@@ -108,6 +108,9 @@ describe('mobile relay host catalog', () => {
|
||||
{ hostId: 'runtime:old-down', label: 'Pi', health: 'connecting', relay: 'update-needed' },
|
||||
{ hostId: 'runtime:new', label: 'Fresh', health: 'connecting', relay: 'unavailable' }
|
||||
])
|
||||
// Listing reads what the desktop already knows; it never contacts a server.
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
expect(call).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('encodes an environment id into the host id the desktop uses', () => {
|
||||
@@ -156,8 +159,7 @@ describe('mobile relay host catalog', () => {
|
||||
])
|
||||
call.mockResolvedValue(psReply([{ worktreeId: 'w', hostId: 'local', status: 'working' }]))
|
||||
const { catalog: hostCatalog, advance } = catalog(hosts)
|
||||
hostCatalog.list()
|
||||
await vi.waitFor(() => expect(call).toHaveBeenCalledTimes(1))
|
||||
await hostCatalog.worktrees('runtime:env')
|
||||
advance(5_000)
|
||||
|
||||
state.environments = [{ id: 'env', name: 'Box', snapshot: snapshot('env', 'unreachable') }]
|
||||
@@ -209,19 +211,21 @@ describe('mobile relay host catalog', () => {
|
||||
await expect(hostCatalog.worktrees('runtime:env')).resolves.toEqual({ worktrees: null })
|
||||
})
|
||||
|
||||
it('shares one in-flight fetch per server between listing and a rows request', async () => {
|
||||
it('shares one in-flight fetch per server between concurrent rows requests', async () => {
|
||||
const { hosts, call } = fakeHosts([
|
||||
{ id: 'env', name: 'Box', snapshot: snapshot('env', 'live') }
|
||||
])
|
||||
let answerFetch: (response: RuntimeRpcResponse<unknown>) => void = () => {}
|
||||
call.mockReturnValue(new Promise((resolve) => (answerFetch = resolve)))
|
||||
const { catalog: hostCatalog } = catalog(hosts)
|
||||
hostCatalog.list()
|
||||
hostCatalog.list()
|
||||
const rows = hostCatalog.worktrees('runtime:env')
|
||||
const first = hostCatalog.worktrees('runtime:env')
|
||||
const second = hostCatalog.worktrees('runtime:env')
|
||||
await vi.waitFor(() => expect(call).toHaveBeenCalledTimes(1))
|
||||
answerFetch(psReply([{ worktreeId: 'w', hostId: 'local' }]))
|
||||
await expect(rows).resolves.toMatchObject({ stale: false })
|
||||
await expect(Promise.all([first, second])).resolves.toMatchObject([
|
||||
{ stale: false },
|
||||
{ stale: false }
|
||||
])
|
||||
expect(call).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { z } from 'zod'
|
||||
import { DELEGATED_MOBILE_DEVICES_RUNTIME_CAPABILITY } from '../../../shared/delegated-mobile-device-contract'
|
||||
import { parseExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host'
|
||||
import { buildExecutionHostRegistry } from '../../../shared/execution-host-registry'
|
||||
import {
|
||||
stampServerWorktreeRows,
|
||||
type MobileRelayHost,
|
||||
type MobileRelayHostRelay,
|
||||
type MobileRelayHostsListResult,
|
||||
type MobileRelayHostWorktreesResult,
|
||||
type MobileRelayServerWorktreeRow
|
||||
parseExecutionHostId,
|
||||
toRuntimeExecutionHostId,
|
||||
type ExecutionHostId
|
||||
} from '../../../shared/execution-host'
|
||||
import { buildExecutionHostRegistry } from '../../../shared/execution-host-registry'
|
||||
import type {
|
||||
MobileRelayHost,
|
||||
MobileRelayHostRelay,
|
||||
MobileRelayHostsListResult,
|
||||
MobileRelayHostWorktreesResult,
|
||||
MobileRelayServerWorktreeRow
|
||||
} from '../../../shared/mobile-relay-hosts-contract'
|
||||
import { lastVerifiedRuntimeStatus } from '../../../shared/runtime-host-status'
|
||||
import type { RuntimeStatus } from '../../../shared/runtime-types'
|
||||
@@ -49,15 +52,13 @@ export class MobileRelayHostCatalog {
|
||||
) {}
|
||||
|
||||
list(): MobileRelayHostsListResult {
|
||||
const hosts = this.describe()
|
||||
for (const host of hosts) {
|
||||
// Why: keeps the last list warm while the server answers, for when it stops answering.
|
||||
if (host.health === 'available') {
|
||||
void this.refresh(host)
|
||||
}
|
||||
}
|
||||
return {
|
||||
hosts: hosts.map(({ hostId, label, health, relay }) => ({ hostId, label, health, relay }))
|
||||
hosts: this.describe().map(({ hostId, label, health, relay }) => ({
|
||||
hostId,
|
||||
label,
|
||||
health,
|
||||
relay
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,3 +165,15 @@ function relayVerdict(
|
||||
}
|
||||
return health === 'available' ? 'ready' : 'unavailable'
|
||||
}
|
||||
|
||||
/**
|
||||
* A server's rows name hosts relative to that server (`local`, its own `ssh:` targets). Like the
|
||||
* desktop sidebar, the phone shows every one of them under the server, so all become its host id.
|
||||
*/
|
||||
function stampServerWorktreeRows<Row extends { hostId?: string }>(
|
||||
environmentId: string,
|
||||
rows: readonly Row[]
|
||||
): (Row & { hostId: `runtime:${string}` })[] {
|
||||
const hostId = toRuntimeExecutionHostId(environmentId)
|
||||
return rows.map((row) => ({ ...row, hostId }))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { z } from 'zod'
|
||||
import { toRuntimeExecutionHostId } from './execution-host'
|
||||
import type { ExecutionHostHealth } from './execution-host-health'
|
||||
|
||||
// Why: both are answered by the paired desktop itself; the phone lists servers the desktop shows.
|
||||
@@ -38,15 +37,3 @@ export type MobileRelayHostWorktreesResult =
|
||||
stale: boolean
|
||||
}
|
||||
| { worktrees: null }
|
||||
|
||||
/**
|
||||
* A server's rows name hosts relative to that server (`local`, its own `ssh:` targets). Like the
|
||||
* desktop sidebar, the phone shows every one of them under the server, so all become its host id.
|
||||
*/
|
||||
export function stampServerWorktreeRows<Row extends { hostId?: string }>(
|
||||
environmentId: string,
|
||||
rows: readonly Row[]
|
||||
): (Row & { hostId: `runtime:${string}` })[] {
|
||||
const hostId = toRuntimeExecutionHostId(environmentId)
|
||||
return rows.map((row) => ({ ...row, hostId }))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user