mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-02-14 20:57:32 +00:00
perf(project-loading): eliminate repeated Codex session rescans and duplicate cursor fetches
The staged changes remove the main source of project-load latency by avoiding repeated full scans of ~/.codex/sessions for every project and by removing redundant client-side cursor session refetches. Server changes (server/projects.js):\n- Add a per-request Codex index reference in getProjects so Codex metadata is built once and reused across all projects, including manually added ones.\n- Introduce normalizeComparablePath() to canonicalize project paths (including Windows long-path prefixes and case-insensitive matching on Windows).\n- Introduce findCodexJsonlFiles() + buildCodexSessionsIndex() to perform a single recursive Codex scan and group sessions by normalized cwd.\n- Update getCodexSessions() to accept indexRef and read from the prebuilt index, with fallback index construction when no ref is provided.\n- Preserve existing session limiting behavior (limit=5 default, limit=0 returns all). Client changes (src/hooks/useProjectsState.ts):\n- Remove loadCursorSessionsForProjects(), which previously triggered one extra /api/cursor/sessions request per project after /api/projects.\n- Use /api/projects response directly during initial load and refresh.\n- Expand projectsHaveChanges() to treat both cursorSessions and codexSessions as external session deltas.\n- Keep refresh comparison aligned with external session updates by using includeExternalSessions=true in sidebar refresh path. Impact:\n- Reduces backend work from roughly O(projects * codex_session_files) to O(codex_session_files + projects) for Codex discovery during a project load cycle.\n- Removes an additional client-side O(projects) network fan-out for Cursor session fetches.\n- Improves perceived and actual sidebar project-loading time, especially in large session datasets.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import { api, authenticatedFetch } from '../utils/api';
|
||||
import { api } from '../utils/api';
|
||||
import type {
|
||||
AppSocketMessage,
|
||||
AppTab,
|
||||
@@ -23,7 +23,7 @@ const serialize = (value: unknown) => JSON.stringify(value ?? null);
|
||||
const projectsHaveChanges = (
|
||||
prevProjects: Project[],
|
||||
nextProjects: Project[],
|
||||
includeCursorSessions: boolean,
|
||||
includeExternalSessions: boolean,
|
||||
): boolean => {
|
||||
if (prevProjects.length !== nextProjects.length) {
|
||||
return true;
|
||||
@@ -46,11 +46,14 @@ const projectsHaveChanges = (
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!includeCursorSessions) {
|
||||
if (!includeExternalSessions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return serialize(nextProject.cursorSessions) !== serialize(prevProject.cursorSessions);
|
||||
return (
|
||||
serialize(nextProject.cursorSessions) !== serialize(prevProject.cursorSessions) ||
|
||||
serialize(nextProject.codexSessions) !== serialize(prevProject.codexSessions)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -98,31 +101,6 @@ const isUpdateAdditive = (
|
||||
);
|
||||
};
|
||||
|
||||
const loadCursorSessionsForProjects = async (projects: Project[]): Promise<Project[]> => {
|
||||
const projectsWithCursor = [...projects];
|
||||
|
||||
for (const project of projectsWithCursor) {
|
||||
try {
|
||||
const projectPath = project.fullPath || project.path;
|
||||
const url = `/api/cursor/sessions?projectPath=${encodeURIComponent(projectPath ?? '')}`;
|
||||
const response = await authenticatedFetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
project.cursorSessions = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
project.cursorSessions = data.success && Array.isArray(data.sessions) ? data.sessions : [];
|
||||
} catch (error) {
|
||||
console.error(`Error fetching Cursor sessions for project ${project.name}:`, error);
|
||||
project.cursorSessions = [];
|
||||
}
|
||||
}
|
||||
|
||||
return projectsWithCursor;
|
||||
};
|
||||
|
||||
export function useProjectsState({
|
||||
sessionId,
|
||||
navigate,
|
||||
@@ -149,15 +127,14 @@ export function useProjectsState({
|
||||
setIsLoadingProjects(true);
|
||||
const response = await api.projects();
|
||||
const projectData = (await response.json()) as Project[];
|
||||
const projectsWithCursor = await loadCursorSessionsForProjects(projectData);
|
||||
|
||||
setProjects((prevProjects) => {
|
||||
if (prevProjects.length === 0) {
|
||||
return projectsWithCursor;
|
||||
return projectData;
|
||||
}
|
||||
|
||||
return projectsHaveChanges(prevProjects, projectsWithCursor, true)
|
||||
? projectsWithCursor
|
||||
return projectsHaveChanges(prevProjects, projectData, true)
|
||||
? projectData
|
||||
: prevProjects;
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -421,7 +398,7 @@ export function useProjectsState({
|
||||
const freshProjects = (await response.json()) as Project[];
|
||||
|
||||
setProjects((prevProjects) =>
|
||||
projectsHaveChanges(prevProjects, freshProjects, false) ? freshProjects : prevProjects,
|
||||
projectsHaveChanges(prevProjects, freshProjects, true) ? freshProjects : prevProjects,
|
||||
);
|
||||
|
||||
if (!selectedProject) {
|
||||
|
||||
Reference in New Issue
Block a user