fix(projects): drop parent folders; attribute sessions to the deepest project
Some checks failed
ios / package-tests (APIClient) (push) Has been cancelled
ios / package-tests (HostRegistry) (push) Has been cancelled
ios / package-tests (SessionCore) (push) Has been cancelled
ios / package-tests (WireProtocol) (push) Has been cancelled
ios / testsupport-tests (push) Has been cancelled
ios / app-tests (push) Has been cancelled
ios / ipad-tests (push) Has been cancelled
ios / integration-tests (push) Has been cancelled
ios / ui-test (push) Has been cancelled
ios / ios17-floor-tests (push) Has been cancelled
relay-tripwire / cross-tenant-tripwire (push) Has been cancelled
Some checks failed
ios / package-tests (APIClient) (push) Has been cancelled
ios / package-tests (HostRegistry) (push) Has been cancelled
ios / package-tests (SessionCore) (push) Has been cancelled
ios / package-tests (WireProtocol) (push) Has been cancelled
ios / testsupport-tests (push) Has been cancelled
ios / app-tests (push) Has been cancelled
ios / ipad-tests (push) Has been cancelled
ios / integration-tests (push) Has been cancelled
ios / ui-test (push) Has been cancelled
ios / ios17-floor-tests (push) Has been cancelled
relay-tripwire / cross-tenant-tripwire (push) Has been cancelled
The projects panel listed bare parent folders (~, ~/Documents) as projects and a single session lit up every ancestor project's 'Active now'. dropParentFolders filters non-git entries that merely contain other listed projects; assignSessions attributes each live session to the DEEPEST containing project (detail page keeps prefix matching). +3 tests; projects 29/29, tsc clean. Frontend unchanged.
This commit is contained in:
@@ -259,6 +259,18 @@ function dedupByPath(projects: readonly ProjectInfo[]): ProjectInfo[] {
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop bare parent folders: a non-git entry (from session history — e.g. the
|
||||
* home dir or ~/Documents, where a session once ran) that merely contains
|
||||
* other discovered projects is not a project itself; listing it would also
|
||||
* double-claim its children's sessions in the UI.
|
||||
*/
|
||||
function dropParentFolders(projects: readonly ProjectInfo[]): ProjectInfo[] {
|
||||
return projects.filter(
|
||||
(p) => p.isGit || !projects.some((q) => q.path !== p.path && belongsTo(q.path, p.path)),
|
||||
)
|
||||
}
|
||||
|
||||
async function runDiscovery(cfg: Config): Promise<ProjectInfo[]> {
|
||||
const repoPaths = await scanRepos(cfg.projectRoots, cfg.projectScanDepth)
|
||||
const repos = await mapWithConcurrency(repoPaths, GIT_CONCURRENCY, async (repoPath) => {
|
||||
@@ -267,7 +279,7 @@ async function runDiscovery(cfg: Config): Promise<ProjectInfo[]> {
|
||||
return makeProject({ path: repoPath, isGit: true, branch, dirty })
|
||||
})
|
||||
const merged = await mergeHistory(repos)
|
||||
return dedupByPath(merged)
|
||||
return dropParentFolders(dedupByPath(merged))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,6 +345,29 @@ function matchSessions(projectPath: string, live: readonly LiveSessionInfo[]): P
|
||||
return live.filter((s) => belongsTo(s.cwd, projectPath)).map(toSessionRef)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute each live session to the DEEPEST project containing its cwd.
|
||||
* Plain prefix matching would also light up every ancestor project (e.g. a
|
||||
* nested repo's session showing as active on the containing repo too).
|
||||
*/
|
||||
function assignSessions(
|
||||
projects: readonly ProjectInfo[],
|
||||
live: readonly LiveSessionInfo[],
|
||||
): Map<string, ProjectSessionRef[]> {
|
||||
const byPath = new Map<string, ProjectSessionRef[]>()
|
||||
for (const s of live) {
|
||||
let deepest: string | undefined
|
||||
for (const p of projects) {
|
||||
if (!belongsTo(s.cwd, p.path)) continue
|
||||
if (deepest === undefined || p.path.length > deepest.length) deepest = p.path
|
||||
}
|
||||
if (deepest === undefined) continue
|
||||
const refs = byPath.get(deepest) ?? []
|
||||
byPath.set(deepest, [...refs, toSessionRef(s)])
|
||||
}
|
||||
return byPath
|
||||
}
|
||||
|
||||
function sortProjects(projects: readonly ProjectInfo[]): ProjectInfo[] {
|
||||
return [...projects].sort((a, b) => {
|
||||
if (a.lastActiveMs !== b.lastActiveMs) {
|
||||
@@ -354,9 +389,10 @@ export async function buildProjects(
|
||||
liveSessions: readonly LiveSessionInfo[],
|
||||
): Promise<ProjectInfo[]> {
|
||||
const base = await discoverProjects(cfg)
|
||||
const sessionsByPath = assignSessions(base, liveSessions)
|
||||
const withSessions = base.map((p) => ({
|
||||
...p,
|
||||
sessions: matchSessions(p.path, liveSessions),
|
||||
sessions: sessionsByPath.get(p.path) ?? [],
|
||||
}))
|
||||
return sortProjects(withSessions).slice(0, MAX_PROJECTS)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user