返回 CodeWhale
home.rs
根目录 / crates / tui / src / commands / groups / core / home.rs
1 //! `/home` command.
2
3 use crate::commands::traits::{CommandInfo, RegisterCommand};
4 use crate::tui::app::App;
5 use codewhale_localization::MessageId;
6
7 use super::CommandResult;
8
9 pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
10 name: "home",
11 aliases: &["zhuye", "shouye"],
12 usage: "/home",
13 description_id: MessageId::CmdHomeDescription,
14 };
15
16 pub(in crate::commands) struct HomeCmd;
17
18 impl RegisterCommand for HomeCmd {
19 fn info() -> &'static CommandInfo {
20 &COMMAND_INFO
21 }
22
23 fn execute(app: &mut App, _arg: Option<&str>) -> CommandResult {
24 if app.session_transition_blocked() {
25 return CommandResult::error(app.tr(MessageId::HomeNavigationBusy).into_owned());
26 }
27 app.launch.workspace = app.workspace.clone();
28 app.launch.restore_card();
29 app.launch.visible = true;
30 app.launch.return_to_session = true;
31 app.needs_redraw = true;
32 CommandResult::ok()
33 }
34 }
35
36 /// Preserve the old read-only statistics view under its descriptive aliases.
37 pub(in crate::commands) struct OverviewCmd;
38
39 impl RegisterCommand for OverviewCmd {
40 fn info() -> &'static CommandInfo {
41 &CommandInfo {
42 name: "overview",
43 aliases: &["stats"],
44 usage: "/overview",
45 description_id: MessageId::CmdOverviewDescription,
46 }
47 }
48
49 fn execute(app: &mut App, _arg: Option<&str>) -> CommandResult {
50 super::core::home_dashboard(app)
51 }
52 }
53
54 #[cfg(test)]
55 mod tests {
56 use super::*;
57 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
58
59 #[test]
60 fn home_preserves_session_draft_and_reveal_and_escape_returns() {
61 let mut app =
62 crate::test_support::test_app_with_options(crate::test_support::test_tui_options("."));
63 app.launch.dismiss();
64 app.current_session_id = Some("retained-session".into());
65 app.api_messages_mut().push(codewhale_models::Message {
66 role: codewhale_models::Role::User,
67 content: vec![codewhale_models::ContentBlock::Text {
68 text: "Retain this conversation".into(),
69 cache_control: None,
70 }],
71 });
72 app.add_message(crate::tui::history::HistoryCell::System {
73 content: "Retain this transcript".into(),
74 });
75 app.input = "unsent draft".into();
76 app.cursor_position = app.input.chars().count();
77 app.launch.mark_reveal_started_at = Some(std::time::Instant::now());
78 let reveal = app.launch.mark_reveal_started_at;
79 let messages = app.api_messages.clone();
80 let history_len = app.history.len();
81 let result = crate::commands::execute("/home", &mut app);
82 assert!(!result.is_error, "{:?}", result.message);
83 assert!(
84 result.action.is_none(),
85 "navigation must not sync/reset the Engine"
86 );
87 assert!(crate::tui::widgets::should_render_empty_state(&app));
88 assert!(app.launch.return_to_session);
89 assert!(!crate::tui::underwater::launch_motion_active(
90 &app, false, true
91 ));
92 crate::tui::underwater::refresh_launch_row_hitboxes(
93 &mut app,
94 ratatui::layout::Rect::new(0, 0, 80, 20),
95 );
96 let rows = crate::tui::underwater::launch_rows_for_app(&app);
97 assert_eq!(
98 crate::tui::underwater::launch_row_click_action(&rows[0].id),
99 crate::tui::underwater::LaunchAction::ReturnToSession,
100 );
101 app.launch.menu_selected = Some(0);
102 assert_eq!(
103 crate::tui::underwater::handle_launch_composer_key(
104 &mut app,
105 KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE),
106 ),
107 crate::tui::underwater::LaunchComposerKey::MenuRun,
108 );
109 assert_eq!(app.input, "unsent draft");
110 crate::tui::underwater::handle_launch_composer_key(
111 &mut app,
112 KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE),
113 );
114 assert!(!app.launch.visible);
115 assert!(!app.launch.return_to_session);
116 assert_eq!(app.current_session_id.as_deref(), Some("retained-session"));
117 assert_eq!(app.api_messages, messages);
118 assert_eq!(app.history.len(), history_len);
119 assert_eq!(app.input, "unsent draft");
120 assert_eq!(app.launch.mark_reveal_started_at, reveal);
121 assert!(!crate::tui::widgets::should_render_empty_state(&app));
122
123 for command in ["/overview", "/stats"] {
124 let result = crate::commands::execute(command, &mut app);
125 assert!(!result.is_error);
126 assert!(result.message.unwrap().contains("Quick Actions"));
127 assert!(!app.launch.visible);
128 }
129 }
130
131 #[test]
132 fn home_refuses_to_cover_active_work() {
133 let mut app =
134 crate::test_support::test_app_with_options(crate::test_support::test_tui_options("."));
135 app.launch.dismiss();
136 app.is_loading = true;
137 let result = HomeCmd::execute(&mut app, None);
138 assert!(result.is_error);
139 assert!(!app.launch.visible);
140 }
141 }
142
142 lines RUST