Skip to content

[codex] fix sidebar removal and rerender churn#3741

Merged
Kitenite merged 1 commit intomainfrom
fix-sidebar-remote-remove
Apr 26, 2026
Merged

[codex] fix sidebar removal and rerender churn#3741
Kitenite merged 1 commit intomainfrom
fix-sidebar-remote-remove

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 25, 2026

Summary

Fixes the sidebar removal race where a still-mounted v2 workspace could reinsert itself after Remove from Sidebar, especially for remote workspaces. The provider now keeps the collections context stable across unrelated renders, and pane-layout persistence no longer calls ensureWorkspaceInSidebar before checking that the local sidebar row still exists.

Also adds low-risk sidebar re-render hardening without splitting v2WorkspaceLocalState: stable local workspace id arrays, stable pull-request maps for structurally equal refetches, stable project references for unchanged project trees, stable shortcut-label maps, and a memo boundary for sortable project rows.

The implementation notes were moved into apps/desktop/plans/done/20260425-1430-sidebar-remove-and-rerender.md.

Validation

  • bunx @biomejs/biome@2.4.2 check <changed files>
  • bun run --cwd apps/desktop typecheck
  • Manual removal checks completed by the requester: current remote workspace, current local workspace, and opening a v2 workspace from the all-workspaces list.

Summary by cubic

Fixes the “Remove from Sidebar” race that caused remote workspaces to reappear and reduces sidebar re-render churn for smoother performance.

  • Bug Fixes

    • Stabilized CollectionsProvider by memoizing getCollections(activeOrganizationId) and the context value, preventing stale workspace effects from re-running.
    • Updated useV2WorkspacePaneLayout to stop auto-ensuring sidebar rows during pane-store updates; now persists layout only if the local row still exists.
  • Refactors

    • Kept local workspace ID arrays stable when IDs are unchanged.
    • Reused the pull-request Map when refetched data is structurally equal.
    • Preserved unchanged project references and added a memo boundary for sortable project rows.
    • Reused the workspace shortcut-label Map when the first nine workspace IDs are unchanged.

Written for commit 3e40656. Summary will update on new commits.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Warning

Rate limit exceeded

@Kitenite has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 40 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 40 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 655f1274-9f63-4d1f-9214-222a13612b91

📥 Commits

Reviewing files that changed from the base of the PR and between 6a3be2d and 3e40656.

📒 Files selected for processing (6)
  • apps/desktop/plans/done/20260425-1430-sidebar-remove-and-rerender.md
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspacePaneLayout/useV2WorkspacePaneLayout.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-sidebar-remote-remove

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 6 files

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

This PR fixes a race condition where a still-mounted v2 workspace component could re-insert itself into the sidebar immediately after being removed — most visibly for remote workspaces. Two targeted patches address the problem: CollectionsProvider now memoizes the getCollections() result by activeOrganizationId so the context value is stable across unrelated parent renders, and useV2WorkspacePaneLayout no longer calls ensureWorkspaceInSidebar inside the pane-store subscription (which ran before the local-row existence check). A second pass adds low-risk render-churn hardening: stable string-array, PR-map, and project-reference identities in useDashboardSidebarData, a stable shortcut-label map in useDashboardSidebarShortcuts, and a React.memo boundary on SortableProjectWrapper.

Confidence Score: 5/5

Safe to merge; both re-add paths are closed and all remaining feedback is P2 style notes.

The two targeted fixes (provider memoisation + removal of ensureWorkspaceInSidebar from the store subscription) are minimal, well-reasoned, and manually verified. The Part 2 hardening is additive and low-risk. Only P2 findings remain: the JSON.stringify fingerprinting edge cases are unlikely to cause visible bugs with the current plain-DTO types and are noted for future improvement.

useDashboardSidebarData.ts — review JSON.stringify fingerprinting edge cases if DashboardSidebarProject or pullRequest types gain undefined-valued or non-serializable fields.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx Memoizes getCollections() by activeOrganizationId and wraps contextValue in useMemo — stabilises the context object and fixes the primary re-add trigger for the sidebar removal race.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useV2WorkspacePaneLayout/useV2WorkspacePaneLayout.ts Removes ensureWorkspaceInSidebar from the store-subscription callback and trims projectId/ensureWorkspaceInSidebar from the effect deps — eliminates the second re-add path after sidebar removal.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts Adds three stable-identity hooks (string array, PR map, project array) using useRef + JSON.stringify fingerprinting; JSON.stringify cost is amortised over 10 s poll intervals but could silently drop undefined-valued PR fields from equality checks.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts Adds useStableWorkspaceShortcutLabels to preserve Map identity when the first nine workspace IDs are unchanged, reducing downstream memo invalidation.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx Wraps SortableProjectWrapper in React.memo; props interface extracted for clarity. Low-risk render optimisation.
apps/desktop/plans/done/20260425-1430-sidebar-remove-and-rerender.md Implementation plan/notes — documentation only, no executable changes.

Sequence Diagram

sequenceDiagram
    participant User as User (right-click)
    participant SidebarState as useDashboardSidebarState
    participant Collections as CollectionsProvider (memoized)
    participant PaneLayout as useV2WorkspacePaneLayout
    participant PaneStore as Pane Store

    User->>SidebarState: removeWorkspaceFromSidebar(id)
    SidebarState->>Collections: v2WorkspaceLocalState.delete(id)
    Note over Collections: Row removed ✓

    Note over PaneStore: Store emits (workspace still mounted)
    PaneStore->>PaneLayout: subscription callback fires
    PaneLayout->>Collections: get(workspaceId) → undefined (FIXED: no ensureWorkspaceInSidebar call)
    PaneLayout-->>PaneLayout: return early — row not re-inserted ✓

    Note over Collections: Parent re-renders (unrelated cause)
    Collections-->>Collections: contextValue stable (useMemo on activeOrganizationId) ✓
    Note over PaneLayout: ensureWorkspaceInSidebar dep unchanged → mount effect does NOT re-run ✓
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
Line: 56-60

Comment:
**`JSON.stringify` fingerprinting silently drops `undefined` fields**

`JSON.stringify` omits object keys whose values are `undefined`, so a `pullRequest` or project field transitioning between `undefined` and a missing key would produce identical fingerprints, masking a real change. The same applies to `getDashboardSidebarProjectFingerprint`. For pure data DTOs this is unlikely to matter in practice, but a shallow recursive equality helper (or a library like `fast-deep-equal`) would be more explicit and wouldn't silently coerce `undefined` → absent.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
Line: 56-66

Comment:
**`getPullRequestRowsFingerprint` sorts before stringifying, but `getDashboardSidebarProjectFingerprint` does not**

`getPullRequestRowsFingerprint` sorts rows by `workspaceId` to get a stable fingerprint regardless of API response order. `getDashboardSidebarProjectFingerprint` calls `JSON.stringify(project)` directly, which depends on property-insertion order. If any nested array inside a project is reordered by the live query (e.g. workspaces within a section), two semantically equal projections can produce different fingerprints, causing unnecessary reference churn instead of the intended stability.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix sidebar removal and rerender churn" | Re-trigger Greptile

Comment on lines +56 to +60
}

function getDashboardSidebarProjectFingerprint(
project: DashboardSidebarProject,
): string {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 JSON.stringify fingerprinting silently drops undefined fields

JSON.stringify omits object keys whose values are undefined, so a pullRequest or project field transitioning between undefined and a missing key would produce identical fingerprints, masking a real change. The same applies to getDashboardSidebarProjectFingerprint. For pure data DTOs this is unlikely to matter in practice, but a shallow recursive equality helper (or a library like fast-deep-equal) would be more explicit and wouldn't silently coerce undefined → absent.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
Line: 56-60

Comment:
**`JSON.stringify` fingerprinting silently drops `undefined` fields**

`JSON.stringify` omits object keys whose values are `undefined`, so a `pullRequest` or project field transitioning between `undefined` and a missing key would produce identical fingerprints, masking a real change. The same applies to `getDashboardSidebarProjectFingerprint`. For pure data DTOs this is unlikely to matter in practice, but a shallow recursive equality helper (or a library like `fast-deep-equal`) would be more explicit and wouldn't silently coerce `undefined` → absent.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +56 to +66
}

function getDashboardSidebarProjectFingerprint(
project: DashboardSidebarProject,
): string {
return JSON.stringify(project);
}

function useStableStringArray(values: string[]): string[] {
const previousRef = useRef<string[] | null>(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.

P2 getPullRequestRowsFingerprint sorts before stringifying, but getDashboardSidebarProjectFingerprint does not

getPullRequestRowsFingerprint sorts rows by workspaceId to get a stable fingerprint regardless of API response order. getDashboardSidebarProjectFingerprint calls JSON.stringify(project) directly, which depends on property-insertion order. If any nested array inside a project is reordered by the live query (e.g. workspaces within a section), two semantically equal projections can produce different fingerprints, causing unnecessary reference churn instead of the intended stability.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
Line: 56-66

Comment:
**`getPullRequestRowsFingerprint` sorts before stringifying, but `getDashboardSidebarProjectFingerprint` does not**

`getPullRequestRowsFingerprint` sorts rows by `workspaceId` to get a stable fingerprint regardless of API response order. `getDashboardSidebarProjectFingerprint` calls `JSON.stringify(project)` directly, which depends on property-insertion order. If any nested array inside a project is reordered by the live query (e.g. workspaces within a section), two semantically equal projections can produce different fingerprints, causing unnecessary reference churn instead of the intended stability.

How can I resolve this? If you propose a fix, please make it concise.

@Kitenite Kitenite merged commit 8182396 into main Apr 26, 2026
7 checks passed
@Kitenite Kitenite deleted the fix-sidebar-remote-remove branch April 26, 2026 18:57
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

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