Codex Handbook
tui/src/history_cell/tests.rs 2590 lines
//! Coverage for history-cell rendering, wrapping, and transcript behavior.use super::*;use crate::exec_cell::CommandOutput;use crate::exec_cell::ExecCall;use crate::exec_cell::ExecCell;use crate::legacy_core::config::Config;use crate::legacy_core::config::ConfigBuilder;use crate::session_state::ThreadSessionState;use crate::wrapping::word_wrap_lines;use codex_app_server_protocol::AskForApproval;use codex_app_server_protocol::McpAuthStatus;use codex_config::types::McpServerConfig;use codex_otel::RuntimeMetricTotals;use codex_otel::RuntimeMetricsSummary;use codex_protocol::ThreadId;use codex_protocol::account::PlanType;use codex_protocol::parse_command::ParsedCommand;use dirs::home_dir;use pretty_assertions::assert_eq;use ratatui::buffer::Buffer;use ratatui::layout::Rect;use serde_json::json;use std::collections::HashMap;use std::path::PathBuf;use codex_app_server_protocol::CommandExecutionSource as ExecCommandSource;use codex_protocol::mcp::CallToolResult;use codex_protocol::mcp::Tool;use rmcp::model::Content;const SMALL_PNG_BASE64: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==";async fn test_config() -> Config {    let codex_home = std::env::temp_dir();    ConfigBuilder::default()        .codex_home(codex_home.clone())        .build()        .await        .expect("config")}fn test_cwd() -> PathBuf {    // These tests only need a stable absolute cwd; using temp_dir() avoids baking Unix- or    // Windows-specific root semantics into the fixtures.    std::env::temp_dir()}#[test]fn streaming_agent_tail_blank_line_uses_one_viewport_row() {    let cell = StreamingAgentTailCell::new(        vec![            HyperlinkLine::from("first"),            HyperlinkLine::from(""),            HyperlinkLine::from("second"),        ],        /*is_first_line*/ false,    );    let lines = cell.display_lines(/*width*/ 80);    insta::assert_snapshot!(render_lines(&lines).join("\n"), @"  first  second");    assert_eq!(cell.desired_height(/*width*/ 80), 3);}fn stdio_server_config(    command: &str,    args: Vec<&str>,    env: Option<HashMap<String, String>>,    env_vars: Vec<&str>,) -> McpServerConfig {    let mut table = toml::Table::new();    table.insert(        "command".to_string(),        toml::Value::String(command.to_string()),    );    if !args.is_empty() {        table.insert(            "args".to_string(),            toml::Value::Array(                args.into_iter()                    .map(|arg| toml::Value::String(arg.to_string()))                    .collect(),            ),        );    }    if let Some(env) = env {        table.insert("env".to_string(), string_map_to_toml_value(env));    }    if !env_vars.is_empty() {        table.insert(            "env_vars".to_string(),            toml::Value::Array(                env_vars                    .into_iter()                    .map(|name| toml::Value::String(name.to_string()))                    .collect(),            ),        );    }    toml::Value::Table(table)        .try_into()        .expect("test stdio MCP config should deserialize")}fn streamable_http_server_config(    url: &str,    bearer_token_env_var: Option<&str>,    http_headers: Option<HashMap<String, String>>,    env_http_headers: Option<HashMap<String, String>>,) -> McpServerConfig {    let mut table = toml::Table::new();    table.insert("url".to_string(), toml::Value::String(url.to_string()));    if let Some(bearer_token_env_var) = bearer_token_env_var {        table.insert(            "bearer_token_env_var".to_string(),            toml::Value::String(bearer_token_env_var.to_string()),        );    }    if let Some(http_headers) = http_headers {        table.insert(            "http_headers".to_string(),            string_map_to_toml_value(http_headers),        );    }    if let Some(env_http_headers) = env_http_headers {        table.insert(            "env_http_headers".to_string(),            string_map_to_toml_value(env_http_headers),        );    }    toml::Value::Table(table)        .try_into()        .expect("test streamable_http MCP config should deserialize")}fn string_map_to_toml_value(entries: HashMap<String, String>) -> toml::Value {    toml::Value::Table(        entries            .into_iter()            .map(|(key, value)| (key, toml::Value::String(value)))            .collect(),    )}fn render_lines(lines: &[Line<'static>]) -> Vec<String> {    lines        .iter()        .map(|line| {            line.spans                .iter()                .map(|span| span.content.as_ref())                .collect::<String>()        })        .collect()}fn render_transcript(cell: &dyn HistoryCell) -> Vec<String> {    render_lines(&cell.transcript_lines(u16::MAX))}fn assert_unstyled_lines(lines: &[Line<'static>]) {    for line in lines {        assert_eq!(line.style, Style::default());        for span in &line.spans {            assert_eq!(span.style, Style::default());        }    }}fn image_block(data: &str) -> serde_json::Value {    serde_json::to_value(Content::image(data.to_string(), "image/png"))        .expect("image content should serialize")}fn text_block(text: &str) -> serde_json::Value {    serde_json::to_value(Content::text(text)).expect("text content should serialize")}fn resource_link_block(    uri: &str,    name: &str,    title: Option<&str>,    description: Option<&str>,) -> serde_json::Value {    serde_json::to_value(Content::resource_link(rmcp::model::RawResource {        uri: uri.to_string(),        name: name.to_string(),        title: title.map(str::to_string),        description: description.map(str::to_string),        mime_type: None,        size: None,        icons: None,        meta: None,    }))    .expect("resource link content should serialize")}#[test]fn raw_lines_from_source_preserves_explicit_blank_lines() {    let lines = raw_lines_from_source("alpha\n\nbeta\n");    assert_eq!(        render_lines(&lines),        vec!["alpha".to_string(), String::new(), "beta".to_string()]    );    assert_unstyled_lines(&lines);}#[test]fn raw_lines_from_source_preserves_trailing_blank_but_not_trailing_newline() {    assert_eq!(        render_lines(&raw_lines_from_source("alpha\n\n")),        vec!["alpha".to_string(), String::new()]    );    assert_eq!(raw_lines_from_source(""), Vec::<Line<'static>>::new());}#[test]fn source_backed_cells_render_raw_source_without_prefix_or_style() {    let user = new_user_prompt(        "hello\n\nworld\n".to_string(),        Vec::new(),        Vec::new(),        Vec::new(),    );    let assistant = AgentMarkdownCell::new(        "- item\n\n| A | B |\n| - | - |\n| x | y |\n".to_string(),        &test_cwd(),    );    let reasoning = ReasoningSummaryCell::new(        "thinking".to_string(),        "first thought\n\nsecond thought".to_string(),        &test_cwd(),        /*transcript_only*/ false,    );    let plan = new_proposed_plan(        "1. Inspect\n\n```sh\ncargo test\n```".to_string(),        &test_cwd(),    );    let user_lines = user.raw_lines();    assert_eq!(        render_lines(&user_lines),        vec!["hello".to_string(), String::new(), "world".to_string()]    );    assert_unstyled_lines(&user_lines);    let assistant_lines = assistant.raw_lines();    assert_eq!(        render_lines(&assistant_lines),        vec![            "- item".to_string(),            String::new(),            "| A | B |".to_string(),            "| - | - |".to_string(),            "| x | y |".to_string(),        ]    );    assert_unstyled_lines(&assistant_lines);    let reasoning_lines = reasoning.raw_lines();    assert_eq!(        render_lines(&reasoning_lines),        vec![            "first thought".to_string(),            String::new(),            "second thought".to_string(),        ]    );    assert_unstyled_lines(&reasoning_lines);    let plan_lines = plan.raw_lines();    assert_eq!(        render_lines(&plan_lines),        vec![            "1. Inspect".to_string(),            String::new(),            "```sh".to_string(),            "cargo test".to_string(),            "```".to_string(),        ]    );    assert_unstyled_lines(&plan_lines);}#[test]fn proposed_plan_cell_renders_markdown_table() {    let plan = new_proposed_plan(        "## Plan\n\n| Step | Owner |\n| --- | --- |\n| Verify | Codex |\n".to_string(),        &test_cwd(),    );    let rendered = render_lines(&plan.display_lines(/*width*/ 80));    assert!(        rendered.iter().any(|line| line.contains('━')),        "expected separated table in proposed plan output: {rendered:?}"    );    assert!(        !rendered            .iter()            .any(|line| line.trim() == "| Step | Owner |"),        "did not expect raw table header in rich proposed plan output: {rendered:?}"    );    let raw = render_lines(&plan.raw_lines());    assert!(        raw.iter().any(|line| line == "| Step | Owner |"),        "expected raw mode to preserve table markdown source: {raw:?}"    );}#[test]fn proposed_plan_cell_preserves_wrapped_table_web_links() {    let destination = "https://example.com/a/very/long/path/to/a/table/artifact";    let plan = new_proposed_plan(        format!("| Step | URL |\n| --- | --- |\n| Verify | {destination} |\n"),        &test_cwd(),    );    let lines = plan.display_hyperlink_lines(/*width*/ 32);    let linked_rows = lines        .iter()        .filter(|line| !line.hyperlinks.is_empty())        .collect::<Vec<_>>();    assert!(linked_rows.len() > 1);    assert!(linked_rows.iter().all(|line| {        line.hyperlinks            .iter()            .all(|link| link.destination == destination)    }));}#[test]fn composite_cell_preserves_child_web_links() {    let destination = "https://chatgpt.com/codex/settings/usage";    let cell = CompositeHistoryCell::new(vec![        Box::new(PlainHistoryCell::new(vec![Line::from("/status")])),        Box::new(WebHyperlinkHistoryCell::new(vec![Line::from(destination)])),    ]);    let lines = cell.display_hyperlink_lines(/*width*/ 80);    assert_eq!(        lines[2].hyperlinks,        vec![crate::terminal_hyperlinks::TerminalHyperlink {            columns: 0..destination.len(),            destination: destination.to_string(),        }]    );}#[test]fn proposed_plan_cell_unwraps_markdown_fenced_table() {    let plan = new_proposed_plan(        "## Plan\n\n```markdown\n| Step | Owner |\n| --- | --- |\n| Verify | Codex |\n```\n"            .to_string(),        &test_cwd(),    );    let rendered = render_lines(&plan.display_lines(/*width*/ 80));    assert!(        rendered.iter().any(|line| line.contains('━')),        "expected separated table for markdown-fenced proposed plan output: {rendered:?}"    );    assert!(        !rendered.iter().any(|line| line.trim() == "```markdown"),        "did not expect markdown fence to render as code block: {rendered:?}"    );}#[test]fn structured_tool_cell_renders_raw_plain_text_without_prefix_or_style() {    let invocation = McpInvocation {        server: "search".into(),        tool: "find_docs".into(),        arguments: Some(json!({"query": "raw mode"})),    };    let result = CallToolResult {        content: vec![text_block("alpha\nbeta")],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-raw".to_string(),        invocation,        /*animations_enabled*/ false,    );    assert!(        cell.complete(Duration::from_millis(1), Ok(result))            .is_none()    );    let lines = cell.raw_lines();    let rendered = render_lines(&lines);    assert!(rendered[0].starts_with("Called search.find_docs("));    assert_eq!(rendered[1..], ["alpha".to_string(), "beta".to_string()]);    assert_unstyled_lines(&lines);}#[test]fn raw_mode_toggle_transcript_snapshot() {    let mut tool_cell = new_active_mcp_tool_call(        "call-snapshot".to_string(),        McpInvocation {            server: "workspace".to_string(),            tool: "inspect".to_string(),            arguments: Some(json!({"path": "README.md"})),        },        /*animations_enabled*/ false,    );    assert!(        tool_cell            .complete(                Duration::from_millis(5),                Ok(CallToolResult {                    content: vec![text_block("structured output\nsecond line")],                    is_error: None,                    structured_content: None,                    meta: None,                }),            )            .is_none()    );    let cells: Vec<Box<dyn HistoryCell>> = vec![            Box::new(new_user_prompt(                "Please format this\nfor copying".to_string(),                Vec::new(),                Vec::new(),                Vec::new(),            )),            Box::new(AgentMarkdownCell::new(                "- first item\n- second item\n\n| Col | Value |\n| --- | --- |\n| code | `x = 1` |\n\n```text\ncopy me\n```".to_string(),                &test_cwd(),            )),            Box::new(tool_cell),        ];    let render = |mode| {        cells            .iter()            .flat_map(|cell| cell.display_lines_for_mode(/*width*/ 40, mode))            .map(|line| {                line.spans                    .into_iter()                    .map(|span| span.content.into_owned())                    .collect::<String>()            })            .collect::<Vec<_>>()            .join("\n")    };    let rendered = format!(        "rich before:\n{}\n\nraw on:\n{}\n\nrich after:\n{}",        render(HistoryRenderMode::Rich),        render(HistoryRenderMode::Raw),        render(HistoryRenderMode::Rich)    );    insta::assert_snapshot!("raw_mode_toggle_transcript", rendered);}#[test]fn image_generation_call_renders_saved_path() {    let saved_path = test_path_buf("/tmp/generated-image.png").abs();    let expected_saved_path = format!(        "  └ Saved to: {}",        Url::from_file_path(saved_path.as_path()).expect("test path should convert to file URL")    );    let cell = new_image_generation_call(        "call-image-generation".to_string(),        "completed",        Some("A tiny blue square".to_string()),        Some(saved_path),    );    assert_eq!(        render_lines(&cell.display_lines(/*width*/ 80)),        vec![            "• Generated Image:".to_string(),            "  └ A tiny blue square".to_string(),            expected_saved_path,        ],    );}fn session_configured_event(model: &str) -> ThreadSessionState {    ThreadSessionState {        thread_id: ThreadId::new(),        forked_from_id: None,        fork_parent_title: None,        thread_name: None,        model: model.to_string(),        model_provider_id: "test-provider".to_string(),        service_tier: None,        approval_policy: AskForApproval::Never,        approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,        permission_profile: PermissionProfile::read_only(),        active_permission_profile: None,        cwd: test_path_buf("/tmp/project").abs(),        runtime_workspace_roots: Vec::new(),        instruction_source_paths: Vec::new(),        reasoning_effort: None,        collaboration_mode: None,        personality: None,        message_history: None,        network_proxy: None,        rollout_path: Some(PathBuf::new()),    }}#[test]fn unified_exec_interaction_cell_renders_input() {    let cell = new_unified_exec_interaction(Some("echo hello".to_string()), "ls\npwd".to_string());    let lines = render_transcript(&cell);    assert_eq!(        lines,        vec![            "↳ Interacted with background terminal · echo hello",            "  └ ls",            "    pwd",        ],    );}#[test]fn unified_exec_interaction_cell_renders_wait() {    let cell = new_unified_exec_interaction(/*command_display*/ None, String::new());    let lines = render_transcript(&cell);    assert_eq!(lines, vec!["• Waited for background terminal"]);}#[test]fn final_message_separator_hides_short_worked_label_and_includes_runtime_metrics() {    let summary = RuntimeMetricsSummary {        tool_calls: RuntimeMetricTotals {            count: 3,            duration_ms: 2_450,        },        api_calls: RuntimeMetricTotals {            count: 2,            duration_ms: 1_200,        },        streaming_events: RuntimeMetricTotals {            count: 6,            duration_ms: 900,        },        websocket_calls: RuntimeMetricTotals {            count: 1,            duration_ms: 700,        },        websocket_events: RuntimeMetricTotals {            count: 4,            duration_ms: 1_200,        },        responses_api_overhead_ms: 650,        responses_api_inference_time_ms: 1_940,        responses_api_engine_iapi_ttft_ms: 410,        responses_api_engine_service_ttft_ms: 460,        responses_api_engine_iapi_tbt_ms: 1_180,        responses_api_engine_service_tbt_ms: 1_240,        turn_ttft_ms: 0,        turn_ttfm_ms: 0,    };    let cell = FinalMessageSeparator::new(Some(12), Some(summary));    let rendered = render_lines(&cell.display_lines(/*width*/ 600));    assert_eq!(rendered.len(), 1);    assert!(!rendered[0].contains("Worked for"));    assert!(rendered[0].contains("Local tools: 3 calls (2.5s)"));    assert!(rendered[0].contains("Inference: 2 calls (1.2s)"));    assert!(rendered[0].contains("WebSocket: 1 events send (700ms)"));    assert!(rendered[0].contains("Streams: 6 events (900ms)"));    assert!(rendered[0].contains("4 events received (1.2s)"));    assert!(rendered[0].contains("Responses API overhead: 650ms"));    assert!(rendered[0].contains("Responses API inference: 1.9s"));    assert!(rendered[0].contains("TTFT: 410ms (iapi) 460ms (service)"));    assert!(rendered[0].contains("TBT: 1.2s (iapi) 1.2s (service)"));}#[test]fn final_message_separator_includes_worked_label_after_one_minute() {    let cell = FinalMessageSeparator::new(Some(61), /*runtime_metrics*/ None);    let rendered = render_lines(&cell.display_lines(/*width*/ 200));    assert_eq!(rendered.len(), 1);    assert!(rendered[0].contains("Worked for"));}#[test]fn ps_output_empty_snapshot() {    let cell = new_unified_exec_processes_output(Vec::new());    let rendered = render_lines(&cell.display_lines(/*width*/ 60)).join("\n");    insta::assert_snapshot!(rendered);}#[tokio::test]async fn session_info_uses_availability_nux_tooltip_override() {    let config = test_config().await;    let cell = new_session_info(        &config,        "gpt-5",        &session_configured_event("gpt-5"),        /*is_first_event*/ false,        Some("Model just became available".to_string()),        Some(PlanType::Free),        /*show_fast_status*/ false,    );    let rendered = render_transcript(&cell).join("\n");    assert!(rendered.contains("Model just became available"));}#[tokio::test]#[cfg_attr(    target_os = "windows",    ignore = "snapshot path rendering differs on Windows")]async fn session_info_availability_nux_tooltip_snapshot() {    let mut config = test_config().await;    config.cwd = test_path_buf("/tmp/project").abs();    let cell = new_session_info(        &config,        "gpt-5",        &session_configured_event("gpt-5"),        /*is_first_event*/ false,        Some("Model just became available".to_string()),        Some(PlanType::Free),        /*show_fast_status*/ false,    );    let rendered = render_transcript(&cell).join("\n");    insta::assert_snapshot!(rendered);}#[tokio::test]async fn session_info_first_event_suppresses_tooltips_and_nux() {    let config = test_config().await;    let cell = new_session_info(        &config,        "gpt-5",        &session_configured_event("gpt-5"),        /*is_first_event*/ true,        Some("Model just became available".to_string()),        Some(PlanType::Free),        /*show_fast_status*/ false,    );    let rendered = render_transcript(&cell).join("\n");    assert!(!rendered.contains("Model just became available"));    assert!(rendered.contains("To get started"));}#[tokio::test]async fn session_info_hides_tooltips_when_disabled() {    let mut config = test_config().await;    config.show_tooltips = false;    let cell = new_session_info(        &config,        "gpt-5",        &session_configured_event("gpt-5"),        /*is_first_event*/ false,        Some("Model just became available".to_string()),        Some(PlanType::Free),        /*show_fast_status*/ false,    );    let rendered = render_transcript(&cell).join("\n");    assert!(!rendered.contains("Model just became available"));}#[test]fn ps_output_multiline_snapshot() {    let cell = new_unified_exec_processes_output(vec![        UnifiedExecProcessDetails {            command_display: "echo hello\nand then some extra text".to_string(),            recent_chunks: vec!["hello".to_string(), "done".to_string()],        },        UnifiedExecProcessDetails {            command_display: "rg \"foo\" src".to_string(),            recent_chunks: vec!["src/main.rs:12:foo".to_string()],        },    ]);    let rendered = render_lines(&cell.display_lines(/*width*/ 40)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn cyber_policy_error_event_snapshot() {    let cell = new_cyber_policy_error_event();    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn cyber_policy_error_event_narrow_snapshot() {    let cell = new_cyber_policy_error_event();    let rendered = render_lines(&cell.display_lines(/*width*/ 36)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn ps_output_long_command_snapshot() {    let cell = new_unified_exec_processes_output(vec![UnifiedExecProcessDetails {        command_display: String::from(            "rg \"foo\" src --glob '**/*.rs' --max-count 1000 --no-ignore --hidden --follow --glob '!target/**'",        ),        recent_chunks: vec!["searching...".to_string()],    }]);    let rendered = render_lines(&cell.display_lines(/*width*/ 36)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn ps_output_many_sessions_snapshot() {    let cell = new_unified_exec_processes_output(        (0..20)            .map(|idx| UnifiedExecProcessDetails {                command_display: format!("command {idx}"),                recent_chunks: Vec::new(),            })            .collect(),    );    let rendered = render_lines(&cell.display_lines(/*width*/ 32)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn ps_output_chunk_leading_whitespace_snapshot() {    let cell = new_unified_exec_processes_output(vec![UnifiedExecProcessDetails {        command_display: "just fix".to_string(),        recent_chunks: vec![            "  indented first".to_string(),            "    more indented".to_string(),        ],    }]);    let rendered = render_lines(&cell.display_lines(/*width*/ 60)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn error_event_oversized_input_snapshot() {    let cell = new_error_event(        "Message exceeds the maximum length of 1048576 characters (1048577 provided).".to_string(),    );    let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");    insta::assert_snapshot!(rendered);}#[tokio::test]async fn mcp_tools_output_masks_sensitive_values() {    let mut config = test_config().await;    let mut env = HashMap::new();    env.insert("TOKEN".to_string(), "secret".to_string());    let stdio_config = stdio_server_config("docs-server", vec![], Some(env), vec!["APP_TOKEN"]);    let mut servers = config.mcp_servers.get().clone();    servers.insert("docs".to_string(), stdio_config);    let mut headers = HashMap::new();    headers.insert("Authorization".to_string(), "Bearer secret".to_string());    let mut env_headers = HashMap::new();    env_headers.insert("X-API-Key".to_string(), "API_KEY_ENV".to_string());    let http_config = streamable_http_server_config(        "https://example.com/mcp",        Some("MCP_TOKEN"),        Some(headers),        Some(env_headers),    );    servers.insert("http".to_string(), http_config);    config        .mcp_servers        .set(servers)        .expect("test mcp servers should accept any configuration");    let mut tools: HashMap<String, Tool> = HashMap::new();    tools.insert(        "mcp__docs__list".to_string(),        Tool {            description: None,            name: "list".to_string(),            title: None,            input_schema: serde_json::json!({"type": "object", "properties": {}}),            output_schema: None,            annotations: None,            icons: None,            meta: None,        },    );    tools.insert(        "mcp__http__ping".to_string(),        Tool {            description: None,            name: "ping".to_string(),            title: None,            input_schema: serde_json::json!({"type": "object", "properties": {}}),            output_schema: None,            annotations: None,            icons: None,            meta: None,        },    );    let auth_statuses: HashMap<String, McpAuthStatus> = HashMap::new();    let cell = new_mcp_tools_output(        &config,        tools,        HashMap::new(),        HashMap::new(),        &auth_statuses,    );    let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");    insta::assert_snapshot!(rendered);}#[tokio::test]async fn mcp_tools_output_lists_tools_for_hyphenated_server_names() {    let mut config = test_config().await;    let mut servers = config.mcp_servers.get().clone();    servers.insert(        "some-server".to_string(),        stdio_server_config("docs-server", vec!["--stdio"], /*env*/ None, vec![]),    );    config        .mcp_servers        .set(servers)        .expect("test mcp servers should accept any configuration");    let tools = HashMap::from([(        "mcp__some_server__lookup".to_string(),        Tool {            description: None,            name: "lookup".to_string(),            title: None,            input_schema: serde_json::json!({"type": "object", "properties": {}}),            output_schema: None,            annotations: None,            icons: None,            meta: None,        },    )]);    let auth_statuses: HashMap<String, McpAuthStatus> = HashMap::new();    let cell = new_mcp_tools_output(        &config,        tools,        HashMap::new(),        HashMap::new(),        &auth_statuses,    );    let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn mcp_tools_output_from_statuses_renders_status_only_servers() {    let statuses = vec![McpServerStatus {        name: "plugin_docs".to_string(),        server_info: None,        tools: HashMap::from([(            "lookup".to_string(),            Tool {                description: None,                name: "lookup".to_string(),                title: None,                input_schema: serde_json::json!({"type": "object", "properties": {}}),                output_schema: None,                annotations: None,                icons: None,                meta: None,            },        )]),        resources: Vec::new(),        resource_templates: Vec::new(),        auth_status: codex_app_server_protocol::McpAuthStatus::Unsupported,    }];    let cell =        new_mcp_tools_output_from_statuses(&statuses, McpServerStatusDetail::ToolsAndAuthOnly);    let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn mcp_tools_output_from_statuses_renders_verbose_inventory() {    let statuses = vec![McpServerStatus {        name: "plugin_docs".to_string(),        server_info: None,        tools: HashMap::from([(            "lookup".to_string(),            Tool {                description: None,                name: "lookup".to_string(),                title: None,                input_schema: serde_json::json!({"type": "object", "properties": {}}),                output_schema: None,                annotations: None,                icons: None,                meta: None,            },        )]),        resources: vec![Resource {            annotations: None,            description: None,            mime_type: None,            name: "docs".to_string(),            size: None,            title: Some("Docs".to_string()),            uri: "file:///docs".to_string(),            icons: None,            meta: None,        }],        resource_templates: vec![ResourceTemplate {            annotations: None,            uri_template: "file:///docs/{id}".to_string(),            name: "doc-template".to_string(),            title: Some("Doc Template".to_string()),            description: None,            mime_type: None,        }],        auth_status: codex_app_server_protocol::McpAuthStatus::Unsupported,    }];    let cell = new_mcp_tools_output_from_statuses(&statuses, McpServerStatusDetail::Full);    let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn empty_agent_message_cell_transcript() {    let cell = AgentMessageCell::new(vec![Line::default()], /*is_first_line*/ false);    assert_eq!(cell.transcript_lines(/*width*/ 80), vec![Line::from("  ")]);    assert_eq!(cell.desired_transcript_height(/*width*/ 80), 1);}#[test]fn prefixed_wrapped_history_cell_indents_wrapped_lines() {    let summary = Line::from(vec![        "You ".into(),        "approved".bold(),        " codex to run ".into(),        "echo something really long to ensure wrapping happens".dim(),        " this time".bold(),    ]);    let cell = PrefixedWrappedHistoryCell::new(summary, "✔ ".green(), "  ");    let rendered = render_lines(&cell.display_lines(/*width*/ 24));    assert_eq!(        rendered,        vec![            "✔ You approved codex to".to_string(),            "  run echo something".to_string(),            "  really long to ensure".to_string(),            "  wrapping happens this".to_string(),            "  time".to_string(),        ]    );}#[test]fn prefixed_wrapped_history_cell_does_not_split_url_like_token() {    let url_like = "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890";    let cell = PrefixedWrappedHistoryCell::new(Line::from(url_like), "✔ ".green(), "  ");    let rendered = render_lines(&cell.display_lines(/*width*/ 24));    assert_eq!(        rendered            .iter()            .filter(|line| line.contains(url_like))            .count(),        1,        "expected full URL-like token in one rendered line, got: {rendered:?}"    );}#[test]fn unified_exec_interaction_cell_does_not_split_url_like_stdin_token() {    let url_like = "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890";    let cell = UnifiedExecInteractionCell::new(Some("true".to_string()), url_like.to_string());    let rendered = render_lines(&cell.display_lines(/*width*/ 24));    assert_eq!(        rendered            .iter()            .filter(|line| line.contains(url_like))            .count(),        1,        "expected full URL-like token in one rendered line, got: {rendered:?}"    );}#[test]fn prefixed_wrapped_history_cell_height_matches_wrapped_rendering() {    let url_like = "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890/artifacts/reports/performance/summary/detail/with/a/very/long/path";    let cell: Box<dyn HistoryCell> = Box::new(PrefixedWrappedHistoryCell::new(        Line::from(url_like),        "✔ ".green(),        "  ",    ));    let width: u16 = 24;    let logical_height = cell.display_lines(width).len() as u16;    let wrapped_height = cell.desired_height(width);    assert!(        wrapped_height > logical_height,        "expected wrapped height to exceed logical line count ({logical_height}), got {wrapped_height}"    );    let area = Rect::new(0, 0, width, wrapped_height);    let mut buf = ratatui::buffer::Buffer::empty(area);    cell.render(area, &mut buf);    let first_row = (0..area.width)        .map(|x| {            let symbol = buf[(x, 0)].symbol();            if symbol.is_empty() {                ' '            } else {                symbol.chars().next().unwrap_or(' ')            }        })        .collect::<String>();    assert!(        first_row.contains("✔"),        "expected first rendered row to keep the prefix visible, got: {first_row:?}"    );}#[test]fn unified_exec_interaction_cell_height_matches_wrapped_rendering() {    let url_like = "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890/artifacts/reports/performance/summary/detail/with/a/very/long/path";    let cell: Box<dyn HistoryCell> = Box::new(UnifiedExecInteractionCell::new(        Some("true".to_string()),        url_like.to_string(),    ));    let width: u16 = 24;    let logical_height = cell.display_lines(width).len() as u16;    let wrapped_height = cell.desired_height(width);    assert!(        wrapped_height > logical_height,        "expected wrapped height to exceed logical line count ({logical_height}), got {wrapped_height}"    );    let area = Rect::new(0, 0, width, wrapped_height);    let mut buf = ratatui::buffer::Buffer::empty(area);    cell.render(area, &mut buf);    let first_row = (0..area.width)        .map(|x| {            let symbol = buf[(x, 0)].symbol();            if symbol.is_empty() {                ' '            } else {                symbol.chars().next().unwrap_or(' ')            }        })        .collect::<String>();    assert!(        first_row.contains("Interacted with"),        "expected first rendered row to keep the header visible, got: {first_row:?}"    );}#[test]fn web_search_history_cell_snapshot() {    let query = "example search query with several generic words to exercise wrapping".to_string();    let cell = new_web_search_call(        "call-1".to_string(),        query.clone(),        WebSearchAction::Search {            query: Some(query),            queries: None,        },    );    let rendered = render_lines(&cell.display_lines(/*width*/ 64)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn standalone_unix_update_available_history_cell_snapshot() {    let cell =        UpdateAvailableHistoryCell::new("9.9.9".to_string(), Some(UpdateAction::StandaloneUnix));    let rendered = render_lines(&cell.display_lines(/*width*/ 110)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn standalone_windows_update_available_history_cell_snapshot() {    let cell =        UpdateAvailableHistoryCell::new("9.9.9".to_string(), Some(UpdateAction::StandaloneWindows));    let rendered = render_lines(&cell.display_lines(/*width*/ 110)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn web_search_history_cell_without_detail_snapshot() {    let cell = new_web_search_call("call-1".to_string(), String::new(), WebSearchAction::Other);    let rendered = render_lines(&cell.display_lines(/*width*/ 64)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn web_search_history_cell_wraps_with_indented_continuation() {    let query = "example search query with several generic words to exercise wrapping".to_string();    let cell = new_web_search_call(        "call-1".to_string(),        query.clone(),        WebSearchAction::Search {            query: Some(query),            queries: None,        },    );    let rendered = render_lines(&cell.display_lines(/*width*/ 64));    assert_eq!(        rendered,        vec![            "• Searched the web for example search query with several generic".to_string(),            "  words to exercise wrapping".to_string(),        ]    );}#[test]fn web_search_history_cell_short_query_does_not_wrap() {    let query = "short query".to_string();    let cell = new_web_search_call(        "call-1".to_string(),        query.clone(),        WebSearchAction::Search {            query: Some(query),            queries: None,        },    );    let rendered = render_lines(&cell.display_lines(/*width*/ 64));    assert_eq!(        rendered,        vec!["• Searched the web for short query".to_string()]    );}#[test]fn web_search_history_cell_transcript_snapshot() {    let query = "example search query with several generic words to exercise wrapping".to_string();    let cell = new_web_search_call(        "call-1".to_string(),        query.clone(),        WebSearchAction::Search {            query: Some(query),            queries: None,        },    );    let rendered = render_lines(&cell.transcript_lines(/*width*/ 64)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn active_mcp_tool_call_snapshot() {    let invocation = McpInvocation {        server: "search".into(),        tool: "find_docs".into(),        arguments: Some(json!({            "query": "ratatui styling",            "limit": 3,        })),    };    let cell = new_active_mcp_tool_call(        "call-1".into(),        invocation,        /*animations_enabled*/ true,    );    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn mcp_inventory_loading_snapshot() {    let cell = new_mcp_inventory_loading(/*animations_enabled*/ true);    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn mcp_inventory_loading_without_animations_is_stable() {    let cell = new_mcp_inventory_loading(/*animations_enabled*/ false);    let first = render_lines(&cell.display_lines(/*width*/ 80));    let second = render_lines(&cell.display_lines(/*width*/ 80));    assert_eq!(first, second);    assert_eq!(first, vec!["• Loading MCP inventory…".to_string()]);}#[test]fn completed_mcp_tool_call_success_snapshot() {    let invocation = McpInvocation {        server: "search".into(),        tool: "find_docs".into(),        arguments: Some(json!({            "query": "ratatui styling",            "limit": 3,        })),    };    let result = CallToolResult {        content: vec![text_block("Found styling guidance in styles.md")],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-2".into(),        invocation,        /*animations_enabled*/ true,    );    assert!(        cell.complete(Duration::from_millis(1420), Ok(result))            .is_none()    );    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn completed_mcp_tool_call_image_after_text_returns_extra_cell() {    let invocation = McpInvocation {        server: "image".into(),        tool: "generate".into(),        arguments: Some(json!({            "prompt": "tiny image",        })),    };    let result = CallToolResult {        content: vec![            text_block("Here is the image:"),            image_block(SMALL_PNG_BASE64),        ],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-image".into(),        invocation,        /*animations_enabled*/ true,    );    let extra_cell = cell        .complete(Duration::from_millis(25), Ok(result))        .expect("expected image cell");    let rendered = render_lines(&extra_cell.display_lines(/*width*/ 80));    assert_eq!(rendered, vec!["tool result (image output)"]);}#[test]fn completed_mcp_tool_call_accepts_data_url_image_blocks() {    let invocation = McpInvocation {        server: "image".into(),        tool: "generate".into(),        arguments: Some(json!({            "prompt": "tiny image",        })),    };    let data_url = format!("data:image/png;base64,{SMALL_PNG_BASE64}");    let result = CallToolResult {        content: vec![image_block(&data_url)],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-image-data-url".into(),        invocation,        /*animations_enabled*/ true,    );    let extra_cell = cell        .complete(Duration::from_millis(25), Ok(result))        .expect("expected image cell");    let rendered = render_lines(&extra_cell.display_lines(/*width*/ 80));    assert_eq!(rendered, vec!["tool result (image output)"]);}#[test]fn completed_mcp_tool_call_skips_invalid_image_blocks() {    let invocation = McpInvocation {        server: "image".into(),        tool: "generate".into(),        arguments: Some(json!({            "prompt": "tiny image",        })),    };    let result = CallToolResult {        content: vec![image_block("not-base64"), image_block(SMALL_PNG_BASE64)],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-image-2".into(),        invocation,        /*animations_enabled*/ true,    );    let extra_cell = cell        .complete(Duration::from_millis(25), Ok(result))        .expect("expected image cell");    let rendered = render_lines(&extra_cell.display_lines(/*width*/ 80));    assert_eq!(rendered, vec!["tool result (image output)"]);}#[test]fn completed_mcp_tool_call_error_snapshot() {    let invocation = McpInvocation {        server: "search".into(),        tool: "find_docs".into(),        arguments: Some(json!({            "query": "ratatui styling",            "limit": 3,        })),    };    let mut cell = new_active_mcp_tool_call(        "call-3".into(),        invocation,        /*animations_enabled*/ true,    );    assert!(        cell.complete(Duration::from_secs(2), Err("network timeout".into()))            .is_none()    );    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn completed_mcp_tool_call_multiple_outputs_snapshot() {    let invocation = McpInvocation {        server: "search".into(),        tool: "find_docs".into(),        arguments: Some(json!({            "query": "ratatui styling",            "limit": 3,        })),    };    let result = CallToolResult {        content: vec![            text_block(                "Found styling guidance in styles.md and additional notes in CONTRIBUTING.md.",            ),            resource_link_block(                "file:///docs/styles.md",                "styles.md",                Some("Styles"),                Some("Link to styles documentation"),            ),        ],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-4".into(),        invocation,        /*animations_enabled*/ true,    );    assert!(        cell.complete(Duration::from_millis(640), Ok(result))            .is_none()    );    let rendered = render_lines(&cell.display_lines(/*width*/ 48)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn completed_mcp_tool_call_wrapped_outputs_snapshot() {    let invocation = McpInvocation {        server: "metrics".into(),        tool: "get_nearby_metric".into(),        arguments: Some(json!({            "query": "very_long_query_that_needs_wrapping_to_display_properly_in_the_history",            "limit": 1,        })),    };    let result = CallToolResult {        content: vec![text_block(            "Line one of the response, which is quite long and needs wrapping.\nLine two continues the response with more detail.",        )],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-5".into(),        invocation,        /*animations_enabled*/ true,    );    assert!(        cell.complete(Duration::from_millis(1280), Ok(result))            .is_none()    );    let rendered = render_lines(&cell.display_lines(/*width*/ 40)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn completed_mcp_tool_call_multiple_outputs_inline_snapshot() {    let invocation = McpInvocation {        server: "metrics".into(),        tool: "summary".into(),        arguments: Some(json!({            "metric": "trace.latency",            "window": "15m",        })),    };    let result = CallToolResult {        content: vec![            text_block("Latency summary: p50=120ms, p95=480ms."),            text_block("No anomalies detected."),        ],        is_error: None,        structured_content: None,        meta: None,    };    let mut cell = new_active_mcp_tool_call(        "call-6".into(),        invocation,        /*animations_enabled*/ true,    );    assert!(        cell.complete(Duration::from_millis(320), Ok(result))            .is_none()    );    let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn session_header_includes_reasoning_level_when_present() {    let cell = SessionHeaderHistoryCell::new(        "gpt-4o".to_string(),        Some(ReasoningEffortConfig::High),        /*show_fast_status*/ true,        std::env::temp_dir(),        "test",    );    let lines = render_lines(&cell.display_lines(/*width*/ 80));    let model_line = lines        .iter()        .find(|line| line.contains("model:"))        .expect("model line");    assert!(model_line.contains("gpt-4o high   fast"));    assert!(model_line.contains("/model to change"));}#[test]fn session_header_hides_fast_status_when_disabled() {    let cell = SessionHeaderHistoryCell::new(        "gpt-4o".to_string(),        Some(ReasoningEffortConfig::High),        /*show_fast_status*/ false,        std::env::temp_dir(),        "test",    );    let lines = render_lines(&cell.display_lines(/*width*/ 80));    let model_line = lines        .iter()        .find(|line| line.contains("model:"))        .expect("model line");    assert!(model_line.contains("gpt-4o high"));    assert!(!model_line.contains("fast"));}#[test]#[cfg_attr(    target_os = "windows",    ignore = "snapshot path rendering differs on Windows")]fn session_header_indicates_yolo_mode() {    let cell = SessionHeaderHistoryCell::new(        "gpt-5".to_string(),        /*reasoning_effort*/ None,        /*show_fast_status*/ false,        test_path_buf("/tmp/project").abs().to_path_buf(),        "test",    )    .with_yolo_mode(/*yolo_mode*/ true);    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn yolo_mode_includes_managed_full_access_profiles() {    let permission_profile: PermissionProfile = PermissionProfile::Managed {        network: NetworkSandboxPolicy::Enabled,        file_system: ManagedFileSystemPermissions::Unrestricted,    };    assert!(has_yolo_permissions(        AskForApproval::Never,        &permission_profile    ));}#[test]fn yolo_mode_excludes_external_sandbox_profiles() {    let permission_profile: PermissionProfile = PermissionProfile::External {        network: NetworkSandboxPolicy::Enabled,    };    assert!(!has_yolo_permissions(        AskForApproval::Never,        &permission_profile    ));}#[test]fn session_header_directory_center_truncates() {    let mut dir = home_dir().expect("home directory");    for part in ["hello", "the", "fox", "is", "very", "fast"] {        dir.push(part);    }    let formatted = SessionHeaderHistoryCell::format_directory_inner(&dir, Some(24));    let sep = std::path::MAIN_SEPARATOR;    let expected = format!("~{sep}hello{sep}the{sep}…{sep}very{sep}fast");    assert_eq!(formatted, expected);}#[test]fn session_header_directory_front_truncates_long_segment() {    let mut dir = home_dir().expect("home directory");    dir.push("supercalifragilisticexpialidocious");    let formatted = SessionHeaderHistoryCell::format_directory_inner(&dir, Some(18));    let sep = std::path::MAIN_SEPARATOR;    let expected = format!("~{sep}…cexpialidocious");    assert_eq!(formatted, expected);}#[test]fn coalesces_sequential_reads_within_one_call() {    // Build one exec cell with a Search followed by two Reads    let call_id = "c1".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), "echo".into()],            parsed: vec![                ParsedCommand::Search {                    query: Some("shimmer_spans".into()),                    path: None,                    cmd: "rg shimmer_spans".into(),                },                ParsedCommand::Read {                    name: "shimmer.rs".into(),                    cmd: "cat shimmer.rs".into(),                    path: "shimmer.rs".into(),                },                ParsedCommand::Read {                    name: "status_indicator_widget.rs".into(),                    cmd: "cat status_indicator_widget.rs".into(),                    path: "status_indicator_widget.rs".into(),                },            ],            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    // Mark call complete so markers are ✓    cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));    let lines = cell.display_lines(/*width*/ 80);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn coalesces_reads_across_multiple_calls() {    let mut cell = ExecCell::new(        ExecCall {            call_id: "c1".to_string(),            command: vec!["bash".into(), "-lc".into(), "echo".into()],            parsed: vec![ParsedCommand::Search {                query: Some("shimmer_spans".into()),                path: None,                cmd: "rg shimmer_spans".into(),            }],            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    // Call 1: Search only    cell.complete_call("c1", CommandOutput::default(), Duration::from_millis(1));    // Call 2: Read A    cell = cell        .with_added_call(            "c2".into(),            vec!["bash".into(), "-lc".into(), "echo".into()],            vec![ParsedCommand::Read {                name: "shimmer.rs".into(),                cmd: "cat shimmer.rs".into(),                path: "shimmer.rs".into(),            }],            ExecCommandSource::Agent,            /*interaction_input*/ None,        )        .unwrap();    cell.complete_call("c2", CommandOutput::default(), Duration::from_millis(1));    // Call 3: Read B    cell = cell        .with_added_call(            "c3".into(),            vec!["bash".into(), "-lc".into(), "echo".into()],            vec![ParsedCommand::Read {                name: "status_indicator_widget.rs".into(),                cmd: "cat status_indicator_widget.rs".into(),                path: "status_indicator_widget.rs".into(),            }],            ExecCommandSource::Agent,            /*interaction_input*/ None,        )        .unwrap();    cell.complete_call("c3", CommandOutput::default(), Duration::from_millis(1));    let lines = cell.display_lines(/*width*/ 80);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn coalesced_reads_dedupe_names() {    let mut cell = ExecCell::new(        ExecCall {            call_id: "c1".to_string(),            command: vec!["bash".into(), "-lc".into(), "echo".into()],            parsed: vec![                ParsedCommand::Read {                    name: "auth.rs".into(),                    cmd: "cat auth.rs".into(),                    path: "auth.rs".into(),                },                ParsedCommand::Read {                    name: "auth.rs".into(),                    cmd: "cat auth.rs".into(),                    path: "auth.rs".into(),                },                ParsedCommand::Read {                    name: "shimmer.rs".into(),                    cmd: "cat shimmer.rs".into(),                    path: "shimmer.rs".into(),                },            ],            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    cell.complete_call("c1", CommandOutput::default(), Duration::from_millis(1));    let lines = cell.display_lines(/*width*/ 80);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn multiline_command_wraps_with_extra_indent_on_subsequent_lines() {    // Create a completed exec cell with a multiline command    let cmd = "set -o pipefail\ncargo test -p codex-tui --quiet".to_string();    let call_id = "c1".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), cmd],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    // Mark call complete so it renders as "Ran"    cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));    // Small width to keep the wrapped continuation-indent path covered.    let width: u16 = 28;    let lines = cell.display_lines(width);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn single_line_command_compact_when_fits() {    let call_id = "c1".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["echo".into(), "ok".into()],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));    // Wide enough that it fits inline    let lines = cell.display_lines(/*width*/ 80);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn single_line_command_wraps_with_four_space_continuation() {    let call_id = "c1".to_string();    let long = "a_very_long_token_without_spaces_to_force_wrapping".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), long],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));    let lines = cell.display_lines(/*width*/ 24);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn multiline_command_without_wrap_uses_branch_then_eight_spaces() {    let call_id = "c1".to_string();    let cmd = "echo one\necho two".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), cmd],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));    let lines = cell.display_lines(/*width*/ 80);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn multiline_command_both_lines_wrap_with_correct_prefixes() {    let call_id = "c1".to_string();    let cmd =        "first_token_is_long_enough_to_wrap\nsecond_token_is_also_long_enough_to_wrap".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), cmd],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));    let lines = cell.display_lines(/*width*/ 28);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn stderr_tail_more_than_five_lines_snapshot() {    // Build an exec cell with a non-zero exit and 10 lines on stderr to exercise    // the head/tail rendering and gutter prefixes.    let call_id = "c_err".to_string();    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), "seq 1 10 1>&2 && false".into()],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    let stderr: String = (1..=10)        .map(|n| n.to_string())        .collect::<Vec<_>>()        .join("\n");    cell.complete_call(        &call_id,        CommandOutput {            exit_code: 1,            formatted_output: String::new(),            aggregated_output: stderr,        },        Duration::from_millis(1),    );    let rendered = cell        .display_lines(/*width*/ 80)        .iter()        .map(|l| {            l.spans                .iter()                .map(|s| s.content.as_ref())                .collect::<String>()        })        .collect::<Vec<_>>()        .join("\n");    insta::assert_snapshot!(rendered);}#[test]fn ran_cell_multiline_with_stderr_snapshot() {    // Build an exec cell that completes (so it renders as "Ran") with a    // command long enough that it must render on its own line under the    // header, and include a couple of stderr lines to verify the output    // block prefixes and wrapping.    let call_id = "c_wrap_err".to_string();    let long_cmd =        "echo this_is_a_very_long_single_token_that_will_wrap_across_the_available_width";    let mut cell = ExecCell::new(        ExecCall {            call_id: call_id.clone(),            command: vec!["bash".into(), "-lc".into(), long_cmd.to_string()],            parsed: Vec::new(),            output: None,            source: ExecCommandSource::Agent,            start_time: Some(Instant::now()),            duration: None,            interaction_input: None,        },        /*animations_enabled*/ true,    );    let stderr = "error: first line on stderr\nerror: second line on stderr".to_string();    cell.complete_call(        &call_id,        CommandOutput {            exit_code: 1,            formatted_output: String::new(),            aggregated_output: stderr,        },        Duration::from_millis(5),    );    // Narrow width to force the command to render under the header line.    let width: u16 = 28;    let rendered = cell        .display_lines(width)        .iter()        .map(|l| {            l.spans                .iter()                .map(|s| s.content.as_ref())                .collect::<String>()        })        .collect::<Vec<_>>()        .join("\n");    insta::assert_snapshot!(rendered);}#[test]fn user_history_cell_wraps_and_prefixes_each_line_snapshot() {    let msg = "one two three four five six seven";    let cell = UserHistoryCell {        message: msg.to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: Vec::new(),    };    // Small width to force wrapping more clearly. Effective wrap width is width-2 due to the ▌ prefix and trailing space.    let width: u16 = 12;    let lines = cell.display_lines(width);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn user_history_cell_renders_remote_image_urls() {    let cell = UserHistoryCell {        message: "describe these".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: vec!["https://example.com/example.png".to_string()],    };    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    assert!(rendered.contains("[Image #1]"));    assert!(rendered.contains("describe these"));    insta::assert_snapshot!(rendered);}#[test]fn user_history_cell_summarizes_inline_data_urls() {    let cell = UserHistoryCell {        message: "describe inline image".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: vec!["data:image/png;base64,aGVsbG8=".to_string()],    };    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    assert!(rendered.contains("[Image #1]"));    assert!(rendered.contains("describe inline image"));}#[test]fn user_history_cell_numbers_multiple_remote_images() {    let cell = UserHistoryCell {        message: "describe both".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: vec![            "https://example.com/one.png".to_string(),            "https://example.com/two.png".to_string(),        ],    };    let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");    assert!(rendered.contains("[Image #1]"));    assert!(rendered.contains("[Image #2]"));    insta::assert_snapshot!(rendered);}#[test]fn user_history_cell_height_matches_rendered_lines_with_remote_images() {    let cell = UserHistoryCell {        message: "line one\nline two".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: vec![            "https://example.com/one.png".to_string(),            "https://example.com/two.png".to_string(),        ],    };    let width = 80;    let rendered_len: u16 = cell        .display_lines(width)        .len()        .try_into()        .unwrap_or(u16::MAX);    assert_eq!(cell.desired_height(width), rendered_len);    assert_eq!(cell.desired_transcript_height(width), rendered_len);}#[test]fn user_history_cell_trims_trailing_blank_message_lines() {    let cell = UserHistoryCell {        message: "line one\n\n   \n\t \n".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: vec!["https://example.com/one.png".to_string()],    };    let rendered = render_lines(&cell.display_lines(/*width*/ 80));    let trailing_blank_count = rendered        .iter()        .rev()        .take_while(|line| line.trim().is_empty())        .count();    assert_eq!(trailing_blank_count, 1);    assert!(rendered.iter().any(|line| line.contains("line one")));}#[test]fn user_history_cell_trims_trailing_blank_message_lines_with_text_elements() {    let message = "tokenized\n\n\n".to_string();    let cell = UserHistoryCell {        message,        text_elements: vec![TextElement::new(            (0..8).into(),            Some("tokenized".to_string()),        )],        local_image_paths: Vec::new(),        remote_image_urls: vec!["https://example.com/one.png".to_string()],    };    let rendered = render_lines(&cell.display_lines(/*width*/ 80));    let trailing_blank_count = rendered        .iter()        .rev()        .take_while(|line| line.trim().is_empty())        .count();    assert_eq!(trailing_blank_count, 1);    assert!(rendered.iter().any(|line| line.contains("tokenized")));}#[test]fn render_uses_wrapping_for_long_url_like_line() {    let url = "https://example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890/artifacts/reports/performance/summary/detail/with/a/very/long/path/that/keeps/going/for/testing/purposes-only-and-does/not/need/to/resolve/index.html?session_id=abc123def456ghi789jkl012mno345pqr678stu901vwx234yz";    let cell: Box<dyn HistoryCell> = Box::new(UserHistoryCell {        message: url.to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: Vec::new(),    });    let width: u16 = 52;    let height = cell.desired_height(width);    assert!(        height > 1,        "expected wrapped height for long URL, got {height}"    );    let area = Rect::new(0, 0, width, height);    let mut buf = ratatui::buffer::Buffer::empty(area);    cell.render(area, &mut buf);    let rendered = (0..area.height)        .map(|y| {            (0..area.width)                .map(|x| {                    let symbol = buf[(x, y)].symbol();                    if symbol.is_empty() {                        ' '                    } else {                        symbol.chars().next().unwrap_or(' ')                    }                })                .collect::<String>()        })        .collect::<Vec<_>>();    let rendered_blob = rendered.join("\n");    assert!(        rendered_blob.contains("session_id=abc123"),        "expected URL tail to be visible after wrapping, got:\n{rendered_blob}"    );    let non_empty_rows = rendered.iter().filter(|row| !row.trim().is_empty()).count() as u16;    assert!(        non_empty_rows > 3,        "expected long URL to span multiple visible rows, got:\n{rendered_blob}"    );}#[test]fn plan_update_with_note_and_wrapping_snapshot() {    // Long explanation forces wrapping; include long step text to verify step wrapping and alignment.    let update = UpdatePlanArgs {            explanation: Some(                "I’ll update Grafana call error handling by adding retries and clearer messages when the backend is unreachable."                    .to_string(),            ),            plan: vec![                PlanItemArg {                    step: "Investigate existing error paths and logging around HTTP timeouts".into(),                    status: StepStatus::Completed,                },                PlanItemArg {                    step: "Harden Grafana client error handling with retry/backoff and user‑friendly messages".into(),                    status: StepStatus::InProgress,                },                PlanItemArg {                    step: "Add tests for transient failure scenarios and surfacing to the UI".into(),                    status: StepStatus::Pending,                },            ],        };    let cell = new_plan_update(update);    // Narrow width to force wrapping for both the note and steps    let lines = cell.display_lines(/*width*/ 32);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn plan_update_without_note_snapshot() {    let update = UpdatePlanArgs {        explanation: None,        plan: vec![            PlanItemArg {                step: "Define error taxonomy".into(),                status: StepStatus::InProgress,            },            PlanItemArg {                step: "Implement mapping to user messages".into(),                status: StepStatus::Pending,            },        ],    };    let cell = new_plan_update(update);    let lines = cell.display_lines(/*width*/ 40);    let rendered = render_lines(&lines).join("\n");    insta::assert_snapshot!(rendered);}#[test]fn plan_update_does_not_split_url_like_tokens_in_note_or_step() {    let note_url = "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890";    let step_url = "example.test/api/v1/projects/beta-team/releases/2026-02-17/builds/0987654321/artifacts/reports/performance";    let update = UpdatePlanArgs {        explanation: Some(format!(            "Investigate failures under {note_url} immediately."        )),        plan: vec![PlanItemArg {            step: format!("Validate callbacks under {step_url} before rollout."),            status: StepStatus::InProgress,        }],    };    let cell = new_plan_update(update);    let rendered = render_lines(&cell.display_lines(/*width*/ 30));    assert_eq!(        rendered            .iter()            .filter(|line| line.contains(note_url))            .count(),        1,        "expected full note URL-like token in one rendered line, got: {rendered:?}"    );    assert_eq!(        rendered            .iter()            .filter(|line| line.contains(step_url))            .count(),        1,        "expected full step URL-like token in one rendered line, got: {rendered:?}"    );}#[test]fn reasoning_summary_block() {    let cell = new_reasoning_summary_block(        "**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),        &test_cwd(),    );    let rendered_display = render_lines(&cell.display_lines(/*width*/ 80));    assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);    let rendered_transcript = render_transcript(cell.as_ref());    assert_eq!(rendered_transcript, vec!["• Detailed reasoning goes here."]);}#[test]fn reasoning_summary_height_matches_wrapped_rendering_for_url_like_content() {    let summary = "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890/artifacts/reports/performance/summary/detail/with/a/very/long/path/that/keeps/going";    let cell: Box<dyn HistoryCell> = Box::new(ReasoningSummaryCell::new(        "High level reasoning".to_string(),        summary.to_string(),        &test_cwd(),        /*transcript_only*/ false,    ));    let width: u16 = 24;    let logical_height = cell.display_lines(width).len() as u16;    let wrapped_height = cell.desired_height(width);    let expected_wrapped_height = Paragraph::new(Text::from(cell.display_lines(width)))        .wrap(Wrap { trim: false })        .line_count(width) as u16;    assert_eq!(wrapped_height, expected_wrapped_height);    assert!(        wrapped_height >= logical_height,        "expected wrapped height to be at least logical line count ({logical_height}), got {wrapped_height}"    );    let wrapped_transcript_height = cell.desired_transcript_height(width);    assert_eq!(wrapped_transcript_height, wrapped_height);    let area = Rect::new(0, 0, width, wrapped_height);    let mut buf = ratatui::buffer::Buffer::empty(area);    cell.render(area, &mut buf);    let first_row = (0..area.width)        .map(|x| {            let symbol = buf[(x, 0)].symbol();            if symbol.is_empty() {                ' '            } else {                symbol.chars().next().unwrap_or(' ')            }        })        .collect::<String>();    assert!(        first_row.contains("•"),        "expected first rendered row to keep summary bullet visible, got: {first_row:?}"    );}#[test]fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() {    let cell =        new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &test_cwd());    let rendered = render_transcript(cell.as_ref());    assert_eq!(rendered, vec!["• Detailed reasoning goes here."]);}#[tokio::test]async fn reasoning_summary_block_respects_config_overrides() {    let mut config = test_config().await;    config.model = Some("gpt-3.5-turbo".to_string());    config.model_supports_reasoning_summaries = Some(true);    let cell = new_reasoning_summary_block(        "**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),        &test_cwd(),    );    let rendered_display = render_lines(&cell.display_lines(/*width*/ 80));    assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);}#[test]fn reasoning_summary_block_falls_back_when_header_is_missing() {    let cell = new_reasoning_summary_block(        "**High level reasoning without closing".to_string(),        &test_cwd(),    );    let rendered = render_transcript(cell.as_ref());    assert_eq!(rendered, vec!["• **High level reasoning without closing"]);}#[test]fn reasoning_summary_block_falls_back_when_summary_is_missing() {    let cell = new_reasoning_summary_block(        "**High level reasoning without closing**".to_string(),        &test_cwd(),    );    let rendered = render_transcript(cell.as_ref());    assert_eq!(rendered, vec!["• High level reasoning without closing"]);    let cell = new_reasoning_summary_block(        "**High level reasoning without closing**\n\n  ".to_string(),        &test_cwd(),    );    let rendered = render_transcript(cell.as_ref());    assert_eq!(rendered, vec!["• High level reasoning without closing"]);}#[test]fn reasoning_summary_block_splits_header_and_summary_when_present() {    let cell = new_reasoning_summary_block(        "**High level plan**\n\nWe should fix the bug next.".to_string(),        &test_cwd(),    );    let rendered_display = render_lines(&cell.display_lines(/*width*/ 80));    assert_eq!(rendered_display, vec!["• We should fix the bug next."]);    let rendered_transcript = render_transcript(cell.as_ref());    assert_eq!(rendered_transcript, vec!["• We should fix the bug next."]);}#[test]fn deprecation_notice_renders_summary_with_details() {    let cell = new_deprecation_notice(        "Feature flag `foo`".to_string(),        Some("Use flag `bar` instead.".to_string()),    );    let lines = cell.display_lines(/*width*/ 80);    let rendered = render_lines(&lines);    assert_eq!(        rendered,        vec![            "⚠ Feature flag `foo`".to_string(),            "Use flag `bar` instead.".to_string(),        ]    );}#[test]fn agent_markdown_cell_renders_source_at_different_widths() {    let source =        "A long agent message that should wrap differently when the terminal width changes.\n";    let cell = AgentMarkdownCell::new(source.to_string(), &test_cwd());    let lines_80 = render_lines(&cell.display_lines(/*width*/ 80));    assert!(        lines_80.first().is_some_and(|line| line.starts_with("• ")),        "first line should start with bullet prefix: {:?}",        lines_80[0]    );    let lines_32 = render_lines(&cell.display_lines(/*width*/ 32));    assert!(        lines_32.len() > lines_80.len(),        "narrower width should produce more wrapped lines: {lines_32:?}",    );}#[test]fn agent_markdown_cell_does_not_split_words_after_inline_markdown() {    let source = "This paragraph is intentionally long so you can inspect soft wrapping behavior while also checking inline formatting like **bold text**, *italic text*, ***bold italic text***, `inline code`, ~~strikethrough~~, a [link to example.com](https://example.com), and a literal path like [README.md](/Users/felipe.coury/code/codex.fcoury-worktrees/README.md) without introducing manual line breaks.\n";    let cell = AgentMarkdownCell::new(source.to_string(), &test_cwd());    let lines = render_lines(&cell.display_lines(/*width*/ 190));    assert!(        lines[0].ends_with("inline code,"),        "expected wrapping to stop before 'strikethrough': {lines:?}",    );    assert!(        lines[1].starts_with("  strikethrough,"),        "expected the next line to resume with the full word: {lines:?}",    );}#[test]fn streamed_agent_list_paragraph_preserves_item_indent_when_wrapped() {    let cell = AgentMessageCell::new(        vec![            Line::from("1. Correctness issue: server tool-search completions are rejected."),            Line::default(),            Line::from(                "   In next_prompt_suggestion.rs, ToolSearchCall records its call id, but a paired output is ignored and suppresses suggestions.",            ),        ],        /*is_first_line*/ true,    );    let lines = render_lines(&cell.display_lines(/*width*/ 64));    assert!(        lines            .iter()            .filter(|line| line.contains("paired output") || line.contains("suggestions."))            .all(|line| line.starts_with("     ")),        "expected all wrapped paragraph rows to retain the assistant gutter and list indent: {lines:?}",    );    insta::assert_snapshot!(lines.join("\n"));}#[test]fn agent_markdown_cell_narrow_width_shows_prefix_only() {    let source = "narrow width coverage\n";    let cell = AgentMarkdownCell::new(source.to_string(), &test_cwd());    let lines = render_lines(&cell.display_lines(/*width*/ 2));    assert_eq!(lines, vec!["• ".to_string()]);}#[test]fn wrapped_and_prefixed_cells_handle_tiny_widths() {    let user_cell = UserHistoryCell {        message: "tiny width coverage for wrapped user history".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: Vec::new(),    };    let agent_message_cell = AgentMessageCell::new(        vec!["tiny width agent line".into()],        /*is_first_line*/ true,    );    let reasoning_cell = ReasoningSummaryCell::new(        "Plan".to_string(),        "Reasoning summary content for tiny widths.".to_string(),        &test_cwd(),        /*transcript_only*/ false,    );    let agent_markdown_cell =        AgentMarkdownCell::new("tiny width agent markdown line\n".to_string(), &test_cwd());    for width in 1..=4 {        assert!(            !user_cell.display_lines(width).is_empty(),            "user cell should render at width {width}",        );        assert!(            !agent_message_cell.display_lines(width).is_empty(),            "agent message cell should render at width {width}",        );        assert!(            !reasoning_cell.display_lines(width).is_empty(),            "reasoning cell should render at width {width}",        );        assert!(            !agent_markdown_cell.display_lines(width).is_empty(),            "agent markdown cell should render at width {width}",        );    }}#[test]fn render_clears_area_when_cell_content_shrinks() {    let area = Rect::new(0, 0, 40, 6);    let mut buf = Buffer::empty(area);    let first: Box<dyn HistoryCell> = Box::new(PlainHistoryCell::new(vec![        Line::from("STALE ROW 1"),        Line::from("STALE ROW 2"),        Line::from("STALE ROW 3"),        Line::from("STALE ROW 4"),    ]));    first.render(area, &mut buf);    let second: Box<dyn HistoryCell> = Box::new(PlainHistoryCell::new(vec![Line::from("fresh")]));    second.render(area, &mut buf);    let mut rendered_rows: Vec<String> = Vec::new();    for y in 0..area.height {        let mut row = String::new();        for x in 0..area.width {            row.push_str(buf.cell((x, y)).expect("cell should exist").symbol());        }        rendered_rows.push(row);    }    assert!(        rendered_rows.iter().all(|row| !row.contains("STALE")),        "rendered buffer should not retain stale glyphs: {rendered_rows:?}",    );    assert!(        rendered_rows            .first()            .is_some_and(|row| row.contains("fresh")),        "expected fresh content in first row: {rendered_rows:?}",    );}#[test]fn agent_markdown_cell_survives_insert_history_rewrap() {    let source = "\  Canary rollout remained at limited traffic longer than planned because p95  latency briefly regressed during cold-cache periods.  Regional expansion succeeded with stable error rates, though internal  analytics lagged temporarily.  ";    let cell = AgentMarkdownCell::new(source.to_string(), &test_cwd());    let width: u16 = 80;    let lines = cell.display_lines(width);    // Simulate what insert_history_lines does: word_wrap_lines with    // the terminal width and no indent.    let rewrapped = word_wrap_lines(&lines, width as usize);    let before = render_lines(&lines);    let after = render_lines(&rewrapped);    assert_eq!(        before, after,        "word_wrap_lines should not alter lines that already fit within width"    );}/// Simulate the consolidation backward-walk logic from `App::handle_event`/// to verify it correctly identifies and replaces `AgentMessageCell` runs.#[test]fn consolidation_walker_replaces_agent_message_cells() {    use std::sync::Arc;    // Build a transcript with: [UserCell, AgentMsg(head), AgentMsg(cont), AgentMsg(cont)]    let user = Arc::new(UserHistoryCell {        message: "hello".to_string(),        text_elements: Vec::new(),        local_image_paths: Vec::new(),        remote_image_urls: Vec::new(),    }) as Arc<dyn HistoryCell>;    let head = Arc::new(AgentMessageCell::new(        vec![Line::from("line 1")],        /*is_first_line*/ true,    )) as Arc<dyn HistoryCell>;    let cont1 = Arc::new(AgentMessageCell::new(        vec![Line::from("line 2")],        /*is_first_line*/ false,    )) as Arc<dyn HistoryCell>;    let cont2 = Arc::new(AgentMessageCell::new(        vec![Line::from("line 3")],        /*is_first_line*/ false,    )) as Arc<dyn HistoryCell>;    let mut transcript_cells: Vec<Arc<dyn HistoryCell>> = vec![user.clone(), head, cont1, cont2];    // Run the same consolidation logic as the handler.    let source = "line 1\nline 2\nline 3\n".to_string();    let end = transcript_cells.len();    let mut start = end;    while start > 0        && transcript_cells[start - 1].is_stream_continuation()        && transcript_cells[start - 1]            .as_any()            .is::<AgentMessageCell>()    {        start -= 1;    }    if start > 0        && transcript_cells[start - 1]            .as_any()            .is::<AgentMessageCell>()        && !transcript_cells[start - 1].is_stream_continuation()    {        start -= 1;    }    assert_eq!(        start, 1,        "should find all 3 agent cells starting at index 1"    );    assert_eq!(end, 4);    // Splice.    let consolidated: Arc<dyn HistoryCell> = Arc::new(AgentMarkdownCell::new(source, &test_cwd()));    transcript_cells.splice(start..end, std::iter::once(consolidated));    assert_eq!(transcript_cells.len(), 2, "should be [user, consolidated]");    // Verify first cell is still the user cell.    assert!(        transcript_cells[0].as_any().is::<UserHistoryCell>(),        "first cell should be UserHistoryCell"    );    // Verify second cell is AgentMarkdownCell.    assert!(        transcript_cells[1].as_any().is::<AgentMarkdownCell>(),        "second cell should be AgentMarkdownCell"    );}