Files
gitlore/crates/lore-tui/src/lib.rs
teernisse e8ecb561cf feat: implement lore explain command (bd-9lbr)
Auto-generates structured narratives for issues and MRs from local DB:
- EntitySummary with title, state, author, labels, status
- Key decisions heuristic (correlates state/label changes with nearby notes)
- Activity summary with event counts and time span
- Open threads detection (unresolved discussions)
- Related entities (closing MRs, related issues)
- Timeline of all events in chronological order

7 unit tests, robot-docs entry, autocorrect registry, CLI dispatch wired.
2026-02-19 09:38:50 -05:00

147 lines
5.6 KiB
Rust

#![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<String>,
/// 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(())
}