diff --git a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
index b666dea9..24f3407e 100644
--- a/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
+++ b/apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
@@ -42,7 +42,7 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
const [isSwitching, setIsSwitching] = useState(false);
const [isActivating, setIsActivating] = useState(false);
const navigate = useNavigate();
- const setPendingTerminalCwd = useAppStore((state) => state.setPendingTerminalCwd);
+ const setPendingTerminal = useAppStore((state) => state.setPendingTerminal);
const handleSwitchBranch = useCallback(
async (worktree: WorktreeInfo, branchName: string) => {
@@ -149,13 +149,13 @@ export function useWorktreeActions({ fetchWorktrees, fetchBranches }: UseWorktre
const handleOpenInTerminal = useCallback(
(worktree: WorktreeInfo) => {
- // Set the pending terminal cwd to the worktree path
- setPendingTerminalCwd(worktree.path);
+ // Set the pending terminal with cwd and branch name
+ setPendingTerminal({ cwd: worktree.path, branchName: worktree.branch });
// Navigate to the terminal page
navigate({ to: '/terminal' });
- logger.info('Opening terminal for worktree:', worktree.path);
+ logger.info('Opening terminal for worktree:', worktree.path, 'branch:', worktree.branch);
},
- [navigate, setPendingTerminalCwd]
+ [navigate, setPendingTerminal]
);
return {
diff --git a/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx b/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx
index f1cebb10..67e4cad1 100644
--- a/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx
+++ b/apps/ui/src/components/views/settings-view/terminal/terminal-section.tsx
@@ -25,6 +25,7 @@ export function TerminalSection() {
setTerminalScrollbackLines,
setTerminalLineHeight,
setTerminalDefaultFontSize,
+ setOpenTerminalMode,
} = useAppStore();
const {
@@ -34,6 +35,7 @@ export function TerminalSection() {
scrollbackLines,
lineHeight,
defaultFontSize,
+ openTerminalMode,
} = terminalState;
return (
@@ -165,6 +167,26 @@ export function TerminalSection() {
/>
+ {/* Open in Terminal Mode */}
+
+
+
+ How to open terminals from the "Open in Terminal" action in the worktree menu
+
+
+
+
{/* Screen Reader Mode */}
diff --git a/apps/ui/src/components/views/terminal-view.tsx b/apps/ui/src/components/views/terminal-view.tsx
index 26db2a62..0f7d65b0 100644
--- a/apps/ui/src/components/views/terminal-view.tsx
+++ b/apps/ui/src/components/views/terminal-view.tsx
@@ -244,7 +244,7 @@ export function TerminalView() {
setTerminalScrollbackLines,
setTerminalScreenReaderMode,
updateTerminalPanelSizes,
- setPendingTerminalCwd,
+ setPendingTerminal,
} = useAppStore();
const [status, setStatus] = useState
(null);
@@ -538,23 +538,24 @@ export function TerminalView() {
}
}, [terminalState.isUnlocked, fetchServerSettings]);
- // Handle pending terminal cwd (from "open in terminal" action on worktree menu)
- // When pendingTerminalCwd is set and we're ready, create a terminal with that cwd
- const pendingTerminalCwdRef = useRef(null);
+ // Handle pending terminal (from "open in terminal" action on worktree menu)
+ // When pendingTerminal is set and we're ready, create a terminal based on openTerminalMode setting
+ const pendingTerminalRef = useRef(null);
const pendingTerminalCreatedRef = useRef(false);
useEffect(() => {
- const pendingCwd = terminalState.pendingTerminalCwd;
+ const pending = terminalState.pendingTerminal;
+ const openMode = terminalState.openTerminalMode;
- // Skip if no pending cwd
- if (!pendingCwd) {
- // Reset the created ref when there's no pending cwd
+ // Skip if no pending terminal
+ if (!pending) {
+ // Reset the created ref when there's no pending terminal
pendingTerminalCreatedRef.current = false;
- pendingTerminalCwdRef.current = null;
+ pendingTerminalRef.current = null;
return;
}
// Skip if we already created a terminal for this exact cwd
- if (pendingCwd === pendingTerminalCwdRef.current && pendingTerminalCreatedRef.current) {
+ if (pending.cwd === pendingTerminalRef.current && pendingTerminalCreatedRef.current) {
return;
}
@@ -571,13 +572,12 @@ export function TerminalView() {
}
// Track that we're processing this cwd
- pendingTerminalCwdRef.current = pendingCwd;
+ pendingTerminalRef.current = pending.cwd;
// Create a terminal with the pending cwd
- logger.info('Creating terminal from pending cwd:', pendingCwd);
+ logger.info('Creating terminal from pending:', pending, 'mode:', openMode);
- // Create terminal with the specified cwd
- const createTerminalWithCwd = async () => {
+ const createTerminalFromPending = async () => {
try {
const headers: Record = {};
const authToken = useAppStore.getState().terminalState.authToken;
@@ -587,46 +587,66 @@ export function TerminalView() {
const response = await apiFetch('/api/terminal/sessions', 'POST', {
headers,
- body: { cwd: pendingCwd, cols: 80, rows: 24 },
+ body: { cwd: pending.cwd, cols: 80, rows: 24 },
});
const data = await response.json();
if (data.success) {
// Mark as successfully created
pendingTerminalCreatedRef.current = true;
- addTerminalToLayout(data.data.id);
+
+ if (openMode === 'newTab') {
+ // Create a new tab named after the branch
+ const newTabId = addTerminalTab(pending.branchName);
+
+ // Set the tab's layout to the new terminal
+ useAppStore
+ .getState()
+ .setTerminalTabLayout(
+ newTabId,
+ { type: 'terminal', sessionId: data.data.id, size: 100 },
+ data.data.id
+ );
+ toast.success(`Opened terminal for ${pending.branchName}`);
+ } else {
+ // Split mode: add to current tab layout
+ addTerminalToLayout(data.data.id);
+ toast.success(`Opened terminal in ${pending.cwd.split('/').pop() || pending.cwd}`);
+ }
+
// Mark this session as new for running initial command
if (defaultRunScript) {
setNewSessionIds((prev) => new Set(prev).add(data.data.id));
}
fetchServerSettings();
- toast.success(`Opened terminal in ${pendingCwd.split('/').pop() || pendingCwd}`);
- // Clear the pending cwd after successful creation
- setPendingTerminalCwd(null);
+ // Clear the pending terminal after successful creation
+ setPendingTerminal(null);
} else {
- logger.error('Failed to create session from pending cwd:', data.error);
+ logger.error('Failed to create session from pending terminal:', data.error);
toast.error('Failed to open terminal', {
description: data.error || 'Unknown error',
});
- // Clear pending cwd on failure to prevent infinite retries
- setPendingTerminalCwd(null);
+ // Clear pending terminal on failure to prevent infinite retries
+ setPendingTerminal(null);
}
} catch (err) {
- logger.error('Create session error from pending cwd:', err);
+ logger.error('Create session error from pending terminal:', err);
toast.error('Failed to open terminal');
- // Clear pending cwd on error to prevent infinite retries
- setPendingTerminalCwd(null);
+ // Clear pending terminal on error to prevent infinite retries
+ setPendingTerminal(null);
}
};
- createTerminalWithCwd();
+ createTerminalFromPending();
}, [
- terminalState.pendingTerminalCwd,
+ terminalState.pendingTerminal,
+ terminalState.openTerminalMode,
terminalState.isUnlocked,
loading,
status?.enabled,
status?.passwordRequired,
- setPendingTerminalCwd,
+ setPendingTerminal,
+ addTerminalTab,
addTerminalToLayout,
defaultRunScript,
fetchServerSettings,
diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts
index bf1b2295..c67a0d6f 100644
--- a/apps/ui/src/store/app-store.ts
+++ b/apps/ui/src/store/app-store.ts
@@ -531,7 +531,8 @@ export interface TerminalState {
lineHeight: number; // Line height multiplier for terminal text
maxSessions: number; // Maximum concurrent terminal sessions (server setting)
lastActiveProjectPath: string | null; // Last project path to detect route changes vs project switches
- pendingTerminalCwd: string | null; // Pending cwd to use when creating next terminal (from "open in terminal" action)
+ pendingTerminal: { cwd: string; branchName: string } | null; // Pending terminal to create (from "open in terminal" action)
+ openTerminalMode: 'newTab' | 'split'; // How to open terminals from "Open in Terminal" action
}
// Persisted terminal layout - now includes sessionIds for reconnection
@@ -1230,7 +1231,8 @@ export interface AppActions {
setTerminalLineHeight: (lineHeight: number) => void;
setTerminalMaxSessions: (maxSessions: number) => void;
setTerminalLastActiveProjectPath: (projectPath: string | null) => void;
- setPendingTerminalCwd: (cwd: string | null) => void;
+ setPendingTerminal: (pending: { cwd: string; branchName: string } | null) => void;
+ setOpenTerminalMode: (mode: 'newTab' | 'split') => void;
addTerminalTab: (name?: string) => string;
removeTerminalTab: (tabId: string) => void;
setActiveTerminalTab: (tabId: string) => void;
@@ -1447,7 +1449,8 @@ const initialState: AppState = {
lineHeight: 1.0,
maxSessions: 100,
lastActiveProjectPath: null,
- pendingTerminalCwd: null,
+ pendingTerminal: null,
+ openTerminalMode: 'newTab',
},
terminalLayoutByProject: {},
specCreatingForProject: null,
@@ -2896,9 +2899,11 @@ export const useAppStore = create()((set, get) => ({
maxSessions: current.maxSessions,
// Preserve lastActiveProjectPath - it will be updated separately when needed
lastActiveProjectPath: current.lastActiveProjectPath,
- // Preserve pendingTerminalCwd - this is set by "open in terminal" action and should
+ // Preserve pendingTerminal - this is set by "open in terminal" action and should
// survive the clearTerminalState() call that happens during project switching
- pendingTerminalCwd: current.pendingTerminalCwd,
+ pendingTerminal: current.pendingTerminal,
+ // Preserve openTerminalMode - user preference
+ openTerminalMode: current.openTerminalMode,
},
});
},
@@ -2990,10 +2995,17 @@ export const useAppStore = create()((set, get) => ({
});
},
- setPendingTerminalCwd: (cwd) => {
+ setPendingTerminal: (pending) => {
const current = get().terminalState;
set({
- terminalState: { ...current, pendingTerminalCwd: cwd },
+ terminalState: { ...current, pendingTerminal: pending },
+ });
+ },
+
+ setOpenTerminalMode: (mode) => {
+ const current = get().terminalState;
+ set({
+ terminalState: { ...current, openTerminalMode: mode },
});
},