From 8ebde439a8677621e24df493ec920550cc6d7714 Mon Sep 17 00:00:00 2001 From: iliya Date: Tue, 7 Apr 2026 12:29:21 +0300 Subject: [PATCH] fix(team): treat online teammates as ready state --- .../services/team/TeamProvisioningService.ts | 12 +++- .../team/TeamProvisioningBanner.tsx | 8 +-- src/renderer/utils/memberHelpers.ts | 4 +- test/renderer/utils/memberHelpers.test.ts | 62 +++++++++++++++++++ 4 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 test/renderer/utils/memberHelpers.test.ts diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 5fe72fc2..31c3e9cd 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -8995,8 +8995,12 @@ export class TeamProvisioningService { const failedSpawnMembers = this.getFailedSpawnMembers(run); const launchSummary = this.getMemberLaunchSummary(run); const hasSpawnFailures = failedSpawnMembers.length > 0; + const stillStartingCount = Math.max( + 0, + launchSummary.pendingCount - launchSummary.runtimeAlivePendingCount + ); const hasPendingBootstrap = - !hasSpawnFailures && launchSummary.pendingCount > 0 && run.expectedMembers.length > 0; + !hasSpawnFailures && stillStartingCount > 0 && run.expectedMembers.length > 0; const readyMessage = hasSpawnFailures ? `Launch completed with teammate errors — ${failedSpawnMembers .map((member) => member.name) @@ -9155,8 +9159,12 @@ export class TeamProvisioningService { const failedSpawnMembers = this.getFailedSpawnMembers(run); const launchSummary = this.getMemberLaunchSummary(run); const hasSpawnFailures = failedSpawnMembers.length > 0; + const stillStartingCount = Math.max( + 0, + launchSummary.pendingCount - launchSummary.runtimeAlivePendingCount + ); const hasPendingBootstrap = - !hasSpawnFailures && launchSummary.pendingCount > 0 && run.expectedMembers.length > 0; + !hasSpawnFailures && stillStartingCount > 0 && run.expectedMembers.length > 0; const progress = updateProgress( run, 'ready', diff --git a/src/renderer/components/team/TeamProvisioningBanner.tsx b/src/renderer/components/team/TeamProvisioningBanner.tsx index 85112c09..82e6bda2 100644 --- a/src/renderer/components/team/TeamProvisioningBanner.tsx +++ b/src/renderer/components/team/TeamProvisioningBanner.tsx @@ -159,9 +159,7 @@ export const TeamProvisioningBanner = ({ ? `Team provisioned — ${heartbeatConfirmedCount}/${fallbackTeammateCount} teammates made contact${processOnlyAliveCount > 0 ? `, ${processOnlyAliveCount} teammate${processOnlyAliveCount === 1 ? '' : 's'} online` : ''}${pendingSpawnCount > 0 ? `${processOnlyAliveCount > 0 ? ', ' : ', '}${pendingSpawnCount} still starting` : ''}` : 'Team provisioned — teammates are still starting'; const readyDetailSeverity = - failedSpawnCount > 0 || processOnlyAliveCount > 0 || pendingSpawnCount > 0 - ? 'warning' - : undefined; + failedSpawnCount > 0 || pendingSpawnCount > 0 ? 'warning' : undefined; const readyMessage = failedSpawnCount > 0 ? `Launch finished with errors — ${failedSpawnCount}/${Math.max(fallbackTeammateCount, failedSpawnCount)} teammates failed to start` @@ -191,9 +189,7 @@ export const TeamProvisioningBanner = ({ onCancel={null} successMessage={readyMessage} successMessageSeverity={ - failedSpawnCount > 0 || processOnlyAliveCount > 0 || pendingSpawnCount > 0 - ? 'warning' - : 'success' + failedSpawnCount > 0 || pendingSpawnCount > 0 ? 'warning' : 'success' } onDismiss={() => setDismissed(true)} /> diff --git a/src/renderer/utils/memberHelpers.ts b/src/renderer/utils/memberHelpers.ts index 830d960d..ae986f61 100644 --- a/src/renderer/utils/memberHelpers.ts +++ b/src/renderer/utils/memberHelpers.ts @@ -101,7 +101,7 @@ export const SPAWN_DOT_COLORS: Record = { export const SPAWN_PRESENCE_LABELS: Record = { offline: 'offline', - waiting: 'online', + waiting: 'starting', spawning: 'starting', online: 'ready', error: 'spawn failed', @@ -123,7 +123,7 @@ export function getSpawnAwareDotClass( return SPAWN_DOT_COLORS.error; } if (spawnLaunchState === 'runtime_pending_bootstrap' && spawnStatus === 'online') { - return SPAWN_DOT_COLORS.spawning; + return SPAWN_DOT_COLORS.online; } if (spawnStatus === 'waiting') { return SPAWN_DOT_COLORS.waiting; diff --git a/test/renderer/utils/memberHelpers.test.ts b/test/renderer/utils/memberHelpers.test.ts new file mode 100644 index 00000000..848e2f3c --- /dev/null +++ b/test/renderer/utils/memberHelpers.test.ts @@ -0,0 +1,62 @@ +import { + getSpawnAwareDotClass, + getSpawnAwarePresenceLabel, +} from '@renderer/utils/memberHelpers'; + +import type { ResolvedTeamMember } from '@shared/types'; + +const member: ResolvedTeamMember = { + name: 'alice', + status: 'unknown', + taskCount: 0, + currentTaskId: null, + lastActiveAt: null, + messageCount: 0, + color: 'blue', + agentType: 'reviewer', + role: 'Reviewer', + removedAt: undefined, +}; + +describe('memberHelpers spawn-aware presence', () => { + it('shows process-online teammates as online with a green dot', () => { + expect( + getSpawnAwarePresenceLabel( + member, + 'online', + 'runtime_pending_bootstrap', + 'process', + true, + true, + false, + undefined + ) + ).toBe('online'); + + expect( + getSpawnAwareDotClass( + member, + 'online', + 'runtime_pending_bootstrap', + true, + false, + undefined + ) + ).toContain('bg-emerald-400'); + }); + + it('keeps accepted-but-not-yet-online teammates in starting state', () => { + expect( + getSpawnAwarePresenceLabel( + member, + 'waiting', + 'starting', + undefined, + false, + true, + true, + undefined + ) + ).toBe('starting'); + }); +});