Skip to content

Commit 38dd003

Browse files
authored
🤖 feat: show deletion feedback in workspace sidebar (#819)
_Generated with `mux`_ When a workspace is being deleted, the UI now provides visual feedback: - **Dimmed row**: The workspace row becomes 50% opacity - **Disabled interaction**: Click/hover disabled during deletion - **Status message**: Shows '🗑️ Deleting...' in place of normal status Works for both regular and force-delete flows.
1 parent 45812f7 commit 38dd003

File tree

2 files changed

+60
-24
lines changed

2 files changed

+60
-24
lines changed

src/browser/components/ProjectSidebar.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
213213
const [expandedOldWorkspaces, setExpandedOldWorkspaces] = usePersistedState<
214214
Record<string, boolean>
215215
>("expandedOldWorkspaces", {});
216+
const [deletingWorkspaceIds, setDeletingWorkspaceIds] = useState<Set<string>>(new Set());
216217
const [removeError, setRemoveError] = useState<{
217218
workspaceId: string;
218219
error: string;
@@ -292,22 +293,34 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
292293

293294
const handleRemoveWorkspace = useCallback(
294295
async (workspaceId: string, buttonElement: HTMLElement) => {
295-
const result = await onRemoveWorkspace(workspaceId);
296-
if (!result.success) {
297-
const error = result.error ?? "Failed to remove workspace";
298-
const rect = buttonElement.getBoundingClientRect();
299-
const anchor = {
300-
top: rect.top + window.scrollY,
301-
left: rect.right + 10, // 10px to the right of button
302-
};
303-
304-
// Show force delete modal on any error to handle all cases
305-
// (uncommitted changes, submodules, etc.)
306-
setForceDeleteModal({
307-
isOpen: true,
308-
workspaceId,
309-
error,
310-
anchor,
296+
// Mark workspace as being deleted for UI feedback
297+
setDeletingWorkspaceIds((prev) => new Set(prev).add(workspaceId));
298+
299+
try {
300+
const result = await onRemoveWorkspace(workspaceId);
301+
if (!result.success) {
302+
const error = result.error ?? "Failed to remove workspace";
303+
const rect = buttonElement.getBoundingClientRect();
304+
const anchor = {
305+
top: rect.top + window.scrollY,
306+
left: rect.right + 10, // 10px to the right of button
307+
};
308+
309+
// Show force delete modal on any error to handle all cases
310+
// (uncommitted changes, submodules, etc.)
311+
setForceDeleteModal({
312+
isOpen: true,
313+
workspaceId,
314+
error,
315+
anchor,
316+
});
317+
}
318+
} finally {
319+
// Clear deleting state (workspace removed or error shown)
320+
setDeletingWorkspaceIds((prev) => {
321+
const next = new Set(prev);
322+
next.delete(workspaceId);
323+
return next;
311324
});
312325
}
313326
},
@@ -329,13 +342,25 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
329342
// Close modal immediately to show that action is in progress
330343
setForceDeleteModal(null);
331344

332-
// Use the same state update logic as regular removal
333-
const result = await onRemoveWorkspace(workspaceId, { force: true });
334-
if (!result.success) {
335-
const errorMessage = result.error ?? "Failed to remove workspace";
336-
console.error("Force delete failed:", result.error);
345+
// Mark workspace as being deleted for UI feedback
346+
setDeletingWorkspaceIds((prev) => new Set(prev).add(workspaceId));
347+
348+
try {
349+
// Use the same state update logic as regular removal
350+
const result = await onRemoveWorkspace(workspaceId, { force: true });
351+
if (!result.success) {
352+
const errorMessage = result.error ?? "Failed to remove workspace";
353+
console.error("Force delete failed:", result.error);
337354

338-
showRemoveError(workspaceId, errorMessage, modalState?.anchor ?? undefined);
355+
showRemoveError(workspaceId, errorMessage, modalState?.anchor ?? undefined);
356+
}
357+
} finally {
358+
// Clear deleting state
359+
setDeletingWorkspaceIds((prev) => {
360+
const next = new Set(prev);
361+
next.delete(workspaceId);
362+
return next;
363+
});
339364
}
340365
};
341366

@@ -574,6 +599,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
574599
projectPath={projectPath}
575600
projectName={projectName}
576601
isSelected={selectedWorkspace?.workspaceId === metadata.id}
602+
isDeleting={deletingWorkspaceIds.has(metadata.id)}
577603
lastReadTimestamp={lastReadTimestamps[metadata.id] ?? 0}
578604
onSelectWorkspace={onSelectWorkspace}
579605
onRemoveWorkspace={handleRemoveWorkspace}

src/browser/components/WorkspaceListItem.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface WorkspaceListItemProps {
2323
projectPath: string;
2424
projectName: string;
2525
isSelected: boolean;
26+
isDeleting?: boolean;
2627
lastReadTimestamp: number;
2728
// Event handlers
2829
onSelectWorkspace: (selection: WorkspaceSelection) => void;
@@ -35,6 +36,7 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
3536
projectPath,
3637
projectName,
3738
isSelected,
39+
isDeleting,
3840
lastReadTimestamp,
3941
onSelectWorkspace,
4042
onRemoveWorkspace,
@@ -104,7 +106,8 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
104106
<div
105107
className={cn(
106108
"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",
107-
isSelected && "bg-hover border-l-blue-400"
109+
isSelected && "bg-hover border-l-blue-400",
110+
isDeleting && "opacity-50 pointer-events-none"
108111
)}
109112
onClick={() =>
110113
onSelectWorkspace({
@@ -199,7 +202,14 @@ const WorkspaceListItemInner: React.FC<WorkspaceListItemProps> = ({
199202
</div>
200203
</div>
201204
<div className="min-w-0">
202-
<WorkspaceStatusIndicator workspaceId={workspaceId} />
205+
{isDeleting ? (
206+
<div className="text-muted flex min-w-0 items-center gap-1.5 text-xs">
207+
<span className="-mt-0.5 shrink-0 text-[10px]">🗑️</span>
208+
<span className="min-w-0 truncate">Deleting...</span>
209+
</div>
210+
) : (
211+
<WorkspaceStatusIndicator workspaceId={workspaceId} />
212+
)}
203213
</div>
204214
</div>
205215
</div>

0 commit comments

Comments
 (0)