#![forbid(unsafe_code)] //! Gitlore TUI — terminal interface for exploring GitLab data locally. //! //! Built on FrankenTUI (Elm architecture): Model, update, view. //! The `lore` CLI spawns `lore-tui` via PATH lookup at runtime. use anyhow::{Context, Result}; // Phase 0 modules. pub mod clock; // Clock trait: SystemClock + FakeClock (bd-2lg6) pub mod message; // Msg, Screen, EntityKey, AppError, InputMode (bd-c9gk) pub mod safety; // Terminal safety: sanitize + URL policy + redact (bd-3ir1) pub mod db; // DbManager: read pool + dedicated writer (bd-2kop) pub mod theme; // Flexoki theme: build_theme, state_color, label_style (bd-5ofk) pub mod app; // LoreApp Model trait impl (Phase 0 proof: bd-2emv, full: bd-6pmy) // Phase 1 modules. pub mod commands; // CommandRegistry: keybindings, help, palette (bd-38lb) pub mod crash_context; // CrashContext ring buffer + panic hook (bd-2fr7) pub mod layout; // Responsive layout: breakpoints, columns, preview pane (bd-1pzj) pub mod navigation; // NavigationStack: back/forward/jump list (bd-1qpp) pub mod state; // AppState, LoadState, ScreenIntent, per-screen states (bd-1v9m) pub mod task_supervisor; // TaskSupervisor: dedup + cancel + generation IDs (bd-3le2) pub mod view; // View layer: render_screen + common widgets (bd-26f2) // Phase 2 modules. pub mod action; // Data-fetching actions for TUI screens (bd-35g5+) pub mod filter_dsl; // Filter DSL tokenizer for list screen filter bars (bd-18qs) // Phase 4 modules. pub mod entity_cache; // Bounded LRU entity cache for detail view reopens (bd-2og9) pub mod render_cache; // Bounded render cache for expensive per-frame computations (bd-2og9) pub mod scope; // Global scope context: SQL helpers + project listing (bd-1ser) // Phase 5 modules. pub mod instance_lock; // Single-instance advisory lock for TUI (bd-3h00) pub mod session; // Session state persistence: save/load/quarantine (bd-3h00) pub mod text_width; // Unicode-aware text width measurement + truncation (bd-3h00) /// Options controlling how the TUI launches. #[derive(Debug, Clone)] pub struct LaunchOptions { /// Path to lore config file. pub config_path: Option, /// Run a background sync before displaying data. pub sync_on_start: bool, /// Clear cached TUI state and start fresh. pub fresh: bool, /// Render backend: "crossterm" or "native". pub render_mode: String, /// Use ASCII-only box drawing characters. pub ascii: bool, /// Disable alternate screen (render inline). pub no_alt_screen: bool, } /// Launch the TUI in browse mode (no sync). /// /// Loads config from `options.config_path` (or default location), /// opens the database read-only, and enters the FrankenTUI event loop. /// /// ## Preflight sequence /// /// 1. **Schema preflight** — validate the database schema version before /// creating the app. If incompatible, print an actionable error and exit /// with a non-zero code. /// 2. **Data readiness** — check whether the database has any entity data. /// If empty, start on the Bootstrap screen; otherwise start on Dashboard. pub fn launch_tui(options: LaunchOptions) -> Result<()> { let _options = options; // remaining fields (fresh, ascii, etc.) consumed in later phases // 1. Resolve database path. let db_path = lore::core::paths::get_db_path(None); if !db_path.exists() { anyhow::bail!( "No lore database found at {}.\n\ Run 'lore init' to create a config, then 'lore sync' to fetch data.", db_path.display() ); } // 2. Open DB and run schema preflight. let db = db::DbManager::open(&db_path) .with_context(|| format!("opening database at {}", db_path.display()))?; db.with_reader(schema_preflight)?; // 3. Check data readiness — bootstrap screen if empty. let start_on_bootstrap = db.with_reader(|conn| { let readiness = action::check_data_readiness(conn)?; Ok(!readiness.has_any_data()) })?; // 4. Build the app model. let mut app = app::LoreApp::new(); app.db = Some(db); if start_on_bootstrap { app.navigation.reset_to(message::Screen::Bootstrap); } // 5. Enter the FrankenTUI event loop. ftui::App::fullscreen(app) .with_mouse() .run() .context("running TUI event loop")?; Ok(()) } /// Run the schema preflight check. /// /// Returns `Ok(())` if the schema is compatible, or an error with an /// actionable message if it's not. The caller should exit non-zero on error. pub fn schema_preflight(conn: &rusqlite::Connection) -> Result<()> { use state::bootstrap::SchemaCheck; match action::check_schema_version(conn, action::MINIMUM_SCHEMA_VERSION) { SchemaCheck::Compatible { .. } => Ok(()), SchemaCheck::NoDB => { anyhow::bail!( "No lore database found.\n\ Run 'lore init' to create a config, then 'lore sync' to fetch data." ); } SchemaCheck::Incompatible { found, minimum } => { anyhow::bail!( "Database schema version {found} is too old (minimum: {minimum}).\n\ Run 'lore migrate' to upgrade, or 'lore sync' to rebuild." ); } } } /// Launch the TUI with an initial sync pass. /// /// Runs `lore sync` in the background while displaying a progress screen, /// then transitions to browse mode once sync completes. pub fn launch_sync_tui(options: LaunchOptions) -> Result<()> { let _options = options; // Phase 2 will implement the sync progress screen eprintln!("lore-tui: sync mode not yet implemented (Phase 2)"); Ok(()) }