Skip to content

Commit 1e6e8a5

Browse files
committed
🤖 fix: show removeProject errors in UI instead of only console
- Changed removeProject to return { success, error } result instead of void - Added projectRemoveError state and showProjectRemoveError helper in ProjectSidebar - Display error popup near the remove button when project removal fails - Matches existing pattern used for workspace removal errors _Generated with mux_
1 parent 4d1947a commit 1e6e8a5

File tree

3 files changed

+75
-7
lines changed

3 files changed

+75
-7
lines changed

src/browser/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,11 @@ function AppInner() {
190190
const openWorkspaceInTerminal = useOpenTerminal();
191191

192192
const handleRemoveProject = useCallback(
193-
async (path: string) => {
193+
async (path: string): Promise<{ success: boolean; error?: string }> => {
194194
if (selectedWorkspace?.projectPath === path) {
195195
setSelectedWorkspace(null);
196196
}
197-
await removeProject(path);
197+
return removeProject(path);
198198
},
199199
// eslint-disable-next-line react-hooks/exhaustive-deps
200200
[selectedWorkspace, setSelectedWorkspace]

src/browser/components/ProjectSidebar.tsx

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
246246
position: { top: number; left: number };
247247
} | null>(null);
248248
const removeErrorTimeoutRef = useRef<number | null>(null);
249+
const [projectRemoveError, setProjectRemoveError] = useState<{
250+
projectPath: string;
251+
error: string;
252+
position: { top: number; left: number };
253+
} | null>(null);
254+
const projectRemoveErrorTimeoutRef = useRef<number | null>(null);
249255
const [secretsModalState, setSecretsModalState] = useState<{
250256
isOpen: boolean;
251257
projectPath: string;
@@ -317,6 +323,39 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
317323
};
318324
}, []);
319325

326+
const showProjectRemoveError = useCallback(
327+
(projectPath: string, error: string, anchor?: { top: number; left: number }) => {
328+
if (projectRemoveErrorTimeoutRef.current) {
329+
window.clearTimeout(projectRemoveErrorTimeoutRef.current);
330+
}
331+
332+
const position = anchor ?? {
333+
top: window.scrollY + 32,
334+
left: Math.max(window.innerWidth - 420, 16),
335+
};
336+
337+
setProjectRemoveError({
338+
projectPath,
339+
error,
340+
position,
341+
});
342+
343+
projectRemoveErrorTimeoutRef.current = window.setTimeout(() => {
344+
setProjectRemoveError(null);
345+
projectRemoveErrorTimeoutRef.current = null;
346+
}, 5000);
347+
},
348+
[]
349+
);
350+
351+
useEffect(() => {
352+
return () => {
353+
if (projectRemoveErrorTimeoutRef.current) {
354+
window.clearTimeout(projectRemoveErrorTimeoutRef.current);
355+
}
356+
};
357+
}, []);
358+
320359
const handleRemoveWorkspace = useCallback(
321360
async (workspaceId: string, buttonElement: HTMLElement) => {
322361
// Mark workspace as being deleted for UI feedback
@@ -577,7 +616,19 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
577616
<button
578617
onClick={(event) => {
579618
event.stopPropagation();
580-
void onRemoveProject(projectPath);
619+
const buttonElement = event.currentTarget;
620+
void (async () => {
621+
const result = await onRemoveProject(projectPath);
622+
if (!result.success) {
623+
const error = result.error ?? "Failed to remove project";
624+
const rect = buttonElement.getBoundingClientRect();
625+
const anchor = {
626+
top: rect.top + window.scrollY,
627+
left: rect.right + 10,
628+
};
629+
showProjectRemoveError(projectPath, error, anchor);
630+
}
631+
})();
581632
}}
582633
title="Remove project"
583634
aria-label={`Remove project ${projectName}`}
@@ -762,6 +813,19 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
762813
</div>,
763814
document.body
764815
)}
816+
{projectRemoveError &&
817+
createPortal(
818+
<div
819+
className="bg-error-bg border-error text-error font-monospace pointer-events-auto fixed z-[10000] max-w-96 rounded-md border p-3 px-4 text-xs leading-[1.4] break-words whitespace-pre-wrap shadow-[0_4px_16px_rgba(0,0,0,0.5)]"
820+
style={{
821+
top: `${projectRemoveError.position.top}px`,
822+
left: `${projectRemoveError.position.left}px`,
823+
}}
824+
>
825+
Failed to remove project: {projectRemoveError.error}
826+
</div>,
827+
document.body
828+
)}
765829
</div>
766830
</DndProvider>
767831
</RenameProvider>

src/browser/contexts/ProjectContext.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface ProjectContext {
2727
projects: Map<string, ProjectConfig>;
2828
refreshProjects: () => Promise<void>;
2929
addProject: (normalizedPath: string, projectConfig: ProjectConfig) => void;
30-
removeProject: (path: string) => Promise<void>;
30+
removeProject: (path: string) => Promise<{ success: boolean; error?: string }>;
3131

3232
// Project creation modal
3333
isProjectCreateModalOpen: boolean;
@@ -94,8 +94,8 @@ export function ProjectProvider(props: { children: ReactNode }) {
9494
}, []);
9595

9696
const removeProject = useCallback(
97-
async (path: string) => {
98-
if (!api) return;
97+
async (path: string): Promise<{ success: boolean; error?: string }> => {
98+
if (!api) return { success: false, error: "API not connected" };
9999
try {
100100
const result = await api.projects.remove({ projectPath: path });
101101
if (result.success) {
@@ -104,11 +104,15 @@ export function ProjectProvider(props: { children: ReactNode }) {
104104
next.delete(path);
105105
return next;
106106
});
107+
return { success: true };
107108
} else {
108109
console.error("Failed to remove project:", result.error);
110+
return { success: false, error: result.error };
109111
}
110112
} catch (error) {
111-
console.error("Failed to remove project:", error);
113+
const errorMessage = error instanceof Error ? error.message : String(error);
114+
console.error("Failed to remove project:", errorMessage);
115+
return { success: false, error: errorMessage };
112116
}
113117
},
114118
[api]

0 commit comments

Comments
 (0)