import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { create } from 'zustand';

import {
  __getTeamScopedTransientStateForTests,
  __resetTeamSliceModuleStateForTests,
  createTeamSlice,
  getActiveTeamPendingReplyWaits,
  getCurrentProvisioningProgressForTeam,
  hasActiveTeamPendingReplyWait,
  loadPersistedMessagesPanelMode,
  savePersistedMessagesPanelMode,
  selectMemberMessagesForTeamMember,
  selectResolvedMemberForTeamName,
  selectResolvedMembersForTeamName,
  selectTeamDataForName,
} from '../../../src/renderer/store/slices/teamSlice';
import {
  __resetTeamRefreshFanoutDiagnosticsForTests,
  getTeamRefreshFanoutSnapshotForTests,
  type TeamRefreshFanoutSnapshot,
} from '../../../src/renderer/store/teamRefreshFanoutDiagnostics';

const hoisted = vi.hoisted(() => ({
  list: vi.fn(),
  getData: vi.fn(),
  getTaskChangePresence: vi.fn(),
  getMessagesPage: vi.fn(),
  getMemberActivityMeta: vi.fn(),
  createTeam: vi.fn(),
  launchTeam: vi.fn(),
  getProvisioningStatus: vi.fn(),
  getMemberSpawnStatuses: vi.fn(),
  getTeamAgentRuntime: vi.fn(),
  cancelProvisioning: vi.fn(),
  deleteTeam: vi.fn(),
  restoreTeam: vi.fn(),
  permanentlyDeleteTeam: vi.fn(),
  sendMessage: vi.fn(),
  getOpenCodeRuntimeDeliveryStatus: vi.fn(),
  retryFailedOpenCodeSecondaryLanes: vi.fn(),
  restartMember: vi.fn(),
  skipMemberForLaunch: vi.fn(),
  requestReview: vi.fn(),
  updateKanban: vi.fn(),
  invalidateTaskChangeSummaries: vi.fn(),
  onProvisioningProgress: vi.fn(() => () => undefined),
}));

const originalWindowAnimationFrame =
  typeof window === 'undefined'
    ? null
    : {
        hasRequest: Object.prototype.hasOwnProperty.call(window, 'requestAnimationFrame'),
        hasCancel: Object.prototype.hasOwnProperty.call(window, 'cancelAnimationFrame'),
        requestAnimationFrame: window.requestAnimationFrame,
        cancelAnimationFrame: window.cancelAnimationFrame,
      };

vi.mock('@renderer/api', () => ({
  api: {
    teams: {
      list: hoisted.list,
      getData: hoisted.getData,
      getTaskChangePresence: hoisted.getTaskChangePresence,
      getMessagesPage: hoisted.getMessagesPage,
      getMemberActivityMeta: hoisted.getMemberActivityMeta,
      createTeam: hoisted.createTeam,
      launchTeam: hoisted.launchTeam,
      getProvisioningStatus: hoisted.getProvisioningStatus,
      getMemberSpawnStatuses: hoisted.getMemberSpawnStatuses,
      getTeamAgentRuntime: hoisted.getTeamAgentRuntime,
      cancelProvisioning: hoisted.cancelProvisioning,
      deleteTeam: hoisted.deleteTeam,
      restoreTeam: hoisted.restoreTeam,
      permanentlyDeleteTeam: hoisted.permanentlyDeleteTeam,
      sendMessage: hoisted.sendMessage,
      getOpenCodeRuntimeDeliveryStatus: hoisted.getOpenCodeRuntimeDeliveryStatus,
      retryFailedOpenCodeSecondaryLanes: hoisted.retryFailedOpenCodeSecondaryLanes,
      restartMember: hoisted.restartMember,
      skipMemberForLaunch: hoisted.skipMemberForLaunch,
      requestReview: hoisted.requestReview,
      updateKanban: hoisted.updateKanban,
      onProvisioningProgress: hoisted.onProvisioningProgress,
    },
    review: {
      invalidateTaskChangeSummaries: hoisted.invalidateTaskChangeSummaries,
    },
  },
}));

vi.mock('../../../src/renderer/utils/unwrapIpc', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../../../src/renderer/utils/unwrapIpc')>();
  return {
    ...actual,
    unwrapIpc: async <T>(_operation: string, fn: () => Promise<T>): Promise<T> => {
      try {
        return await fn();
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        throw new actual.IpcError('mock-op', message, error);
      }
    },
  };
});

function createSliceStore() {
  return create<any>()((set, get, store) => ({
    ...createTeamSlice(set as never, get as never, store as never),
    paneLayout: {
      focusedPaneId: 'pane-default',
      panes: [
        {
          id: 'pane-default',
          widthFraction: 1,
          tabs: [],
          activeTabId: null,
        },
      ],
    },
    openTab: vi.fn(),
    setActiveTab: vi.fn(),
    updateTabLabel: vi.fn(),
    getAllPaneTabs: vi.fn(() => []),
    warmTaskChangeSummaries: vi.fn(async () => undefined),
    invalidateTaskChangePresence: vi.fn(),
    fetchTeams: vi.fn(async () => undefined),
    fetchAllTasks: vi.fn(async () => undefined),
  }));
}

function createTeamSnapshot(overrides: Record<string, unknown> = {}): {
  teamName: string;
  config: { name: string; members?: unknown[]; projectPath?: string };
  tasks: unknown[];
  members: unknown[];
  kanbanState: { teamName: string; reviewers: unknown[]; tasks: Record<string, unknown> };
  processes: unknown[];
  isAlive?: boolean;
} {
  return {
    teamName: 'my-team',
    config: { name: 'My Team' },
    tasks: [],
    members: [],
    kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
    processes: [],
    ...overrides,
  };
}

function createMemberSpawnStatus(overrides: Record<string, unknown> = {}) {
  return {
    status: 'online',
    launchState: 'confirmed_alive',
    error: undefined,
    updatedAt: '2026-03-12T10:00:00.000Z',
    runtimeAlive: true,
    livenessSource: 'heartbeat',
    bootstrapConfirmed: true,
    hardFailure: false,
    firstSpawnAcceptedAt: '2026-03-12T09:59:30.000Z',
    lastHeartbeatAt: '2026-03-12T10:00:00.000Z',
    ...overrides,
  };
}

function createMemberSpawnSnapshot(overrides: Record<string, unknown> = {}) {
  const typedOverrides = overrides as {
    statuses?: Record<string, ReturnType<typeof createMemberSpawnStatus>>;
  };
  return {
    runId: 'runtime-run',
    teamLaunchState: 'clean_success',
    launchPhase: 'finished',
    expectedMembers: ['alice'],
    updatedAt: '2026-03-12T10:00:00.000Z',
    summary: {
      confirmedCount: 1,
      pendingCount: 0,
      failedCount: 0,
      runtimeAlivePendingCount: 0,
    },
    source: 'merged',
    statuses: typedOverrides.statuses ?? { alice: createMemberSpawnStatus() },
    ...overrides,
  };
}

function createDeferredPromise<T>() {
  let resolve!: (value: T | PromiseLike<T>) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

function defineWindowAnimationFrame(
  requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined,
  cancelAnimationFrame: ((handle: number) => void) | undefined
): void {
  if (typeof window === 'undefined') {
    return;
  }
  if (requestAnimationFrame === undefined) {
    delete (window as Partial<Window>).requestAnimationFrame;
  } else {
    Object.defineProperty(window, 'requestAnimationFrame', {
      configurable: true,
      writable: true,
      value: requestAnimationFrame,
    });
  }
  if (cancelAnimationFrame === undefined) {
    delete (window as Partial<Window>).cancelAnimationFrame;
  } else {
    Object.defineProperty(window, 'cancelAnimationFrame', {
      configurable: true,
      writable: true,
      value: cancelAnimationFrame,
    });
  }
}

function restoreWindowAnimationFrame(): void {
  if (typeof window === 'undefined' || originalWindowAnimationFrame === null) {
    return;
  }
  defineWindowAnimationFrame(
    originalWindowAnimationFrame.hasRequest
      ? originalWindowAnimationFrame.requestAnimationFrame
      : undefined,
    originalWindowAnimationFrame.hasCancel
      ? originalWindowAnimationFrame.cancelAnimationFrame
      : undefined
  );
}

function stubAnimationFrameWithTimer(): void {
  defineWindowAnimationFrame(
    (callback) => setTimeout(() => callback(Date.now()), 16) as unknown as number,
    (handle) => clearTimeout(handle as unknown as ReturnType<typeof setTimeout>)
  );
}

function stubAnimationFrameNeverFires(): void {
  defineWindowAnimationFrame(
    () => 1,
    () => undefined
  );
}

async function flushPostPaintTeamEnrichments(): Promise<void> {
  await vi.advanceTimersByTimeAsync(16);
  await vi.runOnlyPendingTimersAsync();
  await Promise.resolve();
  await Promise.resolve();
  await Promise.resolve();
  await Promise.resolve();
}

async function flushMicrotasks(): Promise<void> {
  await Promise.resolve();
  await Promise.resolve();
  await Promise.resolve();
  await Promise.resolve();
}

async function flushAsyncWork(): Promise<void> {
  await flushMicrotasks();
  await new Promise<void>((resolve) => {
    setTimeout(resolve, 0);
  });
  await flushMicrotasks();
}

function createRuntimeSnapshot(overrides: Record<string, unknown> = {}) {
  return {
    teamName: 'my-team',
    updatedAt: '2026-03-12T10:00:00.000Z',
    runId: 'runtime-run',
    members: {
      alice: {
        memberName: 'alice',
        alive: true,
        restartable: true,
        backendType: 'tmux',
        pid: 4242,
        runtimeModel: 'gpt-5.4-mini',
        rssBytes: 256 * 1024 * 1024,
        updatedAt: '2026-03-12T10:00:00.000Z',
      },
    },
    ...overrides,
  };
}

describe('teamSlice actions', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    __resetTeamSliceModuleStateForTests();
    __resetTeamRefreshFanoutDiagnosticsForTests();
    hoisted.list.mockResolvedValue([]);
    hoisted.getData.mockResolvedValue(createTeamSnapshot());
    hoisted.getTaskChangePresence.mockResolvedValue({});
    hoisted.getMessagesPage.mockResolvedValue({
      messages: [],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-1',
    });
    hoisted.getMemberActivityMeta.mockResolvedValue({
      teamName: 'my-team',
      computedAt: '2026-03-12T10:00:00.000Z',
      members: {},
      feedRevision: 'rev-1',
    });
    hoisted.sendMessage.mockResolvedValue({ deliveredToInbox: true, messageId: 'm1' });
    hoisted.getOpenCodeRuntimeDeliveryStatus.mockResolvedValue(null);
    hoisted.requestReview.mockResolvedValue(undefined);
    hoisted.updateKanban.mockResolvedValue(undefined);
    hoisted.createTeam.mockResolvedValue({ runId: 'run-1' });
    hoisted.launchTeam.mockResolvedValue({ runId: 'run-1' });
    hoisted.invalidateTaskChangeSummaries.mockResolvedValue(undefined);
    hoisted.getProvisioningStatus.mockResolvedValue({
      runId: 'run-1',
      teamName: 'my-team',
      state: 'spawning',
      message: 'Starting',
      startedAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    });
    hoisted.getMemberSpawnStatuses.mockResolvedValue({ statuses: {}, runId: null });
    hoisted.getTeamAgentRuntime.mockResolvedValue(
      createRuntimeSnapshot({ runId: null, members: {} })
    );
    hoisted.cancelProvisioning.mockResolvedValue(undefined);
    hoisted.deleteTeam.mockResolvedValue(undefined);
    hoisted.restoreTeam.mockResolvedValue(undefined);
    hoisted.permanentlyDeleteTeam.mockResolvedValue(undefined);
    hoisted.retryFailedOpenCodeSecondaryLanes.mockResolvedValue({
      attempted: [],
      confirmed: [],
      pending: [],
      failed: [],
      skipped: [],
    });
    hoisted.restartMember.mockResolvedValue(undefined);
    hoisted.skipMemberForLaunch.mockResolvedValue(undefined);
    window.localStorage.removeItem('team:messagesPanelMode');
  });

  afterEach(() => {
    restoreWindowAnimationFrame();
    vi.useRealTimers();
  });

  it('restores the selected messages panel mode from localStorage', () => {
    window.localStorage.setItem('team:messagesPanelMode', 'bottom-sheet');

    const store = createSliceStore();

    expect(store.getState().messagesPanelMode).toBe('bottom-sheet');
  });

  it('persists messages panel mode changes and ignores invalid stored values', () => {
    const store = createSliceStore();

    store.getState().setMessagesPanelMode('floating-composer');

    expect(window.localStorage.getItem('team:messagesPanelMode')).toBe('floating-composer');
    expect(loadPersistedMessagesPanelMode()).toBe('floating-composer');

    window.localStorage.setItem('team:messagesPanelMode', 'bad-mode');

    expect(loadPersistedMessagesPanelMode()).toBe('sidebar');

    savePersistedMessagesPanelMode('inline');

    expect(window.localStorage.getItem('team:messagesPanelMode')).toBe('inline');
  });

  it('updates selected team task change presence in one batch', () => {
    const store = createSliceStore();
    const existingData = createTeamSnapshot({
      teamName: 'my-team',
      tasks: [
        { id: 'task-1', subject: 'One', changePresence: 'unknown' },
        { id: 'task-2', subject: 'Two', changePresence: 'unknown' },
      ],
    });
    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: existingData,
      teamDataCacheByName: { 'my-team': existingData },
      globalTasks: [
        { teamName: 'my-team', id: 'task-1', changePresence: 'unknown' },
        { teamName: 'my-team', id: 'task-2', changePresence: 'unknown' },
        { teamName: 'other-team', id: 'task-1', changePresence: 'unknown' },
      ],
    });

    store.getState().setSelectedTeamTaskChangePresences('my-team', {
      'task-1': 'no_changes',
      'task-2': 'has_changes',
    });

    expect(
      store
        .getState()
        .selectedTeamData.tasks.map((task: { changePresence?: string }) => task.changePresence)
    ).toEqual(['no_changes', 'has_changes']);
    expect(
      store
        .getState()
        .teamDataCacheByName[
          'my-team'
        ].tasks.map((task: { changePresence?: string }) => task.changePresence)
    ).toEqual(['no_changes', 'has_changes']);
    expect(
      store.getState().globalTasks.map((task: { changePresence?: string }) => task.changePresence)
    ).toEqual(['no_changes', 'has_changes', 'unknown']);
  });

  it('records terminal provisioning fanout diagnostics without changing visible graph hydrate behavior', () => {
    const store = createSliceStore();
    const fetchTeams = vi.fn(async () => undefined);
    const refreshTeamData = vi.fn(async () => undefined);
    store.setState({
      fetchTeams,
      refreshTeamData,
      selectedTeamName: 'other-team',
      selectedTeamData: createTeamSnapshot({
        teamName: 'other-team',
        config: { name: 'Other Team' },
      }),
      paneLayout: {
        focusedPaneId: 'pane-default',
        panes: [
          {
            id: 'pane-default',
            widthFraction: 1,
            tabs: [{ id: 'graph-my-team', type: 'graph', teamName: 'my-team', label: 'Graph' }],
            activeTabId: 'graph-my-team',
          },
        ],
      },
    });

    store.getState().onProvisioningProgress({
      runId: 'run-ready',
      teamName: 'my-team',
      state: 'ready',
      message: 'Ready',
      startedAt: '2026-03-12T10:00:00.000Z',
      updatedAt: '2026-03-12T10:00:01.000Z',
    } as never);

    expect(fetchTeams).toHaveBeenCalledTimes(1);
    expect(refreshTeamData).toHaveBeenCalledTimes(1);
    expect(refreshTeamData).toHaveBeenCalledWith('my-team', { withDedup: true });
    expect(hoisted.getMemberSpawnStatuses).toHaveBeenCalledWith('my-team');
    expect(hoisted.getTeamAgentRuntime).toHaveBeenCalledWith('my-team');

    const snapshot = getTeamRefreshFanoutSnapshotForTests(
      'my-team'
    ) as TeamRefreshFanoutSnapshot | null;
    expect(
      snapshot?.counts['provisioning-progress:provisioning:terminal-ready:fetchTeams:scheduled']
    ).toBe(1);
    expect(
      snapshot?.counts[
        'provisioning-progress:provisioning:terminal-ready:refreshTeamData:scheduled'
      ]
    ).toBe(1);
    expect(
      snapshot?.counts[
        'provisioning-progress:provisioning:terminal-ready:fetchMemberSpawnStatuses:scheduled'
      ]
    ).toBe(1);
    expect(
      snapshot?.counts[
        'provisioning-progress:provisioning:terminal-ready:fetchTeamAgentRuntime:scheduled'
      ]
    ).toBe(1);
  });

  it('maps inbox verify failure to user-friendly text', async () => {
    const store = createSliceStore();
    hoisted.sendMessage.mockRejectedValue(new Error('Failed to verify inbox write'));

    await expect(
      store.getState().sendTeamMessage('my-team', { member: 'alice', text: 'hello' })
    ).rejects.toThrow('Failed to verify inbox write');

    expect(store.getState().sendMessageError).toBe(
      'Message was written but not verified (race). Please try again.'
    );
  });

  it('keeps send dialog result non-terminal when OpenCode runtime delivery fails after inbox persistence', async () => {
    const store = createSliceStore();
    hoisted.sendMessage.mockResolvedValue({
      deliveredToInbox: true,
      messageId: 'm-opencode-1',
      runtimeDelivery: {
        providerId: 'opencode',
        attempted: true,
        delivered: false,
        reason: 'opencode_runtime_not_active',
      },
    });

    const result = await store.getState().sendTeamMessage('my-team', {
      member: 'bob',
      text: 'hello',
    });

    expect(result.messageId).toBe('m-opencode-1');
    expect(store.getState().lastSendMessageResult).toBeNull();
    expect(store.getState().sendMessageError).toBeNull();
    expect(store.getState().sendMessageWarning).toBe(
      'OpenCode runtime delivery failed. Message was saved to inbox, but live delivery did not complete.'
    );
    expect(store.getState().sendMessageDebugDetails).toMatchObject({
      messageId: 'm-opencode-1',
      providerId: 'opencode',
      delivered: false,
      responsePending: null,
      responseState: null,
      ledgerStatus: null,
      acceptanceUnknown: null,
      reason: 'opencode_runtime_not_active',
      diagnostics: [],
    });
  });

  it('stores hidden OpenCode runtime diagnostics while live response is pending', async () => {
    const store = createSliceStore();
    hoisted.sendMessage.mockResolvedValue({
      deliveredToInbox: true,
      messageId: 'm-opencode-pending',
      runtimeDelivery: {
        providerId: 'opencode',
        attempted: true,
        delivered: true,
        responsePending: true,
        responseState: 'pending',
        ledgerStatus: 'accepted',
        acceptanceUnknown: false,
        reason: 'assistant_response_pending',
        diagnostics: ['assistant_response_pending'],
      },
    });

    const result = await store.getState().sendTeamMessage('my-team', {
      member: 'bob',
      text: 'hello',
    });

    expect(store.getState().lastSendMessageResult).toBe(result);
    expect(store.getState().sendMessageWarning).toBe(
      'OpenCode delivery is still being checked. Message was saved and will be observed before retry if needed.'
    );
    expect(store.getState().sendMessageDebugDetails).toMatchObject({
      messageId: 'm-opencode-pending',
      providerId: 'opencode',
      delivered: true,
      responsePending: true,
      responseState: 'pending',
      ledgerStatus: 'accepted',
      acceptanceUnknown: false,
      reason: 'assistant_response_pending',
      diagnostics: ['assistant_response_pending'],
    });
  });

  it('updates pending OpenCode runtime diagnostics when delivery becomes terminal', async () => {
    const store = createSliceStore();
    hoisted.sendMessage.mockResolvedValue({
      deliveredToInbox: true,
      messageId: 'm-opencode-pending',
      runtimeDelivery: {
        providerId: 'opencode',
        attempted: true,
        delivered: true,
        responsePending: true,
        responseState: 'pending',
        ledgerStatus: 'accepted',
        acceptanceUnknown: false,
        reason: 'assistant_response_pending',
        diagnostics: ['assistant_response_pending'],
      },
    });
    hoisted.getOpenCodeRuntimeDeliveryStatus.mockResolvedValue({
      messageId: 'm-opencode-pending',
      providerId: 'opencode',
      attempted: true,
      delivered: false,
      responsePending: false,
      responseState: 'empty_assistant_turn',
      ledgerStatus: 'failed_terminal',
      acceptanceUnknown: false,
      reason: 'empty_assistant_turn',
      diagnostics: ['empty_assistant_turn'],
    });

    await store.getState().sendTeamMessage('my-team', {
      member: 'bob',
      text: 'hello',
    });
    await store.getState().refreshSendMessageRuntimeDeliveryStatus('my-team', 'm-opencode-pending');

    expect(store.getState().sendMessageWarning).toBe(
      'OpenCode runtime delivery failed. Message was saved to inbox, but live delivery did not complete. Reason: OpenCode returned an empty assistant turn.'
    );
    expect(store.getState().sendMessageDebugDetails).toMatchObject({
      messageId: 'm-opencode-pending',
      delivered: false,
      responsePending: false,
      responseState: 'empty_assistant_turn',
      ledgerStatus: 'failed_terminal',
      reason: 'empty_assistant_turn',
      diagnostics: ['empty_assistant_turn'],
    });
  });

  it('checks the original message when queued blocker impact is no longer user-visible', async () => {
    const store = createSliceStore();
    hoisted.sendMessage.mockResolvedValue({
      deliveredToInbox: true,
      messageId: 'm-opencode-queued',
      runtimeDelivery: {
        providerId: 'opencode',
        attempted: true,
        delivered: true,
        responsePending: true,
        responseState: 'pending',
        ledgerStatus: 'accepted',
        queuedBehindMessageId: 'm-opencode-blocker',
        reason: 'opencode_delivery_response_pending',
        diagnostics: ['opencode_delivery_response_pending'],
        userVisibleImpact: {
          state: 'checking',
        },
      },
    });
    hoisted.getOpenCodeRuntimeDeliveryStatus
      .mockResolvedValueOnce({
        messageId: 'm-opencode-blocker',
        providerId: 'opencode',
        attempted: true,
        delivered: true,
        responsePending: true,
        responseState: 'responded_non_visible_tool',
        ledgerStatus: 'responded',
        acceptanceUnknown: false,
        reason: 'non_visible_tool_without_task_progress',
        diagnostics: ['non_visible_tool_without_task_progress'],
        userVisibleImpact: {
          state: 'none',
        },
      })
      .mockResolvedValueOnce({
        messageId: 'm-opencode-queued',
        providerId: 'opencode',
        attempted: true,
        delivered: false,
        responsePending: false,
        responseState: 'empty_assistant_turn',
        ledgerStatus: 'failed_terminal',
        acceptanceUnknown: false,
        reason: 'empty_assistant_turn',
        diagnostics: ['empty_assistant_turn'],
        userVisibleImpact: {
          state: 'error',
          reasonCode: 'backend_error',
          message: 'empty_assistant_turn',
        },
      });

    await store.getState().sendTeamMessage('my-team', {
      member: 'bob',
      text: 'hello',
    });
    await store.getState().refreshSendMessageRuntimeDeliveryStatus('my-team', {
      messageId: 'm-opencode-queued',
      statusMessageId: 'm-opencode-blocker',
    });

    expect(hoisted.getOpenCodeRuntimeDeliveryStatus).toHaveBeenNthCalledWith(
      1,
      'my-team',
      'm-opencode-blocker'
    );
    expect(hoisted.getOpenCodeRuntimeDeliveryStatus).toHaveBeenNthCalledWith(
      2,
      'my-team',
      'm-opencode-queued'
    );
    expect(store.getState().sendMessageWarning).toBe(
      'OpenCode runtime delivery failed. Message was saved to inbox, but live delivery did not complete. Reason: OpenCode returned an empty assistant turn.'
    );
    expect(store.getState().sendMessageDebugDetails).toMatchObject({
      messageId: 'm-opencode-queued',
      statusMessageId: 'm-opencode-queued',
      userVisibleState: 'error',
    });
  });

  it('clears OpenCode runtime diagnostics only for the matching message id', async () => {
    const store = createSliceStore();
    hoisted.sendMessage.mockResolvedValue({
      deliveredToInbox: true,
      messageId: 'm-opencode-pending',
      runtimeDelivery: {
        providerId: 'opencode',
        attempted: true,
        delivered: true,
        responsePending: true,
        responseState: 'pending',
        ledgerStatus: 'accepted',
        acceptanceUnknown: false,
        reason: 'assistant_response_pending',
        diagnostics: ['assistant_response_pending'],
      },
    });

    await store.getState().sendTeamMessage('my-team', {
      member: 'bob',
      text: 'hello',
    });

    store.getState().clearSendMessageRuntimeDiagnostics('other-message');
    expect(store.getState().sendMessageWarning).toBe(
      'OpenCode delivery is still being checked. Message was saved and will be observed before retry if needed.'
    );
    expect(store.getState().sendMessageDebugDetails?.messageId).toBe('m-opencode-pending');

    store.getState().clearSendMessageRuntimeDiagnostics('m-opencode-pending');
    expect(store.getState().sendMessageWarning).toBeNull();
    expect(store.getState().sendMessageDebugDetails).toBeNull();
  });

  it('clears OpenCode runtime diagnostics after normal success or send failure', async () => {
    const store = createSliceStore();
    hoisted.sendMessage
      .mockResolvedValueOnce({
        deliveredToInbox: true,
        messageId: 'm-opencode-failed',
        runtimeDelivery: {
          providerId: 'opencode',
          attempted: true,
          delivered: false,
          reason: 'runtime_unavailable',
        },
      })
      .mockResolvedValueOnce({
        deliveredToInbox: true,
        messageId: 'm-ok',
      })
      .mockRejectedValueOnce(new Error('boom'));

    await store.getState().sendTeamMessage('my-team', { member: 'bob', text: 'first' });
    expect(store.getState().sendMessageDebugDetails?.messageId).toBe('m-opencode-failed');

    await store.getState().sendTeamMessage('my-team', { member: 'alice', text: 'second' });
    expect(store.getState().sendMessageWarning).toBeNull();
    expect(store.getState().sendMessageDebugDetails).toBeNull();
    expect(store.getState().lastSendMessageResult?.messageId).toBe('m-ok');

    await expect(
      store.getState().sendTeamMessage('my-team', { member: 'alice', text: 'third' })
    ).rejects.toThrow('boom');
    expect(store.getState().sendMessageWarning).toBeNull();
    expect(store.getState().sendMessageDebugDetails).toBeNull();
    expect(store.getState().sendMessageError).toBe('boom');
  });

  it('maps task status verify failure in updateKanban and rethrows', async () => {
    const store = createSliceStore();
    hoisted.updateKanban.mockRejectedValue(new Error('Task status update verification failed: 12'));

    await expect(
      store.getState().updateKanban('my-team', '12', { op: 'request_changes' })
    ).rejects.toThrow('Task status update verification failed: 12');

    expect(store.getState().reviewActionError).toBe(
      'Failed to update task status (possible agent conflict).'
    );
  });

  it('maps task status verify failure in requestReview and rethrows', async () => {
    const store = createSliceStore();
    hoisted.requestReview.mockRejectedValue(
      new Error('Task status update verification failed: 22')
    );

    await expect(store.getState().requestReview('my-team', '22')).rejects.toThrow(
      'Task status update verification failed: 22'
    );
    expect(store.getState().reviewActionError).toBe(
      'Failed to update task status (possible agent conflict).'
    );
  });

  it('does not warm task-change summaries on team open', async () => {
    const store = createSliceStore();
    hoisted.getData.mockResolvedValue({
      teamName: 'my-team',
      config: { name: 'My Team' },
      tasks: [
        {
          id: 'completed-1',
          owner: 'alice',
          status: 'completed',
          createdAt: '2026-03-20T08:00:00.000Z',
          updatedAt: '2026-03-20T12:00:00.000Z',
        },
      ],
      members: [],
      messages: [],
      kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
    });

    await store.getState().selectTeam('my-team');

    expect(store.getState().warmTaskChangeSummaries).not.toHaveBeenCalled();
  });

  it('commits owner slot drops in the current session while persistence is disabled', () => {
    const store = createSliceStore();

    store
      .getState()
      .commitTeamGraphOwnerSlotDrop(
        'my-team',
        'agent-alice',
        { ringIndex: 0, sectorIndex: 2 },
        'agent-bob',
        { ringIndex: 0, sectorIndex: 1 }
      );

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 2 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
    });
    expect(store.getState().graphLayoutSessionByTeam['my-team']).toEqual({
      mode: 'manual',
      signature: null,
    });
  });

  it('stores non-default graph layout mode without mutating radial slot assignments', () => {
    const store = createSliceStore();
    store
      .getState()
      .commitTeamGraphOwnerSlotDrop('my-team', 'agent-alice', { ringIndex: 0, sectorIndex: 2 });

    store.getState().setTeamGraphLayoutMode('my-team', 'radial');

    expect(store.getState().graphLayoutModeByTeam['my-team']).toBe('radial');
    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 2 },
    });

    store.getState().setTeamGraphLayoutMode('my-team', 'grid-under-lead');

    expect(store.getState().graphLayoutModeByTeam['my-team']).toBe('grid-under-lead');
    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 2 },
    });
  });

  it('swaps grid owners from canonical visible order without mutating radial slots', () => {
    const store = createSliceStore();
    store.setState({
      teamDataCacheByName: {
        'my-team': createTeamSnapshot({
          config: {
            name: 'My Team',
            members: [
              { name: 'team-lead', agentId: 'lead-agent' },
              { name: 'alice', agentId: 'agent-alice' },
              { name: 'bob', agentId: 'agent-bob' },
              { name: 'tom', agentId: 'agent-tom' },
            ],
          },
          members: [
            { name: 'team-lead', agentId: 'lead-agent', agentType: 'team-lead' },
            { name: 'alice', agentId: 'agent-alice' },
            { name: 'bob', agentId: 'agent-bob' },
            { name: 'tom', agentId: 'agent-tom' },
          ],
        }),
      },
      slotAssignmentsByTeam: {
        'my-team': {
          'agent-alice': { ringIndex: 0, sectorIndex: 2 },
        },
      },
    });

    store.getState().swapTeamGraphGridOwners('my-team', 'agent-alice', 'agent-tom');

    expect(store.getState().gridOwnerOrderByTeam['my-team']).toEqual([
      'agent-tom',
      'agent-bob',
      'agent-alice',
    ]);
    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 2 },
    });
  });

  it('keeps grid owner order unchanged when radial slots are committed', () => {
    const store = createSliceStore();
    store.setState({
      gridOwnerOrderByTeam: {
        'my-team': ['agent-bob', 'agent-alice'],
      },
    });

    store
      .getState()
      .commitTeamGraphOwnerSlotDrop('my-team', 'agent-alice', { ringIndex: 0, sectorIndex: 2 });

    expect(store.getState().gridOwnerOrderByTeam['my-team']).toEqual(['agent-bob', 'agent-alice']);
  });

  it('replaces persisted slot assignments with defaults while persistence is disabled', () => {
    const store = createSliceStore();
    store.setState({
      slotLayoutVersion: 'stable-slots-v1',
      slotAssignmentsByTeam: {
        'my-team': {
          alice: { ringIndex: 0, sectorIndex: 3 },
        },
      },
    });

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
    ]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
    });
  });

  it('seeds first-open cardinal slot defaults for small visible teams with no saved placements', () => {
    const store = createSliceStore();

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
      { name: 'tom', agentId: 'agent-tom' },
      { name: 'jack', agentId: 'agent-jack' },
    ]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
      'agent-jack': { ringIndex: 0, sectorIndex: 2 },
      'agent-tom': { ringIndex: 0, sectorIndex: 3 },
    });
  });

  it('uses config member order instead of transient visible member array order for defaults', () => {
    const store = createSliceStore();

    store.getState().ensureTeamGraphSlotAssignments(
      'my-team',
      [
        { name: 'jack', agentId: 'agent-jack' },
        { name: 'tom', agentId: 'agent-tom' },
        { name: 'alice', agentId: 'agent-alice' },
        { name: 'bob', agentId: 'agent-bob' },
      ],
      [
        { name: 'alice', agentId: 'agent-alice' },
        { name: 'bob', agentId: 'agent-bob' },
        { name: 'tom', agentId: 'agent-tom' },
        { name: 'jack', agentId: 'agent-jack' },
      ]
    );

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
      'agent-tom': { ringIndex: 0, sectorIndex: 2 },
      'agent-jack': { ringIndex: 0, sectorIndex: 3 },
    });
  });

  it('ignores the lead member when deriving small-team cardinal defaults', () => {
    const store = createSliceStore();

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'team-lead', agentId: 'lead-id' },
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
      { name: 'tom', agentId: 'agent-tom' },
      { name: 'jack', agentId: 'agent-jack' },
    ]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
      'agent-jack': { ringIndex: 0, sectorIndex: 2 },
      'agent-tom': { ringIndex: 0, sectorIndex: 3 },
    });
  });

  it('drops hidden persisted slot assignments and reseeds visible members while persistence is disabled', () => {
    const store = createSliceStore();
    store.setState({
      slotLayoutVersion: 'stable-slots-v1',
      slotAssignmentsByTeam: {
        'my-team': {
          'agent-hidden': { ringIndex: 2, sectorIndex: 4 },
        },
      },
    });

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'hidden', agentId: 'agent-hidden', removedAt: '2026-04-16T08:00:00.000Z' },
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
    ]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
    });
  });

  it('resets stale slot assignments when slot layout version mismatches', () => {
    const store = createSliceStore();
    store.setState({
      slotLayoutVersion: 'legacy-layout-version',
      slotAssignmentsByTeam: {
        'other-team': {
          'agent-old': { ringIndex: 9, sectorIndex: 9 },
        },
        'my-team': {
          alice: { ringIndex: 0, sectorIndex: 1 },
        },
      },
    });

    store
      .getState()
      .ensureTeamGraphSlotAssignments('my-team', [{ name: 'alice', agentId: 'agent-alice' }]);

    expect(store.getState().slotLayoutVersion).toBe('stable-slots-v1');
    expect(store.getState().slotAssignmentsByTeam).toEqual({
      'my-team': {
        'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      },
    });
  });

  it('ignores hidden-member persisted slot assignments while persistence is disabled', () => {
    const store = createSliceStore();
    store.setState({
      slotLayoutVersion: 'stable-slots-v1',
      slotAssignmentsByTeam: {
        'my-team': {
          'agent-hidden': { ringIndex: 1, sectorIndex: 5 },
          'agent-visible': { ringIndex: 0, sectorIndex: 2 },
        },
      },
    });

    store
      .getState()
      .ensureTeamGraphSlotAssignments('my-team', [{ name: 'visible', agentId: 'agent-visible' }]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-visible': { ringIndex: 0, sectorIndex: 0 },
    });
  });

  it('reseeds defaults again while the team remains in default mode and visible owners change', () => {
    const store = createSliceStore();

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
    ]);

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
      { name: 'tom', agentId: 'agent-tom' },
      { name: 'jack', agentId: 'agent-jack' },
    ]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
      'agent-jack': { ringIndex: 0, sectorIndex: 2 },
      'agent-tom': { ringIndex: 0, sectorIndex: 3 },
    });
    expect(store.getState().graphLayoutSessionByTeam['my-team']).toEqual({
      mode: 'default',
      signature: 'agent-alice|agent-bob|agent-jack|agent-tom',
    });
  });

  it('does not reshuffle existing owners after the team enters manual mode', () => {
    const store = createSliceStore();

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
    ]);

    store.getState().setTeamGraphOwnerSlotAssignment('my-team', 'agent-alice', {
      ringIndex: 1,
      sectorIndex: 4,
    });

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
      { name: 'tom', agentId: 'agent-tom' },
      { name: 'jack', agentId: 'agent-jack' },
    ]);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 1, sectorIndex: 4 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
    });
    expect(store.getState().graphLayoutSessionByTeam['my-team']).toEqual({
      mode: 'manual',
      signature: 'agent-alice|agent-bob',
    });
  });

  it('normalizes legacy six-owner row-orbit slots before preserving manual layout', () => {
    const store = createSliceStore();
    const members = [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
      { name: 'tom', agentId: 'agent-tom' },
      { name: 'jack', agentId: 'agent-jack' },
      { name: 'nova', agentId: 'agent-nova' },
      { name: 'atlas', agentId: 'agent-atlas' },
    ];
    store.setState({
      slotAssignmentsByTeam: {
        'my-team': {
          'agent-alice': { ringIndex: 0, sectorIndex: 0 },
          'agent-atlas': { ringIndex: 0, sectorIndex: 1 },
          'agent-bob': { ringIndex: 0, sectorIndex: 2 },
          'agent-jack': { ringIndex: 1, sectorIndex: 0 },
          'agent-nova': { ringIndex: 1, sectorIndex: 1 },
          'agent-tom': { ringIndex: 1, sectorIndex: 2 },
        },
      },
      graphLayoutSessionByTeam: {
        'my-team': {
          mode: 'manual',
          signature: null,
        },
      },
    });

    store.getState().ensureTeamGraphSlotAssignments('my-team', members);

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-atlas': { ringIndex: 0, sectorIndex: 1 },
      'agent-bob': { ringIndex: 0, sectorIndex: 2 },
      'agent-jack': { ringIndex: 2, sectorIndex: 0 },
      'agent-nova': { ringIndex: 2, sectorIndex: 1 },
      'agent-tom': { ringIndex: 2, sectorIndex: 2 },
    });
    expect(store.getState().graphLayoutSessionByTeam['my-team']).toEqual({
      mode: 'manual',
      signature: null,
    });
  });

  it('resets graph slot assignments back to defaults when reopening the graph surface', () => {
    const store = createSliceStore();
    store.setState({
      teamDataCacheByName: {
        'my-team': {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [],
          members: [
            { name: 'alice', agentId: 'agent-alice' },
            { name: 'bob', agentId: 'agent-bob' },
            { name: 'tom', agentId: 'agent-tom' },
            { name: 'jack', agentId: 'agent-jack' },
          ],
          messages: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
          processes: [],
        },
      },
    });

    store.getState().ensureTeamGraphSlotAssignments('my-team', [
      { name: 'alice', agentId: 'agent-alice' },
      { name: 'bob', agentId: 'agent-bob' },
      { name: 'tom', agentId: 'agent-tom' },
      { name: 'jack', agentId: 'agent-jack' },
    ]);

    store
      .getState()
      .commitTeamGraphOwnerSlotDrop(
        'my-team',
        'agent-alice',
        { ringIndex: 0, sectorIndex: 2 },
        'agent-jack',
        { ringIndex: 0, sectorIndex: 0 }
      );

    store.getState().resetTeamGraphSlotAssignmentsToDefaults('my-team');

    expect(store.getState().slotAssignmentsByTeam['my-team']).toEqual({
      'agent-alice': { ringIndex: 0, sectorIndex: 0 },
      'agent-bob': { ringIndex: 0, sectorIndex: 1 },
      'agent-jack': { ringIndex: 0, sectorIndex: 2 },
      'agent-tom': { ringIndex: 0, sectorIndex: 3 },
    });
    expect(store.getState().graphLayoutSessionByTeam['my-team']).toEqual({
      mode: 'default',
      signature: 'agent-alice|agent-bob|agent-jack|agent-tom',
    });
  });

  it('syncs both team and graph tab labels when the team display name changes', async () => {
    const store = createSliceStore();
    const getAllPaneTabs = vi.fn(() => [
      { id: 'team-tab', type: 'team', teamName: 'my-team', label: 'my-team' },
      { id: 'graph-tab', type: 'graph', teamName: 'my-team', label: 'my-team Graph' },
    ]);
    const updateTabLabel = vi.fn();

    store.setState({
      getAllPaneTabs,
      updateTabLabel,
    });

    hoisted.getData.mockResolvedValue({
      teamName: 'my-team',
      config: { name: 'Northstar', members: [], projectPath: '/repo' },
      tasks: [],
      members: [],
      messages: [],
      kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
      processes: [],
    });

    await store.getState().selectTeam('my-team');

    expect(updateTabLabel).toHaveBeenCalledWith('team-tab', 'Northstar');
    expect(updateTabLabel).toHaveBeenCalledWith('graph-tab', 'Northstar Graph');
  });

  it('clears stale selectedTeamData immediately when selecting an uncached team', async () => {
    const store = createSliceStore();
    const nextTeamData = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();

    store.setState({
      selectedTeamName: 'alpha-team',
      selectedTeamData: createTeamSnapshot({
        teamName: 'alpha-team',
        config: { name: 'Alpha Team' },
      }),
    });

    hoisted.getData.mockImplementationOnce(async () => nextTeamData.promise);

    const selectPromise = store.getState().selectTeam('beta-team');

    expect(store.getState().selectedTeamName).toBe('beta-team');
    expect(store.getState().selectedTeamLoading).toBe(true);
    expect(store.getState().selectedTeamData).toBeNull();

    nextTeamData.resolve(
      createTeamSnapshot({
        teamName: 'beta-team',
        config: { name: 'Beta Team' },
      })
    );
    await selectPromise;

    expect(store.getState().selectedTeamData?.teamName).toBe('beta-team');
  });

  it('repoints selectedTeamData to the cached snapshot immediately on team switch', async () => {
    const store = createSliceStore();
    const nextTeamData = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const cachedBeta = createTeamSnapshot({
      teamName: 'beta-team',
      config: { name: 'Beta Team' },
    });

    store.setState({
      selectedTeamName: 'alpha-team',
      selectedTeamData: createTeamSnapshot({
        teamName: 'alpha-team',
        config: { name: 'Alpha Team' },
      }),
      teamDataCacheByName: {
        'beta-team': cachedBeta,
      },
    });

    hoisted.getData.mockImplementationOnce(async () => nextTeamData.promise);

    const selectPromise = store.getState().selectTeam('beta-team');

    expect(store.getState().selectedTeamName).toBe('beta-team');
    expect(store.getState().selectedTeamData).toBe(cachedBeta);

    nextTeamData.resolve(cachedBeta);
    await selectPromise;

    expect(store.getState().selectedTeamData).toBe(cachedBeta);
  });

  it('commits selectTeam thin snapshot before post-paint messages and activity meta refreshes', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const messagesRequest = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        messageId: string;
        source: 'inbox';
      }>;
      nextCursor: null;
      hasMore: false;
      feedRevision: string;
    }>();
    const metaRequest = createDeferredPromise<{
      teamName: string;
      computedAt: string;
      feedRevision: string;
      members: Record<string, never>;
    }>();
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });

    hoisted.getData.mockResolvedValueOnce(thinSnapshot);
    hoisted.getMessagesPage.mockImplementationOnce(() => messagesRequest.promise);
    hoisted.getMemberActivityMeta.mockImplementationOnce(() => metaRequest.promise);

    await store.getState().selectTeam('my-team');

    expect(hoisted.getData).toHaveBeenCalledWith('my-team', {
      includeMemberBranches: false,
    });
    expect(store.getState().selectedTeamLoading).toBe(false);
    expect(store.getState().selectedTeamData).toEqual(thinSnapshot);
    expect(hoisted.getMessagesPage).not.toHaveBeenCalled();
    expect(hoisted.getMemberActivityMeta).not.toHaveBeenCalled();

    await flushPostPaintTeamEnrichments();

    expect(hoisted.getMessagesPage).toHaveBeenCalledWith('my-team', { limit: 50 });
    expect(hoisted.getMemberActivityMeta).not.toHaveBeenCalled();

    messagesRequest.resolve({
      messages: [],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-thin',
    });
    await Promise.resolve();
    await Promise.resolve();
    await Promise.resolve();
    await Promise.resolve();
    await Promise.resolve();
    await Promise.resolve();

    expect(hoisted.getMemberActivityMeta).toHaveBeenCalledWith('my-team');

    metaRequest.resolve({
      teamName: 'my-team',
      computedAt: '2026-03-12T10:00:00.000Z',
      feedRevision: 'rev-thin',
      members: {},
    });
    await Promise.resolve();
    await Promise.resolve();

    expect(store.getState().selectedTeamData).toEqual(thinSnapshot);
    expect(store.getState().selectedTeamError).toBeNull();
  });

  it('keeps selected team data visible when post-paint message refresh fails', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });

    hoisted.getData.mockResolvedValueOnce(thinSnapshot);
    hoisted.getMessagesPage.mockRejectedValueOnce(new Error('message feed unavailable'));

    await store.getState().selectTeam('my-team');
    await flushPostPaintTeamEnrichments();
    await Promise.resolve();
    await Promise.resolve();

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(1);
    expect(store.getState().selectedTeamData).toEqual(thinSnapshot);
    expect(store.getState().selectedTeamError).toBeNull();
    expect(store.getState().teamMessagesByName['my-team']?.loadingHead).toBe(false);
  });

  it('queues a full team refresh behind an in-flight thin selectTeam snapshot', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
    });
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null, gitBranch: 'feature/a' }],
    });

    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockResolvedValueOnce(fullSnapshot);

    const selectPromise = store.getState().selectTeam('my-team');
    await Promise.resolve();

    await store.getState().refreshTeamData('my-team', { withDedup: true });

    expect(hoisted.getData).toHaveBeenCalledTimes(1);
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: true,
    });

    thinRequest.resolve(thinSnapshot);
    await selectPromise;

    expect(store.getState().selectedTeamData).toEqual(thinSnapshot);
    expect(hoisted.getData).toHaveBeenCalledTimes(1);

    await flushPostPaintTeamEnrichments();
    await Promise.resolve();
    await Promise.resolve();

    expect(hoisted.getData).toHaveBeenCalledTimes(2);
    expect(hoisted.getData.mock.calls[1]).toEqual(['my-team']);
    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
    });
  });

  it('drains queued full team refresh through the post-paint fallback when rAF never fires', async () => {
    vi.useFakeTimers();
    stubAnimationFrameNeverFires();
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();

    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockResolvedValueOnce(
        createTeamSnapshot({
          config: { name: 'Full Team After Fallback' },
        })
      );

    const selectPromise = store.getState().selectTeam('my-team');
    await Promise.resolve();
    await store.getState().refreshTeamData('my-team', { withDedup: true });

    thinRequest.resolve(createTeamSnapshot({ config: { name: 'Thin Team' } }));
    await selectPromise;

    await vi.advanceTimersByTimeAsync(499);
    expect(hoisted.getData).toHaveBeenCalledTimes(1);

    await vi.advanceTimersByTimeAsync(1);
    await Promise.resolve();
    await Promise.resolve();

    expect(hoisted.getData).toHaveBeenCalledTimes(2);
    expect(store.getState().selectedTeamData?.config.name).toBe('Full Team After Fallback');
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
      hasPostPaintTeamEnrichmentTimer: false,
    });
  });

  it('keeps selected team data visible when post-paint activity meta refresh fails', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });

    hoisted.getData.mockResolvedValueOnce(thinSnapshot);
    hoisted.getMessagesPage.mockResolvedValueOnce({
      messages: [
        {
          from: 'alice',
          text: 'Fresh message',
          timestamp: '2026-03-12T10:00:00.000Z',
          messageId: 'msg-fresh',
          source: 'inbox',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-meta-fail',
    });
    hoisted.getMemberActivityMeta.mockRejectedValueOnce(new Error('meta unavailable'));

    await store.getState().selectTeam('my-team');
    await flushPostPaintTeamEnrichments();
    await flushMicrotasks();

    expect(hoisted.getMemberActivityMeta).toHaveBeenCalledWith('my-team');
    expect(store.getState().selectedTeamData).toEqual(thinSnapshot);
    expect(store.getState().selectedTeamError).toBeNull();
  });

  it('does not share a forced full refresh request with an in-flight thin selectTeam request', async () => {
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const thinSnapshot = createTeamSnapshot({ config: { name: 'Thin Team' } });
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null, gitBranch: 'feature/a' }],
    });

    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockImplementationOnce(() => fullRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();

    const fullPromise = store.getState().refreshTeamData('my-team', { withDedup: false });

    expect(hoisted.getData).toHaveBeenCalledTimes(2);
    expect(hoisted.getData.mock.calls[0]).toEqual(['my-team', { includeMemberBranches: false }]);
    expect(hoisted.getData.mock.calls[1]).toEqual(['my-team']);

    thinRequest.resolve(thinSnapshot);
    await selectPromise;
    fullRequest.resolve(fullSnapshot);
    await fullPromise;

    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
  });

  it('does not let a late thin selectTeam snapshot clear members loaded by an earlier full refresh', async () => {
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [],
    });
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null, gitBranch: 'feature/a' }],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          members: [{ name: 'alice', role: 'developer' }],
          taskCount: 0,
          lastActivity: null,
        },
      },
    });
    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockImplementationOnce(() => fullRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();
    const fullPromise = store.getState().refreshTeamData('my-team', { withDedup: false });

    fullRequest.resolve(fullSnapshot);
    await fullPromise;
    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);

    thinRequest.resolve(thinSnapshot);
    await selectPromise;

    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(fullSnapshot);
    expect(selectResolvedMembersForTeamName(store.getState(), 'my-team')).toHaveLength(1);
  });

  it('does not let a late failed selectTeam request clear members loaded by a full refresh', async () => {
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null, gitBranch: 'feature/a' }],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          members: [{ name: 'alice', role: 'developer' }],
          taskCount: 0,
          lastActivity: null,
        },
      },
    });
    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockImplementationOnce(() => fullRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();
    const fullPromise = store.getState().refreshTeamData('my-team', { withDedup: false });

    fullRequest.resolve(fullSnapshot);
    await fullPromise;
    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(store.getState().selectedTeamLoading).toBe(true);

    thinRequest.reject(new Error('Timeout after 30000ms: team:getData(my-team,mode=thin)'));
    await selectPromise;

    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(fullSnapshot);
    expect(store.getState().selectedTeamLoading).toBe(false);
    expect(store.getState().selectedTeamError).toBeNull();
  });

  it('preserves an earlier full refresh even when the cached baseline had the same member names', async () => {
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [],
    });
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null, gitBranch: 'feature/a' }],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          members: [{ name: 'alice', role: 'developer' }],
          taskCount: 0,
          lastActivity: null,
        },
      },
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
    });
    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockImplementationOnce(() => fullRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();
    const fullPromise = store.getState().refreshTeamData('my-team', { withDedup: false });

    fullRequest.resolve(fullSnapshot);
    await fullPromise;
    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);

    thinRequest.resolve(thinSnapshot);
    await selectPromise;

    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(fullSnapshot);
  });

  it('does not let an empty selectTeam snapshot clear an already cached member roster', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null, gitBranch: 'feature/a' }],
    });
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          members: [{ name: 'alice', role: 'developer' }],
          taskCount: 0,
          lastActivity: null,
        },
      },
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
    });
    hoisted.getData.mockResolvedValueOnce(thinSnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(cachedSnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(cachedSnapshot);
    expect(selectResolvedMembersForTeamName(store.getState(), 'my-team')).toHaveLength(1);
  });

  it('does not treat a lead-only selectTeam snapshot as a confirmed teammate roster', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [
        { name: 'team-lead', agentType: 'team-lead', currentTaskId: null },
        { name: 'alice', role: 'developer', currentTaskId: null },
      ],
    });
    const leadOnlySnapshot = createTeamSnapshot({
      config: { name: 'Lead Only Thin Team' },
      members: [{ name: 'team-lead', agentType: 'team-lead', currentTaskId: null }],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          members: [{ name: 'alice', role: 'developer' }],
          taskCount: 0,
          lastActivity: null,
        },
      },
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
    });
    hoisted.getData.mockResolvedValueOnce(leadOnlySnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(cachedSnapshot);
    expect(
      selectResolvedMembersForTeamName(store.getState(), 'my-team').map((m) => m.name)
    ).toEqual(['team-lead', 'alice']);
  });

  it('uses summary fallback instead of a stale cached roster when names no longer match', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });
    const emptySnapshot = createTeamSnapshot({
      config: { name: 'Thin Team' },
      members: [],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          members: [{ name: 'bob', role: 'reviewer' }],
          taskCount: 0,
          lastActivity: null,
        },
      },
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
    });
    hoisted.getData.mockResolvedValueOnce(emptySnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(emptySnapshot);
    expect(selectResolvedMembersForTeamName(store.getState(), 'my-team')).toMatchObject([
      { name: 'bob', role: 'reviewer' },
    ]);
  });

  it('commits an empty selectTeam snapshot when the team summary is already solo', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });
    const soloSnapshot = createTeamSnapshot({
      config: { name: 'Solo Team' },
      members: [],
    });

    store.setState({
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'Solo Team',
          description: '',
          memberCount: 0,
          taskCount: 0,
          lastActivity: null,
        },
      },
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
    });
    hoisted.getData.mockResolvedValueOnce(soloSnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(soloSnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(soloSnapshot);
    expect(selectResolvedMembersForTeamName(store.getState(), 'my-team')).toHaveLength(0);
  });

  it('commits an empty cached-team selectTeam snapshot when no summary confirms teammates', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });
    const emptySnapshot = createTeamSnapshot({
      config: { name: 'Empty Team' },
      members: [],
    });

    store.setState({
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
      teamByName: {},
    });
    hoisted.getData.mockResolvedValueOnce(emptySnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(emptySnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(emptySnapshot);
  });

  it('does not preserve a cached roster from a summary count without member names', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });
    const emptySnapshot = createTeamSnapshot({
      config: { name: 'Empty Team' },
      members: [],
    });

    store.setState({
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          taskCount: 0,
          lastActivity: null,
        },
      },
    });
    hoisted.getData.mockResolvedValueOnce(emptySnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(emptySnapshot);
    expect(selectResolvedMembersForTeamName(store.getState(), 'my-team')).toHaveLength(0);
  });

  it('preserves a cached roster when full launch failure metadata confirms the member names', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [
        { name: 'alice', role: 'developer', currentTaskId: null },
        { name: 'bob', role: 'reviewer', currentTaskId: null },
      ],
    });
    const leadOnlySnapshot = createTeamSnapshot({
      config: { name: 'Lead Only Team' },
      members: [{ name: 'team-lead', agentType: 'team-lead', currentTaskId: null }],
    });

    store.setState({
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 0,
          expectedMemberCount: 2,
          partialLaunchFailure: true,
          missingMembers: ['alice', 'bob'],
          taskCount: 0,
          lastActivity: null,
        },
      },
    });
    hoisted.getData.mockResolvedValueOnce(leadOnlySnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(cachedSnapshot);
    expect(
      selectResolvedMembersForTeamName(store.getState(), 'my-team').map((member) => member.name)
    ).toEqual(['alice', 'bob']);
  });

  it('does not preserve a cached roster when launch failure metadata only names part of the team', async () => {
    const store = createSliceStore();
    const cachedSnapshot = createTeamSnapshot({
      config: { name: 'Cached Team' },
      members: [
        { name: 'alice', role: 'developer', currentTaskId: null },
        { name: 'bob', role: 'reviewer', currentTaskId: null },
      ],
    });
    const leadOnlySnapshot = createTeamSnapshot({
      config: { name: 'Lead Only Team' },
      members: [{ name: 'team-lead', agentType: 'team-lead', currentTaskId: null }],
    });

    store.setState({
      teamDataCacheByName: {
        'my-team': cachedSnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 0,
          expectedMemberCount: 2,
          partialLaunchFailure: true,
          missingMembers: ['bob'],
          taskCount: 0,
          lastActivity: null,
        },
      },
    });
    hoisted.getData.mockResolvedValueOnce(leadOnlySnapshot);

    await store.getState().selectTeam('my-team');

    expect(store.getState().selectedTeamData).toEqual(leadOnlySnapshot);
    expect(
      selectResolvedMembersForTeamName(store.getState(), 'my-team').map((member) => member.name)
    ).toEqual(['team-lead']);
  });

  it('commits a late selectTeam snapshot that explicitly marks members as removed', async () => {
    const store = createSliceStore();
    const selectRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const activeSnapshot = createTeamSnapshot({
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
    });
    const removedSnapshot = createTeamSnapshot({
      members: [
        { name: 'alice', role: 'developer', currentTaskId: null, removedAt: 1710000000000 },
      ],
    });

    hoisted.getData.mockImplementationOnce(() => selectRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: activeSnapshot,
      teamDataCacheByName: {
        'my-team': activeSnapshot,
      },
    });

    selectRequest.resolve(removedSnapshot);
    await selectPromise;

    expect(store.getState().selectedTeamData).toEqual(removedSnapshot);
    expect(store.getState().teamDataCacheByName['my-team']).toEqual(removedSnapshot);
  });

  it('still commits a late selectTeam snapshot when concurrent local state only changed tasks', async () => {
    const store = createSliceStore();
    const selectRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const previousSnapshot = createTeamSnapshot({
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
      tasks: [{ id: 'task-1', subject: 'Old task', status: 'pending', owner: 'alice' }],
    });
    const locallyPatchedSnapshot = createTeamSnapshot({
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
      tasks: [{ id: 'task-1', subject: 'Old task', status: 'pending', owner: 'alice' }],
    });
    const incomingSnapshot = createTeamSnapshot({
      config: { name: 'Server Team' },
      members: [
        { name: 'alice', role: 'developer', currentTaskId: null },
        { name: 'bob', role: 'reviewer', currentTaskId: null },
      ],
      tasks: [{ id: 'task-2', subject: 'Server task', status: 'pending', owner: 'bob' }],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: previousSnapshot,
      teamDataCacheByName: {
        'my-team': previousSnapshot,
      },
    });
    hoisted.getData.mockImplementationOnce(() => selectRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();

    store.setState({
      selectedTeamData: locallyPatchedSnapshot,
      teamDataCacheByName: {
        'my-team': locallyPatchedSnapshot,
      },
    });

    selectRequest.resolve(incomingSnapshot);
    await selectPromise;

    expect(store.getState().selectedTeamData).toMatchObject({
      config: { name: 'Server Team' },
      members: [{ name: 'alice' }, { name: 'bob' }],
    });
  });

  it('does not preserve a stale roster when concurrent local state only changed tasks', async () => {
    const store = createSliceStore();
    const selectRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const previousSnapshot = createTeamSnapshot({
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
      tasks: [{ id: 'task-1', subject: 'Old task', status: 'pending', owner: 'alice' }],
    });
    const locallyPatchedSnapshot = createTeamSnapshot({
      members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
      tasks: [{ id: 'task-1', subject: 'Locally changed task', status: 'pending', owner: 'alice' }],
    });
    const leadOnlySnapshot = createTeamSnapshot({
      config: { name: 'Solo Team' },
      members: [{ name: 'team-lead', agentType: 'team-lead', currentTaskId: null }],
      tasks: [],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: previousSnapshot,
      teamDataCacheByName: {
        'my-team': previousSnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'Solo Team',
          description: '',
          memberCount: 0,
          taskCount: 0,
          lastActivity: null,
        },
      },
    });
    hoisted.getData.mockImplementationOnce(() => selectRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();

    store.setState({
      selectedTeamData: locallyPatchedSnapshot,
      teamDataCacheByName: {
        'my-team': locallyPatchedSnapshot,
      },
    });

    selectRequest.resolve(leadOnlySnapshot);
    await selectPromise;

    expect(store.getState().selectedTeamData).toEqual(leadOnlySnapshot);
    expect(
      selectResolvedMembersForTeamName(store.getState(), 'my-team').map((m) => m.name)
    ).toEqual(['team-lead']);
  });

  it('keeps one queued full refresh for repeated fanout while thin selectTeam is pending', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full Team Once' },
    });

    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockResolvedValueOnce(fullSnapshot);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();

    await Promise.all([
      store.getState().refreshTeamData('my-team', { withDedup: true }),
      store.getState().refreshTeamData('my-team', { withDedup: true }),
    ]);

    expect(hoisted.getData).toHaveBeenCalledTimes(1);
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: true,
    });

    thinRequest.resolve(createTeamSnapshot({ config: { name: 'Thin Team' } }));
    await selectPromise;
    await flushPostPaintTeamEnrichments();
    await flushMicrotasks();

    expect(hoisted.getData).toHaveBeenCalledTimes(2);
    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
    });
  });

  it('drains queued full refresh when thin selectTeam becomes stale after switching teams', async () => {
    const store = createSliceStore();
    const alphaThin = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const alphaFull = createTeamSnapshot({
      teamName: 'alpha-team',
      config: { name: 'Alpha Full' },
    });

    hoisted.getData
      .mockImplementationOnce(() => alphaThin.promise)
      .mockResolvedValueOnce(
        createTeamSnapshot({ teamName: 'beta-team', config: { name: 'Beta' } })
      )
      .mockResolvedValueOnce(alphaFull);

    const alphaSelect = store.getState().selectTeam('alpha-team');
    await flushMicrotasks();
    await store.getState().refreshTeamData('alpha-team', { withDedup: true });

    expect(__getTeamScopedTransientStateForTests('alpha-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: true,
    });

    await store.getState().selectTeam('beta-team');

    alphaThin.resolve(
      createTeamSnapshot({ teamName: 'alpha-team', config: { name: 'Alpha Thin' } })
    );
    await alphaSelect;
    await flushAsyncWork();

    expect(hoisted.getData).toHaveBeenCalledTimes(3);
    expect(hoisted.getData.mock.calls[2]).toEqual(['alpha-team']);
    expect(store.getState().selectedTeamName).toBe('beta-team');
    expect(store.getState().teamDataCacheByName['alpha-team']).toEqual(alphaFull);
    expect(__getTeamScopedTransientStateForTests('alpha-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
    });
  });

  it('clears queued full refresh when thin selectTeam fails structurally', async () => {
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();

    hoisted.getData.mockImplementationOnce(() => thinRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();
    await store.getState().refreshTeamData('my-team', { withDedup: true });

    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: true,
    });

    thinRequest.reject(new Error('TEAM_DRAFT'));
    await selectPromise;

    expect(store.getState().selectedTeamError).toBe('TEAM_DRAFT');
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
    });
  });

  it('lets the newer same-team selectTeam drain queued full refresh after its own paint', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const fullSnapshot = createTeamSnapshot({
      config: { name: 'Full After Newer Paint' },
    });

    hoisted.getData
      .mockImplementationOnce(() => thinRequest.promise)
      .mockResolvedValueOnce(fullSnapshot);

    const firstSelect = store.getState().selectTeam('my-team');
    await flushMicrotasks();
    await store.getState().refreshTeamData('my-team', { withDedup: true });
    const secondSelect = store
      .getState()
      .selectTeam('my-team', { allowReloadWhileProvisioning: true });

    thinRequest.resolve(createTeamSnapshot({ config: { name: 'Thin Team' } }));
    await Promise.all([firstSelect, secondSelect]);

    expect(hoisted.getData).toHaveBeenCalledTimes(1);
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: true,
      hasPostPaintTeamEnrichmentTimer: true,
    });

    await flushPostPaintTeamEnrichments();
    await flushMicrotasks();

    expect(hoisted.getData).toHaveBeenCalledTimes(2);
    expect(hoisted.getData.mock.calls[1]).toEqual(['my-team']);
    expect(store.getState().selectedTeamData).toEqual(fullSnapshot);
    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
    });
  });

  it('does not run stale post-paint messages for a team after switching away', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();

    hoisted.getData
      .mockResolvedValueOnce(
        createTeamSnapshot({ teamName: 'alpha-team', config: { name: 'Alpha' } })
      )
      .mockResolvedValueOnce(
        createTeamSnapshot({ teamName: 'beta-team', config: { name: 'Beta' } })
      );

    await store.getState().selectTeam('alpha-team');
    expect(__getTeamScopedTransientStateForTests('alpha-team')).toMatchObject({
      hasPostPaintTeamEnrichmentTimer: true,
    });

    await store.getState().selectTeam('beta-team');
    await flushPostPaintTeamEnrichments();
    await flushMicrotasks();

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(1);
    expect(hoisted.getMessagesPage).toHaveBeenCalledWith('beta-team', { limit: 50 });
    expect(hoisted.getMessagesPage).not.toHaveBeenCalledWith('alpha-team', { limit: 50 });
  });

  it('clears queued full refresh and post-paint timer when deleting a loaded team', async () => {
    vi.useFakeTimers();
    stubAnimationFrameWithTimer();
    const store = createSliceStore();
    const thinRequest = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();

    hoisted.getData.mockImplementationOnce(() => thinRequest.promise);

    const selectPromise = store.getState().selectTeam('my-team');
    await flushMicrotasks();
    await store.getState().refreshTeamData('my-team', { withDedup: true });

    thinRequest.resolve(createTeamSnapshot({ config: { name: 'Thin Team' } }));
    await selectPromise;

    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: true,
      hasPostPaintTeamEnrichmentTimer: true,
    });

    await store.getState().deleteTeam('my-team');

    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasQueuedFullTeamDataRefreshAfterThin: false,
      hasPostPaintTeamEnrichmentTimer: false,
    });

    await flushPostPaintTeamEnrichments();

    expect(hoisted.getMessagesPage).not.toHaveBeenCalled();
    expect(hoisted.getData).toHaveBeenCalledTimes(1);
  });

  it('keeps selected team data visible when post-structural sync work throws', async () => {
    const store = createSliceStore();
    const thinSnapshot = createTeamSnapshot({
      config: { name: 'Renamed Team' },
    });
    const updateTabLabel = vi.fn(() => {
      throw new Error('tab label failed');
    });

    store.setState({
      getAllPaneTabs: vi.fn(() => [
        { id: 'tab-1', type: 'team', teamName: 'my-team', label: 'Old Team' },
      ]),
      updateTabLabel,
    });
    hoisted.getData.mockResolvedValueOnce(thinSnapshot);

    await store.getState().selectTeam('my-team');

    expect(updateTabLabel).toHaveBeenCalledWith('tab-1', 'Renamed Team');
    expect(store.getState().selectedTeamData).toEqual(thinSnapshot);
    expect(store.getState().selectedTeamError).toBeNull();
  });

  it('distinguishes historical feed changes from visible head changes in refreshTeamMessagesHead', async () => {
    const store = createSliceStore();
    const existingMessages = [
      {
        from: 'team-lead',
        text: 'Stable head',
        timestamp: '2026-03-20T08:00:00.000Z',
        read: true,
        source: 'lead_session',
        messageId: 'msg-1',
      },
    ];

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: existingMessages,
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: 'cursor-1',
          hasMore: true,
          lastFetchedAt: 123,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    hoisted.getMessagesPage.mockResolvedValueOnce({
      messages: existingMessages.map((message) => ({ ...message })),
      nextCursor: 'cursor-1',
      hasMore: true,
      feedRevision: 'rev-2',
    });

    const result = await store.getState().refreshTeamMessagesHead('my-team');
    const nextEntry = store.getState().teamMessagesByName['my-team'];

    expect(result).toEqual({
      feedChanged: true,
      headChanged: false,
      feedRevision: 'rev-2',
    });
    expect(nextEntry?.canonicalMessages).toBe(existingMessages);
    expect(nextEntry?.feedRevision).toBe('rev-2');
    expect(nextEntry?.nextCursor).toBe('cursor-1');
    expect(nextEntry?.hasMore).toBe(true);
  });

  it('keeps loaded older tail when head refresh updates only the visible top slice', async () => {
    const store = createSliceStore();
    const existingMessages = [
      {
        from: 'team-lead',
        text: 'Head 2',
        timestamp: '2026-03-20T08:00:03.000Z',
        read: true,
        source: 'lead_session',
        messageId: 'msg-4',
      },
      {
        from: 'alice',
        text: 'Head 1',
        timestamp: '2026-03-20T08:00:02.000Z',
        read: true,
        source: 'inbox',
        messageId: 'msg-3',
      },
      {
        from: 'bob',
        text: 'Older 1',
        timestamp: '2026-03-20T08:00:01.000Z',
        read: true,
        source: 'inbox',
        messageId: 'msg-2',
      },
      {
        from: 'carol',
        text: 'Older 2',
        timestamp: '2026-03-20T08:00:00.000Z',
        read: true,
        source: 'inbox',
        messageId: 'msg-1',
      },
    ];

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: existingMessages,
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: 'cursor-tail',
          hasMore: true,
          lastFetchedAt: 123,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    hoisted.getMessagesPage.mockResolvedValueOnce({
      messages: [
        {
          from: 'team-lead',
          text: 'Fresh head',
          timestamp: '2026-03-20T08:00:04.000Z',
          read: true,
          source: 'lead_session',
          messageId: 'msg-5',
        },
        existingMessages[0],
        existingMessages[1],
      ],
      nextCursor: 'cursor-head',
      hasMore: true,
      feedRevision: 'rev-2',
    });

    const result = await store.getState().refreshTeamMessagesHead('my-team');
    const nextEntry = store.getState().teamMessagesByName['my-team'];

    expect(result).toEqual({
      feedChanged: true,
      headChanged: true,
      feedRevision: 'rev-2',
    });
    expect(
      nextEntry?.canonicalMessages.map((message: { messageId?: string }) => message.messageId)
    ).toEqual(['msg-5', 'msg-4', 'msg-3', 'msg-2', 'msg-1']);
    expect(nextEntry?.nextCursor).toBe('cursor-tail');
    expect(nextEntry?.hasMore).toBe(true);
  });

  it('single-flights concurrent head refreshes and runs one fresh follow-up pass', async () => {
    const store = createSliceStore();
    const firstRequest = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        read: boolean;
        source: string;
        messageId: string;
      }>;
      nextCursor: string | null;
      hasMore: boolean;
      feedRevision: string;
    }>();

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [],
          optimisticMessages: [],
          feedRevision: null,
          nextCursor: null,
          hasMore: false,
          lastFetchedAt: 0,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: false,
        },
      },
    });

    hoisted.getMessagesPage
      .mockImplementationOnce(() => firstRequest.promise)
      .mockResolvedValueOnce({
        messages: [
          {
            from: 'team-lead',
            text: 'Newest head',
            timestamp: '2026-03-20T08:00:01.000Z',
            read: true,
            source: 'lead_session',
            messageId: 'msg-2',
          },
        ],
        nextCursor: 'cursor-2',
        hasMore: true,
        feedRevision: 'rev-2',
      });

    const p1 = store.getState().refreshTeamMessagesHead('my-team');
    const p2 = store.getState().refreshTeamMessagesHead('my-team');

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(1);

    firstRequest.resolve({
      messages: [
        {
          from: 'team-lead',
          text: 'Old head',
          timestamp: '2026-03-20T08:00:00.000Z',
          read: true,
          source: 'lead_session',
          messageId: 'msg-1',
        },
      ],
      nextCursor: 'cursor-1',
      hasMore: true,
      feedRevision: 'rev-1',
    });

    await p1;
    await p2;
    await Promise.resolve();
    await Promise.resolve();

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(2);
    expect(store.getState().teamMessagesByName['my-team']).toMatchObject({
      feedRevision: 'rev-2',
      nextCursor: 'cursor-2',
      hasMore: true,
      loadingHead: false,
      headHydrated: true,
    });
    expect(store.getState().teamMessagesByName['my-team']?.canonicalMessages[0]?.messageId).toBe(
      'msg-2'
    );
  });

  it('serializes head refresh behind an in-flight older-page load', async () => {
    const store = createSliceStore();
    const olderRequest = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        read: boolean;
        source: string;
        messageId: string;
      }>;
      nextCursor: string | null;
      hasMore: boolean;
      feedRevision: string;
    }>();

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [
            {
              from: 'team-lead',
              text: 'Head 1',
              timestamp: '2026-03-20T08:00:02.000Z',
              read: true,
              source: 'lead_session',
              messageId: 'msg-3',
            },
            {
              from: 'alice',
              text: 'Head 0',
              timestamp: '2026-03-20T08:00:01.000Z',
              read: true,
              source: 'inbox',
              messageId: 'msg-2',
            },
          ],
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: 'cursor-older',
          hasMore: true,
          lastFetchedAt: 123,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    hoisted.getMessagesPage
      .mockImplementationOnce(() => olderRequest.promise)
      .mockResolvedValueOnce({
        messages: [
          {
            from: 'team-lead',
            text: 'Fresh head',
            timestamp: '2026-03-20T08:00:03.000Z',
            read: true,
            source: 'lead_session',
            messageId: 'msg-4',
          },
          {
            from: 'team-lead',
            text: 'Head 1',
            timestamp: '2026-03-20T08:00:02.000Z',
            read: true,
            source: 'lead_session',
            messageId: 'msg-3',
          },
        ],
        nextCursor: 'cursor-head',
        hasMore: true,
        feedRevision: 'rev-2',
      });

    const olderPromise = store.getState().loadOlderTeamMessages('my-team');
    const headPromise = store.getState().refreshTeamMessagesHead('my-team');

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(1);
    expect(hoisted.getMessagesPage.mock.calls[0]).toEqual([
      'my-team',
      { cursor: 'cursor-older', limit: 50 },
    ]);

    olderRequest.resolve({
      messages: [
        {
          from: 'bob',
          text: 'Older tail',
          timestamp: '2026-03-20T08:00:00.000Z',
          read: true,
          source: 'inbox',
          messageId: 'msg-1',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-1',
    });

    await olderPromise;
    await headPromise;

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(2);
    expect(hoisted.getMessagesPage.mock.calls[1]).toEqual(['my-team', { limit: 50 }]);
    expect(
      store
        .getState()
        .teamMessagesByName[
          'my-team'
        ]?.canonicalMessages.map((message: { messageId?: string }) => message.messageId)
    ).toEqual(['msg-4', 'msg-3', 'msg-2', 'msg-1']);
  });

  it('drops a queued head refresh behind an older-page load when launch invalidates the team epoch', async () => {
    const store = createSliceStore();
    const olderRequest = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        read: boolean;
        source: string;
        messageId: string;
      }>;
      nextCursor: string | null;
      hasMore: boolean;
      feedRevision: string;
    }>();

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [
            {
              from: 'team-lead',
              text: 'Head 1',
              timestamp: '2026-03-20T08:00:02.000Z',
              read: true,
              source: 'lead_session',
              messageId: 'msg-3',
            },
            {
              from: 'alice',
              text: 'Head 0',
              timestamp: '2026-03-20T08:00:01.000Z',
              read: true,
              source: 'inbox',
              messageId: 'msg-2',
            },
          ],
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: 'cursor-older',
          hasMore: true,
          lastFetchedAt: 123,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    hoisted.getMessagesPage.mockImplementationOnce(() => olderRequest.promise);

    const olderPromise = store.getState().loadOlderTeamMessages('my-team');
    const queuedHeadPromise = store.getState().refreshTeamMessagesHead('my-team');

    await Promise.resolve();
    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    olderRequest.resolve({
      messages: [
        {
          from: 'bob',
          text: 'Older tail',
          timestamp: '2026-03-20T08:00:00.000Z',
          read: true,
          source: 'inbox',
          messageId: 'msg-1',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-1',
    });

    await olderPromise;
    await expect(queuedHeadPromise).resolves.toEqual({
      feedChanged: false,
      headChanged: false,
      feedRevision: null,
    });

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(1);
    expect(store.getState().teamMessagesByName['my-team']?.feedRevision).toBe('rev-1');
  });

  it('does not continue an older-page fetch with a stale cursor after launch invalidates while waiting for head refresh', async () => {
    const store = createSliceStore();
    const headRequest = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        read: boolean;
        source: string;
        messageId: string;
      }>;
      nextCursor: string | null;
      hasMore: boolean;
      feedRevision: string;
    }>();

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [
            {
              from: 'team-lead',
              text: 'Head 1',
              timestamp: '2026-03-20T08:00:02.000Z',
              read: true,
              source: 'lead_session',
              messageId: 'msg-3',
            },
          ],
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: 'cursor-older',
          hasMore: true,
          lastFetchedAt: 123,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    hoisted.getMessagesPage.mockImplementationOnce(() => headRequest.promise);

    const headPromise = store.getState().refreshTeamMessagesHead('my-team');
    const olderPromise = store.getState().loadOlderTeamMessages('my-team');

    await Promise.resolve();
    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    headRequest.resolve({
      messages: [
        {
          from: 'team-lead',
          text: 'Fresh head',
          timestamp: '2026-03-20T08:00:03.000Z',
          read: true,
          source: 'lead_session',
          messageId: 'msg-4',
        },
      ],
      nextCursor: 'cursor-head',
      hasMore: true,
      feedRevision: 'rev-2',
    });

    await headPromise;
    await olderPromise;

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(1);
    expect(store.getState().teamMessagesByName['my-team']?.feedRevision).toBe('rev-1');
    expect(store.getState().teamMessagesByName['my-team']?.loadingOlder).toBe(false);
  });

  it('schedules pending-reply refresh through store-owned timers', async () => {
    vi.useFakeTimers();
    try {
      const store = createSliceStore();
      const refreshTeamMessagesHeadSpy = vi
        .spyOn(store.getState(), 'refreshTeamMessagesHead')
        .mockResolvedValue({
          feedChanged: true,
          headChanged: true,
          feedRevision: 'rev-2',
        });
      const refreshMemberActivityMetaSpy = vi
        .spyOn(store.getState(), 'refreshMemberActivityMeta')
        .mockResolvedValue(undefined);

      store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-a', true, 1_000);

      await vi.advanceTimersByTimeAsync(999);
      expect(refreshTeamMessagesHeadSpy).not.toHaveBeenCalled();

      await vi.advanceTimersByTimeAsync(1);
      expect(refreshTeamMessagesHeadSpy).toHaveBeenCalledTimes(1);
      expect(refreshMemberActivityMetaSpy).toHaveBeenCalledTimes(1);

      store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-a', true, 1_000);
      store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-a', false);

      await vi.advanceTimersByTimeAsync(1_000);
      expect(refreshTeamMessagesHeadSpy).toHaveBeenCalledTimes(1);
    } finally {
      vi.useRealTimers();
    }
  });

  it('keeps pending-reply refresh ownership active while another source still waits for the same team', () => {
    const store = createSliceStore();

    store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-a', true, 1_000);
    store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-b', true, 1_000);
    store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-b', false);

    expect(hasActiveTeamPendingReplyWait('my-team')).toBe(true);
    expect(getActiveTeamPendingReplyWaits()).toEqual(new Set(['my-team']));

    store.getState().syncTeamPendingReplyRefresh('my-team', 'tab-a', false);

    expect(hasActiveTeamPendingReplyWait('my-team')).toBe(false);
    expect(getActiveTeamPendingReplyWaits().size).toBe(0);
  });

  it('single-flights concurrent member activity refreshes and re-fetches after feed revision changes', async () => {
    const store = createSliceStore();
    const firstRequest = createDeferredPromise<{
      teamName: string;
      computedAt: string;
      members: Record<string, unknown>;
      feedRevision: string;
    }>();

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [],
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: null,
          hasMore: false,
          lastFetchedAt: 0,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
      memberActivityMetaByTeam: {},
    });

    hoisted.getMemberActivityMeta
      .mockImplementationOnce(() => firstRequest.promise)
      .mockResolvedValueOnce({
        teamName: 'my-team',
        computedAt: '2026-03-12T10:00:01.000Z',
        members: {
          alice: {
            memberName: 'alice',
            lastAuthoredMessageAt: '2026-03-12T10:00:01.000Z',
            messageCountExact: 3,
            latestAuthoredMessageSignalsTermination: false,
          },
        },
        feedRevision: 'rev-2',
      });

    const p1 = store.getState().refreshMemberActivityMeta('my-team');

    store.setState((state: any) => ({
      teamMessagesByName: {
        ...state.teamMessagesByName,
        'my-team': {
          ...state.teamMessagesByName['my-team'],
          feedRevision: 'rev-2',
        },
      },
    }));

    const p2 = store.getState().refreshMemberActivityMeta('my-team');

    expect(hoisted.getMemberActivityMeta).toHaveBeenCalledTimes(1);

    firstRequest.resolve({
      teamName: 'my-team',
      computedAt: '2026-03-12T10:00:00.000Z',
      members: {
        alice: {
          memberName: 'alice',
          lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
          messageCountExact: 2,
          latestAuthoredMessageSignalsTermination: false,
        },
      },
      feedRevision: 'rev-1',
    });

    await p1;
    await p2;
    await Promise.resolve();
    await Promise.resolve();

    expect(hoisted.getMemberActivityMeta).toHaveBeenCalledTimes(2);
    expect(store.getState().memberActivityMetaByTeam['my-team']).toMatchObject({
      feedRevision: 'rev-2',
      members: {
        alice: {
          messageCountExact: 3,
        },
      },
    });
  });

  it('reuses member activity facts and resolved member refs when only meta wrapper fields change', async () => {
    const store = createSliceStore();
    const initialMetaMembers = {
      alice: {
        memberName: 'alice',
        lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
        messageCountExact: 2,
        latestAuthoredMessageSignalsTermination: false,
      },
    };

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: createTeamSnapshot({
        members: [
          {
            name: 'alice',
            currentTaskId: null,
            taskCount: 0,
          },
        ],
      }),
      teamDataCacheByName: {
        'my-team': createTeamSnapshot({
          members: [
            {
              name: 'alice',
              currentTaskId: null,
              taskCount: 0,
            },
          ],
        }),
      },
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [],
          optimisticMessages: [],
          feedRevision: 'rev-2',
          nextCursor: null,
          hasMore: false,
          lastFetchedAt: 0,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
      memberActivityMetaByTeam: {
        'my-team': {
          teamName: 'my-team',
          computedAt: '2026-03-12T10:00:00.000Z',
          members: initialMetaMembers,
          feedRevision: 'rev-1',
        },
      },
      leadActivityByTeam: {
        'my-team': 'active',
      },
      leadContextByTeam: {
        'my-team': {
          currentTokens: 12,
          contextWindow: 100,
          percent: 12,
          updatedAt: '2026-03-12T10:00:00.000Z',
        },
      },
      memberSpawnStatusesByTeam: {
        'my-team': {
          alice: createMemberSpawnStatus(),
        },
      },
      memberSpawnSnapshotsByTeam: {
        'my-team': createMemberSpawnSnapshot(),
      },
    });

    const initialResolvedMembers = selectResolvedMembersForTeamName(store.getState(), 'my-team');

    hoisted.getMemberActivityMeta.mockResolvedValueOnce({
      teamName: 'my-team',
      computedAt: '2026-03-12T10:00:05.000Z',
      members: {
        alice: {
          memberName: 'alice',
          lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
          messageCountExact: 2,
          latestAuthoredMessageSignalsTermination: false,
        },
      },
      feedRevision: 'rev-2',
    });

    await store.getState().refreshMemberActivityMeta('my-team');

    const nextMeta = store.getState().memberActivityMetaByTeam['my-team'];
    const nextResolvedMembers = selectResolvedMembersForTeamName(store.getState(), 'my-team');

    expect(nextMeta?.feedRevision).toBe('rev-2');
    expect(nextMeta?.members).toBe(initialMetaMembers);
    expect(nextResolvedMembers).toBe(initialResolvedMembers);
  });

  it('prefers selected team data over stale cached data for the active team', () => {
    const store = createSliceStore();
    const staleCachedData = createTeamSnapshot({
      members: [],
    });
    const freshSelectedData = createTeamSnapshot({
      members: [
        {
          name: 'alice',
          currentTaskId: null,
          taskCount: 0,
          color: 'blue',
        },
      ],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: freshSelectedData,
      teamDataCacheByName: {
        'my-team': staleCachedData,
      },
      memberActivityMetaByTeam: {},
    });

    expect(selectTeamDataForName(store.getState(), 'my-team')).toBe(freshSelectedData);
    expect(selectResolvedMembersForTeamName(store.getState(), 'my-team')).toHaveLength(1);
  });

  it('falls back to config roster when snapshot members are temporarily empty', () => {
    const store = createSliceStore();
    const partialSnapshot = createTeamSnapshot({
      config: {
        name: 'My Team',
        members: [
          { name: 'team-lead', agentType: 'team-lead', providerId: 'codex' },
          { name: 'alice', role: 'reviewer', providerId: 'anthropic', color: 'blue' },
          { name: 'bob', role: 'developer', providerId: 'opencode' },
        ],
      },
      members: [],
      tasks: [
        {
          id: 'task-1',
          subject: 'Review current diff',
          status: 'in_progress',
          owner: 'alice',
        },
      ],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: partialSnapshot,
      teamDataCacheByName: {
        'my-team': partialSnapshot,
      },
      memberActivityMetaByTeam: {},
    });

    const members = selectResolvedMembersForTeamName(store.getState(), 'my-team');

    expect(members.map((member) => member.name)).toEqual(['team-lead', 'alice', 'bob']);
    expect(members.find((member) => member.name === 'alice')).toMatchObject({
      role: 'reviewer',
      currentTaskId: 'task-1',
      taskCount: 1,
    });
    expect(selectResolvedMemberForTeamName(store.getState(), 'my-team', 'bob')).toMatchObject({
      name: 'bob',
      role: 'developer',
    });
  });

  it('falls back to team summary roster when detail snapshot temporarily has no members', () => {
    const store = createSliceStore();
    const partialSnapshot = createTeamSnapshot({
      config: {
        name: 'My Team',
        projectPath: '/repo',
      },
      members: [],
      tasks: [
        {
          id: 'task-1',
          subject: 'Build',
          status: 'in_progress',
          owner: 'alice',
        },
      ],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: partialSnapshot,
      teamDataCacheByName: {
        'my-team': partialSnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 2,
          taskCount: 1,
          lastActivity: null,
          leadName: 'team-lead',
          leadColor: 'purple',
          members: [
            { name: 'alice', role: 'developer', color: 'blue' },
            { name: 'bob', role: 'reviewer', color: 'green' },
          ],
        },
      },
      memberActivityMetaByTeam: {},
    });

    const members = selectResolvedMembersForTeamName(store.getState(), 'my-team');

    expect(members.map((member) => member.name)).toEqual(['team-lead', 'alice', 'bob']);
    expect(members.find((member) => member.name === 'alice')).toMatchObject({
      role: 'developer',
      currentTaskId: 'task-1',
      taskCount: 1,
    });
    expect(selectResolvedMemberForTeamName(store.getState(), 'my-team', 'bob')).toMatchObject({
      name: 'bob',
      role: 'reviewer',
    });
  });

  it('falls back to team summary roster when detail snapshot only has the synthetic lead', () => {
    const store = createSliceStore();
    const leadOnlySnapshot = createTeamSnapshot({
      config: {
        name: 'My Team',
        projectPath: '/repo',
      },
      members: [
        {
          name: 'team-lead',
          agentType: 'team-lead',
          currentTaskId: null,
          role: 'Lead from detail',
          color: 'purple',
        },
      ],
      tasks: [],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: leadOnlySnapshot,
      teamDataCacheByName: {
        'my-team': leadOnlySnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 1,
          taskCount: 0,
          lastActivity: null,
          members: [{ name: 'alice', role: 'developer', color: 'blue' }],
        },
      },
      memberActivityMetaByTeam: {},
    });

    const members = selectResolvedMembersForTeamName(store.getState(), 'my-team');

    expect(members.map((m) => m.name)).toEqual(['team-lead', 'alice']);
    expect(members[0]).toMatchObject({
      name: 'team-lead',
      role: 'Lead from detail',
      color: 'purple',
    });
  });

  it('does not synthesize member cards from launch failure names when summary roster is missing', () => {
    const store = createSliceStore();
    const leadOnlySnapshot = createTeamSnapshot({
      config: {
        name: 'My Team',
        projectPath: '/repo',
      },
      members: [
        {
          name: 'team-lead',
          agentType: 'team-lead',
          currentTaskId: null,
          role: 'Lead from detail',
          color: 'purple',
        },
      ],
      tasks: [
        {
          id: 'task-1',
          subject: 'Build',
          status: 'in_progress',
          owner: 'Alice',
        },
      ],
    });

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: leadOnlySnapshot,
      teamDataCacheByName: {
        'my-team': leadOnlySnapshot,
      },
      teamByName: {
        'my-team': {
          teamName: 'my-team',
          displayName: 'My Team',
          description: '',
          memberCount: 0,
          expectedMemberCount: 2,
          leadName: 'Lead',
          partialLaunchFailure: true,
          missingMembers: ['Lead', 'Alice', 'bob'],
          taskCount: 1,
          lastActivity: null,
        },
      },
      memberActivityMetaByTeam: {},
    });

    const members = selectResolvedMembersForTeamName(store.getState(), 'my-team');

    expect(members.map((m) => m.name)).toEqual(['team-lead']);
    expect(selectResolvedMemberForTeamName(store.getState(), 'my-team', 'Alice')).toBeNull();
  });

  it('memoizes team-scoped member messages selectors over the merged message feed', () => {
    const store = createSliceStore();
    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [
            {
              from: 'team-lead',
              to: 'alice',
              text: 'Ping Alice',
              summary: 'Ping Alice',
              timestamp: '2026-03-12T10:00:00.000Z',
              read: false,
              messageId: 'msg-1',
            },
            {
              from: 'team-lead',
              to: 'bob',
              text: 'Ping Bob',
              summary: 'Ping Bob',
              timestamp: '2026-03-12T10:00:01.000Z',
              read: false,
              messageId: 'msg-2',
            },
          ],
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: null,
          hasMore: false,
          lastFetchedAt: 0,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    const first = selectMemberMessagesForTeamMember(store.getState(), 'my-team', 'alice');
    const second = selectMemberMessagesForTeamMember(store.getState(), 'my-team', 'alice');

    expect(first).toBe(second);
    expect(first.map((message) => message.messageId)).toEqual(['msg-1']);

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [
            {
              from: 'team-lead',
              to: 'alice',
              text: 'Ping Alice',
              summary: 'Ping Alice',
              timestamp: '2026-03-12T10:00:00.000Z',
              read: false,
              messageId: 'msg-1',
            },
            {
              from: 'alice',
              to: 'team-lead',
              text: 'Reply from Alice',
              summary: 'Reply from Alice',
              timestamp: '2026-03-12T10:00:02.000Z',
              read: false,
              messageId: 'msg-3',
            },
          ],
          optimisticMessages: [],
          feedRevision: 'rev-2',
          nextCursor: null,
          hasMore: false,
          lastFetchedAt: 1,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    const third = selectMemberMessagesForTeamMember(store.getState(), 'my-team', 'alice');
    expect(third).not.toBe(first);
    expect(third.map((message) => message.messageId)).toEqual(['msg-3', 'msg-1']);
  });

  it('removes non-selected team cache entries on permanent delete', async () => {
    const store = createSliceStore();
    store.setState({
      selectedTeamName: 'other-team',
      selectedTeamData: {
        teamName: 'other-team',
        config: { name: 'Other Team' },
        tasks: [],
        members: [],
        kanbanState: { teamName: 'other-team', reviewers: [], tasks: {} },
        processes: [],
      },
      teamDataCacheByName: {
        'my-team': {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
          processes: [],
        },
        'other-team': {
          teamName: 'other-team',
          config: { name: 'Other Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'other-team', reviewers: [], tasks: {} },
          processes: [],
        },
      },
    });

    await store.getState().permanentlyDeleteTeam('my-team');

    expect(hoisted.permanentlyDeleteTeam).toHaveBeenCalledWith('my-team');
    expect(store.getState().teamDataCacheByName['my-team']).toBeUndefined();
    expect(store.getState().teamDataCacheByName['other-team']).toBeDefined();
  });

  it('clears selected team state and cache on soft delete', async () => {
    const store = createSliceStore();
    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: {
        teamName: 'my-team',
        config: { name: 'My Team' },
        tasks: [],
        members: [],
        kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
        processes: [],
      },
      teamDataCacheByName: {
        'my-team': {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
          processes: [],
        },
      },
    });

    await store.getState().deleteTeam('my-team');

    expect(hoisted.deleteTeam).toHaveBeenCalledWith('my-team');
    expect(store.getState().selectedTeamName).toBeNull();
    expect(store.getState().selectedTeamData).toBeNull();
    expect(store.getState().teamDataCacheByName['my-team']).toBeUndefined();
  });

  it('drops stale cache on restore so the next open refetches fresh data', async () => {
    const store = createSliceStore();
    store.setState({
      teamDataCacheByName: {
        'my-team': {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
          processes: [],
        },
      },
    });

    await store.getState().restoreTeam('my-team');

    expect(hoisted.restoreTeam).toHaveBeenCalledWith('my-team');
    expect(store.getState().teamDataCacheByName['my-team']).toBeUndefined();
  });

  it('clears team-scoped selector and transient caches on delete and restore flows', async () => {
    const store = createSliceStore();
    const message = {
      from: 'alice',
      to: 'team-lead',
      text: 'hello',
      timestamp: '2026-03-12T10:00:00.000Z',
      messageId: 'm-1',
      source: 'inbox' as const,
    };

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: createTeamSnapshot({
        members: [
          {
            name: 'alice',
            role: 'developer',
            currentTaskId: null,
          },
        ],
      }),
      teamDataCacheByName: {
        'my-team': createTeamSnapshot({
          members: [
            {
              name: 'alice',
              role: 'developer',
              currentTaskId: null,
            },
          ],
        }),
      },
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [message],
          optimisticMessages: [],
          nextCursor: null,
          hasMore: false,
          feedRevision: 'rev-1',
          lastFetchedAt: Date.now(),
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
      memberActivityMetaByTeam: {
        'my-team': {
          teamName: 'my-team',
          computedAt: '2026-03-12T10:00:00.000Z',
          feedRevision: 'rev-1',
          members: {
            alice: {
              memberName: 'alice',
              lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
              messageCountExact: 1,
              latestAuthoredMessageSignalsTermination: false,
            },
          },
        },
      },
    });

    selectResolvedMembersForTeamName(store.getState(), 'my-team');
    selectResolvedMemberForTeamName(store.getState(), 'my-team', 'alice');
    selectMemberMessagesForTeamMember(store.getState(), 'my-team', 'alice');

    await store.getState().refreshTeamData('my-team', { withDedup: false });
    store.getState().syncTeamPendingReplyRefresh('my-team', 'test-source', true);

    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasResolvedMembersSelector: true,
      resolvedMemberSelectorCount: 1,
      hasMergedMessagesSelector: true,
      memberMessagesSelectorCount: 1,
      hasLastResolvedTeamDataRefresh: true,
    });

    await store.getState().deleteTeam('my-team');

    expect(__getTeamScopedTransientStateForTests('my-team')).toEqual({
      hasResolvedMembersSelector: false,
      resolvedMemberSelectorCount: 0,
      hasMergedMessagesSelector: false,
      memberMessagesSelectorCount: 0,
      hasPendingFreshTeamDataRefresh: false,
      hasQueuedFullTeamDataRefreshAfterThin: false,
      hasPostPaintTeamEnrichmentTimer: false,
      hasQueuedHeadRefreshAfterOlder: false,
      hasPendingFreshMessagesHeadRefresh: false,
      hasPendingFreshMemberActivityMetaRefresh: false,
      hasLastResolvedTeamDataRefresh: false,
      hasCurrentLocalStateEpoch: true,
      hasMemberSpawnStatusesIpcBackoff: false,
      hasTeamRefreshBurstDiagnostics: false,
      hasMemberSpawnUiEqualLastWarn: false,
    });
    expect(store.getState().leadActivityByTeam['my-team']).toBeUndefined();
    expect(store.getState().leadContextByTeam['my-team']).toBeUndefined();
    expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
    expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBeUndefined();

    store.setState({
      teamDataCacheByName: {
        'my-team': createTeamSnapshot({
          members: [
            {
              name: 'alice',
              role: 'developer',
              currentTaskId: null,
            },
          ],
        }),
      },
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [message],
          optimisticMessages: [],
          nextCursor: null,
          hasMore: false,
          feedRevision: 'rev-1',
          lastFetchedAt: Date.now(),
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
      memberActivityMetaByTeam: {
        'my-team': {
          teamName: 'my-team',
          computedAt: '2026-03-12T10:00:00.000Z',
          feedRevision: 'rev-1',
          members: {
            alice: {
              memberName: 'alice',
              lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
              messageCountExact: 1,
              latestAuthoredMessageSignalsTermination: false,
            },
          },
        },
      },
      leadActivityByTeam: {
        'my-team': 'active',
      },
      leadContextByTeam: {
        'my-team': {
          currentTokens: 12,
          contextWindow: 100,
          percent: 12,
          updatedAt: '2026-03-12T10:00:00.000Z',
        },
      },
      memberSpawnStatusesByTeam: {
        'my-team': {
          alice: createMemberSpawnStatus(),
        },
      },
      memberSpawnSnapshotsByTeam: {
        'my-team': createMemberSpawnSnapshot(),
      },
    });
    selectResolvedMembersForTeamName(store.getState(), 'my-team');
    selectResolvedMemberForTeamName(store.getState(), 'my-team', 'alice');
    selectMemberMessagesForTeamMember(store.getState(), 'my-team', 'alice');

    expect(__getTeamScopedTransientStateForTests('my-team')).toMatchObject({
      hasResolvedMembersSelector: true,
      resolvedMemberSelectorCount: 1,
      hasMergedMessagesSelector: true,
      memberMessagesSelectorCount: 1,
    });

    await store.getState().restoreTeam('my-team');

    expect(__getTeamScopedTransientStateForTests('my-team')).toEqual({
      hasResolvedMembersSelector: false,
      resolvedMemberSelectorCount: 0,
      hasMergedMessagesSelector: false,
      memberMessagesSelectorCount: 0,
      hasPendingFreshTeamDataRefresh: false,
      hasQueuedFullTeamDataRefreshAfterThin: false,
      hasPostPaintTeamEnrichmentTimer: false,
      hasQueuedHeadRefreshAfterOlder: false,
      hasPendingFreshMessagesHeadRefresh: false,
      hasPendingFreshMemberActivityMetaRefresh: false,
      hasLastResolvedTeamDataRefresh: false,
      hasCurrentLocalStateEpoch: true,
      hasMemberSpawnStatusesIpcBackoff: false,
      hasTeamRefreshBurstDiagnostics: false,
      hasMemberSpawnUiEqualLastWarn: false,
    });
    expect(store.getState().leadActivityByTeam['my-team']).toBeUndefined();
    expect(store.getState().leadContextByTeam['my-team']).toBeUndefined();
    expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
    expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBeUndefined();
  });

  it('ignores stale async team snapshot and message refreshes after delete invalidates the team', async () => {
    const store = createSliceStore();
    const deferredData = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const deferredMessages = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        messageId: string;
        source: 'inbox';
      }>;
      nextCursor: null;
      hasMore: false;
      feedRevision: string;
    }>();
    const deferredMeta = createDeferredPromise<{
      teamName: string;
      computedAt: string;
      feedRevision: string;
      members: Record<
        string,
        {
          memberName: string;
          lastAuthoredMessageAt: string | null;
          messageCountExact: number;
          latestAuthoredMessageSignalsTermination: boolean;
        }
      >;
    }>();

    hoisted.getData.mockImplementation(() => deferredData.promise);
    hoisted.getMessagesPage.mockImplementation(() => deferredMessages.promise);
    hoisted.getMemberActivityMeta.mockImplementation(() => deferredMeta.promise);

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [],
          optimisticMessages: [],
          nextCursor: null,
          hasMore: false,
          feedRevision: 'rev-0',
          lastFetchedAt: Date.now(),
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    const refreshDataPromise = store.getState().refreshTeamData('my-team', { withDedup: false });
    const refreshMessagesPromise = store.getState().refreshTeamMessagesHead('my-team');
    const refreshMetaPromise = store.getState().refreshMemberActivityMeta('my-team');

    await Promise.resolve();
    await store.getState().deleteTeam('my-team');

    deferredData.resolve(
      createTeamSnapshot({
        members: [{ name: 'alice', role: 'developer', currentTaskId: null }],
      })
    );
    deferredMessages.resolve({
      messages: [
        {
          from: 'alice',
          text: 'late-message',
          timestamp: '2026-03-12T10:00:00.000Z',
          messageId: 'late-1',
          source: 'inbox',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-late',
    });
    deferredMeta.resolve({
      teamName: 'my-team',
      computedAt: '2026-03-12T10:00:00.000Z',
      feedRevision: 'rev-late',
      members: {
        alice: {
          memberName: 'alice',
          lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
          messageCountExact: 1,
          latestAuthoredMessageSignalsTermination: false,
        },
      },
    });

    await Promise.all([refreshDataPromise, refreshMessagesPromise, refreshMetaPromise]);

    expect(store.getState().teamDataCacheByName['my-team']).toBeUndefined();
    expect(store.getState().teamMessagesByName['my-team']).toBeUndefined();
    expect(store.getState().memberActivityMetaByTeam['my-team']).toBeUndefined();
  });

  it('ignores stale async team refreshes after launch starts a new local epoch for the same team', async () => {
    const store = createSliceStore();
    const existingData = createTeamSnapshot({
      config: { name: 'My Team Before Launch' },
      members: [{ name: 'lead', role: 'lead', currentTaskId: null }],
    });
    const existingMeta: {
      teamName: string;
      computedAt: string;
      feedRevision: string;
      members: Record<
        string,
        {
          memberName: string;
          lastAuthoredMessageAt: string | null;
          messageCountExact: number;
          latestAuthoredMessageSignalsTermination: boolean;
        }
      >;
    } = {
      teamName: 'my-team',
      computedAt: '2026-03-12T09:59:00.000Z',
      feedRevision: 'rev-0',
      members: {
        lead: {
          memberName: 'lead',
          lastAuthoredMessageAt: '2026-03-12T09:59:00.000Z',
          messageCountExact: 1,
          latestAuthoredMessageSignalsTermination: false,
        },
      },
    };
    const deferredData = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const deferredMessages = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        messageId: string;
        source: 'inbox';
      }>;
      nextCursor: null;
      hasMore: false;
      feedRevision: string;
    }>();
    const deferredMeta = createDeferredPromise<typeof existingMeta>();

    hoisted.getData.mockImplementation(() => deferredData.promise);
    hoisted.getMessagesPage.mockImplementation(() => deferredMessages.promise);
    hoisted.getMemberActivityMeta.mockImplementation(() => deferredMeta.promise);

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: existingData,
      teamDataCacheByName: {
        'my-team': existingData,
      },
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [],
          optimisticMessages: [],
          nextCursor: null,
          hasMore: false,
          feedRevision: 'rev-0',
          lastFetchedAt: Date.now(),
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
      memberActivityMetaByTeam: {
        'my-team': existingMeta,
      },
    });

    const refreshDataPromise = store.getState().refreshTeamData('my-team', { withDedup: false });
    const refreshMessagesPromise = store.getState().refreshTeamMessagesHead('my-team');
    const refreshMetaPromise = store.getState().refreshMemberActivityMeta('my-team');

    await Promise.resolve();
    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    expect(store.getState().teamMessagesByName['my-team']?.loadingHead).toBe(false);

    deferredData.resolve(
      createTeamSnapshot({
        config: { name: 'My Team Stale After Launch' },
        members: [{ name: 'alice', role: 'reviewer', currentTaskId: null }],
      })
    );
    deferredMessages.resolve({
      messages: [
        {
          from: 'alice',
          text: 'stale-after-launch',
          timestamp: '2026-03-12T10:00:00.000Z',
          messageId: 'stale-after-launch-1',
          source: 'inbox',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-stale-after-launch',
    });
    deferredMeta.resolve({
      teamName: 'my-team',
      computedAt: '2026-03-12T10:00:00.000Z',
      feedRevision: 'rev-stale-after-launch',
      members: {
        alice: {
          memberName: 'alice',
          lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
          messageCountExact: 3,
          latestAuthoredMessageSignalsTermination: false,
        },
      },
    });

    await Promise.all([refreshDataPromise, refreshMessagesPromise, refreshMetaPromise]);

    expect(store.getState().selectedTeamData).toBe(existingData);
    expect(store.getState().teamDataCacheByName['my-team']).toBe(existingData);
    expect(store.getState().teamMessagesByName['my-team']?.feedRevision).toBe('rev-0');
    expect(store.getState().memberActivityMetaByTeam['my-team']).toEqual(existingMeta);
  });

  it('clears stale selectedTeamLoading when launch invalidates an in-flight selectTeam request', async () => {
    const store = createSliceStore();
    const existingData = createTeamSnapshot({
      config: { name: 'My Team Cached' },
      members: [{ name: 'lead', role: 'lead', currentTaskId: null }],
    });
    const deferredData = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();

    hoisted.getData.mockImplementationOnce(() => deferredData.promise);

    store.setState({
      teamDataCacheByName: {
        'my-team': existingData,
      },
    });

    const selectPromise = store.getState().selectTeam('my-team');
    await Promise.resolve();

    expect(store.getState().selectedTeamLoading).toBe(true);
    expect(store.getState().selectedTeamData).toEqual(existingData);

    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    expect(store.getState().selectedTeamLoading).toBe(false);
    expect(store.getState().selectedTeamError).toBeNull();
    expect(store.getState().selectedTeamData).toEqual(existingData);

    deferredData.resolve(
      createTeamSnapshot({
        config: { name: 'My Team Stale Select' },
        members: [{ name: 'alice', role: 'reviewer', currentTaskId: null }],
      })
    );
    await selectPromise;

    expect(store.getState().selectedTeamLoading).toBe(false);
    expect(store.getState().selectedTeamData).toEqual(existingData);
  });

  it('clears stale loadingOlder when launch invalidates an in-flight older messages request', async () => {
    const store = createSliceStore();
    const olderRequest = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        read: boolean;
        source: string;
        messageId: string;
      }>;
      nextCursor: string | null;
      hasMore: boolean;
      feedRevision: string;
    }>();

    store.setState({
      teamMessagesByName: {
        'my-team': {
          canonicalMessages: [],
          optimisticMessages: [],
          feedRevision: 'rev-1',
          nextCursor: 'cursor-older',
          hasMore: true,
          lastFetchedAt: 123,
          loadingHead: false,
          loadingOlder: false,
          headHydrated: true,
        },
      },
    });

    hoisted.getMessagesPage.mockImplementationOnce(() => olderRequest.promise);

    const olderPromise = store.getState().loadOlderTeamMessages('my-team');
    await Promise.resolve();
    expect(store.getState().teamMessagesByName['my-team']?.loadingOlder).toBe(true);

    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    expect(store.getState().teamMessagesByName['my-team']?.loadingOlder).toBe(false);

    olderRequest.resolve({
      messages: [
        {
          from: 'bob',
          text: 'Older tail',
          timestamp: '2026-03-20T08:00:00.000Z',
          read: true,
          source: 'inbox',
          messageId: 'msg-1',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-1',
    });

    await olderPromise;
    expect(store.getState().teamMessagesByName['my-team']?.loadingOlder).toBe(false);
  });

  it('ignores stale refreshTeamData failures after launch starts a new local epoch', async () => {
    const store = createSliceStore();
    const existingData = createTeamSnapshot({
      config: { name: 'My Team Stable' },
      members: [{ name: 'lead', role: 'lead', currentTaskId: null }],
    });
    const deferredData = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();

    hoisted.getData.mockImplementation(() => deferredData.promise);

    store.setState({
      selectedTeamName: 'my-team',
      selectedTeamData: existingData,
      teamDataCacheByName: {
        'my-team': existingData,
      },
      selectedTeamError: null,
    });

    const refreshPromise = store.getState().refreshTeamData('my-team', { withDedup: false });
    await Promise.resolve();
    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    deferredData.reject(new Error('TEAM_DRAFT'));
    await refreshPromise;

    expect(store.getState().selectedTeamData).toBe(existingData);
    expect(store.getState().teamDataCacheByName['my-team']).toBe(existingData);
    expect(store.getState().selectedTeamError).toBeNull();
  });

  it('keeps the newer messages-head request pinned when a stale pre-launch request settles', async () => {
    const store = createSliceStore();
    const deferredOld = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        messageId: string;
        source: 'inbox';
      }>;
      nextCursor: null;
      hasMore: false;
      feedRevision: string;
    }>();
    const deferredNew = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        messageId: string;
        source: 'inbox';
      }>;
      nextCursor: null;
      hasMore: false;
      feedRevision: string;
    }>();

    hoisted.getMessagesPage
      .mockImplementationOnce(() => deferredOld.promise)
      .mockImplementationOnce(() => deferredNew.promise);

    const firstPromise = store.getState().refreshTeamMessagesHead('my-team');
    await Promise.resolve();
    await store.getState().launchTeam({
      teamName: 'my-team',
      cwd: '/tmp/project',
    });

    const secondPromise = store.getState().refreshTeamMessagesHead('my-team');
    await Promise.resolve();

    deferredOld.reject(new Error('stale head failed'));
    await expect(firstPromise).resolves.toEqual({
      feedChanged: false,
      headChanged: false,
      feedRevision: null,
    });

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(2);

    deferredNew.resolve({
      messages: [
        {
          from: 'bob',
          text: 'fresh-after-launch',
          timestamp: '2026-03-12T10:00:01.000Z',
          messageId: 'fresh-after-launch-1',
          source: 'inbox',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-fresh-after-launch',
    });

    await secondPromise;

    expect(store.getState().teamMessagesByName['my-team']?.feedRevision).toBe(
      'rev-fresh-after-launch'
    );
  });

  it('does not reuse a pre-delete in-flight team snapshot request after the same team is reselected', async () => {
    const store = createSliceStore();
    const deferredOld = createDeferredPromise<ReturnType<typeof createTeamSnapshot>>();
    const freshSnapshot = createTeamSnapshot({
      config: { name: 'My Team Reloaded' },
      members: [{ name: 'bob', role: 'developer', currentTaskId: null }],
    });

    hoisted.getData
      .mockImplementationOnce(() => deferredOld.promise)
      .mockResolvedValueOnce(freshSnapshot);

    const firstSelectPromise = store.getState().selectTeam('my-team');
    await Promise.resolve();
    await store.getState().deleteTeam('my-team');

    const secondSelectPromise = store.getState().selectTeam('my-team');
    await secondSelectPromise;

    expect(hoisted.getData).toHaveBeenCalledTimes(2);
    expect(store.getState().selectedTeamData).toEqual(freshSnapshot);

    deferredOld.resolve(
      createTeamSnapshot({
        config: { name: 'My Team Stale' },
        members: [{ name: 'alice', role: 'reviewer', currentTaskId: null }],
      })
    );
    await firstSelectPromise;

    expect(store.getState().selectedTeamData).toEqual(freshSnapshot);
  });

  it('does not reuse a pre-delete in-flight messages head request after the same team is reselected', async () => {
    const store = createSliceStore();
    const deferredOld = createDeferredPromise<{
      messages: Array<{
        from: string;
        text: string;
        timestamp: string;
        messageId: string;
        source: 'inbox';
      }>;
      nextCursor: null;
      hasMore: false;
      feedRevision: string;
    }>();

    hoisted.getMessagesPage
      .mockImplementationOnce(() => deferredOld.promise)
      .mockResolvedValueOnce({
        messages: [
          {
            from: 'bob',
            text: 'fresh-message',
            timestamp: '2026-03-12T10:00:01.000Z',
            messageId: 'fresh-1',
            source: 'inbox',
          },
        ],
        nextCursor: null,
        hasMore: false,
        feedRevision: 'rev-fresh',
      });

    const firstHeadPromise = store.getState().refreshTeamMessagesHead('my-team');
    await Promise.resolve();
    await store.getState().deleteTeam('my-team');

    const secondHeadPromise = store.getState().refreshTeamMessagesHead('my-team');
    await secondHeadPromise;

    expect(hoisted.getMessagesPage).toHaveBeenCalledTimes(2);
    expect(store.getState().teamMessagesByName['my-team']?.feedRevision).toBe('rev-fresh');
    expect(store.getState().teamMessagesByName['my-team']?.canonicalMessages).toEqual([
      {
        from: 'bob',
        text: 'fresh-message',
        timestamp: '2026-03-12T10:00:01.000Z',
        messageId: 'fresh-1',
        source: 'inbox',
      },
    ]);

    deferredOld.resolve({
      messages: [
        {
          from: 'alice',
          text: 'stale-message',
          timestamp: '2026-03-12T10:00:00.000Z',
          messageId: 'stale-1',
          source: 'inbox',
        },
      ],
      nextCursor: null,
      hasMore: false,
      feedRevision: 'rev-stale',
    });
    await firstHeadPromise;

    expect(store.getState().teamMessagesByName['my-team']?.feedRevision).toBe('rev-fresh');
  });

  it('tombstones current progress runs when delete clears a team so late progress cannot resurrect it', async () => {
    const store = createSliceStore();
    store.setState({
      provisioningRuns: {
        'run-live': {
          runId: 'run-live',
          teamName: 'my-team',
          state: 'assembling',
          message: 'Live run',
          startedAt: '2026-03-12T10:00:00.000Z',
          updatedAt: '2026-03-12T10:00:00.000Z',
        },
      },
      currentProvisioningRunIdByTeam: {
        'my-team': 'run-live',
      },
      currentRuntimeRunIdByTeam: {
        'my-team': 'run-live',
      },
      provisioningStartedAtFloorByTeam: {
        'my-team': '2026-03-12T10:00:00.000Z',
      },
    });

    await store.getState().deleteTeam('my-team');

    expect(store.getState().ignoredProvisioningRunIds['run-live']).toBe('my-team');
    expect(store.getState().ignoredRuntimeRunIds['run-live']).toBe('my-team');
    expect(store.getState().provisioningStartedAtFloorByTeam['my-team']).toBeTruthy();

    store.getState().onProvisioningProgress({
      runId: 'run-live',
      teamName: 'my-team',
      state: 'ready',
      message: 'Late zombie progress',
      startedAt: '2026-03-12T10:00:00.000Z',
      updatedAt: '2026-03-12T10:00:05.000Z',
    });

    expect(store.getState().provisioningRuns['run-live']).toBeUndefined();
    expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBeUndefined();
    expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBeUndefined();
  });

  it('stores runtime snapshots and suppresses semantic no-op refreshes', async () => {
    const store = createSliceStore();
    const snapshot = createRuntimeSnapshot();
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    expect(firstSnapshot).toEqual(snapshot);

    hoisted.getTeamAgentRuntime.mockResolvedValue({
      ...snapshot,
      updatedAt: '2026-03-12T10:00:05.000Z',
      members: {
        alice: {
          ...snapshot.members.alice,
          updatedAt: '2026-03-12T10:00:05.000Z',
        },
      },
    });

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toBe(firstSnapshot);
  });

  it('updates runtime snapshots when liveness diagnostics change', async () => {
    const store = createSliceStore();
    const snapshot = createRuntimeSnapshot();
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    const nextSnapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...snapshot.members.alice,
          alive: false,
          livenessKind: 'shell_only',
          pidSource: 'tmux_pane',
          runtimeDiagnostic: 'tmux pane foreground command is zsh',
          runtimeDiagnosticSeverity: 'warning',
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(nextSnapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).not.toBe(firstSnapshot);
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(nextSnapshot);
  });

  it('updates runtime snapshots when resource history changes', async () => {
    const store = createSliceStore();
    const firstResourceHistory = [
      {
        timestamp: '2026-03-12T10:00:00.000Z',
        rssBytes: 256 * 1024 * 1024,
        cpuPercent: 4,
        pid: 4242,
      },
    ];
    const snapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...createRuntimeSnapshot().members.alice,
          cpuPercent: 4,
          resourceHistory: firstResourceHistory,
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    const nextSnapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...snapshot.members.alice,
          cpuPercent: 14,
          resourceHistory: [
            ...firstResourceHistory,
            {
              timestamp: '2026-03-12T10:00:05.000Z',
              rssBytes: 270 * 1024 * 1024,
              cpuPercent: 14,
              pid: 4242,
            },
          ],
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(nextSnapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).not.toBe(firstSnapshot);
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(nextSnapshot);
  });

  it('does not crash when runtime resource history contains malformed samples', async () => {
    const store = createSliceStore();
    const validSample = {
      timestamp: '2026-03-12T10:00:00.000Z',
      rssBytes: 256 * 1024 * 1024,
      cpuPercent: 4,
      pid: 4242,
    };
    const snapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...createRuntimeSnapshot().members.alice,
          cpuPercent: 4,
          resourceHistory: [null, validSample] as any,
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    const semanticallySameSnapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...snapshot.members.alice,
          resourceHistory: [null, { ...validSample }] as any,
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(semanticallySameSnapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toBe(firstSnapshot);
  });

  it('updates runtime snapshots when aggregate runtime load breakdown changes', async () => {
    const store = createSliceStore();
    const firstBreakdownHistorySample = {
      timestamp: '2026-03-12T10:00:00.000Z',
      rssBytes: 300 * 1024 * 1024,
      cpuPercent: 12,
      primaryCpuPercent: 12,
      primaryRssBytes: 300 * 1024 * 1024,
      processCount: 1,
      runtimeLoadScope: 'single-process',
      pid: 4242,
    };
    const snapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...createRuntimeSnapshot().members.alice,
          cpuPercent: 12,
          rssBytes: 300 * 1024 * 1024,
          primaryCpuPercent: 12,
          primaryRssBytes: 300 * 1024 * 1024,
          processCount: 1,
          runtimeLoadScope: 'single-process',
          resourceHistory: [firstBreakdownHistorySample],
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    const nextSnapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...snapshot.members.alice,
          childCpuPercent: 8,
          childRssBytes: 80 * 1024 * 1024,
          processCount: 3,
          runtimeLoadScope: 'process-tree',
          resourceHistory: [
            {
              ...firstBreakdownHistorySample,
              childCpuPercent: 8,
              childRssBytes: 80 * 1024 * 1024,
              processCount: 3,
              runtimeLoadScope: 'process-tree',
            },
          ],
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(nextSnapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).not.toBe(firstSnapshot);
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(nextSnapshot);
  });

  it('updates runtime snapshots when copy-diagnostics details change', async () => {
    const store = createSliceStore();
    const snapshot = createRuntimeSnapshot({
      members: {
        alice: {
          memberName: 'alice',
          alive: false,
          restartable: true,
          backendType: 'tmux',
          pid: 42,
          livenessKind: 'shell_only',
          pidSource: 'tmux_pane',
          paneId: '%42',
          panePid: 42,
          paneCurrentCommand: 'zsh',
          runtimeDiagnostic: 'tmux pane foreground command is zsh',
          diagnostics: ['tmux pane foreground command is zsh'],
          updatedAt: '2026-03-12T10:00:00.000Z',
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    const nextSnapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...snapshot.members.alice,
          processCommand: 'node runtime --token [redacted]',
          runtimeSessionId: 'session-alice',
          diagnostics: [
            'tmux pane foreground command is zsh',
            'no verified runtime descendant process was found',
          ],
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(nextSnapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).not.toBe(firstSnapshot);
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(nextSnapshot);
  });

  it('updates runtime snapshots when historical bootstrap state changes', async () => {
    const store = createSliceStore();
    const snapshot = createRuntimeSnapshot();
    hoisted.getTeamAgentRuntime.mockResolvedValue(snapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');
    const firstSnapshot = store.getState().teamAgentRuntimeByTeam['my-team'];

    const nextSnapshot = createRuntimeSnapshot({
      members: {
        alice: {
          ...snapshot.members.alice,
          alive: false,
          historicalBootstrapConfirmed: true,
        },
      },
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(nextSnapshot);

    await store.getState().fetchTeamAgentRuntime('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).not.toBe(firstSnapshot);
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(nextSnapshot);
  });

  it('restartMember refreshes spawn statuses and runtime snapshot', async () => {
    const store = createSliceStore();
    hoisted.getMemberSpawnStatuses.mockResolvedValue({
      statuses: {
        alice: createMemberSpawnStatus({ status: 'spawning', launchState: 'starting' }),
      },
      runId: 'runtime-run',
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(createRuntimeSnapshot());

    await store.getState().restartMember('my-team', 'alice');

    expect(hoisted.restartMember).toHaveBeenCalledWith('my-team', 'alice');
    expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual({
      alice: expect.objectContaining({ status: 'spawning', launchState: 'starting' }),
    });
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(createRuntimeSnapshot());
  });

  it('retryFailedOpenCodeSecondaryLanes refreshes only spawn statuses and runtime snapshot', async () => {
    const store = createSliceStore();
    const refreshSpawnStatuses = vi.fn(async (_teamName: string) => undefined);
    const refreshRuntimeSnapshot = vi.fn(async (_teamName: string) => undefined);
    const refreshTeamData = vi.fn(async (_teamName: string) => undefined);
    const fetchTeams = vi.fn(async () => undefined);
    store.setState({
      fetchMemberSpawnStatuses: refreshSpawnStatuses,
      fetchTeamAgentRuntime: refreshRuntimeSnapshot,
      refreshTeamData,
      fetchTeams,
    });
    hoisted.retryFailedOpenCodeSecondaryLanes.mockResolvedValueOnce({
      attempted: ['alice'],
      confirmed: [],
      pending: [],
      failed: [{ memberName: 'alice', error: 'OpenRouter credits exhausted' }],
      skipped: [],
    });

    const result = await store.getState().retryFailedOpenCodeSecondaryLanes('my-team');

    expect(result.failed).toEqual([{ memberName: 'alice', error: 'OpenRouter credits exhausted' }]);
    expect(hoisted.retryFailedOpenCodeSecondaryLanes).toHaveBeenCalledWith('my-team');
    expect(refreshSpawnStatuses).toHaveBeenCalledWith('my-team');
    expect(refreshRuntimeSnapshot).toHaveBeenCalledWith('my-team');
    expect(refreshTeamData).not.toHaveBeenCalled();
    expect(fetchTeams).not.toHaveBeenCalled();
  });

  it('restartMember refreshes spawn statuses and runtime snapshot even when restart fails', async () => {
    const store = createSliceStore();
    const refreshSpawnStatuses = vi.fn(async (_teamName: string) => undefined);
    const refreshRuntimeSnapshot = vi.fn(async (_teamName: string) => undefined);
    store.setState({
      fetchMemberSpawnStatuses: refreshSpawnStatuses,
      fetchTeamAgentRuntime: refreshRuntimeSnapshot,
    });
    hoisted.restartMember.mockRejectedValueOnce(new Error('restart failed'));

    await expect(store.getState().restartMember('my-team', 'alice')).rejects.toThrow(
      'restart failed'
    );

    expect(refreshSpawnStatuses).toHaveBeenCalledWith('my-team');
    expect(refreshRuntimeSnapshot).toHaveBeenCalledWith('my-team');
  });

  it('skipMemberForLaunch refreshes spawn statuses, runtime snapshot, and team list', async () => {
    const store = createSliceStore();
    const refreshTeams = vi.fn(async () => undefined);
    store.setState({ fetchTeams: refreshTeams });
    hoisted.getMemberSpawnStatuses.mockResolvedValue({
      statuses: {
        alice: createMemberSpawnStatus({
          status: 'skipped',
          launchState: 'skipped_for_launch',
          skippedForLaunch: true,
        }),
      },
      runId: 'runtime-run',
    });
    hoisted.getTeamAgentRuntime.mockResolvedValue(createRuntimeSnapshot());

    await store.getState().skipMemberForLaunch('my-team', 'alice');

    expect(hoisted.skipMemberForLaunch).toHaveBeenCalledWith('my-team', 'alice');
    expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual({
      alice: expect.objectContaining({
        status: 'skipped',
        launchState: 'skipped_for_launch',
        skippedForLaunch: true,
      }),
    });
    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toEqual(createRuntimeSnapshot());
    expect(refreshTeams).toHaveBeenCalled();
  });

  it('skipMemberForLaunch refreshes launch data even when skip fails', async () => {
    const store = createSliceStore();
    const refreshSpawnStatuses = vi.fn(async (_teamName: string) => undefined);
    const refreshRuntimeSnapshot = vi.fn(async (_teamName: string) => undefined);
    const refreshTeams = vi.fn(async () => undefined);
    store.setState({
      fetchMemberSpawnStatuses: refreshSpawnStatuses,
      fetchTeamAgentRuntime: refreshRuntimeSnapshot,
      fetchTeams: refreshTeams,
    });
    hoisted.skipMemberForLaunch.mockRejectedValueOnce(new Error('skip failed'));

    await expect(store.getState().skipMemberForLaunch('my-team', 'alice')).rejects.toThrow(
      'skip failed'
    );

    expect(refreshSpawnStatuses).toHaveBeenCalledWith('my-team');
    expect(refreshRuntimeSnapshot).toHaveBeenCalledWith('my-team');
    expect(refreshTeams).toHaveBeenCalled();
  });

  it('clears stale runtime snapshots on delete', async () => {
    const store = createSliceStore();
    store.setState({
      teamAgentRuntimeByTeam: {
        'my-team': createRuntimeSnapshot(),
      },
    });

    await store.getState().deleteTeam('my-team');

    expect(store.getState().teamAgentRuntimeByTeam['my-team']).toBeUndefined();
  });

  describe('refreshTeamData provisioning safety', () => {
    it('does not set fatal error on TEAM_PROVISIONING', async () => {
      const store = createSliceStore();
      // First, select a team so selectedTeamName is set
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
        },
        selectedTeamError: null,
      });

      hoisted.getData.mockRejectedValue(new Error('TEAM_PROVISIONING'));

      await store.getState().refreshTeamData('my-team');

      // Should NOT set error — team is still provisioning
      expect(store.getState().selectedTeamError).toBeNull();
      // Should preserve existing data
      expect(store.getState().selectedTeamData).not.toBeNull();
      expect(store.getState().selectedTeamData?.teamName).toBe('my-team');
    });

    it('preserves existing data on transient refresh error', async () => {
      vi.spyOn(console, 'warn').mockImplementation(() => {});
      const store = createSliceStore();
      const existingData = {
        teamName: 'my-team',
        config: { name: 'My Team' },
        tasks: [],
        members: [],
        kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
      };
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: existingData,
        selectedTeamError: null,
      });

      hoisted.getData.mockRejectedValue(new Error('Network timeout'));

      await store.getState().refreshTeamData('my-team');

      // Should NOT replace data with error — preserve existing data
      expect(store.getState().selectedTeamError).toBeNull();
      expect(store.getState().selectedTeamData).toEqual(existingData);
    });

    it('reuses the existing selectedTeamData ref on a semantic no-op refresh', async () => {
      const store = createSliceStore();
      const existingData = createTeamSnapshot({
        tasks: [
          {
            id: 'task-1',
            subject: 'Stable task',
            status: 'pending',
            createdAt: '2026-03-20T08:00:00.000Z',
            updatedAt: '2026-03-20T08:00:00.000Z',
          },
        ],
        members: [
          {
            name: 'alice',
            currentTaskId: 'task-1',
            taskCount: 1,
          },
        ],
      });

      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: existingData,
        teamDataCacheByName: {
          'my-team': existingData,
        },
        selectedTeamError: 'stale error',
      });

      hoisted.getData.mockResolvedValue({
        ...existingData,
        tasks: existingData.tasks.map((task: any) => ({ ...task })),
        members: existingData.members.map((member: any) => ({ ...member })),
        kanbanState: {
          ...existingData.kanbanState,
          reviewers: [...existingData.kanbanState.reviewers],
          tasks: { ...existingData.kanbanState.tasks },
        },
        processes: [...existingData.processes],
      });

      await store.getState().refreshTeamData('my-team');

      expect(store.getState().selectedTeamData).toBe(existingData);
      expect(store.getState().teamDataCacheByName['my-team']).toBe(existingData);
      expect(store.getState().selectedTeamError).toBeNull();
    });

    it('memoizes focused resolved member selection against unrelated member activity churn', () => {
      const aliceSnapshot = {
        name: 'alice',
        currentTaskId: null,
        taskCount: 0,
        role: 'Reviewer',
      };
      const bobSnapshot = {
        name: 'bob',
        currentTaskId: null,
        taskCount: 0,
        role: 'Builder',
      };
      const baseState = {
        selectedTeamName: 'my-team',
        selectedTeamData: null,
        teamDataCacheByName: {
          'my-team': createTeamSnapshot({
            members: [aliceSnapshot, bobSnapshot],
          }),
        },
        memberActivityMetaByTeam: {
          'my-team': {
            teamName: 'my-team',
            computedAt: '2026-03-12T10:00:00.000Z',
            feedRevision: 'rev-1',
            members: {
              alice: {
                memberName: 'alice',
                lastAuthoredMessageAt: '2026-03-12T10:00:00.000Z',
                messageCountExact: 3,
                latestAuthoredMessageSignalsTermination: false,
              },
              bob: {
                memberName: 'bob',
                lastAuthoredMessageAt: '2026-03-12T10:01:00.000Z',
                messageCountExact: 1,
                latestAuthoredMessageSignalsTermination: false,
              },
            },
          },
        },
      };

      const firstAlice = selectResolvedMemberForTeamName(baseState as never, 'my-team', 'alice');
      const nextState = {
        ...baseState,
        memberActivityMetaByTeam: {
          'my-team': {
            ...baseState.memberActivityMetaByTeam['my-team'],
            computedAt: '2026-03-12T10:02:00.000Z',
            feedRevision: 'rev-2',
            members: {
              ...baseState.memberActivityMetaByTeam['my-team'].members,
              bob: {
                ...baseState.memberActivityMetaByTeam['my-team'].members.bob,
                messageCountExact: 2,
              },
            },
          },
        },
      };

      const secondAlice = selectResolvedMemberForTeamName(nextState as never, 'my-team', 'alice');

      expect(firstAlice).not.toBeNull();
      expect(secondAlice).toBe(firstAlice);
    });

    it('re-canonicalizes selectedTeamData into the cache on a no-op refresh', async () => {
      const store = createSliceStore();
      const existingData = createTeamSnapshot({
        tasks: [
          {
            id: 'task-1',
            subject: 'Stable task',
            status: 'pending',
            createdAt: '2026-03-20T08:00:00.000Z',
            updatedAt: '2026-03-20T08:00:00.000Z',
          },
        ],
      });

      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: existingData,
        teamDataCacheByName: {},
      });

      hoisted.getData.mockResolvedValue({
        ...existingData,
        tasks: existingData.tasks.map((task: any) => ({ ...task })),
        members: existingData.members.map((member: any) => ({ ...member })),
        kanbanState: {
          ...existingData.kanbanState,
          reviewers: [...existingData.kanbanState.reviewers],
          tasks: { ...existingData.kanbanState.tasks },
        },
        processes: [...existingData.processes],
      });

      await store.getState().refreshTeamData('my-team');

      expect(store.getState().teamDataCacheByName['my-team']).toBe(existingData);
      expect(store.getState().selectedTeamData).toBe(existingData);
    });

    it('clears non-selected cache on TEAM_DRAFT refresh failure', async () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'other-team',
        selectedTeamData: {
          teamName: 'other-team',
          config: { name: 'Other Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'other-team', reviewers: [], tasks: {} },
          processes: [],
        },
        teamDataCacheByName: {
          'my-team': {
            teamName: 'my-team',
            config: { name: 'My Team' },
            tasks: [],
            members: [],
            kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
            processes: [],
          },
        },
      });

      hoisted.getData.mockRejectedValue(new Error('TEAM_DRAFT'));

      await store.getState().refreshTeamData('my-team');

      expect(store.getState().teamDataCacheByName['my-team']).toBeUndefined();
      expect(store.getState().selectedTeamData?.teamName).toBe('other-team');
    });

    it('clears non-selected cache when the team no longer exists', async () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'other-team',
        selectedTeamData: {
          teamName: 'other-team',
          config: { name: 'Other Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'other-team', reviewers: [], tasks: {} },
          processes: [],
        },
        teamDataCacheByName: {
          'my-team': {
            teamName: 'my-team',
            config: { name: 'My Team' },
            tasks: [],
            members: [],
            kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
            processes: [],
          },
        },
      });

      hoisted.getData.mockRejectedValue(new Error('Team not found: my-team'));

      await store.getState().refreshTeamData('my-team');

      expect(store.getState().teamDataCacheByName['my-team']).toBeUndefined();
      expect(store.getState().selectedTeamData?.teamName).toBe('other-team');
    });

    it('clears stale selectedTeamError when TEAM_PROVISIONING with existing data', async () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
        },
        selectedTeamError: 'Previous failure',
      });

      hoisted.getData.mockRejectedValue(new Error('TEAM_PROVISIONING'));

      await store.getState().refreshTeamData('my-team');

      // Stale error should be cleared even though provisioning prevents new data
      expect(store.getState().selectedTeamError).toBeNull();
      expect(store.getState().selectedTeamData).not.toBeNull();
    });

    it('clears stale selectedTeamError on transient error when data exists', async () => {
      vi.spyOn(console, 'warn').mockImplementation(() => {});
      const store = createSliceStore();
      const existingData = {
        teamName: 'my-team',
        config: { name: 'My Team' },
        tasks: [],
        members: [],
        kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
      };
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: existingData,
        selectedTeamError: 'Old stale error',
      });

      hoisted.getData.mockRejectedValue(new Error('Network timeout'));

      await store.getState().refreshTeamData('my-team');

      // Stale error should be cleared because we still have usable data
      expect(store.getState().selectedTeamError).toBeNull();
      expect(store.getState().selectedTeamData).toEqual(existingData);
    });

    it('sets error when no previous data exists', async () => {
      vi.spyOn(console, 'warn').mockImplementation(() => {});
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: null,
        selectedTeamError: null,
      });

      hoisted.getData.mockRejectedValue(new Error('Team not found'));

      await store.getState().refreshTeamData('my-team');

      // No previous data — error should be shown
      expect(store.getState().selectedTeamError).toBe('Team not found');
    });

    it('invalidates changed task summaries without warming task availability on refresh', async () => {
      const store = createSliceStore();
      const invalidateTaskChangePresence = vi.fn();
      const warmTaskChangeSummaries = vi.fn(async () => undefined);
      store.setState({
        selectedTeamName: 'my-team',
        invalidateTaskChangePresence,
        warmTaskChangeSummaries,
        selectedTeamData: {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [
            {
              id: 'task-1',
              subject: 'Old completed',
              status: 'completed',
              owner: 'alice',
              createdAt: '2026-03-01T10:00:00.000Z',
              updatedAt: '2026-03-01T10:00:00.000Z',
              workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
              historyEvents: [],
              comments: [],
              attachments: [],
            },
            {
              id: 'task-2',
              subject: 'Still approved',
              status: 'completed',
              owner: 'bob',
              createdAt: '2026-03-01T10:00:00.000Z',
              updatedAt: '2026-03-01T10:00:00.000Z',
              workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
              historyEvents: [
                {
                  id: 'evt-approved',
                  type: 'review_approved',
                  to: 'approved',
                  timestamp: '2026-03-01T10:10:00.000Z',
                },
              ],
              comments: [],
              attachments: [],
            },
          ],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
          processes: [],
        },
      });

      hoisted.getData.mockResolvedValue({
        teamName: 'my-team',
        config: { name: 'My Team' },
        tasks: [
          {
            id: 'task-1',
            subject: 'Moved to review',
            status: 'completed',
            owner: 'alice',
            createdAt: '2026-03-01T10:00:00.000Z',
            updatedAt: '2026-03-01T11:00:00.000Z',
            workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
            historyEvents: [
              {
                id: 'evt-review',
                type: 'review_requested',
                to: 'review',
                timestamp: '2026-03-01T11:00:00.000Z',
              },
            ],
            comments: [],
            attachments: [],
          },
          {
            id: 'task-2',
            subject: 'Still approved',
            status: 'completed',
            owner: 'bob',
            createdAt: '2026-03-01T10:00:00.000Z',
            updatedAt: '2026-03-01T10:00:00.000Z',
            workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
            historyEvents: [
              {
                id: 'evt-approved',
                type: 'review_approved',
                to: 'approved',
                timestamp: '2026-03-01T10:10:00.000Z',
              },
            ],
            comments: [],
            attachments: [],
          },
        ],
        members: [],
        kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
        processes: [],
      });

      await store.getState().refreshTeamData('my-team');

      expect(hoisted.invalidateTaskChangeSummaries).toHaveBeenCalledWith('my-team', ['task-1']);
      expect(invalidateTaskChangePresence).toHaveBeenCalledTimes(1);
      expect(warmTaskChangeSummaries).not.toHaveBeenCalled();
    });

    it('preserves known task changePresence across refresh when task change signature is unchanged', async () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: {
          teamName: 'my-team',
          config: { name: 'My Team' },
          tasks: [
            {
              id: 'task-1',
              subject: 'Known changes',
              status: 'in_progress',
              owner: 'alice',
              createdAt: '2026-03-01T10:00:00.000Z',
              updatedAt: '2026-03-01T10:00:00.000Z',
              workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
              historyEvents: [],
              comments: [],
              attachments: [],
              changePresence: 'has_changes',
            },
          ],
          members: [],
          kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
          processes: [],
        },
      });

      hoisted.getData.mockResolvedValue({
        teamName: 'my-team',
        config: { name: 'My Team' },
        tasks: [
          {
            id: 'task-1',
            subject: 'Known changes',
            status: 'in_progress',
            owner: 'alice',
            createdAt: '2026-03-01T10:00:00.000Z',
            updatedAt: '2026-03-01T10:00:00.000Z',
            workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
            historyEvents: [],
            comments: [],
            attachments: [],
            changePresence: 'unknown',
          },
        ],
        members: [],
        kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
        processes: [],
      });

      await store.getState().refreshTeamData('my-team');

      expect(store.getState().selectedTeamData?.tasks[0]?.changePresence).toBe('has_changes');
    });

    it('does not clear known task changePresence when presence refresh returns unknown', async () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: createTeamSnapshot({
          tasks: [
            {
              id: 'task-1',
              subject: 'Known changes',
              status: 'in_progress',
              owner: 'alice',
              createdAt: '2026-03-01T10:00:00.000Z',
              updatedAt: '2026-03-01T10:00:00.000Z',
              workIntervals: [{ startedAt: '2026-03-01T10:05:00.000Z' }],
              historyEvents: [],
              comments: [],
              attachments: [],
              changePresence: 'has_changes',
            },
          ],
        }),
      });

      hoisted.getTaskChangePresence.mockResolvedValue({ 'task-1': 'unknown' });

      await store.getState().refreshTeamChangePresence('my-team');

      expect(store.getState().selectedTeamData?.tasks[0]?.changePresence).toBe('has_changes');
    });
  });

  describe('provisioning run scoping', () => {
    it('persists providerBackendId into createTeam launch params', async () => {
      const store = createSliceStore();

      await store.getState().createTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        members: [],
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.4',
        effort: 'medium',
      });

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.4',
        effort: 'medium',
        limitContext: false,
      });
    });

    it('persists providerBackendId into launchTeam launch params', async () => {
      const store = createSliceStore();

      await store.getState().launchTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.4',
        effort: 'medium',
      });

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.4',
        effort: 'medium',
        limitContext: false,
      });
    });

    it('stages changed launchTeam params before the launch IPC resolves', async () => {
      const store = createSliceStore();
      const launchRequest = createDeferredPromise<{ runId: string }>();
      hoisted.launchTeam.mockImplementationOnce(() => launchRequest.promise);
      store.setState({
        launchParamsByTeam: {
          'my-team': {
            providerId: 'codex',
            providerBackendId: 'codex-native',
            model: 'gpt-5.5',
            effort: 'medium',
            limitContext: false,
          },
        },
      });

      const launchPromise = store.getState().launchTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        providerId: 'anthropic',
        model: 'sonnet',
        effort: 'low',
      });
      await Promise.resolve();

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'anthropic',
        providerBackendId: undefined,
        model: 'sonnet',
        effort: 'low',
        limitContext: false,
      });

      launchRequest.resolve({ runId: 'run-2' });
      await launchPromise;

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'anthropic',
        providerBackendId: undefined,
        model: 'sonnet',
        effort: 'low',
        limitContext: false,
      });
    });

    it('sanitizes stale providerBackendId before staging launchTeam params', async () => {
      const store = createSliceStore();
      const launchRequest = createDeferredPromise<{ runId: string }>();
      hoisted.launchTeam.mockImplementationOnce(() => launchRequest.promise);

      const launchPromise = store.getState().launchTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        providerId: 'anthropic',
        providerBackendId: 'codex-native',
        model: 'haiku',
        effort: 'low',
      });
      await Promise.resolve();

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'anthropic',
        providerBackendId: undefined,
        model: 'haiku',
        effort: 'low',
        limitContext: false,
      });

      launchRequest.resolve({ runId: 'run-2' });
      await launchPromise;

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'anthropic',
        providerBackendId: undefined,
        model: 'haiku',
        effort: 'low',
        limitContext: false,
      });
    });

    it('does not stage a previous model when launchTeam changes provider without a model', async () => {
      const store = createSliceStore();
      const launchRequest = createDeferredPromise<{ runId: string }>();
      hoisted.launchTeam.mockImplementationOnce(() => launchRequest.promise);
      store.setState({
        launchParamsByTeam: {
          'my-team': {
            providerId: 'codex',
            providerBackendId: 'codex-native',
            model: 'gpt-5.5',
            effort: 'medium',
            limitContext: true,
          },
        },
      });

      const launchPromise = store.getState().launchTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        providerId: 'anthropic',
      });
      await Promise.resolve();

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'anthropic',
        providerBackendId: undefined,
        model: 'default',
        effort: undefined,
        limitContext: false,
      });

      launchRequest.resolve({ runId: 'run-2' });
      await launchPromise;
    });

    it('stages Default when launchTeam keeps the provider but explicitly clears the model', async () => {
      const store = createSliceStore();
      const launchRequest = createDeferredPromise<{ runId: string }>();
      hoisted.launchTeam.mockImplementationOnce(() => launchRequest.promise);
      store.setState({
        launchParamsByTeam: {
          'my-team': {
            providerId: 'codex',
            providerBackendId: 'codex-native',
            model: 'gpt-5.5',
            effort: 'medium',
            limitContext: false,
          },
        },
      });

      const launchPromise = store.getState().launchTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: undefined,
        effort: 'low',
      });
      await Promise.resolve();

      expect(store.getState().launchParamsByTeam['my-team']).toEqual({
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'default',
        effort: 'low',
        limitContext: false,
      });

      launchRequest.resolve({ runId: 'run-2' });
      await launchPromise;
    });

    it('keeps previous launch params while a metadata-only relaunch request is pending', async () => {
      const store = createSliceStore();
      const previousParams = {
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.5',
        effort: 'medium',
        limitContext: false,
      };
      store.setState({
        launchParamsByTeam: {
          'my-team': previousParams,
        },
      });
      const launchRequest = createDeferredPromise<{ runId: string }>();
      hoisted.launchTeam.mockImplementationOnce(() => launchRequest.promise);

      const launchPromise = store.getState().launchTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
      });
      await Promise.resolve();

      expect(store.getState().launchParamsByTeam['my-team']).toEqual(previousParams);

      launchRequest.resolve({ runId: 'run-2' });
      await launchPromise;

      expect(store.getState().launchParamsByTeam['my-team']).toEqual(previousParams);
    });

    it('rolls back staged launch params when launchTeam fails before provisioning starts', async () => {
      const store = createSliceStore();
      const previousParams = {
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.5',
        effort: 'medium',
        limitContext: false,
      };
      store.setState({
        launchParamsByTeam: {
          'my-team': previousParams,
        },
      });
      hoisted.launchTeam.mockRejectedValueOnce(new Error('launch failed'));

      await expect(
        store.getState().launchTeam({
          teamName: 'my-team',
          cwd: '/tmp/project',
          providerId: 'anthropic',
          model: 'sonnet',
          effort: 'low',
        })
      ).rejects.toThrow('launch failed');

      expect(store.getState().launchParamsByTeam['my-team']).toEqual(previousParams);
    });

    it('rolls back optimistic pending run on early createTeam failure', async () => {
      const store = createSliceStore();
      const previousParams = {
        providerId: 'codex',
        providerBackendId: 'codex-native',
        model: 'gpt-5.5',
        effort: 'medium',
        limitContext: false,
      };
      store.setState({
        launchParamsByTeam: {
          'my-team': previousParams,
        },
      });
      hoisted.createTeam.mockRejectedValue(new Error('create failed'));

      await expect(
        store.getState().createTeam({
          teamName: 'my-team',
          cwd: '/tmp/project',
          members: [],
          providerId: 'anthropic',
          model: 'sonnet',
          effort: 'low',
        })
      ).rejects.toThrow('create failed');

      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBeUndefined();
      expect(Object.values(store.getState().provisioningRuns)).toHaveLength(0);
      expect(store.getState().provisioningErrorByTeam['my-team']).toBe('create failed');
      expect(store.getState().launchParamsByTeam['my-team']).toEqual(previousParams);
    });

    it('hydrates visible non-selected graph tabs when config becomes ready', () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'other-team',
        selectedTeamData: {
          teamName: 'other-team',
          config: { name: 'Other Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'other-team', reviewers: [], tasks: {} },
          processes: [],
        },
        paneLayout: {
          focusedPaneId: 'pane-default',
          panes: [
            {
              id: 'pane-default',
              widthFraction: 1,
              tabs: [{ id: 'graph-1', type: 'graph', teamName: 'my-team', label: 'My Team' }],
              activeTabId: 'graph-1',
            },
          ],
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'run-current',
        },
      });

      const refreshTeamDataSpy = vi.spyOn(store.getState(), 'refreshTeamData');
      const selectTeamSpy = vi.spyOn(store.getState(), 'selectTeam');

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'assembling',
        configReady: true,
        message: 'Config written',
        startedAt: '2026-03-12T10:00:00.000Z',
        updatedAt: '2026-03-12T10:00:01.000Z',
      });

      expect(refreshTeamDataSpy).toHaveBeenCalledWith('my-team', { withDedup: true });
      expect(selectTeamSpy).not.toHaveBeenCalled();
    });

    it('refreshes visible non-selected graph tabs when the canonical run reaches ready', () => {
      const store = createSliceStore();
      store.setState({
        selectedTeamName: 'other-team',
        selectedTeamData: {
          teamName: 'other-team',
          config: { name: 'Other Team' },
          tasks: [],
          members: [],
          kanbanState: { teamName: 'other-team', reviewers: [], tasks: {} },
          processes: [],
        },
        paneLayout: {
          focusedPaneId: 'pane-default',
          panes: [
            {
              id: 'pane-default',
              widthFraction: 1,
              tabs: [{ id: 'graph-1', type: 'graph', teamName: 'my-team', label: 'My Team' }],
              activeTabId: 'graph-1',
            },
          ],
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'run-current',
        },
      });

      const refreshTeamDataSpy = vi.spyOn(store.getState(), 'refreshTeamData');
      const selectTeamSpy = vi.spyOn(store.getState(), 'selectTeam');

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'ready',
        message: 'Ready',
        startedAt: '2026-03-12T10:00:00.000Z',
        updatedAt: '2026-03-12T10:00:02.000Z',
      });

      expect(refreshTeamDataSpy).toHaveBeenCalledWith('my-team', { withDedup: true });
      expect(selectTeamSpy).not.toHaveBeenCalled();
    });

    it('keeps the current run pinned when stale progress from another run arrives', () => {
      const store = createSliceStore();
      const startedAt = '2026-03-12T10:00:00.000Z';

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'spawning',
        message: 'Current run',
        startedAt,
        updatedAt: startedAt,
      });

      store.getState().onProvisioningProgress({
        runId: 'run-stale',
        teamName: 'my-team',
        state: 'failed',
        message: 'Stale failure',
        error: 'stale',
        startedAt: '2026-03-12T10:00:01.000Z',
        updatedAt: '2026-03-12T10:00:01.000Z',
      });

      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBe('run-current');
      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBe('run-current');
      expect(store.getState().provisioningErrorByTeam['my-team']).toBeUndefined();
      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({
        provisioningRuns: {
          'pending:my-team:1': {
            runId: 'pending:my-team:1',
            teamName: 'my-team',
            state: 'spawning',
            message: 'Launching',
            startedAt: '2026-03-12T10:00:00.000Z',
            updatedAt: '2026-03-12T10:00:00.000Z',
          },
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'pending:my-team:1',
        },
      });

      expect(() =>
        store.getState().onProvisioningProgress({
          runId: 'run-real',
          teamName: 'my-team',
          state: 'assembling',
          message: 'Real run',
          startedAt: '2026-03-12T10:00:01.000Z',
          updatedAt: '2026-03-12T10:00:01.000Z',
        })
      ).not.toThrow();

      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBe('run-real');
      expect(store.getState().provisioningRuns['pending:my-team:1']).toBeUndefined();
      expect(store.getState().provisioningRuns['run-real']).toEqual(
        expect.objectContaining({
          runId: 'run-real',
          state: 'assembling',
        })
      );
    });

    it('clears orphaned runs when polling reports Unknown runId', () => {
      const store = createSliceStore();
      store.setState({
        provisioningRuns: {
          'pending:my-team:1': {
            runId: 'pending:my-team:1',
            teamName: 'my-team',
            state: 'spawning',
            message: 'Launching',
            startedAt: '2026-03-12T10:00:00.000Z',
            updatedAt: '2026-03-12T10:00:00.000Z',
          },
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'pending:my-team:1',
        },
        currentRuntimeRunIdByTeam: {
          'my-team': 'pending:my-team:1',
        },
        memberSpawnStatusesByTeam: {
          'my-team': {
            alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
          },
        },
      });

      store.getState().clearMissingProvisioningRun('pending:my-team:1');

      expect(store.getState().provisioningRuns['pending:my-team:1']).toBeUndefined();
      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBeUndefined();
      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBeUndefined();
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
      expect(store.getState().ignoredProvisioningRunIds['pending:my-team:1']).toBe('my-team');
      expect(store.getState().ignoredRuntimeRunIds['pending:my-team:1']).toBe('my-team');
    });

    it('does not resurrect a cleared missing run when late progress arrives', () => {
      const store = createSliceStore();
      store.setState({
        provisioningRuns: {
          'pending:my-team:1': {
            runId: 'pending:my-team:1',
            teamName: 'my-team',
            state: 'spawning',
            message: 'Launching',
            startedAt: '2026-03-12T10:00:00.000Z',
            updatedAt: '2026-03-12T10:00:00.000Z',
          },
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'pending:my-team:1',
        },
      });

      store.getState().clearMissingProvisioningRun('pending:my-team:1');
      store.getState().onProvisioningProgress({
        runId: 'pending:my-team:1',
        teamName: 'my-team',
        state: 'assembling',
        message: 'Late zombie progress',
        startedAt: '2026-03-12T10:00:00.000Z',
        updatedAt: '2026-03-12T10:00:02.000Z',
      });

      expect(store.getState().provisioningRuns['pending:my-team:1']).toBeUndefined();
      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBeUndefined();
    });

    it('keeps runtime run id separate from provisioning run id when fetching spawn statuses', async () => {
      const store = createSliceStore();
      store.setState({
        currentProvisioningRunIdByTeam: {
          'my-team': 'provisioning-run',
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue({
        runId: 'runtime-run',
        statuses: {
          alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
        },
      });

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBe('provisioning-run');
      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBe('runtime-run');
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual({
        alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
      });
    });

    it('suppresses renderer rewrites when only lastHeartbeatAt changes', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot();
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      hoisted.getMemberSpawnStatuses.mockResolvedValue(
        createMemberSpawnSnapshot({
          statuses: {
            alice: createMemberSpawnStatus({
              lastHeartbeatAt: '2026-03-12T10:00:09.000Z',
            }),
          },
        })
      );

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBe(previousStatuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBe(previousSnapshot);
    });

    it('suppresses renderer rewrites when only firstSpawnAcceptedAt changes', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot();
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      hoisted.getMemberSpawnStatuses.mockResolvedValue(
        createMemberSpawnSnapshot({
          statuses: {
            alice: createMemberSpawnStatus({
              firstSpawnAcceptedAt: '2026-03-12T09:59:35.000Z',
            }),
          },
        })
      );

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBe(previousStatuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBe(previousSnapshot);
    });

    it('suppresses renderer rewrites when only updatedAt changes', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot();
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      hoisted.getMemberSpawnStatuses.mockResolvedValue(
        createMemberSpawnSnapshot({
          updatedAt: '2026-03-12T10:00:11.000Z',
          statuses: {
            alice: createMemberSpawnStatus({
              updatedAt: '2026-03-12T10:00:11.000Z',
            }),
          },
        })
      );

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBe(previousStatuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBe(previousSnapshot);
    });

    it('rewrites renderer state when runtimeAlive changes', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot({
        statuses: {
          alice: createMemberSpawnStatus({
            launchState: 'runtime_pending_bootstrap',
            livenessSource: 'process',
            bootstrapConfirmed: false,
          }),
        },
        teamLaunchState: 'partial_pending',
        summary: {
          confirmedCount: 0,
          pendingCount: 1,
          failedCount: 0,
          runtimeAlivePendingCount: 1,
        },
      });
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      const nextSnapshot = createMemberSpawnSnapshot();
      hoisted.getMemberSpawnStatuses.mockResolvedValue(nextSnapshot);

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).not.toBe(previousStatuses);
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual(nextSnapshot.statuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toEqual(nextSnapshot);
    });

    it('rewrites renderer state when error semantics change', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot({
        statuses: {
          alice: createMemberSpawnStatus({
            status: 'waiting',
            launchState: 'runtime_pending_bootstrap',
            runtimeAlive: false,
            livenessSource: undefined,
            bootstrapConfirmed: false,
          }),
        },
        teamLaunchState: 'partial_pending',
        summary: {
          confirmedCount: 0,
          pendingCount: 1,
          failedCount: 0,
          runtimeAlivePendingCount: 0,
        },
      });
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      const nextSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'partial_failure',
        summary: {
          confirmedCount: 0,
          pendingCount: 0,
          failedCount: 1,
          runtimeAlivePendingCount: 0,
        },
        statuses: {
          alice: createMemberSpawnStatus({
            status: 'error',
            launchState: 'failed_to_start',
            error: 'bootstrap failed',
            runtimeAlive: false,
            livenessSource: undefined,
            bootstrapConfirmed: false,
            hardFailure: true,
          }),
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue(nextSnapshot);

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).not.toBe(previousStatuses);
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual(nextSnapshot.statuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toEqual(nextSnapshot);
    });

    it('rewrites renderer state when only hard failure reason changes', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'partial_failure',
        summary: {
          confirmedCount: 0,
          pendingCount: 0,
          failedCount: 1,
          runtimeAlivePendingCount: 0,
        },
        statuses: {
          alice: createMemberSpawnStatus({
            status: 'error',
            launchState: 'failed_to_start',
            runtimeAlive: false,
            livenessSource: undefined,
            bootstrapConfirmed: false,
            hardFailure: true,
            hardFailureReason: 'initial failure',
          }),
        },
      });
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      const nextSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'partial_failure',
        summary: {
          confirmedCount: 0,
          pendingCount: 0,
          failedCount: 1,
          runtimeAlivePendingCount: 0,
        },
        statuses: {
          alice: createMemberSpawnStatus({
            status: 'error',
            launchState: 'failed_to_start',
            runtimeAlive: false,
            livenessSource: undefined,
            bootstrapConfirmed: false,
            hardFailure: true,
            hardFailureReason: 'resolved runtime reported missing auth',
          }),
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue(nextSnapshot);

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).not.toBe(previousStatuses);
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual(nextSnapshot.statuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toEqual(nextSnapshot);
    });

    it('rewrites renderer state when top-level launch summary changes', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'partial_pending',
        summary: {
          confirmedCount: 0,
          pendingCount: 1,
          failedCount: 0,
          runtimeAlivePendingCount: 1,
        },
        statuses: {
          alice: createMemberSpawnStatus({
            launchState: 'runtime_pending_bootstrap',
            livenessSource: 'process',
            bootstrapConfirmed: false,
          }),
        },
      });
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-run',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      const nextSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'clean_success',
        summary: {
          confirmedCount: 1,
          pendingCount: 0,
          failedCount: 0,
          runtimeAlivePendingCount: 0,
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue(nextSnapshot);

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).not.toBe(previousStatuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toEqual(nextSnapshot);
    });

    it('preserves spawn snapshot references while still updating bookkeeping on suppressed snapshots', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot();
      const previousStatuses = previousSnapshot.statuses;

      store.setState({
        ignoredRuntimeRunIds: {
          'runtime-old': 'my-team',
        },
        memberSpawnStatusesByTeam: {
          'my-team': previousStatuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      hoisted.getMemberSpawnStatuses.mockResolvedValue(
        createMemberSpawnSnapshot({
          statuses: {
            alice: createMemberSpawnStatus({
              lastHeartbeatAt: '2026-03-12T10:00:09.000Z',
            }),
          },
        })
      );

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBe('runtime-run');
      expect(store.getState().ignoredRuntimeRunIds['runtime-old']).toBe('my-team');
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBe(previousStatuses);
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBe(previousSnapshot);
    });

    it('does not suppress spawn snapshots when pending permission request ids change', async () => {
      const store = createSliceStore();
      const previousSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'partial_pending',
        launchPhase: 'active',
        summary: {
          confirmedCount: 0,
          pendingCount: 1,
          failedCount: 0,
          runtimeAlivePendingCount: 0,
        },
        statuses: {
          alice: createMemberSpawnStatus({
            status: 'waiting',
            launchState: 'runtime_pending_bootstrap',
            runtimeAlive: false,
            livenessSource: undefined,
            bootstrapConfirmed: false,
            firstSpawnAcceptedAt: '2026-03-12T09:59:30.000Z',
            lastHeartbeatAt: undefined,
          }),
        },
      });

      store.setState({
        memberSpawnStatusesByTeam: {
          'my-team': previousSnapshot.statuses,
        },
        memberSpawnSnapshotsByTeam: {
          'my-team': previousSnapshot,
        },
      });

      const nextSnapshot = createMemberSpawnSnapshot({
        teamLaunchState: 'partial_pending',
        launchPhase: 'active',
        summary: {
          confirmedCount: 0,
          pendingCount: 1,
          failedCount: 0,
          runtimeAlivePendingCount: 0,
        },
        statuses: {
          alice: createMemberSpawnStatus({
            status: 'waiting',
            launchState: 'runtime_pending_bootstrap',
            runtimeAlive: false,
            livenessSource: undefined,
            bootstrapConfirmed: false,
            firstSpawnAcceptedAt: '2026-03-12T09:59:30.000Z',
            lastHeartbeatAt: undefined,
            pendingPermissionRequestIds: ['perm-1'],
          }),
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue(nextSnapshot);

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).not.toBe(previousSnapshot);
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).not.toBe(
        previousSnapshot.statuses
      );
      expect(
        store.getState().memberSpawnStatusesByTeam['my-team']?.alice?.pendingPermissionRequestIds
      ).toEqual(['perm-1']);
    });

    it('ignores stale spawn-status fetches after runtime already went offline', async () => {
      const store = createSliceStore();
      store.setState({
        currentProvisioningRunIdByTeam: {
          'my-team': 'provisioning-run',
        },
        leadActivityByTeam: {
          'my-team': 'offline',
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue({
        runId: 'old-runtime-run',
        statuses: {
          alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
        },
      });

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBeUndefined();
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
    });

    it('tombstones the previous runtime run and clears tool layers before creating a new run', async () => {
      const store = createSliceStore();
      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-old',
        },
        activeToolsByTeam: {
          'my-team': {
            'team-lead': {
              'tool-a': {
                memberName: 'team-lead',
                toolUseId: 'tool-a',
                toolName: 'Read',
                startedAt: '2026-03-12T10:00:00.000Z',
                state: 'running',
                source: 'runtime',
              },
            },
          },
        },
        finishedVisibleByTeam: {
          'my-team': {
            'team-lead': {
              'tool-b': {
                memberName: 'team-lead',
                toolUseId: 'tool-b',
                toolName: 'Bash',
                startedAt: '2026-03-12T10:00:01.000Z',
                finishedAt: '2026-03-12T10:00:02.000Z',
                state: 'complete',
                source: 'runtime',
              },
            },
          },
        },
        toolHistoryByTeam: {
          'my-team': {
            'team-lead': [
              {
                memberName: 'team-lead',
                toolUseId: 'tool-b',
                toolName: 'Bash',
                startedAt: '2026-03-12T10:00:01.000Z',
                finishedAt: '2026-03-12T10:00:02.000Z',
                state: 'complete',
                source: 'runtime',
              },
            ],
          },
        },
      });

      await store.getState().createTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        members: [],
      });

      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBe('run-1');
      expect(store.getState().ignoredRuntimeRunIds['runtime-old']).toBe('my-team');
      expect(store.getState().activeToolsByTeam['my-team']).toBeUndefined();
      expect(store.getState().finishedVisibleByTeam['my-team']).toBeUndefined();
      expect(store.getState().toolHistoryByTeam['my-team']).toBeUndefined();
    });

    it('keeps tombstoned runtime ids ignored during createTeam startup before the new run is pinned', async () => {
      const store = createSliceStore();
      const createDeferred = createDeferredPromise<{ runId: string }>();
      hoisted.createTeam.mockImplementation(() => createDeferred.promise);
      store.setState({
        currentRuntimeRunIdByTeam: {
          'my-team': 'runtime-live',
        },
        ignoredRuntimeRunIds: {
          'runtime-old': 'my-team',
        },
      });

      const createPromise = store.getState().createTeam({
        teamName: 'my-team',
        cwd: '/tmp/project',
        members: [],
      });

      await Promise.resolve();

      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBeUndefined();
      expect(store.getState().ignoredRuntimeRunIds['runtime-old']).toBe('my-team');
      expect(store.getState().ignoredRuntimeRunIds['runtime-live']).toBe('my-team');

      hoisted.getMemberSpawnStatuses.mockResolvedValue(
        createMemberSpawnSnapshot({
          runId: 'runtime-old',
        })
      );

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
      expect(store.getState().memberSpawnSnapshotsByTeam['my-team']).toBeUndefined();

      createDeferred.resolve({ runId: 'run-1' });
      await createPromise;
    });

    it('keeps older tombstoned runtime ids after canonical provisioning progress arrives', () => {
      const store = createSliceStore();
      store.setState({
        ignoredRuntimeRunIds: {
          'runtime-old': 'my-team',
        },
      });

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'assembling',
        message: 'Current run',
        startedAt: '2026-03-12T10:00:00.000Z',
        updatedAt: '2026-03-12T10:00:01.000Z',
      });

      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBe('run-current');
      expect(store.getState().ignoredRuntimeRunIds['runtime-old']).toBe('my-team');
    });

    it('ignores tombstoned runtime spawn-status snapshots', async () => {
      const store = createSliceStore();
      store.setState({
        ignoredRuntimeRunIds: {
          'runtime-old': 'my-team',
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue({
        runId: 'runtime-old',
        statuses: {
          alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
        },
      });

      await store.getState().fetchMemberSpawnStatuses('my-team');

      expect(store.getState().currentRuntimeRunIdByTeam['my-team']).toBeUndefined();
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
    });

    it('preserves current spawn statuses when clearing a non-canonical missing run', () => {
      const store = createSliceStore();
      store.setState({
        provisioningRuns: {
          'run-current': {
            runId: 'run-current',
            teamName: 'my-team',
            state: 'assembling',
            message: 'Current run',
            startedAt: '2026-03-12T10:00:00.000Z',
            updatedAt: '2026-03-12T10:00:00.000Z',
          },
          'run-stale': {
            runId: 'run-stale',
            teamName: 'my-team',
            state: 'failed',
            message: 'Stale run',
            startedAt: '2026-03-12T10:00:01.000Z',
            updatedAt: '2026-03-12T10:00:01.000Z',
          },
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'run-current',
        },
        memberSpawnStatusesByTeam: {
          'my-team': {
            alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
          },
        },
      });

      store.getState().clearMissingProvisioningRun('run-stale');

      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBe('run-current');
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toEqual({
        alice: { status: 'spawning', updatedAt: '2026-03-12T10:00:00.000Z' },
      });
    });

    it('keeps the terminal canonical run pinned and does not fall back to other team runs', () => {
      const store = createSliceStore();
      const startedAt = '2026-03-12T10:00:00.000Z';

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'assembling',
        message: 'Current run',
        startedAt,
        updatedAt: startedAt,
      });

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'disconnected',
        message: 'Disconnected',
        startedAt,
        updatedAt: '2026-03-12T10:00:01.000Z',
      });

      store.setState((state: ReturnType<typeof store.getState>) => ({
        provisioningRuns: {
          ...state.provisioningRuns,
          'run-stale': {
            runId: 'run-stale',
            teamName: 'my-team',
            state: 'failed',
            message: 'Stale run',
            startedAt: '2026-03-12T10:00:02.000Z',
            updatedAt: '2026-03-12T10:00:02.000Z',
          },
        },
      }));

      expect(store.getState().currentProvisioningRunIdByTeam['my-team']).toBe('run-current');
      expect(store.getState().memberSpawnStatusesByTeam['my-team']).toBeUndefined();
      expect(getCurrentProvisioningProgressForTeam(store.getState(), 'my-team')).toEqual(
        expect.objectContaining({
          runId: 'run-current',
          state: 'disconnected',
        })
      );
    });

    it('refreshes retained terminal spawn errors after disconnected progress', async () => {
      const store = createSliceStore();
      const startedAt = '2026-03-12T10:00:00.000Z';
      const staleReason = 'CLI process exited (code 1) \u2014 team provisioned but not alive';
      store.setState({
        selectedTeamName: 'my-team',
        selectedTeamData: createTeamSnapshot(),
        paneLayout: {
          focusedPaneId: 'pane-default',
          panes: [
            {
              id: 'pane-default',
              widthFraction: 1,
              tabs: [{ id: 'team-my-team', type: 'team', teamName: 'my-team', label: 'My Team' }],
              activeTabId: 'team-my-team',
            },
          ],
        },
        currentProvisioningRunIdByTeam: {
          'my-team': 'run-current',
        },
        currentRuntimeRunIdByTeam: {
          'my-team': 'run-current',
        },
        memberSpawnStatusesByTeam: {
          'my-team': {
            tom: createMemberSpawnStatus({
              status: 'error',
              launchState: 'failed_to_start',
              error: staleReason,
              hardFailure: true,
              hardFailureReason: staleReason,
              bootstrapConfirmed: true,
              runtimeAlive: false,
            }),
          },
        },
      });
      hoisted.getMemberSpawnStatuses.mockResolvedValue(
        createMemberSpawnSnapshot({
          runId: 'run-current',
          expectedMembers: ['tom'],
          statuses: {
            tom: createMemberSpawnStatus({
              status: 'online',
              launchState: 'confirmed_alive',
              runtimeAlive: false,
              livenessKind: 'confirmed_bootstrap',
              hardFailure: false,
              hardFailureReason: undefined,
              error: undefined,
            }),
          },
        })
      );

      store.getState().onProvisioningProgress({
        runId: 'run-current',
        teamName: 'my-team',
        state: 'disconnected',
        message: 'Disconnected',
        startedAt,
        updatedAt: '2026-03-12T10:00:01.000Z',
      });

      await vi.waitFor(() => {
        expect(store.getState().memberSpawnStatusesByTeam['my-team']?.tom).toMatchObject({
          status: 'online',
          launchState: 'confirmed_alive',
          bootstrapConfirmed: true,
          hardFailure: false,
        });
      });
      expect(
        store.getState().memberSpawnStatusesByTeam['my-team']?.tom?.hardFailureReason
      ).toBeUndefined();
    });

    it('does not fall back to a team-wide latest run when no current run is pinned', () => {
      expect(
        getCurrentProvisioningProgressForTeam(
          {
            currentProvisioningRunIdByTeam: {},
            provisioningRuns: {
              'run-stale': {
                runId: 'run-stale',
                teamName: 'my-team',
                state: 'failed',
                message: 'Stale run',
                startedAt: '2026-03-12T10:00:00.000Z',
                updatedAt: '2026-03-12T10:00:00.000Z',
              },
            },
          },
          'my-team'
        )
      ).toBeNull();
    });
  });
});
