fix(chat): localize monitoring status without render churn

This commit is contained in:
Merge Sim
2026-09-04 15:31:25 -07:00
parent 80d4bcd4e6
commit 25ac2f6536
4 changed files with 80 additions and 11 deletions
@@ -1,6 +1,5 @@
import { AgentStateDot, agentStateLabel } from '@/components/AgentStateDot'
const MONITORING_LABEL = agentStateLabel('monitoring')
import { AgentStateDot } from '@/components/AgentStateDot'
import { translate } from '@/i18n/i18n'
export function NativeChatMonitoringStatus({
monitoring
@@ -20,7 +19,9 @@ export function NativeChatMonitoringStatus({
<span aria-hidden="true">
<AgentStateDot state="monitoring" title={null} />
</span>
<span>{MONITORING_LABEL}</span>
<span>
{translate('components.native-chat.monitoringStatus.label', 'Monitoring background tasks')}
</span>
</div>
)
}
@@ -44,12 +44,11 @@ function resolveNativeChatHookStatus(
}
export function useNativeChatMonitoringStatus(paneKey: string): boolean {
// Why: monitoring stays out of foreground lifecycle while remaining visible from the tab glyph's pane-status source.
const agentStatusEpoch = useAppStore((store) => store.agentStatusEpoch)
void agentStatusEpoch
return useAppStore((store) =>
resolveNativeChatMonitoringStatus(store.agentStatusByPaneKey[paneKey])
)
return useAppStore((store) => {
// Why: epoch invalidates freshness; the primitive result avoids unrelated-pane rerenders.
void store.agentStatusEpoch
return resolveNativeChatMonitoringStatus(store.agentStatusByPaneKey[paneKey])
})
}
export function useNativeChatHookStatus(
@@ -0,0 +1,66 @@
// @vitest-environment happy-dom
import '@testing-library/jest-dom/vitest'
import { act, cleanup, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentStatusEntry } from '../../../../shared/agent-status-types'
import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status-types'
import { useAppStore } from '@/store'
import { useNativeChatMonitoringStatus } from './use-native-chat-hook-status'
const PANE_KEY = 'tab-1:11111111-1111-4111-8111-111111111111'
function monitoringEntry(updatedAt: number): AgentStatusEntry {
return {
paneKey: PANE_KEY,
state: 'working',
workingMode: 'monitoring',
prompt: 'Monitor background tasks',
updatedAt,
stateStartedAt: updatedAt,
stateHistory: []
}
}
describe('useNativeChatMonitoringStatus', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2026-09-04T12:00:00.000Z'))
useAppStore.setState({
agentStatusByPaneKey: { [PANE_KEY]: monitoringEntry(Date.now()) },
agentStatusEpoch: 0
})
})
afterEach(() => {
cleanup()
useAppStore.setState({ agentStatusByPaneKey: {}, agentStatusEpoch: 0 })
vi.useRealTimers()
})
it('rerenders at expiry without rerendering for an unrelated epoch', () => {
let renders = 0
function Probe(): React.JSX.Element {
renders += 1
const monitoring = useNativeChatMonitoringStatus(PANE_KEY)
return <span>{monitoring ? 'monitoring' : 'idle'}</span>
}
render(<Probe />)
expect(screen.getByText('monitoring')).toBeInTheDocument()
expect(renders).toBe(1)
act(() => {
useAppStore.setState((state) => ({ agentStatusEpoch: state.agentStatusEpoch + 1 }))
})
expect(renders).toBe(1)
vi.setSystemTime(Date.now() + AGENT_STATUS_STALE_AFTER_MS + 1)
act(() => {
useAppStore.setState((state) => ({ agentStatusEpoch: state.agentStatusEpoch + 1 }))
})
expect(screen.getByText('idle')).toBeInTheDocument()
expect(renders).toBe(2)
})
})
+4 -1
View File
@@ -17009,7 +17009,10 @@
},
"structuredSessionCloseFailed": "Could not close this Codex chat",
"structuredSessionLaunchFailed": "Could not open Codex chat",
"structuredSessionCloseFailedDescription": "The terminal stayed open so the provider remains recoverable."
"structuredSessionCloseFailedDescription": "The terminal stayed open so the provider remains recoverable.",
"monitoringStatus": {
"label": "Monitoring background tasks"
}
},
"tab": {
"bar": {