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 />
</SelectTrigger>
<SelectContent>
<SelectItem value="newTab">New Tab (named after branch)</SelectItem>
<SelectItem value="newTab">New Tab</SelectItem>
<SelectItem value="split">Split Current Tab</SelectItem>
</SelectContent>
</Select>

View File

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

View File

@@ -21,6 +21,7 @@ import {
Maximize2,
Minimize2,
ArrowDown,
GitBranch,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
@@ -94,6 +95,7 @@ interface TerminalPanelProps {
onCommandRan?: () => void; // Callback when the initial command has been sent
isMaximized?: boolean;
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
@@ -124,6 +126,7 @@ export function TerminalPanel({
onCommandRan,
isMaximized = false,
onToggleMaximize,
branchName,
}: TerminalPanelProps) {
const terminalRef = 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">
<Terminal className="h-3 w-3 shrink-0 text-muted-foreground" />
<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 */}
{fontSize !== DEFAULT_FONT_SIZE && (
<button

View File

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

View File

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

View File

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