Skip to content

fix: await removeProject to prevent race condition on project re-add (fixes #1910)#1998

Open
octo-patch wants to merge 1 commit intoAndyMik90:developfrom
octo-patch:fix/issue-1910-project-reopen-empty-ui
Open

fix: await removeProject to prevent race condition on project re-add (fixes #1910)#1998
octo-patch wants to merge 1 commit intoAndyMik90:developfrom
octo-patch:fix/issue-1910-project-reopen-empty-ui

Conversation

@octo-patch
Copy link
Copy Markdown

@octo-patch octo-patch commented Apr 7, 2026

Fixes #1910

Problem

After closing a project tab and immediately re-adding the same directory, the UI shows up completely empty — no Kanban cards, no roadmap.

The root cause is a race condition in handleConfirmRemoveProject: removeProject() was called without await, so the dialog closed immediately. If the user quickly re-added the same directory, addProject IPC could arrive at the main process before removeProject completed — so the backend still found the old project and returned the old project ID. When removal finally resolved, it called store.removeProject(oldId) + store.closeProjectTab(oldId), wiping all tasks from state and leaving the UI empty.

Solution

Make handleConfirmRemoveProject async and await removeProject(). The dialog stays open until removal fully completes, eliminating the race window. Also disable dialog buttons while removal is in progress to prevent double-submission.

Testing

  1. Open two projects (so the close tab X button appears)
  2. Close one project tab and confirm with Remove
  3. Immediately click Add Project and re-add the same directory
  4. Verify the project reopens with all Kanban tasks and roadmap intact

Summary by CodeRabbit

Bug Fixes

  • Improved project removal workflow by disabling dialog buttons during the removal process, preventing accidental interactions.
  • Enhanced error handling to display descriptive messages when project removal fails.
  • Added loading state feedback to indicate when a removal operation is in progress.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

The change adds async state tracking for project removal operations in the App component. The remove-project dialog now waits for the removal operation to complete, disables buttons during removal, and only closes on successful removal with error feedback otherwise.

Changes

Cohort / File(s) Summary
Project Removal UI State
apps/desktop/src/renderer/App.tsx
Added isRemovingProject state to track removal progress. Made handleConfirmRemoveProject async with success/error handling. Dialog buttons disabled during removal, closes only on successful operation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 With async grace, the project takes flight,
Our buttons rest gently while we set things right,
We wait for success before closing the door,
Error feedback flows, so we know what's in store! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding await to removeProject to prevent a race condition when re-adding projects, which directly matches the code changes in App.tsx.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@octo-patch octo-patch changed the title fix fix: await removeProject to prevent race condition on project re-add (fixes #1910) Apr 7, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a loading state and asynchronous handling for project removal to prevent race conditions. It ensures the backend operation completes before the UI updates and disables action buttons during the process. A review comment suggests removing a redundant namespace prefix in a translation key for consistency.

setShowRemoveProjectDialog(false);
setProjectToRemove(null);
} else {
setRemoveProjectError(t('dialogs:removeProject.error'));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The useTranslation hook is initialized with the dialogs namespace on line 314. Including the dialogs: prefix here is redundant and inconsistent with other translation calls in this file (e.g., lines 1137 and 1140).

Suggested change
setRemoveProjectError(t('dialogs:removeProject.error'));
setRemoveProjectError(t('removeProject.error'));

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/App.tsx (1)

696-701: 🧹 Nitpick | 🔵 Trivial

Minor edge case: canceling during removal may cause inconsistent state.

If the user presses Escape while removal is in progress (the dialog's onOpenChange calls handleCancelRemoveProject), isRemovingProject resets to false but the async operation continues in the background. This is unlikely to cause problems in practice since the main race condition (re-adding project) is now blocked by awaiting the removal.

Consider disabling dialog dismissal while isRemovingProject is true:

🔧 Optional: Prevent dialog dismissal during removal
-        <Dialog open={showRemoveProjectDialog} onOpenChange={(open) => {
-          if (!open) handleCancelRemoveProject();
+        <Dialog open={showRemoveProjectDialog} onOpenChange={(open) => {
+          if (!open && !isRemovingProject) handleCancelRemoveProject();
         }}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/App.tsx` around lines 696 - 701, Prevent the dialog
from being dismissed while an async removal is in progress by making the dialog
ignore outside clicks/Escape when isRemovingProject is true: update the dialog's
open/onOpenChange handling so that onOpenChange (which currently calls
handleCancelRemoveProject) returns early or is a no-op if isRemovingProject is
true, and/or pass a prop to the Dialog component to disable dismissal when
isRemovingProject is true; keep handleCancelRemoveProject as the cancel path
when not removing (it should continue to call setShowRemoveProjectDialog(false),
setProjectToRemove(null), setRemoveProjectError(null),
setIsRemovingProject(false)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/renderer/App.tsx`:
- Around line 1135-1141: Add a loading indicator to the destructive "Remove"
button so users see progress when isRemovingProject is true: in the DialogFooter
where the Button with onClick={handleConfirmRemoveProject} and
disabled={isRemovingProject} is rendered, show the same spinner icon/visual used
by the Initialize button when isInitializing is true (condition on
isRemovingProject) and swap the button label to the new translation key
removeProject.removing while keeping the existing removeProject.remove for the
idle state; ensure to add the removeProject.removing translation entry and
preserve the disabled prop and onClick handler (handleConfirmRemoveProject).

---

Outside diff comments:
In `@apps/desktop/src/renderer/App.tsx`:
- Around line 696-701: Prevent the dialog from being dismissed while an async
removal is in progress by making the dialog ignore outside clicks/Escape when
isRemovingProject is true: update the dialog's open/onOpenChange handling so
that onOpenChange (which currently calls handleCancelRemoveProject) returns
early or is a no-op if isRemovingProject is true, and/or pass a prop to the
Dialog component to disable dismissal when isRemovingProject is true; keep
handleCancelRemoveProject as the cancel path when not removing (it should
continue to call setShowRemoveProjectDialog(false), setProjectToRemove(null),
setRemoveProjectError(null), setIsRemovingProject(false)).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9e56fa00-3813-44f9-9d52-be580364a1cd

📥 Commits

Reviewing files that changed from the base of the PR and between cba7a02 and 78a4ba6.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/App.tsx

Comment on lines 1135 to 1141
<DialogFooter>
<Button variant="outline" onClick={handleCancelRemoveProject}>
<Button variant="outline" onClick={handleCancelRemoveProject} disabled={isRemovingProject}>
{t('removeProject.cancel')}
</Button>
<Button variant="destructive" onClick={handleConfirmRemoveProject}>
<Button variant="destructive" onClick={handleConfirmRemoveProject} disabled={isRemovingProject}>
{t('removeProject.remove')}
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding a loading indicator to the Remove button.

The buttons are correctly disabled during removal, but unlike the Initialize button (which shows a spinner when isInitializing), the Remove button provides no visual feedback that an operation is in progress. This is a minor UX inconsistency.

💅 Optional: Add loading spinner for consistency
-              <Button variant="destructive" onClick={handleConfirmRemoveProject} disabled={isRemovingProject}>
-                {t('removeProject.remove')}
+              <Button variant="destructive" onClick={handleConfirmRemoveProject} disabled={isRemovingProject}>
+                {isRemovingProject ? (
+                  <>
+                    <RefreshCw className="mr-2 h-4 w-4 animate-spin" />
+                    {t('removeProject.removing')}
+                  </>
+                ) : (
+                  t('removeProject.remove')
+                )}
               </Button>

Note: This would require adding a removeProject.removing translation key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/App.tsx` around lines 1135 - 1141, Add a loading
indicator to the destructive "Remove" button so users see progress when
isRemovingProject is true: in the DialogFooter where the Button with
onClick={handleConfirmRemoveProject} and disabled={isRemovingProject} is
rendered, show the same spinner icon/visual used by the Initialize button when
isInitializing is true (condition on isRemovingProject) and swap the button
label to the new translation key removeProject.removing while keeping the
existing removeProject.remove for the idle state; ensure to add the
removeProject.removing translation entry and preserve the disabled prop and
onClick handler (handleConfirmRemoveProject).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reopening of project not reloading the UI correctly

2 participants