Files
orca/src/shared/workspace-session-schema-field-coverage.test.ts
T
Neil 40ac8e5f9b fix(remote): keep a paired host's undelivered tab close across a reload (#30758)
* fix(remote): keep a paired host's undelivered tab close across a reload

* fix(remote): show a tab again when its held close expires

* fix(remote): one choke point shows every held close the store drops

* fix(remote): keep held closes while the pairing is unknown; tidy dispositions

* fix(remote): save a held close for a long path-named editor tab
2026-10-19 09:40:17 -07:00

123 lines
5.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { z } from 'zod'
import { parseWorkspaceSession, workspaceSessionStateSchema } from './workspace-session-schema'
import type { WorkspaceSessionState } from './workspace-session-state-types'
/**
* `workspaceSessionStateSchema` is the load boundary for BOTH partitions —
* `normalize-loaded-state-collections.ts` for `local`, `workspace-session-partitions.ts` for
* `ssh:<target>`. Zod strips unknown keys and the write side does not validate, so a field declared
* on WorkspaceSessionState but missing here reaches disk and is silently discarded on the next
* launch.
*
* That failure is invisible to any test living inside one app process, which is how the closed-tab
* tombstone map shipped unpersisted: its unit suites, its e2e oracle and an A/B control were all
* green, and the fix still did not survive a quit-and-relaunch.
*
* Two sibling tables (`profile-project-session-field-disposition.ts`,
* `workspace-session-host-field-ownership.ts`) already pin themselves with
* `satisfies Record<keyof WorkspaceSessionState, ...>`. This schema had no such guard and is the one
* that fell behind, so the ratchet below is the same shape.
*/
const PERSISTED_WORKSPACE_SESSION_FIELDS = {
activeRepoId: true,
activeWorkspaceKey: true,
activeWorkspaceExecutionHostId: true,
activeWorktreeId: true,
activeTabId: true,
tabsByWorktree: true,
terminalLayoutsByTabId: true,
localOnlyScrollbackByTabId: true,
activeWorktreeIdsOnShutdown: true,
openFilesByWorktree: true,
activeFileIdByWorktree: true,
markdownFrontmatterVisible: true,
browserTabsByWorktree: true,
browserPagesByWorkspace: true,
activeBrowserTabIdByWorktree: true,
clientHostedBrowserPagesByWorktree: true,
clientHostedBrowserCloseIntentsByEnvironment: true,
pairedTerminalTabCloseIntentsByEnvironment: true,
activeTabTypeByWorktree: true,
browserUrlHistory: true,
workspaceDocHistory: true,
activeTabIdByWorktree: true,
unifiedTabs: true,
tabGroups: true,
tabGroupLayouts: true,
activeGroupIdByWorktree: true,
activeConnectionIdsAtShutdown: true,
remoteSessionIdsByTabId: true,
lastVisitedAtByWorktreeId: true,
defaultTerminalTabsAppliedByWorktreeId: true,
sleepingAgentSessionsByPaneKey: true,
terminalPtyIncarnationsByPaneKey: true,
terminalTopologyRevisionByRepoId: true,
terminalSurfaceTombstonesByPaneKey: true,
closedTerminalTabTombstonesByTabId: true
} satisfies Record<keyof WorkspaceSessionState, true>
const MINIMAL_SESSION = {
activeRepoId: null,
activeWorktreeId: null,
activeTabId: null,
tabsByWorktree: {},
terminalLayoutsByTabId: {}
}
describe('workspaceSessionStateSchema field coverage', () => {
it('parses every field declared on WorkspaceSessionState', () => {
// The runtime half. `satisfies` above already makes a forgotten field a compile error; this
// reports it by name, and catches the reverse case where the list is updated but the schema
// is not.
// Why the cast: the export is annotated `z.ZodType<WorkspaceSessionState>`, which hides
// `.shape`. The value is a z.object; this reads its keys without widening the public type.
const schemaKeys = new Set(
Object.keys(
(workspaceSessionStateSchema as unknown as z.ZodObject<Record<string, z.ZodTypeAny>>).shape
)
)
const missing = Object.keys(PERSISTED_WORKSPACE_SESSION_FIELDS).filter(
(field) => !schemaKeys.has(field)
)
expect(missing, `declared on WorkspaceSessionState but absent from the load schema`).toEqual([])
})
it('round-trips the closed-tab tombstone map through parseWorkspaceSession', () => {
// The regression this file exists for. Recorded on close, written to disk, stripped on the next
// launch — so a close the transport never delivered resurrected after a quit-and-relaunch,
// which is precisely the reported shape ("every day I open orca and it opens more tabs").
const parsed = parseWorkspaceSession({
...MINIMAL_SESSION,
closedTerminalTabTombstonesByTabId: {
'tab-1': { closedAt: 1_700_000_000_000, worktreeId: 'repo:wt-1', reason: 'cleanup' },
// An older build's acknowledgement stamp is dropped; the record itself survives.
'tab-2': { closedAt: 1_700_000_000_000, worktreeId: 'repo:wt-1', ackRevision: 4 }
}
})
expect(parsed.ok).toBe(true)
expect(parsed.ok && parsed.value.closedTerminalTabTombstonesByTabId).toEqual({
'tab-1': { closedAt: 1_700_000_000_000, worktreeId: 'repo:wt-1', reason: 'cleanup' },
'tab-2': { closedAt: 1_700_000_000_000, worktreeId: 'repo:wt-1' }
})
})
it('salvages a malformed tombstone entry rather than dropping the whole map', () => {
const parsed = parseWorkspaceSession({
...MINIMAL_SESSION,
closedTerminalTabTombstonesByTabId: {
'tab-good': { closedAt: 1, worktreeId: 'repo:wt-1' },
'tab-bad': { closedAt: 'nope', worktreeId: 'repo:wt-1' }
}
})
expect(parsed.ok).toBe(true)
expect(
Object.keys((parsed.ok && parsed.value.closedTerminalTabTombstonesByTabId) || {})
).toEqual(['tab-good'])
})
})