mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
perf(renderer): keep the repo filter array identity across no-op refetches
Three fetch sites unconditionally reallocated filterRepoIds (`s.filterRepoIds.filter(...)`) on every repo catalog refresh, even when nothing was pruned. Six identity-sensitive subscribers select this array, including App.tsx at the root, so every refresh woke the whole tree. retainValidFilterRepoIds returns the input when every id is still valid; `.every` short-circuits so the common case allocates nothing at all. It lives in its own module rather than ui.ts: repos.ts does not import ui.ts and this codebase has a documented circular-slice-import hazard. The store type widens to `readonly string[]`, which takes three more declaration edits (visible-worktrees, add-repo-skip-finalization, the setter). PersistedUI.filterRepoIds deliberately stays `string[]`: main owns that array, and widening it would fail the ui-state schema-parity assertion unless the zod schema gained `.readonly()`, which Object.freeze()s every parsed `ui.set` payload arriving from a paired client. Copy at the App.tsx IPC boundary instead — one allocation per debounced 150ms persist, not per render. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -1416,7 +1416,9 @@ function App(): React.JSX.Element {
|
||||
hideWorkspacesFromOtherDevices,
|
||||
alwaysShowDefaultBranchWorkspace,
|
||||
showDotfilesByWorktree,
|
||||
filterRepoIds,
|
||||
// Why: the store keeps this readonly for identity stability, but PersistedUI crosses to
|
||||
// main, which owns a mutable array — copy at the boundary rather than widening the wire type.
|
||||
filterRepoIds: [...filterRepoIds],
|
||||
// Why (#9002): activeView is deliberately NOT included here. It used to
|
||||
// ride this same 150ms writer (#8265), which meant every top-level view
|
||||
// switch scheduled a full durable-state save. The narrow preference
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isDefaultBranchWorkspace } from './visible-worktrees'
|
||||
|
||||
export type AddRepoSkipFinalizationState = {
|
||||
activeRepoId: string | null
|
||||
filterRepoIds: string[]
|
||||
filterRepoIds: readonly string[]
|
||||
showActiveOnly: boolean
|
||||
hideDefaultBranchWorkspace: boolean
|
||||
showSleepingWorkspaces: boolean
|
||||
|
||||
@@ -186,7 +186,7 @@ export function computeVisibleWorktreeIds(
|
||||
worktreesByRepo: Record<string, Worktree[]>,
|
||||
sortedIds: string[],
|
||||
opts: {
|
||||
filterRepoIds: string[]
|
||||
filterRepoIds: readonly string[]
|
||||
showSleepingWorkspaces: boolean
|
||||
tabsByWorktree: Record<string, Pick<TerminalTab, 'id'>[]> | null
|
||||
ptyIdsByTabId: Record<string, string[]> | null
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Why: catalog refreshes re-filter this on every fetch; six identity-sensitive subscribers
|
||||
// (App.tsx at the root among them) re-render on a new array even when nothing was pruned.
|
||||
export function retainValidFilterRepoIds(
|
||||
filterRepoIds: readonly string[],
|
||||
validRepoIds: ReadonlySet<string>
|
||||
): readonly string[] {
|
||||
return filterRepoIds.every((repoId) => validRepoIds.has(repoId))
|
||||
? filterRepoIds
|
||||
: filterRepoIds.filter((repoId) => validRepoIds.has(repoId))
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import { isPathInsideOrEqual } from '../../../../shared/cross-platform-path'
|
||||
import { getRepoIdFromWorktreeId } from '../../../../shared/worktree-id'
|
||||
import { selectProjectGroupRemovalTargets } from './project-group-removal-targets'
|
||||
import { reconcileFetchedRepos } from './repo-identity-reconcile'
|
||||
import { retainValidFilterRepoIds } from './repo-filter-selection'
|
||||
import {
|
||||
mergeSshRepoReadoptions,
|
||||
reconcileReadoptedSshRepoRows,
|
||||
@@ -2102,7 +2103,7 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
? {}
|
||||
: { folderWorkspacePathStatuses: {} }),
|
||||
activeRepoId: s.activeRepoId && validRepoIds.has(s.activeRepoId) ? s.activeRepoId : null,
|
||||
filterRepoIds: s.filterRepoIds.filter((projectId) => validRepoIds.has(projectId)),
|
||||
filterRepoIds: retainValidFilterRepoIds(s.filterRepoIds, validRepoIds),
|
||||
setupScriptPromptDismissedRepoIds: filterSetupScriptPromptDismissalsToValidRepos(
|
||||
s.setupScriptPromptDismissedRepoIds,
|
||||
validRepoHostIdentities
|
||||
@@ -2186,7 +2187,7 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
...reconcileReadoptedSshWorktreeState(s, s.pendingSshRepoReadoptions),
|
||||
...mergedProjectCompatibility,
|
||||
activeRepoId: s.activeRepoId && validRepoIds.has(s.activeRepoId) ? s.activeRepoId : null,
|
||||
filterRepoIds: s.filterRepoIds.filter((projectId) => validRepoIds.has(projectId)),
|
||||
filterRepoIds: retainValidFilterRepoIds(s.filterRepoIds, validRepoIds),
|
||||
setupScriptPromptDismissedRepoIds: filterSetupScriptPromptDismissalsToValidRepos(
|
||||
s.setupScriptPromptDismissedRepoIds,
|
||||
validRepoHostIdentities
|
||||
@@ -2268,7 +2269,7 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
const validRepoHostIdentities = new Set(s.repos.map(getRepoHostIdentity))
|
||||
return {
|
||||
activeRepoId: s.activeRepoId && validRepoIds.has(s.activeRepoId) ? s.activeRepoId : null,
|
||||
filterRepoIds: s.filterRepoIds.filter((projectId) => validRepoIds.has(projectId)),
|
||||
filterRepoIds: retainValidFilterRepoIds(s.filterRepoIds, validRepoIds),
|
||||
setupScriptPromptDismissedRepoIds: filterSetupScriptPromptDismissalsToValidRepos(
|
||||
s.setupScriptPromptDismissedRepoIds,
|
||||
validRepoHostIdentities
|
||||
|
||||
@@ -904,8 +904,8 @@ export type UISlice = {
|
||||
showDotfilesByWorktree: Record<string, boolean>
|
||||
setShowDotfilesForWorktree: (worktreeId: string, showDotfiles: boolean) => void
|
||||
toggleShowDotfilesForWorktree: (worktreeId: string) => void
|
||||
filterRepoIds: string[]
|
||||
setFilterRepoIds: (ids: string[]) => void
|
||||
filterRepoIds: readonly string[]
|
||||
setFilterRepoIds: (ids: readonly string[]) => void
|
||||
collapsedGroups: Set<string>
|
||||
toggleCollapsedGroup: (key: string) => void
|
||||
worktreeCardProperties: WorktreeCardProperty[]
|
||||
|
||||
Reference in New Issue
Block a user