Avoid no-op worktree activation fanout (#4416)

This commit is contained in:
Neil 2026-06-01 15:56:01 -07:00 committed by GitHub
parent 6f336acff9
commit 36384f17f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 1 deletions

View File

@ -129,3 +129,16 @@ The follow-up extends the existing tab-registration wait from page-specific
creation to worktree/global wake flows. Runtime commands now wait for the
renderer's actual `browser:registerGuest` IPC before routing automation, with
the same timeout fallback used by tab creation.
## Follow-up: Worktree Activation No-Op Fanout
The next renderer-store check found that repeated activation of an already
active, already-reconciled worktree could still publish a new Zustand root state
because `setActiveWorktree` rebuilt `activeTabTypeByWorktree` even when its
stored value was unchanged. That woke every store subscriber, including session
persistence and runtime graph sync, for a visible no-op.
The follow-up preserves the existing state reference when all derived active
fields, unread state, and first-activation bookkeeping are unchanged. A
regression test subscribes to the store and asserts that reselecting the
already-active reconciled worktree does not notify subscribers.

View File

@ -761,6 +761,62 @@ describe('setActiveWorktree', () => {
expect(store.getState().rightSidebarTab).toBe('checks')
})
it('does not notify subscribers when reselecting the already-active reconciled worktree', () => {
const store = createTestStore()
const wt = 'repo1::/path/wt1'
const tabId = 'terminal-1'
const groupId = 'group-1'
seedStore(store, {
worktreesByRepo: {
repo1: [makeWorktree({ id: wt, repoId: 'repo1', path: '/path/wt1' })]
},
activeWorktreeId: wt,
activeTabId: tabId,
activeTabType: 'terminal',
activeTabTypeByWorktree: { [wt]: 'terminal' },
tabsByWorktree: {
[wt]: [makeTab({ id: tabId, worktreeId: wt, ptyId: 'pty-1' })]
},
ptyIdsByTabId: { [tabId]: ['pty-1'] },
unifiedTabsByWorktree: {
[wt]: [
makeUnifiedTab({
id: tabId,
entityId: tabId,
worktreeId: wt,
groupId,
contentType: 'terminal'
})
]
},
groupsByWorktree: {
[wt]: [
makeTabGroup({
id: groupId,
worktreeId: wt,
activeTabId: tabId,
tabOrder: [tabId]
})
]
},
activeGroupIdByWorktree: { [wt]: groupId },
everActivatedWorktreeIds: new Set([wt]),
refreshGitHubForWorktree: vi.fn(),
refreshGitHubForWorktreeIfStale: vi.fn()
})
const before = store.getState()
const listener = vi.fn()
const unsubscribe = store.subscribe(listener)
store.getState().setActiveWorktree(wt)
unsubscribe()
expect(listener).not.toHaveBeenCalled()
expect(store.getState()).toBe(before)
})
it('does not clobber the current right sidebar tab when clearing the active worktree', () => {
const store = createTestStore()

View File

@ -2145,12 +2145,33 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
? applyDetectedWorktreeUpdates(s.detectedWorktreesByRepo, worktreeId, metaUpdates)
: s.detectedWorktreesByRepo
const nextActiveTabTypeByWorktree =
s.activeTabTypeByWorktree[worktreeId] === activeTabType
? s.activeTabTypeByWorktree
: { ...s.activeTabTypeByWorktree, [worktreeId]: activeTabType }
const hasStateChange =
s.activeWorktreeId !== worktreeId ||
s.activeFileId !== activeFileId ||
s.activeBrowserTabId !== activeBrowserTabId ||
s.activeTabType !== activeTabType ||
s.activeTabId !== activeTabId ||
nextActiveTabTypeByWorktree !== s.activeTabTypeByWorktree ||
nextEverActivated !== s.everActivatedWorktreeIds ||
nextWorktrees !== s.worktreesByRepo ||
nextDetectedWorktrees !== s.detectedWorktreesByRepo
if (!hasStateChange) {
// Why: repeated activation of the already-active worktree can come from
// clicks, IPC, and automation restore paths. Preserve the root Zustand
// reference so session persistence/runtime sync do not fan out on a no-op.
return s
}
return {
activeWorktreeId: worktreeId,
activeFileId,
activeBrowserTabId,
activeTabType,
activeTabTypeByWorktree: { ...s.activeTabTypeByWorktree, [worktreeId]: activeTabType },
activeTabTypeByWorktree: nextActiveTabTypeByWorktree,
activeTabId,
everActivatedWorktreeIds: nextEverActivated,
...(nextWorktrees !== s.worktreesByRepo ? { worktreesByRepo: nextWorktrees } : {}),