Skip to content

Commit d64b2a1

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 284dbc7 commit d64b2a1

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
@@ -211,6 +211,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
211211
const [expandedOldWorkspaces, setExpandedOldWorkspaces] = usePersistedState<
212212
Record<string, boolean>
213213
>("expandedOldWorkspaces", {});
214+
const [deletingWorkspaceIds, setDeletingWorkspaceIds] = useState<Set<string>>(new Set());
214215
const [removeError, setRemoveError] = useState<{
215216
workspaceId: string;
216217
error: string;
@@ -289,22 +290,34 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
289290

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

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

335-
showRemoveError(workspaceId, errorMessage, modalState?.anchor ?? undefined);
352+
showRemoveError(workspaceId, errorMessage, modalState?.anchor ?? undefined);
353+
}
354+
} finally {
355+
// Clear deleting state
356+
setDeletingWorkspaceIds((prev) => {
357+
const next = new Set(prev);
358+
next.delete(workspaceId);
359+
return next;
360+
});
336361
}
337362
};
338363

@@ -572,6 +597,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
572597
projectPath={projectPath}
573598
projectName={projectName}
574599
isSelected={selectedWorkspace?.workspaceId === metadata.id}
600+
isDeleting={deletingWorkspaceIds.has(metadata.id)}
575601
lastReadTimestamp={lastReadTimestamps[metadata.id] ?? 0}
576602
onSelectWorkspace={onSelectWorkspace}
577603
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)