Files
claudecodeui/src/components/sidebar/view/subcomponents/SidebarProjectList.tsx
PaloSP 198e3da89b feat: implement session rename with SQLite storage (#413)
* feat: implement session rename with SQLite storage (closes #72, fixes #358)

- Add session_names table to store custom display names per provider
- Add PUT /api/sessions/:sessionId/rename endpoint
- Replace stub updateSessionSummary with real API call
- Apply custom names across all providers (Claude, Codex, Cursor)
- Fix project rename destroying config (spread merge instead of overwrite)
- Thread provider parameter through sidebar component chain
- Add i18n error messages for rename failures (en, ja, ko, zh-CN)

* fix: address CodeRabbit review feedback for session rename

- Log migration errors instead of swallowing them silently (db.js)
- Add try/catch to applyCustomSessionNames to prevent getProjects abort
- Move applyCustomSessionNames to db.js as shared helper (DRY)
- Fix Cursor getSessionName to check session.summary for custom names
- Move edit state clearing to finally block in updateSessionSummary
- Sanitize sessionId, add 500-char summary limit, validate provider whitelist
- Remove dead applyCustomSessionNames call on empty manual project sessions

* fix: reject sessionId on mismatch instead of silent normalization

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: enable rename for all providers, add Gemini support, clean up orphans

- Enable rename UI (pencil icon) for Codex, Cursor, and Gemini sessions
- Keep delete button hidden for Cursor (no backend delete endpoint)
- Add 'gemini' to VALID_PROVIDERS and hoist to module scope
- Add sessionNamesDb.deleteName on session delete (claude, codex, gemini)
- Fix token-usage endpoint sessionId mismatch validation
- Remove redundant try/catch in sessionNamesDb methods
- Let session_names migration errors propagate to outer handler

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com>
2026-03-03 18:11:26 +03:00

164 lines
5.5 KiB
TypeScript

import { useEffect } from 'react';
import type { TFunction } from 'i18next';
import type { LoadingProgress, Project, ProjectSession, SessionProvider } from '../../../../types/app';
import type {
LoadingSessionsByProject,
MCPServerStatus,
SessionWithProvider,
TouchHandlerFactory,
} from '../../types/types';
import SidebarProjectItem from './SidebarProjectItem';
import SidebarProjectsState from './SidebarProjectsState';
export type SidebarProjectListProps = {
projects: Project[];
filteredProjects: Project[];
selectedProject: Project | null;
selectedSession: ProjectSession | null;
isLoading: boolean;
loadingProgress: LoadingProgress | null;
expandedProjects: Set<string>;
editingProject: string | null;
editingName: string;
loadingSessions: LoadingSessionsByProject;
initialSessionsLoaded: Set<string>;
currentTime: Date;
editingSession: string | null;
editingSessionName: string;
deletingProjects: Set<string>;
tasksEnabled: boolean;
mcpServerStatus: MCPServerStatus;
getProjectSessions: (project: Project) => SessionWithProvider[];
isProjectStarred: (projectName: string) => boolean;
onEditingNameChange: (value: string) => void;
onToggleProject: (projectName: string) => void;
onProjectSelect: (project: Project) => void;
onToggleStarProject: (projectName: string) => void;
onStartEditingProject: (project: Project) => void;
onCancelEditingProject: () => void;
onSaveProjectName: (projectName: string) => void;
onDeleteProject: (project: Project) => void;
onSessionSelect: (session: SessionWithProvider, projectName: string) => void;
onDeleteSession: (
projectName: string,
sessionId: string,
sessionTitle: string,
provider: SessionProvider,
) => void;
onLoadMoreSessions: (project: Project) => void;
onNewSession: (project: Project) => void;
onEditingSessionNameChange: (value: string) => void;
onStartEditingSession: (sessionId: string, initialName: string) => void;
onCancelEditingSession: () => void;
onSaveEditingSession: (projectName: string, sessionId: string, summary: string, provider: SessionProvider) => void;
touchHandlerFactory: TouchHandlerFactory;
t: TFunction;
};
export default function SidebarProjectList({
projects,
filteredProjects,
selectedProject,
selectedSession,
isLoading,
loadingProgress,
expandedProjects,
editingProject,
editingName,
loadingSessions,
initialSessionsLoaded,
currentTime,
editingSession,
editingSessionName,
deletingProjects,
tasksEnabled,
mcpServerStatus,
getProjectSessions,
isProjectStarred,
onEditingNameChange,
onToggleProject,
onProjectSelect,
onToggleStarProject,
onStartEditingProject,
onCancelEditingProject,
onSaveProjectName,
onDeleteProject,
onSessionSelect,
onDeleteSession,
onLoadMoreSessions,
onNewSession,
onEditingSessionNameChange,
onStartEditingSession,
onCancelEditingSession,
onSaveEditingSession,
touchHandlerFactory,
t,
}: SidebarProjectListProps) {
const state = (
<SidebarProjectsState
isLoading={isLoading}
loadingProgress={loadingProgress}
projectsCount={projects.length}
filteredProjectsCount={filteredProjects.length}
t={t}
/>
);
useEffect(() => {
let baseTitle = 'CloudCLI UI';
const displayName = selectedProject?.displayName?.trim();
if (displayName) {
baseTitle = `${displayName} - ${baseTitle}`;
}
document.title = baseTitle;
}, [selectedProject]);
const showProjects = !isLoading && projects.length > 0 && filteredProjects.length > 0;
return (
<div className="md:space-y-1 pb-safe-area-inset-bottom">
{!showProjects
? state
: filteredProjects.map((project) => (
<SidebarProjectItem
key={project.name}
project={project}
selectedProject={selectedProject}
selectedSession={selectedSession}
isExpanded={expandedProjects.has(project.name)}
isDeleting={deletingProjects.has(project.name)}
isStarred={isProjectStarred(project.name)}
editingProject={editingProject}
editingName={editingName}
sessions={getProjectSessions(project)}
initialSessionsLoaded={initialSessionsLoaded.has(project.name)}
isLoadingSessions={Boolean(loadingSessions[project.name])}
currentTime={currentTime}
editingSession={editingSession}
editingSessionName={editingSessionName}
tasksEnabled={tasksEnabled}
mcpServerStatus={mcpServerStatus}
onEditingNameChange={onEditingNameChange}
onToggleProject={onToggleProject}
onProjectSelect={onProjectSelect}
onToggleStarProject={onToggleStarProject}
onStartEditingProject={onStartEditingProject}
onCancelEditingProject={onCancelEditingProject}
onSaveProjectName={onSaveProjectName}
onDeleteProject={onDeleteProject}
onSessionSelect={onSessionSelect}
onDeleteSession={onDeleteSession}
onLoadMoreSessions={onLoadMoreSessions}
onNewSession={onNewSession}
onEditingSessionNameChange={onEditingSessionNameChange}
onStartEditingSession={onStartEditingSession}
onCancelEditingSession={onCancelEditingSession}
onSaveEditingSession={onSaveEditingSession}
touchHandlerFactory={touchHandlerFactory}
t={t}
/>
))}
</div>
);
}