fix(desktop): evict stale lane entry when overlay moves session to worktree

When a session's cwd moves from the main checkout to a newly created
worktree, overlayRepoLanes places it into the matching worktree lane but
never removed the stale entry from the main lane. The session appeared
under both groups until the user left and re-entered the project view.

Add a cross-lane eviction loop that removes the session from all other
lanes before inserting it into the target lane.
This commit is contained in:
liuhao1024
2026-06-30 23:40:42 +08:00
committed by Teknium
parent 0fd223d1bd
commit 3defb25a42
2 changed files with 49 additions and 0 deletions
@@ -901,6 +901,39 @@ describe('overlayLiveLanes', () => {
expect(overlayLiveLanes(home, [makeSession('/www/app', { id: 'fresh' })])).toBe(home)
})
it('evicts a session from the main lane when the live overlay places it into a worktree lane', () => {
// Session was in main when the backend tree was captured, but the live
// $sessions cache now has it under a worktree cwd. The overlay must place
// it ONLY in the worktree lane — not both.
const session = makeSession('/www/app/.worktrees/feature', { id: 'moved', git_branch: 'feature' })
const project = projectNode({
id: '/www/app',
repos: [
{
id: '/www/app',
label: 'app',
path: '/www/app',
sessionCount: 1,
groups: [
lane({ id: '/www/app::branch::main', label: 'main', isMain: true, path: '/www/app', sessions: [session] }),
lane({ id: '/www/app/.worktrees/feature', label: 'feature', path: '/www/app/.worktrees/feature', sessions: [] })
]
}
]
})
const overlaid = overlayLiveLanes(project, [session])
const mainLane = overlaid.repos[0].groups.find(g => g.isMain)
const featureLane = overlaid.repos[0].groups.find(g => g.path === '/www/app/.worktrees/feature')
// Session must NOT appear in the main lane
expect(mainLane?.sessions ?? []).toHaveLength(0)
// Session must appear only in the worktree lane
expect(featureLane?.sessions.map(s => s.id)).toEqual(['moved'])
expect(overlaid.sessionCount).toBe(1)
})
})
describe('overlayLivePreviews', () => {
@@ -586,6 +586,22 @@ export function overlayRepoLanes(
}
}
// Evict the session from any OTHER lane the backend snapshot may have
// placed it in (e.g. a turn that moved the session's cwd from main to a
// new worktree — the overlay places it into the worktree lane, but without
// this eviction the stale main-lane entry persists and the session appears
// under both groups until the next backend tree refresh).
for (const g of lanes) {
if (g !== lane) {
const idx = g.sessions.findIndex(s => s.id === session.id)
if (idx >= 0) {
g.sessions = [...g.sessions.slice(0, idx), ...g.sessions.slice(idx + 1)]
changed = true
}
}
}
lane.sessions = upsertSession(lane.sessions, session)
changed = true
}