fn draw_diff_overlay()

in codex-rs/cloud-tasks/src/ui.rs [312:467]


fn draw_diff_overlay(frame: &mut Frame, area: Rect, app: &mut App) {
    let inner = overlay_outer(area);
    if app.diff_overlay.is_none() {
        return;
    }
    let ov_can_apply = app
        .diff_overlay
        .as_ref()
        .map(super::app::DiffOverlay::current_can_apply)
        .unwrap_or(false);
    let is_error = app
        .diff_overlay
        .as_ref()
        .and_then(|o| o.sd.wrapped_lines().first().cloned())
        .map(|s| s.trim_start().starts_with("Task failed:"))
        .unwrap_or(false)
        && !ov_can_apply;
    let title = app
        .diff_overlay
        .as_ref()
        .map(|o| o.title.clone())
        .unwrap_or_default();

    // Title block
    let title_ref = title.as_str();
    let mut title_spans: Vec<ratatui::text::Span> = if is_error {
        vec![
            "Details ".magenta(),
            "[FAILED]".red().bold(),
            " ".into(),
            title_ref.magenta(),
        ]
    } else if ov_can_apply {
        vec!["Diff: ".magenta(), title_ref.magenta()]
    } else {
        vec!["Details: ".magenta(), title_ref.magenta()]
    };
    if let Some(p) = app
        .diff_overlay
        .as_ref()
        .and_then(|o| o.sd.percent_scrolled())
    {
        title_spans.push("  • ".dim());
        title_spans.push(format!("{p}%").dim());
    }
    frame.render_widget(Clear, inner);
    frame.render_widget(
        overlay_block().title(Line::from(title_spans)).clone(),
        inner,
    );

    // Content area and optional status bar
    let content_full = overlay_content(inner);
    let mut content_area = content_full;
    if let Some(ov) = app.diff_overlay.as_mut() {
        let has_text = ov.current_attempt().is_some_and(AttemptView::has_text);
        let has_diff = ov.current_attempt().is_some_and(AttemptView::has_diff) || ov.base_can_apply;
        if has_diff || has_text {
            let rows = Layout::default()
                .direction(Direction::Vertical)
                .constraints([Constraint::Length(1), Constraint::Min(1)])
                .split(content_full);
            // Status bar label
            let mut spans: Vec<ratatui::text::Span> = Vec::new();
            if has_diff && has_text {
                let prompt_lbl = if matches!(ov.current_view, crate::app::DetailView::Prompt) {
                    "[Prompt]".magenta().bold()
                } else {
                    "Prompt".dim()
                };
                let diff_lbl = if matches!(ov.current_view, crate::app::DetailView::Diff) {
                    "[Diff]".magenta().bold()
                } else {
                    "Diff".dim()
                };
                spans.extend(vec![
                    prompt_lbl,
                    "  ".into(),
                    diff_lbl,
                    "  ".into(),
                    "(← → to switch view)".dim(),
                ]);
            } else if has_text {
                spans.push("Conversation".magenta().bold());
            } else {
                spans.push("Diff".magenta().bold());
            }
            if let Some(total) = ov.expected_attempts().or({
                if ov.attempts.is_empty() {
                    None
                } else {
                    Some(ov.attempts.len())
                }
            }) && total > 1
            {
                spans.extend(vec![
                    "  ".into(),
                    format!("Attempt {}/{}", ov.selected_attempt + 1, total)
                        .bold()
                        .dim(),
                    "  ".into(),
                    "(Tab/Shift-Tab or [ ] to cycle attempts)".dim(),
                ]);
            }
            frame.render_widget(Paragraph::new(Line::from(spans)), rows[0]);
            ov.sd.set_width(rows[1].width);
            ov.sd.set_viewport(rows[1].height);
            content_area = rows[1];
        } else {
            ov.sd.set_width(content_full.width);
            ov.sd.set_viewport(content_full.height);
            content_area = content_full;
        }
    }

    // Styled content render
    // Choose styling by the active view, not just presence of a diff
    let is_diff_view = app
        .diff_overlay
        .as_ref()
        .map(|o| matches!(o.current_view, crate::app::DetailView::Diff))
        .unwrap_or(false);
    let styled_lines: Vec<Line<'static>> = if is_diff_view {
        let raw = app.diff_overlay.as_ref().map(|o| o.sd.wrapped_lines());
        raw.unwrap_or(&[])
            .iter()
            .map(|l| style_diff_line(l))
            .collect()
    } else {
        app.diff_overlay
            .as_ref()
            .map(|o| style_conversation_lines(&o.sd, o.current_attempt()))
            .unwrap_or_default()
    };
    let raw_empty = app
        .diff_overlay
        .as_ref()
        .map(|o| o.sd.wrapped_lines().is_empty())
        .unwrap_or(true);
    if app.details_inflight && raw_empty {
        draw_centered_spinner(
            frame,
            content_area,
            &mut app.spinner_start,
            "Loading details…",
        );
    } else {
        let scroll = app
            .diff_overlay
            .as_ref()
            .map(|o| o.sd.state.scroll)
            .unwrap_or(0);
        let content = Paragraph::new(Text::from(styled_lines)).scroll((scroll, 0));
        frame.render_widget(content, content_area);
    }
}