feat(ui): display branch name in terminal header with git icon

- Move branch name display from tab name to terminal header
- Show full branch name (no truncation) with GitBranch icon
- Display branch name for both 'new tab' and 'split' modes
- Persist openTerminalMode setting to server and include in import/export
- Update settings dropdown to simplified "New Tab" label
This commit is contained in:
Stefan de Vogelaere
2026-01-17 20:43:23 +01:00
parent ce4b9b6a14
commit 502361fc7c
7 changed files with 53 additions and 18 deletions

View File

@@ -181,7 +181,7 @@ export function TerminalSection() {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="newTab">New Tab (named after branch)</SelectItem> <SelectItem value="newTab">New Tab</SelectItem>
<SelectItem value="split">Split Current Tab</SelectItem> <SelectItem value="split">Split Current Tab</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>

View File

@@ -596,22 +596,27 @@ export function TerminalView() {
pendingTerminalCreatedRef.current = true; pendingTerminalCreatedRef.current = true;
if (openMode === 'newTab') { if (openMode === 'newTab') {
// Create a new tab named after the branch // Create a new tab with default naming
const newTabId = addTerminalTab(pending.branchName); const newTabId = addTerminalTab();
// Set the tab's layout to the new terminal // Set the tab's layout to the new terminal with branch name for display in header
useAppStore useAppStore
.getState() .getState()
.setTerminalTabLayout( .setTerminalTabLayout(
newTabId, newTabId,
{ type: 'terminal', sessionId: data.data.id, size: 100 }, {
type: 'terminal',
sessionId: data.data.id,
size: 100,
branchName: pending.branchName,
},
data.data.id data.data.id
); );
toast.success(`Opened terminal for ${pending.branchName}`); toast.success(`Opened terminal for ${pending.branchName}`);
} else { } else {
// Split mode: add to current tab layout // Split mode: add to current tab layout with branch name
addTerminalToLayout(data.data.id); addTerminalToLayout(data.data.id, 'horizontal', undefined, pending.branchName);
toast.success(`Opened terminal in ${pending.cwd.split('/').pop() || pending.cwd}`); toast.success(`Opened terminal for ${pending.branchName}`);
} }
// Mark this session as new for running initial command // Mark this session as new for running initial command
@@ -789,6 +794,7 @@ export function TerminalView() {
sessionId, sessionId,
size: persisted.size, size: persisted.size,
fontSize: persisted.fontSize, fontSize: persisted.fontSize,
branchName: persisted.branchName,
}; };
} }
@@ -1347,6 +1353,7 @@ export function TerminalView() {
onCommandRan={() => handleCommandRan(content.sessionId)} onCommandRan={() => handleCommandRan(content.sessionId)}
isMaximized={terminalState.maximizedSessionId === content.sessionId} isMaximized={terminalState.maximizedSessionId === content.sessionId}
onToggleMaximize={() => toggleTerminalMaximized(content.sessionId)} onToggleMaximize={() => toggleTerminalMaximized(content.sessionId)}
branchName={content.branchName}
/> />
</TerminalErrorBoundary> </TerminalErrorBoundary>
); );

View File

@@ -21,6 +21,7 @@ import {
Maximize2, Maximize2,
Minimize2, Minimize2,
ArrowDown, ArrowDown,
GitBranch,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner'; import { Spinner } from '@/components/ui/spinner';
@@ -94,6 +95,7 @@ interface TerminalPanelProps {
onCommandRan?: () => void; // Callback when the initial command has been sent onCommandRan?: () => void; // Callback when the initial command has been sent
isMaximized?: boolean; isMaximized?: boolean;
onToggleMaximize?: () => void; onToggleMaximize?: () => void;
branchName?: string; // Branch name to display in header (from "Open in Terminal" action)
} }
// Type for xterm Terminal - we'll use any since we're dynamically importing // Type for xterm Terminal - we'll use any since we're dynamically importing
@@ -124,6 +126,7 @@ export function TerminalPanel({
onCommandRan, onCommandRan,
isMaximized = false, isMaximized = false,
onToggleMaximize, onToggleMaximize,
branchName,
}: TerminalPanelProps) { }: TerminalPanelProps) {
const terminalRef = useRef<HTMLDivElement>(null); const terminalRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -1776,6 +1779,13 @@ export function TerminalPanel({
<div className="flex items-center gap-1.5 flex-1 min-w-0"> <div className="flex items-center gap-1.5 flex-1 min-w-0">
<Terminal className="h-3 w-3 shrink-0 text-muted-foreground" /> <Terminal className="h-3 w-3 shrink-0 text-muted-foreground" />
<span className="text-xs truncate text-foreground">{shellName}</span> <span className="text-xs truncate text-foreground">{shellName}</span>
{/* Branch name indicator - show when terminal was opened from worktree */}
{branchName && (
<span className="flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded bg-brand-500/10 text-brand-500 shrink-0">
<GitBranch className="h-2.5 w-2.5 shrink-0" />
<span>{branchName}</span>
</span>
)}
{/* Font size indicator - only show when not default */} {/* Font size indicator - only show when not default */}
{fontSize !== DEFAULT_FONT_SIZE && ( {fontSize !== DEFAULT_FONT_SIZE && (
<button <button

View File

@@ -36,6 +36,7 @@ const SETTINGS_FIELDS_TO_SYNC = [
'fontFamilySans', 'fontFamilySans',
'fontFamilyMono', 'fontFamilyMono',
'terminalFontFamily', // Maps to terminalState.fontFamily 'terminalFontFamily', // Maps to terminalState.fontFamily
'openTerminalMode', // Maps to terminalState.openTerminalMode
'sidebarOpen', 'sidebarOpen',
'chatHistoryOpen', 'chatHistoryOpen',
'maxConcurrency', 'maxConcurrency',
@@ -101,6 +102,9 @@ function getSettingsFieldValue(
if (field === 'terminalFontFamily') { if (field === 'terminalFontFamily') {
return appState.terminalState.fontFamily; return appState.terminalState.fontFamily;
} }
if (field === 'openTerminalMode') {
return appState.terminalState.openTerminalMode;
}
return appState[field as keyof typeof appState]; return appState[field as keyof typeof appState];
} }
@@ -128,6 +132,9 @@ function hasSettingsFieldChanged(
if (field === 'terminalFontFamily') { if (field === 'terminalFontFamily') {
return newState.terminalState.fontFamily !== prevState.terminalState.fontFamily; return newState.terminalState.fontFamily !== prevState.terminalState.fontFamily;
} }
if (field === 'openTerminalMode') {
return newState.terminalState.openTerminalMode !== prevState.terminalState.openTerminalMode;
}
const key = field as keyof typeof newState; const key = field as keyof typeof newState;
return newState[key] !== prevState[key]; return newState[key] !== prevState[key];
} }
@@ -571,11 +578,16 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
worktreePanelCollapsed: serverSettings.worktreePanelCollapsed ?? false, worktreePanelCollapsed: serverSettings.worktreePanelCollapsed ?? false,
lastProjectDir: serverSettings.lastProjectDir ?? '', lastProjectDir: serverSettings.lastProjectDir ?? '',
recentFolders: serverSettings.recentFolders ?? [], recentFolders: serverSettings.recentFolders ?? [],
// Terminal font (nested in terminalState) // Terminal settings (nested in terminalState)
...(serverSettings.terminalFontFamily && { ...((serverSettings.terminalFontFamily || serverSettings.openTerminalMode) && {
terminalState: { terminalState: {
...currentAppState.terminalState, ...currentAppState.terminalState,
fontFamily: serverSettings.terminalFontFamily, ...(serverSettings.terminalFontFamily && {
fontFamily: serverSettings.terminalFontFamily,
}),
...(serverSettings.openTerminalMode && {
openTerminalMode: serverSettings.openTerminalMode,
}),
}, },
}), }),
}); });

View File

@@ -1805,8 +1805,6 @@ export class HttpApiClient implements ElectronAPI {
this.post('/api/worktree/switch-branch', { worktreePath, branchName }), this.post('/api/worktree/switch-branch', { worktreePath, branchName }),
openInEditor: (worktreePath: string, editorCommand?: string) => openInEditor: (worktreePath: string, editorCommand?: string) =>
this.post('/api/worktree/open-in-editor', { worktreePath, editorCommand }), this.post('/api/worktree/open-in-editor', { worktreePath, editorCommand }),
openInTerminal: (worktreePath: string) =>
this.post('/api/worktree/open-in-terminal', { worktreePath }),
getDefaultEditor: () => this.get('/api/worktree/default-editor'), getDefaultEditor: () => this.get('/api/worktree/default-editor'),
getAvailableEditors: () => this.get('/api/worktree/available-editors'), getAvailableEditors: () => this.get('/api/worktree/available-editors'),
refreshEditors: () => this.post('/api/worktree/refresh-editors', {}), refreshEditors: () => this.post('/api/worktree/refresh-editors', {}),

View File

@@ -500,7 +500,7 @@ export interface ProjectAnalysis {
// Terminal panel layout types (recursive for splits) // Terminal panel layout types (recursive for splits)
export type TerminalPanelContent = export type TerminalPanelContent =
| { type: 'terminal'; sessionId: string; size?: number; fontSize?: number } | { type: 'terminal'; sessionId: string; size?: number; fontSize?: number; branchName?: string }
| { | {
type: 'split'; type: 'split';
id: string; // Stable ID for React key stability id: string; // Stable ID for React key stability
@@ -538,7 +538,7 @@ export interface TerminalState {
// Persisted terminal layout - now includes sessionIds for reconnection // Persisted terminal layout - now includes sessionIds for reconnection
// Used to restore terminal layout structure when switching projects // Used to restore terminal layout structure when switching projects
export type PersistedTerminalPanel = export type PersistedTerminalPanel =
| { type: 'terminal'; size?: number; fontSize?: number; sessionId?: string } | { type: 'terminal'; size?: number; fontSize?: number; sessionId?: string; branchName?: string }
| { | {
type: 'split'; type: 'split';
id?: string; // Optional for backwards compatibility with older persisted layouts id?: string; // Optional for backwards compatibility with older persisted layouts
@@ -576,6 +576,7 @@ export interface PersistedTerminalSettings {
scrollbackLines: number; scrollbackLines: number;
lineHeight: number; lineHeight: number;
maxSessions: number; maxSessions: number;
openTerminalMode: 'newTab' | 'split';
} }
/** State for worktree init script execution */ /** State for worktree init script execution */
@@ -1217,7 +1218,8 @@ export interface AppActions {
addTerminalToLayout: ( addTerminalToLayout: (
sessionId: string, sessionId: string,
direction?: 'horizontal' | 'vertical', direction?: 'horizontal' | 'vertical',
targetSessionId?: string targetSessionId?: string,
branchName?: string
) => void; ) => void;
removeTerminalFromLayout: (sessionId: string) => void; removeTerminalFromLayout: (sessionId: string) => void;
swapTerminals: (sessionId1: string, sessionId2: string) => void; swapTerminals: (sessionId1: string, sessionId2: string) => void;
@@ -2676,12 +2678,13 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
}); });
}, },
addTerminalToLayout: (sessionId, direction = 'horizontal', targetSessionId) => { addTerminalToLayout: (sessionId, direction = 'horizontal', targetSessionId, branchName) => {
const current = get().terminalState; const current = get().terminalState;
const newTerminal: TerminalPanelContent = { const newTerminal: TerminalPanelContent = {
type: 'terminal', type: 'terminal',
sessionId, sessionId,
size: 50, size: 50,
branchName,
}; };
// If no tabs, create first tab // If no tabs, create first tab
@@ -2694,7 +2697,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
{ {
id: newTabId, id: newTabId,
name: 'Terminal 1', name: 'Terminal 1',
layout: { type: 'terminal', sessionId, size: 100 }, layout: { type: 'terminal', sessionId, size: 100, branchName },
}, },
], ],
activeTabId: newTabId, activeTabId: newTabId,
@@ -3396,6 +3399,7 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
size: panel.size, size: panel.size,
fontSize: panel.fontSize, fontSize: panel.fontSize,
sessionId: panel.sessionId, // Preserve for reconnection sessionId: panel.sessionId, // Preserve for reconnection
branchName: panel.branchName, // Preserve branch name for display
}; };
} }
return { return {

View File

@@ -475,6 +475,10 @@ export interface GlobalSettings {
/** Terminal font family (undefined = use default Menlo/Monaco) */ /** Terminal font family (undefined = use default Menlo/Monaco) */
terminalFontFamily?: string; terminalFontFamily?: string;
// Terminal Configuration
/** How to open terminals from "Open in Terminal" worktree action */
openTerminalMode?: 'newTab' | 'split';
// UI State Preferences // UI State Preferences
/** Whether sidebar is currently open */ /** Whether sidebar is currently open */
sidebarOpen: boolean; sidebarOpen: boolean;