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
This commit is contained in:
134
crates/lore-tui/src/view/bootstrap.rs
Normal file
134
crates/lore-tui/src/view/bootstrap.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
#![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.
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user