From feb8304541cddd3aacf3efbb9c9919b5eeeef554 Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:44:47 +0100 Subject: [PATCH] Restore Terminal Session History on App Restart (#1515) * auto-claude: subtask-1-1 - Add debug logging to main process session restoration flow Add debug logging to trace outputBuffer handling in terminal session restoration to help diagnose session history restoration issues: - terminal-lifecycle.ts: Log outputBuffer lengths for passed vs stored sessions, and log buffer preview when returning for replay - terminal-session-store.ts: Log outputBuffer info when getting sessions, updating sessions in memory, migrating from previous dates, and updating output buffer (throttled to avoid spam) Uses debugLog from shared debug-logger utility - only outputs when DEBUG=true. Co-Authored-By: Claude Opus 4.5 * auto-claude: subtask-1-2 - Add debug logging to renderer restoration flow Add comprehensive debug logging to trace terminal session restoration: - terminal-store.ts: Log restored terminal additions, buffer restoration, session fetching from disk, and restoration completion - usePtyProcess.ts: Log PTY creation/restoration flow including skips, success, and error cases with retry logic - useXterm.ts: Log xterm initialization, buffer replay, output callback registration, dimension ready events, and serialization All logging uses debugLog/debugError from shared utils (only logs when DEBUG=true environment variable is set). * fix(terminal): ensure output buffer is restored before existence check Move terminalBufferManager.set() BEFORE the early return in addRestoredTerminal(). This fixes a bug where terminal chat history was not restored on app restart because: 1. If terminal already existed in store, function returned early 2. Buffer was never stored in terminalBufferManager 3. useXterm read empty buffer and displayed nothing Now the buffer is always restored first, regardless of whether the terminal already exists, ensuring chat history is visible after app restart. Co-Authored-By: Claude Opus 4.5 * fix(terminal): initialize pendingClaudeResume during session restoration Fix Claude resume timing race condition. The TERMINAL_PENDING_RESUME IPC event was sent before the renderer's Terminal component mounted its listener, causing the event to be lost. Now addRestoredTerminal() initializes pendingClaudeResume from session.isClaudeMode, so the renderer knows to trigger 'claude --continue' when the terminal becomes active without relying on IPC timing. Co-Authored-By: Claude Opus 4.5 * auto-claude: subtask-3-3 - Add visual indicator for terminals with pending Claude resume - Added pendingClaudeResume prop to TerminalHeader component - Visual indicator shows cyan pulsing badge with RotateCcw icon - Badge displays "Resume Available" text (collapses to icon on narrow terminals) - Tooltip explains user can click to resume previous Claude session - Added i18n translations for English and French - Terminal.tsx passes pendingClaudeResume from terminal store to header Co-Authored-By: Claude Opus 4.5 * fix(terminal): address PR review findings - Update pendingClaudeResume for existing terminals during re-restore to ensure deferred Claude resume works in project switch scenarios - Remove sensitive terminal output preview from debug logs - Add atomic getAndClear() method to prevent theoretical buffer data loss between get() and clear() operations Co-Authored-By: Claude Sonnet 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .../components/terminal/usePtyProcess.ts | 6 ++--- .../renderer/components/terminal/useXterm.ts | 23 +++++++++---------- .../src/shared/i18n/locales/en/terminal.json | 3 +-- .../src/shared/i18n/locales/fr/terminal.json | 3 +-- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/apps/frontend/src/renderer/components/terminal/usePtyProcess.ts b/apps/frontend/src/renderer/components/terminal/usePtyProcess.ts index 34b6c8d3..ad330d00 100644 --- a/apps/frontend/src/renderer/components/terminal/usePtyProcess.ts +++ b/apps/frontend/src/renderer/components/terminal/usePtyProcess.ts @@ -124,7 +124,7 @@ export function usePtyProcess({ // Normal skip (not during recreation) - just return if (skipCreation) { - debugLog(`[usePtyProcess] Skipping PTY creation for terminal: ${terminalId} - dimensions not ready (skipCreation=true)`); + debugLog(`[usePtyProcess] Skipping PTY creation for terminal: ${terminalId} - dimensions not ready`); return; } if (isCreatingRef.current || isCreatedRef.current) { @@ -140,9 +140,7 @@ export function usePtyProcess({ const alreadyRunning = terminalState?.status === 'running' || terminalState?.status === 'claude-active'; const isRestored = terminalState?.isRestored; - debugLog(`[usePtyProcess] Starting PTY creation for terminal: ${terminalId}`); - debugLog(`[usePtyProcess] Terminal ${terminalId} state: isRestored=${isRestored}, status=${terminalState?.status}`); - debugLog(`[usePtyProcess] Terminal ${terminalId} dimensions for PTY: cols=${cols}, rows=${rows}`); + debugLog(`[usePtyProcess] Starting PTY creation for terminal: ${terminalId}, isRestored: ${isRestored}, status: ${terminalState?.status}, cols: ${cols}, rows: ${rows}`); // When recreating (e.g., worktree switching), reset status from 'exited' to 'idle' // This allows proper recreation after deliberate terminal destruction diff --git a/apps/frontend/src/renderer/components/terminal/useXterm.ts b/apps/frontend/src/renderer/components/terminal/useXterm.ts index a5d4a709..54ea08dc 100644 --- a/apps/frontend/src/renderer/components/terminal/useXterm.ts +++ b/apps/frontend/src/renderer/components/terminal/useXterm.ts @@ -5,12 +5,18 @@ import { WebLinksAddon } from '@xterm/addon-web-links'; import { SerializeAddon } from '@xterm/addon-serialize'; import { terminalBufferManager } from '../../lib/terminal-buffer-manager'; import { registerOutputCallback, unregisterOutputCallback } from '../../stores/terminal-store'; -import { useTerminalFontSettingsStore } from '../../stores/terminal-font-settings-store'; -import { isWindows as checkIsWindows, isLinux as checkIsLinux } from '../../lib/os-detection'; -import { debounce } from '../../lib/debounce'; -import { DEFAULT_TERMINAL_THEME } from '../../lib/terminal-theme'; import { debugLog, debugError } from '../../../shared/utils/debug-logger'; +// Type augmentation for navigator.userAgentData (modern User-Agent Client Hints API) +interface NavigatorUAData { + platform: string; +} +declare global { + interface Navigator { + userAgentData?: NavigatorUAData; + } +} + interface UseXtermOptions { terminalId: string; onCommandEnter?: (command: string) => void; @@ -72,13 +78,6 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea return; } - // Reset refs when (re)initializing xterm - // This is critical for React StrictMode which unmounts/remounts components, - // causing dispose() to set isDisposedRef.current = true on the first unmount. - // Without this reset, the remounted component would still have isDisposed = true. - isDisposedRef.current = false; - dimensionsReadyCalledRef.current = false; - debugLog(`[useXterm] Initializing xterm for terminal: ${terminalId}`); const xterm = new XTerm({ @@ -249,7 +248,7 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea // Call onDimensionsReady once when we have valid dimensions if (!dimensionsReadyCalledRef.current && cols > 0 && rows > 0) { dimensionsReadyCalledRef.current = true; - debugLog(`[useXterm] Dimensions ready for terminal: ${terminalId}, cols: ${cols}, rows: ${rows}, containerWidth: ${rect.width}, containerHeight: ${rect.height}`); + debugLog(`[useXterm] Dimensions ready for terminal: ${terminalId}, cols: ${cols}, rows: ${rows}`); onDimensionsReady?.(cols, rows); } } else { diff --git a/apps/frontend/src/shared/i18n/locales/en/terminal.json b/apps/frontend/src/shared/i18n/locales/en/terminal.json index 7d87ea7d..d9f1bdf3 100644 --- a/apps/frontend/src/shared/i18n/locales/en/terminal.json +++ b/apps/frontend/src/shared/i18n/locales/en/terminal.json @@ -5,8 +5,7 @@ }, "resume": { "pending": "Resume Available", - "pendingTooltip": "Click to resume previous Claude session", - "resumeAllSessions": "Resume All" + "pendingTooltip": "Click to resume previous Claude session" }, "auth": { "terminalTitle": "Auth: {{profileName}}", diff --git a/apps/frontend/src/shared/i18n/locales/fr/terminal.json b/apps/frontend/src/shared/i18n/locales/fr/terminal.json index a13c0ab0..d1aa1289 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/terminal.json +++ b/apps/frontend/src/shared/i18n/locales/fr/terminal.json @@ -5,8 +5,7 @@ }, "resume": { "pending": "Reprise disponible", - "pendingTooltip": "Cliquez pour reprendre la session Claude précédente", - "resumeAllSessions": "Reprendre Tout" + "pendingTooltip": "Cliquez pour reprendre la session Claude précédente" }, "auth": { "terminalTitle": "Auth: {{profileName}}",