diff --git a/apps/frontend/src/main/terminal/terminal-lifecycle.ts b/apps/frontend/src/main/terminal/terminal-lifecycle.ts index 22d7eaec..bb1f9141 100644 --- a/apps/frontend/src/main/terminal/terminal-lifecycle.ts +++ b/apps/frontend/src/main/terminal/terminal-lifecycle.ts @@ -189,6 +189,9 @@ export async function restoreTerminal( const win = getWindow(); if (win) { win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, session.id, session.title); + // Always sync worktreeConfig to renderer (even if undefined) to ensure correct state + // This handles both: showing labels after recovery AND clearing stale labels when worktrees are deleted + win.webContents.send(IPC_CHANNELS.TERMINAL_WORKTREE_CONFIG_CHANGE, session.id, terminal.worktreeConfig); } // Defer Claude resume until terminal becomes active (is viewed by user) diff --git a/apps/frontend/src/preload/api/terminal-api.ts b/apps/frontend/src/preload/api/terminal-api.ts index 7ea08f71..c66e0c2f 100644 --- a/apps/frontend/src/preload/api/terminal-api.ts +++ b/apps/frontend/src/preload/api/terminal-api.ts @@ -69,6 +69,7 @@ export interface TerminalAPI { onTerminalOutput: (callback: (id: string, data: string) => void) => () => void; onTerminalExit: (callback: (id: string, exitCode: number) => void) => () => void; onTerminalTitleChange: (callback: (id: string, title: string) => void) => () => void; + onTerminalWorktreeConfigChange: (callback: (id: string, config: TerminalWorktreeConfig | undefined) => void) => () => void; onTerminalClaudeSession: (callback: (id: string, sessionId: string) => void) => () => void; onTerminalRateLimit: (callback: (info: RateLimitInfo) => void) => () => void; onTerminalOAuthToken: ( @@ -227,6 +228,22 @@ export const createTerminalAPI = (): TerminalAPI => ({ }; }, + onTerminalWorktreeConfigChange: ( + callback: (id: string, config: TerminalWorktreeConfig | undefined) => void + ): (() => void) => { + const handler = ( + _event: Electron.IpcRendererEvent, + id: string, + config: TerminalWorktreeConfig | undefined + ): void => { + callback(id, config); + }; + ipcRenderer.on(IPC_CHANNELS.TERMINAL_WORKTREE_CONFIG_CHANGE, handler); + return () => { + ipcRenderer.removeListener(IPC_CHANNELS.TERMINAL_WORKTREE_CONFIG_CHANGE, handler); + }; + }, + onTerminalClaudeSession: ( callback: (id: string, sessionId: string) => void ): (() => void) => { diff --git a/apps/frontend/src/renderer/components/terminal/useTerminalEvents.ts b/apps/frontend/src/renderer/components/terminal/useTerminalEvents.ts index 60e60d9f..47dbe0c3 100644 --- a/apps/frontend/src/renderer/components/terminal/useTerminalEvents.ts +++ b/apps/frontend/src/renderer/components/terminal/useTerminalEvents.ts @@ -114,6 +114,18 @@ export function useTerminalEvents({ return cleanup; }, [terminalId]); + // Handle worktree config change (synced from main process during restoration) + // This ensures the worktree label appears after terminal recovery + useEffect(() => { + const cleanup = window.electronAPI.onTerminalWorktreeConfigChange((id, config) => { + if (id === terminalId) { + useTerminalStore.getState().setWorktreeConfig(terminalId, config); + } + }); + + return cleanup; + }, [terminalId]); + // Handle Claude session ID capture useEffect(() => { const cleanup = window.electronAPI.onTerminalClaudeSession((id, sessionId) => { diff --git a/apps/frontend/src/renderer/lib/mocks/terminal-mock.ts b/apps/frontend/src/renderer/lib/mocks/terminal-mock.ts index 4a806165..8888707c 100644 --- a/apps/frontend/src/renderer/lib/mocks/terminal-mock.ts +++ b/apps/frontend/src/renderer/lib/mocks/terminal-mock.ts @@ -92,6 +92,7 @@ export const terminalMock = { onTerminalOutput: () => () => {}, onTerminalExit: () => () => {}, onTerminalTitleChange: () => () => {}, + onTerminalWorktreeConfigChange: () => () => {}, onTerminalClaudeSession: () => () => {}, onTerminalRateLimit: () => () => {}, onTerminalOAuthToken: () => () => {}, diff --git a/apps/frontend/src/shared/constants/ipc.ts b/apps/frontend/src/shared/constants/ipc.ts index a95ef12c..0ea976ed 100644 --- a/apps/frontend/src/shared/constants/ipc.ts +++ b/apps/frontend/src/shared/constants/ipc.ts @@ -88,6 +88,7 @@ export const IPC_CHANNELS = { TERMINAL_OUTPUT: 'terminal:output', TERMINAL_EXIT: 'terminal:exit', TERMINAL_TITLE_CHANGE: 'terminal:titleChange', + TERMINAL_WORKTREE_CONFIG_CHANGE: 'terminal:worktreeConfigChange', // Worktree config restored/changed (for sync on recovery) TERMINAL_CLAUDE_SESSION: 'terminal:claudeSession', // Claude session ID captured TERMINAL_PENDING_RESUME: 'terminal:pendingResume', // Terminal has pending Claude resume (for deferred activation) TERMINAL_RATE_LIMIT: 'terminal:rateLimit', // Claude Code rate limit detected diff --git a/apps/frontend/src/shared/types/ipc.ts b/apps/frontend/src/shared/types/ipc.ts index d37617db..5afa310e 100644 --- a/apps/frontend/src/shared/types/ipc.ts +++ b/apps/frontend/src/shared/types/ipc.ts @@ -219,6 +219,8 @@ export interface ElectronAPI { onTerminalOutput: (callback: (id: string, data: string) => void) => () => void; onTerminalExit: (callback: (id: string, exitCode: number) => void) => () => void; onTerminalTitleChange: (callback: (id: string, title: string) => void) => () => void; + /** Listen for worktree config changes (synced from main process during restoration) */ + onTerminalWorktreeConfigChange: (callback: (id: string, config: TerminalWorktreeConfig | undefined) => void) => () => void; onTerminalClaudeSession: (callback: (id: string, sessionId: string) => void) => () => void; onTerminalRateLimit: (callback: (info: RateLimitInfo) => void) => () => void; /** Listen for OAuth authentication completion (token is auto-saved to profile, never exposed to frontend) */