8.7 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-safety-foundation | 02 | execute | 1 |
|
true |
|
|
Purpose: The login-shell deployment context means any terminal corruption locks out SSH users. This plan establishes the safety envelope: every exit path (panic, signal, normal quit) restores the terminal correctly.
Output: src/terminal.rs with init/restore/panic-hook, src/signals.rs with signal flag registration.
<execution_context> @/Users/ruohki/.claude/get-shit-done/workflows/execute-plan.md @/Users/ruohki/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-safety-foundation/01-RESEARCH.md Task 1: Implement terminal init/restore and panic hook src/terminal.rs Create `src/terminal.rs` with:-
init_terminal() -> std::io::Result<Terminal<CrosstermBackend<std::io::Stdout>>>- Call
crossterm::terminal::enable_raw_mode()? - Call
crossterm::execute!(stdout(), Clear(ClearType::All), MoveTo(0, 0))?— clears main screen buffer for immersive BBS feel - Create
CrosstermBackend::new(stdout()) - Return
Terminal::with_options(backend, TerminalOptions { viewport: Viewport::Fullscreen }) - IMPORTANT: Do NOT enter alternate screen. Do NOT use
ratatui::init().
- Call
-
restore_terminal()(no return value — must never panic)let _ = crossterm::terminal::disable_raw_mode();let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);- Do NOT call
ratatui::restore()(it calls LeaveAlternateScreen which we never entered) - Every operation uses
let _ =to suppress errors — this runs in cleanup paths including panic hook
-
install_panic_hook()- Call
std::panic::take_hook()to capture the original hook - Call
std::panic::set_hook(Box::new(move |panic_info| { ... }))with a closure that: a. Callsrestore_terminal()to clean up the terminal b. Prints friendly BBS-themed message to stderr witheprintln!():(Use\r\n+----------------------------------------------+ | SYSTEM ERROR: An unexpected fault occurred. | | The BBS has exited safely. | | SysOp has been notified. | +----------------------------------------------+\r\r\nand trailing\rfor proper display after raw mode restoration) c. Calls the original hook withpanic_info— this outputs the technical backtrace to stderr (captured by systemd journal / SSH log) - All I/O in the hook uses
let _ =oreprintln!()(which silently ignores errors). Neverunwrap()inside the hook.
- Call
-
Broken pipe safety (LIFE-04): At the module level, add a doc comment noting that all write operations in the TUI must use
let _ =or handleErrorKind::BrokenPipe. The actual enforcement happens in the event loop (Plan 03), but restore_terminal() already follows this pattern withlet _ =on every call.
Use these imports:
use std::io::stdout;
use crossterm::{
execute,
terminal::{enable_raw_mode, disable_raw_mode, Clear, ClearType},
cursor::{MoveTo, Show},
};
use ratatui::{backend::CrosstermBackend, Terminal, TerminalOptions, Viewport};
Export the Terminal type alias for use in app.rs:
pub type Term = Terminal<CrosstermBackend<std::io::Stdout>>;
Review: init_terminal does NOT contain EnterAlternateScreen. restore_terminal does NOT contain LeaveAlternateScreen. Panic hook does NOT contain any unwrap() or ? operator.
Terminal init enters raw mode and clears main screen without alternate screen buffer. Restore disables raw mode and shows cursor with let _ = on every call. Panic hook restores terminal, prints friendly BBS message, then delegates to original hook for technical details.
-
SignalFlagsstruct:pub struct SignalFlags { pub terminate: Arc<AtomicBool>, } -
register_signals() -> std::io::Result<SignalFlags>:- Create
terminate = Arc::new(AtomicBool::new(false)) - Register SIGTERM:
signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&terminate))? - Register SIGHUP:
signal_hook::flag::register(signal_hook::consts::SIGHUP, Arc::clone(&terminate))? - Do NOT register SIGINT — Ctrl+C is handled as a crossterm key event in the event loop (avoids double-handling per research findings)
- Return
Ok(SignalFlags { terminate })
- Create
-
SignalFlags::should_terminate(&self) -> bool:- Returns
self.terminate.load(Ordering::Relaxed) - Convenience method for polling in the event loop
- Returns
-
LIFE-03 (logging): Add a simple
init_logging()function stub. For Phase 1, logging is minimal — the requirement is that after TUI init, nothing writes to stdout/stderr directly. The panic hook is the exception (it restores terminal first). For now,init_logging()is a no-op that returns(). A doc comment should note: "Phase 1: no file logging needed yet. When LIFE-03 becomes relevant in Phase 2+, replace this with file-based logging. For now, the rule is simply: do not write to stdout/stderr after terminal init except through ratatui's draw cycle or after calling restore_terminal()."
Use these imports:
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use signal_hook::consts::signal::{SIGHUP, SIGTERM};
use signal_hook::flag as signal_flag;
Review: SIGINT is NOT registered (only SIGHUP and SIGTERM). should_terminate() uses Ordering::Relaxed.
SIGHUP and SIGTERM set a shared AtomicBool flag. SignalFlags provides a should_terminate() method for polling. SIGINT is deliberately not registered (Ctrl+C handled via crossterm key events). Logging stub documents the stdout/stderr rule for LIFE-03.
<success_criteria>
- Terminal initializes on main screen buffer with raw mode
- Panic hook restores terminal and prints BBS-friendly message
- Signals set a pollable flag for clean shutdown
- No alternate screen buffer is used anywhere
- All cleanup paths are panic-safe (no unwrap in error paths) </success_criteria>