From f1acccfd03a3465268d5aea7a111e576ab454630 Mon Sep 17 00:00:00 2001 From: parsakhaz Date: Sat, 20 Jun 2026 11:31:43 -0700 Subject: [PATCH] Fix RunPane background session focus --- frontend/src/stores/sessionStore.test.ts | 58 ++++++++++++++++++++++++ frontend/src/stores/sessionStore.ts | 3 +- frontend/src/types/session.ts | 1 + main/src/ipc/runpane.test.ts | 1 + main/src/ipc/runpane.ts | 1 + main/src/services/sessionManager.ts | 7 ++- main/src/services/taskQueue.ts | 5 +- main/src/types/session.ts | 1 + 8 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 frontend/src/stores/sessionStore.test.ts diff --git a/frontend/src/stores/sessionStore.test.ts b/frontend/src/stores/sessionStore.test.ts new file mode 100644 index 00000000..080092e1 --- /dev/null +++ b/frontend/src/stores/sessionStore.test.ts @@ -0,0 +1,58 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import type { Session } from '../types/session'; +import { useSessionStore } from './sessionStore'; + +function session(overrides: Partial = {}): Session { + return { + id: 'session-new', + name: 'New pane', + worktreePath: '/repo/worktrees/new-pane', + prompt: '', + status: 'stopped', + createdAt: '2026-01-01T00:00:00.000Z', + output: [], + jsonMessages: [], + ...overrides, + }; +} + +describe('sessionStore', () => { + beforeEach(() => { + useSessionStore.setState({ + sessions: [], + activeSessionId: null, + activeMainRepoSession: null, + isLoaded: false, + terminalOutput: {}, + deletingSessionIds: new Set(), + gitStatusLoading: new Set(), + pendingGitStatusLoading: new Map(), + pendingGitStatusUpdates: new Map(), + gitStatusBatchTimer: null, + activeSpotlights: new Map(), + }); + }); + + it('keeps the current active pane when a background-created session arrives', () => { + useSessionStore.setState({ activeSessionId: 'session-existing' }); + + useSessionStore.getState().addSession(session({ + id: 'session-background', + activateOnCreate: false, + })); + + const state = useSessionStore.getState(); + expect(state.sessions[0].id).toBe('session-background'); + expect(state.activeSessionId).toBe('session-existing'); + }); + + it('activates newly created sessions by default', () => { + useSessionStore.setState({ activeSessionId: 'session-existing' }); + + useSessionStore.getState().addSession(session({ + id: 'session-foreground', + })); + + expect(useSessionStore.getState().activeSessionId).toBe('session-foreground'); + }); +}); diff --git a/frontend/src/stores/sessionStore.ts b/frontend/src/stores/sessionStore.ts index b4eccd53..c804283d 100644 --- a/frontend/src/stores/sessionStore.ts +++ b/frontend/src/stores/sessionStore.ts @@ -92,6 +92,7 @@ export const useSessionStore = create((set, get) => ({ addSession: (session) => set((state) => { const normalizedSession = normalizeSession(session); + const shouldActivate = normalizedSession.activateOnCreate !== false; // Initialize arrays if they don't exist const sessionWithArrays = { @@ -102,7 +103,7 @@ export const useSessionStore = create((set, get) => ({ return { sessions: [sessionWithArrays, ...state.sessions], // Add new sessions at the top - activeSessionId: normalizedSession.id // Automatically set as active + activeSessionId: shouldActivate ? normalizedSession.id : state.activeSessionId }; }), diff --git a/frontend/src/types/session.ts b/frontend/src/types/session.ts index 041753e0..c14202be 100644 --- a/frontend/src/types/session.ts +++ b/frontend/src/types/session.ts @@ -102,6 +102,7 @@ export interface Session { gitStatus?: GitStatus; baseCommit?: string; baseBranch?: string; + activateOnCreate?: boolean; } export interface GitStatus { diff --git a/main/src/ipc/runpane.test.ts b/main/src/ipc/runpane.test.ts index 420c0bba..1c03ae55 100644 --- a/main/src/ipc/runpane.test.ts +++ b/main/src/ipc/runpane.test.ts @@ -1107,6 +1107,7 @@ describe('runpane IPC handlers', () => { projectId: project.id, baseBranch: 'main', toolType: 'none', + activateOnCreate: false, }, { timeoutMs: 1234 }); expect(panelManager.createPanel).toHaveBeenCalledWith({ sessionId: session.id, diff --git a/main/src/ipc/runpane.ts b/main/src/ipc/runpane.ts index b12eb259..9a1dd29c 100644 --- a/main/src/ipc/runpane.ts +++ b/main/src/ipc/runpane.ts @@ -624,6 +624,7 @@ async function createPaneItem( projectId: repo.id, baseBranch: item.baseBranch, toolType: 'none', + activateOnCreate: options.activate !== false, }, { timeoutMs: options.timeoutMs }); const session = sessionManager.getSession(sessionResult.sessionId); diff --git a/main/src/services/sessionManager.ts b/main/src/services/sessionManager.ts index cba2e425..cf02d41d 100644 --- a/main/src/services/sessionManager.ts +++ b/main/src/services/sessionManager.ts @@ -491,8 +491,11 @@ export class SessionManager extends EventEmitter { }); } - emitSessionCreated(session: Session): void { - this.emit('session-created', session); + emitSessionCreated(session: Session, options: { activateOnCreate?: boolean } = {}): void { + this.emit('session-created', { + ...session, + activateOnCreate: options.activateOnCreate !== false, + }); } updateSession(id: string, update: SessionUpdate): void { diff --git a/main/src/services/taskQueue.ts b/main/src/services/taskQueue.ts index 150201b6..dc553d32 100644 --- a/main/src/services/taskQueue.ts +++ b/main/src/services/taskQueue.ts @@ -40,6 +40,7 @@ interface CreateSessionJob { baseBranch?: string; toolType?: 'claude' | 'none'; startPinned?: boolean; + activateOnCreate?: boolean; } interface ContinueSessionJob { @@ -291,7 +292,9 @@ export class TaskQueue { } // Emit the session-created event BEFORE running build script so UI shows immediately - sessionManager.emitSessionCreated(session); + sessionManager.emitSessionCreated(session, { + activateOnCreate: job.data.activateOnCreate !== false, + }); // Worktree file sync — copy gitignored files in background, then run install // Fire-and-forget: copies first, then writes install command to the terminal diff --git a/main/src/types/session.ts b/main/src/types/session.ts index 336d6775..0a4bcae1 100644 --- a/main/src/types/session.ts +++ b/main/src/types/session.ts @@ -28,6 +28,7 @@ export interface Session { baseCommit?: string; baseBranch?: string; pr_renamed?: boolean; + activateOnCreate?: boolean; } export interface GitStatus {