fix(desktop): show repo-root-only sessions in project drill-in (#94552)

This commit is contained in:
Gille
2026-08-26 12:37:43 -06:00
committed by GitHub
parent b0dbf72f76
commit d0fdbfd655
2 changed files with 76 additions and 9 deletions

View File

@@ -629,6 +629,60 @@ describe('overlayLiveLanes', () => {
expect(overlaid.sessionCount).toBe(1)
})
it('keeps cwd-less repo sessions visible in both the overview and project drill-in', () => {
const project = projectNode({
id: '/www/app',
isAuto: true,
repos: [
{
id: '/www/app',
label: 'app',
path: '/www/app',
sessionCount: 0,
groups: [lane({ id: '/www/app::branch::main', label: 'main', isMain: true, path: '/www/app' })]
}
]
})
const live = makeCwdSession(null, { id: 'fresh', git_branch: 'main', git_repo_root: '/www/app' })
expect(overlayLivePreviews([project], [live], [], 3)['/www/app'].map(session => session.id)).toEqual(['fresh'])
const overlaid = overlayLiveLanes(project, [live])
expect(overlaid.repos[0].groups.flatMap(group => group.sessions.map(session => session.id))).toEqual(['fresh'])
expect(overlaid.sessionCount).toBe(1)
})
it('places a cwd-less repo session only in its exact repo subtree', () => {
const project = projectNode({
id: '/www',
repos: [
{
id: '/www',
label: 'www',
path: '/www',
sessionCount: 0,
groups: [lane({ id: '/www::branch::main', label: 'main', isMain: true, path: '/www' })]
},
{
id: '/www/app',
label: 'app',
path: '/www/app',
sessionCount: 0,
groups: [lane({ id: '/www/app::branch::main', label: 'main', isMain: true, path: '/www/app' })]
}
]
})
const live = makeCwdSession(null, { id: 'fresh', git_branch: 'main', git_repo_root: '/www/app' })
const overlaid = overlayLiveLanes(project, [live])
expect(overlaid.repos[0].groups.flatMap(group => group.sessions)).toEqual([])
expect(overlaid.repos[1].groups.flatMap(group => group.sessions.map(session => session.id))).toEqual(['fresh'])
expect(overlaid.sessionCount).toBe(1)
})
it('injects a session created in a fresh worktree into that worktree lane (no git_repo_root yet)', () => {
// The brand-new session row has only a cwd — no git_repo_root. The entered
// project knows its repo root, so the worktree session still lands in its

View File

@@ -451,21 +451,34 @@ export function sessionProjectColor(session: SessionInfo, projects: ProjectInfo[
const upsertSession = (rows: SessionInfo[], session: SessionInfo): SessionInfo[] =>
[session, ...rows.filter(row => row.id !== session.id)].sort((a, b) => sessionRecency(b) - sessionRecency(a))
/** A live row's placement path, with an exact repo-root fallback when cwd is absent. */
function livePathForRepo(repoRoot: string, session: SessionInfo): string {
const cwd = (session.cwd || '').trim()
if (cwd) {
return cwd
}
const persistedRoot = (session.git_repo_root || '').trim()
return persistedRoot && pathKey(persistedRoot) === pathKey(repoRoot) ? persistedRoot : ''
}
/**
* The lane a live session belongs to WITHIN a known repo root, by path — the
* entered project already knows its repo roots, so we don't need the session's
* (often-unset, on a fresh row) git_repo_root. Mirrors the backend's lane ids:
* The lane a live session belongs to WITHIN a known repo root, by path. A fresh
* row normally uses cwd; older/imported rows can carry only git_repo_root, which
* still identifies the main checkout exactly. Mirrors the backend's lane ids:
* main checkout -> branch lane, `.worktrees/t_<hex>` -> kanban, any other
* `.worktrees/<slug>` -> that worktree's own lane.
*/
function liveLaneForRepo(repoRoot: string, session: SessionInfo): null | SidebarSessionGroup {
const cwd = (session.cwd || '').trim()
const sessionPath = livePathForRepo(repoRoot, session)
if (!cwd || !isPathUnder(repoRoot, cwd)) {
if (!sessionPath || !isPathUnder(repoRoot, sessionPath)) {
return null
}
const wt = cwd.match(/^(.*[/\\]\.worktrees)[/\\]([^/\\]+)/)
const wt = sessionPath.match(/^(.*[/\\]\.worktrees)[/\\]([^/\\]+)/)
if (wt) {
const [worktreeRoot, worktreesDir, slug] = [wt[0], wt[1], wt[2]]
@@ -516,9 +529,9 @@ export function overlayRepoLanes(
})
for (const session of live) {
const cwd = (session.cwd || '').trim()
const sessionPath = livePathForRepo(repo.path ?? '', session)
if (removed.has(session.id) || !cwd) {
if (removed.has(session.id) || !sessionPath) {
continue
}
@@ -533,7 +546,7 @@ export function overlayRepoLanes(
for (const g of lanes) {
const lanePath = normalizePath(g.path)
if (!lanePath || pathKey(lanePath) === repoRootKey || !isPathUnder(lanePath, cwd)) {
if (!lanePath || pathKey(lanePath) === repoRootKey || !isPathUnder(lanePath, sessionPath)) {
continue
}