diff --git a/src/terminal.rs b/src/terminal.rs index deff53b..0db4d12 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -1,22 +1,11 @@ //! Terminal initialization, restoration, and panic hook for bbs-md. //! -//! # Design -//! -//! This module uses the main screen buffer (no alternate screen) for the immersive BBS feel. -//! On exit, TUI output remains visible in the terminal scroll history — the user can scroll -//! up to review what they read, and the goodbye message persists after the process exits. -//! //! # Broken Pipe Safety (LIFE-04) //! //! All write operations in the TUI must use `let _ =` or handle `ErrorKind::BrokenPipe`. //! SSH connections can close mid-write; Rust propagates this as `ErrorKind::BrokenPipe`. -//! The actual enforcement happens in the event loop (Plan 03), but `restore_terminal()` +//! The actual enforcement happens in the event loop, but `restore_terminal()` //! already follows this pattern with `let _ =` on every call. -//! -//! # Important -//! -//! Do NOT call `ratatui::restore()` — it enters alternate screen via `LeaveAlternateScreen`, -//! which we never entered. Use `restore_terminal()` from this module instead. // Functions in this module are used in Plan 02 and 03; suppress Phase 1 dead code warnings. #![allow(dead_code)] @@ -24,26 +13,24 @@ use std::io::stdout; use ratatui::crossterm::{ execute, - terminal::{enable_raw_mode, disable_raw_mode, Clear, ClearType}, - cursor::{MoveTo, Show}, + terminal::{enable_raw_mode, disable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + cursor::Show, }; use ratatui::{backend::CrosstermBackend, Terminal, TerminalOptions, Viewport}; /// Type alias for the terminal used throughout the application. pub type Term = Terminal>; -/// Initialize the terminal on the main screen buffer with raw mode enabled. +/// Initialize the terminal with alternate screen buffer and raw mode. /// -/// This does NOT enter the alternate screen buffer. Instead it clears the main screen -/// so the TUI occupies the user's actual terminal. On exit, TUI output remains in -/// scroll history. +/// Uses the alternate screen so the TUI exits cleanly without leaving artifacts. /// /// # Errors /// -/// Returns an error if raw mode cannot be enabled or if the screen cannot be cleared. +/// Returns an error if raw mode cannot be enabled or the alternate screen cannot be entered. pub fn init_terminal() -> std::io::Result { enable_raw_mode()?; - execute!(stdout(), Clear(ClearType::All), MoveTo(0, 0))?; + execute!(stdout(), EnterAlternateScreen)?; let backend = CrosstermBackend::new(stdout()); Terminal::with_options(backend, TerminalOptions { viewport: Viewport::Fullscreen, @@ -52,13 +39,11 @@ pub fn init_terminal() -> std::io::Result { /// Restore the terminal to a usable state. /// -/// Disables raw mode and shows the cursor. Every operation uses `let _ =` to suppress -/// errors — this function is called from cleanup paths including the panic hook, where -/// panicking would hide the original error. -/// -/// Do NOT call `ratatui::restore()` — it unconditionally calls `LeaveAlternateScreen`, -/// which corrupts the terminal since we never entered the alternate screen. +/// Leaves alternate screen, disables raw mode, and shows the cursor. Every operation +/// uses `let _ =` to suppress errors — this function is called from cleanup paths +/// including the panic hook, where panicking would hide the original error. pub fn restore_terminal() { + let _ = execute!(std::io::stdout(), LeaveAlternateScreen); let _ = disable_raw_mode(); let _ = execute!(std::io::stdout(), Show); } @@ -75,6 +60,10 @@ pub fn install_panic_hook() { let original_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { // Restore terminal — use let _ to avoid double-panic if cleanup itself fails + let _ = ratatui::crossterm::execute!( + std::io::stdout(), + ratatui::crossterm::terminal::LeaveAlternateScreen + ); let _ = ratatui::crossterm::terminal::disable_raw_mode(); let _ = ratatui::crossterm::execute!( std::io::stdout(),