magi-code 0.80.2

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;
use crate::tui::{render, state::MissionControlState, transcript_projection};
use ratatui::{Terminal, backend::TestBackend};

fn image_state() -> MissionControlState {
    let mut state = MissionControlState::default();
    let mut images = TranscriptImages::start((10, 20));
    images.cache.push_back((
        "image.png".into(),
        Some(Arc::new(PreparedImage {
            width: 400,
            height: 400,
            encoded: String::new(),
        })),
    ));
    state.images = std::cell::RefCell::new(images);
    state
        .transcript
        .push_back("assistant: ![sample image](image.png)");
    state
}

fn draw(state: &MissionControlState, width: u16, height: u16) {
    let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap();
    terminal.draw(|frame| render::draw(frame, state)).unwrap();
}

fn assert_preview_vertical_padding(lines: &[crate::tui::transcript_cards::TranscriptVisualLine]) {
    let first = lines
        .iter()
        .position(|line| line.image_row.is_some())
        .unwrap();
    let last = lines
        .iter()
        .rposition(|line| line.image_row.is_some())
        .unwrap();
    for padding in [&lines[first - 1], &lines[last + 1]] {
        assert!(padding.image_row.is_none());
        assert!(!padding.copyable);
        assert!(padding.copy_text.is_empty());
        assert!(
            padding
                .line
                .spans
                .iter()
                .all(|span| span.content.trim().is_empty())
        );
        assert_eq!(padding.line.style.bg, lines[first].line.style.bg);
    }
}

#[test]
fn markdown_images_have_scroll_rows_but_no_copyable_graphics_codes() {
    let state = image_state();
    let lines = transcript_projection::uncached_transcript_visual_lines(&state, 60);
    assert_preview_vertical_padding(&lines);
    let rows: Vec<_> = lines
        .iter()
        .filter_map(|line| line.image_row.as_ref())
        .collect();
    assert_eq!(rows.len(), usize::from(PREVIEW_ROWS));
    assert_eq!(
        rows.iter().map(|row| row.row).collect::<Vec<_>>(),
        (0..PREVIEW_ROWS).collect::<Vec<_>>()
    );
    assert!(
        lines
            .iter()
            .filter(|line| line.image_row.is_some())
            .all(|line| !line.copyable && line.copy_text.is_empty())
    );
    assert!(lines.iter().all(|line| !line.copy_text.contains('\x1b')));
}

#[test]
fn native_placements_stay_inside_transcript_and_disappear_under_overlays() {
    let mut state = image_state();
    draw(&state, 120, 40);
    let area = render::transcript_text_area_for_pane(
        render::layout_for(Rect::new(0, 0, 120, 40), &state).transcript,
    );
    assert_eq!(
        state.images.borrow().placements.len(),
        usize::from(PREVIEW_ROWS)
    );
    assert!(
        state
            .images
            .borrow()
            .placements
            .iter()
            .all(|p| p.area.intersection(area) == p.area)
    );
    state.show_copy_success_toast(std::time::Instant::now());
    draw(&state, 120, 40);
    assert!(state.images.borrow().placements.is_empty());
    assert!(state.images.borrow().visible_paths.is_empty());
    state.toast = None;
    draw(&state, 120, 40);
    assert_eq!(
        state.images.borrow().placements.len(),
        usize::from(PREVIEW_ROWS)
    );
    state.transcript.clear();
    draw(&state, 120, 40);
    assert!(state.images.borrow().placements.is_empty());
}

#[test]
fn short_viewport_crops_image_top_and_preserves_source_row_offsets() {
    let state = image_state();
    draw(&state, 120, 12);
    let images = state.images.borrow();
    assert!(!images.placements.is_empty());
    assert!(images.placements.len() < usize::from(PREVIEW_ROWS));
    assert!(images.placements[0].source_y > 0);
    assert_eq!(
        images.placements.last().unwrap().source_y
            + images.placements.last().unwrap().source_height,
        400
    );
}

#[test]
fn unsupported_terminals_and_remote_markdown_keep_text_only() {
    let mut state = MissionControlState::default();
    state
        .transcript
        .push_back("assistant: ![sample image](image.png)");
    let lines = transcript_projection::uncached_transcript_visual_lines(&state, 60);
    assert!(lines.iter().all(|line| line.image_row.is_none()));
    assert!(
        lines
            .iter()
            .any(|line| line.copy_text.contains("sample image"))
    );
    let mut state = image_state();
    state.transcript.clear();
    state.transcript.push_back(
        "assistant: ![remote](https://host/image.png) ![inline](data:image/png;base64,AAAA)",
    );
    let lines = transcript_projection::uncached_transcript_visual_lines(&state, 60);
    assert!(lines.iter().all(|line| line.image_row.is_none()));
}

#[test]
fn view_image_uses_parameter_reference_not_display_metadata_as_authority() {
    use crate::output::{ActivityEvent, ActivityId, ActivityStatus, ToolActivityDetail};
    let mut state = image_state();
    state.transcript.clear();
    state.transcript.push_back("tool: ✓ view_image • done");
    let id = ActivityId::new("view");
    state.transcript.link_activity(
        0,
        crate::tui::state::TranscriptActivityLink {
            activity_id: id.clone(),
            tool_name: "view_image".into(),
        },
    );
    state.apply_activity_event(ActivityEvent::ToolResultDetail {
        id,
        detail: ToolActivityDetail {
            tool_name: "view_image".into(),
            label: "image".into(),
            params: serde_json::json!({"path":"image.png"}),
            metadata: serde_json::json!({"path":"wrong.png"}),
            status: ActivityStatus::Success,
            output: "A sample".into(),
            applied_diff: None,
        },
    });
    let lines = transcript_projection::uncached_transcript_visual_lines(&state, 60);
    assert_preview_vertical_padding(&lines);
    let rows: Vec<_> = lines
        .iter()
        .filter_map(|line| line.image_row.as_ref())
        .collect();
    assert_eq!(rows.len(), usize::from(PREVIEW_ROWS));
    assert!(rows.iter().all(|row| row.path == "image.png"));
    let preview_lines: Vec<_> = lines
        .iter()
        .filter(|line| line.image_row.is_some())
        .collect();
    let mut buffer = ratatui::buffer::Buffer::empty(Rect::new(0, 0, 60, PREVIEW_ROWS));
    use ratatui::widgets::Widget;
    ratatui::widgets::Paragraph::new(
        preview_lines
            .iter()
            .map(|line| line.line.clone())
            .collect::<Vec<_>>(),
    )
    .render(buffer.area, &mut buffer);
    for cell in &buffer.content {
        assert_eq!(cell.symbol(), " ", "image pixels must not overlap text");
        assert_eq!(cell.bg, state.theme.surface_panel());
    }
}