1pub mod app;
2pub mod review_wizard;
3pub mod widgets;
4
5use std::sync::{Arc, Mutex};
6
7use crate::error::CliError;
8
9pub fn run_review_tui_with_conn(
14 branch_id: &str,
15 conn: &Arc<Mutex<rusqlite::Connection>>,
16) -> Result<Vec<app::ReviewAction>, CliError> {
17 let (conventions, _queried_branch_id) = app::query_conventions_for_review(conn, branch_id)?;
18
19 if conventions.is_empty() {
20 eprintln!("No conventions found to review.");
21 return Ok(Vec::new());
22 }
23
24 let convention_count = conventions.len();
25 let mut terminal = ratatui::init();
26 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
27 review_wizard::run_app(&mut terminal, conventions, conn, branch_id)
28 }));
29 ratatui::restore();
30
31 let already_confirmed = app::count_confirmed_conventions(conn);
32
33 match result {
34 Ok(Ok(r)) => {
35 app::show_summary(
36 &r,
37 &app::SummaryContext {
38 total_in_scope: convention_count,
39 already_confirmed,
40 },
41 );
42 Ok(r)
43 }
44 Ok(Err(e)) => Err(e),
45 Err(_) => Err(CliError::TuiError(
46 "TUI panicked; terminal state has been restored".to_owned(),
47 )),
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use rusqlite::Connection;
55
56 fn setup_empty_db() -> Arc<Mutex<Connection>> {
57 let conn = Connection::open_in_memory().unwrap();
58 conn.execute_batch(
61 "CREATE TABLE nodes (
62 id INTEGER PRIMARY KEY,
63 description TEXT,
64 nature TEXT,
65 weight TEXT,
66 confidence REAL,
67 adoption_count INTEGER,
68 total_count INTEGER,
69 ext_data TEXT,
70 description_hash TEXT,
71 branch_id TEXT,
72 removed INTEGER DEFAULT 0
73 );
74 CREATE TABLE edges (
75 id INTEGER PRIMARY KEY,
76 source_node_id INTEGER,
77 target_node_id INTEGER,
78 edge_type TEXT,
79 ext_data TEXT,
80 branch_id TEXT,
81 removed INTEGER DEFAULT 0
82 );
83 CREATE TABLE fts_index (
84 content TEXT,
85 node_id INTEGER,
86 branch_id TEXT
87 );
88 CREATE TABLE decisions (
89 description_hash TEXT NOT NULL PRIMARY KEY,
90 description TEXT NOT NULL,
91 state TEXT NOT NULL,
92 nature TEXT NOT NULL,
93 weight TEXT NOT NULL,
94 category TEXT,
95 reason TEXT,
96 examples TEXT,
97 decided_on_branch TEXT NOT NULL,
98 decided_at INTEGER NOT NULL,
99 updated_at INTEGER NOT NULL
100 );",
101 )
102 .unwrap();
103 Arc::new(Mutex::new(conn))
104 }
105
106 #[test]
107 fn empty_conventions_returns_empty_vec() {
108 let conn = setup_empty_db();
109 let result = run_review_tui_with_conn("main", &conn);
110 assert!(result.is_ok());
111 assert!(result.unwrap().is_empty());
112 }
113
114 #[test]
115 fn error_from_query_is_propagated() {
116 let conn = setup_empty_db();
117 {
119 let guard = conn.lock().unwrap();
120 guard.execute_batch("DROP TABLE nodes").unwrap();
121 }
122 let result = run_review_tui_with_conn("main", &conn);
123 assert!(result.is_err());
124 }
125}