mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-14 00:34:17 +00:00
Users previously had an all-or-nothing choice for completed sessions: either keep them in the active sidebar or permanently delete them. That made long-lived usage brittle because valuable history stayed in the way unless users destroyed it. This change introduces archiving as a first-class lifecycle so completed work can be hidden without losing transcript history, workspace context, or restoreability. The backend now persists session archive state and excludes archived rows from active session queries by default. Dedicated archive queries and routes make archived sessions and archived workspaces addressable on their own, which is necessary once hidden data can no longer be rebuilt from the active project list. Hard-delete behavior still cleans up transcript files so destructive deletes remain truly destructive. The frontend now mirrors that lifecycle in the sidebar. Delete flows distinguish between archive and permanent delete, archived sessions can be restored, archived workspaces appear beside standalone archived sessions, and archived project sessions open with the correct workspace context instead of routing to a session URL that leaves the main view empty. Follow-up archive UI polish keeps the status affordances explicit without competing with workspace names.
222 lines
8.8 KiB
TypeScript
222 lines
8.8 KiB
TypeScript
import { useMemo } from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import { AlertTriangle, EyeOff, Trash2 } from 'lucide-react';
|
|
import type { TFunction } from 'i18next';
|
|
import { Button } from '../../../../shared/view/ui';
|
|
import Settings from '../../../settings/view/Settings';
|
|
import VersionUpgradeModal from '../../../version-upgrade/view';
|
|
import type { Project } from '../../../../types/app';
|
|
import type { ReleaseInfo } from '../../../../types/sharedTypes';
|
|
import type { InstallMode } from '../../../../hooks/useVersionCheck';
|
|
import { normalizeProjectForSettings } from '../../utils/utils';
|
|
import type { DeleteProjectConfirmation, SessionDeleteConfirmation, SettingsProject } from '../../types/types';
|
|
import ProjectCreationWizard from '../../../project-creation-wizard';
|
|
|
|
type SidebarModalsProps = {
|
|
projects: Project[];
|
|
showSettings: boolean;
|
|
settingsInitialTab: string;
|
|
onCloseSettings: () => void;
|
|
showNewProject: boolean;
|
|
onCloseNewProject: () => void;
|
|
onProjectCreated: () => void;
|
|
deleteConfirmation: DeleteProjectConfirmation | null;
|
|
onCancelDeleteProject: () => void;
|
|
onConfirmDeleteProject: (deleteData?: boolean) => void;
|
|
sessionDeleteConfirmation: SessionDeleteConfirmation | null;
|
|
onCancelDeleteSession: () => void;
|
|
onConfirmDeleteSession: (hardDelete?: boolean) => void;
|
|
showVersionModal: boolean;
|
|
onCloseVersionModal: () => void;
|
|
releaseInfo: ReleaseInfo | null;
|
|
currentVersion: string;
|
|
latestVersion: string | null;
|
|
installMode: InstallMode;
|
|
t: TFunction;
|
|
};
|
|
|
|
type TypedSettingsProps = {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
projects: SettingsProject[];
|
|
initialTab: string;
|
|
};
|
|
|
|
const SettingsComponent = Settings as (props: TypedSettingsProps) => JSX.Element;
|
|
|
|
function TypedSettings(props: TypedSettingsProps) {
|
|
return <SettingsComponent {...props} />;
|
|
}
|
|
|
|
export default function SidebarModals({
|
|
projects,
|
|
showSettings,
|
|
settingsInitialTab,
|
|
onCloseSettings,
|
|
showNewProject,
|
|
onCloseNewProject,
|
|
onProjectCreated,
|
|
deleteConfirmation,
|
|
onCancelDeleteProject,
|
|
onConfirmDeleteProject,
|
|
sessionDeleteConfirmation,
|
|
onCancelDeleteSession,
|
|
onConfirmDeleteSession,
|
|
showVersionModal,
|
|
onCloseVersionModal,
|
|
releaseInfo,
|
|
currentVersion,
|
|
latestVersion,
|
|
installMode,
|
|
t,
|
|
}: SidebarModalsProps) {
|
|
// Settings expects project identity/path fields to be present for dropdown labels and local-scope MCP config.
|
|
const settingsProjects = useMemo(
|
|
() => projects.map(normalizeProjectForSettings),
|
|
[projects],
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{showNewProject &&
|
|
ReactDOM.createPortal(
|
|
<ProjectCreationWizard
|
|
onClose={onCloseNewProject}
|
|
onProjectCreated={onProjectCreated}
|
|
/>,
|
|
document.body,
|
|
)}
|
|
|
|
{showSettings &&
|
|
ReactDOM.createPortal(
|
|
<TypedSettings
|
|
isOpen={showSettings}
|
|
onClose={onCloseSettings}
|
|
projects={settingsProjects}
|
|
initialTab={settingsInitialTab}
|
|
/>,
|
|
document.body,
|
|
)}
|
|
|
|
{deleteConfirmation &&
|
|
ReactDOM.createPortal(
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm">
|
|
<div className="w-full max-w-md overflow-hidden rounded-xl border border-border bg-card shadow-2xl">
|
|
<div className="p-6">
|
|
<div className="flex items-start gap-4">
|
|
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-orange-100 dark:bg-orange-900/30">
|
|
<AlertTriangle className="h-6 w-6 text-orange-600 dark:text-orange-400" />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="mb-2 text-lg font-semibold text-foreground">
|
|
{t('deleteConfirmation.deleteProject')}
|
|
</h3>
|
|
<p className="mb-1 text-sm text-muted-foreground">
|
|
{t('deleteConfirmation.confirmDelete')}{' '}
|
|
<span className="font-medium text-foreground">
|
|
{deleteConfirmation.project.displayName || deleteConfirmation.project.projectId}
|
|
</span>
|
|
?
|
|
</p>
|
|
{deleteConfirmation.sessionCount > 0 && (
|
|
<p className="mt-2 text-sm text-muted-foreground">
|
|
{t('deleteConfirmation.sessionCount', { count: deleteConfirmation.sessionCount })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-2 border-t border-border bg-muted/30 p-4">
|
|
<Button
|
|
variant="outline"
|
|
className="w-full justify-start"
|
|
onClick={() => onConfirmDeleteProject(false)}
|
|
>
|
|
<EyeOff className="mr-2 h-4 w-4" />
|
|
{t('deleteConfirmation.archiveProject', 'Archive project')}
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
className="w-full justify-start bg-red-600 text-white hover:bg-red-700"
|
|
onClick={() => onConfirmDeleteProject(true)}
|
|
>
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
{t('deleteConfirmation.deleteAllData')}
|
|
</Button>
|
|
<Button variant="ghost" className="w-full" onClick={onCancelDeleteProject}>
|
|
{t('actions.cancel')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
)}
|
|
|
|
{sessionDeleteConfirmation &&
|
|
ReactDOM.createPortal(
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm">
|
|
<div className="w-full max-w-md overflow-hidden rounded-xl border border-border bg-card shadow-2xl">
|
|
<div className="p-6">
|
|
<div className="flex items-start gap-4">
|
|
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/30">
|
|
<AlertTriangle className="h-6 w-6 text-red-600 dark:text-red-400" />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="mb-2 text-lg font-semibold text-foreground">
|
|
{t('deleteConfirmation.deleteSession')}
|
|
</h3>
|
|
<p className="mb-1 text-sm text-muted-foreground">
|
|
{t('deleteConfirmation.confirmDelete')}{' '}
|
|
<span className="font-medium text-foreground">
|
|
{sessionDeleteConfirmation.sessionTitle || t('sessions.unnamed')}
|
|
</span>
|
|
?
|
|
</p>
|
|
<p className="mt-3 text-xs text-muted-foreground">
|
|
{sessionDeleteConfirmation.isArchived
|
|
? t('deleteConfirmation.archivedSessionNotice', 'This session is already archived. You can keep it hidden or delete it permanently.')
|
|
: t('deleteConfirmation.archiveSessionNotice', 'Archive keeps the session out of the active list while preserving its history.')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-2 border-t border-border bg-muted/30 p-4">
|
|
{!sessionDeleteConfirmation.isArchived && (
|
|
<Button
|
|
variant="outline"
|
|
className="w-full justify-start"
|
|
onClick={() => onConfirmDeleteSession(false)}
|
|
>
|
|
<EyeOff className="mr-2 h-4 w-4" />
|
|
{t('deleteConfirmation.archiveSession', 'Archive session')}
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="destructive"
|
|
className="w-full justify-start bg-red-600 text-white hover:bg-red-700"
|
|
onClick={() => onConfirmDeleteSession(true)}
|
|
>
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
{t('deleteConfirmation.deleteSessionPermanently', 'Delete permanently')}
|
|
</Button>
|
|
<Button variant="ghost" className="w-full" onClick={onCancelDeleteSession}>
|
|
{t('actions.cancel')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
)}
|
|
|
|
<VersionUpgradeModal
|
|
isOpen={showVersionModal}
|
|
onClose={onCloseVersionModal}
|
|
releaseInfo={releaseInfo}
|
|
currentVersion={currentVersion}
|
|
latestVersion={latestVersion}
|
|
installMode={installMode}
|
|
/>
|
|
</>
|
|
);
|
|
}
|