fix(sidebar): prevent home-project archived session overlap crash (#2017)
* fix(sidebar): scope archived sessions to deepest project * fix(sidebar): prefer session directory over worktree --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
bashrusakh
parent
e5bba59a75
commit
fbcf4ea2b9
@@ -1,7 +1,14 @@
|
||||
import React from 'react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import type { WorktreeMetadata } from '@/types/worktree';
|
||||
import { dedupeSessionsById, getArchivedScopeKey, isSessionRelatedToProject, normalizePath, resolveArchivedFolderName } from '../utils';
|
||||
import {
|
||||
collectKnownProjectDirectories,
|
||||
dedupeSessionsById,
|
||||
getArchivedScopeKey,
|
||||
isSessionRelatedToProject,
|
||||
normalizePath,
|
||||
resolveArchivedFolderName,
|
||||
} from '../utils';
|
||||
|
||||
type ProjectForArchivedFolders = {
|
||||
normalizedPath: string;
|
||||
@@ -28,7 +35,9 @@ type Args = {
|
||||
|
||||
const getArchivedSessionsForProject = (
|
||||
project: ProjectForArchivedFolders,
|
||||
params: Pick<Args, 'sessions' | 'archivedSessions' | 'availableWorktreesByProject' | 'isVSCode'>,
|
||||
params: Pick<Args, 'sessions' | 'archivedSessions' | 'availableWorktreesByProject' | 'isVSCode'> & {
|
||||
knownProjectDirectories: Set<string>;
|
||||
},
|
||||
): Session[] => {
|
||||
const worktreesForProject = params.isVSCode ? [] : (params.availableWorktreesByProject.get(project.normalizedPath) ?? []);
|
||||
const validDirectories = new Set<string>([
|
||||
@@ -39,7 +48,7 @@ const getArchivedSessionsForProject = (
|
||||
]);
|
||||
|
||||
const collect = (input: Session[]): Session[] => input.filter((session) =>
|
||||
isSessionRelatedToProject(session, project.normalizedPath, validDirectories),
|
||||
isSessionRelatedToProject(session, project.normalizedPath, validDirectories, params.knownProjectDirectories),
|
||||
);
|
||||
|
||||
const archived = collect(params.archivedSessions);
|
||||
@@ -51,7 +60,7 @@ const getArchivedSessionsForProject = (
|
||||
if (sessionDirectory) {
|
||||
return false;
|
||||
}
|
||||
return isSessionRelatedToProject(session, project.normalizedPath, validDirectories);
|
||||
return isSessionRelatedToProject(session, project.normalizedPath, validDirectories, params.knownProjectDirectories);
|
||||
});
|
||||
|
||||
return dedupeSessionsById([...archived, ...unassignedLive]);
|
||||
@@ -71,6 +80,11 @@ export const useArchivedAutoFolders = (args: Args): void => {
|
||||
cleanupSessions,
|
||||
} = args;
|
||||
|
||||
const knownProjectDirectories = React.useMemo(
|
||||
() => collectKnownProjectDirectories(normalizedProjects, availableWorktreesByProject, isVSCode),
|
||||
[normalizedProjects, availableWorktreesByProject, isVSCode],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isSessionsLoading) {
|
||||
return;
|
||||
@@ -83,6 +97,7 @@ export const useArchivedAutoFolders = (args: Args): void => {
|
||||
archivedSessions,
|
||||
availableWorktreesByProject,
|
||||
isVSCode,
|
||||
knownProjectDirectories,
|
||||
});
|
||||
const sessionIds = new Set(projectArchivedSessions.map((session) => session.id));
|
||||
|
||||
@@ -110,6 +125,7 @@ export const useArchivedAutoFolders = (args: Args): void => {
|
||||
sessions,
|
||||
archivedSessions,
|
||||
availableWorktreesByProject,
|
||||
knownProjectDirectories,
|
||||
isVSCode,
|
||||
isSessionsLoading,
|
||||
foldersMap,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { resolveGlobalSessionDirectory } from '@/stores/useGlobalSessionsStore';
|
||||
import { dedupeSessionsById, isSessionRelatedToProject, normalizePath } from '../utils';
|
||||
import { collectKnownProjectDirectories, dedupeSessionsById, isSessionRelatedToProject, normalizePath } from '../utils';
|
||||
|
||||
type WorktreeMeta = { path: string };
|
||||
|
||||
@@ -37,23 +37,10 @@ export const useProjectSessionLists = (args: Args) => {
|
||||
// worktree. Walking this set is O(P + W) per Sidebar render and lets
|
||||
// us skip the bulk of `sessions` (whose directory is not associated
|
||||
// with a known project) when building `sessionsByDirectory`.
|
||||
const allowedDirectories = React.useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
normalizedProjects.forEach((project) => {
|
||||
if (project.normalizedPath) {
|
||||
set.add(project.normalizedPath);
|
||||
}
|
||||
});
|
||||
if (!isVSCode) {
|
||||
for (const worktrees of availableWorktreesByProject.values()) {
|
||||
for (const worktree of worktrees) {
|
||||
const normalized = normalizePath(worktree.path);
|
||||
if (normalized) set.add(normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}, [normalizedProjects, availableWorktreesByProject, isVSCode]);
|
||||
const knownProjectDirectories = React.useMemo(
|
||||
() => collectKnownProjectDirectories(normalizedProjects, availableWorktreesByProject, isVSCode),
|
||||
[normalizedProjects, availableWorktreesByProject, isVSCode],
|
||||
);
|
||||
|
||||
const sessionsByDirectory = React.useMemo(() => {
|
||||
const next = new Map<string, Session[]>();
|
||||
@@ -67,7 +54,7 @@ export const useProjectSessionLists = (args: Args) => {
|
||||
// every session the server has ever seen, even ones for
|
||||
// long-removed worktrees; the sidebar's downstream filters
|
||||
// would then drop them anyway.
|
||||
if (!allowedDirectories.has(directory)) {
|
||||
if (!knownProjectDirectories.has(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +63,7 @@ export const useProjectSessionLists = (args: Args) => {
|
||||
next.set(directory, collection);
|
||||
});
|
||||
return next;
|
||||
}, [sessions, allowedDirectories]);
|
||||
}, [sessions, knownProjectDirectories]);
|
||||
|
||||
const getSessionsForProject = React.useCallback(
|
||||
(project: { normalizedPath: string }) => {
|
||||
@@ -92,7 +79,7 @@ export const useProjectSessionLists = (args: Args) => {
|
||||
const collected: Session[] = [];
|
||||
|
||||
directories.forEach((directory) => {
|
||||
const sessionsForDirectory = sessionsByDirectory.get(directory) ?? [];
|
||||
const sessionsForDirectory: Session[] = sessionsByDirectory.get(directory) ?? [];
|
||||
sessionsForDirectory.forEach((session) => {
|
||||
if (seen.has(session.id)) {
|
||||
return;
|
||||
@@ -145,7 +132,7 @@ export const useProjectSessionLists = (args: Args) => {
|
||||
]);
|
||||
|
||||
const collect = (input: Session[]): Session[] => input.filter((session) =>
|
||||
isSessionRelatedToProject(session, project.normalizedPath, validDirectories),
|
||||
isSessionRelatedToProject(session, project.normalizedPath, validDirectories, knownProjectDirectories),
|
||||
);
|
||||
|
||||
const archived = collect(archivedSessions);
|
||||
@@ -161,12 +148,12 @@ export const useProjectSessionLists = (args: Args) => {
|
||||
if (!projectWorktree) {
|
||||
return false;
|
||||
}
|
||||
return projectWorktree === project.normalizedPath || projectWorktree.startsWith(`${project.normalizedPath}/`);
|
||||
return isSessionRelatedToProject(session, project.normalizedPath, validDirectories, knownProjectDirectories);
|
||||
});
|
||||
|
||||
return dedupeSessionsById([...archived, ...unassignedLive]);
|
||||
},
|
||||
[archivedSessions, availableWorktreesByProject, isVSCode, sessions],
|
||||
[archivedSessions, availableWorktreesByProject, isVSCode, knownProjectDirectories, sessions],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React from 'react';
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
import { useSessionFoldersStore } from '@/stores/useSessionFoldersStore';
|
||||
import { dedupeSessionsById, getArchivedScopeKey, isSessionRelatedToProject, normalizePath } from '../utils';
|
||||
import {
|
||||
collectKnownProjectDirectories,
|
||||
dedupeSessionsById,
|
||||
getArchivedScopeKey,
|
||||
isSessionRelatedToProject,
|
||||
normalizePath,
|
||||
} from '../utils';
|
||||
|
||||
type NormalizedProject = {
|
||||
id: string;
|
||||
@@ -33,6 +39,11 @@ export const useSessionFolderCleanup = (args: Args): void => {
|
||||
cleanupSessions,
|
||||
} = args;
|
||||
|
||||
const knownProjectDirectories = React.useMemo(
|
||||
() => collectKnownProjectDirectories(normalizedProjects, availableWorktreesByProject, isVSCode),
|
||||
[normalizedProjects, availableWorktreesByProject, isVSCode],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isSessionsLoading || !hasLoadedGlobalSessions) {
|
||||
return;
|
||||
@@ -76,9 +87,9 @@ export const useSessionFolderCleanup = (args: Args): void => {
|
||||
if (sessionDirectory) {
|
||||
return false;
|
||||
}
|
||||
return isSessionRelatedToProject(session, project.normalizedPath, validDirectories);
|
||||
return isSessionRelatedToProject(session, project.normalizedPath, validDirectories, knownProjectDirectories);
|
||||
}),
|
||||
]).filter((session) => isSessionRelatedToProject(session, project.normalizedPath, validDirectories));
|
||||
]).filter((session) => isSessionRelatedToProject(session, project.normalizedPath, validDirectories, knownProjectDirectories));
|
||||
|
||||
idsByScope.set(scopeKey, new Set(archivedForProject.map((session) => session.id)));
|
||||
});
|
||||
@@ -95,6 +106,7 @@ export const useSessionFolderCleanup = (args: Args): void => {
|
||||
hasLoadedGlobalSessions,
|
||||
isSessionsLoading,
|
||||
isVSCode,
|
||||
knownProjectDirectories,
|
||||
normalizedProjects,
|
||||
sessions,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user