Files
gitlore/crates/lore-tui/src/view/bootstrap.rs
teernisse fb40fdc677 feat(tui): Phase 3 power features — Who, Search, Timeline, Trace, File History screens
Complete TUI Phase 3 implementation with all 5 power feature screens:

- Who screen: 5 modes (expert/workload/reviews/active/overlap) with
  mode tabs, input bar, result rendering, and hint bar
- Search screen: full-text search with result list and scoring display
- Timeline screen: chronological event feed with time-relative display
- Trace screen: file provenance chains with expand/collapse, rename
  tracking, and linked issues/discussions
- File History screen: per-file MR timeline with rename chain display
  and discussion snippets

Also includes:
- Command palette overlay (fuzzy search)
- Bootstrap screen (initial sync flow)
- Action layer split from monolithic action.rs to per-screen modules
- Entity and render cache infrastructure
- Shared who_types module in core crate
- All screens wired into view/mod.rs dispatch
- 597 tests passing, clippy clean (pedantic + nursery), fmt clean
2026-02-18 22:56:38 -05:00

135 lines
3.9 KiB
Rust

#![allow(dead_code)] // Phase 2.5: consumed by render_screen dispatch
//! Bootstrap screen view.
//!
//! Shown when the database has no entity data. Guides users to run
//! a sync to populate the database.
use ftui::core::geometry::Rect;
use ftui::render::cell::{Cell, PackedRgba};
use ftui::render::drawing::Draw;
use ftui::render::frame::Frame;
use crate::state::bootstrap::BootstrapState;
// Colors (Flexoki palette).
const TEXT: PackedRgba = PackedRgba::rgb(0xCE, 0xCD, 0xC3); // tx
const MUTED: PackedRgba = PackedRgba::rgb(0x87, 0x87, 0x80); // tx-2
const ACCENT: PackedRgba = PackedRgba::rgb(0xDA, 0x70, 0x2C); // orange
/// Render the bootstrap screen.
///
/// Centers a message in the content area, guiding the user to start a sync.
/// When a sync is in progress, shows a "syncing" message instead.
pub fn render_bootstrap(frame: &mut Frame<'_>, state: &BootstrapState, area: Rect) {
if area.width < 10 || area.height < 5 {
return;
}
let center_y = area.y + area.height / 2;
let max_x = area.x.saturating_add(area.width);
// Title.
let title = "No data found";
let title_x = area.x + area.width.saturating_sub(title.len() as u16) / 2;
frame.print_text_clipped(
title_x,
center_y.saturating_sub(2),
title,
Cell {
fg: ACCENT,
..Cell::default()
},
max_x,
);
if state.sync_started {
// Sync in progress.
let msg = "Syncing data from GitLab...";
let msg_x = area.x + area.width.saturating_sub(msg.len() as u16) / 2;
frame.print_text_clipped(
msg_x,
center_y,
msg,
Cell {
fg: TEXT,
..Cell::default()
},
max_x,
);
} else {
// Prompt user to start sync.
let msg = "Run sync to get started.";
let msg_x = area.x + area.width.saturating_sub(msg.len() as u16) / 2;
frame.print_text_clipped(
msg_x,
center_y,
msg,
Cell {
fg: TEXT,
..Cell::default()
},
max_x,
);
let hint = "Press 'g' then 's' to start sync, or 'q' to quit.";
let hint_x = area.x + area.width.saturating_sub(hint.len() as u16) / 2;
frame.print_text_clipped(
hint_x,
center_y + 2,
hint,
Cell {
fg: MUTED,
..Cell::default()
},
max_x,
);
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use ftui::render::grapheme_pool::GraphemePool;
macro_rules! with_frame {
($width:expr, $height:expr, |$frame:ident| $body:block) => {{
let mut pool = GraphemePool::new();
let mut $frame = Frame::new($width, $height, &mut pool);
$body
}};
}
#[test]
fn test_render_bootstrap_no_panic() {
with_frame!(80, 24, |frame| {
let state = BootstrapState::default();
render_bootstrap(&mut frame, &state, Rect::new(0, 1, 80, 22));
});
}
#[test]
fn test_render_bootstrap_sync_started() {
with_frame!(80, 24, |frame| {
let state = BootstrapState {
sync_started: true,
..Default::default()
};
render_bootstrap(&mut frame, &state, Rect::new(0, 1, 80, 22));
});
}
#[test]
fn test_render_bootstrap_tiny_area_noop() {
with_frame!(8, 3, |frame| {
let state = BootstrapState::default();
render_bootstrap(&mut frame, &state, Rect::new(0, 0, 8, 3));
// Should not panic — early return for tiny areas.
});
}
}