mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
* feat(mobile): add session.tabs.list handler to mock server
The mock WebSocket server had no handler for session.tabs.list, so the
session screen of a paired dev client hung on 'Loading tabs' forever —
the terminal pane, live input, and command input could never be
exercised against the mock. Respond with a single ready terminal tab
wired to the existing term-1 fixture so the whole session surface works
offline.
* fix(mobile): complete the session.tabs.list mock contract
The new mock response omitted four non-optional fields of
RuntimeMobileSessionTabsResult: publicationEpoch and activeGroupId on the
result, and parentTabId and leafId on the terminal tab. Nothing caught it —
the object literal had no type annotation, and MobileSessionTabsStreamHealth
is generic over both result and tab. A shape-incomplete mock yields
untrustworthy repros for exactly the bugs it gets used for (session tabs,
split panes, pane-to-tab attribution).
Fill the fields with host-realistic values: a per-process publisher epoch, a
layout UUID leaf id, and the `${parentTabId}::${leafId}` surface id
mobileTerminalSurfaceId actually emits. Pin the shape with an explicit return
type so a future required field fails typecheck instead of silently drifting.
Move the fixture into its own module: inlining it pushed
mock-server-rpc-handlers.ts to 317 lines against a 300-line max-lines cap,
which broke `pnpm lint` on the parent commit. It registers through the file's
existing delegation chain, after the native-chat scenario so MOCK_NATIVE_CHAT=1
keeps ownership of the method.
Co-authored-by: Hanjoon Choe <hanjoonchoe@gmail.com>
* test(mobile): pin session tabs mock fidelity
Normalize the selector-backed worktree ID like the real runtime and cover the complete terminal surface response so future contract drift fails the mobile suite.
* fix(mobile): share terminal.list worktree resolution with session tabs
Main added `terminalListWorktreeId`, which the rebased session-tabs fixture
duplicated with a different no-selector fallback — `terminal.list` resolved to
the active fake worktree while `session.tabs.list` returned a literal 'mock',
so a session repro saw two different worktree ids for one screen.
* test(mobile): cover the bare session-tabs worktree selector
Answers the review note that only the `id:`-prefixed path was exercised.
* fix(mobile): make the mock publication epoch unique per process
Date.now() can repeat across a sub-millisecond restart, so the epoch did not
actually guarantee the fresh-publisher identity its comment claimed.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { WebSocket } from 'ws'
|
|
import {
|
|
handleRequest,
|
|
type RpcRequest,
|
|
type RpcResponse
|
|
} from '../scripts/mock-server-rpc-handlers'
|
|
|
|
function callRpc(method: string, params?: Record<string, unknown>): RpcResponse {
|
|
let response: RpcResponse | undefined
|
|
const request: RpcRequest = { id: 'request-1', method, ...(params ? { params } : {}) }
|
|
handleRequest(
|
|
request,
|
|
(nextResponse) => {
|
|
response = nextResponse
|
|
},
|
|
{} as WebSocket
|
|
)
|
|
expect(response).toBeDefined()
|
|
return response!
|
|
}
|
|
|
|
function listSessionTabs(worktree: string): RpcResponse {
|
|
return callRpc('session.tabs.list', { worktree })
|
|
}
|
|
|
|
describe('mock server session tabs fixture', () => {
|
|
it('returns a contract-complete terminal surface for the requested worktree', () => {
|
|
const response = listSessionTabs('id:repo-1::worktree-1')
|
|
|
|
expect(response.result).toEqual({
|
|
worktree: 'repo-1::worktree-1',
|
|
publicationEpoch: expect.stringMatching(/^mock-server:/),
|
|
snapshotVersion: 1,
|
|
activeGroupId: 'group-1',
|
|
activeTabId: 'tab-1::f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
|
activeTabType: 'terminal',
|
|
tabGroups: [
|
|
{
|
|
id: 'group-1',
|
|
activeTabId: 'tab-1',
|
|
tabOrder: ['tab-1'],
|
|
recentTabIds: ['tab-1']
|
|
}
|
|
],
|
|
tabs: [
|
|
{
|
|
type: 'terminal',
|
|
id: 'tab-1::f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
|
title: 'zsh',
|
|
parentTabId: 'tab-1',
|
|
leafId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
|
status: 'ready',
|
|
terminal: 'term-1',
|
|
isActive: true
|
|
}
|
|
]
|
|
})
|
|
})
|
|
|
|
it('passes a bare worktree selector through unprefixed', () => {
|
|
const response = listSessionTabs('repo-1::worktree-1')
|
|
|
|
expect((response.result as { worktree: string }).worktree).toBe('repo-1::worktree-1')
|
|
})
|
|
|
|
it('falls back to the same worktree terminal.list uses when no selector is sent', () => {
|
|
const terminals = callRpc('terminal.list').result as {
|
|
terminals: { worktreeId: string }[]
|
|
}
|
|
const expected = terminals.terminals[0]?.worktreeId
|
|
expect(expected).toBeTruthy()
|
|
|
|
const tabs = callRpc('session.tabs.list').result as { worktree: string }
|
|
expect(tabs.worktree).toBe(expected)
|
|
})
|
|
})
|