import { useKeyboard, useTerminalDimensions } from "@opentui/react"; import { useCallback, useMemo, useState } from "react"; import type { LogSession } from "@/lib/logger"; import { formatProjectNameForDisplay } from "@/lib/tui/sessions/session-display"; import { CenteredModal } from "@/lib/tui/shared/CenteredModal"; import { TUI_COLORS } from "@/lib/tui/shared/colors"; import { KeyboardShortcutsModal } from "@/lib/tui/shared/KeyboardShortcutsModal"; import { ShortcutHint } from "@/lib/tui/shared/ShortcutHint"; import { SessionDetailPane } from "./SessionListDetailPane"; import { buildSessionOverlayOptions, resolveSessionOverlayKeyAction, } from "./session-overlay-utils"; import { useSessionOverlayState } from "./use-session-overlay-state"; const NARROW_WIDTH_THRESHOLD = 110; interface SessionOverlayProps { onClose: () => void; } function sessionLabel(session: LogSession): string { return session.name.replace(/\.jsonl$/, ""); } function SessionHelpModal({ onClose }: { onClose: () => void }) { useKeyboard((key) => { if (key.name === "escape" || key.name === "q" || key.name === "?" || key.name === "h") { onClose(); } }); return ; } const SESSION_OVERLAY_SHORTCUTS = [ { keys: "[Tab ←/→]", label: "Switch pane focus" }, { keys: "[↑/↓ j/k]", label: "Navigate / Scroll" }, { keys: "[d]", label: "Delete selected log" }, { keys: "[h/?]", label: "Toggle help" }, ] as const; interface SessionDeleteModalProps { sessionName: string; error: string | null; isDeleting: boolean; } function SessionDeleteModal({ sessionName, error, isDeleting }: SessionDeleteModalProps) { return ( {sessionName} This cannot be undone. [y] Delete [n/Esc] Cancel {isDeleting && Deleting...} {error && {error}} ); } type OverlayPane = "list" | "detail"; export function SessionOverlay({ onClose }: SessionOverlayProps) { const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions(); const { sessions, selectedPath, selectedStats, isLoading, sessionsError, statsLoading, statsError, isDeleting, deleteError, setSelectedPath, clearDeleteError, deleteSelectedSession, } = useSessionOverlayState(); const [showHelp, setShowHelp] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [focusedPane, setFocusedPane] = useState("list"); const cycleFocus = useCallback(() => { setFocusedPane((prev) => (prev === "list" ? "detail" : "list")); }, []); const { selectOptions, sessionSlots } = useMemo( () => buildSessionOverlayOptions(sessions), [sessions] ); const selectedSession = selectedPath ? (sessions.find((s) => s.path === selectedPath) ?? null) : null; const sessionTitle = selectedSession ? `${formatProjectNameForDisplay(selectedSession.projectName)} Logs` : "Logs"; const confirmDeleteSelectedSession = useCallback(async () => { const result = await deleteSelectedSession(); if (result.deleted) { setShowDeleteConfirm(false); } }, [deleteSelectedSession]); const closeDeleteConfirm = useCallback(() => { if (isDeleting) { return; } setShowDeleteConfirm(false); clearDeleteError(); }, [clearDeleteError, isDeleting]); const isOverlayBlocked = showHelp || showDeleteConfirm; const isNarrow = terminalWidth < NARROW_WIDTH_THRESHOLD; // Overhead: outer padding (2) + panel border (2) + panel padding (2) + status bar (2) = 8. // In narrow mode the tab strip (1) and its gap (1) add two more rows. const selectHeight = Math.max(3, terminalHeight - (isNarrow ? 10 : 8)); useKeyboard((key) => { const action = resolveSessionOverlayKeyAction({ keyName: key.name, showHelp, showDeleteConfirm, hasSelectedSession: Boolean(selectedSession), isNarrow, focusedPane, }); if (action === "close-delete-confirm") { closeDeleteConfirm(); return; } if (action === "confirm-delete") { void confirmDeleteSelectedSession(); return; } if (action === "toggle-help") { setShowHelp((prev) => !prev); return; } if (action === "close-help") { setShowHelp(false); return; } if (action === "open-delete-confirm") { clearDeleteError(); setShowDeleteConfirm(true); return; } if (action === "cycle-focus") { cycleFocus(); return; } if (action === "focus-detail") { setFocusedPane("detail"); return; } if (action === "close-overlay") { onClose(); return; } }); const listBorderColor = focusedPane === "list" ? TUI_COLORS.ui.borderFocused : TUI_COLORS.ui.border; const detailBorderColor = focusedPane === "detail" ? TUI_COLORS.ui.borderFocused : TUI_COLORS.ui.border; const listPanel = ( {isLoading ? ( Loading... ) : sessionsError ? ( {sessionsError} ) : selectOptions.length === 0 ? ( No sessions found ) : (