perf(renderer): scope editor Git subscriptions by worktree (#8107)

This commit is contained in:
Neil
2026-07-10 16:26:26 -07:00
committed by GitHub
parent 5489b975d8
commit 402274a35c
7 changed files with 123 additions and 22 deletions
@@ -18,6 +18,10 @@ import { useEditorPanelContentState } from './useEditorPanelContentState'
import { useMarkdownPreviewShortcut } from './useMarkdownPreviewShortcut'
import { useUntitledFileRename } from './useUntitledFileRename'
import { extractFrontMatter } from './markdown-frontmatter'
import {
selectEditorPanelGitBranchEntries,
selectEditorPanelGitStatusEntries
} from './editor-panel-git-entry-selector'
function EditorPanelInner({
activeFileId: activeFileIdProp,
@@ -33,10 +37,17 @@ function EditorPanelInner({
const activeFileId = activeFileIdProp ?? globalActiveFileId
const activeViewStateId = activeViewStateIdProp ?? activeFileId
const activeFile = openFiles.find((f) => f.id === activeFileId) ?? null
const activeWorktreeId = activeFile?.worktreeId
const markFileDirty = useAppStore((s) => s.markFileDirty)
const pendingEditorReveal = useAppStore((s) => s.pendingEditorReveal)
const gitStatusByWorktree = useAppStore((s) => s.gitStatusByWorktree)
const gitBranchChangesByWorktree = useAppStore((s) => s.gitBranchChangesByWorktree)
// Why: background Git refreshes for other worktrees must not wake every
// mounted Monaco/rich editor pane.
const gitStatusEntries = useAppStore((s) =>
selectEditorPanelGitStatusEntries(s, activeWorktreeId)
)
const gitBranchEntries = useAppStore((s) =>
selectEditorPanelGitBranchEntries(s, activeWorktreeId)
)
const markdownViewMode = useAppStore((s) => s.markdownViewMode)
const setMarkdownViewMode = useAppStore((s) => s.setMarkdownViewMode)
const editorViewMode = useAppStore((s) => s.editorViewMode)
@@ -96,7 +107,7 @@ function EditorPanelInner({
activeFile,
isChangesMode: requestedChangesMode,
openFiles,
gitStatusByWorktree,
gitStatusEntries,
editorViewMode
})
const isChangesMode =
@@ -225,8 +236,8 @@ function EditorPanelInner({
activeFile,
fileContents,
editorDrafts,
gitStatusByWorktree,
gitBranchChangesByWorktree,
gitStatusEntries,
gitBranchEntries,
markdownViewMode,
isChangesMode
})
@@ -0,0 +1,74 @@
import { describe, expect, it } from 'vitest'
import type { GitBranchChangeEntry, GitStatusEntry } from '../../../../shared/types'
import {
selectEditorPanelGitBranchEntries,
selectEditorPanelGitStatusEntries
} from './editor-panel-git-entry-selector'
describe('editor panel Git entry selectors', () => {
it('ignore background worktree writes across mounted editor panels', () => {
const panelCount = 200
const worktreeId = 'worktree-active'
const statusEntries = [{ path: 'src/index.ts' }] as GitStatusEntry[]
const branchEntries = [{ path: 'src/index.ts' }] as GitBranchChangeEntry[]
let gitStatusByWorktree = { [worktreeId]: statusEntries }
let gitBranchChangesByWorktree = { [worktreeId]: branchEntries }
let wholeMapInvalidations = 0
let scopedEntryInvalidations = 0
for (let write = 0; write < 200; write += 1) {
const previousStatusMap = gitStatusByWorktree
gitStatusByWorktree = {
...gitStatusByWorktree,
[`background-${write}`]: [{ path: `generated-${write}.ts` } as GitStatusEntry]
}
const previousBranchMap = gitBranchChangesByWorktree
gitBranchChangesByWorktree = {
...gitBranchChangesByWorktree,
[`background-${write}`]: [{ path: `generated-${write}.ts` } as GitBranchChangeEntry]
}
for (let panel = 0; panel < panelCount; panel += 1) {
wholeMapInvalidations += Number(previousStatusMap !== gitStatusByWorktree)
wholeMapInvalidations += Number(previousBranchMap !== gitBranchChangesByWorktree)
scopedEntryInvalidations += Number(
selectEditorPanelGitStatusEntries({ gitStatusByWorktree }, worktreeId) !== statusEntries
)
scopedEntryInvalidations += Number(
selectEditorPanelGitBranchEntries({ gitBranchChangesByWorktree }, worktreeId) !==
branchEntries
)
}
}
expect(wholeMapInvalidations).toBe(80_000)
expect(scopedEntryInvalidations).toBe(0)
})
it('publishes owning entry replacements and handles an absent worktree', () => {
const firstStatus = [{ path: 'src/old.ts' }] as GitStatusEntry[]
const nextStatus = [{ path: 'src/new.ts' }] as GitStatusEntry[]
const firstBranch = [{ path: 'src/old.ts' }] as GitBranchChangeEntry[]
const nextBranch = [{ path: 'src/new.ts' }] as GitBranchChangeEntry[]
expect(
selectEditorPanelGitStatusEntries({ gitStatusByWorktree: { active: firstStatus } }, 'active')
).toBe(firstStatus)
expect(
selectEditorPanelGitStatusEntries({ gitStatusByWorktree: { active: nextStatus } }, 'active')
).toBe(nextStatus)
expect(
selectEditorPanelGitBranchEntries(
{ gitBranchChangesByWorktree: { active: firstBranch } },
'active'
)
).toBe(firstBranch)
expect(
selectEditorPanelGitBranchEntries(
{ gitBranchChangesByWorktree: { active: nextBranch } },
'active'
)
).toBe(nextBranch)
expect(selectEditorPanelGitStatusEntries({ gitStatusByWorktree: {} }, null)).toBeUndefined()
})
})
@@ -0,0 +1,17 @@
import type { AppState } from '@/store'
type EditorPanelGitEntryState = Pick<AppState, 'gitBranchChangesByWorktree' | 'gitStatusByWorktree'>
export function selectEditorPanelGitStatusEntries(
state: Pick<EditorPanelGitEntryState, 'gitStatusByWorktree'>,
worktreeId: string | null | undefined
): AppState['gitStatusByWorktree'][string] | undefined {
return worktreeId ? state.gitStatusByWorktree[worktreeId] : undefined
}
export function selectEditorPanelGitBranchEntries(
state: Pick<EditorPanelGitEntryState, 'gitBranchChangesByWorktree'>,
worktreeId: string | null | undefined
): AppState['gitBranchChangesByWorktree'][string] | undefined {
return worktreeId ? state.gitBranchChangesByWorktree[worktreeId] : undefined
}
@@ -34,12 +34,13 @@ function renderModel(args: {
isChangesMode?: boolean
gitStatusByWorktree?: Record<string, GitStatusEntry[]>
}) {
const activeFile = args.activeFile ?? markdownFile()
return getEditorPanelRenderModel({
activeFile: args.activeFile ?? markdownFile(),
activeFile,
fileContents: args.fileContents ?? { '/repo/README.md': textContent() },
editorDrafts: args.editorDrafts ?? {},
gitStatusByWorktree: args.gitStatusByWorktree ?? {},
gitBranchChangesByWorktree: {},
gitStatusEntries: args.gitStatusByWorktree?.[activeFile.worktreeId],
gitBranchEntries: undefined,
markdownViewMode: args.markdownViewMode ?? {},
isChangesMode: args.isChangesMode ?? false
})
@@ -22,8 +22,8 @@ type EditorPanelRenderModelParams = {
activeFile: OpenFile
fileContents: Record<string, FileContent>
editorDrafts: StoreState['editorDrafts']
gitStatusByWorktree: StoreState['gitStatusByWorktree']
gitBranchChangesByWorktree: StoreState['gitBranchChangesByWorktree']
gitStatusEntries: StoreState['gitStatusByWorktree'][string] | undefined
gitBranchEntries: StoreState['gitBranchChangesByWorktree'][string] | undefined
markdownViewMode: StoreState['markdownViewMode']
isChangesMode: boolean
}
@@ -32,8 +32,8 @@ export function getEditorPanelRenderModel({
activeFile,
fileContents,
editorDrafts,
gitStatusByWorktree,
gitBranchChangesByWorktree,
gitStatusEntries,
gitBranchEntries,
markdownViewMode,
isChangesMode
}: EditorPanelRenderModelParams) {
@@ -54,8 +54,8 @@ export function getEditorPanelRenderModel({
activeFile.mode === 'diff'
? detectLanguage(activeFile.relativePath)
: detectLanguage(activeFile.filePath)
const worktreeEntries = gitStatusByWorktree[activeFile.worktreeId] ?? []
const branchEntries = gitBranchChangesByWorktree[activeFile.worktreeId] ?? []
const worktreeEntries = gitStatusEntries ?? []
const branchEntries = gitBranchEntries ?? []
const matchingWorktreeEntry =
activeFile.mode === 'diff' &&
(activeFile.diffSource === 'staged' || activeFile.diffSource === 'unstaged')
@@ -101,7 +101,7 @@ function HookProbe({
activeFile,
isChangesMode: false,
openFiles,
gitStatusByWorktree,
gitStatusEntries: activeFile ? gitStatusByWorktree[activeFile.worktreeId] : undefined,
editorViewMode: {}
})
latestFileContents = state.fileContents
@@ -41,7 +41,7 @@ type UseEditorPanelContentStateParams = {
activeFile: OpenFile | null
isChangesMode: boolean
openFiles: OpenFile[]
gitStatusByWorktree: GitStatusByWorktree
gitStatusEntries: GitStatusByWorktree[string] | undefined
editorViewMode: EditorViewModeByFile
}
@@ -94,7 +94,7 @@ export function useEditorPanelContentState({
activeFile,
isChangesMode,
openFiles,
gitStatusByWorktree,
gitStatusEntries,
editorViewMode
}: UseEditorPanelContentStateParams): UseEditorPanelContentStateResult {
const [fileContents, setFileContents] = useState<Record<string, FileContent>>({})
@@ -362,7 +362,7 @@ export function useEditorPanelContentState({
}
const snapshotPaths = new Set(snapshotEntries.map((entry) => entry.path))
const liveEntries = gitStatusByWorktree[activeFile.worktreeId] ?? []
const liveEntries = gitStatusEntries ?? []
for (const entry of liveEntries) {
if (
!snapshotPaths.has(entry.path) ||
@@ -411,7 +411,7 @@ export function useEditorPanelContentState({
activeFile?.conflictReview?.snapshotTimestamp,
selectedConflictReviewFile?.id,
isChangesMode,
gitStatusByWorktree
gitStatusEntries
])
useEditorPanelFileLoadRetry({
@@ -423,9 +423,7 @@ export function useEditorPanelContentState({
setFileContents
})
const changesStatusEntries = activeFile?.worktreeId
? gitStatusByWorktree[activeFile.worktreeId]
: undefined
const changesStatusEntries = activeFile?.worktreeId ? gitStatusEntries : undefined
const activeFileGitStatusEntries = useMemo(() => {
if (!activeFile?.relativePath || !changesStatusEntries) {
return undefined