authorgravatar for 206951365+cursor[bot]@users.noreply.github.comcursor[bot] <206951365+cursor[bot]@users.noreply.github.com> 2026-05-07 21:44:30-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-07 21:44:30-07:00
log7455472c347de3255a8f9f1306b8c4cd96eac5d4
tree43726d09e33e9b91f7ad80e76588c9597d8e6d37
parent9d919d0bb8d6a2d561464201713117e2d0c588ad
signature Signed by PGP key B5690EEEBB952194

Reduce sidebar selection rerenders (#2560)

Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>

1 files changed, 5 insertions(+), 15 deletions(-)

apps/web/src/components/Sidebar.tsx+5-15
...@@ -341,7 +341,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -341,7 +341,6 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
341 const threadKey = scopedThreadKey(threadRef);341 const threadKey = scopedThreadKey(threadRef);
342 const lastVisitedAt = useUiStateStore((state) => state.threadLastVisitedAtById[threadKey]);342 const lastVisitedAt = useUiStateStore((state) => state.threadLastVisitedAtById[threadKey]);
343 const isSelected = useThreadSelectionStore((state) => state.selectedThreadKeys.has(threadKey));343 const isSelected = useThreadSelectionStore((state) => state.selectedThreadKeys.has(threadKey));
344 const hasSelection = useThreadSelectionStore((state) => state.selectedThreadKeys.size > 0);
345 const runningTerminalIds = useTerminalStateStore(344 const runningTerminalIds = useTerminalStateStore(
346 (state) =>345 (state) =>
347 selectThreadTerminalState(state.terminalStateByThreadKey, threadRef).runningTerminalIds,346 selectThreadTerminalState(state.terminalStateByThreadKey, threadRef).runningTerminalIds,
...@@ -427,6 +426,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -427,6 +426,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
427 const handleRowContextMenu = useCallback(426 const handleRowContextMenu = useCallback(
428 (event: React.MouseEvent) => {427 (event: React.MouseEvent) => {
429 event.preventDefault();428 event.preventDefault();
429 const hasSelection = useThreadSelectionStore.getState().hasSelection();
430 if (hasSelection && isSelected) {430 if (hasSelection && isSelected) {
431 void handleMultiSelectContextMenu({431 void handleMultiSelectContextMenu({
432 x: event.clientX,432 x: event.clientX,
...@@ -443,14 +443,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -443,14 +443,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
443 y: event.clientY,443 y: event.clientY,
444 });444 });
445 },445 },
446 [446 [clearSelection, handleMultiSelectContextMenu, handleThreadContextMenu, isSelected, threadRef],
447 clearSelection,
448 handleMultiSelectContextMenu,
449 handleThreadContextMenu,
450 hasSelection,
451 isSelected,
452 threadRef,
453 ],
454 );447 );
455 const handlePrClick = useCallback(448 const handlePrClick = useCallback(
456 (event: React.MouseEvent<HTMLButtonElement>) => {449 (event: React.MouseEvent<HTMLButtonElement>) => {
...@@ -964,7 +957,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -964,7 +957,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
964 const clearSelection = useThreadSelectionStore((state) => state.clearSelection);957 const clearSelection = useThreadSelectionStore((state) => state.clearSelection);
965 const removeFromSelection = useThreadSelectionStore((state) => state.removeFromSelection);958 const removeFromSelection = useThreadSelectionStore((state) => state.removeFromSelection);
966 const setSelectionAnchor = useThreadSelectionStore((state) => state.setAnchor);959 const setSelectionAnchor = useThreadSelectionStore((state) => state.setAnchor);
967 const selectedThreadCount = useThreadSelectionStore((state) => state.selectedThreadKeys.size);
968 const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{960 const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{
969 threadId: ThreadId;961 threadId: ThreadId;
970 }>({962 }>({
...@@ -1235,7 +1227,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -1235,7 +1227,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
1235 event.stopPropagation();1227 event.stopPropagation();
1236 return;1228 return;
1237 }1229 }
1238 if (selectedThreadCount > 0) {1230 if (useThreadSelectionStore.getState().hasSelection()) {
1239 clearSelection();1231 clearSelection();
1240 }1232 }
1241 toggleProject(project.projectKey);1233 toggleProject(project.projectKey);
...@@ -1244,7 +1236,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec...@@ -1244,7 +1236,6 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
1244 clearSelection,1236 clearSelection,
1245 dragInProgressRef,1237 dragInProgressRef,
1246 project.projectKey,1238 project.projectKey,
1247 selectedThreadCount,
1248 suppressProjectClickAfterDragRef,1239 suppressProjectClickAfterDragRef,
1249 suppressProjectClickForContextMenuRef,1240 suppressProjectClickForContextMenuRef,
1250 toggleProject,1241 toggleProject,
...@@ -2833,7 +2824,6 @@ export default function Sidebar() {...@@ -2833,7 +2824,6 @@ export default function Sidebar() {
2833 const suppressProjectClickAfterDragRef = useRef(false);2824 const suppressProjectClickAfterDragRef = useRef(false);
2834 const suppressProjectClickForContextMenuRef = useRef(false);2825 const suppressProjectClickForContextMenuRef = useRef(false);
2835 const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null);2826 const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null);
2836 const selectedThreadCount = useThreadSelectionStore((s) => s.selectedThreadKeys.size);
2837 const clearSelection = useThreadSelectionStore((s) => s.clearSelection);2827 const clearSelection = useThreadSelectionStore((s) => s.clearSelection);
2838 const setSelectionAnchor = useThreadSelectionStore((s) => s.setAnchor);2828 const setSelectionAnchor = useThreadSelectionStore((s) => s.setAnchor);
2839 const platform = navigator.platform;2829 const platform = navigator.platform;
...@@ -3279,7 +3269,7 @@ export default function Sidebar() {...@@ -3279,7 +3269,7 @@ export default function Sidebar() {
32793269
3280 useEffect(() => {3270 useEffect(() => {
3281 const onMouseDown = (event: globalThis.MouseEvent) => {3271 const onMouseDown = (event: globalThis.MouseEvent) => {
3282 if (selectedThreadCount === 0) return;3272 if (!useThreadSelectionStore.getState().hasSelection()) return;
3283 const target = event.target instanceof HTMLElement ? event.target : null;3273 const target = event.target instanceof HTMLElement ? event.target : null;
3284 if (!shouldClearThreadSelectionOnMouseDown(target)) return;3274 if (!shouldClearThreadSelectionOnMouseDown(target)) return;
3285 clearSelection();3275 clearSelection();
...@@ -3289,7 +3279,7 @@ export default function Sidebar() {...@@ -3289,7 +3279,7 @@ export default function Sidebar() {
3289 return () => {3279 return () => {
3290 window.removeEventListener("mousedown", onMouseDown);3280 window.removeEventListener("mousedown", onMouseDown);
3291 };3281 };
3292 }, [clearSelection, selectedThreadCount]);3282 }, [clearSelection]);
32933283
3294 useEffect(() => {3284 useEffect(() => {
3295 if (!isElectron) return;3285 if (!isElectron) return;