Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions frontend/src/stores/sessionStore.test.ts
Original file line number Diff line number Diff line change
@@ -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> = {}): 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');
});
});
3 changes: 2 additions & 1 deletion frontend/src/stores/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export const useSessionStore = create<SessionStore>((set, get) => ({

addSession: (session) => set((state) => {
const normalizedSession = normalizeSession(session);
const shouldActivate = normalizedSession.activateOnCreate !== false;

// Initialize arrays if they don't exist
const sessionWithArrays = {
Expand All @@ -102,7 +103,7 @@ export const useSessionStore = create<SessionStore>((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
};
}),

Expand Down
1 change: 1 addition & 0 deletions frontend/src/types/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export interface Session {
gitStatus?: GitStatus;
baseCommit?: string;
baseBranch?: string;
activateOnCreate?: boolean;
}

export interface GitStatus {
Expand Down
1 change: 1 addition & 0 deletions main/src/ipc/runpane.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions main/src/ipc/runpane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions main/src/services/sessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion main/src/services/taskQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface CreateSessionJob {
baseBranch?: string;
toolType?: 'claude' | 'none';
startPinned?: boolean;
activateOnCreate?: boolean;
}

interface ContinueSessionJob {
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions main/src/types/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Session {
baseCommit?: string;
baseBranch?: string;
pr_renamed?: boolean;
activateOnCreate?: boolean;
}

export interface GitStatus {
Expand Down
Loading