fix(sidebar): add project sort modes (#2067)

* fix(sidebar): add project sort modes

* fix(sidebar): preserve manual sort order

* fix(sidebar): move sort state before render

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
Leonid
2026-07-11 14:43:57 +03:00
committed by GitHub
co-authored by bashrusakh
parent f6326e1c35
commit bfaf62e222
16 changed files with 256 additions and 18 deletions
+41 -11
View File
@@ -46,6 +46,7 @@ interface VSCodeWorkspaceFolderConfig {
interface ProjectsStore {
projects: ProjectEntry[];
activeProjectId: string | null;
manualProjectOrder: string[];
addProject: (path: string, options?: { label?: string; id?: string }) => ProjectEntry | null;
removeProject: (id: string) => void;
@@ -315,6 +316,17 @@ const readPersistedProjects = (): ProjectEntry[] => {
}
};
const readPersistedManualOrder = (): string[] => {
try {
const raw = safeStorage.getItem(getProjectsStorageKey() + ':manualOrder');
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string') : [];
} catch {
return [];
}
};
const readPersistedActiveProjectId = (): string | null => {
try {
const raw = safeStorage.getItem(getActiveProjectStorageKey())
@@ -347,11 +359,22 @@ const cacheProjects = (projects: ProjectEntry[], activeProjectId: string | null)
}
};
const persistProjects = (projects: ProjectEntry[], activeProjectId: string | null) => {
const persistProjects = (projects: ProjectEntry[], activeProjectId: string | null, manualOrder?: string[]) => {
cacheProjects(projects, activeProjectId);
if (manualOrder) {
persistManualProjectOrder(manualOrder);
}
void updateDesktopSettings({ projects, activeProjectId: activeProjectId ?? undefined });
};
const persistManualProjectOrder = (manualOrder: string[]) => {
try {
safeStorage.setItem(getProjectsStorageKey() + ':manualOrder', JSON.stringify(manualOrder));
} catch {
// ignored
}
};
const initialProjects = readPersistedProjects();
const normalizeVSCodeWorkspaceFolders = (folders: VSCodeWorkspaceFolderConfig[]): VSCodeWorkspaceFolderConfig[] => {
const result: VSCodeWorkspaceFolderConfig[] = [];
@@ -536,6 +559,7 @@ export const useProjectsStore = create<ProjectsStore>()(
devtools((set, get) => ({
projects: effectiveInitialProjects,
activeProjectId: initialActiveProjectId,
manualProjectOrder: readPersistedManualOrder(),
validateProjectPath: (path: string): ProjectPathValidationResult => {
if (typeof path !== 'string' || path.trim().length === 0) {
@@ -604,8 +628,9 @@ export const useProjectsStore = create<ProjectsStore>()(
nextActiveId = nextProjects[0]?.id ?? null;
}
set({ projects: nextProjects, activeProjectId: nextActiveId });
persistProjects(nextProjects, nextActiveId);
const nextManualOrder = get().manualProjectOrder.filter((oid) => oid !== id);
set({ projects: nextProjects, activeProjectId: nextActiveId, manualProjectOrder: nextManualOrder });
persistProjects(nextProjects, nextActiveId, nextManualOrder);
// Clean up worktree entries for the removed project
if (project) {
@@ -647,7 +672,7 @@ export const useProjectsStore = create<ProjectsStore>()(
);
set({ projects: nextProjects, activeProjectId: id });
persistProjects(nextProjects, id);
persistProjects(nextProjects, id, get().manualProjectOrder);
opencodeClient.setDirectory(target.path);
useDirectoryStore.getState().setDirectory(target.path, { showOverlay: false });
@@ -672,7 +697,7 @@ export const useProjectsStore = create<ProjectsStore>()(
);
set({ projects: nextProjects, activeProjectId: id });
persistProjects(nextProjects, id);
persistProjects(nextProjects, id, get().manualProjectOrder);
},
renameProject: (id: string, label: string) => {
@@ -689,7 +714,7 @@ export const useProjectsStore = create<ProjectsStore>()(
project.id === id ? { ...project, label: trimmed } : project
);
set({ projects: nextProjects });
persistProjects(nextProjects, activeProjectId);
persistProjects(nextProjects, activeProjectId, get().manualProjectOrder);
},
updateProjectMeta: (id: string, meta: {
@@ -726,7 +751,7 @@ export const useProjectsStore = create<ProjectsStore>()(
return updated;
});
set({ projects: nextProjects });
persistProjects(nextProjects, activeProjectId);
persistProjects(nextProjects, activeProjectId, get().manualProjectOrder);
},
uploadProjectIcon: async (id: string, file: File) => {
@@ -863,8 +888,9 @@ export const useProjectsStore = create<ProjectsStore>()(
const [moved] = nextProjects.splice(fromIndex, 1);
nextProjects.splice(toIndex, 0, moved);
set({ projects: nextProjects });
persistProjects(nextProjects, activeProjectId);
const newOrder = nextProjects.map((p) => p.id);
set({ projects: nextProjects, manualProjectOrder: newOrder });
persistProjects(nextProjects, activeProjectId, newOrder);
},
resetForRuntimeSwitch: () => {
@@ -876,7 +902,7 @@ export const useProjectsStore = create<ProjectsStore>()(
const nextActiveProjectId = projects.some((project) => project.id === activeProjectId)
? activeProjectId
: projects[0]?.id ?? null;
set({ projects, activeProjectId: nextActiveProjectId });
set({ projects, activeProjectId: nextActiveProjectId, manualProjectOrder: [] });
},
synchronizeFromSettings: (settings: DesktopSettings) => {
@@ -903,6 +929,7 @@ export const useProjectsStore = create<ProjectsStore>()(
if (activeExists) {
set({ activeProjectId: incomingActive });
cacheProjects(current.projects, incomingActive);
persistManualProjectOrder(get().manualProjectOrder);
}
}
return;
@@ -915,8 +942,11 @@ export const useProjectsStore = create<ProjectsStore>()(
return;
}
set({ projects: incomingProjects, activeProjectId: incomingActive });
const incomingIds = new Set(incomingProjects.map((p) => p.id));
const cleanedOrder = get().manualProjectOrder.filter((id) => incomingIds.has(id));
set({ projects: incomingProjects, activeProjectId: incomingActive, manualProjectOrder: cleanedOrder });
cacheProjects(incomingProjects, incomingActive);
persistManualProjectOrder(cleanedOrder);
if (incomingActive) {
const activeProject = incomingProjects.find((project) => project.id === incomingActive);