返回 CodeWhale
tree.rs
根目录 / crates / tui / src / commands / groups / session / tree.rs
1 //! `/tree` command — render the session entry journal or linear transcript.
2
3 use super::CommandResult;
4
5 use codewhale_command_contract::facets::{CommandSessionLifecycleContext, TreeBodyProjection};
6 use codewhale_command_contract::handler::{CommandContexts, CommandHandler};
7 use codewhale_command_contract::metadata::{
8 CommandInfo as ContractInfo, RegisterCommand as ContractRegisterCommand,
9 };
10
11 pub(in crate::commands) struct TreeCmd;
12
13 // ---------------------------------------------------------------------------
14 // FEAT-023 Phase 4 (D3/D5/D6): portable contextual registration and handler.
15 // ---------------------------------------------------------------------------
16
17 pub(in crate::commands) const CONTRACT_INFO: ContractInfo = ContractInfo {
18 name: "tree",
19 aliases: &[],
20 usage: "/tree [interactive]",
21 description_key: "cmd_tree_description",
22 };
23
24 impl ContractRegisterCommand<CommandResult> for TreeCmd {
25 fn info() -> &'static ContractInfo {
26 &CONTRACT_INFO
27 }
28 fn handler() -> CommandHandler<CommandResult> {
29 CommandHandler::Contextual {
30 capabilities:
31 codewhale_command_contract::handler::CommandCapabilities::SESSION_LIFECYCLE,
32 handler: tree_contextual,
33 }
34 }
35 }
36
37 pub(in crate::commands) fn tree_contextual(
38 contexts: CommandContexts<'_>,
39 arg: Option<&str>,
40 ) -> CommandResult {
41 let mut parts = contexts.into_parts();
42 let Some(lifecycle) = parts.lifecycle.as_deref_mut() else {
43 return CommandResult::error(
44 "Command capability unavailable: session_lifecycle".to_string(),
45 );
46 };
47 tree_portable(lifecycle, arg)
48 }
49
50 pub(in crate::commands) fn tree_portable(
51 lifecycle: &mut dyn CommandSessionLifecycleContext,
52 _arg: Option<&str>,
53 ) -> CommandResult {
54 match lifecycle.tree_body() {
55 Ok(TreeBodyProjection::Journal { rendered }) => {
56 let mut out = rendered;
57 out.push_str("\nUse `/branch <entry_id>` to branch (moves leaf only, never rewrites history).\n");
58 out.push_str("Use `/fork [session_id]` to fork this session at any node.\n");
59 CommandResult::message(out)
60 }
61 Ok(TreeBodyProjection::Linear { rendered }) => {
62 let mut out = rendered;
63 out.push_str("\nUse `/branch <n>` with entry id after journal is saved.\n");
64 CommandResult::message(out)
65 }
66 Ok(TreeBodyProjection::EmptySession) => CommandResult::message(
67 "(empty session — no entries yet)\nSend a message first, then `/tree` will show the entry journal."
68 .to_string(),
69 ),
70 Ok(TreeBodyProjection::NoSession) => CommandResult::message(
71 "No active session. Use `/resume` to pick a session, then `/tree` to see its journal."
72 .to_string(),
73 ),
74 Err(error) => CommandResult::error(error),
75 }
76 }
77
77 lines RUST