codewhale-tui 0.9.8

Terminal UI for open-source and open-weight coding models
Documentation
use super::CommandResult;
use crate::commands::traits::{CommandInfo, RegisterCommand};
use crate::localization::MessageId;
use crate::tui::app::App;
pub(in crate::commands) const COMMAND_INFO: CommandInfo = CommandInfo {
    name: "branch",
    aliases: &[],
    usage: "/branch <entry_id>",
    description_id: MessageId::CmdBranchDescription,
};
pub(in crate::commands) struct BranchCmd;
impl RegisterCommand for BranchCmd {
    fn info() -> &'static CommandInfo {
        &COMMAND_INFO
    }
    fn execute(app: &mut App, arg: Option<&str>) -> CommandResult {
        branch(app, arg)
    }
}
fn branch(app: &mut App, arg: Option<&str>) -> CommandResult {
    if app.session_transition_blocked() {
        return CommandResult::error(
            "Cannot branch while runtime work is active. Wait for the turn to finish, or cancel it first.",
        );
    }
    let Some(entry_id) = arg.map(str::trim).filter(|s| !s.is_empty()) else {
        if let Some(session_id) = app.current_session_id.as_deref()
            && let Ok(manager) = crate::session_manager::SessionManager::default_location()
            && let Ok(mut session) = manager.load_session(session_id)
        {
            session.ensure_journal();
            if let Some(journal) = session.journal.as_ref()
                && let Some(leaf) = journal.leaf_id.as_deref()
            {
                return CommandResult::message(format!(
                    "Current leaf: {leaf}\nUse `/branch <entry_id>` to move the leaf (history is never rewritten).\nUse `/tree` to list entry ids."
                ));
            }
        }
        return CommandResult::message(
            "Usage: /branch <entry_id>\nMoves the active leaf to an existing entry. Future appends become children of that entry.\nHistory is never rewritten — branching only moves the leaf.\n\nUse `/tree` to see entry ids.",
        );
    };
    let session_id = match app.current_session_id.clone() {
        Some(id) => id,
        None => {
            return CommandResult::error(
                "No active session to branch. Resume or create a session first.",
            );
        }
    };
    let manager = match crate::session_manager::SessionManager::default_location() {
        Ok(m) => m,
        Err(e) => return CommandResult::error(format!("could not open sessions directory: {e}")),
    };
    let mut session = match manager.load_session(&session_id) {
        Ok(s) => s,
        Err(e) => return CommandResult::error(format!("could not load session {session_id}: {e}")),
    };
    session.ensure_journal();
    let journal_len_before = session
        .journal
        .as_ref()
        .map(|j| j.entries.len())
        .unwrap_or(0);
    match session.journal_branch_to(entry_id) {
        Ok(()) => {
            if let Err(e) = manager.save_session(&session) {
                return CommandResult::error(format!("branch saved but persist failed: {e}"));
            }
            app.api_messages = session.messages.clone();
            let leaf = session
                .leaf_id
                .clone()
                .unwrap_or_else(|| "(none)".to_string());
            let msg = format!(
                "Branched to entry {entry_id} (leaf now {leaf}); journal entries {journal_len_before} (history preserved, leaf moved only)"
            );
            CommandResult::message(msg)
        }
        Err(e) => CommandResult::error(format!(
            "branch failed: {e}. Use `/tree` to see valid entry ids."
        )),
    }
}