perf: consume repository snapshots in checks

This commit is contained in:
Neil
2026-07-30 05:00:39 -07:00
parent 98134f6f7b
commit 6fbeb2abda
14 changed files with 863 additions and 46 deletions
@@ -107,6 +107,64 @@ describe('GitRepositorySnapshotOwner', () => {
expect(second?.freshness.upstream.revision).toBe(2)
})
it('reduces settled polling plus Checks demand from two physical loads to one per projection', async () => {
const pushTarget = {
remoteName: 'fork',
branchName: 'feature',
remoteUrl: 'ssh://git.example/repo',
remoteCreated: false
}
const upstreamResult = {
hasUpstream: true,
upstreamName: 'fork/feature',
ahead: 1,
behind: 0
}
const baselineOwner = new GitRepositorySnapshotOwner()
const baselineStatusLoads = vi.fn(async () => statusResult)
const baselineUpstreamLoads = vi.fn(async () => upstreamResult)
await baselineOwner.readStatus(
native,
'/repo',
defaultStatusIdentity,
undefined,
baselineStatusLoads
)
await baselineOwner.readUpstream(native, '/repo', pushTarget, baselineUpstreamLoads)
await baselineOwner.readStatus(
native,
'/repo',
defaultStatusIdentity,
undefined,
baselineStatusLoads
)
await baselineOwner.readUpstream(native, '/repo', pushTarget, baselineUpstreamLoads)
const migratedOwner = new GitRepositorySnapshotOwner()
const migratedStatusLoads = vi.fn(async () => statusResult)
const migratedUpstreamLoads = vi.fn(async () => upstreamResult)
await migratedOwner.readStatus(
native,
'/repo',
defaultStatusIdentity,
undefined,
migratedStatusLoads
)
await migratedOwner.readUpstream(native, '/repo', pushTarget, migratedUpstreamLoads)
const snapshot = migratedOwner.getSnapshot(
query(native, '/repo', defaultStatusIdentity, pushTarget)
)
expect(baselineStatusLoads).toHaveBeenCalledTimes(2)
expect(baselineUpstreamLoads).toHaveBeenCalledTimes(2)
expect(migratedStatusLoads).toHaveBeenCalledOnce()
expect(migratedUpstreamLoads).toHaveBeenCalledOnce()
expect(snapshot).toMatchObject({
freshness: { status: { state: 'fresh' }, upstream: { state: 'fresh' } },
upstream: { upstreamName: 'fork/feature' }
})
})
it('does not publish rejected reads and always retries settled work', async () => {
const owner = new GitRepositorySnapshotOwner()
const failure = new Error('read failed')
@@ -1,12 +1,20 @@
import type {
GitConflictOperation,
GitPushTarget,
GitStatusEntry,
GitStatusResult,
GitUpstreamStatus
} from '../../shared/types'
import type {
GitRepositoryProjectionFreshness,
GitRepositorySnapshot
} from '../../shared/git-repository-snapshot'
import { stableInFlightKey } from '../../shared/in-flight-promise-dedupe'
export type {
GitRepositoryProjectionFreshness,
GitRepositorySnapshot
} from '../../shared/git-repository-snapshot'
export const MAX_GIT_REPOSITORY_SNAPSHOT_STATUS_ENTRIES = 1_000
export const MAX_GIT_REPOSITORY_SNAPSHOT_IGNORED_PATHS = 1_000
export const MAX_GIT_REPOSITORY_SNAPSHOT_TEXT_CODE_UNITS = 64 * 1_024
@@ -24,43 +32,11 @@ export type GitRepositoryStatusIdentity = {
sharedLinkPaths: readonly string[]
}
export type GitRepositoryProjectionFreshness = Readonly<{
state: 'missing' | 'fresh' | 'stale' | 'failed' | 'placeholder'
generation: number
currentGeneration: number
revision: number | null
identity: string | null
}>
export type GitRepositorySnapshot = Readonly<{
revision: number
generatedAt: number
repositoryIdentity: Readonly<{ head: string | null; branch: string | null }>
status: Readonly<{
entries: readonly Readonly<GitStatusEntry>[]
didHitLimit: boolean
statusLength: number | null
ignoredPaths: readonly string[]
lineStatsState: 'missing' | 'complete' | 'skipped-at-limit'
retentionTruncated: boolean
}>
upstream: Readonly<GitUpstreamStatus> | null
conflicts: GitConflictOperation | null
worktreeGraphVersion: number
freshness: Readonly<{
repositoryIdentity: GitRepositoryProjectionFreshness
status: GitRepositoryProjectionFreshness
upstream: GitRepositoryProjectionFreshness
conflicts: GitRepositoryProjectionFreshness
worktreeGraph: GitRepositoryProjectionFreshness
}>
}>
export type GitRepositoryStatusProjection = {
repositoryIdentity: Readonly<{ head: string | null; branch: string | null }>
status: GitRepositorySnapshot['status']
upstream: Readonly<GitUpstreamStatus> | null
conflicts: GitConflictOperation
conflicts: GitStatusResult['conflictOperation']
}
export type GitRepositorySnapshotQuery = {
+109
View File
@@ -21,6 +21,7 @@ const {
lstatMock,
commitChangesMock,
getStatusMock,
getGitRepositorySnapshotMock,
abortMergeMock,
abortRebaseMock,
getDiffMock,
@@ -66,6 +67,7 @@ const {
lstatMock: vi.fn(),
commitChangesMock: vi.fn(),
getStatusMock: vi.fn(),
getGitRepositorySnapshotMock: vi.fn(),
abortMergeMock: vi.fn(),
abortRebaseMock: vi.fn(),
getDiffMock: vi.fn(),
@@ -139,6 +141,7 @@ vi.mock('../local-downloaded-folder-promotion', () => ({
vi.mock('../git/status', () => ({
commitChanges: commitChangesMock,
getStatus: getStatusMock,
getGitRepositorySnapshot: getGitRepositorySnapshotMock,
abortMerge: abortMergeMock,
abortRebase: abortRebaseMock,
getDiff: getDiffMock,
@@ -287,6 +290,7 @@ describe('registerFilesystemHandlers', () => {
recordCrashBreadcrumbMock,
commitChangesMock,
getStatusMock,
getGitRepositorySnapshotMock,
abortMergeMock,
abortRebaseMock,
getDiffMock,
@@ -1521,6 +1525,111 @@ describe('registerFilesystemHandlers', () => {
})
})
it('reads exact local and SSH repository snapshot identities without running Git', async () => {
const pushTarget = {
remoteName: 'fork',
branchName: 'feature/checks',
remoteUrl: 'git@github.com:org/repo.git',
remoteCreated: false
}
const snapshot = { revision: 3 }
const sharedStore = {
...store,
getRepos: () => [{ ...store.getRepos()[0], symlinkPaths: ['node_modules'] }],
getAllWorktreeMeta: () => ({ [`repo-1::${WORKTREE_FEATURE_PATH}`]: {} })
}
registerWorktreeRootsForRepo(sharedStore as never, 'repo-1', [REPO_PATH, WORKTREE_FEATURE_PATH])
getGitRepositorySnapshotMock.mockReturnValue(snapshot)
const sshProvider = { getRepositorySnapshot: vi.fn().mockReturnValue(snapshot) }
getSshGitProviderMock.mockReturnValue(sshProvider)
registerFilesystemHandlers(sharedStore as never)
const args = {
includeIgnored: true,
bypassEffectiveUpstreamNegativeCache: true,
reuseLineStats: true,
pushTarget
}
await expect(
handlers.get('git:repositorySnapshot')!(null, {
worktreePath: WORKTREE_FEATURE_PATH,
...args
})
).resolves.toBe(snapshot)
await expect(
handlers.get('git:repositorySnapshot')!(null, {
worktreePath: '/remote/repo',
connectionId: 'ssh-1',
...args
})
).resolves.toBe(snapshot)
expect(getGitRepositorySnapshotMock).toHaveBeenCalledWith(
WORKTREE_FEATURE_PATH,
{
includeIgnored: true,
bypassEffectiveUpstreamNegativeCache: true,
reuseLineStats: true,
sharedLinkPaths: ['node_modules']
},
pushTarget
)
expect(sshProvider.getRepositorySnapshot).toHaveBeenCalledWith(
'/remote/repo',
{
includeIgnored: true,
bypassEffectiveUpstreamNegativeCache: true,
reuseLineStats: true
},
pushTarget
)
expect(getStatusMock).not.toHaveBeenCalled()
})
it('reads repository snapshots from the exact configured WSL distro', async () => {
await withPlatform('win32', async () => {
const wslStore = {
...store,
getRepos: () => [
{
id: 'repo-1',
path: WORKTREE_FEATURE_PATH,
displayName: 'repo',
badgeColor: '#000',
addedAt: 0
}
],
getProjects: () => [
{
id: 'project-1',
sourceRepoIds: ['repo-1'],
localWindowsRuntimePreference: { kind: 'wsl', distro: 'Ubuntu' }
}
],
getSettings: () => ({
workspaceDir: WORKSPACE_DIR,
localWindowsRuntimeDefault: { kind: 'windows-host' }
})
}
registerWorktreeRootsForRepo(wslStore as never, 'repo-1', [WORKTREE_FEATURE_PATH])
getGitRepositorySnapshotMock.mockReturnValue({ revision: 1 })
registerFilesystemHandlers(wslStore as never)
await handlers.get('git:repositorySnapshot')!(null, {
worktreePath: WORKTREE_FEATURE_PATH
})
expect(getGitRepositorySnapshotMock).toHaveBeenCalledWith(
WORKTREE_FEATURE_PATH,
{
includeIgnored: false,
wslDistro: 'Ubuntu'
},
undefined
)
})
})
it('aborts tokenized local status without crossing renderer boundaries', async () => {
registerWorktreeRootsForRepo(store as never, 'repo-1', [REPO_PATH, WORKTREE_FEATURE_PATH])
const statusSignals: AbortSignal[] = []
+48
View File
@@ -29,6 +29,7 @@ import type {
TuiAgent
} from '../../shared/types'
import type { GitHistoryOptions, GitHistoryResult } from '../../shared/git-history'
import type { GitRepositorySnapshot } from '../../shared/git-repository-snapshot'
import type { SshMutationExpectation } from '../../shared/ssh-types'
import { assertSshMutationExpectation } from '../ssh/ssh-connection-generation'
import {
@@ -41,6 +42,7 @@ import {
} from '../../shared/text-search'
import {
getStatus,
getGitRepositorySnapshot,
getSubmoduleStatus,
abortMerge,
abortRebase,
@@ -1141,6 +1143,52 @@ export function registerFilesystemHandlers(
gitStatusCancellations.cancel(event, args.requestToken)
})
ipcMain.handle(
'git:repositorySnapshot',
async (
_event,
args: {
worktreePath: string
connectionId?: string
includeIgnored?: boolean
bypassEffectiveUpstreamNegativeCache?: boolean
reuseLineStats?: boolean
pushTarget?: GitPushTarget
}
): Promise<GitRepositorySnapshot | null> => {
if (args.pushTarget) {
assertGitPushTargetShape(args.pushTarget)
}
const options = {
includeIgnored: args.includeIgnored ?? false,
...(args.reuseLineStats === true ? { reuseLineStats: true } : {}),
...(args.bypassEffectiveUpstreamNegativeCache === true
? { bypassEffectiveUpstreamNegativeCache: true }
: {})
}
if (args.connectionId) {
const provider = getSshGitProvider(args.connectionId)
if (!provider) {
throw new Error(SSH_GIT_PROVIDER_UNAVAILABLE_MESSAGE)
}
return provider.getRepositorySnapshot(args.worktreePath, options, args.pushTarget)
}
const worktreePath = await resolveRegisteredWorktreePath(args.worktreePath, store)
const repo = getLocalRepoForRegisteredWorktree(store, args.worktreePath, worktreePath)
const gitOptions = getLocalGitOptionsForRepo(store, repo)
const sharedLinkPaths = repo ? getWorktreeSharedLinkPaths(repo) : []
return getGitRepositorySnapshot(
worktreePath,
{
...options,
...gitOptions,
...(sharedLinkPaths.length > 0 ? { sharedLinkPaths } : {})
},
args.pushTarget
)
}
)
// Why: parent status reports only one gitlink row per submodule; fetch inner per-file changes from the submodule's own worktree.
ipcMain.handle(
'git:submoduleStatus',
@@ -1161,6 +1161,70 @@ describe('SshGitProvider', () => {
expect(replacement.getRepositorySnapshot('/home/user/repo')).toBeNull()
})
it('reduces settled polling plus Checks demand from two RPCs to one per projection', async () => {
const pushTarget = {
remoteName: 'fork',
branchName: 'feature',
remoteUrl: 'ssh://git.example/repo',
remoteCreated: false
}
mux.request.mockImplementation((method) => {
if (method === 'git.status') {
return Promise.resolve({
entries: [],
conflictOperation: 'unknown',
head: 'abc123',
branch: 'feature'
})
}
if (method === 'git.upstreamStatus') {
return Promise.resolve({
hasUpstream: true,
upstreamName: 'fork/feature',
ahead: 1,
behind: 0
})
}
return Promise.resolve(undefined)
})
await provider.getStatus('/home/user/repo')
await provider.getUpstreamStatus('/home/user/repo', pushTarget)
await provider.getStatus('/home/user/repo')
await provider.getUpstreamStatus('/home/user/repo', pushTarget)
const baselineCounts = {
status: mux.request.mock.calls.filter(([method]) => method === 'git.status').length,
upstream: mux.request.mock.calls.filter(([method]) => method === 'git.upstreamStatus').length
}
mux.request.mockClear()
const migratedProvider = new SshGitProvider('conn-1', mux as never)
await migratedProvider.getStatus('/home/user/repo')
await migratedProvider.getUpstreamStatus('/home/user/repo', pushTarget)
const snapshot = migratedProvider.getRepositorySnapshot('/home/user/repo', {}, pushTarget)
const migratedCounts = {
status: mux.request.mock.calls.filter(([method]) => method === 'git.status').length,
upstream: mux.request.mock.calls.filter(([method]) => method === 'git.upstreamStatus').length
}
expect(baselineCounts).toEqual({ status: 2, upstream: 2 })
expect(migratedCounts).toEqual({ status: 1, upstream: 1 })
expect(snapshot).toMatchObject({
freshness: { status: { state: 'fresh' }, upstream: { state: 'fresh' } },
upstream: { upstreamName: 'fork/feature' }
})
expect(mux.request).toHaveBeenNthCalledWith(
1,
'git.status',
{ worktreePath: '/home/user/repo' },
{ signal: expect.any(AbortSignal) }
)
expect(mux.request).toHaveBeenNthCalledWith(2, 'git.upstreamStatus', {
worktreePath: '/home/user/repo',
pushTarget
})
})
it('marks retained snapshots stale across the existing SSH mutation fence', async () => {
mux.request.mockImplementation((method) => {
if (method === 'git.status') {
+9
View File
@@ -282,6 +282,7 @@ import type {
import type { SetupScriptImportCandidate } from '../shared/setup-script-imports'
import type { GitHistoryOptions, GitHistoryResult } from '../shared/git-history'
import type { GitRepositorySnapshot } from '../shared/git-repository-snapshot'
import type { PublicKnownRuntimeEnvironment } from '../shared/runtime-environments'
import type { EphemeralVmRecipeDoctorResult } from '../shared/ephemeral-vm-recipes'
import type { EphemeralVmRecipeResultWarning } from '../shared/ephemeral-vm-recipe-diagnostics'
@@ -2837,6 +2838,14 @@ export type PreloadApi = {
requestToken?: string
}) => Promise<GitStatusResult>
cancelStatus: (args: { requestToken: string }) => Promise<void>
repositorySnapshot: (args: {
worktreePath: string
connectionId?: string
includeIgnored?: boolean
bypassEffectiveUpstreamNegativeCache?: boolean
reuseLineStats?: boolean
pushTarget?: GitPushTarget
}) => Promise<GitRepositorySnapshot | null>
submoduleStatus: (args: {
worktreePath: string
submodulePath: string
+9
View File
@@ -14,6 +14,7 @@ import type { AgentHookInstallStatus } from '../shared/agent-hook-types'
import type { CodexConfigSyncStatus } from '../shared/codex-config-sync-types'
import type { TerminalPaneSplitSource } from '../shared/feature-education-telemetry'
import type { ProjectExecutionRuntimeResolution } from '../shared/project-execution-runtime'
import type { GitRepositorySnapshot } from '../shared/git-repository-snapshot'
import type { StartupCommandDelivery } from '../shared/codex-startup-delivery'
import type {
AgentProviderSessionMetadata,
@@ -3175,6 +3176,14 @@ const api = {
}): Promise<unknown> => ipcRenderer.invoke('git:status', args),
cancelStatus: (args: { requestToken: string }): Promise<void> =>
ipcRenderer.invoke('git:cancelStatus', args),
repositorySnapshot: (args: {
worktreePath: string
connectionId?: string
includeIgnored?: boolean
bypassEffectiveUpstreamNegativeCache?: boolean
reuseLineStats?: boolean
pushTarget?: GitPushTarget
}): Promise<GitRepositorySnapshot | null> => ipcRenderer.invoke('git:repositorySnapshot', args),
submoduleStatus: (args: {
worktreePath: string
submodulePath: string
@@ -122,8 +122,10 @@ import {
getRuntimeGitUpstreamStatus,
type RuntimeGeneratePullRequestFieldsOverrides
} from '@/runtime/runtime-git-client'
import { getDesktopGitRepositorySnapshot } from '@/runtime/desktop-git-repository-snapshot-client'
import {
buildChecksPanelGitStatusContextKey,
readChecksPanelRepositorySnapshot,
readChecksPanelPublishActionGitStatus,
readChecksPanelGitStatusSnapshot,
readChecksPanelRefreshGitIdentitySnapshot,
@@ -1511,6 +1513,33 @@ export default function ChecksPanel(): React.JSX.Element {
connectionId
}
void (async () => {
let repositorySnapshot = readChecksPanelRepositorySnapshot(
await getDesktopGitRepositorySnapshot(
context,
activeWorktreePushTarget ? { pushTarget: activeWorktreePushTarget } : undefined
),
requestContextKey,
branch
)
if (!repositorySnapshot) {
repositorySnapshot = readChecksPanelRepositorySnapshot(
await getDesktopGitRepositorySnapshot(context, {
reuseLineStats: true,
...(activeWorktreePushTarget ? { pushTarget: activeWorktreePushTarget } : {})
}),
requestContextKey,
branch
)
}
if (repositorySnapshot) {
if (
!stale &&
shouldCommitChecksPanelGitStatusSnapshot(panelContextKeyRef.current, requestContextKey)
) {
updateWorktreeGitIdentity(activeWorktreeId, repositorySnapshot.gitIdentity ?? {})
}
return repositorySnapshot
}
const status = await getRuntimeGitStatus(context)
if (
!stale &&
@@ -1533,22 +1562,22 @@ export default function ChecksPanel(): React.JSX.Element {
) {
freshRemoteStatus = await getRuntimeGitUpstreamStatus(context)
}
return { status, remoteStatus: freshRemoteStatus }
return {
contextKey: requestContextKey,
hasUncommittedChanges: status.entries.length > 0,
remoteStatus: freshRemoteStatus,
gitIdentity: {
head: status.head,
branch: status.branch ?? (status.head ? null : undefined)
}
}
})()
.then(({ status, remoteStatus }) => {
.then((snapshot) => {
if (
!stale &&
shouldCommitChecksPanelGitStatusSnapshot(panelContextKeyRef.current, requestContextKey)
) {
setGitStatusSnapshot({
contextKey: requestContextKey,
hasUncommittedChanges: status.entries.length > 0,
remoteStatus,
gitIdentity: {
head: status.head,
branch: status.branch ?? (status.head ? null : undefined)
}
})
setGitStatusSnapshot(snapshot)
// A fresh probe succeeded, so this context is no longer in the "could not check branch status" state.
setGitStatusProbeErrorContextKey((key) => (key === requestContextKey ? null : key))
}
@@ -1,6 +1,8 @@
import { describe, expect, it } from 'vitest'
import type { GitRepositorySnapshot } from '../../../../shared/git-repository-snapshot'
import {
buildChecksPanelGitStatusContextKey,
readChecksPanelRepositorySnapshot,
readChecksPanelPublishActionGitStatus,
readChecksPanelRefreshGitIdentitySnapshot,
readChecksPanelGitStatusSnapshot,
@@ -26,6 +28,40 @@ const SNAPSHOT: ChecksPanelGitStatusSnapshot = {
}
}
function repositorySnapshot(overrides: Partial<GitRepositorySnapshot> = {}): GitRepositorySnapshot {
const freshness = {
state: 'fresh' as const,
generation: 2,
currentGeneration: 2,
revision: 4,
identity: 'identity'
}
return {
revision: 4,
generatedAt: 100,
repositoryIdentity: { head: 'abc123', branch: 'feature/checks' },
status: {
entries: [{ path: 'src/app.ts', status: 'modified', area: 'unstaged' }],
didHitLimit: false,
statusLength: 1,
ignoredPaths: [],
lineStatsState: 'complete',
retentionTruncated: false
},
upstream: { hasUpstream: true, upstreamName: 'origin/main', ahead: 1, behind: 0 },
conflicts: null,
worktreeGraphVersion: 0,
freshness: {
repositoryIdentity: freshness,
status: freshness,
upstream: freshness,
conflicts: freshness,
worktreeGraph: { ...freshness, state: 'placeholder' }
},
...overrides
}
}
describe('buildChecksPanelGitStatusContextKey', () => {
it('changes when an explicit push target changes', () => {
const base = {
@@ -50,6 +86,26 @@ describe('buildChecksPanelGitStatusContextKey', () => {
)
})
it('distinguishes every explicit target field including absent versus false', () => {
const base = {
repoId: 'repo-1',
worktreeId: 'worktree-1',
worktreePath: 'repo-worktree',
branch: 'feature/checks',
runtimeEnvironmentId: null,
repoConnectionId: null
}
const context = (
pushTarget: NonNullable<
Parameters<typeof buildChecksPanelGitStatusContextKey>[0]['pushTarget']
>
) => buildChecksPanelGitStatusContextKey({ ...base, pushTarget })
const target = { remoteName: 'origin', branchName: 'feature/checks' }
expect(context(target)).not.toBe(context({ ...target, remoteCreated: false }))
expect(context(target)).not.toBe(context({ ...target, remoteUrl: 'ssh://git.example/repo' }))
})
it('changes when linked hosted review metadata changes', () => {
const base = {
repoId: 'repo-1',
@@ -112,6 +168,92 @@ describe('buildChecksPanelGitStatusContextKey', () => {
})
})
describe('readChecksPanelRepositorySnapshot', () => {
it('projects a complete current-generation host snapshot without mutating it', () => {
const snapshot = repositorySnapshot()
expect(readChecksPanelRepositorySnapshot(snapshot, 'panel-context', 'feature/checks')).toEqual({
contextKey: 'panel-context',
hasUncommittedChanges: true,
remoteStatus: snapshot.upstream,
gitIdentity: { head: 'abc123', branch: 'feature/checks' }
})
expect(snapshot.status.entries).toHaveLength(1)
})
it.each(['missing', 'stale', 'failed'] as const)('rejects a %s status projection', (state) => {
const snapshot = repositorySnapshot({
freshness: {
...repositorySnapshot().freshness,
status: { ...repositorySnapshot().freshness.status, state }
}
})
expect(
readChecksPanelRepositorySnapshot(snapshot, 'panel-context', 'feature/checks')
).toBeNull()
})
it.each(['missing', 'stale', 'failed'] as const)('rejects a %s upstream projection', (state) => {
expect(
readChecksPanelRepositorySnapshot(
repositorySnapshot({
freshness: {
...repositorySnapshot().freshness,
upstream: { ...repositorySnapshot().freshness.upstream, state }
}
}),
'panel-context',
'feature/checks'
)
).toBeNull()
})
it('rejects missing upstream data and retention-truncated status projections', () => {
expect(
readChecksPanelRepositorySnapshot(
repositorySnapshot({
upstream: null
}),
'panel-context',
'feature/checks'
)
).toBeNull()
expect(
readChecksPanelRepositorySnapshot(
repositorySnapshot({
status: { ...repositorySnapshot().status, retentionTruncated: true }
}),
'panel-context',
'feature/checks'
)
).toBeNull()
})
it('rejects an ambiguous embedded upstream projection', () => {
expect(
readChecksPanelRepositorySnapshot(
repositorySnapshot({
upstream: {
hasUpstream: true,
upstreamName: 'origin/main',
ahead: 1,
behind: 1
}
}),
'panel-context',
'feature/checks'
)
).toBeNull()
})
it('rejects a fresh projection from another branch', () => {
expect(
readChecksPanelRepositorySnapshot(repositorySnapshot(), 'panel-context', 'feature/other')
).toBeNull()
})
})
describe('readChecksPanelGitStatusSnapshot', () => {
it('returns status inputs for the matching panel context', () => {
expect(readChecksPanelGitStatusSnapshot(SNAPSHOT, SNAPSHOT.contextKey)).toEqual({
@@ -1,4 +1,5 @@
import type { GitPushTarget, GitStatusEntry, GitUpstreamStatus } from '../../../../shared/types'
import type { GitRepositorySnapshot } from '../../../../shared/git-repository-snapshot'
export type ChecksPanelGitStatusContextInput = {
repoId: string | null | undefined
@@ -70,12 +71,42 @@ export function buildChecksPanelGitStatusContextKey(
remoteName: input.pushTarget.remoteName,
branchName: input.pushTarget.branchName,
remoteUrl: input.pushTarget.remoteUrl ?? null,
remoteCreated: input.pushTarget.remoteCreated ?? false
remoteCreated: input.pushTarget.remoteCreated ?? null
}
: null
})
}
export function readChecksPanelRepositorySnapshot(
snapshot: GitRepositorySnapshot | null,
contextKey: string,
expectedBranch: string
): ChecksPanelGitStatusSnapshot | null {
if (
!snapshot ||
snapshot.freshness.status.state !== 'fresh' ||
snapshot.freshness.upstream.state !== 'fresh' ||
snapshot.status.retentionTruncated ||
!snapshot.upstream ||
(snapshot.upstream.ahead > 0 &&
snapshot.upstream.behind > 0 &&
snapshot.upstream.behindCommitsArePatchEquivalent === undefined) ||
canonicalBranchIdentity(snapshot.repositoryIdentity.branch) !==
canonicalBranchIdentity(expectedBranch)
) {
return null
}
return {
contextKey,
hasUncommittedChanges: snapshot.status.entries.length > 0,
remoteStatus: snapshot.upstream,
gitIdentity: {
head: snapshot.repositoryIdentity.head ?? undefined,
branch: snapshot.repositoryIdentity.branch
}
}
}
export function shouldPollChecksPanelRuntimeSshStatus(input: {
isPanelVisible: boolean
runtimeEnvironmentId: string | null
@@ -0,0 +1,91 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getDesktopGitRepositorySnapshot } from './desktop-git-repository-snapshot-client'
const repositorySnapshot = vi.fn()
const runtimeEnvironmentCall = vi.fn()
beforeEach(() => {
repositorySnapshot.mockReset()
runtimeEnvironmentCall.mockReset()
vi.stubGlobal('window', {
api: {
git: { repositorySnapshot },
runtimeEnvironments: { call: runtimeEnvironmentCall }
}
})
})
describe('getDesktopGitRepositorySnapshot', () => {
it('reads the exact desktop host snapshot identity', async () => {
repositorySnapshot.mockResolvedValue(null)
const pushTarget = {
remoteName: 'fork',
branchName: 'feature/checks',
remoteUrl: 'ssh://git.example/repo',
remoteCreated: false
}
await expect(
getDesktopGitRepositorySnapshot(
{
settings: { activeRuntimeEnvironmentId: null },
worktreeId: 'wt-1',
worktreePath: '/repo',
connectionId: 'ssh-1'
},
{
includeIgnored: true,
bypassEffectiveUpstreamNegativeCache: true,
reuseLineStats: true,
pushTarget
}
)
).resolves.toBeNull()
expect(repositorySnapshot).toHaveBeenCalledWith({
worktreePath: '/repo',
connectionId: 'ssh-1',
includeIgnored: true,
bypassEffectiveUpstreamNegativeCache: true,
reuseLineStats: true,
pushTarget
})
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
})
it('uses the backing folder path for a local folder workspace', async () => {
repositorySnapshot.mockResolvedValue(null)
const workspaceId = '123e4567-e89b-12d3-a456-426614174000'
await getDesktopGitRepositorySnapshot({
settings: { activeRuntimeEnvironmentId: null },
worktreeId: `folder-repo::/home/user::workspace:${workspaceId}`,
worktreePath: `/home/user::workspace:${workspaceId}`
})
expect(repositorySnapshot).toHaveBeenCalledWith({
worktreePath: '/home/user',
connectionId: undefined
})
})
it('leaves active runtime-environment transport to a later slice', async () => {
await expect(
getDesktopGitRepositorySnapshot(
{
settings: { activeRuntimeEnvironmentId: 'env-1' },
worktreeId: 'wt-1',
worktreePath: '/repo'
},
{
includeIgnored: true,
bypassEffectiveUpstreamNegativeCache: true,
reuseLineStats: true
}
)
).resolves.toBeNull()
expect(repositorySnapshot).not.toHaveBeenCalled()
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,36 @@
import type { GitPushTarget } from '../../../shared/types'
import type { GitRepositorySnapshot } from '../../../shared/git-repository-snapshot'
import { splitWorktreeIdForFilesystem } from '../../../shared/worktree-id'
import { getActiveRuntimeTarget } from './runtime-rpc-client'
import type { RuntimeGitContext } from './runtime-git-client'
function resolveDesktopSnapshotWorktreePath(context: RuntimeGitContext): string {
return context.worktreeId
? (splitWorktreeIdForFilesystem(context.worktreeId)?.worktreePath ?? context.worktreePath)
: context.worktreePath
}
export async function getDesktopGitRepositorySnapshot(
context: RuntimeGitContext,
options: {
includeIgnored?: boolean
bypassEffectiveUpstreamNegativeCache?: boolean
reuseLineStats?: boolean
pushTarget?: GitPushTarget
} = {}
): Promise<GitRepositorySnapshot | null> {
const target = getActiveRuntimeTarget(context.settings)
if (target.kind !== 'local' && context.worktreeId) {
return null
}
return window.api.git.repositorySnapshot({
worktreePath: resolveDesktopSnapshotWorktreePath(context),
connectionId: context.connectionId,
...(options.includeIgnored ? { includeIgnored: true } : {}),
...(options.bypassEffectiveUpstreamNegativeCache
? { bypassEffectiveUpstreamNegativeCache: true }
: {}),
...(options.reuseLineStats ? { reuseLineStats: true } : {}),
...(options.pushTarget ? { pushTarget: options.pushTarget } : {})
})
}
+33
View File
@@ -0,0 +1,33 @@
import type { GitConflictOperation, GitStatusEntry, GitUpstreamStatus } from './types'
export type GitRepositoryProjectionFreshness = Readonly<{
state: 'missing' | 'fresh' | 'stale' | 'failed' | 'placeholder'
generation: number
currentGeneration: number
revision: number | null
identity: string | null
}>
export type GitRepositorySnapshot = Readonly<{
revision: number
generatedAt: number
repositoryIdentity: Readonly<{ head: string | null; branch: string | null }>
status: Readonly<{
entries: readonly Readonly<GitStatusEntry>[]
didHitLimit: boolean
statusLength: number | null
ignoredPaths: readonly string[]
lineStatsState: 'missing' | 'complete' | 'skipped-at-limit'
retentionTruncated: boolean
}>
upstream: Readonly<GitUpstreamStatus> | null
conflicts: GitConflictOperation | null
worktreeGraphVersion: number
freshness: Readonly<{
repositoryIdentity: GitRepositoryProjectionFreshness
status: GitRepositoryProjectionFreshness
upstream: GitRepositoryProjectionFreshness
conflicts: GitRepositoryProjectionFreshness
worktreeGraph: GitRepositoryProjectionFreshness
}>
}>
@@ -0,0 +1,182 @@
# Phase 2 Checks repository-snapshot consumer — 2026-07-30
## Decision
Retain a read-only desktop projection query and migrate automatic Checks eligibility reads to the
host-owned `GitRepositorySnapshot` seam. When active polling has already published a complete
current-generation status and upstream projection, Checks now uses that immutable projection
instead of repeating status and upstream work. Manual Checks Refresh and every incomplete,
stale, failed, truncated, branch-mismatched, or runtime-routed case keep the existing fresh path.
Checkpoint: `98134f6f7b8921f0964d99395a2fa91b91ea806e`.
This is intentionally not a TTL cache. Existing status and upstream APIs still run fresh after
settlement, and the new IPC query never runs Git or an SSH RPC.
## Consumer boundary
The active polling path remains the producer. Its existing native, exact-WSL-distro, and
per-`SshGitProvider` owners publish status and upstream projections through
`GitRepositorySnapshotOwner`; existing pre/post mutation fences continue to mark those projections
stale and suppress late publication into the current generation.
Automatic Checks now:
1. queries the exact desktop owner identity;
2. admits only `fresh` status and upstream projections;
3. rejects retention-truncated status, a missing upstream projection, and a branch mismatch; and
4. falls back to the unchanged fresh status/upstream path when admission fails.
The query checks both existing polling status identities: the ordinary identity and the safety
poll identity with `reuseLineStats: true`. These remain separate owner identities; the second
lookup is read-only and does not merge them. Identity also retains worktree path, native versus
exact WSL distro, shared-link paths, `includeIgnored`, negative-cache bypass, and every explicit
push-target field. The Checks context key now preserves absent `remoteCreated` versus explicit
`false`, as well as absent versus present `remoteUrl`.
SSH lookup is routed to the current provider instance for `connectionId`, so replacement and
reconnect incarnations cannot share projections. Local folder-workspace IDs are reduced to their
registered backing folder path before IPC. Active runtime-environment ownership returns `null`
from the desktop client and uses the existing runtime Git path; runtime/mobile snapshot transport
is not added in this slice.
Manual Checks Refresh remains on the existing fresh APIs. No Git command, SSH RPC method or
payload, retry timer, visibility interval, slow-task scheduler, signal, mutation wrapper, relay
routing, or provider-specific review behavior changed.
## Deterministic physical-owner measurement
The focused owner and SSH-provider tests model the same settled overlap: active polling completes
status plus upstream publication, then automatic Checks requests the same identity in that refresh
horizon.
| Boundary | A — fresh Checks | B — snapshot query |
| ------------------------------------------ | ---------------: | -----------------: |
| Local/WSL physical status loads | 2 | 1 |
| Local/WSL physical explicit-upstream loads | 2 | 1 |
| SSH `git.status` RPCs | 2 | 1 |
| SSH `git.upstreamStatus` RPCs | 2 | 1 |
The B query itself adds two read-only owner lookups in the worst case—ordinary and
`reuseLineStats` identities—but zero subprocesses and zero mux requests. If neither snapshot is
admissible, the original fresh load count and behavior are preserved.
The physical status command remains:
```text
git -c core.quotePath=false status --porcelain=v2 --branch --untracked-files=all
```
The configured-upstream chain remains:
```text
git symbolic-ref --quiet --short HEAD
git rev-parse --abbrev-ref HEAD@{u}
git rev-list --left-right --count HEAD...origin/main
git log --oneline --cherry-mark --right-only HEAD...origin/main --
```
The SSH methods and payload omission rules remain:
```json
[
{
"method": "git.status",
"payload": { "worktreePath": "/home/user/repo" }
},
{
"method": "git.upstreamStatus",
"payload": {
"worktreePath": "/home/user/repo",
"pushTarget": {
"remoteName": "fork",
"branchName": "feature",
"remoteUrl": "ssh://git.example/repo",
"remoteCreated": false
}
}
}
]
```
Absent options and absent `pushTarget` remain omitted. Existing command and provider suites
continue to cover configured upstreams, explicit publish targets, ahead/behind and patch
equivalence, no-upstream behavior, Git 2.25 compatibility, WSL path handling, SSH/system-SSH, and
relay routing.
## Production A/B
Fresh production builds were archived outside the worktree:
- A: `/tmp/orca-phase2-checks-snapshot-a.hBrWy8/out`
- B: `/tmp/orca-phase2-checks-snapshot-b-reviewed.bNuaXj/out`
| Artifact | A raw / gzip | B raw / gzip | Change | A SHA-256 | B SHA-256 |
| ---------------------- | ----------------- | ----------------- | ---------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| Electron main entry | 789,517 / 176,812 | 789,869 / 176,858 | +352 / +46 | `6413904f6420882168b8993c1240614bacde1de92dfb26a55c4db67c84007b8e` | `7f4984a54c65cf5b749a87ed7ec7f2074c5b844992215ad25e8c17d179045660` |
| Electron preload entry | 130,798 / 20,642 | 130,891 / 20,658 | +93 / +16 | `c388a39cdca9609760e286d95b87ad1e53793720450e507f830ff1f6c5bd259f` | `afa8494e803f0e57cabdb5d2ff636f30fc8021069a7694d57d2ecdcdd6837a8a` |
| Renderer manifest | 403,000 / 48,291 | 403,000 / 48,288 | 0 / -3 | `92ad617f8c8aee6d4c4a23622f524266cbe9c93b3fe65544d1acb833dc3f0d67` | `9f57d550a1c9f6c75bf2d3db4f5ff53e53c4c52eceb8b10e9b86d8659a48d7fe` |
The Electron-main raw budget remains 825,109 bytes; B retains 35,240 bytes of headroom. File
counts remain 184 main, one preload, and 787 renderer artifacts.
| Renderer entry | A raw / gzip | B raw / gzip | Change | A JS/CSS | B JS/CSS |
| -------------- | --------------------- | --------------------- | ------ | -------- | -------- |
| Index | 8,416,540 / 1,877,823 | 8,416,540 / 1,877,824 | 0 / +1 | 292 / 2 | 292 / 2 |
| Popout | 4,507,253 / 984,615 | 4,507,253 / 984,615 | 0 / 0 | 77 / 2 | 77 / 2 |
| Web | 4,360,652 / 928,355 | 4,360,652 / 928,352 | 0 / -3 | 33 / 1 | 33 / 1 |
Deterministic tree hashes use sorted relative paths plus file bytes:
| Tree | A SHA-256 | B SHA-256 |
| -------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| Main | `70eee832a22181051dd0a807ac8192f2d2863b0f6706c21199b98245abb11d82` | `8d24293dafe6879300ff618f62582ebc68483a1af85f09c943dcdf63009f2696` |
| Preload | `2d85a03ecdb77d9762630a9fe2783b4da2042e09e470a43250b697f24008f209` | `f211a353bdfcb09de909134f1c2d91c920819f20b3f8b26baca5e72ba9f6e05b` |
| Renderer | `0b5610733f4e17999a72ea356965276cd728320400af97fdd8c83c42a50a0e71` | `2d792b983f3d45c8d3a2e270599705518f45c2b4a936925256bbea04505b0d52` |
Complete A and B manifest validation found 778 records, three HTML entries, 6,247 static edges,
213 dynamic edges, and 860 emitted references, with zero missing or escaping targets, zero
cross-entry imports, and zero cycles.
An initial rejected renderer arm placed the desktop query in broad `runtime-git-client.ts`. Its
archive is `/tmp/orca-phase2-checks-snapshot-b.IaVlMF/out`; it added 643 raw bytes to each of
index, popout, and web. Moving the query to the concrete
`desktop-git-repository-snapshot-client.ts` boundary eliminates that eager raw-byte tax while
keeping the same main/preload cost and call-count win.
## Validation
- Focused owner/provider/filesystem/runtime/Checks suite:
`pnpm exec vitest run ...` — 276 passed, one skipped after the independent review added an
ambiguous embedded-upstream fallback regression.
- Broad host owner, status/cache, native/WSL, SSH provider/dispatch, and filesystem suite:
`pnpm exec vitest run ...` — 364 passed, one skipped.
- Broad renderer runtime, status refresh/polling, push-target cache, Checks, and folder-workspace
suite: `pnpm exec vitest run --config config/vitest.config.ts ...` — 128 passed.
- Independent combined host/renderer review suite: 481 passed, one skipped.
- `pnpm run typecheck:node`: passed.
- `pnpm run typecheck:web`: passed.
- Targeted `pnpm exec oxlint --deny-warnings`: passed.
- Targeted `pnpm exec oxfmt --check`: passed.
- `pnpm run check:max-lines-ratchet`: passed with 354 grandfathered suppressions and no new bypass.
- Fresh A and B `pnpm run build:electron-vite`: passed.
- `pnpm run check:electron-bundle-budgets`: passed against fresh B.
- Complete renderer manifest/path and static-cycle validation: passed for A and B.
- `git diff --check`: passed.
- Added/changed max-lines-disable scan: zero.
## Limitations and next seam
- Savings occur when polling has published an admissible projection before automatic Checks
demand. A cold Checks open, stale projection, mutation fence, branch transition, truncated
status, ambiguous upstream, or failed read deliberately pays the existing fresh work.
- The counts are deterministic mocked physical-owner and mux-boundary measurements, not live
subprocess latency measurements across every supported host.
- The read-only IPC is desktop-only. Active runtime environments intentionally fall back to their
existing fresh RPC route; runtime/mobile projection transport remains later work.
- This slice does not subscribe Checks to snapshot revisions. The next coherent seam is a bounded
host-to-renderer revision subscription that invalidates consumers without adding another polling
loop, followed by migration of remaining renderer status consumers.
- No packaged smoke was run on Windows, WSL, Linux, or a live SSH host. Existing routing,
identity, folder-workspace, provider, mutation-fence, and Git-command suites cover the unchanged
boundaries.