From 1de283751b6c6c84b6990cfd90c0305e999a2547 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Fri, 20 Feb 2026 21:21:02 +0800 Subject: [PATCH] feat(terminal-dashboard): improve UX for pane/session management - Add confirmation dialog when closing pane with active session - Add explicit "Close Session" button to terminate backend PTY - Handle "session not found" scenario with user-friendly message - Add i18n keys for new UI elements (en/zh) --- .../terminal-dashboard/TerminalPane.tsx | 37 ++++++++++++-- .../src/locales/en/terminal-dashboard.json | 2 + .../src/locales/zh/terminal-dashboard.json | 2 + .../src/stores/sessionManagerStore.ts | 50 +++++++++++++++++++ ccw/frontend/src/types/terminal-dashboard.ts | 14 +++++- 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx b/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx index 58804d93..e387b952 100644 --- a/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx +++ b/ccw/frontend/src/components/terminal-dashboard/TerminalPane.tsx @@ -6,7 +6,7 @@ // Renders within the TerminalGrid recursive layout. // File preview is triggered from right sidebar FileSidebarPanel. -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; import { SplitSquareHorizontal, @@ -23,6 +23,7 @@ import { FileText, ArrowLeft, LogOut, + WifiOff, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { TerminalInstance } from './TerminalInstance'; @@ -64,6 +65,7 @@ const statusDotStyles: Record = { error: 'bg-red-500', paused: 'bg-yellow-500', resuming: 'bg-blue-400 animate-pulse', + locked: 'bg-amber-500 animate-pulse', }; // ========== Props ========== @@ -129,6 +131,20 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { const status: TerminalStatus = meta?.status ?? 'idle'; const alertCount = meta?.alertCount ?? 0; + // Check if session exists - handles case where pane references a sessionId + // that no longer exists in cliSessionStore (e.g., after page refresh if backend restarted) + const isSessionNotFound = sessionId && !sessions[sessionId]; + + // Clear invalid sessionId after showing the message + useEffect(() => { + if (isSessionNotFound) { + const timer = setTimeout(() => { + assignSession(paneId, null); + }, 3000); // Clear after 3 seconds to let user see the message + return () => clearTimeout(timer); + } + }, [isSessionNotFound, paneId, assignSession]); + // Build session options for dropdown // Use sessions from cliSessionStore directly (all sessions, not just grouped ones) const sessionOptions = useMemo(() => { @@ -261,7 +277,9 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { ) : ( // Terminal mode header <> - {sessionId && ( + {isSessionNotFound ? ( + + ) : sessionId && ( @@ -314,7 +332,7 @@ export function TerminalPane({ paneId }: TerminalPaneProps) { > - {!isFileMode && sessionId && ( + {!isFileMode && sessionId && !isSessionNotFound && ( <> {/* Restart button */}