mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
perf: index editor ownership and restored workspace projections (#19444)
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
This commit is contained in:
@@ -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))
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user