From 0ec8a3f96255705628f992167c5996fa0c86b2b9 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 10:37:16 +0300 Subject: [PATCH] perf(renderer): ignore provisioning heartbeat rerenders --- src/renderer/store/slices/teamSlice.ts | 67 ++++++++++++++++++++++++-- test/renderer/store/teamSlice.test.ts | 47 ++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/renderer/store/slices/teamSlice.ts b/src/renderer/store/slices/teamSlice.ts index c1769627..9b6932b8 100644 --- a/src/renderer/store/slices/teamSlice.ts +++ b/src/renderer/store/slices/teamSlice.ts @@ -171,6 +171,7 @@ import type { TeamAgentRuntimeSnapshot, TeamCreateRequest, TeamGetDataOptions, + TeamLaunchDiagnosticItem, TeamLaunchRequest, TeamMemberActivityMeta, TeamProvisioningProgress, @@ -1194,6 +1195,65 @@ export function getCurrentProvisioningProgressForTeam( return currentRunId ? (state.provisioningRuns[currentRunId] ?? null) : null; } +function stringArraysEqual( + a: readonly string[] | undefined, + b: readonly string[] | undefined +): boolean { + if (a === b) return true; + if (!a || !b || a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +} + +function launchDiagnosticsEqual( + a: readonly TeamLaunchDiagnosticItem[] | undefined, + b: readonly TeamLaunchDiagnosticItem[] | undefined +): boolean { + if (a === b) return true; + if (!a || !b || a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + const left = a[i]; + const right = b[i]; + if ( + !left || + !right || + left.id !== right.id || + left.memberName !== right.memberName || + left.severity !== right.severity || + left.code !== right.code || + left.label !== right.label || + left.detail !== right.detail || + left.observedAt !== right.observedAt + ) { + return false; + } + } + return true; +} + +function provisioningProgressPayloadEqual( + a: TeamProvisioningProgress, + b: TeamProvisioningProgress +): boolean { + return ( + a.runId === b.runId && + a.teamName === b.teamName && + a.state === b.state && + a.message === b.message && + a.messageSeverity === b.messageSeverity && + a.startedAt === b.startedAt && + a.pid === b.pid && + a.error === b.error && + a.cliLogsTail === b.cliLogsTail && + a.assistantOutput === b.assistantOutput && + a.configReady === b.configReady && + stringArraysEqual(a.warnings, b.warnings) && + launchDiagnosticsEqual(a.launchDiagnostics, b.launchDiagnostics) + ); +} + export function isTeamProvisioningActive( state: Pick, teamName: string @@ -4004,11 +4064,8 @@ export const createTeamSlice: StateCreator = (set, const becameConfigReady = progress.configReady === true && existingProgress?.configReady !== true; const isDuplicateProgress = - existingProgress?.updatedAt === progress.updatedAt && - existingProgress?.state === progress.state && - existingProgress?.message === progress.message && - existingProgress?.error === progress.error && - existingProgress?.pid === progress.pid; + existingProgress !== undefined && + provisioningProgressPayloadEqual(existingProgress, progress); if (isDuplicateProgress && currentRunId === progress.runId) { return; } diff --git a/test/renderer/store/teamSlice.test.ts b/test/renderer/store/teamSlice.test.ts index ffd422fe..b6a3a250 100644 --- a/test/renderer/store/teamSlice.test.ts +++ b/test/renderer/store/teamSlice.test.ts @@ -5602,6 +5602,53 @@ describe('teamSlice actions', () => { expect(store.getState().provisioningRuns['run-stale']).toBeUndefined(); }); + it('ignores provisioning heartbeat updates when only updatedAt changes', () => { + const store = createSliceStore(); + const baseProgress = { + runId: 'run-current', + teamName: 'my-team', + state: 'spawning' as const, + message: 'Waiting for model response', + startedAt: '2026-03-12T10:00:00.000Z', + updatedAt: '2026-03-12T10:00:00.000Z', + pid: 1234, + assistantOutput: 'partial output', + warnings: ['slow response'], + launchDiagnostics: [ + { + id: 'diag-1', + severity: 'warning' as const, + code: 'runtime_process_candidate' as const, + label: 'Runtime candidate', + observedAt: '2026-03-12T10:00:00.000Z', + }, + ], + }; + + store.getState().onProvisioningProgress(baseProgress); + const firstRuns = store.getState().provisioningRuns; + const firstProgress = firstRuns['run-current']; + + store.getState().onProvisioningProgress({ + ...baseProgress, + updatedAt: '2026-03-12T10:00:01.000Z', + }); + + expect(store.getState().provisioningRuns).toBe(firstRuns); + expect(store.getState().provisioningRuns['run-current']).toBe(firstProgress); + + store.getState().onProvisioningProgress({ + ...baseProgress, + updatedAt: '2026-03-12T10:00:02.000Z', + assistantOutput: 'partial output\\nnext chunk', + }); + + expect(store.getState().provisioningRuns).not.toBe(firstRuns); + expect(store.getState().provisioningRuns['run-current']?.assistantOutput).toContain( + 'next chunk' + ); + }); + it('promotes a pending run to a real run without throwing', () => { const store = createSliceStore(); store.setState({