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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-01-26 09:44:47 +01:00
committed by GitHub
parent b269ac3050
commit 63e2847fc5
10 changed files with 181 additions and 18 deletions
@@ -2,6 +2,7 @@ import { app } from 'electron';
import { join } from 'path';
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync, promises as fsPromises } from 'fs';
import type { TerminalWorktreeConfig } from '../shared/types';
import { debugLog } from '../shared/utils/debug-logger';
/**
* Persisted terminal session data
@@ -376,12 +377,24 @@ export class TerminalSessionStore {
todaySessions[projectPath] = [];
}
// Debug: Log incoming outputBuffer info
const incomingBufferLen = session.outputBuffer?.length ?? 0;
debugLog('[TerminalSessionStore] Updating session in memory:', session.id,
'incoming outputBuffer:', incomingBufferLen, 'bytes',
'isClaudeMode:', session.isClaudeMode);
// Update existing or add new
const existingIndex = todaySessions[projectPath].findIndex(s => s.id === session.id);
if (existingIndex >= 0) {
// Preserve displayOrder from existing session if not provided in incoming session
// This prevents periodic saves (which don't include displayOrder) from losing tab order
const existingSession = todaySessions[projectPath][existingIndex];
const existingBufferLen = existingSession.outputBuffer?.length ?? 0;
const truncatedLen = session.outputBuffer.slice(-MAX_OUTPUT_BUFFER).length;
debugLog('[TerminalSessionStore] Updating existing session:', session.id,
'existing outputBuffer:', existingBufferLen, 'bytes',
'new outputBuffer (after truncation):', truncatedLen, 'bytes');
todaySessions[projectPath][existingIndex] = {
...session,
// Limit output buffer size
@@ -391,6 +404,10 @@ export class TerminalSessionStore {
displayOrder: session.displayOrder ?? existingSession.displayOrder,
};
} else {
const truncatedLen = session.outputBuffer.slice(-MAX_OUTPUT_BUFFER).length;
debugLog('[TerminalSessionStore] Creating new session:', session.id,
'outputBuffer (after truncation):', truncatedLen, 'bytes');
todaySessions[projectPath].push({
...session,
outputBuffer: session.outputBuffer.slice(-MAX_OUTPUT_BUFFER),
@@ -437,9 +454,18 @@ export class TerminalSessionStore {
getSessions(projectPath: string): TerminalSession[] {
const today = getDateString();
debugLog('[TerminalSessionStore] Getting sessions for project:', projectPath, 'date:', today);
// First check today
const todaySessions = this.getTodaysSessions();
if (todaySessions[projectPath]?.length > 0) {
// Debug: Log outputBuffer info for each session
for (const session of todaySessions[projectPath]) {
const bufferLen = session.outputBuffer?.length ?? 0;
debugLog('[TerminalSessionStore] Session', session.id, 'outputBuffer:', bufferLen, 'bytes',
'isClaudeMode:', session.isClaudeMode,
'hasBuffer:', bufferLen > 0);
}
// Validate worktree configs before returning
return todaySessions[projectPath].map(session => ({
...session,
@@ -462,6 +488,15 @@ export class TerminalSessionStore {
console.warn(`[TerminalSessionStore] No sessions today, migrating sessions from ${mostRecentDate} to today`);
const sessions = this.data.sessionsByDate[mostRecentDate][projectPath] || [];
// Debug: Log outputBuffer info for sessions being migrated
for (const session of sessions) {
const bufferLen = session.outputBuffer?.length ?? 0;
debugLog('[TerminalSessionStore] Migrating session', session.id, 'from', mostRecentDate,
'outputBuffer:', bufferLen, 'bytes',
'isClaudeMode:', session.isClaudeMode,
'hasBuffer:', bufferLen > 0);
}
// MIGRATE: Copy sessions to today's bucket with validated worktree configs
const migratedSessions = sessions.map(session => ({
...session,
@@ -629,8 +664,16 @@ export class TerminalSessionStore {
const session = sessions.find(s => s.id === sessionId);
if (session) {
const prevLen = session.outputBuffer?.length ?? 0;
session.outputBuffer = (session.outputBuffer + output).slice(-MAX_OUTPUT_BUFFER);
const newLen = session.outputBuffer.length;
session.lastActiveAt = new Date().toISOString();
// Debug: Log buffer update (throttled to avoid spam - only log when significant changes)
if (newLen - prevLen > 1000 || prevLen === 0) {
debugLog('[TerminalSessionStore] updateOutputBuffer:', sessionId,
'prev:', prevLen, 'bytes, added:', output.length, 'bytes, new total:', newLen, 'bytes');
}
// Note: We don't save immediately here to avoid excessive disk writes
// Call saveAllPending() periodically or on app quit
}
@@ -149,6 +149,11 @@ export async function restoreTerminal(
'Stored Claude mode:', storedIsClaudeMode,
'Stored session ID:', storedClaudeSessionId);
// Debug: Log outputBuffer info from both passed and stored session
const passedBufferLen = session.outputBuffer?.length ?? 0;
const storedBufferLen = storedSession?.outputBuffer?.length ?? 0;
debugLog('[TerminalLifecycle] OutputBuffer info - passed session:', passedBufferLen, 'bytes, stored session:', storedBufferLen, 'bytes');
// Validate cwd exists - if the directory was deleted (e.g., worktree removed),
// fall back to project path to prevent shell exit with code 1
let effectiveCwd = session.cwd;
@@ -235,6 +240,12 @@ export async function restoreTerminal(
}
}
// Debug: Log the outputBuffer being returned for replay
const returnBufferLen = session.outputBuffer?.length ?? 0;
debugLog('[TerminalLifecycle] Returning outputBuffer for terminal:', session.id,
'length:', returnBufferLen, 'bytes',
'hasContent:', returnBufferLen > 0);
return {
success: true,
outputBuffer: session.outputBuffer
@@ -439,6 +439,7 @@ Please confirm you're ready by saying: I'm ready to work on ${selectedTask.title
dragHandleListeners={dragHandleListeners}
isExpanded={isExpanded}
onToggleExpand={onToggleExpand}
pendingClaudeResume={terminal?.pendingClaudeResume}
/>
<div
@@ -1,4 +1,4 @@
import { X, Sparkles, TerminalSquare, FolderGit, ExternalLink, GripVertical, Maximize2, Minimize2 } from 'lucide-react';
import { X, Sparkles, TerminalSquare, FolderGit, ExternalLink, GripVertical, Maximize2, Minimize2, RotateCcw } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import type { Task, TerminalWorktreeConfig } from '../../../shared/types';
@@ -40,6 +40,8 @@ interface TerminalHeaderProps {
isExpanded?: boolean;
/** Callback to toggle expanded state */
onToggleExpand?: () => void;
/** Whether this terminal has a pending Claude resume (deferred until tab activated) */
pendingClaudeResume?: boolean;
}
export function TerminalHeader({
@@ -64,6 +66,7 @@ export function TerminalHeader({
dragHandleListeners,
isExpanded,
onToggleExpand,
pendingClaudeResume,
}: TerminalHeaderProps) {
const { t } = useTranslation(['terminal', 'common']);
const backlogTasks = tasks.filter((t) => t.status === 'backlog');
@@ -106,6 +109,15 @@ export function TerminalHeader({
{terminalCount < 4 && <span>Claude</span>}
</span>
)}
{pendingClaudeResume && (
<span
className="flex items-center gap-1 text-[10px] font-medium text-cyan-500 bg-cyan-500/10 px-1.5 py-0.5 rounded animate-pulse"
title={t('terminal:resume.pendingTooltip')}
>
<RotateCcw className="h-2.5 w-2.5" />
{terminalCount < 4 && <span>{t('terminal:resume.pending')}</span>}
</span>
)}
{isClaudeMode && (
<TaskSelector
terminalId={terminalId}
@@ -1,5 +1,6 @@
import { useEffect, useRef, useCallback, useState, type RefObject } from 'react';
import { useTerminalStore } from '../../stores/terminal-store';
import { debugLog, debugError } from '../../../shared/utils/debug-logger';
// Maximum retry attempts for recreation when dimensions aren't ready
// Increased from 10 to 30 (3 seconds total) to handle slow app startup scenarios
@@ -116,13 +117,20 @@ export function usePtyProcess({
// During recreation, if dimensions aren't ready, schedule a retry instead of giving up
if (skipCreation && isRecreatingRef?.current) {
debugLog(`[usePtyProcess] Skipping PTY creation for terminal: ${terminalId} - dimensions not ready during recreation, scheduling retry`);
scheduleRetryOrFail('Terminal recreation failed: dimensions not ready');
return;
}
// Normal skip (not during recreation) - just return
if (skipCreation) return;
if (isCreatingRef.current || isCreatedRef.current) return;
if (skipCreation) {
debugLog(`[usePtyProcess] Skipping PTY creation for terminal: ${terminalId} - dimensions not ready`);
return;
}
if (isCreatingRef.current || isCreatedRef.current) {
debugLog(`[usePtyProcess] Skipping PTY creation for terminal: ${terminalId} - already creating: ${isCreatingRef.current}, already created: ${isCreatedRef.current}`);
return;
}
// Clear retry counter since we're proceeding with creation
recreationRetryCountRef.current = 0;
@@ -132,6 +140,8 @@ export function usePtyProcess({
const alreadyRunning = terminalState?.status === 'running' || terminalState?.status === 'claude-active';
const isRestored = terminalState?.isRestored;
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
if (isRecreatingRef?.current && terminalState?.status === 'exited') {
@@ -160,6 +170,7 @@ export function usePtyProcess({
if (isRestored && terminalState) {
// Restored session
debugLog(`[usePtyProcess] Restoring session for terminal: ${terminalId}, cwd: ${terminalState.cwd}, isClaudeMode: ${terminalState.isClaudeMode}, claudeSessionId: ${terminalState.claudeSessionId || 'none'}`);
window.electronAPI.restoreTerminalSession(
{
id: terminalState.id,
@@ -178,19 +189,24 @@ export function usePtyProcess({
rows
).then((result) => {
if (result.success && result.data?.success) {
debugLog(`[usePtyProcess] Successfully restored PTY session for terminal: ${terminalId}`);
handleSuccess();
const store = getStore();
store.setTerminalStatus(terminalId, terminalState.isClaudeMode ? 'claude-active' : 'running');
store.updateTerminal(terminalId, { isRestored: false });
onCreated?.();
} else {
handleError(`Error restoring session: ${result.data?.error || result.error}`);
const errorMsg = `Error restoring session: ${result.data?.error || result.error}`;
debugError(`[usePtyProcess] Failed to restore PTY session for terminal: ${terminalId}, error: ${errorMsg}`);
handleError(errorMsg);
}
}).catch((err) => {
debugError(`[usePtyProcess] Exception restoring PTY session for terminal: ${terminalId}, error:`, err);
handleError(err.message);
});
} else {
// New terminal
debugLog(`[usePtyProcess] Creating new PTY for terminal: ${terminalId}, cwd: ${cwd}, projectPath: ${projectPath}`);
window.electronAPI.createTerminal({
id: terminalId,
cwd,
@@ -199,15 +215,19 @@ export function usePtyProcess({
projectPath,
}).then((result) => {
if (result.success) {
debugLog(`[usePtyProcess] Successfully created PTY for terminal: ${terminalId}`);
handleSuccess();
if (!alreadyRunning) {
getStore().setTerminalStatus(terminalId, 'running');
}
onCreated?.();
} else {
handleError(result.error || 'Unknown error');
const errorMsg = result.error || 'Unknown error';
debugError(`[usePtyProcess] Failed to create PTY for terminal: ${terminalId}, error: ${errorMsg}`);
handleError(errorMsg);
}
}).catch((err) => {
debugError(`[usePtyProcess] Exception creating PTY for terminal: ${terminalId}, error:`, err);
handleError(err.message);
});
}
@@ -5,6 +5,7 @@ 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 { debugLog, debugError } from '../../../shared/utils/debug-logger';
// Type augmentation for navigator.userAgentData (modern User-Agent Client Hints API)
interface NavigatorUAData {
@@ -44,7 +45,12 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea
// Initialize xterm.js UI
useEffect(() => {
if (!terminalRef.current || xtermRef.current) return;
if (!terminalRef.current || xtermRef.current) {
debugLog(`[useXterm] Skipping xterm initialization for terminal: ${terminalId} - already initialized or container not ready`);
return;
}
debugLog(`[useXterm] Initializing xterm for terminal: ${terminalId}`);
const xterm = new XTerm({
cursorBlink: true,
@@ -242,6 +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}`);
onDimensionsReady?.(cols, rows);
}
} else {
@@ -255,11 +262,14 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea
// Replay buffered output if this is a remount or restored session
// This now includes ANSI codes for proper formatting/colors/prompt
const bufferedOutput = terminalBufferManager.get(terminalId);
// Use atomic getAndClear to prevent race condition where new output could arrive between get() and clear()
const bufferedOutput = terminalBufferManager.getAndClear(terminalId);
if (bufferedOutput && bufferedOutput.length > 0) {
debugLog(`[useXterm] Replaying buffered output for terminal: ${terminalId}, buffer size: ${bufferedOutput.length} chars`);
xterm.write(bufferedOutput);
// Clear buffer after replay to avoid duplicate output
terminalBufferManager.clear(terminalId);
debugLog(`[useXterm] Buffer replay complete and cleared for terminal: ${terminalId}`);
} else {
debugLog(`[useXterm] No buffered output to replay for terminal: ${terminalId}`);
}
// Handle terminal input
@@ -298,7 +308,12 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea
// This allows the global listener to write directly to xterm when terminal is visible
useEffect(() => {
// Only register if xterm is ready
if (!xtermRef.current) return;
if (!xtermRef.current) {
debugLog(`[useXterm] Skipping output callback registration for terminal: ${terminalId} - xterm not ready`);
return;
}
debugLog(`[useXterm] Registering output callback for terminal: ${terminalId}`);
// Create a write function that writes directly to this xterm instance
const writeCallback = (data: string) => {
@@ -312,6 +327,7 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea
// Cleanup: unregister callback when component unmounts
return () => {
debugLog(`[useXterm] Unregistering output callback for terminal: ${terminalId}`);
unregisterOutputCallback(terminalId);
};
}, [terminalId]);
@@ -394,19 +410,29 @@ export function useXterm({ terminalId, onCommandEnter, onResize, onDimensionsRea
const serializeBuffer = useCallback(() => {
if (xtermRef.current && serializeAddonRef.current) {
try {
debugLog(`[useXterm] Serializing buffer for terminal: ${terminalId}`);
const serialized = serializeAddonRef.current.serialize();
if (serialized && serialized.length > 0) {
terminalBufferManager.set(terminalId, serialized);
debugLog(`[useXterm] Buffer serialized for terminal: ${terminalId}, size: ${serialized.length} chars`);
} else {
debugLog(`[useXterm] No content to serialize for terminal: ${terminalId}`);
}
} catch (error) {
console.error('[useXterm] Failed to serialize terminal buffer:', error);
debugError('[useXterm] Failed to serialize terminal buffer:', error);
}
} else {
debugLog(`[useXterm] Cannot serialize buffer for terminal: ${terminalId} - xterm or serializeAddon not available`);
}
}, [terminalId]);
const dispose = useCallback(() => {
// Guard against double dispose (can happen in React StrictMode or rapid unmount)
if (isDisposedRef.current) return;
if (isDisposedRef.current) {
debugLog(`[useXterm] Skipping dispose for terminal: ${terminalId} - already disposed`);
return;
}
debugLog(`[useXterm] Disposing xterm for terminal: ${terminalId}`);
isDisposedRef.current = true;
// Serialize buffer before disposing to preserve ANSI formatting
@@ -65,6 +65,16 @@ class TerminalBufferManager {
this.buffers.delete(id);
}
/**
* Atomically get and clear a terminal's buffer
* This prevents race conditions where data could be appended between get() and clear()
*/
getAndClear(id: string): string {
const buffer = this.buffers.get(id) || '';
this.buffers.delete(id);
return buffer;
}
/**
* Check if a terminal has a buffer
*/
@@ -192,10 +192,36 @@ export const useTerminalStore = create<TerminalState>((set, get) => ({
addRestoredTerminal: (session: TerminalSession) => {
const state = get();
debugLog(`[TerminalStore] addRestoredTerminal called for session: ${session.id}, title: "${session.title}", projectPath: ${session.projectPath}`);
// CRITICAL: Always restore buffer to buffer manager FIRST, even if terminal already exists.
// This ensures useXterm can replay the buffer regardless of whether this is a fresh restore
// or a re-restore (e.g., after project switch). The buffer must be available before
// the Terminal component mounts and useXterm tries to read it.
if (session.outputBuffer) {
terminalBufferManager.set(session.id, session.outputBuffer);
debugLog(`[TerminalStore] Restored buffer for terminal ${session.id}, size: ${session.outputBuffer.length} chars`);
} else {
debugLog(`[TerminalStore] No output buffer to restore for terminal ${session.id}`);
}
// Check if terminal already exists
const existingTerminal = state.terminals.find(t => t.id === session.id);
if (existingTerminal) {
debugLog(`[TerminalStore] Terminal ${session.id} already exists in store, returning existing (buffer was still restored above)`);
// If session was in Claude mode before shutdown, update pendingClaudeResume for re-restore scenarios
// (e.g., after project switch). This ensures the deferred resume logic can trigger even when
// the terminal already exists in the store.
if (session.isClaudeMode === true && !existingTerminal.pendingClaudeResume) {
debugLog(`[TerminalStore] Updating pendingClaudeResume for existing terminal ${session.id}`);
set((state) => ({
terminals: state.terminals.map(t =>
t.id === session.id ? { ...t, pendingClaudeResume: true } : t
)
}));
}
return existingTerminal;
}
@@ -214,25 +240,26 @@ export const useTerminalStore = create<TerminalState>((set, get) => ({
// Keep claudeSessionId so users can resume by clicking the invoke button
isClaudeMode: false,
claudeSessionId: session.claudeSessionId,
// outputBuffer now stored in terminalBufferManager
// outputBuffer now stored in terminalBufferManager (done above before existence check)
isRestored: true,
projectPath: session.projectPath,
// Worktree config is validated in main process before restore
worktreeConfig: session.worktreeConfig,
// Restore displayOrder for tab position persistence (falls back to end if not set)
displayOrder: session.displayOrder ?? state.terminals.length,
// If session was in Claude mode before shutdown, mark for deferred resume.
// This ensures the renderer knows to trigger 'claude --continue' when the terminal
// becomes active, without relying on the TERMINAL_PENDING_RESUME IPC event timing
// (which may be sent before the Terminal component mounts its listener).
pendingClaudeResume: session.isClaudeMode === true,
};
// Restore buffer to buffer manager
if (session.outputBuffer) {
terminalBufferManager.set(session.id, session.outputBuffer);
}
set((state) => ({
terminals: [...state.terminals, restoredTerminal],
activeTerminalId: state.activeTerminalId || restoredTerminal.id,
}));
debugLog(`[TerminalStore] Successfully added restored terminal ${session.id} to store, isRestored: true, claudeSessionId: ${session.claudeSessionId || 'none'}, pendingClaudeResume: ${session.isClaudeMode === true}`);
return restoredTerminal;
},
@@ -489,10 +516,13 @@ export async function restoreTerminalSessions(projectPath: string): Promise<void
}
// Restore from disk
debugLog(`[TerminalStore] Fetching terminal sessions from disk for project: ${projectPath}`);
const result = await window.electronAPI.getTerminalSessions(projectPath);
if (!result.success || !result.data || result.data.length === 0) {
debugLog(`[TerminalStore] No sessions found on disk for project: ${projectPath}, success: ${result.success}, sessionCount: ${result.data?.length || 0}`);
return;
}
debugLog(`[TerminalStore] Found ${result.data.length} sessions on disk for project: ${projectPath}`);
// Sort sessions by displayOrder before restoring (lower = further left)
// Sessions without displayOrder are placed at the end
@@ -503,11 +533,13 @@ export async function restoreTerminalSessions(projectPath: string): Promise<void
});
// Add terminals to the store in correct order (they'll be created in the TerminalGrid component)
debugLog(`[TerminalStore] Adding ${sortedSessions.length} sorted sessions to store`);
for (const session of sortedSessions) {
store.addRestoredTerminal(session);
}
store.setHasRestoredSessions(true);
debugLog(`[TerminalStore] Completed terminal session restoration for project: ${projectPath}`);
} catch (error) {
debugError('[TerminalStore] Error restoring sessions:', error);
} finally {
@@ -3,6 +3,10 @@
"expand": "Expand terminal",
"collapse": "Collapse terminal"
},
"resume": {
"pending": "Resume Available",
"pendingTooltip": "Click to resume previous Claude session"
},
"auth": {
"terminalTitle": "Auth: {{profileName}}",
"maxTerminalsReached": "Cannot open auth terminal: maximum terminals reached. Close a terminal first."
@@ -3,6 +3,10 @@
"expand": "Agrandir le terminal",
"collapse": "Reduire le terminal"
},
"resume": {
"pending": "Reprise disponible",
"pendingTooltip": "Cliquez pour reprendre la session Claude précédente"
},
"auth": {
"terminalTitle": "Auth: {{profileName}}",
"maxTerminalsReached": "Impossible d'ouvrir le terminal d'auth: nombre maximum de terminaux atteint. Fermez un terminal d'abord."