Skip to content

fix(desktop): use getAllGrouped as single source of truth for workspace queries#715

Closed
Kitenite wants to merge 1 commit intomainfrom
fix/desktop-workspace-cache-single-source-of-truth
Closed

fix(desktop): use getAllGrouped as single source of truth for workspace queries#715
Kitenite wants to merge 1 commit intomainfrom
fix/desktop-workspace-cache-single-source-of-truth

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 12, 2026

Summary

  • Fixes StartView flash when deleting/closing the active workspace
  • Uses getAllGrouped as the single source of truth for workspace data (always cached by sidebar)
  • Removes unused getAll query from frontend and backend

Root Cause

The optimistic updates in useDeleteWorkspace and useCloseWorkspace relied on the getAll query cache to find the next workspace. However, getAll was rarely queried (only used by PortsList), so its cache was usually empty. When empty, the optimistic update would incorrectly set activeWorkspace to null, causing StartView to flash briefly.

Changes

Frontend:

  • useDeleteWorkspace.ts / useCloseWorkspace.ts: Use getAllGrouped as source of truth instead of getAll
  • usePortsData.ts: Derive workspace list from getAllGrouped instead of querying getAll
  • useSetActiveWorkspace.ts / useReorderWorkspaces.ts: Remove dead getAll.invalidate() calls

Backend:

  • procedures/query.ts: Remove unused getAll procedure

Test plan

  • Delete active workspace with multiple workspaces open → should switch to next workspace without StartView flash
  • Delete the only/last workspace → should show StartView
  • Close active workspace → should switch to next workspace without flash
  • Ports list should still display correctly

🤖 Generated with Claude Code


Note

Consolidates workspace data fetching to a single query and improves optimistic updates to eliminate UI flicker on workspace changes.

  • Backend: Remove unused getAll procedure; keep get, getAllGrouped, and getActive.
  • Frontend: Update useCloseWorkspace and useDeleteWorkspace to rely on getAllGrouped for optimistic next-workspace selection and avoid invalidating getActive to prevent flashes.
  • Frontend: Simplify invalidations in useReorderWorkspaces and useSetActiveWorkspace (drop getAll); keep getAllGrouped/getActive where needed.
  • Frontend: usePortsData derives flat workspace list from getAllGrouped instead of querying getAll.

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

Summary by CodeRabbit

  • Refactor

    • Consolidated workspace data handling to use grouped workspace data as the single source of truth.
  • Bug Fixes

    • Reduced UI flashes and improved optimistic updates when closing, deleting, reordering, or switching workspaces; next-active selection logic simplified.
  • Documentation

    • Updated inline docs to reflect the new data flow and behavior.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

This PR removes the getAll workspace query procedure and updates client-side hooks and cache logic to use getAllGrouped as the single source of truth for optimistic updates, invalidations, and workspace selection logic.

Changes

Cohort / File(s) Summary
Backend TRPC Workspace Queries
apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts, apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
Removed the getAll procedure and updated router documentation to list only get, getAllGrouped, and getActive.
Workspace Closure Mutation
apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
Removed previousAll from CloseContext; refactored optimistic update, rollback, and next-workspace selection to derive state from previousGrouped/getAllGrouped; removed getAll cancellations/invalidations.
Workspace Deletion Mutation
apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
Removed previousAll from DeleteContext; switched optimistic updates and next-active selection to use getAllGrouped; removed getAll snapshots and invalidations; adapted setData payload for new next-active shape.
Reorder & Activate Mutations
apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts, apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
Removed invalidation calls to utils.workspaces.getAll.invalidate() in onSuccess, keeping getAllGrouped (and getActive where applicable).
Workspace Data Consumption (UI)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
Replaced getAll with getAllGrouped, added useMemo to flatten grouped workspaces into a memoized flat array for downstream use.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant UI as Client Hook
  participant Cache as React Query Cache (getAllGrouped)
  participant TRPC as trpc.mutation -> Server
  participant Server

  User->>UI: trigger close/delete workspace
  UI->>Cache: optimistic update (update getAllGrouped)
  UI->>TRPC: send mutation request
  TRPC->>Server: apply change
  Server-->>TRPC: mutation result
  TRPC-->>UI: onSuccess callback
  UI->>Cache: invalidate getAllGrouped (and getActive if needed)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A hop, a tweak, a grouped bouquet,
The flat list folded softly away,
No getAll track to chase or brood,
We follow grouped paths — tidy and chewed,
Hooray for calmer cacheful days! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.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
Title check ✅ Passed The title clearly and concisely describes the main change: consolidating workspace queries to use getAllGrouped as the single source of truth, which directly addresses the root cause of the StartView flash issue.
Description check ✅ Passed The description includes a clear summary, root cause explanation, detailed frontend and backend changes, and a comprehensive test plan. It follows the repository's template structure and provides sufficient context for review.

✏️ 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)

61-72: Hardcoded worktree metadata may cause brief UI inconsistency.

The optimistic update uses placeholder values (baseBranch: null, needsRebase: false) that may not match the actual worktree state. While this is acceptable for a brief optimistic window before the cache is invalidated in onSuccess, be aware that UI components relying on these fields could display stale data momentarily.


48-98: Consider extracting duplicated next-workspace selection logic.

This block (~50 lines) is nearly identical to useDeleteWorkspace.ts. Extracting a shared helper would reduce duplication and ensure consistent behavior when selecting the next active workspace.

♻️ Suggested helper function

Create a shared utility, e.g., in a utils file:

// workspaceOptimisticHelpers.ts
export function selectNextWorkspace({
  previousGrouped,
  excludeId,
}: {
  previousGrouped: GroupedWorkspaces | undefined;
  excludeId: string;
}) {
  const remainingWorkspaces = previousGrouped
    ?.flatMap((g) =>
      g.workspaces
        .filter((w) => w.id !== excludeId)
        .map((w) => ({ workspace: w, project: g.project })),
    )
    .sort((a, b) => b.workspace.lastOpenedAt - a.workspace.lastOpenedAt);

  if (!remainingWorkspaces?.length) return null;

  const { workspace: nextWorkspace, project } = remainingWorkspaces[0];
  
  const worktreeData =
    nextWorkspace.type === "worktree"
      ? {
          branch: nextWorkspace.branch,
          baseBranch: null,
          gitStatus: {
            branch: nextWorkspace.branch,
            needsRebase: false,
            lastRefreshed: Date.now(),
          },
        }
      : null;

  return {
    id: nextWorkspace.id,
    projectId: nextWorkspace.projectId,
    worktreeId: nextWorkspace.worktreeId,
    branch: nextWorkspace.branch,
    name: nextWorkspace.name,
    tabOrder: nextWorkspace.tabOrder,
    createdAt: nextWorkspace.createdAt,
    updatedAt: nextWorkspace.updatedAt,
    lastOpenedAt: nextWorkspace.lastOpenedAt,
    isUnread: nextWorkspace.isUnread,
    type: nextWorkspace.type,
    worktreePath: nextWorkspace.worktreePath,
    deletingAt: null,
    project: {
      id: project.id,
      name: project.name,
      mainRepoPath: project.mainRepoPath,
    },
    worktree: worktreeData,
  };
}

Then both hooks can call:

const nextActiveData = selectNextWorkspace({ previousGrouped, excludeId: id });
utils.workspaces.getActive.setData(undefined, nextActiveData);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5197d41 and 12bb1dc.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts
  • apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
💤 Files with no reviewable changes (2)
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.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/useSetActiveWorkspace.ts
  • 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/workspaces.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/useSetActiveWorkspace.ts
  • 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/workspaces.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/useSetActiveWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/useSetActiveWorkspace.ts
  • 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/workspaces.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/useSetActiveWorkspace.ts
  • 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/workspaces.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/renderer/react-query/workspaces/useSetActiveWorkspace.ts (1)

34-38: LGTM!

The removal of getAll invalidation aligns with the PR goal of using getAllGrouped as the single source of truth. The parallel invalidation of getActive and getAllGrouped is appropriate.

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

102-118: LGTM!

Error handling correctly restores both previousGrouped and previousActive caches. The onSuccess invalidation strategy ensures data consistency after the mutation completes.

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

102-121: LGTM!

The onSettled pattern ensures cache invalidation happens regardless of success/error, which is appropriate for delete operations. Error handling correctly restores both caches.

apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts (1)

16-16: LGTM!

Documentation correctly reflects the removal of getAll from the query procedures.

apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts (1)

17-22: LGTM!

The useMemo derivation is efficient and correctly flattens grouped workspaces. The default empty array handles the undefined case appropriately, and the comment clearly explains the rationale for using getAllGrouped.

…ce queries

Root cause: The optimistic updates in useDeleteWorkspace and useCloseWorkspace
relied on `getAll` query cache to find the next workspace, but `getAll` was
rarely queried (only used by PortsList). When the cache was empty, the
optimistic update would incorrectly set activeWorkspace to null, causing
StartView to flash.

Additionally, `utils.workspaces.invalidate()` was invalidating ALL queries
including `getActive`, causing the optimistically-set data to be refetched
and briefly showing undefined during the refetch.

Changes:
- Use `getAllGrouped` as the single source of truth (always cached by sidebar)
- Remove all `getAll` query usage from frontend mutation hooks
- Update `usePortsData` to derive workspace list from `getAllGrouped`
- Remove unused `getAll` procedure from backend
- Only invalidate `getAllGrouped` after delete/close, not `getActive`
  (since we already set it optimistically and don't want a refetch flash)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Kitenite Kitenite force-pushed the fix/desktop-workspace-cache-single-source-of-truth branch from 12bb1dc to 31f0ac0 Compare January 12, 2026 07:08
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 (1)
apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts (1)

48-98: Consider extracting shared next-workspace logic.

The logic for computing the next active workspace (lines 50-94) is nearly identical to useDeleteWorkspace.ts. This includes the remainingWorkspaces derivation, worktreeData construction, and the full setData payload.

Consider extracting a shared helper function (e.g., buildNextActiveWorkspacePayload) to reduce duplication and ensure both hooks stay in sync.

♻️ Example shared helper
// In a shared utils file, e.g., renderer/react-query/workspaces/utils.ts

export function computeNextActiveWorkspace(
  groupedWorkspaces: GroupedWorkspaces | undefined,
  excludeId: string,
) {
  const remaining = groupedWorkspaces
    ?.flatMap((g) =>
      g.workspaces
        .filter((w) => w.id !== excludeId)
        .map((w) => ({ workspace: w, project: g.project })),
    )
    .sort((a, b) => b.workspace.lastOpenedAt - a.workspace.lastOpenedAt);

  if (!remaining || remaining.length === 0) return null;

  const { workspace, project } = remaining[0];
  const worktreeData =
    workspace.type === "worktree"
      ? {
          branch: workspace.branch,
          baseBranch: null,
          gitStatus: {
            branch: workspace.branch,
            needsRebase: false,
            lastRefreshed: Date.now(),
          },
        }
      : null;

  return {
    id: workspace.id,
    projectId: workspace.projectId,
    // ... rest of payload
    worktree: worktreeData,
  };
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12bb1dc and 31f0ac0.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts
  • apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
💤 Files with no reviewable changes (2)
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.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/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.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 (6)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts (1)

17-22: LGTM! Clean derivation from grouped data.

The change correctly uses getAllGrouped as the source of truth and derives a flattened workspace list via useMemo. The empty array fallback handles the undefined case gracefully, and the dependency array is correct.

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

27-46: LGTM! Proper optimistic update pattern.

The cancellation of in-flight queries before snapshotting, followed by optimistic cache updates, follows the correct react-query mutation pattern. Filtering out the closed workspace from grouped data and removing empty groups is handled correctly.


113-120: LGTM! Intentional invalidation strategy to prevent flash.

The comment clearly explains the rationale for not invalidating getActive. The optimistic update provides the UI state immediately, and only getAllGrouped needs server reconciliation.

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

27-46: LGTM! Consistent optimistic update pattern.

The cancellation and optimistic update logic correctly mirrors useCloseWorkspace, maintaining consistency across the codebase for workspace mutations.


102-107: Verify: Inconsistent invalidation timing between hooks.

useDeleteWorkspace invalidates in onSettled (runs on both success and error), while useCloseWorkspace invalidates in onSuccess (runs only on success).

If this is intentional (e.g., delete operations need server reconciliation even on failure), consider adding a comment explaining the difference. Otherwise, consider aligning both hooks to use onSuccess for consistency, since the rollback in onError already restores the cache state.


111-123: LGTM! Proper rollback on error.

The error handler correctly restores both previousGrouped and previousActive caches before forwarding to the caller's error handler.

@Kitenite Kitenite closed this Jan 12, 2026
@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! 🎉

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