magi-code 0.77.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

fn visible_text(state: &MissionControlState, width: u16) -> String {
    crate::tui::transcript_projection::visible_transcript_lines(state, width, 40)
        .lines
        .iter()
        .map(|line| line.to_string())
        .collect::<Vec<_>>()
        .join("\n")
}

#[test]
fn activity_motion_decorates_tool_status_without_rebuilding_cache_or_copy_text() {
    let mut state = linked_tool_state(
        "motion-tool",
        "dynamic_tool",
        "dynamic_tool",
        ActivityStatus::Running,
        vec![],
        "body ● stays literal",
    );
    state.start_running_prompt("work".into());
    state.activity_motion.run_namespace = Some("motion-".into());
    let before_copy = state.transcript_visual_text();
    let first = visible_text(&state, 60);
    assert!(first.contains(state.activity_spinner()), "{first}");
    assert!(first.contains("body ● stays literal"));
    let cached = std::sync::Arc::clone(&state.transcript_cache.borrow().visual_blocks[&60][&0]);
    state.activity_motion.phase = 1;
    let second = visible_text(&state, 60);
    assert_ne!(first, second);
    assert_eq!(state.transcript_visual_text(), before_copy);
    assert!(std::sync::Arc::ptr_eq(
        &cached,
        &state.transcript_cache.borrow().visual_blocks[&60][&0]
    ));
    state.reduced_motion = true;
    let static_text = visible_text(&state, 60);
    assert!(!static_text.contains(state.activity_spinner()));
    assert_eq!(static_text, first.replace('', ""));
    state.reduced_motion = false;
    state.apply_activity_event(ActivityEvent::Finished {
        id: ActivityId::new("motion-tool"),
        status: ActivityStatus::Failed,
        metadata: None,
    });
    let failed = visible_text(&state, 60);
    assert!(!failed.contains(state.activity_spinner()));
    assert!(failed.contains(''), "{failed}");
}

#[test]
fn activity_motion_tracks_subagent_status_and_narrow_clipping() {
    let mut state = MissionControlState::default();
    append_subagent_card_batch(
        &mut state,
        "motion-batch",
        &[
            ActivityStatus::Running,
            ActivityStatus::Success,
            ActivityStatus::Failed,
            ActivityStatus::Canceled,
        ],
    );
    state.start_running_prompt("work".into());
    state.activity_motion.run_namespace = Some("motion-".into());
    let copy = state.transcript_visual_text();
    let text = visible_text(&state, 120);
    // Running roster chip plus the selected running task's summary.
    assert_eq!(text.matches(state.activity_spinner()).count(), 2, "{text}");
    assert!(text.contains('') && text.contains('') && text.contains(''));
    for width in [1, 2, 3, 4, 8, 16] {
        let static_lines = visible_scrollback_visual_lines(&state, width, 40, 0);
        let animated =
            crate::tui::transcript_projection::visible_transcript_lines(&state, width, 40);
        for (original, decorated) in static_lines.lines.iter().zip(animated.lines.iter()) {
            assert_eq!(original.line.width(), decorated.width());
        }
    }
    assert_eq!(state.transcript_visual_text(), copy);
}

#[test]
fn activity_motion_keeps_selection_style_and_literal_dots() {
    use ratatui::{
        style::Style,
        text::{Line, Span},
    };
    let mut state = MissionControlState::default();
    state.start_running_prompt("work".into());
    state.activity_motion.run_namespace = Some("run-1/".into());
    let style = Style::default().add_modifier(Modifier::REVERSED);
    let mut line = Line::from(vec![Span::styled("界 ● literal ●", style)]);
    crate::tui::transcript_projection::decorate_status_indicators(
        &state,
        &[(3, Some(ActivityId::new("run-1/tool")))],
        &mut line,
    );
    assert_eq!(
        line.to_string(),
        format!("{} literal ●", state.activity_spinner())
    );
    assert_eq!(line.spans[0].style, style);
}

fn finish_live_card(
    state: &mut MissionControlState,
    id: &str,
    status: ActivityStatus,
    now: std::time::Instant,
) {
    let event = ActivityEvent::Finished {
        id: ActivityId::new(id),
        status,
        metadata: None,
    };
    state.observe_live_card_completion(&event, now);
    state.apply_activity_event(event);
}

fn accented_success_count(state: &MissionControlState, width: u16) -> usize {
    crate::tui::transcript_projection::visible_transcript_lines(state, width, 40)
        .lines
        .iter()
        .flat_map(|line| &line.spans)
        .filter(|span| {
            span.content == "" && span.style.add_modifier.contains(Modifier::UNDERLINED)
        })
        .count()
}

#[test]
fn activity_motion_success_accent_expires_without_changing_cache_or_copy() {
    let mut state = linked_tool_state(
        "success",
        "dynamic_tool",
        "dynamic_tool",
        ActivityStatus::Running,
        vec![],
        "literal ✓ remains plain",
    );
    state.start_running_prompt("work".into());
    let now = std::time::Instant::now();
    finish_live_card(&mut state, "success", ActivityStatus::Success, now);
    state.clear_running_prompt();
    assert_eq!(accented_success_count(&state, 60), 1);
    let copy = state.transcript_visual_text();
    let text = visible_text(&state, 60);
    let cached = std::sync::Arc::clone(&state.transcript_cache.borrow().visual_blocks[&60][&0]);
    assert!(state.activity_motion_timeout(now).is_some());
    assert!(state.tick_activity_motion(now + std::time::Duration::from_secs(1)));
    assert_eq!(accented_success_count(&state, 60), 0);
    assert_eq!(state.activity_motion_timeout(now), None);
    assert_eq!(state.transcript_visual_text(), copy);
    assert_eq!(visible_text(&state, 60), text);
    assert!(std::sync::Arc::ptr_eq(
        &cached,
        &state.transcript_cache.borrow().visual_blocks[&60][&0]
    ));
}

#[test]
fn activity_motion_success_accents_exclude_replay_failures_and_cancellation() {
    for status in [
        ActivityStatus::Success,
        ActivityStatus::Failed,
        ActivityStatus::Canceled,
    ] {
        let mut state = linked_tool_state(
            "historical",
            "dynamic_tool",
            "dynamic_tool",
            ActivityStatus::Running,
            vec![],
            "body",
        );
        state.apply_activity_event(ActivityEvent::Finished {
            id: ActivityId::new("historical"),
            status,
            metadata: None,
        });
        state.start_running_prompt("next turn".into());
        assert_eq!(accented_success_count(&state, 60), 0);
        finish_live_card(&mut state, "historical", status, std::time::Instant::now());
        assert_eq!(accented_success_count(&state, 60), 0);
    }
    for cancel in [false, true] {
        let mut state = linked_tool_state(
            "tool",
            "dynamic_tool",
            "dynamic_tool",
            ActivityStatus::Running,
            vec![],
            "body",
        );
        state.start_running_prompt("work".into());
        if cancel {
            state.request_running_prompt_cancel();
        } else {
            state.reduced_motion = true;
        }
        finish_live_card(
            &mut state,
            "tool",
            ActivityStatus::Success,
            std::time::Instant::now(),
        );
        assert_eq!(accented_success_count(&state, 60), 0);
    }
}

#[test]
fn activity_motion_subagent_completion_accents_only_the_completed_task() {
    let mut state = MissionControlState::default();
    append_subagent_card_batch(
        &mut state,
        "batch",
        &[ActivityStatus::Running, ActivityStatus::Success],
    );
    state.start_running_prompt("work".into());
    let id = state
        .nodes
        .iter()
        .find(|(id, node)| id.as_str().ends_with("/g1") && node.status == ActivityStatus::Running)
        .map(|(id, _)| id.clone())
        .expect("running first task");
    finish_live_card(
        &mut state,
        id.as_str(),
        ActivityStatus::Success,
        std::time::Instant::now(),
    );
    assert_eq!(accented_success_count(&state, 120), 2);
    for width in [1, 2, 3, 8, 16] {
        let original = visible_scrollback_visual_lines(&state, width, 40, 0);
        let accented =
            crate::tui::transcript_projection::visible_transcript_lines(&state, width, 40);
        for (before, after) in original.lines.iter().zip(&accented.lines) {
            assert_eq!(before.line.width(), after.width());
            assert_eq!(before.line.to_string(), after.to_string());
        }
    }
}

#[test]
fn activity_motion_primary_tool_result_then_finished_accents_success() {
    use crate::tui::TuiEvent;
    use crate::tui::controller::{DrainResult, apply_tui_event_to_state};
    let mut state = MissionControlState::default();
    state.start_running_prompt("work".into());
    let call = crate::providers::ToolCall {
        id: "run-1/tool".into(),
        name: "dynamic_tool".into(),
        arguments: serde_json::json!({}),
    };
    let mut drain = DrainResult::default();
    apply_tui_event_to_state(
        &mut state,
        TuiEvent::Output(OutputEvent::ToolStarted {
            call: Box::new(call.clone()),
            label: "dynamic_tool".into(),
        }),
        &mut drain,
    );
    let result = crate::tools::ToolResult {
        tool_name: call.name.clone(),
        success: true,
        content: "done".into(),
        metadata: serde_json::json!({}),
        display: Default::default(),
    };
    let summary = crate::output::tool_display_summary(&call, &result);
    apply_tui_event_to_state(
        &mut state,
        TuiEvent::Output(OutputEvent::ToolResult {
            call: Box::new(call),
            result: Box::new(result),
            summary: Box::new(summary),
            changed_paths: vec![],
        }),
        &mut drain,
    );
    assert_eq!(accented_success_count(&state, 60), 1);
    apply_tui_event_to_state(
        &mut state,
        TuiEvent::Activity(ActivityEvent::Finished {
            id: ActivityId::new("run-1/tool"),
            status: ActivityStatus::Success,
            metadata: None,
        }),
        &mut drain,
    );
    assert_eq!(accented_success_count(&state, 60), 1);
    state.tick_activity_motion(std::time::Instant::now() + std::time::Duration::from_secs(1));
    assert_eq!(accented_success_count(&state, 60), 0);
}

#[test]
fn activity_motion_resumed_unfinished_tool_stays_static_during_new_prompt() {
    use crate::tui::sessions::commands::{
        apply_session_hydration_snapshot, hydrate_session_history_snapshot,
    };
    let event = crate::sessions::SessionEvent::new(
        "tool_call",
        "session".into(),
        std::path::PathBuf::from("."),
        serde_json::json!({"id":"old-tool", "name":"dynamic_tool", "arguments":{}}),
    );
    let snapshot = hydrate_session_history_snapshot(&[event], &Default::default());
    let mut state = MissionControlState::default();
    apply_session_hydration_snapshot(&mut state, snapshot);
    assert!(
        state
            .nodes
            .values()
            .any(|node| node.status == ActivityStatus::Running)
    );
    let historical = visible_text(&state, 60);
    assert!(historical.contains(''), "{historical}");
    state.start_running_prompt("new work".into());
    state.activity_motion.run_namespace = Some("run-2/".into());
    assert_eq!(visible_text(&state, 60), historical);
    state.apply_output_event(&OutputEvent::ToolStarted {
        call: Box::new(crate::providers::ToolCall {
            id: "run-2/new-tool".into(),
            name: "dynamic_tool".into(),
            arguments: serde_json::json!({}),
        }),
        label: "new tool".into(),
    });
    for phase in [0, 1] {
        state.activity_motion.phase = phase;
        let text = visible_text(&state, 60);
        assert!(text.contains(&historical), "{text}");
        assert_eq!(text.matches(state.activity_spinner()).count(), 1, "{text}");
    }
}