Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 48 additions & 22 deletions src/browser/components/ProjectSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
const [expandedOldWorkspaces, setExpandedOldWorkspaces] = usePersistedState<
Record<string, boolean>
>("expandedOldWorkspaces", {});
const [deletingWorkspaceIds, setDeletingWorkspaceIds] = useState<Set<string>>(new Set());
const [removeError, setRemoveError] = useState<{
workspaceId: string;
error: string;
Expand Down Expand Up @@ -289,22 +290,34 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({

const handleRemoveWorkspace = useCallback(
async (workspaceId: string, buttonElement: HTMLElement) => {
const result = await onRemoveWorkspace(workspaceId);
if (!result.success) {
const error = result.error ?? "Failed to remove workspace";
const rect = buttonElement.getBoundingClientRect();
const anchor = {
top: rect.top + window.scrollY,
left: rect.right + 10, // 10px to the right of button
};

// Show force delete modal on any error to handle all cases
// (uncommitted changes, submodules, etc.)
setForceDeleteModal({
isOpen: true,
workspaceId,
error,
anchor,
// Mark workspace as being deleted for UI feedback
setDeletingWorkspaceIds((prev) => new Set(prev).add(workspaceId));

try {
const result = await onRemoveWorkspace(workspaceId);
if (!result.success) {
const error = result.error ?? "Failed to remove workspace";
const rect = buttonElement.getBoundingClientRect();
const anchor = {
top: rect.top + window.scrollY,
left: rect.right + 10, // 10px to the right of button
};

// Show force delete modal on any error to handle all cases
// (uncommitted changes, submodules, etc.)
setForceDeleteModal({
isOpen: true,
workspaceId,
error,
anchor,
});
}
} finally {
// Clear deleting state (workspace removed or error shown)
setDeletingWorkspaceIds((prev) => {
const next = new Set(prev);
next.delete(workspaceId);
return next;
});
}
},
Expand All @@ -326,13 +339,25 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
// Close modal immediately to show that action is in progress
setForceDeleteModal(null);

// Use the same state update logic as regular removal
const result = await onRemoveWorkspace(workspaceId, { force: true });
if (!result.success) {
const errorMessage = result.error ?? "Failed to remove workspace";
console.error("Force delete failed:", result.error);
// Mark workspace as being deleted for UI feedback
setDeletingWorkspaceIds((prev) => new Set(prev).add(workspaceId));

try {
// Use the same state update logic as regular removal
const result = await onRemoveWorkspace(workspaceId, { force: true });
if (!result.success) {
const errorMessage = result.error ?? "Failed to remove workspace";
console.error("Force delete failed:", result.error);

showRemoveError(workspaceId, errorMessage, modalState?.anchor ?? undefined);
showRemoveError(workspaceId, errorMessage, modalState?.anchor ?? undefined);
}
} finally {
// Clear deleting state
setDeletingWorkspaceIds((prev) => {
const next = new Set(prev);
next.delete(workspaceId);
return next;
});
}
};

Expand Down Expand Up @@ -572,6 +597,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
projectPath={projectPath}
projectName={projectName}
isSelected={selectedWorkspace?.workspaceId === metadata.id}
isDeleting={deletingWorkspaceIds.has(metadata.id)}
lastReadTimestamp={lastReadTimestamps[metadata.id] ?? 0}
onSelectWorkspace={onSelectWorkspace}
onRemoveWorkspace={handleRemoveWorkspace}
Expand Down
14 changes: 12 additions & 2 deletions src/browser/components/WorkspaceListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface WorkspaceListItemProps {
projectPath: string;
projectName: string;
isSelected: boolean;
isDeleting?: boolean;
lastReadTimestamp: number;
// Event handlers
onSelectWorkspace: (selection: WorkspaceSelection) => void;
Expand All @@ -35,6 +36,7 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
projectPath,
projectName,
isSelected,
isDeleting,
lastReadTimestamp,
onSelectWorkspace,
onRemoveWorkspace,
Expand Down Expand Up @@ -104,7 +106,8 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
<div
className={cn(
"py-1.5 pl-4 pr-2 cursor-pointer border-l-[3px] border-transparent transition-all duration-150 text-[13px] relative hover:bg-hover [&:hover_button]:opacity-100 flex gap-2",
isSelected && "bg-hover border-l-blue-400"
isSelected && "bg-hover border-l-blue-400",
isDeleting && "opacity-50 pointer-events-none"
)}
onClick={() =>
onSelectWorkspace({
Expand Down Expand Up @@ -199,7 +202,14 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
</div>
</div>
<div className="min-w-0">
<WorkspaceStatusIndicator workspaceId={workspaceId} />
{isDeleting ? (
<div className="text-muted flex min-w-0 items-center gap-1.5 text-xs">
<span className="-mt-0.5 shrink-0 text-[10px]">🗑️</span>
<span className="min-w-0 truncate">Deleting...</span>
</div>
) : (
<WorkspaceStatusIndicator workspaceId={workspaceId} />
)}
</div>
</div>
</div>
Expand Down