From d5af7de2e8854644795f3fe389e99078e36ea265 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Thu, 19 Mar 2026 17:21:41 +0900 Subject: [PATCH] fix(tui): stabilize now via useMemo to prevent non-deterministic re-renders - Replace Date.now() in render body with useMemo(() => Date.now(), [tick]) - Add test verifying now stays stable across non-tick re-renders Closes #698 --- .../mcp-server/src/tui/dashboard-app.spec.tsx | 19 +++++++++++++++++++ apps/mcp-server/src/tui/dashboard-app.tsx | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/mcp-server/src/tui/dashboard-app.spec.tsx b/apps/mcp-server/src/tui/dashboard-app.spec.tsx index 2b4070da..9940ddf0 100644 --- a/apps/mcp-server/src/tui/dashboard-app.spec.tsx +++ b/apps/mcp-server/src/tui/dashboard-app.spec.tsx @@ -159,6 +159,25 @@ describe('DashboardApp', () => { expect(frame).not.toContain('/from-state'); }); + it('memoizes now based on tick to avoid non-deterministic re-renders', () => { + const dateNowSpy = vi.spyOn(Date, 'now').mockReturnValue(1700000000000); + + const state = createInitialDashboardState(); + const { lastFrame, rerender } = render(); + const frame1 = lastFrame() ?? ''; + + // Advance Date.now() by 1 minute — but tick has NOT changed (still 0 from mock) + dateNowSpy.mockReturnValue(1700000060000); + rerender(); + const frame2 = lastFrame() ?? ''; + + // With useMemo([tick]): now stays cached → frames identical + // Without useMemo: now = Date.now() changes → time display differs + expect(frame1).toBe(frame2); + + dateNowSpy.mockRestore(); + }); + it('should use externalState when provided (multi-session mode)', () => { const mockState = createInitialDashboardState(); // Modify some fields to verify they propagate diff --git a/apps/mcp-server/src/tui/dashboard-app.tsx b/apps/mcp-server/src/tui/dashboard-app.tsx index 1e849ae8..352726ff 100644 --- a/apps/mcp-server/src/tui/dashboard-app.tsx +++ b/apps/mcp-server/src/tui/dashboard-app.tsx @@ -27,7 +27,7 @@ export function DashboardApp({ }: DashboardAppProps): React.ReactElement { const { columns, rows, layoutMode } = useTerminalSize(); const tick = useTick(1000); - const now = Date.now(); + const now = useMemo(() => Date.now(), [tick]); const internalState = useDashboardState(externalState ? undefined : eventBus); const state = externalState ?? internalState; const focusedAgent = state.focusedAgentId