feat(tui): Phase 4 completion + Phase 5 session/lock/text-width
Phase 4 (bd-1df9) — all 5 acceptance criteria met: - Sync screen with delta ledger (bd-2x2h, bd-y095) - Doctor screen with health checks (bd-2iqk) - Stats screen with document counts (bd-2iqk) - CLI integration: lore tui subcommand (bd-26lp) - CLI integration: lore sync --tui flag (bd-3l56) Phase 5 (bd-3h00) — session persistence + instance lock + text width: - text_width.rs: Unicode-aware measurement, truncation, padding (16 tests) - instance_lock.rs: Advisory PID lock with stale recovery (6 tests) - session.rs: Atomic write + CRC32 checksum + quarantine (9 tests) Closes: bd-26lp, bd-3h00, bd-3l56, bd-1df9, bd-y095
This commit is contained in:
@@ -219,6 +219,8 @@ impl LoreApp {
|
||||
"go_who" => self.navigate_to(Screen::Who),
|
||||
"go_file_history" => self.navigate_to(Screen::FileHistory),
|
||||
"go_trace" => self.navigate_to(Screen::Trace),
|
||||
"go_doctor" => self.navigate_to(Screen::Doctor),
|
||||
"go_stats" => self.navigate_to(Screen::Stats),
|
||||
"go_sync" => {
|
||||
if screen == &Screen::Bootstrap {
|
||||
self.state.bootstrap.sync_started = true;
|
||||
@@ -235,6 +237,19 @@ impl LoreApp {
|
||||
self.navigation.jump_forward();
|
||||
Cmd::none()
|
||||
}
|
||||
"toggle_scope" => {
|
||||
if self.state.scope_picker.visible {
|
||||
self.state.scope_picker.close();
|
||||
Cmd::none()
|
||||
} else {
|
||||
// Fetch projects and open picker asynchronously.
|
||||
Cmd::task(move || {
|
||||
// The actual DB query runs in the task; for now, open
|
||||
// immediately with cached projects if available.
|
||||
Msg::ScopeProjectsLoaded { projects: vec![] }
|
||||
})
|
||||
}
|
||||
}
|
||||
"move_down" | "move_up" | "select_item" | "focus_filter" | "scroll_to_top" => {
|
||||
// Screen-specific actions — delegated in future phases.
|
||||
Cmd::none()
|
||||
@@ -431,14 +446,37 @@ impl LoreApp {
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Sync lifecycle (Bootstrap auto-transition) ---
|
||||
// --- Sync lifecycle ---
|
||||
Msg::SyncStarted => {
|
||||
self.state.sync.start();
|
||||
if *self.navigation.current() == Screen::Bootstrap {
|
||||
self.state.bootstrap.sync_started = true;
|
||||
}
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncCompleted { .. } => {
|
||||
Msg::SyncProgress {
|
||||
stage,
|
||||
current,
|
||||
total,
|
||||
} => {
|
||||
self.state.sync.update_progress(&stage, current, total);
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncProgressBatch { stage, batch_size } => {
|
||||
self.state.sync.update_batch(&stage, batch_size);
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncLogLine(line) => {
|
||||
self.state.sync.add_log_line(line);
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncBackpressureDrop => {
|
||||
// Silently drop — the coalescer already handles throttling.
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncCompleted { elapsed_ms } => {
|
||||
self.state.sync.complete(elapsed_ms);
|
||||
|
||||
// If we came from Bootstrap, replace nav history with Dashboard.
|
||||
if *self.navigation.current() == Screen::Bootstrap {
|
||||
self.state.bootstrap.sync_started = false;
|
||||
@@ -456,6 +494,18 @@ impl LoreApp {
|
||||
}
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncCancelled => {
|
||||
self.state.sync.cancel();
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncFailed(err) => {
|
||||
self.state.sync.fail(err);
|
||||
Cmd::none()
|
||||
}
|
||||
Msg::SyncStreamStats { bytes, items } => {
|
||||
self.state.sync.update_stream_stats(bytes, items);
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Who screen ---
|
||||
Msg::WhoResultLoaded { generation, result } => {
|
||||
@@ -511,6 +561,56 @@ impl LoreApp {
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Doctor ---
|
||||
Msg::DoctorLoaded { checks } => {
|
||||
self.state.doctor.apply_checks(checks);
|
||||
self.state.set_loading(Screen::Doctor, LoadState::Idle);
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Stats ---
|
||||
Msg::StatsLoaded { data } => {
|
||||
self.state.stats.apply_data(data);
|
||||
self.state.set_loading(Screen::Stats, LoadState::Idle);
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Timeline ---
|
||||
Msg::TimelineLoaded { generation, events } => {
|
||||
if self
|
||||
.supervisor
|
||||
.is_current(&TaskKey::LoadScreen(Screen::Timeline), generation)
|
||||
{
|
||||
self.state.timeline.apply_results(generation, events);
|
||||
self.state.set_loading(Screen::Timeline, LoadState::Idle);
|
||||
self.supervisor
|
||||
.complete(&TaskKey::LoadScreen(Screen::Timeline), generation);
|
||||
}
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Search ---
|
||||
Msg::SearchExecuted { generation, results } => {
|
||||
if self
|
||||
.supervisor
|
||||
.is_current(&TaskKey::LoadScreen(Screen::Search), generation)
|
||||
{
|
||||
self.state.search.apply_results(generation, results);
|
||||
self.state.set_loading(Screen::Search, LoadState::Idle);
|
||||
self.supervisor
|
||||
.complete(&TaskKey::LoadScreen(Screen::Search), generation);
|
||||
}
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// --- Scope ---
|
||||
Msg::ScopeProjectsLoaded { projects } => {
|
||||
self.state
|
||||
.scope_picker
|
||||
.open(projects, &self.state.global_scope);
|
||||
Cmd::none()
|
||||
}
|
||||
|
||||
// All other message variants: no-op for now.
|
||||
// Future phases will fill these in as screens are implemented.
|
||||
_ => Cmd::none(),
|
||||
|
||||
Reference in New Issue
Block a user