Files
bbs-md/.planning/phases/01-safety-foundation/01-02-PLAN.md
T

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
src/terminal.rs
src/signals.rs
true
LIFE-01
LIFE-02
LIFE-03
LIFE-04
truths artifacts key_links
When the app panics, raw mode is disabled and a friendly BBS message is printed before exiting
Panic details go to stderr for sysop; user sees only the friendly message
SIGHUP sets a terminate flag that the event loop can poll
SIGTERM sets a terminate flag that the event loop can poll
Writing to stdout after SSH disconnect does not panic (broken pipe handled)
Terminal init uses main screen buffer (no alternate screen) with raw mode and clear
Terminal restore disables raw mode and shows cursor without calling LeaveAlternateScreen
path provides contains
src/terminal.rs init_terminal(), restore_terminal(), install_panic_hook() disable_raw_mode
path provides contains
src/signals.rs SignalFlags, register_signals() AtomicBool
from to via pattern
src/terminal.rs crossterm enable_raw_mode, disable_raw_mode, Clear, cursor::Show enable_raw_mode
from to via pattern
src/terminal.rs ratatui Terminal::with_options(Viewport::Fullscreen) Viewport::Fullscreen
from to via pattern
src/signals.rs signal-hook flag::register for SIGHUP and SIGTERM flag::register
Implement terminal initialization (main screen, no alternate buffer), terminal restoration, panic hook with friendly BBS messaging, UNIX signal handling for SIGHUP/SIGTERM, and broken pipe safety. These are the safety primitives that all other code depends on.

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:
  1. 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().
  2. 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
  3. 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. Calls restore_terminal() to clean up the terminal b. Prints friendly BBS-themed message to stderr with eprintln!():
      \r\n+----------------------------------------------+
      | SYSTEM ERROR: An unexpected fault occurred.  |
      | The BBS has exited safely.                   |
      | SysOp has been notified.                     |
      +----------------------------------------------+\r
      
      (Use \r\n and trailing \r for proper display after raw mode restoration) c. Calls the original hook with panic_info — this outputs the technical backtrace to stderr (captured by systemd journal / SSH log)
    • All I/O in the hook uses let _ = or eprintln!() (which silently ignores errors). Never unwrap() inside the hook.
  4. 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 handle ErrorKind::BrokenPipe. The actual enforcement happens in the event loop (Plan 03), but restore_terminal() already follows this pattern with let _ = 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>>;
`cargo build` succeeds (after adding `mod terminal;` temporarily to main.rs or just checking compilation).

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.

Task 2: Implement signal handling and file logging stub src/signals.rs Create `src/signals.rs` with:
  1. SignalFlags struct:

    pub struct SignalFlags {
        pub terminate: Arc<AtomicBool>,
    }
    
  2. 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 })
  3. SignalFlags::should_terminate(&self) -> bool:

    • Returns self.terminate.load(Ordering::Relaxed)
    • Convenience method for polling in the event loop
  4. 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;
`cargo build` succeeds (after adding `mod signals;` temporarily to main.rs).

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.

1. `cargo build` compiles with both new modules 2. `init_terminal()` uses `Viewport::Fullscreen` and never enters alternate screen 3. `restore_terminal()` uses `let _ =` for every operation 4. Panic hook contains no `unwrap()` or `?` — only `let _ =` and `eprintln!()` 5. Signal registration covers SIGHUP and SIGTERM but NOT SIGINT 6. `should_terminate()` polls the AtomicBool

<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>
After completion, create `.planning/phases/01-safety-foundation/01-02-SUMMARY.md`