perf(dashboard-popout): park the kanban clock when hidden or timestamp-free (#9881)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil 2026-07-22 03:32:30 -07:00 committed by GitHub
parent 95ae346b87
commit 39b124c75b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 60 additions and 4 deletions

View File

@ -2,7 +2,7 @@
import '@testing-library/jest-dom/vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react'
import { act, cleanup, fireEvent, render, screen, within } from '@testing-library/react'
import type { DashboardCard, DashboardSnapshot } from '../../../../shared/dashboard-snapshot'
import { AgentKanbanBoard } from './AgentKanbanBoard'
@ -11,15 +11,18 @@ import { AgentKanbanBoard } from './AgentKanbanBoard'
vi.mock('./AgentKanbanCard', () => ({
AgentKanbanCard: ({
card,
now,
onOpenTerminal
}: {
card: DashboardCard
now: number
onOpenTerminal: (card: DashboardCard) => void
}) => (
<div
data-testid="card"
data-bucket={card.bucket}
data-unseen={card.unseen}
data-now={now}
onClick={() => onOpenTerminal(card)}
>
{card.worktreeName}
@ -81,7 +84,9 @@ describe('AgentKanbanBoard', () => {
})
afterEach(() => {
cleanup()
vi.useRealTimers()
vi.clearAllMocks()
vi.restoreAllMocks()
})
it('renders the three fixed columns in order', () => {
@ -119,6 +124,47 @@ describe('AgentKanbanBoard', () => {
expect(names).toEqual(['new-move', 'mid-move', 'old-move'])
})
it('does not start the clock when no card renders a relative timestamp', () => {
vi.useFakeTimers()
vi.setSystemTime(100_000)
const { rerender } = render(<AgentKanbanBoard snapshot={{ generatedAt: 1, cards: [] }} />)
expect(vi.getTimerCount()).toBe(0)
rerender(
<AgentKanbanBoard
snapshot={{ generatedAt: 2, cards: [card({ startedAt: 0, finishedAt: null })] }}
/>
)
const initialNow = screen.getByTestId('card').dataset.now
expect(vi.getTimerCount()).toBe(0)
act(() => vi.advanceTimersByTime(30_000))
expect(screen.getByTestId('card').dataset.now).toBe(initialNow)
})
it('parks the clock while hidden, catches up on reveal, and ticks while visible', () => {
vi.useFakeTimers()
vi.setSystemTime(100_000)
let visibilityState: DocumentVisibilityState = 'hidden'
vi.spyOn(document, 'visibilityState', 'get').mockImplementation(() => visibilityState)
renderBoard([card({ startedAt: 1 })])
expect(screen.getByTestId('card').dataset.now).toBe('100000')
expect(vi.getTimerCount()).toBe(0)
act(() => vi.advanceTimersByTime(60_000))
expect(screen.getByTestId('card').dataset.now).toBe('100000')
visibilityState = 'visible'
act(() => document.dispatchEvent(new Event('visibilitychange')))
expect(screen.getByTestId('card').dataset.now).toBe('160000')
expect(vi.getTimerCount()).toBe(1)
act(() => vi.advanceTimersByTime(30_000))
expect(screen.getByTestId('card').dataset.now).toBe('190000')
})
it('keeps the terminal dialog open across bucket moves and card removal', () => {
const agent = card({ paneKey: 'pk-1', bucket: 'idle', worktreeName: 'wt1' })
const { rerender } = render(<AgentKanbanBoard snapshot={{ generatedAt: 1, cards: [agent] }} />)

View File

@ -6,6 +6,7 @@ import {
type DashboardSnapshot
} from '../../../../shared/dashboard-snapshot'
import { cn } from '@/lib/utils'
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
import { AgentKanbanCard } from './AgentKanbanCard'
import { AgentTerminalDialog } from './AgentTerminalDialog'
import './agent-board-transitions.css'
@ -89,12 +90,21 @@ function KanbanColumn({
/** The pop-out agent board: status columns fed by the relayed snapshot. */
export function AgentKanbanBoard({ snapshot }: { snapshot: DashboardSnapshot }): React.JSX.Element {
const grouped = useMemo(() => groupByBucket(snapshot.cards), [snapshot.cards])
const hasRelativeTimestamps = useMemo(
() => snapshot.cards.some((card) => (card.finishedAt ?? card.startedAt) > 0),
[snapshot.cards]
)
const [now, setNow] = useState(() => Date.now())
useEffect(() => {
const timer = setInterval(() => setNow(Date.now()), 30_000)
return () => clearInterval(timer)
}, [])
if (!hasRelativeTimestamps) {
return
}
return installWindowVisibilityInterval({
run: () => setNow(Date.now()),
intervalMs: 30_000
})
}, [hasRelativeTimestamps])
// The open terminal dialog survives bucket moves: only the paneKey is
// remembered, and the card data is re-resolved from each fresh snapshot.