import { buildMemberLaunchPresentation, getLaunchAwarePresenceLabel, getSpawnAwareDotClass, getSpawnAwarePresenceLabel, getSpawnCardClass, getMemberRuntimeAdvisoryLabel, getMemberRuntimeAdvisoryTitle, } 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', providerId: 'gemini', 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, false, true, false, undefined ) ).toBe('online'); expect( getSpawnAwareDotClass( member, 'online', 'runtime_pending_bootstrap', true, false, 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, false, true, false, undefined ) ).toBe('starting'); }); it('keeps starting visuals after provisioning already transitioned out of active state', () => { expect( getSpawnAwarePresenceLabel( member, 'spawning', 'starting', undefined, false, false, true, false, undefined ) ).toBe('starting'); expect( getSpawnAwareDotClass(member, 'spawning', 'starting', false, false, true, false, undefined) ).toContain( 'bg-amber-400' ); expect(getSpawnCardClass('spawning', 'starting', false, false)).toContain( 'member-waiting-shimmer' ); }); it('shows offline instead of stale starting visuals when the team is offline', () => { expect( getSpawnAwarePresenceLabel( member, 'spawning', 'starting', undefined, false, false, false, false, undefined ) ).toBe('offline'); expect( getSpawnAwareDotClass( member, 'spawning', 'starting', false, false, false, false, undefined ) ).toContain('bg-red-400'); expect(getSpawnCardClass('spawning', 'starting', false, false, false, false)).toBe(''); }); it('keeps runtime-pending teammates in starting state while launch is still settling', () => { expect( getSpawnAwarePresenceLabel( member, 'online', 'runtime_pending_bootstrap', 'process', true, true, true, false, undefined ) ).toBe('starting'); expect( getSpawnAwareDotClass( member, 'online', 'runtime_pending_bootstrap', true, true, true, false, undefined ) ).toContain('bg-zinc-400'); expect(getSpawnCardClass('online', 'runtime_pending_bootstrap', true, true, true, false)).toContain( 'member-waiting-shimmer' ); }); it('shows confirmed teammates as ready instead of idle while launch is still settling', () => { expect( getSpawnAwarePresenceLabel( member, 'online', 'confirmed_alive', 'heartbeat', true, true, true, false, undefined ) ).toBe('ready'); }); it('derives runtime-pending and settling visual states from the same launch inputs', () => { const runtimePending = buildMemberLaunchPresentation({ member, spawnStatus: 'online', spawnLaunchState: 'runtime_pending_bootstrap', spawnLivenessSource: 'process', spawnRuntimeAlive: true, runtimeAdvisory: undefined, isLaunchSettling: false, isTeamAlive: true, isTeamProvisioning: false, }); const settling = buildMemberLaunchPresentation({ member, spawnStatus: 'online', spawnLaunchState: 'confirmed_alive', spawnLivenessSource: 'heartbeat', spawnRuntimeAlive: true, runtimeAdvisory: undefined, isLaunchSettling: true, isTeamAlive: true, isTeamProvisioning: false, }); expect(runtimePending.launchVisualState).toBe('runtime_pending'); expect(runtimePending.launchStatusLabel).toBe('connecting'); expect(settling.launchVisualState).toBe('settling'); expect(settling.launchStatusLabel).toBe('joining team'); }); it('surfaces permission-blocked teammates as awaiting permission instead of generic starting', () => { const permissionPending = buildMemberLaunchPresentation({ member, spawnStatus: 'online', spawnLaunchState: 'runtime_pending_permission', spawnLivenessSource: 'process', spawnRuntimeAlive: true, runtimeAdvisory: undefined, isLaunchSettling: false, isTeamAlive: true, isTeamProvisioning: false, }); expect(permissionPending.presenceLabel).toBe('connecting'); expect(permissionPending.launchVisualState).toBe('permission_pending'); expect(permissionPending.launchStatusLabel).toBe('awaiting permission'); expect(permissionPending.dotClass).toContain('bg-amber-400'); expect(permissionPending.cardClass).toContain('member-waiting-shimmer'); }); it('returns shared launch status labels without changing generic presence labels', () => { expect( buildMemberLaunchPresentation({ member, spawnStatus: 'waiting', spawnLaunchState: 'starting', spawnLivenessSource: undefined, spawnRuntimeAlive: false, runtimeAdvisory: undefined, isLaunchSettling: false, isTeamAlive: true, isTeamProvisioning: false, }) ).toMatchObject({ presenceLabel: 'starting', launchVisualState: 'waiting', launchStatusLabel: 'waiting to start', }); expect( buildMemberLaunchPresentation({ member, spawnStatus: 'spawning', spawnLaunchState: 'starting', spawnLivenessSource: undefined, spawnRuntimeAlive: false, runtimeAdvisory: undefined, isLaunchSettling: false, isTeamAlive: true, isTeamProvisioning: false, }) ).toMatchObject({ presenceLabel: 'starting', launchVisualState: 'spawning', launchStatusLabel: 'starting', }); expect( buildMemberLaunchPresentation({ member, spawnStatus: 'error', spawnLaunchState: 'failed_to_start', spawnLivenessSource: undefined, spawnRuntimeAlive: false, runtimeAdvisory: undefined, isLaunchSettling: false, isTeamAlive: true, isTeamProvisioning: false, }) ).toMatchObject({ presenceLabel: 'spawn failed', launchVisualState: 'error', launchStatusLabel: 'failed', }); }); it('renders unified retry advisory labels for provider retries', () => { expect( getMemberRuntimeAdvisoryLabel( { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2026-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'quota_exhausted', message: 'Gemini cli backend error: capacity exceeded.', }, 'gemini', Date.parse('2026-04-07T09:00:00.000Z') ) ).toBe('Gemini quota retry · 45s'); expect( getMemberRuntimeAdvisoryTitle( { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2026-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'rate_limited', message: 'Gemini cli backend error: rate limit 429.', }, 'gemini' ) ).toContain('Gemini rate limited the request'); }); it('keeps network advisories provider-neutral and appends raw details to the title', () => { expect( getMemberRuntimeAdvisoryLabel( { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2026-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'network_error', message: 'Connection timed out while contacting provider.', }, 'gemini', Date.parse('2026-04-07T09:00:00.000Z') ) ).toBe('Network retry · 45s'); expect( getMemberRuntimeAdvisoryTitle( { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2026-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'network_error', message: 'Connection timed out while contacting provider.', }, 'gemini' ) ).toContain('Connection timed out while contacting provider.'); }); it('falls back to the existing generic retry wording when no structured reason is present', () => { expect( getMemberRuntimeAdvisoryLabel( { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2026-04-07T09:00:45.000Z', retryDelayMs: 45_000, message: 'Gemini cli backend error: capacity exceeded.', }, 'gemini', Date.parse('2026-04-07T09:00:00.000Z') ) ).toBe('retrying now · 45s'); }); it('surfaces retry advisory text instead of plain online while bootstrap contact is still pending', () => { expect( getLaunchAwarePresenceLabel( member, 'online', 'runtime_pending_bootstrap', 'process', true, { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2099-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'quota_exhausted', message: 'Gemini cli backend error: capacity exceeded.', }, false, true, false, undefined ) ).toContain('Gemini quota retry'); expect( getLaunchAwarePresenceLabel( member, 'online', 'runtime_pending_bootstrap', 'process', false, { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2099-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'quota_exhausted', message: 'Gemini cli backend error: capacity exceeded.', }, false, true, false, undefined ) ).toBe('starting'); }); it('keeps retry advisory visible after contact when the teammate is otherwise just idle or ready', () => { expect( getLaunchAwarePresenceLabel( member, 'online', 'confirmed_alive', 'heartbeat', true, { kind: 'sdk_retrying', observedAt: '2026-04-07T09:00:00.000Z', retryUntil: '2099-04-07T09:00:45.000Z', retryDelayMs: 45_000, reasonCode: 'quota_exhausted', message: 'Gemini cli backend error: capacity exceeded.', }, false, true, false, undefined ) ).toContain('Gemini quota retry'); }); });