Skip to content

Commit 261feaa

Browse files
committed
🤖 fix: reduce ProjectSidebar re-renders during streaming
ProjectSidebar was re-rendering on every streaming delta and Git status poll, causing unnecessary UI churn when idling looking at a streaming chat. Changes: - Stabilize unreadStatus Map identity in useUnreadTracking - Only return new Map reference when values actually change - Prevents ProjectSidebar re-renders when unread state hasn't changed - Keep Git status interval at 3s for interactive updates - Reverted temporary increase to 10s to maintain snappy UI feel - Git status changes don't cause full sidebar re-renders due to WorkspaceGitStatusIndicator reading from context directly Performance impact: - ProjectSidebar only re-renders when inputs that affect rendering change - Streaming in non-selected workspace no longer causes sidebar churn - Git status updates remain fast (3s) without dragging parent tree _Generated with `cmux`_
1 parent 539d428 commit 261feaa

File tree

2 files changed

+22
-5
lines changed

2 files changed

+22
-5
lines changed

src/contexts/GitStatusContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ interface GitStatusProviderProps {
3131
* - Max 5 concurrent git status checks to prevent bash process explosion
3232
*/
3333
// Configuration - enabled by default, no env variables needed
34-
const GIT_STATUS_INTERVAL_MS = 3000; // 3 seconds
34+
const GIT_STATUS_INTERVAL_MS = 3000; // 3 seconds - interactive updates
3535
const MAX_CONCURRENT_GIT_OPS = 5;
3636

3737
// Fetch configuration - aggressive intervals for fresh data

src/hooks/useUnreadTracking.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,14 @@ export function useUnreadTracking(
7373
}, [selectedWorkspace?.workspaceId, workspaceStates, markAsRead]);
7474

7575
// Calculate unread status for all workspaces
76+
const unreadStatusRef = useRef<Map<string, boolean>>(new Map());
7677
const unreadStatus = useMemo(() => {
77-
const result = new Map<string, boolean>();
78+
const next = new Map<string, boolean>();
7879

7980
for (const [workspaceId, state] of workspaceStates) {
8081
// Streaming workspaces are never unread
8182
if (state.canInterrupt) {
82-
result.set(workspaceId, false);
83+
next.set(workspaceId, false);
8384
continue;
8485
}
8586

@@ -92,10 +93,26 @@ export function useUnreadTracking(
9293
msg.type !== "user" && msg.type !== "history-hidden" && (msg.timestamp ?? 0) > lastRead
9394
);
9495

95-
result.set(workspaceId, hasUnread);
96+
next.set(workspaceId, hasUnread);
9697
}
9798

98-
return result;
99+
// Return previous Map reference if nothing actually changed to keep identity stable
100+
const prev = unreadStatusRef.current;
101+
if (prev.size === next.size) {
102+
let same = true;
103+
for (const [k, v] of next) {
104+
if (prev.get(k) !== v) {
105+
same = false;
106+
break;
107+
}
108+
}
109+
if (same) {
110+
return prev;
111+
}
112+
}
113+
114+
unreadStatusRef.current = next;
115+
return next;
99116
}, [workspaceStates, lastReadMap]);
100117

101118
// Manual toggle function for clicking the indicator

0 commit comments

Comments
 (0)