mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
fix(ssh): prune the hoist ledgers by relevance, not recency
Finding B. Capping to the newest 512 evicted exactly the entries a long-lived profile needs most: a ledger entry's only job is to stop a tab the user closed being folded back in, and a re-hoisted id returns that tab to the plane the tab bar renders from — on screen, not merely in state. Now drops ids no ssh partition mentions any more. That bounds growth by what the host actually offers rather than by an arbitrary count, and it can never evict an entry that is still load-bearing, because an entry is only dropped once the tab it guards is gone from the partition too. MAX_HOISTED_TAB_IDS is removed. Mutation-proofed: restoring the recency cap reddens the new 600-tab eviction test alone. 512 tests across the persistence suites, E2E still green.
This commit is contained in:
+26
-13
@@ -2849,11 +2849,6 @@ export type PtyBindingSourceExpectation = {
|
||||
incarnationId?: string
|
||||
}
|
||||
|
||||
/** Caps the hoist ledger. A live tab is protected by the local-session check, not by this cap, so
|
||||
* eviction is safe for open tabs; the cost of evicting an id is that its tab could be re-hoisted
|
||||
* once if it is later closed while the partition still lists it. */
|
||||
const MAX_HOISTED_TAB_IDS = 512
|
||||
|
||||
/** Why a `mayCreate: false` binding write was refused. `noTab` means the pane has no durable tab,
|
||||
* so publishing it anywhere would be publishing something that does not exist. */
|
||||
export type PtyBindingRefusalReason = 'noTab' | 'noMembership'
|
||||
@@ -3021,16 +3016,34 @@ export function hoistSshPartitionsIntoLocalSession(
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
// Bounded, newest-last. A tab id can be reused by the CLI after its tab closes, and an
|
||||
// unbounded list would both grow for the life of the profile and strand such a pane forever.
|
||||
// KNOWN COST, and it is worse than it reads: an evicted id can be re-hoisted once, and since
|
||||
// the unified ledger gates the plane the tab bar renders from, that means a long-closed tab
|
||||
// reappearing ON SCREEN. Needs >512 hoisted tabs. Tracked, not fixed here.
|
||||
const bounded = [...alreadyHoisted].slice(-MAX_HOISTED_TAB_IDS)
|
||||
// Pruned by RELEVANCE, not recency. A ledger entry only has a job while the partition still
|
||||
// offers that tab: it is what stops a tab the user closed being folded back in. Slicing to the
|
||||
// newest N evicted exactly the entries a long-lived profile needs most, and a re-hoisted id
|
||||
// returns a closed tab to the plane the tab bar renders from — on screen. Dropping ids no
|
||||
// partition mentions any more bounds growth by what the host actually offers, and can never
|
||||
// evict an entry that is still load-bearing.
|
||||
const offeredTabIds = new Set<string>()
|
||||
for (const [hostId, hostSession] of Object.entries(partitions)) {
|
||||
if (!hostId.startsWith('ssh:') || !hostSession) {
|
||||
continue
|
||||
}
|
||||
for (const tabs of Object.values(hostSession.tabsByWorktree ?? {})) {
|
||||
for (const tab of tabs ?? []) {
|
||||
offeredTabIds.add(tab.id)
|
||||
}
|
||||
}
|
||||
for (const tabs of Object.values(hostSession.unifiedTabs ?? {})) {
|
||||
for (const tab of tabs ?? []) {
|
||||
offeredTabIds.add(tab.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
state.settings = {
|
||||
...state.settings,
|
||||
sshHoistedTabIds: bounded,
|
||||
sshHoistedUnifiedTabIds: [...alreadyHoistedUnified].slice(-MAX_HOISTED_TAB_IDS)
|
||||
sshHoistedTabIds: [...alreadyHoisted].filter((tabId) => offeredTabIds.has(tabId)),
|
||||
sshHoistedUnifiedTabIds: [...alreadyHoistedUnified].filter((tabId) =>
|
||||
offeredTabIds.has(tabId)
|
||||
)
|
||||
}
|
||||
}
|
||||
return changed
|
||||
|
||||
@@ -592,4 +592,43 @@ describe('an SSH pane whose membership was persisted into the ssh partition', ()
|
||||
'a tab the user closed came back in the tab bar'
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
// The ledger is what stops a closed tab being folded back in, so an entry must survive exactly
|
||||
// as long as the partition still offers that tab. Capping by recency evicted the entries a
|
||||
// long-lived profile needs most, and a re-hoisted id returns a closed tab to the plane the tab
|
||||
// bar renders from — on screen.
|
||||
it('keeps ledger entries for every tab the partition still offers, however many there are', async () => {
|
||||
const before = await createStore()
|
||||
const many = paneSession()
|
||||
const ids = Array.from({ length: 600 }, (_, index) => `tab-${index}`)
|
||||
many.tabsByWorktree[WORKTREE] = ids.map((id) => ({
|
||||
...many.tabsByWorktree[WORKTREE]![0]!,
|
||||
id
|
||||
}))
|
||||
many.unifiedTabs = { [WORKTREE]: ids.map((id) => unifiedTab(id)) } as typeof many.unifiedTabs
|
||||
before.setWorkspaceSession(many as never, SSH_PARTITION)
|
||||
before.flushOrThrow()
|
||||
stripHoistMarker()
|
||||
|
||||
const upgraded = await reopenStore()
|
||||
expect(
|
||||
upgraded.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? [],
|
||||
'the tabs never hoisted, so eviction cannot be observed'
|
||||
).toHaveLength(ids.length)
|
||||
|
||||
// The user closes them all.
|
||||
upgraded.setWorkspaceSession({
|
||||
...upgraded.getWorkspaceSession(),
|
||||
tabsByWorktree: { [WORKTREE]: [] },
|
||||
unifiedTabs: { [WORKTREE]: [] }
|
||||
} as never)
|
||||
upgraded.flushOrThrow()
|
||||
|
||||
const relaunched = await reopenStore()
|
||||
|
||||
expect(
|
||||
relaunched.getWorkspaceSession().unifiedTabs?.[WORKTREE] ?? [],
|
||||
'an evicted ledger entry let a closed tab reappear in the tab bar'
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user