test: remove 49 low-value graph, host and layout tests (#34473)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-27 10:21:11 -07:00
committed by GitHub
co-authored by OrcaWin
parent 1c8c0685fb
commit 94b06930b9
20 changed files with 6 additions and 1268 deletions
+5 -11
View File
@@ -2715,31 +2715,24 @@
"providers": ["local", "daemon", "ssh", "remote-runtime"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["local", "daemon", "ssh", "remote-runtime"],
"coverageNotes": "The always-mounted desktop subscriber is covered through pure projection/reference and graph-sync contracts. All providers use the same serialized fields. WSL, platform launch, liveness, transport and mobile rendering are unaffected.",
"coverageNotes": "The always-mounted desktop subscriber uses the reviewed pure projection/cache resource path and graph-sync contracts; this gate does not independently prove every serialized field reaches a mobile recipient. All providers use the same serialized fields. WSL, platform launch, liveness, transport and mobile rendering are unaffected.",
"motivatingLinks": [
"https://github.com/stablyai/orca/blob/main/src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts"
],
"invariant": "The desktop agent-status comparison projection remains identical for every serialized field, key membership and freshness bucket while avoiding a full aggregate join when per-entry serialized content is unchanged.",
"oracle": "500 statuses with accumulated assistant previews receive 50 same-bucket heartbeat replacements with zero aggregate joins. A bucket boundary and assistant-detail change invalidate the projection. Existing reference comparisons cover field content, ordering, insertion/removal and subscriber decisions.",
"oracle": "500 statuses with accumulated assistant previews receive 50 same-bucket heartbeat replacements with zero aggregate joins. A bucket boundary and assistant-detail change invalidate the projection. Retained resource checks additionally cover entry-reference reuse without sort/join, changed-file draft hashing, localeCompare avoidance, and changed open-file/browser serialization work. Removed private field/membership matrices no longer provide an exhaustive serialization claim.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts src/renderer/src/runtime/sync-runtime-graph.test.ts"
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts src/renderer/src/runtime/sync-runtime-graph.test.ts"
],
"testFiles": [
"src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts",
"src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts",
"src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts",
"src/renderer/src/runtime/sync-runtime-graph.test.ts"
],
"assertionRefs": [
{
"file": "src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts",
"assertions": [
"does not rejoin accumulated previews for timestamp-only heartbeats",
"still rebuilds when an entry changes",
"still rebuilds when a pane is removed, even though every survivor is reused",
"still rebuilds when key membership swaps at a constant entry count",
"still rebuilds when a pane is added"
]
"assertions": ["does not rejoin accumulated previews for timestamp-only heartbeats"]
}
],
"evidenceRuns": [
@@ -2748,6 +2741,7 @@
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts src/renderer/src/runtime/sync-runtime-graph.test.ts",
"currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts src/renderer/src/runtime/sync-runtime-graph.test.ts",
"result": "passed",
"summary": "38 tests passed across four projection and graph-sync files; renderer typecheck passed.",
"durationSeconds": 2.7
-3
View File
@@ -11147,7 +11147,6 @@
"src/renderer/src/runtime/host-session-mirror-hydration-drain.test.ts": 564,
"src/renderer/src/runtime/host-session-mirror-hydration-frame-ordering.test.tsx": 1804,
"src/renderer/src/runtime/host-session-mirror-settle-receipt-frames.test.tsx": 1953,
"src/renderer/src/runtime/host-session-snapshot-authority-client-hosted.test.ts": 80,
"src/renderer/src/runtime/host-structured-agent-availability.test.ts": 69,
"src/renderer/src/runtime/host-structured-agents.test.ts": 512,
"src/renderer/src/runtime/host-unverifiable-agent-row-presentation.test.ts": 1432,
@@ -11277,7 +11276,6 @@
"src/renderer/src/runtime/server-layout-frames.test.ts": 1464,
"src/renderer/src/runtime/server-layout-view-report.test.ts": 2249,
"src/renderer/src/runtime/session-tab-rows-main-window-parity.test.ts": 299,
"src/renderer/src/runtime/sleeping-records-to-layout-owner.test.ts": 1443,
"src/renderer/src/runtime/structured-agent-session-client.test.ts": 270,
"src/renderer/src/runtime/structured-agent-session-close.test.ts": 91,
"src/renderer/src/runtime/structured-agent-session-host-capability-scaling.test.tsx": 1739,
@@ -11292,7 +11290,6 @@
"src/renderer/src/runtime/structured-session-orca-session-id.test.ts": 295,
"src/renderer/src/runtime/structured-session-tab-inventory.test.ts": 1338,
"src/renderer/src/runtime/sync-runtime-graph-agent-status-projection-join.test.ts": 104,
"src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts": 61,
"src/renderer/src/runtime/sync-runtime-graph-automation-leaf.test.ts": 872,
"src/renderer/src/runtime/sync-runtime-graph-browser.test.ts": 321,
"src/renderer/src/runtime/sync-runtime-graph-chat-tab-order.test.ts": 262,
@@ -92,12 +92,6 @@ describe('host model catalog snapshots', () => {
})
).toBeUndefined()
})
it('drops an agent’s answers when the host says the account has no list', () => {
recordHostModelCatalogSnapshot(LOCAL, 'codex', 'wt-1', list(true))
recordHostModelCatalogSnapshot(LOCAL, 'codex', 'wt-1', { origin: 'unknown' })
expect(readHostModelCatalogSnapshot(LOCAL, 'codex', NEW_CHAT)).toBeUndefined()
})
})
const SAVED_ONLY = 'agent-session.model-catalog.saved-only.v1'
@@ -108,12 +102,6 @@ function registerAgents(hostId: string, runtimeId: string | null): void {
mocks.agentListeners.forEach((listener) => listener())
}
/** The host saved lists for Grok, OpenCode and OMP only; nothing for Claude or Codex. */
function savedFor(agents: readonly string[]) {
return async (_target: unknown, _method: string, params: { agent: string }) =>
agents.includes(params.agent) ? list(false) : { origin: 'unknown' }
}
describe('host model catalog snapshots sync', () => {
let stop: (() => void) | null = null
beforeEach(() => {
@@ -150,26 +138,6 @@ describe('host model catalog snapshots sync', () => {
expect(mocks.call).toHaveBeenCalledTimes(1)
})
it('loads every registered agent saved-only, picked or not; an agent with nothing saved stays unknown', async () => {
mocks.capabilities = [SAVED_ONLY]
mocks.call.mockImplementation(savedFor(['grok', 'opencode', 'omp']))
stop = installHostModelCatalogSnapshotsSync()
// Nothing is read until the host's agents are known; their arrival reads them.
await Promise.resolve()
expect(mocks.call).not.toHaveBeenCalled()
registerAgents('local', null)
await vi.waitFor(() =>
expect(readHostModelCatalogSnapshot(LOCAL, 'omp', NEW_CHAT)).toEqual(list(false))
)
expect(mocks.call.mock.calls.map(([, method, params]) => [method, params])).toEqual(
REGISTERED.map(({ agent }) => ['agentSession.modelCatalog', { agent, savedOnly: true }])
)
expect(readHostModelCatalogSnapshot(LOCAL, 'grok', NEW_CHAT)).toEqual(list(false))
expect(readHostModelCatalogSnapshot(LOCAL, 'opencode', NEW_CHAT)).toEqual(list(false))
expect(readHostModelCatalogSnapshot(LOCAL, 'claude', NEW_CHAT)).toBeUndefined()
expect(readHostModelCatalogSnapshot(LOCAL, 'codex', NEW_CHAT)).toBeUndefined()
})
it('reads again for the new account after an account change, dropping an answer sent before it', async () => {
mocks.capabilities = [SAVED_ONLY]
registerAgents('local', null)
@@ -1,9 +1,7 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { clearRuntimeEnvironmentConnectionGenerationsForTests } from '@/store/slices/runtime-status'
import {
getParkedHostSessionMirrorWaiterCountForTests,
markHostSessionMirrorHydrated,
MAX_PARKED_HOST_SESSION_MIRROR_WAITERS,
parkUntilHostSessionMirrorHydrates,
resetHostSessionMirrorHydrationForTests
} from './host-session-mirror-hydration'
@@ -27,14 +25,4 @@ describe('host session mirror hydration drain', () => {
expect(() => markHostSessionMirrorHydrated(ENVIRONMENT_ID)).not.toThrow()
expect(secondReplay).toHaveBeenCalledTimes(1)
})
it('bounds parked waiter growth when environments churn', () => {
for (let index = 0; index < MAX_PARKED_HOST_SESSION_MIRROR_WAITERS + 4; index += 1) {
parkUntilHostSessionMirrorHydrates(`env-${index}`, 'repo::worktree', () => {})
}
expect(getParkedHostSessionMirrorWaiterCountForTests()).toBe(
MAX_PARKED_HOST_SESSION_MIRROR_WAITERS
)
})
})
@@ -1,65 +0,0 @@
import { describe, expect, it } from 'vitest'
import { UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH } from '../../../shared/runtime-types'
import { hostSnapshotAffirmsClientHostedPages } from './host-session-snapshot-authority'
const REAL_EPOCH = 'headless:abc'
describe('reading whether a snapshot answers for client-hosted pages', () => {
it('affirms a fully published snapshot carrying no unreconciled flag', () => {
expect(
hostSnapshotAffirmsClientHostedPages({ publicationEpoch: REAL_EPOCH, snapshotVersion: 3 })
).toBe(true)
})
// Restarted runtime’s real epoch/version affirms its terminals, not client-hosted browser pages awaiting reconciliation.
it('does not affirm a restarted runtime that has not heard from its hosts yet', () => {
expect(
hostSnapshotAffirmsClientHostedPages({
publicationEpoch: REAL_EPOCH,
snapshotVersion: 3,
clientHostedPagesUnreconciled: true
})
).toBe(false)
})
// Inherited from the worktree-level gate: the placeholder pair is the runtime saying "ask later".
it('does not affirm the unpublished-worktree placeholder frame', () => {
expect(
hostSnapshotAffirmsClientHostedPages({
publicationEpoch: UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH,
snapshotVersion: 0
})
).toBe(false)
})
it('does not affirm when the placeholder frame also carries the unreconciled flag', () => {
expect(
hostSnapshotAffirmsClientHostedPages({
publicationEpoch: UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH,
snapshotVersion: 0,
clientHostedPagesUnreconciled: true
})
).toBe(false)
})
// Only the placeholder epoch marks a synthesized frame; version zero alone is a legitimate state.
it('affirms a real epoch at version zero', () => {
expect(
hostSnapshotAffirmsClientHostedPages({ publicationEpoch: REAL_EPOCH, snapshotVersion: 0 })
).toBe(true)
})
// Explicit `undefined` as well as an absent key: a host that omits the flag means reconciled.
it('affirms when the unreconciled flag is absent or explicitly undefined', () => {
expect(
hostSnapshotAffirmsClientHostedPages({ publicationEpoch: REAL_EPOCH, snapshotVersion: 1 })
).toBe(true)
expect(
hostSnapshotAffirmsClientHostedPages({
publicationEpoch: REAL_EPOCH,
snapshotVersion: 1,
clientHostedPagesUnreconciled: undefined
})
).toBe(true)
})
})
@@ -12,25 +12,6 @@ import {
describe('afterLayoutFrame', () => {
afterEach(() => endLayoutFramePositions())
it('runs at once once the stream ended, or for a reply that names no frame', () => {
endLayoutFramePositions()
const run = vi.fn()
afterLayoutFrame(9, run)
expect(run).toHaveBeenCalledTimes(1)
noteLayoutFramePosition(3)
afterLayoutFrame(undefined, run)
expect(run).toHaveBeenCalledTimes(2)
})
it('runs at once while the window has opened no stream', () => {
_resetLayoutFramePositionForTests()
const run = vi.fn()
afterLayoutFrame(2, run)
expect(run).toHaveBeenCalledOnce()
expect(layoutFramesFlowing()).toBe(false)
})
it('waits for an opened stream before its first frame: that frame, or its end', () => {
_resetLayoutFramePositionForTests()
beginLayoutFramePositions()
@@ -2,14 +2,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
_resetLayoutOwnerPaneIntentsForTests,
expectPaneToClose,
expectTabRowFields,
holdTabPaneTree,
isTabPaneTreeHeld,
onTabPaneTreeRedraw,
panesLeftAfterPendingCloses,
requestTabPaneTreeRedraw,
takePaneCloseIntent,
takeTabRowFields
takePaneCloseIntent
} from './layout-owner-pane-intents'
describe('layout-owner pane intents', () => {
@@ -41,16 +38,6 @@ describe('layout-owner pane intents', () => {
expect(takePaneCloseIntent('tab-1', 'leaf-64')).toBe('exit')
})
it('hands out a new row’s fields once, until released', () => {
const release = expectTabRowFields('tab-1', { pendingActivationSpawn: true })
expect(takeTabRowFields('tab-1')).toEqual({ pendingActivationSpawn: true })
expect(takeTabRowFields('tab-1')).toBeNull()
expectTabRowFields('tab-2', { pendingActivationSpawn: true })
release()
expect(takeTabRowFields('tab-2')).toEqual({ pendingActivationSpawn: true })
})
it('holds a tab until its last hold is released, then tells the listener once', () => {
const listener = vi.fn()
onTabPaneTreeRedraw(listener)
@@ -69,11 +56,4 @@ describe('layout-owner pane intents', () => {
requestTabPaneTreeRedraw('tab-2')
expect(listener).toHaveBeenLastCalledWith('tab-2')
})
it('counts the panes no pending close of this tab takes', () => {
expectPaneToClose('tab-1', 'a', 'exit')
expectPaneToClose('tab-2', 'b', 'user')
expect(panesLeftAfterPendingCloses('tab-1', ['a', 'b', 'c'])).toBe(2)
})
})
@@ -143,17 +143,6 @@ describe('a mirrored remote row decays on the replica clock, not the host clock'
expect(mirroredRowIsFreshAt(store, T0 + AGENT_STATUS_STALE_AFTER_MS + 1)).toBe(false)
})
it('keeps decaying while the host publishes nothing new', () => {
// No fresh snapshot arrives during silence, so replica must age its last evidence locally.
const store = seedPairedClientStore()
applyChangedHostSnapshot(store, T0 + HOST_SKEW_MS, T0)
for (const elapsed of [1_000, AGENT_STATUS_STALE_AFTER_MS / 2]) {
expect(mirroredRowIsFreshAt(store, T0 + elapsed)).toBe(true)
}
expect(mirroredRowIsFreshAt(store, T0 + AGENT_STATUS_STALE_AFTER_MS * 3)).toBe(false)
})
it('does not restart the window when the host republishes evidence already seen', () => {
const store = seedPairedClientStore()
const hostNow = T0 + HOST_SKEW_MS
@@ -39,8 +39,6 @@ const HOST_EPOCH = 'host-epoch-1'
const T0 = 1_700_000_000_000
/** The laptop was closed well past the agent-status freshness boundary. */
const LONG_SLEEP_MS = AGENT_STATUS_STALE_AFTER_MS * 6
/** A blip the client rides out without its own status going stale. */
const BRIEF_DROP_MS = 5_000
type TestStore = ReturnType<typeof createTestStore>
@@ -267,14 +265,6 @@ describe('STA-3107: sidebar agent rows survive a paired-client sleep/wake reconn
resetRendererOwnedAgentStatusPanesForTests()
})
it('control: a brief transport drop keeps every pane in the sidebar', () => {
const store = seedPairedClientStore()
const reconnected = runSleepWakeReconnect(store, BRIEF_DROP_MS)
expect(reconnected.tabIds).toHaveLength(PANES.length)
expect(reconnected.rowPaneKeys.sort()).toEqual(PANES.map(mirrorPaneKey).sort())
})
it('keeps a sidebar row for every still-live pane after a long sleep', () => {
const store = seedPairedClientStore()
const reconnected = runSleepWakeReconnect(store, LONG_SLEEP_MS)
@@ -109,28 +109,4 @@ describe('a local pane rename over host snapshots', () => {
[leafId]: 'second'
})
})
it('names each renamed pane in exactly one push', () => {
const leafId = '66666666-6666-4666-8666-666666666666'
const sibling = '77777777-7777-4777-8777-777777777777'
rename(leafId, 'mine')
expect(takeUnsentPaneTitleRenames([leafId, sibling])).toEqual([leafId])
expect(takeUnsentPaneTitleRenames([leafId, sibling])).toEqual([])
})
it("holds nothing for a window's own local tab", () => {
const leafId = '88888888-8888-4888-8888-888888888888'
notePendingPaneTitleRename({
leafId,
title: 'mine',
layout: layoutFor(leafId, 'old', 'local-pty'),
now: T0
})
expect(takeUnsentPaneTitleRenames([leafId])).toEqual([])
expect(withPendingPaneTitleRenames({ [leafId]: 'old' }, [leafId], T0)).toEqual({
[leafId]: 'old'
})
})
})
@@ -206,103 +206,6 @@ describe('#12906: remote agent rows keep the host-published last assistant messa
resetRendererOwnedAgentStatusPanesForTests()
})
it('shows the message on a pane this renderer owns from the byte stream', () => {
const store = seedPairedClientStore()
// Host hook receives completion text while client OSC independently observes idle.
const mirrored = applyHostSnapshot(
store,
makeHostSnapshot({
snapshotVersion: 1,
hostNow: T0 - 1_000,
statusByHostTabId: {
[OWNED.hostTabId]: {
state: 'done',
lastAssistantMessage: HOST_MESSAGE,
stateStartedAt: T0 - 2_000
}
}
}),
T0
)
expect(mirrored, 'harness must reach the mirror, not stop at the freshness gate').toBe(true)
replayClientByteStatus(store, OWNED, 'done', T0)
// Republication is where the loss happens: the client now owns the key.
applyHostSnapshot(
store,
makeHostSnapshot({
snapshotVersion: 2,
hostNow: T0 + 1_000,
statusByHostTabId: {
[OWNED.hostTabId]: {
state: 'done',
lastAssistantMessage: HOST_MESSAGE,
stateStartedAt: T0 - 2_000
}
}
}),
T0 + 2_000
)
const messages = observeRowMessages(store, T0 + 2_000)
expect(messages, JSON.stringify(messages, null, 2)).toMatchObject({
[mirrorPaneKey(OWNED)]: HOST_MESSAGE
})
})
it('control: a pane this renderer never wrote status for already showed it', () => {
const store = seedPairedClientStore()
applyHostSnapshot(
store,
makeHostSnapshot({
snapshotVersion: 1,
hostNow: T0 - 1_000,
statusByHostTabId: {
[CEDED.hostTabId]: {
state: 'done',
lastAssistantMessage: HOST_MESSAGE,
stateStartedAt: T0 - 2_000
}
}
}),
T0
)
// Pins the causal boundary to the ownership fence, not the mirror as a whole.
expect(observeRowMessages(store, T0)[mirrorPaneKey(CEDED)]).toBe(HOST_MESSAGE)
})
it('never invents a message the host did not publish (old host / new client)', () => {
const store = seedPairedClientStore()
applyHostSnapshot(
store,
makeHostSnapshot({
snapshotVersion: 1,
hostNow: T0 - 1_000,
statusByHostTabId: {
[OWNED.hostTabId]: { state: 'done', stateStartedAt: T0 - 2_000 }
}
}),
T0
)
replayClientByteStatus(store, OWNED, 'done', T0)
applyHostSnapshot(
store,
makeHostSnapshot({
snapshotVersion: 2,
hostNow: T0 + 1_000,
statusByHostTabId: {
[OWNED.hostTabId]: { state: 'done', stateStartedAt: T0 - 2_000 }
}
}),
T0 + 2_000
)
const messages = observeRowMessages(store, T0 + 2_000)
// Guard against a vacuous pass: the row must exist for its blank message to mean anything.
expect(Object.keys(messages)).toContain(mirrorPaneKey(OWNED))
expect(messages[mirrorPaneKey(OWNED)]).toBeUndefined()
})
it('keeps a mirrored message when a later host frame omits the field (old host / new client)', () => {
const store = seedPairedClientStore()
applyHostSnapshot(
@@ -1,151 +0,0 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as AgentStatusModule from '@/lib/agent-status'
import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume'
import {
createTestStore,
makeRuntimeOwnedWorktree,
makeWorktree,
runtimeOwnedRepoState,
seedStore
} from '../store/slices/store-test-helpers'
import { createStoreCascadesMockApi } from '../store/slices/store-cascades-test-harness'
import { recordPaneWakeThroughLayoutOwner } from '../store/terminals/terminal-sleep-layout-owner'
import { followSleepingRecordsInLayoutOwner } from './sleeping-records-to-layout-owner'
const mocks = vi.hoisted(() => ({
callRuntimeRpc: vi.fn(
async (_target: unknown, _method: string, _params: { command: unknown }) => ({
ok: true,
result: {}
})
)
}))
const held: { store?: ReturnType<typeof createTestStore> } = {}
vi.mock('@/store', () => ({
get useAppStore() {
return held.store
}
}))
vi.mock('@/runtime/runtime-rpc-client', () => ({ callRuntimeRpc: mocks.callRuntimeRpc }))
vi.mock('sonner', () => ({
toast: { info: vi.fn(), success: vi.fn(), error: vi.fn(), warning: vi.fn() }
}))
vi.mock('@/lib/agent-status', async (importOriginal) => {
const actual = await importOriginal<typeof AgentStatusModule>()
return { ...actual, detectAgentStatusFromTitle: vi.fn().mockReturnValue(null) }
})
createStoreCascadesMockApi()
const LOCAL = 'repo1::/path/wt'
const PAIRED = 'repo2::/remote/wt'
const TAB = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
const PANE = `${TAB}:11111111-1111-4111-8111-111111111111`
const PAIRED_TAB = 'bbbbbbbb-cccc-4ddd-8eee-ffffffffffff'
const PAIRED_PANE = `${PAIRED_TAB}:22222222-2222-4222-8222-222222222222`
function record(paneKey: string, worktreeId: string): SleepingAgentSessionRecord {
return {
paneKey,
worktreeId,
agent: 'codex',
providerSession: { key: 'session_id', id: paneKey },
prompt: '',
state: 'working',
capturedAt: 1,
updatedAt: 1,
origin: 'live'
}
}
function terminalRow(id: string, worktreeId: string) {
return {
id,
ptyId: null,
worktreeId,
title: 'Terminal 1',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
}
function setRecords(records: Record<string, SleepingAgentSessionRecord>): void {
held.store!.setState({ sleepingAgentSessionsByPaneKey: records })
}
const sent = () => mocks.callRuntimeRpc.mock.calls.map((call) => call[2].command)
beforeAll(() => {
const store = createTestStore()
seedStore(
store,
runtimeOwnedRepoState('env-1', 'repo2', '/remote', {
worktreesByRepo: {
repo1: [makeWorktree({ id: LOCAL, repoId: 'repo1', path: '/path/wt' })],
repo2: [makeRuntimeOwnedWorktree({ id: PAIRED, repoId: 'repo2' }, 'env-1')]
},
tabsByWorktree: {
[LOCAL]: [terminalRow(TAB, LOCAL)],
[PAIRED]: [terminalRow(PAIRED_TAB, PAIRED)]
},
// Restore's records are main's own.
sleepingAgentSessionsByPaneKey: { [PANE]: { ...record(PANE, LOCAL), state: 'done' } }
})
)
held.store = store
})
beforeEach(() => {
mocks.callRuntimeRpc.mockClear()
})
describe('main’s copy of the window’s resume records', () => {
it('sends nothing for the records restore loaded, and subscribes once', () => {
followSleepingRecordsInLayoutOwner()
followSleepingRecordsInLayoutOwner()
expect(mocks.callRuntimeRpc).not.toHaveBeenCalled()
setRecords({})
expect(mocks.callRuntimeRpc).toHaveBeenCalledTimes(1)
})
it('records an added record without stopping its pane, and a dropped one without starting it', () => {
const live = record(PANE, LOCAL)
setRecords({ [PANE]: live })
setRecords({})
expect(sent()).toEqual([
{
type: 'sleep',
workspace: LOCAL,
tabId: TAB,
paneKeys: [PANE],
records: [live],
recordOnly: true
},
{ type: 'wake', workspace: LOCAL, tabId: TAB, paneKeys: [PANE], recordOnly: true }
])
})
it('sends nothing for a paired server’s workspace', () => {
setRecords({ [PAIRED_PANE]: record(PAIRED_PANE, PAIRED) })
setRecords({})
expect(mocks.callRuntimeRpc).not.toHaveBeenCalled()
})
it('sends a resumed pane’s wake, record-only so main starts nothing, before the record drop', () => {
setRecords({ [PANE]: record(PANE, LOCAL) })
mocks.callRuntimeRpc.mockClear()
recordPaneWakeThroughLayoutOwner(held.store!.getState(), LOCAL, PANE)
held.store!.getState().clearSleepingAgentSession(PANE)
expect(sent()).toEqual([
{ type: 'wake', workspace: LOCAL, tabId: TAB, paneKeys: [PANE], recordOnly: true },
{ type: 'wake', workspace: LOCAL, tabId: TAB, paneKeys: [PANE], recordOnly: true }
])
})
})
@@ -86,21 +86,6 @@ describe('agent-status projection join short circuit', () => {
expect(sorts).toBe(0)
})
it('caches the new map identity on the short-circuit path', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const map = mapOf([0, 1])
buildRuntimeMobileAgentStatusProjectionForTests(map)
const again = respread(map)
buildRuntimeMobileAgentStatusProjectionForTests(again)
// A repeat call with the same identity must hit the identity early-out, not re-walk the keys.
const { joins, sorts } = countJoins(() =>
buildRuntimeMobileAgentStatusProjectionForTests(again)
)
expect(joins).toBe(0)
expect(sorts).toBe(0)
})
it('does not rejoin accumulated previews for timestamp-only heartbeats', () => {
let map = mapOf(Array.from({ length: 500 }, (_, index) => index))
for (const paneKey of Object.keys(map)) {
@@ -135,61 +120,4 @@ describe('agent-status projection join short circuit', () => {
})
).not.toBe(first)
})
it('still rebuilds when an entry changes', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const map = mapOf([0, 1])
const first = buildRuntimeMobileAgentStatusProjectionForTests(map)
const changed = { ...map, 'tab-1:leaf-0': makeEntry(1, { state: 'idle' }) }
const { result, joins } = countJoins(() =>
buildRuntimeMobileAgentStatusProjectionForTests(changed)
)
expect(result).not.toBe(first)
expect(joins).toBeGreaterThan(0)
})
it('still rebuilds when a pane is removed, even though every survivor is reused', () => {
// The reuse check alone cannot see a removal; only the entry-count check does.
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const map = mapOf([0, 1])
const first = buildRuntimeMobileAgentStatusProjectionForTests(map)
const removed = { 'tab-0:leaf-0': map['tab-0:leaf-0'] }
const result = buildRuntimeMobileAgentStatusProjectionForTests(removed)
expect(result).not.toBe(first)
expect(result).toBe(
buildRuntimeMobileAgentStatusProjectionForTests({
'tab-0:leaf-0': map['tab-0:leaf-0']
})
)
})
it('still rebuilds when key membership swaps at a constant entry count', () => {
// The entry-count check cannot see a same-size swap; only the per-key lookup does.
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const map = mapOf([0, 1])
const first = buildRuntimeMobileAgentStatusProjectionForTests(map)
const swapped = { 'tab-0:leaf-0': map['tab-0:leaf-0'], 'tab-9:leaf-0': makeEntry(9) }
const result = buildRuntimeMobileAgentStatusProjectionForTests(swapped)
expect(result).not.toBe(first)
expect(result).toContain('tab-9:leaf-0')
expect(result).not.toContain('tab-1:leaf-0')
})
it('still rebuilds when a pane is added', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const map = mapOf([0])
const first = buildRuntimeMobileAgentStatusProjectionForTests(map)
const added = { ...map, 'tab-9:leaf-0': makeEntry(9) }
const result = buildRuntimeMobileAgentStatusProjectionForTests(added)
expect(result).not.toBe(first)
expect(result).toContain('tab-9:leaf-0')
})
})
@@ -1,211 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { AppState } from '@/store/types'
import {
AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS_FOR_TESTS,
buildRuntimeMobileAgentStatusProjectionForTests,
resetRuntimeMobileAgentStatusProjectionCacheForTests
} from './sync-runtime-graph/agent-status-projection'
// Keep pre-change serialization as oracle, except code-unit sorting used only for equality. Pin identical entries across locale order and read bucket width from production to avoid unrelated constant drift.
const BUCKET_MS = AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS_FOR_TESTS
type MainAgentStatus = AppState['agentStatusByPaneKey'][string]['mainAgent']
function mainAgentKey(mainAgent: MainAgentStatus) {
return mainAgent ? [mainAgent.state, mainAgent.outcome ?? null, mainAgent.stateStartedAt] : null
}
function referenceProjection(map: AppState['agentStatusByPaneKey']): string {
return JSON.stringify(
Object.entries(map)
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([paneKey, entry]) => ({
paneKey,
entryPaneKey: entry.paneKey,
state: entry.state,
workingMode: entry.workingMode ?? null,
prompt: entry.prompt,
updatedAtBucket: Math.floor(entry.updatedAt / BUCKET_MS),
stateStartedAt: entry.stateStartedAt,
// A new turn can keep the state (child work held it open); its stamp alone must republish.
turnStartedAt: entry.turnStartedAt ?? null,
turnEndedAt: entry.turnEndedAt ?? null,
turnEndDisposition: entry.turnEndDisposition ?? null,
agentType: entry.agentType ?? null,
terminalTitle: entry.terminalTitle ?? null,
stateHistory: entry.stateHistory.map((history) => ({
state: history.state,
prompt: history.prompt,
startedAt: history.startedAt,
interrupted: history.interrupted ?? null,
mainAgent: mainAgentKey(history.mainAgent)
})),
toolName: entry.toolName ?? null,
toolInput: entry.toolInput ?? null,
interactivePrompt: entry.interactivePrompt ?? null,
lastAssistantMessage: entry.lastAssistantMessage ?? null,
lastAssistantMessageIsToolOutput: entry.lastAssistantMessageIsToolOutput ?? null,
interrupted: entry.interrupted ?? null,
mainAgent: mainAgentKey(entry.mainAgent),
programStatus: entry.programStatus
? [
entry.programStatus.state,
entry.programStatus.kind ?? null,
entry.programStatus.progress ?? null,
entry.programStatus.message ?? null
]
: null
}))
)
}
function makeEntry(index: number, overrides: Record<string, unknown> = {}): never {
return {
paneKey: `tab-${index}:leaf-0`,
state: 'working',
prompt: `prompt ${index} with "quotes" and \\ backslash`,
updatedAt: 1740000000000 + index * 17,
stateStartedAt: 1740000000000,
agentType: 'claude',
terminalTitle: `agent ${index} 日本 \u{1f389}`,
stateHistory: Array.from({ length: 3 }, (_value, h) => ({
state: 'working',
prompt: `step ${h}`,
startedAt: 1740000000000 + h
})),
toolName: 'shell_command',
toolInput: 'ls -la',
lastAssistantMessage: 'answer',
...overrides
} as never
}
describe('mobile agent-status projection equivalence', () => {
it('matches the whole-array serialization across status maps and cache reuse', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const statusMaps: AppState['agentStatusByPaneKey'][] = []
statusMaps.push({})
statusMaps.push({ 'tab-0:leaf-0': makeEntry(0) })
statusMaps.push({ 'tab-0:leaf-0': makeEntry(0, { workingMode: 'monitoring' }) })
statusMaps.push({ 'tab-0:leaf-0': makeEntry(0, { turnStartedAt: 1740000000500 }) })
const many: AppState['agentStatusByPaneKey'] = {}
for (let index = 0; index < 12; index += 1) {
many[`tab-${index}:leaf-0`] = makeEntry(index)
}
statusMaps.push(many)
// Optional fields absent entirely, which the ?? null fallbacks must cover.
statusMaps.push({
'tab-9:leaf-1': makeEntry(9, {
agentType: undefined,
terminalTitle: undefined,
toolName: undefined,
toolInput: undefined,
interactivePrompt: undefined,
lastAssistantMessage: undefined,
interrupted: undefined
})
})
// Keys deliberately out of insertion order to pin the sort.
statusMaps.push({
'tab-z:leaf-0': makeEntry(2),
'tab-a:leaf-0': makeEntry(1),
'tab-m:leaf-0': makeEntry(3)
})
for (const [index, statusMap] of statusMaps.entries()) {
expect({
index,
projection: buildRuntimeMobileAgentStatusProjectionForTests(statusMap)
}).toEqual({ index, projection: referenceProjection(statusMap) })
}
// Replace one entry like setAgentStatus and require observable projection change; sub-bucket timestamp nudges cannot catch stale reuse.
let current = many
for (let round = 0; round < 4; round += 1) {
current = {
...current,
'tab-0:leaf-0': makeEntry(0, {
updatedAt: 1740000000000 + BUCKET_MS * 3 * (round + 1),
state: round % 2 === 0 ? 'done' : 'working',
prompt: `changed prompt round ${round}`,
lastAssistantMessage: `answer round ${round}`
})
}
expect({
round,
projection: buildRuntimeMobileAgentStatusProjectionForTests(current)
}).toEqual({ round, projection: referenceProjection(current) })
}
})
it('serializes the same entries the localeCompare order did', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
// Use keys whose locale/code-unit orders differ, including tab-1: versus tab-10: and case/accents.
const map: AppState['agentStatusByPaneKey'] = {}
for (const paneKey of ['tab-1:leaf-0', 'tab-10:leaf-0', 'B:leaf-0', 'a:leaf-0', 'á:leaf-0']) {
map[paneKey] = makeEntry(0, { paneKey })
}
const localeCompareOrderedEntries = JSON.parse(
JSON.stringify(
Object.entries(map)
.sort(([a], [b]) => a.localeCompare(b))
.map(([paneKey]) => paneKey)
)
) as string[]
const projected = (
JSON.parse(buildRuntimeMobileAgentStatusProjectionForTests(map)) as { paneKey: string }[]
).map((entry) => entry.paneKey)
expect([...projected].sort()).toEqual([...localeCompareOrderedEntries].sort())
expect(projected).toHaveLength(localeCompareOrderedEntries.length)
})
it('republishes a verdict change that leaves the interrupted flag as it was', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const project = (live: 'success' | 'failure', history: 'success' | 'failure'): string =>
buildRuntimeMobileAgentStatusProjectionForTests({
'tab-0:leaf-0': makeEntry(0, {
state: 'done',
mainAgent: { state: 'done', outcome: live, stateStartedAt: 1740000000000 },
stateHistory: [
{
state: 'done',
prompt: 'p',
startedAt: 1,
mainAgent: { state: 'done', outcome: history, stateStartedAt: 1 }
}
]
})
})
expect(project('failure', 'success')).not.toBe(project('success', 'success'))
expect(project('success', 'failure')).not.toBe(project('success', 'success'))
})
it('republishes a main agent failing while its subagents keep the row working', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const project = (outcome?: 'failure', stateStartedAt = 1740000000000): string =>
buildRuntimeMobileAgentStatusProjectionForTests({
'tab-0:leaf-0': makeEntry(0, {
state: 'working',
mainAgent: { state: 'done', ...(outcome ? { outcome } : {}), stateStartedAt }
})
})
expect(project('failure')).not.toBe(project())
// The main agent's own clock dates the failure on mobile, so moving it republishes too.
expect(project('failure', 1740000001000)).not.toBe(project('failure'))
})
})
describe('mobile agent-status projection program-status facet', () => {
it('republishes on facet content but not on a new observedAt alone', () => {
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const facet = { state: 'working', progress: 10, observedAt: 1 }
const base = buildRuntimeMobileAgentStatusProjectionForTests({
'tab-0:leaf-0': makeEntry(0, { programStatus: facet })
})
const reobserved = buildRuntimeMobileAgentStatusProjectionForTests({
'tab-0:leaf-0': makeEntry(0, { programStatus: { ...facet, observedAt: 2 } })
})
const progressed = buildRuntimeMobileAgentStatusProjectionForTests({
'tab-0:leaf-0': makeEntry(0, { programStatus: { ...facet, progress: 20 } })
})
expect(reobserved).toBe(base)
expect(progressed).not.toBe(base)
})
})
@@ -69,12 +69,6 @@ function worktreeSnapshot(
)
}
function publishedBrowserWorkspaceIds(store: ReturnType<typeof createTestStore>): string[] {
return (worktreeSnapshot(store)?.tabs ?? [])
.filter((tab) => tab.type === 'browser')
.map((tab) => (tab as { browserWorkspaceId?: string }).browserWorkspaceId ?? '')
}
function collectLayoutGroupIds(node: unknown, into: string[] = []): string[] {
if (!node || typeof node !== 'object') {
return into
@@ -111,37 +105,6 @@ function publishedUnifiedTabIds(store: ReturnType<typeof createTestStore>): Set<
// Real conversion actions must flip docLocation and publication predicate in one commit; no intermediate document publish or web-page suppression.
describe('mobile publish across an address-bar conversion', () => {
it('starts publishing a doc tab the moment it converts to web, and stops on the way back', async () => {
const store = await createStoreWithWorktree()
// Require ordinary URL presence so a broken browser publisher cannot pass on empty results.
const urlTab = store.getState().createBrowserTab(WORKTREE_ID, 'https://example.com/')
const docTab = store.getState().createBrowserTab(WORKTREE_ID, '', {
docLocation: DOC_LOCATION,
title: 'index.html',
browserRuntimeEnvironmentId: null
})
await settled()
const docPageId = store.getState().browserPagesByWorkspace[docTab.id]?.[0]?.id ?? ''
expect(publishedBrowserWorkspaceIds(store)).toEqual([urlTab.id])
const webPage = store.getState().convertBrowserPage(docPageId, {
kind: 'web',
url: 'https://converted.example/'
})
expect(webPage).not.toBeNull()
await settled()
expect(publishedBrowserWorkspaceIds(store).sort()).toEqual([urlTab.id, docTab.id].sort())
const docPage = store.getState().convertBrowserPage(webPage?.id ?? '', {
kind: 'workspace-doc',
docLocation: DOC_LOCATION
})
expect(docPage).not.toBeNull()
await settled()
expect(publishedBrowserWorkspaceIds(store)).toEqual([urlTab.id])
})
// Exercise group/layout projection independently: doc-only groups must not reference tabs withheld from phones.
it('holds the doc tab out of published groups and the layout, and admits it on conversion', async () => {
const store = await createStoreWithWorktree()
@@ -24,103 +24,11 @@ function makeState(overrides: Partial<AppState> = {}): AppState {
} as AppState
}
function makeOpenMarkdownFile(): AppState['openFiles'][number] {
return {
id: '/repo/README.md',
filePath: '/repo/README.md',
relativePath: 'README.md',
worktreeId: 'wt-1',
language: 'markdown',
mode: 'edit',
isDirty: false
}
}
afterEach(() => {
vi.restoreAllMocks()
})
describe('runtime mobile session sync key projection reuse', () => {
it('reuses serialized projections when only runtime pane titles change', () => {
const base = makeState({
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Codex working', customTitle: null }]
} as unknown as AppState['tabsByWorktree'],
runtimePaneTitlesByTabId: {
'term-1': { 1: 'Codex working' }
} as unknown as AppState['runtimePaneTitlesByTabId'],
openFiles: [makeOpenMarkdownFile()],
editorDrafts: { '/repo/README.md': '# draft' }
})
const baseKey = getRuntimeMobileSessionSyncKey(base)
const titleTick = makeState({
...base,
runtimePaneTitlesByTabId: {
'term-1': { 1: 'Codex spinner frame' }
} as unknown as AppState['runtimePaneTitlesByTabId']
})
const stringifySpy = vi.spyOn(JSON, 'stringify')
stringifySpy.mockClear()
const titleTickKey = getRuntimeMobileSessionSyncKey(titleTick, base, baseKey)
expect(stringifySpy).not.toHaveBeenCalled()
expect(titleTickKey.tabsProjection).toBe(baseKey.tabsProjection)
expect(titleTickKey.openFilesProjection).toBe(baseKey.openFilesProjection)
expect(titleTickKey.editorDraftsProjection).toBe(baseKey.editorDraftsProjection)
expect(runtimeMobileSessionSyncKeysEqual(baseKey, titleTickKey)).toBe(false)
})
it('only rebuilds the tab projection when a terminal tab title changes', () => {
const base = makeState({
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Codex working', customTitle: null }]
} as unknown as AppState['tabsByWorktree'],
openFiles: [makeOpenMarkdownFile()],
editorDrafts: { '/repo/README.md': '# draft' }
})
const baseKey = getRuntimeMobileSessionSyncKey(base)
const titleTick = makeState({
...base,
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Codex spinner frame', customTitle: null }]
} as unknown as AppState['tabsByWorktree']
})
const stringifySpy = vi.spyOn(JSON, 'stringify')
stringifySpy.mockClear()
const titleTickKey = getRuntimeMobileSessionSyncKey(titleTick, base, baseKey)
expect(stringifySpy).toHaveBeenCalledTimes(1)
expect(titleTickKey.tabsProjection).not.toBe(baseKey.tabsProjection)
expect(titleTickKey.openFilesProjection).toBe(baseKey.openFilesProjection)
expect(titleTickKey.editorDraftsProjection).toBe(baseKey.editorDraftsProjection)
expect(runtimeMobileSessionSyncKeysEqual(baseKey, titleTickKey)).toBe(false)
})
it('does not rebuild serialized projections when only activeTabId changes', () => {
const base = makeState({
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Codex working', customTitle: null }]
} as unknown as AppState['tabsByWorktree']
})
const baseKey = getRuntimeMobileSessionSyncKey(base)
const activated = makeState({
...base,
activeTabId: 'term-1'
})
const stringifySpy = vi.spyOn(JSON, 'stringify')
stringifySpy.mockClear()
const activatedKey = getRuntimeMobileSessionSyncKey(activated, base, baseKey)
expect(stringifySpy).not.toHaveBeenCalled()
expect(activatedKey.tabsProjection).toBe(baseKey.tabsProjection)
expect(activatedKey.openFilesProjection).toBe(baseKey.openFilesProjection)
expect(activatedKey.editorDraftsProjection).toBe(baseKey.editorDraftsProjection)
expect(runtimeMobileSessionSyncKeysEqual(baseKey, activatedKey)).toBe(false)
})
it('reuses unchanged worktree tab projections when one worktree title changes', () => {
const unchangedTitle = vi.fn(() => 'Unchanged agent')
const unchangedTab = {
@@ -157,61 +65,6 @@ describe('runtime mobile session sync key projection reuse', () => {
})
describe('mobile session snapshot reuse', () => {
it('reuses draft document versions when only runtime pane titles change', () => {
const draft = {
length: 1,
charCodeAt: vi.fn(() => 120)
} as unknown as string
const draftSpy = (draft as unknown as { charCodeAt: ReturnType<typeof vi.fn> }).charCodeAt
const base = makeState({
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Codex working', customTitle: null }]
} as unknown as AppState['tabsByWorktree'],
tabBarOrderByWorktree: { 'wt-1': ['term-1', '/repo/README.md'] },
runtimePaneTitlesByTabId: {
'term-1': { 1: 'Codex working' }
} as unknown as AppState['runtimePaneTitlesByTabId'],
openFiles: [makeOpenMarkdownFile()],
editorDrafts: { '/repo/README.md': draft },
activeFileId: '/repo/README.md'
})
buildMobileSessionTabSnapshots(base)
expect(draftSpy).toHaveBeenCalled()
draftSpy.mockClear()
buildMobileSessionTabSnapshots({
...base,
runtimePaneTitlesByTabId: {
'term-1': { 1: 'Codex spinner frame' }
} as unknown as AppState['runtimePaneTitlesByTabId']
})
expect(draftSpy).not.toHaveBeenCalled()
})
it('keeps (publicationEpoch, snapshotVersion) stable for byte-identical worktree content', () => {
const state = makeState({
tabsByWorktree: {
'wt-stable': [{ id: 'term-1', title: 'Codex working', customTitle: null }]
} as unknown as AppState['tabsByWorktree'],
terminalLayoutsByTabId: {
'term-1': {
root: { type: 'leaf', leafId: '11111111-1111-4111-8111-111111111111' },
activeLeafId: '11111111-1111-4111-8111-111111111111',
expandedLeafId: null
}
} as unknown as AppState['terminalLayoutsByTabId']
})
const first = buildMobileSessionTabSnapshots(state)[0]
const second = buildMobileSessionTabSnapshots(state)[0]
// Stable epoch/version gates mobile fan-out; no-op graph rebuilds must not republish every workspace.
expect(second?.publicationEpoch).toBe(first?.publicationEpoch)
expect(second?.snapshotVersion).toBe(first?.snapshotVersion)
})
it('bumps only the changed worktree version when a sibling stays byte-identical', () => {
const makeTwoWorktreeState = (title: string): AppState =>
makeState({
@@ -93,16 +93,6 @@ describe('syncRuntimeGraph in a layout-owner build', () => {
])
})
it("carries the parked pane's runtime title", async () => {
parkWatcher(7)
const graph = await publish(() => parkedState({ [TAB_ID]: { 7: 'codex [working]' } }))
expect(graph?.mountedLeaves).toEqual([
expect.objectContaining({ leafId: LEAF, paneTitle: 'codex [working]' })
])
})
it('sends nothing for a parked pane whose watcher was disposed', async () => {
parkWatcher(7, false)
@@ -88,30 +88,6 @@ function startSync(
}
describe('mobile session graph payload partition', () => {
it('withholds every unchanged worktree from the republished payload', async () => {
const syncWindowGraph = vi.fn().mockResolvedValue({})
startSync(makeState('a', 3), syncWindowGraph)
await flushSync()
expect(publishedWorktrees(syncWindowGraph.mock.calls[0])).toEqual([
'repo::/a-wt-0',
'repo::/a-wt-1',
'repo::/a-wt-2'
])
expect(withheldWorktrees(syncWindowGraph.mock.calls[0])).toEqual([])
scheduleRuntimeGraphSync()
await flushSync()
expect(syncWindowGraph).toHaveBeenCalledTimes(2)
expect(publishedWorktrees(syncWindowGraph.mock.calls[1])).toEqual([])
expect(withheldWorktrees(syncWindowGraph.mock.calls[1])).toEqual([
'repo::/a-wt-0',
'repo::/a-wt-1',
'repo::/a-wt-2'
])
})
it('publishes only the worktree whose snapshot changed', async () => {
const syncWindowGraph = vi.fn().mockResolvedValue({})
const { setState } = startSync(makeState('b', 3), syncWindowGraph)
@@ -152,24 +128,6 @@ describe('mobile session graph payload partition', () => {
])
})
it('resends everything when the first publication throws', async () => {
const syncWindowGraph = vi.fn().mockRejectedValueOnce(new Error('ipc closed'))
syncWindowGraph.mockResolvedValue({})
startSync(makeState('d', 3), syncWindowGraph)
await flushSync()
expect(publishedWorktrees(syncWindowGraph.mock.calls[0])).toHaveLength(3)
scheduleRuntimeGraphSync()
await flushSync()
expect(publishedWorktrees(syncWindowGraph.mock.calls[1])).toEqual([
'repo::/d-wt-0',
'repo::/d-wt-1',
'repo::/d-wt-2'
])
expect(withheldWorktrees(syncWindowGraph.mock.calls[1])).toEqual([])
})
it('republishes worktrees main reports it dropped after acknowledging them', async () => {
const syncWindowGraph = vi.fn().mockResolvedValue({})
startSync(makeState('e', 3), syncWindowGraph)
@@ -22,8 +22,6 @@ const {
buildRuntimeMobileAgentStatusProjectionForTests,
resetRuntimeMobileAgentStatusProjectionCacheForTests
} = await import('./sync-runtime-graph/agent-status-projection')
const { getRuntimeMobileSessionSyncKey, runtimeMobileSessionSyncKeysEqual } =
await import('./sync-runtime-graph/sync-key')
const {
resetRuntimeMobileSyncProjectionCachesForTests,
buildRuntimeMobileBrowserProjection,
@@ -265,26 +263,6 @@ describe('editor draft projection on the typing path', () => {
})
expect(before.chars / after.chars).toBeGreaterThan(DRAFT_FILE_COUNT - 0.1)
})
it('matches the uncached projection byte for byte across draft shapes', () => {
const draftCases: Record<string, string>[] = [
{},
{ 'file-a': '' },
{ 'file-a': 'hello' },
{ 'file-a': 'hello', 'file-b': 'world' },
{ 'file-b': 'world', 'file-a': 'hello' },
{ '2': 'numeric-like key', 'file-a': 'hello', '1': 'other' },
{ 'quote"and\\slash': 'body with "quotes" and \\ and \u{1f389}' },
{ 'file-a': 'hello', 'file-b': 'world', 'file-c': 'third' },
{ 'file-a': 'HELLO', 'file-c': 'third' }
]
for (const [index, draft] of draftCases.entries()) {
expect({ index, projection: buildRuntimeMobileEditorDraftsProjection(draft) }).toEqual({
index,
projection: referenceEditorDraftsProjection(draft)
})
}
})
})
describe('agent-status projection sort', () => {
@@ -327,21 +305,6 @@ describe('agent-status projection sort', () => {
sortedProjectionEntries(referenceAgentStatusProjection(map))
)
})
it('is deterministic for keys where locale and code-unit order disagree', () => {
const map: AppState['agentStatusByPaneKey'] = {}
for (const paneKey of ['b:leaf', 'A:leaf', 'a:leaf', 'á:leaf', 'B:leaf']) {
map[paneKey] = makeAgentStatusEntry({ paneKey })
}
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const first = buildRuntimeMobileAgentStatusProjectionForTests(map)
resetRuntimeMobileAgentStatusProjectionCacheForTests()
const second = buildRuntimeMobileAgentStatusProjectionForTests({ ...map })
expect(second).toBe(first)
expect(sortedProjectionEntries(first)).toEqual(
sortedProjectionEntries(referenceAgentStatusProjection(map))
)
})
})
describe('open-files and browser projections', () => {
@@ -408,178 +371,4 @@ describe('open-files and browser projections', () => {
// Only the 'wt-1' bucket re-serializes; 'wt-2' and every page bucket are reused.
expect(before / after).toBeGreaterThan(2.5)
})
it('matches the uncached projections byte for byte across shapes', () => {
const openFileCases: AppState['openFiles'][] = [
[] as unknown as AppState['openFiles'],
[makeOpenFile(0)] as unknown as AppState['openFiles'],
[makeOpenFile(0, { mirroredFromRuntimeSession: true })],
[makeOpenFile(0, { isDirty: true })] as unknown as AppState['openFiles'],
[
makeOpenFile(0, { isDirty: true, diffSource: 'working' }),
makeOpenFile(1, { mode: 'diff', markdownPreviewSourceFileId: 'file-0' }),
makeOpenFile(2, { isUntitled: true, deleteUntouchedOnClose: true, language: undefined })
] as unknown as AppState['openFiles']
]
for (const [index, openFiles] of openFileCases.entries()) {
expect({ index, projection: buildRuntimeMobileOpenFilesProjection(openFiles) }).toEqual({
index,
projection: referenceOpenFilesProjection(openFiles)
})
}
const browserCases: AppState[] = [
makeState({}),
makeState({ browserTabsByWorktree: { 'wt-1': [makeBrowserWorkspace(0)] } as never }),
makeState({
browserTabsByWorktree: {
'wt-1': [makeBrowserWorkspace(0, { title: undefined, loading: true })],
'3': [makeBrowserWorkspace(1)]
} as never,
browserPagesByWorkspace: {
'ws-0': [makeBrowserPage(0), makeBrowserPage(1, { canGoBack: true })],
'ws-1': []
} as never
}),
makeState({
browserTabsByWorktree: {} as never,
browserPagesByWorkspace: { 'ws-9': [makeBrowserPage(9, { url: 'a"b\\c' })] } as never
})
]
for (const [index, state] of browserCases.entries()) {
expect({ index, projection: buildRuntimeMobileBrowserProjection(state) }).toEqual({
index,
projection: referenceBrowserProjection(state)
})
}
})
})
describe('sync key transitions', () => {
it('fires on exactly the transitions the uncached projections would have fired on', () => {
const drafts = { 'file-0': 'aaa', 'file-1': 'bbb' }
const files = [makeOpenFile(0), makeOpenFile(1)] as unknown as AppState['openFiles']
const workspaces = [makeBrowserWorkspace(0)] as never
const status = {
'tab-0:leaf-0': makeAgentStatusEntry({ paneKey: 'tab-0:leaf-0' })
} as AppState['agentStatusByPaneKey']
const base = makeState({
editorDrafts: drafts,
openFiles: files,
browserTabsByWorktree: { 'wt-1': workspaces } as never,
browserPagesByWorkspace: { 'ws-0': [makeBrowserPage(0)] } as never,
agentStatusByPaneKey: status
})
// Each step returns the next state; the flag is whether a mobile-visible input really moved.
const steps: { name: string; next: (from: AppState) => AppState }[] = [
{ name: 'no-op re-spread', next: (from) => makeState({ ...from }) },
{
name: 'keystroke in one draft',
next: (from) =>
makeState({ ...from, editorDrafts: { ...from.editorDrafts, 'file-0': 'aaab' } })
},
{
name: 'draft reverted to the same text',
next: (from) =>
makeState({ ...from, editorDrafts: { ...from.editorDrafts, 'file-0': 'aaab' } })
},
{
name: 'draft removed',
next: (from) => makeState({ ...from, editorDrafts: { 'file-1': 'bbb' } })
},
{
name: 'isDirty flip',
next: (from) =>
makeState({
...from,
openFiles: [makeOpenFile(0, { isDirty: true }), from.openFiles[1]] as never
})
},
{
name: 'open-files re-spread with identical content',
next: (from) => makeState({ ...from, openFiles: [...from.openFiles] as never })
},
{
name: 'browser title tick',
next: (from) =>
makeState({
...from,
browserTabsByWorktree: {
'wt-1': [makeBrowserWorkspace(0, { title: 'new title' })]
} as never
})
},
{
name: 'browser page loading flip',
next: (from) =>
makeState({
...from,
browserPagesByWorkspace: {
'ws-0': [makeBrowserPage(0, { loading: true })]
} as never
})
},
{
name: 'agent-status ping with an unchanged payload',
next: (from) =>
makeState({
...from,
agentStatusByPaneKey: {
'tab-0:leaf-0': makeAgentStatusEntry({ paneKey: 'tab-0:leaf-0' })
} as never
})
},
{
name: 'agent-status prompt change',
next: (from) =>
makeState({
...from,
agentStatusByPaneKey: {
'tab-0:leaf-0': makeAgentStatusEntry({ paneKey: 'tab-0:leaf-0', prompt: 'new' })
} as never
})
},
{
name: 'second agent added',
next: (from) =>
makeState({
...from,
agentStatusByPaneKey: {
...from.agentStatusByPaneKey,
'tab-1:leaf-0': makeAgentStatusEntry({ paneKey: 'tab-1:leaf-0' })
} as never
})
}
]
const referenceTuple = (state: AppState): string[] => [
referenceEditorDraftsProjection(state.editorDrafts),
referenceOpenFilesProjection(state.openFiles),
referenceBrowserProjection(state),
referenceAgentStatusProjection(state.agentStatusByPaneKey ?? {})
]
resetRuntimeMobileSyncProjectionCachesForTests()
resetRuntimeMobileAgentStatusProjectionCacheForTests()
let previousState = base
let previousKey = getRuntimeMobileSessionSyncKey(base, undefined, undefined, false)
let previousReference = referenceTuple(base)
for (const step of steps) {
const state = step.next(previousState)
const key = getRuntimeMobileSessionSyncKey(state, previousState, previousKey, false)
const reference = referenceTuple(state)
const referenceChanged = reference.some((part, index) => part !== previousReference[index])
const keyChanged = !runtimeMobileSessionSyncKeysEqual(key, previousKey)
expect({ step: step.name, changed: keyChanged }).toEqual({
step: step.name,
changed: referenceChanged
})
previousState = state
previousKey = key
previousReference = reference
}
})
})
@@ -3,49 +3,6 @@ import { buildMobileSessionTabSnapshots } from './sync-runtime-graph/mobile-sess
import { registerRuntimeTerminalTab } from './mounted-terminal-tab-registry'
import type { AppState } from '../store/types'
// Count per-worktree slice reads in the build loop to measure work without timing flakiness.
function makeCountingState(worktreeCount: number): {
state: AppState
reads: () => number
resetReads: () => void
} {
let reads = 0
const tabsByWorktree: Record<string, unknown[]> = {}
for (let i = 0; i < worktreeCount; i++) {
tabsByWorktree[`repo::/wt-${i}`] = [
{ id: `term-${i}`, title: `Agent ${i}`, customTitle: null, type: 'terminal' }
]
}
const state = {
tabsByWorktree,
terminalLayoutsByTabId: {},
runtimePaneTitlesByTabId: {},
groupsByWorktree: {},
activeGroupIdByWorktree: {},
unifiedTabsByWorktree: {},
tabBarOrderByWorktree: {},
activeFileId: null,
activeFileIdByWorktree: {},
openFiles: [],
editorDrafts: {},
activeTabId: null,
agentStatusByPaneKey: {},
get browserTabsByWorktree() {
reads++
return {}
}
} as unknown as AppState
return {
state,
reads: () => reads,
resetReads: () => {
reads = 0
}
}
}
// tab.title getter counts snapshot rebuilds; slice reads alone would also pass with a cheap hoist.
function makeTitleCountingState(worktreeCount: number): {
state: AppState
@@ -129,45 +86,6 @@ function makeTitleCountingState(worktreeCount: number): {
}
describe('mobile session publication cost', () => {
it('does not redo per-worktree work when nothing changed', () => {
const WORKTREES = 300
const { state, reads, resetReads } = makeCountingState(WORKTREES)
buildMobileSessionTabSnapshots(state)
resetReads()
// Same state object, no mutation: a republish should do no per-worktree work.
buildMobileSessionTabSnapshots(state)
expect(reads()).toBeLessThan(WORKTREES / 10)
})
it('rebuilds only the worktrees whose inputs changed', () => {
const WORKTREES = 300
const { state, reads, resetReads } = makeCountingState(WORKTREES)
buildMobileSessionTabSnapshots(state)
resetReads()
// One worktree's tabs change — the other 299 are untouched.
const next = {
...state,
tabsByWorktree: {
...state.tabsByWorktree,
'repo::/wt-7': [
{ id: 'term-7', title: 'Agent 7 (done)', customTitle: null, type: 'terminal' }
]
},
get browserTabsByWorktree() {
return (state as unknown as { browserTabsByWorktree: unknown }).browserTabsByWorktree
}
} as unknown as AppState
buildMobileSessionTabSnapshots(next)
expect(reads()).toBeLessThan(WORKTREES / 10)
})
it('builds no worktree content when nothing changed', () => {
const { state, titleReads, resetTitleReads } = makeTitleCountingState(300)