Gate resource popover store slices
Avoids subscribing the closed resource status segment to popover-only high-churn store maps.
This commit is contained in:
parent
98822fdf7e
commit
a97191d4aa
|
|
@ -32,7 +32,7 @@ import { cn } from '@/lib/utils'
|
|||
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
|
||||
import { activateTabAndFocusPane } from '@/lib/activate-tab-and-focus-pane'
|
||||
import { useAppStore } from '../../store'
|
||||
import { useAllWorktrees, useWorktreeMap } from '../../store/selectors'
|
||||
import { useWorktreeMap } from '../../store/selectors'
|
||||
import { runWorktreeDelete } from '../sidebar/delete-worktree-flow'
|
||||
import { runSleepWorktree } from '../sidebar/sleep-worktree-flow'
|
||||
import { useDaemonActions, DaemonActionDialog } from '../shared/useDaemonActions'
|
||||
|
|
@ -55,6 +55,12 @@ import {
|
|||
isResourceSessionActivationKey,
|
||||
navigateResourceSessionToTab
|
||||
} from './resource-session-navigation'
|
||||
import {
|
||||
getResourceUsageAllWorktrees,
|
||||
getResourceUsageRepos,
|
||||
getResourceUsageRuntimePaneTitlesByTabId,
|
||||
getResourceUsageTabsByWorktree
|
||||
} from './resource-usage-open-slices'
|
||||
|
||||
const POLL_MS = 2_000
|
||||
const SESSIONS_POLL_MS = 10_000
|
||||
|
|
@ -659,16 +665,12 @@ export function ResourceUsageStatusSegment({
|
|||
const fetchSnapshot = useAppStore((s) => s.fetchMemorySnapshot)
|
||||
const workspaceSessionReady = useAppStore((s) => s.workspaceSessionReady)
|
||||
const ptyIdsByTabId = useAppStore((s) => s.ptyIdsByTabId)
|
||||
const tabsByWorktree = useAppStore((s) => s.tabsByWorktree)
|
||||
const runtimePaneTitlesByTabId = useAppStore((s) => s.runtimePaneTitlesByTabId)
|
||||
const setActiveView = useAppStore((s) => s.setActiveView)
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const openSpacePage = useAppStore((s) => s.openSpacePage)
|
||||
const activeView = useAppStore((s) => s.activeView)
|
||||
const workspaceSpaceScannedAt = useAppStore((s) => s.workspaceSpaceAnalysis?.scannedAt ?? null)
|
||||
const workspaceSpaceScanning = useAppStore((s) => s.workspaceSpaceScanning)
|
||||
const repos = useAppStore((s) => s.repos)
|
||||
const allWorktrees = useAllWorktrees()
|
||||
const activeRuntimeEnvironmentId = useAppStore(
|
||||
(s) => s.settings?.activeRuntimeEnvironmentId ?? null
|
||||
)
|
||||
|
|
@ -684,6 +686,19 @@ export function ResourceUsageStatusSegment({
|
|||
const [killConfirm, setKillConfirm] = useState<UnifiedSessionRow | null>(null)
|
||||
const [killing, setKilling] = useState(false)
|
||||
const [spaceScanReady, setSpaceScanReady] = useState(false)
|
||||
// Why: tab titles can update on terminal keystrokes. The resource popover's
|
||||
// merged tree needs them only while open, so closed status-bar badges should
|
||||
// not subscribe to those high-churn maps.
|
||||
const runtimePaneTitlesByTabId = useAppStore((s) =>
|
||||
getResourceUsageRuntimePaneTitlesByTabId(s, open, runtimeEnvironmentActive)
|
||||
)
|
||||
const repos = useAppStore((s) => getResourceUsageRepos(s, open, runtimeEnvironmentActive))
|
||||
const allWorktrees = useAppStore((s) =>
|
||||
getResourceUsageAllWorktrees(s, open, runtimeEnvironmentActive)
|
||||
)
|
||||
const tabsByWorktree = useAppStore((s) =>
|
||||
getResourceUsageTabsByWorktree(s, open, runtimeEnvironmentActive)
|
||||
)
|
||||
const previousSpaceScanningRef = useRef(workspaceSpaceScanning)
|
||||
const lastSeenSpaceScanAtRef = useRef<number | null>(workspaceSpaceScannedAt)
|
||||
// Why: this segment only understands the local Electron PTY/resource daemon.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
getResourceUsageAllWorktrees,
|
||||
getResourceUsageRepos,
|
||||
getResourceUsageRuntimePaneTitlesByTabId,
|
||||
getResourceUsageTabsByWorktree
|
||||
} from './resource-usage-open-slices'
|
||||
import type { AppState } from '../../store'
|
||||
|
||||
const terminalTab = (id: string): AppState['tabsByWorktree'][string][number] => ({
|
||||
id,
|
||||
ptyId: null,
|
||||
worktreeId: 'wt-1',
|
||||
title: id,
|
||||
customTitle: null,
|
||||
color: null,
|
||||
sortOrder: 0,
|
||||
createdAt: 0
|
||||
})
|
||||
|
||||
const worktree = (): AppState['worktreesByRepo'][string][number] => ({
|
||||
id: 'wt-1',
|
||||
repoId: 'repo-1',
|
||||
path: '/repo/wt-1',
|
||||
displayName: 'wt-1',
|
||||
comment: '',
|
||||
branch: 'main',
|
||||
head: 'abc123',
|
||||
isBare: false,
|
||||
isMainWorktree: false,
|
||||
linkedIssue: null,
|
||||
linkedPR: null,
|
||||
linkedLinearIssue: null,
|
||||
isArchived: false,
|
||||
isUnread: false,
|
||||
isPinned: false,
|
||||
sortOrder: 0,
|
||||
lastActivityAt: 0
|
||||
})
|
||||
|
||||
describe('resource usage open slices', () => {
|
||||
it('returns stable empty slices while the popover is closed', () => {
|
||||
const tabsByWorktree = { 'wt-1': [terminalTab('tab-1')] }
|
||||
const runtimePaneTitlesByTabId = {
|
||||
'tab-1': { 'tab-1:0': 'Working' }
|
||||
} as AppState['runtimePaneTitlesByTabId']
|
||||
|
||||
const closedTabs = getResourceUsageTabsByWorktree({ tabsByWorktree }, false)
|
||||
const closedTitles = getResourceUsageRuntimePaneTitlesByTabId(
|
||||
{ runtimePaneTitlesByTabId },
|
||||
false
|
||||
)
|
||||
|
||||
expect(closedTabs).toBe(getResourceUsageTabsByWorktree({ tabsByWorktree: {} }, false))
|
||||
expect(closedTitles).toBe(
|
||||
getResourceUsageRuntimePaneTitlesByTabId({ runtimePaneTitlesByTabId: {} }, false)
|
||||
)
|
||||
expect(closedTabs).toEqual({})
|
||||
expect(closedTitles).toEqual({})
|
||||
})
|
||||
|
||||
it('returns live slices while the popover is open', () => {
|
||||
const tabsByWorktree = { 'wt-1': [terminalTab('tab-1')] }
|
||||
const runtimePaneTitlesByTabId = {
|
||||
'tab-1': { 'tab-1:0': 'Working' }
|
||||
} as AppState['runtimePaneTitlesByTabId']
|
||||
|
||||
expect(getResourceUsageTabsByWorktree({ tabsByWorktree }, true)).toBe(tabsByWorktree)
|
||||
expect(getResourceUsageRuntimePaneTitlesByTabId({ runtimePaneTitlesByTabId }, true)).toBe(
|
||||
runtimePaneTitlesByTabId
|
||||
)
|
||||
})
|
||||
|
||||
it('gates repo and worktree slices while closed or runtime-backed', () => {
|
||||
const repos = [{ id: 'repo-1', path: '/repo', kind: 'git' }] as AppState['repos']
|
||||
const row = worktree()
|
||||
const worktreesByRepo = {
|
||||
'repo-1': [row]
|
||||
}
|
||||
|
||||
expect(getResourceUsageRepos({ repos }, false, false)).toBe(
|
||||
getResourceUsageRepos({ repos: [] }, false, false)
|
||||
)
|
||||
expect(getResourceUsageAllWorktrees({ worktreesByRepo }, false, false)).toBe(
|
||||
getResourceUsageAllWorktrees({ worktreesByRepo: {} }, false, false)
|
||||
)
|
||||
expect(getResourceUsageRepos({ repos }, true, true)).toEqual([])
|
||||
expect(getResourceUsageAllWorktrees({ worktreesByRepo }, true, true)).toEqual([])
|
||||
expect(getResourceUsageRepos({ repos }, true, false)).toBe(repos)
|
||||
expect(getResourceUsageAllWorktrees({ worktreesByRepo }, true, false)).toEqual([row])
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import type { AppState } from '../../store'
|
||||
import { getAllWorktreesFromState } from '../../store/selectors'
|
||||
|
||||
const EMPTY_TABS_BY_WORKTREE: AppState['tabsByWorktree'] = {}
|
||||
const EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID: AppState['runtimePaneTitlesByTabId'] = {}
|
||||
const EMPTY_REPOS: AppState['repos'] = []
|
||||
const EMPTY_WORKTREES: ReturnType<typeof getAllWorktreesFromState> = []
|
||||
|
||||
function shouldReadPopoverSlices(open: boolean, runtimeEnvironmentActive: boolean): boolean {
|
||||
return open && !runtimeEnvironmentActive
|
||||
}
|
||||
|
||||
export function getResourceUsageTabsByWorktree(
|
||||
state: Pick<AppState, 'tabsByWorktree'>,
|
||||
open: boolean,
|
||||
runtimeEnvironmentActive = false
|
||||
): AppState['tabsByWorktree'] {
|
||||
return shouldReadPopoverSlices(open, runtimeEnvironmentActive)
|
||||
? state.tabsByWorktree
|
||||
: EMPTY_TABS_BY_WORKTREE
|
||||
}
|
||||
|
||||
export function getResourceUsageRuntimePaneTitlesByTabId(
|
||||
state: Pick<AppState, 'runtimePaneTitlesByTabId'>,
|
||||
open: boolean,
|
||||
runtimeEnvironmentActive = false
|
||||
): AppState['runtimePaneTitlesByTabId'] {
|
||||
return shouldReadPopoverSlices(open, runtimeEnvironmentActive)
|
||||
? state.runtimePaneTitlesByTabId
|
||||
: EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID
|
||||
}
|
||||
|
||||
export function getResourceUsageRepos(
|
||||
state: Pick<AppState, 'repos'>,
|
||||
open: boolean,
|
||||
runtimeEnvironmentActive: boolean
|
||||
): AppState['repos'] {
|
||||
return shouldReadPopoverSlices(open, runtimeEnvironmentActive) ? state.repos : EMPTY_REPOS
|
||||
}
|
||||
|
||||
export function getResourceUsageAllWorktrees(
|
||||
state: Pick<AppState, 'worktreesByRepo'>,
|
||||
open: boolean,
|
||||
runtimeEnvironmentActive: boolean
|
||||
): ReturnType<typeof getAllWorktreesFromState> {
|
||||
return shouldReadPopoverSlices(open, runtimeEnvironmentActive)
|
||||
? getAllWorktreesFromState(state)
|
||||
: EMPTY_WORKTREES
|
||||
}
|
||||
Loading…
Reference in New Issue