perf: index editor ownership and restored workspace projections (#19444)

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
This commit is contained in:
OrcaWin
2026-09-07 22:10:00 -07:00
committed by GitHub
co-authored by m4air
parent 919087897e
commit 253fa43256
7 changed files with 466 additions and 102 deletions
@@ -0,0 +1,44 @@
import { expect, it, vi } from 'vitest'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types'
import { createTestStore } from './store-test-helpers'
import { createStoreSessionMockApi } from './store-session-test-harness'
vi.mock('sonner', () => ({ toast: { info: vi.fn(), success: vi.fn(), error: vi.fn() } }))
createStoreSessionMockApi()
it('restores a large editor session without rescanning earlier file owners', () => {
const store = createTestStore()
const count = 2_000
let pathReads = 0
const files = Array.from({ length: count }, (_, index) => ({
get filePath() {
pathReads++
return `/project/file-${index}.ts`
},
relativePath: `file-${index}.ts`,
worktreeId: FLOATING_TERMINAL_WORKTREE_ID,
language: 'typescript',
runtimeEnvironmentId: index % 2 ? ' peer ' : null,
dirtyDraftContent: `unsaved ${index}`
}))
const session: WorkspaceSessionState = {
activeRepoId: null,
activeWorktreeId: FLOATING_TERMINAL_WORKTREE_ID,
activeTabId: null,
tabsByWorktree: {},
terminalLayoutsByTabId: {},
openFilesByWorktree: { [FLOATING_TERMINAL_WORKTREE_ID]: files }
}
store.setState({ activeWorktreeId: FLOATING_TERMINAL_WORKTREE_ID })
store.getState().hydrateEditorSession(session)
const state = store.getState()
expect(state.openFiles).toHaveLength(count)
expect(pathReads).toBeLessThan(count * 20)
for (let index = 0; index < count; index++) {
const file = state.openFiles[index]
expect(file.filePath).toBe(`/project/file-${index}.ts`)
expect(state.editorDrafts[file.id]).toBe(`unsaved ${index}`)
}
expect(state.activeFileId).toBe(state.openFiles[0].id)
})
@@ -7,14 +7,14 @@ import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
import type { WorkspaceVisibleTabType } from '../../../../../../shared/tab-types'
import type { OpenFile } from '../types/open-file'
import { buildValidWorktreeIdsForSessionHydration } from '../../degraded-repo-worktree-validity'
import { buildOwnedEditorFileId, isSameEditorOwner } from '../file-ids/editor-file-ids'
import { buildOwnedEditorFileId } from '../file-ids/editor-file-ids'
import { resolveHydratedEditorFileSelection } from '../file-ids/hydrated-editor-file-selection'
import { resolveHydratedEditorFrontmatter } from '../file-ids/hydrated-editor-frontmatter'
import {
addEditorFileIdMigration,
migrateEditorFileId,
migrateHydratedEditorTabsAndGroups,
resolveLegacyHydratedEditorFileId,
shouldHydrateWithOwnedEditorFileId,
type LegacyHydratedEditorFile
LegacyHydratedEditorFileIndex,
shouldHydrateWithOwnedEditorFileId
} from '../file-ids/hydrated-editor-file-ids'
export function createHydrateEditorSession(
@@ -42,7 +42,7 @@ export function createHydrateEditorSession(
const openFiles: OpenFile[] = []
const editorDrafts: Record<string, string> = {}
const usedOpenFileIds = new Set<string>()
const legacyHydratedOpenFiles: LegacyHydratedEditorFile[] = []
const legacyFileIndex = new LegacyHydratedEditorFileIndex()
const editorFileIdMigrationsByWorktree: Record<string, Map<string, string>> = {}
for (const [worktreeId, files] of Object.entries(openFilesByWorktree)) {
if (!validWorktreeIds.has(worktreeId)) {
@@ -50,20 +50,10 @@ export function createHydrateEditorSession(
}
for (const pf of files) {
// Split tabs share one OpenFile; repeated records for the same owner are corruption.
if (
legacyHydratedOpenFiles.some(
(file) =>
file.filePath === pf.filePath &&
isSameEditorOwner(file, worktreeId, pf.runtimeEnvironmentId)
)
) {
if (legacyFileIndex.hasOwner(pf, worktreeId)) {
continue
}
const legacyId = resolveLegacyHydratedEditorFileId(
legacyHydratedOpenFiles,
pf,
worktreeId
)
const legacyId = legacyFileIndex.resolve(pf, worktreeId)
// Why: floating/runtime-owned files need IDs that survive peers disappearing between restarts; collision-based IDs drift when the path is no longer open elsewhere.
const ownedId = buildOwnedEditorFileId(pf.filePath, worktreeId, pf.runtimeEnvironmentId)
const id =
@@ -78,7 +68,7 @@ export function createHydrateEditorSession(
usedOpenFileIds.add(id)
// Why: map from the collision-derived legacy id; keying by filePath would collapse same-path local/runtime tabs onto the last owner to hydrate.
addEditorFileIdMigration(editorFileIdMigrationsByWorktree, worktreeId, legacyId, id)
legacyHydratedOpenFiles.push({
legacyFileIndex.add({
id: legacyId,
filePath: pf.filePath,
worktreeId,
@@ -117,47 +107,21 @@ export function createHydrateEditorSession(
// Why: use the store's activeWorktreeId — hydrateWorkspaceSession may have nulled an invalid ID, and we must respect that.
const activeWorktreeId = s.activeWorktreeId
const fallbackActiveFileId = activeWorktreeId
? (openFiles.find((f) => f.worktreeId === activeWorktreeId)?.id ?? null)
: null
const persistedActiveFileId = activeWorktreeId
? migrateEditorFileId(
editorFileIdMigrationsByWorktree,
activeWorktreeId,
persistedActiveFileIdByWorktree[activeWorktreeId]
)
: null
// Why: the persisted active file may be gone (worktree validation or stale path), so verify it exists in the restored set.
const activeFileExists = persistedActiveFileId
? openFiles.some(
(f) => f.id === persistedActiveFileId && f.worktreeId === activeWorktreeId
)
: false
// Why: the previous active surface may have been a transient diff/conflict tab (not restored), so promote the first restored edit file.
const nextActiveFileId = activeFileExists ? persistedActiveFileId : fallbackActiveFileId
const {
activeFileId: nextActiveFileId,
activeFileIdByWorktree: filteredActiveFileIdByWorktree
} = resolveHydratedEditorFileSelection({
openFiles,
validWorktreeIds,
activeWorktreeId,
persistedActiveFileIds: persistedActiveFileIdByWorktree,
migrations: editorFileIdMigrationsByWorktree
})
const activeTabType: WorkspaceVisibleTabType =
activeWorktreeId && persistedActiveTabTypeByWorktree[activeWorktreeId]
? persistedActiveTabTypeByWorktree[activeWorktreeId]
: 'terminal'
// Filter per-worktree maps to only valid worktrees with valid file references
const filteredActiveFileIdByWorktree = Object.fromEntries(
[...validWorktreeIds].flatMap((wId) => {
const persistedFileId = migrateEditorFileId(
editorFileIdMigrationsByWorktree,
wId,
persistedActiveFileIdByWorktree[wId]
)
if (
persistedFileId &&
openFiles.some((f) => f.id === persistedFileId && f.worktreeId === wId)
) {
return [[wId, persistedFileId]]
}
const fallbackFileId = openFiles.find((f) => f.worktreeId === wId)?.id
return fallbackFileId ? [[wId, fallbackFileId]] : []
})
)
const filteredActiveTabTypeByWorktree = Object.fromEntries(
Object.entries(persistedActiveTabTypeByWorktree).filter(([wId, tabType]) => {
if (!validWorktreeIds.has(wId)) {
@@ -174,26 +138,11 @@ export function createHydrateEditorSession(
// Why: transient diff/conflict surfaces aren't restored, so clear a stale "editor" marker and fall back to terminal.
const nextActiveTabType =
nextActiveFileId || activeTabType !== 'editor' ? activeTabType : 'terminal'
const openFileIds = new Set(openFiles.map((file) => file.id))
// Why: visible is the default, so restore only per-file hide overrides (`false`); legacy `true` entries collapse to the default.
const hiddenFrontmatterEntries = new Map<string, boolean>()
for (const [persistedFileId, visible] of Object.entries(
persistedMarkdownFrontmatterVisible
)) {
if (visible) {
continue
}
if (openFileIds.has(persistedFileId)) {
hiddenFrontmatterEntries.set(persistedFileId, false)
}
for (const migrations of Object.values(editorFileIdMigrationsByWorktree)) {
const migratedFileId = migrations.get(persistedFileId)
if (migratedFileId && openFileIds.has(migratedFileId)) {
hiddenFrontmatterEntries.set(migratedFileId, false)
}
}
}
const markdownFrontmatterVisible = Object.fromEntries(hiddenFrontmatterEntries)
const markdownFrontmatterVisible = resolveHydratedEditorFrontmatter(
persistedMarkdownFrontmatterVisible,
usedOpenFileIds,
editorFileIdMigrationsByWorktree
)
return {
openFiles,
@@ -4,12 +4,7 @@ import type { PersistedOpenFile } from '../../../../../../shared/workspace-sessi
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../../../shared/constants'
import type { OpenFile } from '../types/open-file'
import { isEditorTabContentType } from '../tabs/editor-tab-content-type'
import {
buildOwnedEditorFileId,
isEditorFileIdOccupiedByOtherOwner,
isSameEditorOwner,
runtimeOwnerKey
} from './editor-file-ids'
import { buildOwnedEditorFileId, runtimeOwnerKey } from './editor-file-ids'
export function shouldHydrateWithOwnedEditorFileId(
worktreeId: string,
@@ -39,29 +34,58 @@ export type LegacyHydratedEditorFile = Pick<
'id' | 'filePath' | 'worktreeId' | 'runtimeEnvironmentId' | 'markdownPreviewSourceFileId'
>
export function resolveLegacyHydratedEditorFileId(
files: readonly LegacyHydratedEditorFile[],
persistedFile: PersistedOpenFile,
worktreeId: string
): string {
const existing = files.find(
(file) =>
file.filePath === persistedFile.filePath &&
isSameEditorOwner(file, worktreeId, persistedFile.runtimeEnvironmentId)
)
if (existing) {
return existing.id
export class LegacyHydratedEditorFileIndex {
private readonly filesByPath = new Map<string, Map<string, string>>()
private readonly ownersById = new Map<string, Set<string>>()
private ownerKey(worktreeId: string, runtimeEnvironmentId: string | null | undefined): string {
return JSON.stringify([worktreeId, runtimeOwnerKey(runtimeEnvironmentId)])
}
return files.some((file) =>
isEditorFileIdOccupiedByOtherOwner(
file,
persistedFile.filePath,
worktreeId,
persistedFile.runtimeEnvironmentId
hasOwner(file: PersistedOpenFile, worktreeId: string): boolean {
return (
this.filesByPath
.get(file.filePath)
?.has(this.ownerKey(worktreeId, file.runtimeEnvironmentId)) ?? false
)
)
? buildOwnedEditorFileId(persistedFile.filePath, worktreeId, persistedFile.runtimeEnvironmentId)
: persistedFile.filePath
}
resolve(file: PersistedOpenFile, worktreeId: string): string {
const owner = this.ownerKey(worktreeId, file.runtimeEnvironmentId)
const existing = this.filesByPath.get(file.filePath)?.get(owner)
if (existing !== undefined) {
return existing
}
const occupied = this.ownersById.get(file.filePath)
return occupied && (occupied.size > 1 || !occupied.has(owner))
? buildOwnedEditorFileId(file.filePath, worktreeId, file.runtimeEnvironmentId)
: file.filePath
}
add(file: LegacyHydratedEditorFile): void {
const owner = this.ownerKey(file.worktreeId, file.runtimeEnvironmentId)
let files = this.filesByPath.get(file.filePath)
if (!files) {
files = new Map()
this.filesByPath.set(file.filePath, files)
}
if (!files.has(owner)) {
files.set(owner, file.id)
}
this.addIdOwner(file.id, owner)
if (file.markdownPreviewSourceFileId !== undefined) {
this.addIdOwner(file.markdownPreviewSourceFileId, owner)
}
}
private addIdOwner(id: string, owner: string): void {
let owners = this.ownersById.get(id)
if (!owners) {
owners = new Set()
this.ownersById.set(id, owners)
}
owners.add(owner)
}
}
export function migrateEditorFileId(
@@ -0,0 +1,82 @@
import { describe, expect, it } from 'vitest'
import type { PersistedOpenFile } from '../../../../../../shared/workspace-session-state-types'
import {
LegacyHydratedEditorFileIndex,
type LegacyHydratedEditorFile
} from './hydrated-editor-file-ids'
import {
buildOwnedEditorFileId,
isEditorFileIdOccupiedByOtherOwner,
isSameEditorOwner
} from './editor-file-ids'
function persisted(filePath: string, runtimeEnvironmentId: string | null): PersistedOpenFile {
return {
filePath,
runtimeEnvironmentId,
relativePath: filePath,
worktreeId: '',
language: 'text'
}
}
function referenceId(
files: LegacyHydratedEditorFile[],
file: PersistedOpenFile,
worktreeId: string
) {
const existing = files.find(
(prior) =>
prior.filePath === file.filePath &&
isSameEditorOwner(prior, worktreeId, file.runtimeEnvironmentId)
)
if (existing) {
return existing.id
}
return files.some((prior) =>
isEditorFileIdOccupiedByOtherOwner(prior, file.filePath, worktreeId, file.runtimeEnvironmentId)
)
? buildOwnedEditorFileId(file.filePath, worktreeId, file.runtimeEnvironmentId)
: file.filePath
}
describe('legacy hydrated editor file index', () => {
it('matches the old lookup for mixed owners, first-wins duplicates and ID reservations', () => {
const index = new LegacyHydratedEditorFileIndex()
const prior: LegacyHydratedEditorFile[] = []
const paths = [
'/same.ts',
'C:\\work\\same.ts',
'/other.ts',
'editor:folder:local:%2Fsame.ts',
'',
'/preview.md'
]
const worktrees = ['folder:one', 'wt:two', 'floating-terminals']
const runtimes = [null, '', ' ', 'local', 'peer', ' peer ', 'a:b', '["a","b"]']
for (let step = 0; step < 120; step++) {
for (const filePath of paths) {
for (const worktreeId of worktrees) {
for (const runtime of runtimes) {
const file = persisted(filePath, runtime)
expect(index.hasOwner(file, worktreeId)).toBe(
prior.some(
(row) => row.filePath === filePath && isSameEditorOwner(row, worktreeId, runtime)
)
)
expect(index.resolve(file, worktreeId)).toBe(referenceId(prior, file, worktreeId))
}
}
}
const row: LegacyHydratedEditorFile = {
filePath: paths[step % paths.length],
id: paths[(step * 3) % paths.length],
worktreeId: worktrees[Math.floor(step / paths.length) % worktrees.length],
runtimeEnvironmentId: runtimes[Math.floor(step / worktrees.length) % runtimes.length],
...(step % 4 === 0 ? { markdownPreviewSourceFileId: '/preview.md' } : {})
}
index.add(row)
prior.push(row)
}
})
})
@@ -0,0 +1,40 @@
import type { OpenFile } from '../types/open-file'
import { migrateEditorFileId } from './hydrated-editor-file-ids'
export function resolveHydratedEditorFileSelection(args: {
openFiles: readonly Pick<OpenFile, 'id' | 'worktreeId'>[]
validWorktreeIds: ReadonlySet<string>
activeWorktreeId: string | null
persistedActiveFileIds: Record<string, string | null>
migrations: Record<string, Map<string, string>>
}): { activeFileId: string | null; activeFileIdByWorktree: Record<string, string> } {
const workspaces = new Map<string, { firstFileId: string; ids: Set<string> }>()
for (const file of args.openFiles) {
let workspace = workspaces.get(file.worktreeId)
if (!workspace) {
workspace = { firstFileId: file.id, ids: new Set() }
workspaces.set(file.worktreeId, workspace)
}
workspace.ids.add(file.id)
}
const selectedId = (worktreeId: string): string | null => {
const workspace = workspaces.get(worktreeId)
const persistedId = migrateEditorFileId(
args.migrations,
worktreeId,
args.persistedActiveFileIds[worktreeId]
)
return persistedId && workspace?.ids.has(persistedId)
? persistedId
: (workspace?.firstFileId ?? null)
}
return {
activeFileId: args.activeWorktreeId ? selectedId(args.activeWorktreeId) : null,
activeFileIdByWorktree: Object.fromEntries(
[...args.validWorktreeIds].flatMap((worktreeId) => {
const fileId = selectedId(worktreeId)
return fileId ? [[worktreeId, fileId]] : []
})
)
}
}
@@ -0,0 +1,50 @@
export function resolveHydratedEditorFrontmatter(
persistedVisibility: Record<string, boolean>,
openFileIds: ReadonlySet<string>,
migrationsByWorktree: Record<string, Map<string, string>>
): Record<string, boolean> {
const hiddenIds = new Set(
Object.entries(persistedVisibility)
.filter(([, visible]) => !visible)
.map(([id]) => id)
)
if (hiddenIds.size === 0) {
return {}
}
const migratedIds = new Map<string, string[]>()
const addMigration = (from: string, to: string | undefined): void => {
if (!to || !openFileIds.has(to)) {
return
}
const targets = migratedIds.get(from)
if (targets) {
targets.push(to)
} else {
migratedIds.set(from, [to])
}
}
for (const migrations of Object.values(migrationsByWorktree)) {
// Scan the smaller side so sparse overrides never pay for a large migration map.
if (migrations.size < hiddenIds.size) {
for (const [from, to] of migrations) {
if (hiddenIds.has(from)) {
addMigration(from, to)
}
}
} else {
for (const from of hiddenIds) {
addMigration(from, migrations.get(from))
}
}
}
const hidden = new Map<string, boolean>()
for (const persistedId of hiddenIds) {
if (openFileIds.has(persistedId)) {
hidden.set(persistedId, false)
}
for (const migratedId of migratedIds.get(persistedId) ?? []) {
hidden.set(migratedId, false)
}
}
return Object.fromEntries(hidden)
}
@@ -0,0 +1,175 @@
import { describe, expect, it } from 'vitest'
import { resolveHydratedEditorFileSelection } from './hydrated-editor-file-selection'
import { resolveHydratedEditorFrontmatter } from './hydrated-editor-frontmatter'
import { migrateEditorFileId } from './hydrated-editor-file-ids'
type SelectionInput = Parameters<typeof resolveHydratedEditorFileSelection>[0]
function referenceSelection(args: SelectionInput) {
const select = (worktreeId: string) => {
const persisted = migrateEditorFileId(
args.migrations,
worktreeId,
args.persistedActiveFileIds[worktreeId]
)
return persisted &&
args.openFiles.some((file) => file.id === persisted && file.worktreeId === worktreeId)
? persisted
: (args.openFiles.find((file) => file.worktreeId === worktreeId)?.id ?? null)
}
return {
activeFileId: args.activeWorktreeId ? select(args.activeWorktreeId) : null,
activeFileIdByWorktree: Object.fromEntries(
[...args.validWorktreeIds].flatMap((worktreeId) => {
const fileId = select(worktreeId)
return fileId ? [[worktreeId, fileId]] : []
})
)
}
}
function referenceFrontmatter(
visibility: Record<string, boolean>,
openIds: Set<string>,
migrations: Record<string, Map<string, string>>
) {
const hidden = new Map<string, boolean>()
for (const [id, visible] of Object.entries(visibility)) {
if (visible) {
continue
}
if (openIds.has(id)) {
hidden.set(id, false)
}
for (const migration of Object.values(migrations)) {
const target = migration.get(id)
if (target && openIds.has(target)) {
hidden.set(target, false)
}
}
}
return Object.fromEntries(hidden)
}
class CountedMigrations extends Map<string, string> {
reads = 0
override get(key: string): string | undefined {
this.reads++
return super.get(key)
}
override *[Symbol.iterator](): MapIterator<[string, string]> {
for (const entry of super[Symbol.iterator]()) {
this.reads++
yield entry
}
}
}
describe('hydrated editor selection', () => {
it('indexes files once across many workspace selections', () => {
const count = 1_000
let reads = 0
const files = Array.from({ length: count }, (_, index) => ({
get id() {
reads++
return `file-${index}`
},
get worktreeId() {
reads++
return `folder:${index}`
}
}))
const args: SelectionInput = {
openFiles: files,
validWorktreeIds: new Set(files.map((file) => file.worktreeId)),
activeWorktreeId: 'folder:999',
persistedActiveFileIds: Object.fromEntries(files.map((file) => [file.worktreeId, file.id])),
migrations: {}
}
reads = 0
const expected = referenceSelection(args)
expect(reads).toBeGreaterThan((count * count) / 2)
reads = 0
expect(resolveHydratedEditorFileSelection(args)).toEqual(expected)
expect(reads).toBeLessThan(count * 6)
})
it('preserves owner checks, migration, first-file fallbacks and empty IDs', () => {
for (let sample = 0; sample < 100; sample++) {
const args: SelectionInput = {
openFiles: Array.from({ length: 20 }, (_, index) => ({
id: (index + sample) % 7 ? `file-${(index + sample) % 9}` : '',
worktreeId: `wt-${(index * 3 + sample) % 5}`
})),
activeWorktreeId: sample % 3 ? `wt-${sample % 7}` : null,
validWorktreeIds: new Set(Array.from({ length: 7 }, (_, index) => `wt-${index}`)),
persistedActiveFileIds: {
'wt-0': 'legacy',
'wt-1': 'file-3',
'wt-2': 'absent',
'wt-3': ''
},
migrations: { 'wt-0': new Map([['legacy', 'file-1']]) }
}
expect(resolveHydratedEditorFileSelection(args)).toEqual(referenceSelection(args))
}
})
})
describe('hydrated frontmatter migration', () => {
it('avoids workspace-by-override fanout', () => {
const count = 1_000
const visibility = Object.fromEntries(
Array.from({ length: count }, (_, i) => [`old-${i}`, false])
)
const openIds = new Set(Array.from({ length: count }, (_, i) => `new-${i}`))
const migrations = Object.fromEntries(
Array.from({ length: count }, (_, i) => [
`wt-${i}`,
new CountedMigrations([[`old-${i}`, `new-${i}`]])
])
)
const expected = referenceFrontmatter(visibility, openIds, migrations)
expect(Object.values(migrations).reduce((sum, map) => sum + map.reads, 0)).toBe(count * count)
for (const map of Object.values(migrations)) {
map.reads = 0
}
expect(resolveHydratedEditorFrontmatter(visibility, openIds, migrations)).toEqual(expected)
expect(Object.values(migrations).reduce((sum, map) => sum + map.reads, 0)).toBe(count)
})
it('does no migration scan without overrides and one lookup for a sparse override', () => {
const map = new CountedMigrations(
Array.from({ length: 10_000 }, (_, i) => [`old-${i}`, `new-${i}`])
)
const ids = new Set(['new-9999'])
expect(resolveHydratedEditorFrontmatter({ 'old-0': true }, ids, { wt: map })).toEqual({})
expect(map.reads).toBe(0)
expect(resolveHydratedEditorFrontmatter({ 'old-9999': false }, ids, { wt: map })).toEqual({
'new-9999': false
})
expect(map.reads).toBe(1)
})
it('preserves insertion order, multiple owners, direct IDs and missing targets', () => {
for (let sample = 0; sample < 50; sample++) {
const visibility = Object.fromEntries(
Array.from({ length: 15 }, (_, i) => [`file-${i}`, (i + sample) % 4 === 0])
)
const ids = new Set(Array.from({ length: 10 }, (_, i) => `file-${(i + sample) % 16}`))
const migrations = Object.fromEntries(
Array.from({ length: 5 }, (_, w) => [
`wt-${w}`,
new Map(
Array.from({ length: 8 }, (_, i) => [
`file-${(i + w) % 15}`,
`file-${(i + sample) % 17}`
])
)
])
)
expect(Object.entries(resolveHydratedEditorFrontmatter(visibility, ids, migrations))).toEqual(
Object.entries(referenceFrontmatter(visibility, ids, migrations))
)
}
})
})