perf(renderer): ignore provisioning heartbeat rerenders

This commit is contained in:
777genius 2026-05-31 10:37:16 +03:00
parent dc3001f713
commit 0ec8a3f962
2 changed files with 109 additions and 5 deletions

View file

@ -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<TeamSlice, 'currentProvisioningRunIdByTeam' | 'provisioningRuns'>,
teamName: string
@ -4004,11 +4064,8 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (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;
}

View file

@ -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({