Skip to content

fix(desktop): prevent StartView flash when deleting active workspace#714

Merged
Kitenite merged 1 commit intomainfrom
fix/workspace-deletion-startview-bug
Jan 12, 2026
Merged

fix(desktop): prevent StartView flash when deleting active workspace#714
Kitenite merged 1 commit intomainfrom
fix/workspace-deletion-startview-bug

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 12, 2026

Summary

  • Fixed bug where deleting the active workspace would sometimes cause the StartView ("select project" screen) to appear even when other workspaces exist
  • Root cause: mismatch between backend workspace selection (all workspaces) and frontend cache (only visible projects)

Changes

Backend (db-helpers.ts):

  • selectNextActiveWorkspace() now only selects from visible projects by joining with the projects table and filtering by tabOrder != null

Frontend (useDeleteWorkspace.ts, useCloseWorkspace.ts):

  • Iterate through all remaining workspaces to find one with full data in previousGrouped, instead of only checking the first one
  • Add fallback that sets minimal workspace data instead of null when cache is missing, preventing StartView flash

Test plan

  • Delete the active workspace when multiple workspaces exist in the same project
  • Delete the active workspace when workspaces exist in different projects
  • Verify no StartView flash occurs during workspace deletion
  • Verify the next workspace becomes active correctly

🤖 Generated with Claude Code


Note

Aligns next-workspace selection to visible projects and hardens optimistic switching to avoid StartView flashes when closing/deleting the active workspace.

  • Backend: selectNextActiveWorkspace() now joins projects and filters by projects.tabOrder != null, selecting most-recent from visible projects only
  • Frontend (close/delete hooks): iterate remaining workspaces to find one present in getAllGrouped and optimistically set it active; add minimal-data fallback instead of null to prevent StartView flash; keep caches in sync and invalidate after ops (plus recents on close)

Written by Cursor Bugbot for commit 975f461. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved workspace selection when closing or deleting a workspace—now correctly filters to visible workspaces only.
    • Enhanced data consistency during workspace transitions to reduce UI flashing and provide a smoother experience.

✏️ Tip: You can customize this high-level summary in your review settings.

Root cause: When deleting the active workspace, the optimistic update
tried to find the next workspace in `previousGrouped` (which only
contains visible projects). If the next workspace was in a hidden
project or the cache was empty, it fell back to setting `getActive`
to null, causing StartView to incorrectly appear.

Changes:
- Backend: `selectNextActiveWorkspace()` now only selects from visible
  projects (joins with projects table and filters by tabOrder != null)
- Frontend: Iterate through remaining workspaces to find one with full
  data in cache, instead of only checking the first one
- Frontend: Add fallback that sets minimal workspace data instead of
  null when cache miss occurs, preventing StartView flash

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Modified workspace selection logic to filter visible projects only (non-null tabOrder). Enhanced optimistic updates in close/delete handlers to search for candidates with full grouped data. Improved data integrity and reduced UI flash with minimal safe fallback structure.

Changes

Cohort / File(s) Summary
Backend Workspace Selection
apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts
Modified selectNextActiveWorkspace to filter workspaces by project visibility (innerJoin with non-null tabOrder) instead of selecting any non-deleting workspace. Narrowed selection to id and lastOpenedAt columns, maintains ordering by lastOpenedAt. Updates descriptive comment to clarify VISIBLE projects only.
Frontend Optimistic Updates
apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts, useDeleteWorkspace.ts
Replaced hard-coded next workspace selection with robust candidate search over remainingWorkspaces to locate entry with full data in previousGrouped structure. When candidate found, adopts complete data including branch info for worktrees. Falls back to minimal safe data structure (type, worktreePath, project, worktree only) when no full data available, preventing UI flash over null state.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A workspace selection tale in spring,
No more to flash or blink and fling,
We search through grouped data with care,
Pick visible ones, smooth and fair,
With fallback paths, no null despair!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main bug fix: preventing StartView flash when deleting the active workspace.
Description check ✅ Passed The description is well-structured with summary, specific changes by file, and a test plan. However, it deviates from the provided template structure by not using the prescribed sections (Related Issues, Type of Change, Testing, Screenshots, Additional Notes).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

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: 0

🧹 Nitpick comments (2)
apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts (2)

118-128: Verify UI components handle project: null gracefully.

The fallback prevents the StartView flash, but components consuming getActive data may not expect project: null during the brief window before refetch completes. Ensure any UI rendering active workspace metadata uses optional chaining or null checks.

Also, the type normalization on line 123 appears redundant if fallback.type is already constrained to "branch" | "worktree". Consider simplifying:

-  type: fallback.type === "branch" ? "branch" : "worktree",
+  type: fallback.type,

67-132: Consider extracting shared candidate selection logic.

The next-active-workspace selection logic (lines 69-131) is nearly identical to useDeleteWorkspace.ts. This could be extracted to a shared utility function to reduce duplication and ensure consistent behavior.

♻️ Example extraction
// In a shared utility file, e.g., workspaceSelectionUtils.ts
export function selectNextActiveWorkspaceFromCache({
  remainingWorkspaces,
  previousGrouped,
}: {
  remainingWorkspaces: WorkspaceType[];
  previousGrouped: GroupedWorkspaces | undefined;
}): ActiveWorkspaceData | null {
  // ... shared selection logic
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a77cba2 and 975f461.

📒 Files selected for processing (3)
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
🧰 Additional context used
📓 Path-based instructions (5)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Files:

  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build
🔇 Additional comments (5)
apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts (1)

112-130: LGTM! Query correctly filters for visible projects.

The innerJoin with projects and isNotNull(projects.tabOrder) constraint ensures the selected workspace belongs to a visible project, aligning backend selection with frontend cache expectations. The narrowed select to only id and lastOpenedAt is appropriate since only id is used in the return value.

apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts (1)

74-91: Candidate selection logic is sound.

The iteration through remainingWorkspaces to find a workspace with complete grouped data is a good approach. Breaking on the first match ensures deterministic behavior while respecting the lastOpenedAt sort order.

apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts (3)

69-86: Candidate selection logic correctly prioritizes data completeness.

The loop through remainingWorkspaces sorted by lastOpenedAt ensures the most recently opened workspace with full cached data is selected. This aligns backend and frontend selection behavior.


113-123: Same fallback pattern as useCloseWorkspace - ensure consistency.

The fallback logic mirrors useCloseWorkspace.ts. The same note about verifying UI components handle project: null applies here. Since this is duplicated code, extracting to a shared utility (as noted in the other file) would ensure any future fixes apply to both paths.


88-112: Optimistic update data construction is correct.

The worktreeData construction and active workspace update properly combine data from selectedWorkspace (timestamps, branch) with workspaceFromGrouped (type, worktreePath) and projectGroup (project metadata). This ensures the optimistic state is complete and consistent.

@Kitenite Kitenite merged commit 5197d41 into main Jan 12, 2026
6 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

worktreePath: "",
project: null,
worktree: null,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fallback triggers initialization view for worktree workspaces

Medium Severity

The fallback sets worktree: null while also setting type to "worktree" for non-branch workspaces. This combination causes hasIncompleteInit to evaluate to true in WorkspaceView (since worktree?.gitStatus becomes undefined), which displays WorkspaceInitializingView instead of the normal content. The main path provides minimal worktreeData with a gitStatus object for worktree types, but this fallback doesn't, defeating the goal of preventing incorrect views during workspace transitions.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

1 participant