//! Frame rendering for [`AgentView`]: the `draw` entry point plus shortcut
//! hints and the subagent fullscreen view.
use super::{
    ActivePane, AgentPane, AgentView, AgentViewLayout, InlineMediaHitAreas, MODE_BANNER_FADE_TICKS,
    PromptMode, collect_citation_links, dropdown_items_width, render_dropdown_chrome,
    supports_osc22,
};
use crate::actions::{ActionId, ActionRegistry};
use crate::key;
use crate::render::SafeBuf;
use crate::render::line_utils::truncate_line;
use crate::scrollback::block::BlockContent;
use crate::scrollback::layout::HorizontalLayout;
use crate::scrollback::render::ScratchBuffer;
use crate::scrollback::text_selection::{
    ResolvedSelectionBoundaries, ResolvedSelectionModel, render_active_selection_overlay,
    render_block_drag_overlay, render_persistent_selection_overlay,
};
use crate::theme::Theme;
use crate::views::btw_overlay::BTW_OVERLAY_ENTRY_IDX;
use crate::views::modal;
use crate::views::plan_approval_view::PlanApprovalFocus;
use crate::views::prompt_widget::{PromptFlag, PromptInfo, PromptStyle};
use crate::views::question_view::QUESTION_VIEW_HPAD;
use crate::views::shortcuts_bar::{HintItem, PendingHint, ShortcutsBar};
use crate::views::{agent, turn_status};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Widget;
use std::collections::HashSet;
use std::time::Instant;
impl AgentView {
    pub(crate) fn update_scrollback_selection_state(
        &mut self,
        model: ResolvedSelectionModel,
        boundaries: ResolvedSelectionBoundaries,
    ) {
        self.last_scrollback_selection_model = model;
        self.last_scrollback_selection_boundaries = boundaries;
    }
    fn clear_scrollback_selection_state(&mut self) {
        self.update_scrollback_selection_state(Default::default(), Default::default());
    }
    /// Keep [`Self::timeline_hover_preview`] in sync with [`Self::timeline_hover`].
    /// Called when hover changes (mouse Moved or rail rebuild under a
    /// stationary pointer) so render can borrow the cached text.
    pub(crate) fn sync_timeline_hover_preview(&mut self) {
        match self.timeline_hover {
            Some(crate::views::timeline::TimelineHit::Tick(turn_idx)) => {
                if self
                    .timeline_hover_preview
                    .as_ref()
                    .is_some_and(|(idx, _)| *idx == turn_idx)
                {
                    return;
                }
                self.timeline_hover_preview = self
                    .scrollback
                    .turn_preview(turn_idx)
                    .map(|text| (turn_idx, text));
            }
            _ => self.timeline_hover_preview = None,
        }
    }
    /// Open the fullscreen subagent view for `child_sid`, replaying child
    /// `updates.jsonl` when scrollback only has the injected task prompt.
    pub(crate) fn open_subagent_fullscreen(&mut self, child_sid: String) {
        if self.subagent_views.contains_key(&child_sid) {
            crate::app::subagent::ensure_subagent_child_replayed(self, &child_sid);
            self.active_subagent = Some(child_sid);
        }
    }
    /// Shortcut hints for the plan-approval prompt/comment focus states.
    ///
    /// Shared by `draw` and the cheatsheet Current section. `Tab:plan` is
    /// omitted when there is nothing to open (empty approval and no line
    /// viewer) so the footer never advertises a dead key.
    fn plan_approval_shortcut_hints(
        &self,
        pav: &crate::views::plan_approval_view::PlanApprovalViewState,
    ) -> Vec<HintItem> {
        match pav.focus {
            PlanApprovalFocus::Commenting => {
                vec![
                    HintItem::new(key!(Enter), "save comment"),
                    HintItem::new(key!(Esc), "cancel"),
                ]
            }
            PlanApprovalFocus::Prompt => {
                let has_content = !pav.comments.is_empty() || !self.prompt.text().trim().is_empty();
                if has_content {
                    vec![
                        HintItem::new(key!(Enter), "request changes"),
                        HintItem::new(key!(Tab), "plan"),
                        HintItem::new(key!(Esc), "back"),
                    ]
                } else {
                    vec![
                        HintItem::new(key!(Enter), "approve"),
                        HintItem::new(key!(Tab), "plan"),
                        HintItem::new(key!(Esc), "back"),
                    ]
                }
            }
            PlanApprovalFocus::Preview => vec![],
        }
    }
    /// Returns the *exact* hints the bottom shortcuts bar would render right now.
    ///
    /// Single source of truth for context-sensitive shortcuts (pane, overlays,
    /// sub-modes, selection state, turn running, plan/queue). Both the bar
    /// renderer and the Ctrl+. cheatsheet Current section delegate here, so
    /// they are guaranteed identical and every shortcut makes sense in the
    /// active context.
    ///
    /// Known transient: when a subagent is fullscreen (`active_subagent.is_some()`),
    /// draw returns early and the child renders its own bar; Current on the parent
    /// still reflects parent context (documented limitation, pre-existing before
    /// this change).
    pub fn current_shortcut_hints(&self, registry: &ActionRegistry) -> Vec<HintItem> {
        use crate::views::shortcuts_bar::HintItem;
        if let Some(ref viewer) = self.block_viewer {
            viewer.shortcuts_hints()
        } else if !self.permission_queue.is_empty() {
            use crate::views::permission_view::PermissionFocus;
            if let Some(perm) = self.permission_queue.front() {
                match perm.focus {
                    PermissionFocus::FollowupInput => {
                        vec![
                            HintItem::new(key!(Enter), "send"),
                            HintItem::new(key!(Esc), "back"),
                        ]
                    }
                    PermissionFocus::Options => {
                        use crate::input::key::KeyShortcut;
                        use crossterm::event::{KeyCode, KeyModifiers};
                        let n = perm.options.len().min(9) as u8;
                        let last_ch = char::from(b'0' + n.max(1));
                        let last_key = KeyShortcut::new(KeyCode::Char(last_ch), KeyModifiers::NONE);
                        let mut hints = vec![HintItem::paired(key!('1'), last_key, "select")];
                        if perm.has_adjustable_scope() {
                            hints.push(HintItem::paired(key!(Left), key!(Right), "scope"));
                        }
                        if !perm.description.is_empty() {
                            let label = if perm.args_expanded {
                                "collapse"
                            } else {
                                "expand"
                            };
                            hints.push(HintItem::new(key!('f', CONTROL), label));
                        }
                        hints.push(HintItem::new(key!('o', CONTROL), "always-approve"));
                        hints.push(HintItem::new(key!('c', CONTROL), "cancel"));
                        hints
                    }
                }
            } else {
                unreachable!("permission_queue non-empty per outer guard")
            }
        } else if let Some(ref pav) = self.plan_approval_view {
            self.plan_approval_shortcut_hints(pav)
        } else if self.line_viewer.is_some() && self.is_plan_viewer() {
            let suppress_shortcuts = self
                .line_viewer
                .as_ref()
                .is_some_and(|v| v.fullscreen && v.list_state.input_mode().is_some());
            if suppress_shortcuts {
                vec![]
            } else if self.is_casual_commenting() {
                vec![
                    HintItem::new(key!(Enter), "save comment"),
                    HintItem::new(key!(Esc), "cancel"),
                ]
            } else {
                let mut h = vec![
                    HintItem::new(key!('c'), "comment"),
                    HintItem::new(key!('f', CONTROL), "fullscreen"),
                ];
                if !self.plan_comments.is_empty() {
                    h.push(HintItem::new(key!('s'), "send"));
                }
                h.push(HintItem::new(key!(Esc), "close"));
                h
            }
        } else if let Some(ref qv) = self.question_view {
            use crate::views::question_view::QuestionFocus;
            match qv.focus {
                QuestionFocus::InputMode => {
                    if self.prompt.file_search_visible() {
                        vec![
                            HintItem::paired(key!(Up), key!(Down), "nav"),
                            HintItem::new(key!(Tab), "accept"),
                            HintItem::new(key!(Right), "drill"),
                            HintItem::new(key!(Esc), "dismiss"),
                        ]
                    } else {
                        vec![
                            HintItem::new(key!(Enter), "submit"),
                            HintItem::new(key!(Esc), "back"),
                        ]
                    }
                }
                QuestionFocus::Navigation => {
                    vec![
                        HintItem::new(key!(Esc), "unselect"),
                        HintItem::new(key!(Tab), "scrollback"),
                        HintItem::new(key!('X'), "dismiss"),
                    ]
                }
            }
        } else if self.cancel_turn_view.is_some() {
            vec![
                HintItem::paired(key!('1'), key!('4'), "select"),
                HintItem::new(key!(Enter), "confirm"),
                HintItem::new(key!(Esc), "keep running"),
                HintItem::new(key!(Tab), "scrollback"),
            ]
        } else {
            self.normal_pane_hints(registry)
        }
    }
    /// Shared "normal pane" hints: flag computation + `build_hints` + queue hint.
    /// Single source of truth for the two former duplicated blocks in
    /// `current_shortcut_hints` and `draw`.
    fn normal_pane_hints(&self, registry: &ActionRegistry) -> Vec<HintItem> {
        let fold_label = self.selected_fold_label();
        let is_editing = matches!(self.prompt_mode, PromptMode::EditingQueued { .. });
        let selected_entry = self
            .scrollback
            .selected()
            .and_then(|idx| self.scrollback.entry(idx));
        let selected_is_group_header = self
            .scrollback
            .selected()
            .is_some_and(|idx| self.scrollback.entry_content_hidden_by_group(idx));
        let (selected_supports_copy, selected_meta_label, selected_supports_fullscreen) =
            if self.active_pane == ActivePane::Catalog {
                (false, None, self.catalog.selected_entry().is_some())
            } else if self.active_pane == ActivePane::Tasks {
                let has_selected = self.tasks.selected_task_id().is_some_and(|tid| {
                    self.session
                        .bg_tasks
                        .get(tid)
                        .is_some_and(|t| !t.stdout.is_empty())
                });
                let has_any_selected = self.tasks.selected_task_id().is_some();
                (has_selected, None, has_any_selected)
            } else if self.active_pane == ActivePane::Scrollback {
                let is_bg_task = selected_entry.is_some_and(|e| {
                    matches!(e.block, crate::scrollback::block::RenderBlock::BgTask(_))
                });
                if is_bg_task {
                    let has_stdout = selected_entry
                        .and_then(|e| {
                            if let crate::scrollback::block::RenderBlock::BgTask(b) = &e.block {
                                Some(&b.task_id)
                            } else {
                                None
                            }
                        })
                        .is_some_and(|tid| {
                            self.session
                                .bg_tasks
                                .get(tid)
                                .is_some_and(|t| !t.stdout.is_empty())
                        });
                    (has_stdout, None, true)
                } else {
                    let is_viewable_subagent = selected_entry.is_some_and(|e| {
                        if let crate::scrollback::block::RenderBlock::Subagent(ref sb) = e.block {
                            self.subagent_views.contains_key(&sb.child_session_id)
                        } else {
                            false
                        }
                    });
                    (
                        !selected_is_group_header
                            && selected_entry.is_some_and(|e| e.block.supports_copy()),
                        selected_entry
                            .and_then(|e| e.block.copy_meta_label())
                            .filter(|_| !selected_is_group_header),
                        selected_entry.is_some_and(|e| e.block.supports_fullscreen())
                            || is_viewable_subagent,
                    )
                }
            } else {
                (
                    !selected_is_group_header
                        && selected_entry.is_some_and(|e| e.block.supports_copy()),
                    selected_entry
                        .and_then(|e| e.block.copy_meta_label())
                        .filter(|_| !selected_is_group_header),
                    selected_entry.is_some_and(|e| e.block.supports_fullscreen()),
                )
            };
        let can_demote = self
            .session
            .tracker
            .running_execute_tool_call_id()
            .is_some();
        let selected_can_kill = if self.active_pane == ActivePane::Catalog {
            false
        } else if self.active_pane == ActivePane::Tasks {
            self.tasks
                .selected_task_id()
                .and_then(|tid| self.session.bg_tasks.get(tid))
                .is_some_and(|t| {
                    t.status == crate::app::agent::BgTaskStatus::Running && !t.pending_kill
                })
        } else if self.active_pane == ActivePane::Scrollback {
            !selected_is_group_header
                && selected_entry
                    .and_then(|e| {
                        if let crate::scrollback::block::RenderBlock::BgTask(b) = &e.block {
                            Some(&b.task_id)
                        } else {
                            None
                        }
                    })
                    .and_then(|tid| self.session.bg_tasks.get(tid))
                    .is_some_and(|t| {
                        t.status == crate::app::agent::BgTaskStatus::Running && !t.pending_kill
                    })
        } else {
            false
        };
        let thinking_label = self.scrollback.thinking_fold_label();
        let selected_is_user_prompt = selected_entry.is_some_and(|e| e.block.is_user_prompt());
        let selected_is_agent_message = selected_entry.is_some_and(|e| e.block.is_agent_message());
        let mut hints = agent::build_hints(
            self.active_pane,
            &self.prompt,
            registry,
            is_editing,
            fold_label,
            self.scrollback.selected_group_header_fold_label(),
            thinking_label,
            if self.active_pane == ActivePane::Tasks {
                self.tasks.show_done()
            } else {
                self.todo.show_done()
            },
            selected_supports_copy,
            selected_meta_label,
            selected_supports_fullscreen,
            can_demote,
            selected_can_kill,
            self.multiline_mode,
            self.vim_mode,
            self.is_subagent_view,
            self.session.state.is_turn_running() && !self.renders_parked(),
            !self.visible_queue_is_empty(),
            selected_is_user_prompt,
            selected_is_agent_message,
            crate::terminal::terminal_context().shift_enter_unavailable(),
            self.scrollback_search.as_ref(),
        );
        if (self.queue.is_visible() || !self.visible_queue_is_empty())
            && self.active_pane != ActivePane::Queue
            && !matches!(self.prompt_mode, PromptMode::EditingQueued { .. })
            && let Some(def) = registry.find(ActionId::ToggleQueue)
        {
            hints.push(def.hint());
        }
        hints
    }
    /// Render the agent view into the given area.
    ///
    /// Thin orchestrator: computes layout, then calls shared widgets and
    /// agent-specific overlay helpers in sequence. Each component takes
    /// only the state it needs — no arg threading.
    ///
    /// Returns cursor position if the prompt is focused (for terminal cursor).
    /// Render a fullscreen subagent view — replaces the ENTIRE parent view.
    ///
    /// Draws:
    /// 1. Background fill
    /// 2. Title bar: icon + type + description + meta + badge + progress + [elapsed] + [✗]
    /// 3. Border frame (single-line box)
    /// 4. Child `AgentView::draw()` inside the frame
    #[allow(clippy::too_many_arguments)]
    fn draw_subagent_fullscreen(
        &mut self,
        child_sid: &str,
        area: Rect,
        buf: &mut Buffer,
        registry: &ActionRegistry,
        scratch: &mut ScratchBuffer,
        theme: &Theme,
        bundle_state: &crate::app::bundle::BundleState,
    ) -> (
        Option<(u16, u16)>,
        Option<crate::terminal::overlay::PostFlush>,
    ) {
        use crate::app::subagent::{format_context_badge, format_subagent_label};
        use ratatui::style::Modifier;
        use unicode_width::UnicodeWidthStr;
        let appearance = self.scrollback.appearance().clone();
        let layout_cfg = &appearance.scrollback.layout;
        let compact = appearance.prompt.compact;
        agent::fill_background(buf, area, layout_cfg, compact, theme);
        let padded = Rect {
            x: area.x + layout_cfg.eff_hpad_left(compact),
            y: area.y + layout_cfg.eff_outer_vpad(compact),
            width: area.width.saturating_sub(
                layout_cfg.eff_hpad_left(compact) + layout_cfg.eff_hpad_right(compact),
            ),
            height: area
                .height
                .saturating_sub(layout_cfg.eff_outer_vpad(compact) * 2),
        };
        if padded.width < 10 || padded.height < 5 {
            return (None, crate::terminal::overlay::clear().map(Into::into));
        }
        let border_color = theme.selection_border;
        let frame = match crate::views::picker::render_bordered_frame(
            buf,
            padded,
            border_color,
            theme.bg_base,
        ) {
            Some(f) => f,
            None => {
                return (None, crate::terminal::overlay::clear().map(Into::into));
            }
        };
        let title_y = frame.title_row.y;
        let _title_row = frame.title_row;
        let inner = frame.content;
        let _border_style = Style::default().fg(border_color);
        let info = self.subagent_sessions.get(child_sid);
        let raw_description = info.map(|s| s.description.as_ref()).unwrap_or("subagent");
        let is_running = info.is_some_and(|s| s.is_running());
        let elapsed = info
            .map(|s| crate::util::format_duration(s.display_elapsed()))
            .unwrap_or_default();
        let (type_label, description): (String, String) = match info {
            Some(s) => format_subagent_label(s),
            None => (String::new(), raw_description.to_string()),
        };
        let icon = if is_running {
            let spinner_frames = crate::glyphs::dot_spinner_frames();
            let tick = self.tasks.tick_count();
            let frame_idx = (tick / 4) as usize % spinner_frames.len();
            spinner_frames[frame_idx]
        } else if info.and_then(|s| s.status.as_deref()) == Some("completed") {
            crate::glyphs::check_mark()
        } else {
            crate::glyphs::ballot_x()
        };
        let icon_color = if is_running {
            theme.accent_running
        } else if info.and_then(|s| s.status.as_deref()) == Some("completed") {
            theme.accent_success
        } else {
            theme.accent_error
        };
        let label_color = if info.is_some_and(|s| s.pending_kill) {
            theme.accent_error
        } else if is_running {
            theme.accent_running
        } else if info.and_then(|s| s.status.as_deref()) == Some("completed") {
            theme.accent_success
        } else {
            theme.accent_error
        };
        let meta = info
            .and_then(|s| s.model.as_deref())
            .map(str::trim)
            .filter(|s| !s.is_empty())
            .unwrap_or("")
            .to_string();
        let badge = info.map(format_context_badge).unwrap_or("");
        let activity_label: Option<String> = if is_running {
            self.subagent_views.get(child_sid).and_then(|cv| {
                cv.resolve_turn_activity()
                    .map(|a| crate::app::subagent::format_activity_label(&a))
                    .or_else(|| cv.session.state.is_busy().then(|| "Waiting".to_string()))
            })
        } else {
            None
        };
        let title_x = padded.x + 1;
        buf.set_span_safe(
            title_x,
            title_y,
            &Span::styled(format!(" {icon}"), Style::default().fg(icon_color)),
            3,
        );
        let close_text = "[\u{2717}]";
        let close_width: u16 = close_text.width() as u16;
        let elapsed_text = elapsed.clone();
        let right_margin: u16 = 1;
        let badge_width = if badge.is_empty() {
            0
        } else {
            badge.width() as u16 + 1
        };
        let activity_width: u16 = activity_label
            .as_deref()
            .map(|s| s.width() as u16 + 3)
            .unwrap_or(0);
        let right_width = activity_width
            + elapsed_text.width() as u16
            + 1
            + close_width
            + right_margin
            + badge_width;
        let desc_start_x = title_x + 3;
        let avail = padded.width.saturating_sub(5 + right_width) as usize;
        let type_text = if type_label.is_empty() {
            String::new()
        } else if description.is_empty() {
            type_label.clone()
        } else {
            format!("{type_label} ")
        };
        let meta_text = if meta.is_empty() {
            String::new()
        } else {
            format!(" {meta}")
        };
        let overhead = type_text.width() + meta_text.width();
        let desc_max = avail.saturating_sub(overhead);
        let desc_display = crate::render::line_utils::truncate_str(&description, desc_max);
        if !type_text.is_empty() {
            buf.set_span_safe(
                desc_start_x,
                title_y,
                &Span::styled(&type_text, Style::default().fg(label_color)),
                type_text.width() as u16,
            );
        }
        let desc_x = desc_start_x + type_text.width() as u16;
        buf.set_span_safe(
            desc_x,
            title_y,
            &Span::styled(
                &desc_display,
                Style::default()
                    .fg(theme.text_primary)
                    .add_modifier(Modifier::BOLD),
            ),
            desc_display.width() as u16,
        );
        let after_desc_x = desc_x + desc_display.width() as u16;
        if !meta_text.is_empty() {
            buf.set_span_safe(
                after_desc_x,
                title_y,
                &Span::styled(&meta_text, Style::default().fg(theme.gray)),
                meta_text.width() as u16,
            );
        }
        let mut rx = padded.x + padded.width.saturating_sub(right_margin + close_width + 1);
        let close_style = if self.hit_subagent_frame_close.hovered {
            Style::default()
                .fg(theme.text_primary)
                .add_modifier(Modifier::BOLD)
        } else {
            Style::default().fg(theme.gray)
        };
        buf.set_span_safe(
            rx,
            title_y,
            &Span::styled(close_text, close_style),
            close_width,
        );
        self.hit_subagent_frame_close.rect = Some(Rect::new(rx, title_y, close_width, 1));
        rx = rx.saturating_sub(elapsed_text.width() as u16 + 1);
        buf.set_span_safe(
            rx,
            title_y,
            &Span::styled(&elapsed_text, Style::default().fg(theme.gray)),
            elapsed_text.width() as u16,
        );
        if let Some(activity) = activity_label.as_deref() {
            let segment = format!("{activity} \u{00b7} ");
            let w = segment.width() as u16;
            rx = rx.saturating_sub(w);
            buf.set_span_safe(
                rx,
                title_y,
                &Span::styled(segment, Style::default().fg(theme.gray)),
                w,
            );
        }
        if !badge.is_empty() {
            rx = rx.saturating_sub(badge.width() as u16 + 1);
            buf.set_span_safe(
                rx,
                title_y,
                &Span::styled(badge, Style::default().fg(theme.gray_dim)),
                badge.width() as u16,
            );
        }
        let mut child_post_flush = None;
        if inner.width > 5
            && inner.height > 3
            && let Some(child_view) = self.subagent_views.get_mut(child_sid)
        {
            let (_, post_flush) = child_view.draw(
                inner,
                buf,
                registry,
                scratch,
                None,
                false,
                0,
                None,
                bundle_state,
                false,
                &mut Vec::new(),
            );
            child_post_flush = post_flush;
        }
        (None, child_post_flush)
    }
    pub fn should_show_tip(&mut self) -> bool {
        false
    }
    /// `area` is the screen region assigned to this agent view.
    /// When a tracing overlay is visible, this is smaller than `f.area()`.
    ///
    #[allow(clippy::too_many_arguments)]
    /// Render the agent into `area`.
    ///
    /// `in_dashboard_overlay` is `true` when this view is being
    /// rendered inside the dashboard's session-overlay; it appends
    /// `Ctrl+\\:dashboard` and `Ctrl+[/]:agents` hints to the bottom shortcuts
    /// bar so the user can discover the keyboard back-out and
    /// agent-navigation shortcuts from inside the agent view itself
    /// (not just from the overlay's top header).
    #[allow(clippy::too_many_arguments)]
    pub fn draw(
        &mut self,
        area: Rect,
        buf: &mut Buffer,
        registry: &ActionRegistry,
        scratch: &mut ScratchBuffer,
        pending_hint: Option<PendingHint>,
        overlay_focused: bool,
        banner_height: u16,
        tip: Option<&str>,
        bundle_state: &crate::app::bundle::BundleState,
        in_dashboard_overlay: bool,
        link_spans_out: &mut Vec<kigi_ratatui_inline::LinkSpan>,
    ) -> (
        Option<(u16, u16)>,
        Option<crate::terminal::overlay::PostFlush>,
    ) {
        self.in_dashboard_overlay = in_dashboard_overlay;
        self.frame_occluder_rects.clear();
        self.clear_scrollback_selection_state();
        self.refresh_prompt_suggestion_gate();
        let theme = Theme::current();
        let link_active_style = Style::default()
            .fg(theme.link_fg)
            .add_modifier(ratatui::style::Modifier::UNDERLINED | ratatui::style::Modifier::BOLD);
        self.note_terminal_size((area.width, area.height));
        #[allow(unused_assignments)]
        let mut scrollback_inline_media = Vec::new();
        #[allow(unused_assignments)]
        let mut scrollback_diagram_affordances: Vec<
            crate::scrollback::render::DiagramAffordancePlacement,
        > = Vec::new();
        if self.inline_media_active
            && (self.image_viewer.is_some()
                || self.video_viewer.is_some()
                || self.gboom.is_some()
                || self.block_viewer.is_some()
                || self.extensions_modal.is_some()
                || self.agents_modal.is_some()
                || self.btw_state.is_some()
                || self.line_viewer.is_some()
                || self.active_modal.is_some())
        {
            self.inline_media_active = false;
            kigi_shell::util::with_locked_stderr(|stderr| {
                for &id in self.inline_media_ids.values() {
                    let clear = crate::terminal::image::clear_kitty_image(id);
                    let _ = std::io::Write::write_all(stderr, clear.as_bytes());
                }
            });
            self.inline_media_ids.clear();
            self.inline_media_iterm_emitted.clear();
        }
        if let Some(ref child_sid) = self.active_subagent.clone() {
            if let Some(esc) = self.take_own_inline_media_clear_escapes() {
                kigi_shell::util::with_locked_stderr(|stderr| {
                    let _ = std::io::Write::write_all(stderr, esc.as_bytes());
                });
            }
            return self.draw_subagent_fullscreen(
                &child_sid.clone(),
                area,
                buf,
                registry,
                scratch,
                &theme,
                bundle_state,
            );
        }
        if let Some(esc) = self.take_subagent_inline_media_clear_escapes() {
            kigi_shell::util::with_locked_stderr(|stderr| {
                let _ = std::io::Write::write_all(stderr, esc.as_bytes());
            });
        }
        let appearance = self.scrollback.appearance().clone();
        let layout_cfg = &appearance.scrollback.layout;
        let scrollbar_cfg = &appearance.scrollback.scrollbar;
        let model_id = self
            .session
            .models
            .current_model_name()
            .unwrap_or_else(|| "unknown".to_string());
        let effective_plan = self.plan_mode_pending.unwrap_or(self.plan_mode_active);
        let casual_commenting = self.is_casual_commenting();
        let prompt_focused = if self.plan_approval_view.is_some() {
            self.plan_approval_view
                .as_ref()
                .is_some_and(|pav| pav.focus != PlanApprovalFocus::Preview)
        } else if casual_commenting {
            true
        } else {
            self.active_pane == AgentPane::Prompt && !overlay_focused
        };
        let prompt_style = PromptStyle {
            focused: prompt_focused,
            show_prefix: appearance.prompt.show_prefix,
            vpad_top: 1,
            compact: appearance.prompt.compact,
            chrome: true,
            chrome_pad_left: layout_cfg.block_pad_left,
            chrome_pad_right: layout_cfg.block_pad_right,
            bg_override: None,
            accent_color_override: if let Some(c) = self.prompt_input_mode.accent_color(&theme) {
                Some(c)
            } else if effective_plan || casual_commenting {
                Some(theme.accent_plan)
            } else {
                None
            },
            border_color_override: if effective_plan || casual_commenting {
                crate::render::color::blend_color(theme.bg_base, theme.accent_plan, 0.4)
            } else {
                None
            },
            prefix_override: if let Some(p) = self.prompt_input_mode.prefix_override(&theme) {
                Some(p)
            } else if casual_commenting
                || self
                    .plan_approval_view
                    .as_ref()
                    .is_some_and(|pav| pav.focus == PlanApprovalFocus::Commenting)
            {
                Some((
                    if crate::glyphs::is_legacy_windows_console() {
                        "\u{2022} "
                    } else {
                        "\u{25CF} "
                    },
                    theme.accent_plan,
                ))
            } else {
                None
            },
            placeholder_override: if let Some(ph) = self
                .prompt_input_mode
                .placeholder_override(self.multiline_mode)
            {
                Some(ph)
            } else if casual_commenting
                || self
                    .plan_approval_view
                    .as_ref()
                    .is_some_and(|pav| pav.focus == PlanApprovalFocus::Commenting)
            {
                Some("Type your comment...")
            } else {
                None
            },
            show_accent_line: false,
            show_borders: true,
            title: self.display_name.clone(),
            image_preview: true,
        };
        let compact = appearance.prompt.compact;
        let inner_width = AgentViewLayout::inner_width(area, layout_cfg, compact);
        let banner_height = if banner_height > 0 {
            if let Some(tip_text) = tip {
                banner_height.max(crate::tips::render::tip_height(inner_width, tip_text))
            } else {
                banner_height
            }
        } else {
            banner_height
        };
        let tip_row_visible =
            self.ephemeral_tip_renderable(area.height) && self.ephemeral_tip.is_active();
        let banner_height = banner_height.max(u16::from(tip_row_visible));
        let max_prompt_height = area.height / 2;
        let base_prompt_height = if !prompt_focused && appearance.prompt.collapse_unfocused {
            self.prompt
                .desired_height(inner_width, &prompt_style, true, max_prompt_height)
                .min(prompt_style.vpad_top + 1 + prompt_style.info_block(true))
        } else {
            self.prompt
                .desired_height(inner_width, &prompt_style, true, max_prompt_height)
        };
        let overlay_content_w = inner_width.saturating_sub(QUESTION_VIEW_HPAD) as usize;
        let permission_view_h = if let Some(perm) = self.permission_queue.front() {
            crate::views::permission_view::permission_view_height(
                perm,
                area.height,
                overlay_content_w,
            )
        } else {
            0
        };
        let question_view_h = if permission_view_h == 0 {
            if let Some(ref mut qv) = self.question_view {
                crate::views::question_view::question_view_height(
                    qv,
                    area.height,
                    overlay_content_w,
                )
            } else {
                0
            }
        } else {
            0
        };
        let rewind_view_h = if permission_view_h == 0 && question_view_h == 0 {
            if let Some(ref rw) = self.rewind_state {
                crate::views::rewind::rewind_overlay_height(&rw.phase, area.height)
            } else {
                0
            }
        } else {
            0
        };
        let cancel_turn_view_h =
            if permission_view_h == 0 && question_view_h == 0 && rewind_view_h == 0 {
                if self.cancel_turn_view.is_some() {
                    modal::cancel_turn_panel_height(area.height)
                } else {
                    0
                }
            } else {
                0
            };
        let jump_view_h = if !self.jump_slot_taken() {
            if let Some(ref js) = self.jump_state {
                crate::views::jump::jump_overlay_height(js, area.height)
            } else {
                0
            }
        } else {
            0
        };
        let is_question_input_mode = self
            .question_view
            .as_ref()
            .map(|qv| qv.focus == crate::views::question_view::QuestionFocus::InputMode)
            .unwrap_or(false);
        let question_input_style = PromptStyle {
            focused: true,
            show_prefix: true,
            vpad_top: 0,
            chrome: false,
            chrome_pad_left: 0,
            chrome_pad_right: 0,
            bg_override: Some(theme.bg_visual),
            accent_color_override: None,
            border_color_override: None,
            prefix_override: None,
            placeholder_override: None,
            compact: false,
            show_accent_line: false,
            show_borders: false,
            title: None,
            image_preview: true,
        };
        let inline_prompt_max = ((area.height as u32) / 3).clamp(3, 15) as u16;
        let question_prompt_body_h = if question_view_h > 0 && is_question_input_mode {
            let question_text_w = crate::views::question_view::inline_text_width(inner_width);
            self.prompt.desired_height(
                question_text_w,
                &question_input_style,
                false,
                inline_prompt_max,
            )
        } else {
            0
        };
        let is_permission_followup = self.permission_queue.front().is_some_and(|p| {
            p.focus == crate::views::permission_view::PermissionFocus::FollowupInput
        });
        let perm_inline_prompt_max = ((area.height as u32) / 3).clamp(3, 15) as u16;
        let perm_inline_prompt_h: u16 = if permission_view_h > 0 && is_permission_followup {
            let style = PromptStyle {
                focused: true,
                show_prefix: false,
                vpad_top: 0,
                chrome: false,
                chrome_pad_left: 0,
                chrome_pad_right: 0,
                bg_override: Some(theme.bg_visual),
                accent_color_override: None,
                border_color_override: None,
                prefix_override: None,
                placeholder_override: None,
                compact: false,
                show_accent_line: false,
                show_borders: false,
                title: None,
                image_preview: true,
            };
            let perm_text_w = crate::views::permission_view::inline_text_width(inner_width);
            self.prompt
                .desired_height(perm_text_w, &style, false, perm_inline_prompt_max)
                .max(1)
        } else {
            0
        };
        let question_footer_h: u16 = if question_view_h > 0 { 3 } else { 0 };
        let prompt_height = if permission_view_h > 0 {
            if is_permission_followup && perm_inline_prompt_h > 1 {
                permission_view_h + perm_inline_prompt_h.saturating_sub(1)
            } else {
                permission_view_h
            }
        } else if question_view_h > 0 {
            let freeform_offset = if is_question_input_mode { 1u16 } else { 0 };
            question_view_h.saturating_sub(freeform_offset)
                + question_prompt_body_h
                + question_footer_h
        } else if rewind_view_h > 0 {
            rewind_view_h
        } else if jump_view_h > 0 {
            jump_view_h
        } else if cancel_turn_view_h > 0 {
            cancel_turn_view_h
        } else {
            base_prompt_height
        };
        let prompt_height =
            prompt_height.max(prompt_style.vpad_top + 1 + prompt_style.info_block(true));
        let prompt_height = if self.is_subagent_view {
            0
        } else {
            prompt_height
        };
        let prompt_height = if question_view_h > 0 {
            let reserved = 1 + 5 + 1 + 3;
            prompt_height.min(area.height.saturating_sub(reserved))
        } else {
            prompt_height
        };
        {
            use crate::app::agent::PENDING_KILL_TIMEOUT_SECS;
            let now = Instant::now();
            for task in self.session.bg_tasks.values_mut() {
                if let Some(requested) = task.kill_requested_at
                    && now.duration_since(requested).as_secs() >= PENDING_KILL_TIMEOUT_SECS
                {
                    task.pending_kill = false;
                    task.kill_requested_at = None;
                }
            }
            for info in self.subagent_sessions.values_mut() {
                if let Some(requested) = info.kill_requested_at
                    && now.duration_since(requested).as_secs() >= PENDING_KILL_TIMEOUT_SECS
                {
                    info.pending_kill = false;
                    info.kill_requested_at = None;
                }
            }
        }
        let queued_cron_ids: HashSet<&str> = self
            .session
            .pending_prompts
            .iter()
            .filter(|p| p.kind == crate::app::agent::QueueEntryKind::Cron)
            .filter_map(|p| p.task_id.as_deref())
            .collect();
        self.tasks.sync(
            &self.session.bg_tasks,
            &self.subagent_sessions,
            &self.session.scheduled_tasks,
            self.cron_task_id.as_deref(),
            &queued_cron_ids,
        );
        if self.active_pane == ActivePane::Tasks && !self.tasks.is_visible() {
            self.active_pane = ActivePane::Scrollback;
        }
        self.catalog.sync_from_bundle(bundle_state);
        if self.active_pane == ActivePane::Catalog && !self.catalog.is_visible() {
            self.active_pane = ActivePane::Scrollback;
        }
        self.catalog.sync_from_bundle(bundle_state);
        if self.active_pane == ActivePane::Catalog && !self.catalog.is_visible() {
            self.active_pane = ActivePane::Scrollback;
        }
        let viewer_open = self.active_subagent.is_some();
        let tasks_height = if viewer_open {
            0
        } else {
            self.tasks.desired_height(area.height)
        };
        let catalog_height = if viewer_open {
            0
        } else {
            self.catalog.desired_height(area.height)
        };
        let todo_height = if viewer_open {
            0
        } else {
            self.todo.desired_height(area.height)
        };
        self.sync_queue_pane();
        if self.active_pane == ActivePane::Queue && !self.queue.is_visible() {
            self.active_pane = ActivePane::Scrollback;
        }
        let queue_height = self.queue.desired_height();
        let drain_blocked = self.drain_blocked();
        let watchers = turn_status::Watchers {
            monitors: self
                .session
                .bg_tasks
                .values()
                .filter(|t| t.is_monitor && t.status == crate::app::agent::BgTaskStatus::Running)
                .count(),
            loops: self.session.scheduled_tasks.len(),
            subagents: self
                .subagent_sessions
                .values()
                .filter(|s| s.is_running())
                .count(),
        };
        let parked = self.renders_parked();
        let turn_status_height = if !parked
            && turn_status::should_show(
                &self.session.state,
                drain_blocked,
                self.mcp_init_progress.as_ref(),
                watchers,
            ) {
            1
        } else {
            0
        };
        let prompt_gap = if appearance.prompt.compact
            || (turn_status_height > 0 && !appearance.turn_status.gap)
            || area.height <= agent::SHORT_TERMINAL_ROWS
        {
            0
        } else {
            1
        };
        let _tool_usage_height = 0u16;
        let btw_height =
            crate::views::btw_overlay::btw_panel_height(self.btw_state.as_ref(), inner_width);
        let follow_ups_height = u16::from(self.follow_ups.is_some());
        let timeline_width = crate::views::timeline::rail_width(
            appearance.show_timeline,
            self.is_subagent_view,
            area.width,
            self.scrollback.turn_count(),
        );
        let mut layout = AgentViewLayout::compute(
            area,
            layout_cfg,
            scrollbar_cfg,
            timeline_width,
            prompt_height,
            tasks_height,
            catalog_height,
            todo_height,
            queue_height,
            btw_height,
            turn_status_height,
            banner_height,
            follow_ups_height,
            0,
            prompt_gap,
            1,
            compact,
        );
        let search_active =
            self.scrollback_search.is_some() && self.active_pane == AgentPane::Scrollback;
        let search_reserved_rows =
            Self::search_reserved_rows(layout.scrollback.height, search_active);
        if search_reserved_rows > 0 {
            layout.scrollback.height -= search_reserved_rows;
            layout.scrollback_content.height = layout
                .scrollback_content
                .height
                .saturating_sub(search_reserved_rows);
        }
        let overlay_blocks_rail_hover = self.jump_state.is_some()
            || self.rewind_state.is_some()
            || self.question_view.is_some()
            || !self.permission_queue.is_empty()
            || self.cancel_turn_view.is_some()
            || self.block_viewer.is_some();
        if layout.timeline_width > 0 {
            self.sync_pending_user_input_marks();
            self.scrollback.set_cwd(Some(self.session.cwd.clone()));
            let _ = self.sync_inline_edit_layout(layout.scrollback_content.width);
            self.scrollback.prepare_layout(
                layout.scrollback_content.width,
                layout.scrollback_content.height,
            );
            let viewport = crate::views::timeline::RailViewport {
                active: self.scrollback.active_turn_for_viewport(),
                up_target: self.scrollback.turn_above_viewport_top(),
                down_target: self.scrollback.turn_below_viewport_top(),
                at_bottom: !self.scrollback.has_content_below(),
            };
            match crate::views::timeline::compute_rail(
                layout.scrollback,
                layout.timeline_x,
                self.scrollback.turn_count(),
                viewport,
            ) {
                Some(rail) => {
                    self.timeline_rail = Some(rail);
                    if overlay_blocks_rail_hover {
                        self.timeline_hover = None;
                        self.timeline_hover_preview = None;
                    } else {
                        let (col, row) = self.last_mouse_pos;
                        let new_hover = self.timeline_rail.as_ref().and_then(|r| r.hit(col, row));
                        if new_hover != self.timeline_hover {
                            self.timeline_hover = new_hover;
                            self.sync_timeline_hover_preview();
                        }
                    }
                }
                None => {
                    self.timeline_rail = None;
                    self.timeline_hover = None;
                    self.timeline_hover_preview = None;
                    layout = AgentViewLayout::compute(
                        area,
                        layout_cfg,
                        scrollbar_cfg,
                        0,
                        prompt_height,
                        tasks_height,
                        catalog_height,
                        todo_height,
                        queue_height,
                        btw_height,
                        turn_status_height,
                        banner_height,
                        follow_ups_height,
                        0,
                        prompt_gap,
                        1,
                        compact,
                    );
                    if search_reserved_rows > 0 {
                        layout.scrollback.height -= search_reserved_rows;
                        layout.scrollback_content.height = layout
                            .scrollback_content
                            .height
                            .saturating_sub(search_reserved_rows);
                    }
                    self.scrollback.prepare_layout(
                        layout.scrollback_content.width,
                        layout.scrollback_content.height,
                    );
                }
            }
        } else {
            self.timeline_rail = None;
            self.timeline_hover = None;
            self.timeline_hover_preview = None;
        }
        agent::fill_background(buf, area, layout_cfg, compact, &theme);
        use crate::views::agent_status::AgentStatusBar;
        use crate::views::context_bar;
        let mut status = AgentStatusBar::new(&theme);
        if let Some(url) = self.highlighted_link_url() {
            let max_len = layout.status_bar.width.saturating_sub(20) as usize;
            let display = if url.len() > max_len {
                let truncated: String = url.chars().take(max_len.saturating_sub(1)).collect();
                format!("{truncated}\u{2026}")
            } else {
                url.to_string()
            };
            let link_style = Style::default().fg(theme.link_fg).bg(theme.bg_base);
            status.push("link_url", Line::from(Span::styled(display, link_style)));
        }
        let running_count = self.tasks.running_count(
            &self.session.bg_tasks,
            &self.subagent_sessions,
            &self.session.scheduled_tasks,
        );
        if running_count > 0 {
            let spinner_frames = crate::glyphs::dot_spinner_frames();
            let frame_idx = (self.tasks.tick_count() / 4) as usize % spinner_frames.len();
            let frame = spinner_frames[frame_idx];
            let indicator = format!("{frame} {running_count}");
            let mut indicator_style = Style::default().fg(theme.accent_running).bg(theme.bg_base);
            if self.hit_bg_status.hovered {
                indicator_style = indicator_style.add_modifier(ratatui::style::Modifier::BOLD);
            }
            status.push(
                "bg_tasks",
                Line::from(Span::styled(indicator, indicator_style)),
            );
        }
        if self.should_show_plan_chip(&appearance) {
            let mut plan_style = Style::default().fg(theme.accent_plan).bg(theme.bg_base);
            if self.hit_plan_button.hovered {
                plan_style = plan_style.add_modifier(ratatui::style::Modifier::BOLD);
            }
            status.push("plan", Line::from(Span::styled("plan", plan_style)));
        }
        if let Some(ref goal) = self.goal_state {
            let tick = self.tasks.tick_count() as usize;
            let active_subagent_tokens: u64 = self
                .subagent_sessions
                .values()
                .filter(|s| !s.finished)
                .filter_map(|s| s.tokens_used)
                .sum();
            status.push(
                "goal",
                crate::views::agent_status::goal_status_line(
                    goal,
                    &theme,
                    self.hit_goal_status.hovered,
                    tick,
                    self.context_state.as_ref().map(|c| c.used),
                    active_subagent_tokens,
                ),
            );
        }
        if let Some(ref graph) = self.graph_state {
            let tick = self.tasks.tick_count() as usize;
            status.push(
                "graph",
                crate::views::agent_status::graph_status_line(graph, &theme, tick),
            );
        }
        if let Some(mcp_line) = self.mcp_init_progress.as_ref().and_then(|p| {
            crate::views::agent_status::mcp_status_line(p, self.scrollback.animation_tick(), &theme)
        }) {
            status.push("mcp", mcp_line);
        }
        let ctx_used = self.context_state.as_ref().map(|c| c.used);
        let model_window = self.session.models.get_context_window();
        let ctx_total = self
            .context_state
            .as_ref()
            .and_then(|c| (c.total > 0).then_some(c.total))
            .or(model_window);
        if let Some(ctx_line) = context_bar::context_bar_line_for_session(
            ctx_used,
            ctx_total,
            self.hit_context.hovered,
            &theme,
            self.chat_kind,
        ) {
            status.push("context", ctx_line);
        }
        let running = self.session.current_prompt_id.as_deref();
        let queue_len = self.session.queue_len()
            + self
                .shared_queue
                .iter()
                .filter(|e| Some(e.id.as_str()) != running)
                .count();
        if queue_len > 0 {
            use ratatui::style::Modifier;
            let mut queue_style = ratatui::style::Style::default()
                .fg(theme.accent_user)
                .bg(theme.bg_base);
            if self.hit_queue_badge.hovered {
                queue_style = queue_style.add_modifier(Modifier::BOLD);
            }
            status.push(
                "queue",
                Line::from(Span::styled(format!("+{queue_len}"), queue_style)),
            );
        }
        let counts = self.todo.counts();
        if let Some(badge_spans) = agent::render_todo_badge_spans(
            &counts,
            self.hit_badge.hovered,
            self.todo.badge_flash_active(),
            appearance.todo.badge_format,
            &theme,
        ) {
            status.push("badge", Line::from(badge_spans));
        }
        let areas = status.render(buf, layout.status_bar);
        self.hit_bg_status.rect = areas.get("bg_tasks").copied();
        self.hit_goal_status.rect = areas.get("goal").copied();
        self.hit_context.rect = areas.get("context").copied();
        self.hit_plan_button.rect = areas.get("plan").copied();
        self.hit_queue_badge.rect = areas.get("queue").copied();
        self.hit_badge.rect = areas.get("badge").copied();
        let home = std::env::var("HOME").ok();
        let display = self.session.cwd.display().to_string();
        let short = match &home {
            Some(h) if display.starts_with(h.as_str()) => {
                format!("~{}", &display[h.len()..])
            }
            _ => display,
        };
        let cwd_style = Style::default().fg(theme.gray_dim).bg(theme.bg_base);
        use unicode_width::UnicodeWidthStr;
        let mut parts: Vec<Span> = Vec::new();
        let mut path_offset: u16 = 0;
        let lazy_git = crate::git_info::cwd_git_info_lazy(&self.session.cwd);
        let branch = self
            .current_branch
            .clone()
            .or_else(|| lazy_git.as_ref().and_then(|i| i.branch.clone()));
        let git_text = branch.map(|b| {
            let icon = crate::git_info::branch_icon();
            if b.is_empty() {
                format!("{icon} detached")
            } else {
                format!("{icon} {b}")
            }
        });
        if let Some(git_text) = git_text {
            let git_style = Style::default()
                .fg(theme.text_primary)
                .bg(theme.bg_base)
                .add_modifier(ratatui::style::Modifier::DIM);
            path_offset += git_text.width() as u16;
            parts.push(Span::styled(git_text, git_style));
            path_offset += 1;
            parts.push(Span::styled(" ", Style::default().bg(theme.bg_base)));
        }
        let show_worktree_label = self.is_worktree
            || self.session.is_worktree
            || lazy_git.as_ref().is_some_and(|i| i.is_worktree);
        if show_worktree_label {
            let label_style = Style::default().fg(theme.accent_user).bg(theme.bg_base);
            path_offset += "worktree ".width() as u16;
            parts.push(Span::styled("worktree ", label_style));
        }
        if let Some(profile) = kigi_sandbox::profile_name() {
            let sandbox_text = format!("sandbox:{profile} ");
            let sandbox_style = Style::default().fg(theme.warning).bg(theme.bg_base);
            path_offset += sandbox_text.width() as u16;
            parts.push(Span::styled(sandbox_text, sandbox_style));
        }
        let path_width = short.width() as u16;
        let path_style = if self.hit_cwd.hovered {
            Style::default().fg(theme.text_primary).bg(theme.bg_base)
        } else {
            cwd_style
        };
        parts.push(Span::styled(short, path_style));
        let main_repo_display = self
            .main_repo
            .clone()
            .or_else(|| lazy_git.as_ref().and_then(|i| i.main_repo.clone()));
        if let Some(main_repo) = main_repo_display {
            parts.push(Span::styled(
                format!(" (worktree of {main_repo})"),
                cwd_style,
            ));
        }
        let cwd_line = Line::from(parts);
        let max_cwd_width = areas
            .values()
            .map(|r| r.x)
            .min()
            .map(|min_x| min_x.saturating_sub(layout.status_bar.x).saturating_sub(1))
            .unwrap_or(layout.status_bar.width);
        let cwd_line = truncate_line(cwd_line, max_cwd_width as usize);
        let cwd_width = cwd_line.width() as u16;
        buf.set_line_safe(
            layout.status_bar.x,
            layout.status_bar.y,
            &cwd_line,
            cwd_width,
        );
        let path_x = layout.status_bar.x + path_offset;
        let visible_path_width = path_width.min(cwd_width.saturating_sub(path_offset));
        self.hit_cwd.rect = (visible_path_width > 0).then_some(Rect {
            x: path_x,
            y: layout.status_bar.y,
            width: visible_path_width,
            height: 1,
        });
        let dropdown_open = self.prompt.any_dropdown_open();
        let mut inline_edit_cursor: Option<(u16, u16)> = None;
        {
            self.sync_pending_user_input_marks();
            self.scrollback.set_cwd(Some(self.session.cwd.clone()));
            let inline_edit_dim_from =
                self.sync_inline_edit_layout(layout.scrollback_content.width);
            self.scrollback.prepare_layout(
                layout.scrollback_content.width,
                layout.scrollback_content.height,
            );
            let rewind_dim_from = self.rewind_dim_from_entry().or(inline_edit_dim_from);
            let sb_focused = self.active_pane == ActivePane::Scrollback && !overlay_focused;
            let search_highlight = if search_active {
                self.scrollback_search
                    .as_ref()
                    .and_then(|s| s.highlight_regex())
            } else {
                None
            };
            self.ensure_media_link_paths();
            let sb_rendered = crate::scrollback::ScrollbackPane::new()
                .active(sb_focused)
                .with_mouse_pos(self.last_mouse_pos)
                .with_dim_from(rewind_dim_from)
                .with_hovered_entry(self.hovered_entry)
                .with_search_highlight(search_highlight)
                .with_media_paths(self.media_link_paths.clone())
                .render_with_scratch_and_selection_boundaries(
                    layout.scrollback_content,
                    buf,
                    &self.scrollback,
                    scratch,
                );
            let sb_output = sb_rendered.output;
            self.update_scrollback_selection_state(
                sb_output.selection_model.clone(),
                sb_rendered.selection_boundaries,
            );
            self.reclamp_drag_head_post_render(false);
            if self.inline_edit.is_some() {
                let cursor = self.render_inline_edit(buf, layout.scrollback_content);
                if self.rewind_state.is_none() {
                    inline_edit_cursor = cursor;
                }
            }
            if search_reserved_rows > 0
                && let Some(search) = self.scrollback_search.as_ref()
            {
                let reserved_top = layout.scrollback.y + layout.scrollback.height;
                let bar_y = reserved_top + (search_reserved_rows - 1);
                if search_reserved_rows >= 2 {
                    crate::views::picker::render_divider(
                        buf,
                        layout.scrollback.x,
                        reserved_top,
                        layout.scrollback.width,
                        &theme,
                        None,
                    );
                }
                let query = search.query();
                crate::views::picker::render_search_bar(
                    buf,
                    layout.scrollback.x,
                    bar_y,
                    layout.scrollback.width,
                    &theme,
                    query,
                    search.is_composing(),
                    !search.is_composing(),
                    query.len(),
                    None,
                );
                let counter = match search.current_index() {
                    Some(i) => Some(format!("{}/{}", i + 1, search.match_count())),
                    None if search.has_error() => Some("bad pattern".to_string()),
                    None if !query.is_empty() => Some("no matches".to_string()),
                    None => None,
                };
                if let Some(counter) = counter {
                    let w = counter.len() as u16;
                    if layout.scrollback.width > w {
                        buf.set_string(
                            layout.scrollback.x + layout.scrollback.width - w,
                            bar_y,
                            &counter,
                            Style::default().fg(theme.gray),
                        );
                    }
                }
            }
            self.last_link_overlay = sb_output.link_overlay;
            scrollback_inline_media = sb_output.inline_media;
            scrollback_diagram_affordances = sb_output.diagram_affordances;
            if self.visible_link_map.is_stale(self.scrollback.generation()) {
                let citation_links =
                    collect_citation_links(&self.scrollback, &sb_output.selection_model);
                self.visible_link_map.rebuild(
                    self.scrollback.generation(),
                    &self.last_link_overlay,
                    citation_links,
                );
                self.scrollback_visible_link_count = self.visible_link_map.len();
            } else {
                self.visible_link_map
                    .truncate(self.scrollback_visible_link_count);
            }
            let sb_link_n = self.scrollback_visible_link_count;
            self.paint_link_highlights(buf, link_active_style, 0..sb_link_n);
            agent::render_entry_hover(
                buf,
                layout.scrollback,
                &self.scrollback,
                self.hovered_entry,
                &theme,
            );
            if let Some(ref drag) = self.drag_selection
                && drag.anchor.entry_idx != BTW_OVERLAY_ENTRY_IDX
            {
                render_active_selection_overlay(
                    &self.last_scrollback_selection_model,
                    drag,
                    self.table_geometry_for_selection(drag.anchor.entry_idx, drag.anchor.range_id),
                    buf,
                );
            } else if let Some(ref block_drag) = self.block_drag_selection {
                render_block_drag_overlay(&self.last_scrollback_selection_model, block_drag, buf);
            } else if let Some(ref sel) = self.persistent_text_selection
                && sel.entry_idx != BTW_OVERLAY_ENTRY_IDX
            {
                render_persistent_selection_overlay(
                    &self.last_scrollback_selection_model,
                    sel,
                    self.table_geometry_for_selection(sel.entry_idx, sel.range_id),
                    buf,
                );
            }
            agent::render_hook_hover_popup(
                buf,
                layout.scrollback,
                &self.scrollback,
                self.hovered_entry,
                self.last_mouse_pos,
                &theme,
            );
            let any_drag_active =
                self.drag_selection.is_some() || self.block_drag_selection.is_some();
            if !any_drag_active
                && !overlay_focused
                && let Some(ref selection_box) = sb_output.selection_box
            {
                selection_box.render(buf);
                self.render_selection_buttons(
                    buf,
                    selection_box,
                    sb_output.selected_entry_area,
                    &theme,
                );
            } else {
                self.hit_sb_copy.clear();
                self.hit_sb_view.clear();
            }
            let rail_shown = self.timeline_rail.is_some();
            if !rail_shown {
                agent::render_scrollbar(
                    buf,
                    layout.scrollback,
                    layout.scrollbar_x,
                    scrollbar_cfg,
                    sb_output.scroll_info,
                    self.scrollback.is_follow_mode(),
                    &theme,
                );
            }
            if !rail_shown && scrollbar_cfg.enabled && sb_output.scroll_info.is_some() {
                self.hit_scrollbar.set(Some(Rect {
                    x: layout.scrollbar_x,
                    y: layout.scrollback.y,
                    width: 1,
                    height: layout.scrollback.height,
                }));
            } else {
                self.hit_scrollbar.clear();
            }
            if let Some(ref rail) = self.timeline_rail {
                crate::views::timeline::render_rail(buf, rail, self.timeline_hover, &theme);
                if let Some(crate::views::timeline::TimelineHit::Tick(turn_idx)) =
                    self.timeline_hover
                {
                    let preview = self
                        .timeline_hover_preview
                        .as_ref()
                        .filter(|(idx, _)| *idx == turn_idx)
                        .map(|(_, text)| text.as_str());
                    if let Some(preview) = preview {
                        crate::views::timeline::render_tick_hover_popup(
                            buf,
                            rail,
                            layout.scrollback,
                            turn_idx,
                            preview,
                            &theme,
                        );
                    }
                }
            }
        }
        if self.block_viewer.is_none() && !search_active {
            use crate::appearance::FollowIndicator;
            let gap_y = layout.scrollback.y + layout.scrollback.height;
            let gap_x = layout.scrollback.x;
            let gap_w = layout.scrollback.width;
            let mut content_line_y: Option<u16> = None;
            if appearance.scrollback.display.line_under_last_entry && !self.scrollback.is_empty() {
                let (scroll_offset, _, total_height) = self.scrollback.scroll_info();
                let content_end_screen = u16::try_from(
                    layout.scrollback.y as usize + total_height.saturating_sub(scroll_offset),
                )
                .unwrap_or(u16::MAX);
                if content_end_screen <= gap_y && content_end_screen >= layout.scrollback.y {
                    let line_y = content_end_screen;
                    content_line_y = Some(line_y);
                    let line_x = gap_x + 3;
                    let line_end = (gap_x + gap_w).saturating_sub(2);
                    let line_style = ratatui::style::Style::default().fg(theme.bg_light);
                    for x in line_x..line_end {
                        if let Some(cell) = buf.cell_mut((x, line_y)) {
                            cell.set_symbol("╌");
                            cell.set_style(line_style);
                        }
                    }
                }
            }
            let show_indicator = appearance.scrollback.scroll.follow_indicator
                != FollowIndicator::None
                && !self.scrollback.is_follow_mode()
                && self.scrollback.has_content_below()
                && content_line_y.is_none();
            if show_indicator {
                let center_x = gap_x + gap_w / 2;
                let indicator_style =
                    ratatui::style::Style::default().fg(if self.hit_follow_indicator.hovered {
                        theme.gray_bright
                    } else {
                        theme.gray
                    });
                if let Some(cell) = buf.cell_mut((center_x, gap_y)) {
                    cell.set_symbol("▼");
                    cell.set_style(indicator_style);
                }
                self.hit_follow_indicator.set(Some(Rect::new(
                    center_x.saturating_sub(1),
                    gap_y,
                    3,
                    1,
                )));
            } else {
                self.hit_follow_indicator.clear();
            }
        }
        if let Some(msg) = self.active_toast_message() {
            let sb = layout.scrollback;
            let toast_text = format!(" {msg} ");
            let w = toast_text.chars().count() as u16;
            if sb.height > 0 && sb.width > w + 2 {
                let x = sb.right().saturating_sub(w + 1);
                let y = sb.bottom().saturating_sub(1);
                for (i, ch) in toast_text.chars().enumerate() {
                    if let Some(cell) = buf.cell_mut((x + i as u16, y)) {
                        cell.set_char(ch);
                        cell.fg = theme.accent_user;
                        cell.bg = theme.bg_base;
                        cell.modifier = ratatui::prelude::Modifier::BOLD;
                    }
                }
                self.frame_occluder_rects.push(Rect {
                    x,
                    y,
                    width: w,
                    height: 1,
                });
            }
        }
        if tasks_height > 0 {
            let bg_focused = self.active_pane == ActivePane::Tasks && !overlay_focused;
            self.tasks.render(
                layout.tasks,
                buf,
                bg_focused,
                layout_cfg,
                &self.session.bg_tasks,
                &self.subagent_sessions,
                &self.session.scheduled_tasks,
            );
            let close_rect = agent::render_todo_chrome(
                buf,
                layout.tasks,
                layout_cfg,
                bg_focused,
                false,
                self.hit_bg_close.hovered,
                &theme,
            )
            .and_then(|sel| sel.close_button_rect());
            self.hit_bg_close.set(close_rect);
        }
        if catalog_height > 0 {
            let cat_focused = self.active_pane == ActivePane::Catalog && !overlay_focused;
            self.catalog
                .render(layout.catalog, buf, cat_focused, layout_cfg);
            let close_rect = agent::render_todo_chrome(
                buf,
                layout.catalog,
                layout_cfg,
                cat_focused,
                false,
                self.hit_catalog_close.hovered,
                &theme,
            )
            .and_then(|sel| sel.close_button_rect());
            self.hit_catalog_close.set(close_rect);
        } else {
            self.hit_catalog_close.clear();
        }
        if todo_height > 0 {
            let todo_focused = self.active_pane == ActivePane::Todo && !overlay_focused;
            self.todo.render(layout.todo, buf, todo_focused, layout_cfg);
            let close_rect = agent::render_todo_chrome(
                buf,
                layout.todo,
                layout_cfg,
                todo_focused,
                false,
                self.hit_todo_close.hovered,
                &theme,
            )
            .and_then(|sel| sel.close_button_rect());
            self.hit_todo_close.set(close_rect);
        } else {
            self.hit_todo_close.clear();
        }
        if queue_height > 0 {
            let queue_focused = self.active_pane == ActivePane::Queue && !overlay_focused;
            self.queue.render(
                layout.queue,
                buf,
                queue_focused,
                layout_cfg,
                Some(layout.scrollback),
                self.session.state.is_turn_running(),
            );
            let close_rect = agent::render_todo_chrome_with_close_label(
                buf,
                layout.queue,
                layout_cfg,
                queue_focused,
                false,
                self.hit_queue_close.hovered,
                &theme,
                Some(crate::glyphs::ballot_x_button()),
            )
            .and_then(|sel| sel.close_button_rect());
            self.hit_queue_close.set(close_rect);
        } else {
            self.hit_queue_close.clear();
        }
        self.last_btw_selection_model = ResolvedSelectionModel::default();
        self.last_btw_area = Rect::default();
        if btw_height > 0
            && let Some(ref btw) = self.btw_state
        {
            let tick = self.scrollback.animation_tick();
            let mut btw_links = crate::render::osc8::LinkOverlay::new();
            crate::views::btw_overlay::render_btw_panel(
                buf,
                btw,
                layout.btw,
                tick,
                self.btw_focused && self.active_pane == AgentPane::Prompt,
                Some(&mut self.hit_btw_close),
                &mut self.last_btw_selection_model,
                Some(&mut btw_links),
                &self.media_link_paths,
            );
            self.last_btw_area = layout.btw;
            if !btw_links.is_empty() {
                self.last_link_overlay.extend_from(&btw_links);
                self.visible_link_map.append_from_overlay(&btw_links);
            }
            let sb_n = self.scrollback_visible_link_count;
            let total_links = self.visible_link_map.len();
            self.paint_link_highlights(buf, link_active_style, sb_n..total_links);
            self.reclamp_drag_head_post_render(true);
            if let Some(ref drag) = self.drag_selection
                && drag.anchor.entry_idx == BTW_OVERLAY_ENTRY_IDX
            {
                render_active_selection_overlay(&self.last_btw_selection_model, drag, None, buf);
            } else if let Some(ref sel) = self.persistent_text_selection
                && sel.entry_idx == BTW_OVERLAY_ENTRY_IDX
            {
                render_persistent_selection_overlay(&self.last_btw_selection_model, sel, None, buf);
            }
        } else {
            self.hit_btw_close.clear();
        }
        if let Some(idx) = self.highlighted_link_idx {
            if self.visible_link_map.is_empty() {
                self.highlighted_link_idx = None;
            } else if idx >= self.visible_link_map.links().len() {
                self.highlighted_link_idx = Some(self.visible_link_map.links().len() - 1);
            }
        }
        if let Some(idx) = self.hovered_link_idx
            && (self.visible_link_map.is_empty() || idx >= self.visible_link_map.links().len())
        {
            self.hovered_link_idx = None;
        }
        if turn_status_height > 0 {
            let pad_left = HorizontalLayout::ACCENT + layout_cfg.block_pad_left.saturating_sub(1);
            let turn_area = Rect {
                x: layout.turn_status.x + pad_left,
                y: layout.turn_status.y,
                width: layout.turn_status.width.saturating_sub(pad_left),
                height: layout.turn_status.height,
            };
            let tick = self.scrollback.animation_tick();
            let activity = self.resolve_turn_activity();
            if activity != self.last_activity {
                if let Some(prev) = &self.last_activity {
                    let phase_ms = self
                        .activity_started_at
                        .map(|t| t.elapsed().as_millis() as u64)
                        .unwrap_or(0);
                    let prev_label = prev.as_label();
                    let next_label = activity.as_ref().map(|a| a.as_label()).unwrap_or("idle");
                    let sid = self.session.session_id.as_ref().map(|s| s.0.as_ref());
                    crate::unified_log::debug(
                        "turn.phase_transition",
                        sid,
                        Some(serde_json::json!(
                            { "from" : prev_label, "to" : next_label, "phase_elapsed_ms"
                            : phase_ms, }
                        )),
                    );
                }
                self.activity_started_at = Some(Instant::now());
                self.last_activity = activity.clone();
            }
            self.hit_plan_approval_status.clear();
            if let Some(ref pav) = self.plan_approval_view {
                let diamond_color = crate::views::turn_status::pending_diamond_color(
                    &theme,
                    theme.accent_plan,
                    tick,
                );
                let text_style = if self.hit_plan_approval_status.hovered {
                    Style::default()
                        .fg(theme.text_primary)
                        .add_modifier(ratatui::style::Modifier::UNDERLINED)
                } else {
                    Style::default().fg(theme.gray)
                };
                let status_label =
                    crate::views::plan_approval_view::plan_approval_status_label(pav.has_plan);
                let spans = vec![
                    Span::styled(
                        format!("{} ", crate::glyphs::diamond_filled()),
                        Style::default().fg(diamond_color),
                    ),
                    Span::styled(status_label, text_style),
                ];
                buf.set_line_safe(
                    turn_area.x,
                    turn_area.y,
                    &Line::from(spans),
                    turn_area.width,
                );
                let item_width: u16 = 2u16.saturating_add(status_label.len() as u16);
                self.hit_plan_approval_status.rect = Some(Rect::new(
                    turn_area.x,
                    turn_area.y,
                    item_width.min(turn_area.width),
                    1,
                ));
                self.hit_cancel_button.rect = None;
                self.hit_bg_button.rect = None;
            } else {
                let has_running_execute = self
                    .session
                    .tracker
                    .running_execute_tool_call_id()
                    .is_some();
                let is_pending_user_input =
                    !self.permission_queue.is_empty() || self.question_view.is_some();
                let goal_verifying = self
                    .goal_state
                    .as_ref()
                    .is_some_and(|g| g.verifying_completion);
                let held_queue = self.held_queue_count();
                let held_queue_top_sendable = self.held_queue_top_sendable();
                let turn_output = turn_status::render_turn_status(
                    buf,
                    turn_area,
                    &self.session.state,
                    &activity,
                    self.turn_elapsed(),
                    self.activity_started_at,
                    tick,
                    drain_blocked,
                    Some(turn_status::MouseButtons {
                        cancel_hovered: self.hit_cancel_button.hovered,
                        bg_hovered: self.hit_bg_button.hovered,
                    }),
                    has_running_execute,
                    self.context_state.as_ref().map(|c| c.used),
                    self.mcp_init_progress.as_ref(),
                    self.bash_turn,
                    is_pending_user_input,
                    goal_verifying,
                    watchers,
                    false,
                    held_queue,
                    held_queue_top_sendable,
                );
                self.hit_cancel_button
                    .set_unless_dropdown(turn_output.cancel_button, dropdown_open);
                self.hit_bg_button
                    .set_unless_dropdown(turn_output.bg_button, dropdown_open);
            }
        } else {
            self.hit_cancel_button.clear();
            self.hit_bg_button.clear();
            self.hit_plan_approval_status.clear();
        }
        if let Some((ref msg, remaining)) = self.mode_switch_banner {
            if layout.banner.height > 0 && layout.banner.width > 4 {
                let bg = theme.bg_base;
                for col in 0..layout.banner.width {
                    if let Some(cell) = buf.cell_mut((layout.banner.x + col, layout.banner.y)) {
                        cell.set_char(' ');
                        cell.fg = bg;
                        cell.bg = bg;
                    }
                }
                let opacity = if remaining > MODE_BANNER_FADE_TICKS {
                    1.0
                } else {
                    remaining as f32 / MODE_BANNER_FADE_TICKS as f32
                };
                let base_fg = theme.text_secondary;
                let fg = crate::render::color::blend_color(theme.bg_base, base_fg, opacity)
                    .unwrap_or(base_fg);
                let text = format!("  {}", msg);
                let maxw = layout.banner.width.saturating_sub(2) as usize;
                let display: String = if text.len() > maxw {
                    text.chars()
                        .take(maxw.saturating_sub(1))
                        .collect::<String>()
                        + "…"
                } else {
                    text
                };
                let x = layout.banner.x;
                let y = layout.banner.y;
                for (i, ch) in display.chars().enumerate() {
                    if let Some(cell) = buf.cell_mut((x + i as u16, y)) {
                        cell.set_char(ch);
                        cell.fg = fg;
                        cell.bg = bg;
                    }
                }
            }
        } else {
            if banner_height > 0
                && let Some(tip_text) = tip
            {
                crate::tips::render::render_tip(layout.banner, buf, tip_text);
            }
            if tip_row_visible && let Some(line) = self.ephemeral_tip.line() {
                crate::tips::render::render_ephemeral_tip(layout.banner, buf, line);
            }
        }
        self.follow_up_chips = match self.follow_ups.as_ref() {
            Some(fu) => agent::render_follow_ups(
                layout.follow_ups,
                buf,
                &theme,
                &fu.suggestions,
                self.hovered_follow_up_chip,
            ),
            None => {
                self.hovered_follow_up_chip = None;
                Vec::new()
            }
        };
        let editing_label;
        let commenting_label;
        let theme = Theme::current();
        let mut mode_flags_vec: Vec<PromptFlag> = Vec::new();
        let approval_is_commenting = self
            .plan_approval_view
            .as_ref()
            .is_some_and(|pav| pav.focus == PlanApprovalFocus::Commenting);
        if effective_plan || casual_commenting {
            let commenting_range: Option<&std::ops::Range<usize>> = if approval_is_commenting {
                self.plan_approval_view
                    .as_ref()
                    .and_then(|pav| pav.commenting_range.as_ref())
            } else if casual_commenting {
                self.casual_commenting_range.as_ref()
            } else {
                None
            };
            let plan_label: &str = if approval_is_commenting || casual_commenting {
                commenting_label = match commenting_range {
                    Some(r) if r.len() == 1 => format!("commenting L{}", r.start),
                    Some(r) => format!("commenting L{}-{}", r.start, r.end - 1),
                    None => "commenting".to_string(),
                };
                commenting_label.as_str()
            } else if self.plan_approval_view.is_some() {
                "plan approval"
            } else {
                "plan"
            };
            mode_flags_vec.push(PromptFlag {
                text: plan_label,
                color: Some(theme.accent_plan),
                bold: false,
            });
        }
        if self.session.is_yolo() && !effective_plan {
            mode_flags_vec.push(PromptFlag {
                text: "always-approve",
                color: None,
                bold: false,
            });
        }
        if self.auto_flag_visible(effective_plan) {
            mode_flags_vec.push(PromptFlag {
                text: "auto",
                color: Some(theme.accent_system),
                bold: false,
            });
        }
        let mode_flags: &[PromptFlag] = &mode_flags_vec;
        let multiline = self.multiline_mode;
        let model_label = match self.session.models.reasoning_effort_display() {
            Some(eff) => format!("{model_id} ({eff})"),
            None => model_id,
        };
        let info = match &self.prompt_mode {
            PromptMode::Normal => PromptInfo {
                model_name: &model_label,
                flags: mode_flags,
                multiline,
            },
            PromptMode::EditingQueued { id, .. } => {
                let pos = self.session.queue_position(*id).map(|i| i + 1).unwrap_or(1);
                editing_label = format!("editing queued #{pos}");
                PromptInfo {
                    model_name: &editing_label,
                    flags: mode_flags,
                    multiline,
                }
            }
        };
        let info = if let Some(label) = self.prompt_input_mode.prompt_info_override() {
            PromptInfo {
                model_name: label,
                flags: &[],
                multiline: false,
            }
        } else {
            info
        };
        let mut prompt_cursor_pos: Option<(u16, u16)> = None;
        let mut prompt_post_flush: Option<crate::terminal::overlay::PostFlush> = None;
        if permission_view_h > 0 {
            let perm_area = layout.prompt;
            if let Some(perm) = self.permission_queue.front() {
                let followup_text = self.prompt.text();
                let render_result = crate::views::permission_view::render_permission_view(
                    buf,
                    perm_area,
                    perm,
                    followup_text,
                    self.hovered_permission_item,
                    &theme,
                    prompt_focused,
                );
                if let Some(ref iarea) = render_result.inline_prompt {
                    let row_bg = theme.bg_visual;
                    let remaining_h = (perm_area.y + perm_area.height).saturating_sub(iarea.y);
                    let perm_followup_style = PromptStyle {
                        focused: true,
                        show_prefix: false,
                        vpad_top: 0,
                        chrome: false,
                        chrome_pad_left: 0,
                        chrome_pad_right: 0,
                        bg_override: Some(row_bg),
                        accent_color_override: None,
                        border_color_override: None,
                        prefix_override: None,
                        placeholder_override: None,
                        compact: false,
                        show_accent_line: false,
                        show_borders: false,
                        title: None,
                        image_preview: true,
                    };
                    let prompt_h = remaining_h.saturating_sub(1).max(1);
                    let prompt_draw_area = Rect {
                        x: iarea.text_x,
                        y: iarea.y,
                        width: iarea.text_w,
                        height: prompt_h,
                    };
                    let prompt_result_inner =
                        self.prompt
                            .draw(buf, prompt_draw_area, None, &perm_followup_style, None);
                    if let Some(pos) = prompt_result_inner.cursor_pos {
                        prompt_cursor_pos = Some(pos);
                    }
                    let accent_color = theme.accent_user;
                    let track_x = prompt_draw_area.x + prompt_draw_area.width.saturating_sub(1);
                    let accent_end = iarea.y.saturating_add(prompt_h);
                    for extra_y in (iarea.y + 1)..accent_end {
                        buf.set_style(
                            Rect {
                                x: layout.prompt.x + 1,
                                y: extra_y,
                                width: track_x.saturating_sub(layout.prompt.x + 1),
                                height: 1,
                            },
                            Style::default().bg(row_bg),
                        );
                        if let Some(cell) = buf.cell_mut((layout.prompt.x, extra_y)) {
                            cell.set_symbol(crate::glyphs::accent_bar());
                            cell.set_style(Style::default().fg(accent_color).bg(row_bg));
                        }
                    }
                    let track_bg = Style::default().bg(theme.bg_dark);
                    for track_y in iarea.y..accent_end {
                        if let Some(cell) = buf.cell_mut((track_x, track_y))
                            && cell.symbol() == " "
                        {
                            cell.set_style(track_bg);
                        }
                    }
                }
            }
        } else if question_view_h > 0 {
            let question_accent = if effective_plan {
                theme.accent_plan
            } else {
                theme.accent_user
            };
            let is_input_mode = self
                .question_view
                .as_ref()
                .map(|qv| qv.focus == crate::views::question_view::QuestionFocus::InputMode)
                .unwrap_or(false);
            let inline_prompt_h: u16 = if is_input_mode {
                question_prompt_body_h.max(1)
            } else {
                0
            };
            let question_area = Rect {
                x: layout.prompt.x,
                y: layout.prompt.y,
                width: layout.prompt.width,
                height: layout
                    .prompt
                    .height
                    .saturating_sub(inline_prompt_h)
                    .saturating_sub(question_footer_h),
            };
            if let Some(ref mut qv) = self.question_view {
                let content_w = inner_width.saturating_sub(QUESTION_VIEW_HPAD) as usize;
                if let Some(question) = qv.questions.get(qv.active_tab) {
                    let visible_options_h = crate::views::question_view::visible_options_height(
                        question,
                        question_area.height,
                        content_w,
                        qv.focused_preview(),
                        qv.fullscreen,
                        qv.cached_desc_cap,
                        qv.cached_preview_cap,
                    );
                    qv.clamp_scroll(visible_options_h, content_w);
                }
            }
            if let Some(ref qv) = self.question_view {
                let render_result = crate::views::question_view::render_question_view(
                    buf,
                    question_area,
                    qv,
                    self.hovered_question_item,
                    &theme,
                    prompt_focused,
                );
                self.question_scroll_region =
                    Some((render_result.options_start_y, render_result.options_end_y));
            }
            if is_input_mode && inline_prompt_h > 0 {
                let row_y = question_area.y + question_area.height;
                let content_x = layout.prompt.x + 3;
                let content_w = layout.prompt.width.saturating_sub(3);
                let row_bg = theme.bg_visual;
                buf.set_style(
                    Rect {
                        x: content_x,
                        y: row_y,
                        width: content_w,
                        height: 1,
                    },
                    Style::default().bg(row_bg),
                );
                let prefix_w = self
                    .question_view
                    .as_ref()
                    .and_then(|qv| qv.questions.get(qv.active_tab))
                    .map(crate::views::question_view::option_prefix_w)
                    .unwrap_or(6) as u16;
                let num_style = Style::default().fg(theme.accent_user).bg(row_bg);
                let marker_style = Style::default()
                    .fg(theme.text_primary)
                    .bg(row_bg)
                    .add_modifier(ratatui::style::Modifier::BOLD);
                let prompt_ind = Style::default().fg(theme.accent_user).bg(row_bg);
                buf.set_span_safe(content_x, row_y, &Span::styled("z ", num_style), 2);
                let is_multi = self
                    .question_view
                    .as_ref()
                    .and_then(|qv| qv.questions.get(qv.active_tab))
                    .and_then(|q| q.multi_select)
                    .unwrap_or(false);
                let freeform_sel = self
                    .question_view
                    .as_ref()
                    .and_then(|qv| {
                        qv.per_question_freeform_selected
                            .get(qv.active_tab)
                            .copied()
                    })
                    .unwrap_or(false);
                let (marker_text, actual_marker_style) = if is_multi {
                    if freeform_sel {
                        ("[x] ".to_string(), marker_style)
                    } else {
                        (
                            "[ ] ".to_string(),
                            Style::default().fg(theme.gray).bg(row_bg),
                        )
                    }
                } else if freeform_sel {
                    (format!("({}) ", crate::glyphs::filled_dot()), marker_style)
                } else {
                    (
                        "(\u{25cb}) ".to_string(),
                        Style::default().fg(theme.gray).bg(row_bg),
                    )
                };
                buf.set_span_safe(
                    content_x + 2,
                    row_y,
                    &Span::styled(marker_text, actual_marker_style),
                    4,
                );
                buf.set_span_safe(
                    content_x + prefix_w,
                    row_y,
                    &Span::styled(crate::glyphs::prompt_arrow(), prompt_ind),
                    2,
                );
                let text_x = content_x + prefix_w + 2;
                let text_w = content_w.saturating_sub(prefix_w + 2);
                let text_style = PromptStyle {
                    show_prefix: false,
                    ..question_input_style.clone()
                };
                let prompt_draw_area = Rect {
                    x: text_x,
                    y: row_y,
                    width: text_w,
                    height: inline_prompt_h,
                };
                let prompt_result_inner = self.prompt.draw(
                    buf,
                    prompt_draw_area,
                    Some(layout.scrollback),
                    &text_style,
                    None,
                );
                prompt_cursor_pos = prompt_result_inner.cursor_pos;
                self.inline_prompt_area = Some(Rect {
                    x: layout.prompt.x,
                    y: row_y,
                    width: layout.prompt.width,
                    height: inline_prompt_h,
                });
                for y in row_y..row_y.saturating_add(inline_prompt_h) {
                    if y > row_y {
                        buf.set_style(
                            Rect {
                                x: content_x,
                                y,
                                width: prefix_w + 2,
                                height: 1,
                            },
                            Style::default().bg(row_bg),
                        );
                    }
                    for col in (layout.prompt.x + 1)..(layout.prompt.x + 3) {
                        if let Some(cell) = buf.cell_mut((col, y)) {
                            cell.set_char(' ');
                            cell.set_style(Style::default().bg(row_bg));
                        }
                    }
                    if let Some(cell) = buf.cell_mut((layout.prompt.x, y)) {
                        cell.set_symbol(crate::glyphs::accent_bar());
                        cell.set_style(Style::default().fg(question_accent).bg(row_bg));
                    }
                }
            }
            if !is_input_mode {
                self.inline_prompt_area = None;
            }
            if let Some(ref qv) = self.question_view {
                let footer_y = question_area.y + question_area.height + inline_prompt_h + 1;
                let footer_x = layout.prompt.x;
                let footer_w = layout.prompt.width;
                if footer_y < layout.prompt.y + layout.prompt.height && footer_w > 10 {
                    use ratatui::style::Modifier;
                    let footer_bg = theme.bg_light;
                    let gap_above = footer_y.saturating_sub(1);
                    if gap_above >= question_area.y + question_area.height + inline_prompt_h {
                        buf.set_style(
                            Rect {
                                x: footer_x,
                                y: gap_above,
                                width: footer_w,
                                height: 1,
                            },
                            Style::default().bg(footer_bg),
                        );
                    }
                    let gap_below = footer_y + 1;
                    if gap_below < layout.prompt.y + layout.prompt.height {
                        buf.set_style(
                            Rect {
                                x: footer_x,
                                y: gap_below,
                                width: footer_w,
                                height: 1,
                            },
                            Style::default().bg(footer_bg),
                        );
                    }
                    let footer_rect = Rect {
                        x: footer_x,
                        y: footer_y,
                        width: footer_w,
                        height: 1,
                    };
                    buf.set_style(footer_rect, Style::default().bg(footer_bg));
                    let content_x = layout.prompt.x + 3;
                    let hint_style = Style::default()
                        .fg(theme.gray)
                        .bg(footer_bg)
                        .add_modifier(Modifier::BOLD);
                    let hint_key = Style::default()
                        .fg(question_accent)
                        .bg(footer_bg)
                        .add_modifier(Modifier::BOLD);
                    let mut left_spans: Vec<Span<'_>> = Vec::new();
                    if qv.questions.len() > 1 {
                        let counter = format!("[{}/{}] ", qv.active_tab + 1, qv.questions.len());
                        left_spans.push(Span::styled(counter, hint_style));
                    }
                    left_spans.push(Span::styled("\u{2191}/\u{2193}", hint_key));
                    left_spans.push(Span::styled(" navigate", hint_style));
                    if qv.questions.len() > 1 {
                        left_spans.push(Span::styled(" \u{b7} ", hint_style));
                        left_spans.push(Span::styled("\u{2190}/\u{2192}", hint_key));
                        left_spans.push(Span::styled(" question", hint_style));
                    }
                    left_spans.push(Span::styled(" \u{b7} ", hint_style));
                    left_spans.push(Span::styled("y", hint_key));
                    left_spans.push(Span::styled(" copy", hint_style));
                    let left_line = Line::from(left_spans);
                    let avail_w = footer_w.saturating_sub(3);
                    buf.set_line_safe(content_x, footer_y, &left_line, avail_w);
                    let is_last = qv.active_tab >= qv.questions.len().saturating_sub(1);
                    let enter_label = if qv.is_on_freeform_row() {
                        "edit"
                    } else if is_last {
                        "submit"
                    } else {
                        "select"
                    };
                    let btn_key = "Enter";
                    let btn_bg = theme.bg_base;
                    let bkey_style = Style::default()
                        .fg(question_accent)
                        .bg(btn_bg)
                        .add_modifier(Modifier::BOLD);
                    let blabel_style = Style::default().fg(theme.gray).bg(btn_bg);
                    let bpad_style = Style::default().bg(btn_bg);
                    let bw = (1 + btn_key.len() + 1 + enter_label.len() + 1) as u16;
                    let btn_x = footer_x + footer_w.saturating_sub(3).saturating_sub(bw);
                    if btn_x > content_x {
                        buf.set_span_safe(btn_x, footer_y, &Span::styled(" ", bpad_style), 1);
                        buf.set_span_safe(
                            btn_x + 1,
                            footer_y,
                            &Span::styled(btn_key, bkey_style),
                            btn_key.len() as u16,
                        );
                        let cx = btn_x + 1 + btn_key.len() as u16;
                        buf.set_span_safe(cx, footer_y, &Span::styled(":", blabel_style), 1);
                        buf.set_span_safe(
                            cx + 1,
                            footer_y,
                            &Span::styled(enter_label, blabel_style),
                            enter_label.len() as u16,
                        );
                        buf.set_span_safe(
                            cx + 1 + enter_label.len() as u16,
                            footer_y,
                            &Span::styled(" ", bpad_style),
                            1,
                        );
                        self.question_nav_buttons.clear();
                        let btn_rect = Rect {
                            x: btn_x,
                            y: footer_y,
                            width: bw,
                            height: 1,
                        };
                        self.question_nav_buttons.push(('\n', btn_rect));
                    } else {
                        self.question_nav_buttons.clear();
                    }
                }
            }
            let accent_style = Style::default().fg(question_accent).bg(theme.bg_light);
            for y in layout.prompt.y..layout.prompt.y + layout.prompt.height {
                if let Some(cell) = buf.cell_mut((layout.prompt.x, y)) {
                    cell.set_symbol(crate::glyphs::accent_bar());
                    cell.set_style(accent_style);
                }
            }
            if let Some(ref qv) = self.question_view {
                let inner_scrollbar_x = layout.prompt.x + layout.prompt.width - 1;
                let scroll_region = self
                    .question_scroll_region
                    .unwrap_or((question_area.y, question_area.y + question_area.height));
                let sb_rect = crate::views::question_view::render_question_scrollbar(
                    buf,
                    inner_scrollbar_x,
                    qv,
                    &theme,
                    scroll_region,
                );
                self.hit_question_scrollbar.set(sb_rect);
            } else {
                self.hit_question_scrollbar.clear();
            }
        } else if rewind_view_h > 0 {
            if let Some(ref rw) = self.rewind_state {
                crate::views::rewind::render_rewind_overlay(
                    buf,
                    layout.prompt,
                    &rw.phase,
                    prompt_focused,
                );
            }
        } else if jump_view_h > 0 {
            if let Some(ref js) = self.jump_state {
                crate::views::jump::render_jump_overlay(buf, layout.prompt, js, prompt_focused);
            }
        } else if cancel_turn_view_h > 0 {
            let buttons = &mut self.cancel_turn_buttons;
            if let Some(ctv) = self.cancel_turn_view.as_ref() {
                modal::render_cancel_turn_panel(buf, layout.prompt, ctv, prompt_focused, buttons);
            } else {
                buttons.clear();
            }
        } else {
            let collapsed = !prompt_focused && appearance.prompt.collapse_unfocused;
            let saved_scroll = if collapsed {
                let s = self.prompt.scroll();
                let ovr = self.prompt.textarea.scroll_override();
                self.prompt.textarea.set_scroll_override(Some(0));
                Some((s, ovr))
            } else {
                None
            };
            let prompt_result_inner = self.prompt.draw(
                buf,
                layout.prompt,
                Some(layout.scrollback),
                &prompt_style,
                Some(&info),
            );
            if let Some((s, ovr)) = saved_scroll {
                self.prompt.textarea.set_scroll_override(ovr);
                self.prompt.set_scroll(s);
            }
            prompt_cursor_pos = prompt_result_inner.cursor_pos;
            if let Some(escapes) = prompt_result_inner.post_flush_escapes {
                prompt_post_flush = Some(escapes.into());
            }
        }
        if self.prompt.file_search_visible() {
            use crate::views::file_search::dropdown::{MAX_DROPDOWN_ROWS, render_dropdown};
            let item_count = self.prompt.file_search.result_count();
            let item_rows = (item_count as u16).min(MAX_DROPDOWN_ROWS);
            if item_rows > 0 {
                self.prompt.file_search.ensure_visible(item_rows as usize);
                let bottom_border_y = if let Some(ipa) = self.inline_prompt_area {
                    ipa.y.saturating_sub(1)
                } else {
                    layout.prompt.y.saturating_sub(1)
                };
                let panel_height = item_rows + 2;
                let top_border_y = bottom_border_y.saturating_sub(panel_height - 1);
                let panel_x = area.x + layout_cfg.eff_hpad_left(compact);
                let panel_width = area.width.saturating_sub(
                    layout_cfg.eff_hpad_left(compact) + layout_cfg.eff_hpad_right(compact),
                );
                if top_border_y < bottom_border_y && panel_width > 4 {
                    let panel_area = Rect {
                        x: panel_x,
                        y: top_border_y,
                        width: panel_width,
                        height: panel_height,
                    };
                    ratatui::widgets::Clear.render(panel_area, buf);
                    self.frame_occluder_rects.push(panel_area);
                    buf.set_style(
                        panel_area,
                        Style::default().fg(theme.text_primary).bg(theme.bg_light),
                    );
                    let border_style = Style::default().fg(theme.bg_highlight).bg(theme.bg_base);
                    let border_line = Line::styled("─".repeat(panel_width as usize), border_style);
                    buf.set_line_safe(panel_x, top_border_y, &border_line, panel_width);
                    buf.set_line_safe(panel_x, bottom_border_y, &border_line, panel_width);
                    {
                        let (k, n) = (
                            self.prompt.file_search.result_count(),
                            self.prompt.file_search.total_items(),
                        );
                        let hint = if k >= 1000 {
                            format!("1k+/{n}")
                        } else {
                            format!("{k}/{n}")
                        };
                        let hint_w = hint.len() as u16;
                        if hint_w + 2 <= panel_width {
                            let hint_x = panel_x + panel_width - hint_w - 1;
                            let hint_line = Line::styled(
                                hint,
                                Style::default().fg(theme.gray).bg(theme.bg_base),
                            );
                            buf.set_line_safe(hint_x, top_border_y, &hint_line, hint_w);
                        }
                    }
                    let content_inset = 1 + layout_cfg.eff_hpad_left(compact);
                    let items_x = layout.prompt.x + content_inset;
                    let items_width = layout.prompt.width.saturating_sub(content_inset);
                    let items_area = Rect {
                        x: items_x,
                        y: top_border_y + 1,
                        width: items_width,
                        height: item_rows,
                    };
                    render_dropdown(buf, items_area, &self.prompt.file_search, &theme);
                    self.dropdown_items_area = Some(items_area);
                } else {
                    self.dropdown_items_area = None;
                }
            } else {
                self.dropdown_items_area = None;
            }
        } else {
            self.dropdown_items_area = None;
        }
        if !self.prompt.file_search_visible() && self.prompt.slash_open() {
            use crate::views::slash_dropdown::{
                desired_item_rows, render_dropdown as render_slash,
            };
            let snap = self.prompt.slash_snapshot();
            let item_count = snap.matches.len();
            let items_width = dropdown_items_width(layout.prompt, layout_cfg, compact);
            let item_rows = desired_item_rows(&snap.matches, items_width);
            if item_rows > 0 {
                if let Some(chrome) = render_dropdown_chrome(
                    buf,
                    item_count,
                    item_rows,
                    self.inline_prompt_area,
                    layout.prompt,
                    area,
                    layout_cfg,
                    compact,
                    false,
                    &theme,
                ) {
                    let hovered = self.prompt.slash_hovered();
                    self.slash_dropdown_hit =
                        render_slash(buf, chrome.items, &snap, hovered, &theme);
                    self.slash_dropdown_items_area = Some(chrome.items);
                    self.frame_occluder_rects.push(chrome.panel);
                } else {
                    self.slash_dropdown_items_area = None;
                    self.slash_dropdown_hit = Default::default();
                }
            } else {
                self.slash_dropdown_items_area = None;
                self.slash_dropdown_hit = Default::default();
            }
        } else {
            self.slash_dropdown_items_area = None;
            self.slash_dropdown_hit = Default::default();
        }
        if !self.prompt.slash_open()
            && !self.prompt.file_search_visible()
            && self.prompt.completion_dropdown_open()
        {
            use crate::views::completion_dropdown::{
                MAX_VISIBLE_ROWS, render_dropdown as render_completions,
            };
            let dd = &self.prompt.suggestions.dropdown;
            let item_count = dd.items.len();
            let item_rows = (item_count as u16).min(MAX_VISIBLE_ROWS);
            if item_rows > 0 {
                if let Some(chrome) = render_dropdown_chrome(
                    buf,
                    item_count,
                    item_rows,
                    self.inline_prompt_area,
                    layout.prompt,
                    area,
                    layout_cfg,
                    compact,
                    false,
                    &theme,
                ) {
                    render_completions(buf, chrome.items, dd, &theme);
                    self.completion_dropdown_items_area = Some(chrome.items);
                    self.frame_occluder_rects.push(chrome.panel);
                } else {
                    self.completion_dropdown_items_area = None;
                }
            } else {
                self.completion_dropdown_items_area = None;
            }
        } else {
            self.completion_dropdown_items_area = None;
        }
        if self.prompt.history_search.is_active() {
            use crate::render::scrollbar::render_scrollbar_styled;
            use ratatui::style::Modifier;
            let max_rows: u16 = 8;
            let result_count = self.prompt.history_search.result_count();
            let item_rows = (result_count as u16).min(max_rows).max(1);
            let panel_height = item_rows + 2;
            let bottom_border_y = layout.prompt.y.saturating_sub(1);
            let top_border_y = bottom_border_y.saturating_sub(panel_height - 1);
            let panel_x = area.x + layout_cfg.eff_hpad_left(compact);
            let panel_width = area.width.saturating_sub(
                layout_cfg.eff_hpad_left(compact) + layout_cfg.eff_hpad_right(compact),
            );
            if panel_height >= 3 && top_border_y < bottom_border_y && panel_width > 4 {
                let panel_area = Rect {
                    x: panel_x,
                    y: top_border_y,
                    width: panel_width,
                    height: panel_height,
                };
                ratatui::widgets::Clear.render(panel_area, buf);
                self.frame_occluder_rects.push(panel_area);
                buf.set_style(
                    panel_area,
                    Style::default().fg(theme.text_primary).bg(theme.bg_light),
                );
                let border_style = Style::default().fg(theme.bg_highlight).bg(theme.bg_base);
                let border_line =
                    Line::styled("\u{2500}".repeat(panel_width as usize), border_style);
                buf.set_line_safe(panel_x, top_border_y, &border_line, panel_width);
                buf.set_line_safe(panel_x, bottom_border_y, &border_line, panel_width);
                {
                    let hint = format!("{result_count}");
                    let hint_w = hint.len() as u16;
                    if hint_w + 2 <= panel_width {
                        let hint_x = panel_x + panel_width - hint_w - 1;
                        buf.set_line_safe(
                            hint_x,
                            top_border_y,
                            &Line::styled(hint, Style::default().fg(theme.gray).bg(theme.bg_base)),
                            hint_w,
                        );
                    }
                    let label = " history ";
                    let label_w = label.len() as u16;
                    if label_w + 2 <= panel_width {
                        buf.set_line_safe(
                            panel_x + 1,
                            top_border_y,
                            &Line::styled(label, Style::default().fg(theme.gray).bg(theme.bg_base)),
                            label_w,
                        );
                    }
                }
                let content_inset = 1 + layout_cfg.eff_hpad_left(compact);
                let items_x = layout.prompt.x + content_inset;
                let items_width = layout.prompt.width.saturating_sub(content_inset);
                let items_area_rect = Rect {
                    x: items_x,
                    y: top_border_y + 1,
                    width: items_width,
                    height: item_rows,
                };
                self.history_dropdown_area = Some(items_area_rect);
                let hover_bg = theme.bg_hover;
                if result_count == 0 {
                    let msg_y = top_border_y + 1;
                    if msg_y < bottom_border_y {
                        let message = if self.prompt_history_loading() {
                            "  Loading..."
                        } else {
                            "  no matching history"
                        };
                        buf.set_string_safe(
                            items_x,
                            msg_y,
                            message,
                            Style::default().fg(theme.gray).bg(theme.bg_light),
                        );
                    }
                } else {
                    let selected = self.prompt.history_search.selected;
                    let visible_rows = item_rows as usize;
                    let scroll_offset = if selected >= visible_rows {
                        selected - (visible_rows - 1)
                    } else {
                        0
                    };
                    let visible_end = result_count.min(scroll_offset + visible_rows);
                    let needs_scrollbar = result_count > visible_rows;
                    let text_width = if needs_scrollbar {
                        items_width.saturating_sub(2)
                    } else {
                        items_width
                    };
                    for (vi, ri) in (scroll_offset..visible_end).enumerate() {
                        let row_y = top_border_y + 1 + vi as u16;
                        if row_y >= bottom_border_y {
                            break;
                        }
                        let result = match self.prompt.history_search.result_at(ri) {
                            Some(r) => r,
                            None => continue,
                        };
                        let is_selected = ri == selected;
                        let is_hovered =
                            self.prompt.history_search.hovered() == Some(ri) && !is_selected;
                        let row_bg = if is_selected {
                            theme.bg_visual
                        } else if is_hovered {
                            hover_bg
                        } else {
                            theme.bg_light
                        };
                        let bold = if is_selected {
                            Modifier::BOLD
                        } else {
                            Modifier::empty()
                        };
                        let fill_width = if needs_scrollbar {
                            items_width.saturating_sub(1)
                        } else {
                            items_width
                        };
                        for col in items_x..items_x + fill_width {
                            if let Some(cell) = buf.cell_mut((col, row_y)) {
                                cell.set_char(' ');
                                cell.set_style(Style::default().bg(row_bg));
                            }
                        }
                        let prefix_w: u16 = crate::glyphs::PROMPT_ARROW_WIDTH;
                        let prefix = if is_selected {
                            crate::glyphs::prompt_arrow()
                        } else {
                            "  "
                        };
                        let pfx_style = Style::default()
                            .fg(theme.text_primary)
                            .bg(row_bg)
                            .add_modifier(bold);
                        for (i, ch) in prefix.chars().enumerate() {
                            let px = items_x + i as u16;
                            if px < items_x + text_width
                                && let Some(cell) = buf.cell_mut((px, row_y))
                            {
                                cell.set_char(ch);
                                cell.set_style(if is_selected {
                                    pfx_style
                                } else {
                                    Style::default().bg(row_bg)
                                });
                            }
                        }
                        let match_style = Style::default()
                            .fg(theme.accent_user)
                            .bg(row_bg)
                            .add_modifier(bold);
                        let normal_style = Style::default()
                            .fg(theme.text_primary)
                            .bg(row_bg)
                            .add_modifier(bold);
                        let mut indices = &result.indices[..];
                        let mut col = items_x + prefix_w;
                        let max_col = items_x + prefix_w + text_width.saturating_sub(prefix_w);
                        let preview: String = result
                            .text
                            .chars()
                            .map(|c| if c == '\n' || c == '\r' { ' ' } else { c })
                            .collect();
                        for (ci, ch) in preview.chars().enumerate() {
                            let cw = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) as u16;
                            if col + cw > max_col {
                                if col > items_x + prefix_w
                                    && let Some(cell) = buf.cell_mut((col.saturating_sub(1), row_y))
                                {
                                    cell.set_char('\u{2026}');
                                }
                                break;
                            }
                            let is_match = indices.first() == Some(&(ci as u32));
                            if is_match {
                                indices = &indices[1..];
                            }
                            let st = if is_match { match_style } else { normal_style };
                            if let Some(cell) = buf.cell_mut((col, row_y)) {
                                cell.set_symbol(&ch.to_string());
                                cell.set_style(st);
                            }
                            col += cw;
                        }
                    }
                    if needs_scrollbar {
                        let sb_area = Rect {
                            x: items_x + items_width - 1,
                            y: top_border_y + 1,
                            width: 1,
                            height: item_rows,
                        };
                        render_scrollbar_styled(
                            buf,
                            Some(sb_area),
                            result_count as u16,
                            item_rows,
                            scroll_offset as u16,
                            Style::default().bg(theme.bg_dark),
                            Style::default().fg(theme.gray_dim).bg(theme.bg_dark),
                        );
                    }
                }
            }
        } else {
            self.history_dropdown_area = None;
        }
        if self.active_modal.is_some() {
            self.draw_active_modal(area, buf, theme, compact);
            self.pane_areas = layout.pane_areas();
            return (None, crate::terminal::overlay::clear().map(Into::into));
        }
        if let Some(ref viewer) = self.block_viewer {
            let hints = viewer.shortcuts_hints();
            ShortcutsBar::new(&hints)
                .with_pending(pending_hint)
                .render(layout.shortcuts, buf);
        } else if !self.permission_queue.is_empty() {
            use crate::views::permission_view::PermissionFocus;
            use crate::views::shortcuts_bar::HintItem;
            let hints = if let Some(perm) = self.permission_queue.front() {
                match perm.focus {
                    PermissionFocus::FollowupInput => {
                        vec![
                            HintItem::new(key!(Enter), "send"),
                            HintItem::new(key!(Esc), "back"),
                        ]
                    }
                    PermissionFocus::Options => {
                        use crate::input::key::KeyShortcut;
                        use crossterm::event::{KeyCode, KeyModifiers};
                        let n = perm.options.len().min(9) as u8;
                        let last_ch = char::from(b'0' + n.max(1));
                        let last_key = KeyShortcut::new(KeyCode::Char(last_ch), KeyModifiers::NONE);
                        let mut hints = vec![HintItem::paired(key!('1'), last_key, "select")];
                        if perm.has_adjustable_scope() {
                            hints.push(HintItem::paired(key!(Left), key!(Right), "scope"));
                        }
                        if !perm.description.is_empty() {
                            let label = if perm.args_expanded {
                                "collapse"
                            } else {
                                "expand"
                            };
                            hints.push(HintItem::new(key!('f', CONTROL), label));
                        }
                        hints.push(HintItem::new(key!('o', CONTROL), "always-approve"));
                        hints.push(HintItem::new(key!('c', CONTROL), "cancel"));
                        hints
                    }
                }
            } else {
                vec![]
            };
            ShortcutsBar::new(&hints)
                .with_pending(pending_hint)
                .render(layout.shortcuts, buf);
        } else if let Some(ref pav) = self.plan_approval_view {
            let hints = self.plan_approval_shortcut_hints(pav);
            if !hints.is_empty() {
                ShortcutsBar::new(&hints)
                    .with_pending(pending_hint)
                    .render(layout.shortcuts, buf);
            }
        } else if self.line_viewer.is_some() && self.is_plan_viewer() {
            let suppress_shortcuts = self
                .line_viewer
                .as_ref()
                .is_some_and(|v| v.fullscreen && v.list_state.input_mode().is_some());
            if !suppress_shortcuts {
                use crate::views::shortcuts_bar::HintItem;
                let hints = if self.is_casual_commenting() {
                    vec![
                        HintItem::new(key!(Enter), "save comment"),
                        HintItem::new(key!(Esc), "cancel"),
                    ]
                } else {
                    let mut h = vec![
                        HintItem::new(key!('c'), "comment"),
                        HintItem::new(key!('f', CONTROL), "fullscreen"),
                    ];
                    if !self.plan_comments.is_empty() {
                        h.push(HintItem::new(key!('s'), "send"));
                    }
                    h.push(HintItem::new(key!(Esc), "close"));
                    h
                };
                ShortcutsBar::new(&hints)
                    .with_pending(pending_hint)
                    .render(layout.shortcuts, buf);
            }
        } else if let Some(ref qv) = self.question_view {
            use crate::views::question_view::QuestionFocus;
            use crate::views::shortcuts_bar::HintItem;
            let hints = match qv.focus {
                QuestionFocus::InputMode => {
                    if self.prompt.file_search_visible() {
                        vec![
                            HintItem::paired(key!(Up), key!(Down), "nav"),
                            HintItem::new(key!(Tab), "accept"),
                            HintItem::new(key!(Right), "drill"),
                            HintItem::new(key!(Esc), "dismiss"),
                        ]
                    } else {
                        vec![
                            HintItem::new(key!(Enter), "submit"),
                            HintItem::new(key!(Esc), "back"),
                        ]
                    }
                }
                QuestionFocus::Navigation => {
                    vec![
                        HintItem::new(key!(Esc), "unselect"),
                        HintItem::new(key!(Tab), "scrollback"),
                        HintItem::new(key!('X'), "dismiss"),
                    ]
                }
            };
            ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
        } else if self.cancel_turn_view.is_some() {
            use crate::views::shortcuts_bar::HintItem;
            let hints = vec![
                HintItem::paired(key!('1'), key!('4'), "select"),
                HintItem::new(key!(Enter), "confirm"),
                HintItem::new(key!(Esc), "keep running"),
                HintItem::new(key!(Tab), "scrollback"),
            ];
            ShortcutsBar::new(&hints)
                .with_pending(pending_hint)
                .render(layout.shortcuts, buf);
        } else {
            let mut hints = self.normal_pane_hints(registry);
            if in_dashboard_overlay {
                use crate::views::shortcuts_bar::HintItem;
                hints.insert(
                    0,
                    HintItem::new(
                        registry
                            .find(ActionId::DashboardOverlayStop)
                            .map(|def| def.default_key)
                            .unwrap_or(key!('x', CONTROL)),
                        "stop",
                    ),
                );
                hints.insert(
                    0,
                    HintItem {
                        keys: vec![key!('[', CONTROL), key!(']', CONTROL)],
                        label: "agents".into(),
                        custom_display: Some("Ctrl+[/]"),
                        description: None,
                        pinned: false,
                    },
                );
                hints.insert(0, HintItem::new(key!('\\', CONTROL), "dashboard"));
            }
            let help_hint = registry.find(ActionId::ShortcutsHelp).map(|def| {
                let mut hint = def.hint();
                if in_dashboard_overlay
                    && def.default_key == key!('x', CONTROL)
                    && let Some(alt) = def.alt_keys.first()
                {
                    hint.keys = vec![*alt];
                }
                hint
            });
            ShortcutsBar::new(&hints)
                .compact(5, help_hint)
                .with_pending(pending_hint)
                .render(layout.shortcuts, buf);
        }
        let is_plan_viewer = self.is_plan_viewer();
        let has_plan_comments = !self.plan_comments.is_empty();
        let casual_commenting = self.is_casual_commenting();
        if let Some(ref mut viewer) = self.line_viewer {
            use crate::views::file_search::line_viewer::render_line_viewer;
            use crate::views::shortcuts_bar::HintItem;
            let plan_prompt_focused = self
                .plan_approval_view
                .as_ref()
                .is_some_and(|p| p.focus != PlanApprovalFocus::Preview);
            let overlay_bottom = if layout.turn_status.height > 0 {
                layout.turn_status.y
            } else {
                layout.prompt.y
            };
            let overlay_area = Rect {
                x: area.x,
                y: area.y,
                width: area.width,
                height: overlay_bottom.saturating_sub(area.y),
            };
            let approval_comment_count = self
                .plan_approval_view
                .as_ref()
                .map(|pav| pav.comments.len())
                .unwrap_or(0);
            let effective_comment_count = if approval_comment_count > 0 {
                approval_comment_count
            } else {
                self.plan_comments.len()
            };
            if let Some(ref pav) = self.plan_approval_view {
                viewer.plan_mut().active_commenting_range = pav.commenting_range.clone();
            } else {
                viewer.plan_mut().active_commenting_range = self.casual_commenting_range.clone();
            }
            render_line_viewer(
                buf,
                overlay_area,
                viewer,
                &self.session.cwd,
                &theme,
                effective_comment_count,
            );
            let in_plan_approval = self.plan_approval_view.is_some();
            let on_comment = in_plan_approval
                && viewer
                    .list_state
                    .selected_index()
                    .and_then(|vi| {
                        let pi = viewer.list_state.to_physical(vi);
                        viewer.lines.get(pi)
                    })
                    .is_some_and(|item| item.comment_id().is_some());
            let approval_has_comments = in_plan_approval
                && self
                    .plan_approval_view
                    .as_ref()
                    .is_some_and(|pav| !pav.comments.is_empty());
            let viewer_hints = if in_plan_approval && on_comment {
                let mut h = vec![
                    HintItem::new(key!(Enter), "edit"),
                    HintItem::new(key!('x'), "delete"),
                ];
                if approval_has_comments {
                    h.push(HintItem::new(key!('s'), "send"));
                } else {
                    h.push(HintItem::new(key!('a'), "approve"));
                }
                h.push(HintItem::new(key!('q'), "quit plan"));
                h.push(HintItem::new(key!(Tab), "prompt"));
                h
            } else if in_plan_approval {
                let mut h = vec![HintItem::new(key!('c'), "comment")];
                if approval_has_comments {
                    h.push(HintItem::new(key!('s'), "send"));
                } else {
                    h.push(HintItem::new(key!('a'), "approve"));
                }
                h.push(HintItem::new(key!('q'), "quit plan"));
                if self.vim_mode {
                    h.push(HintItem::paired(key!('j'), key!('k'), "nav"));
                }
                h.push(HintItem::new(key!('v'), "select"));
                h.push(HintItem::new(key!(Tab), "prompt"));
                h
            } else if is_plan_viewer {
                let on_casual_comment = viewer
                    .list_state
                    .selected_index()
                    .and_then(|vi| {
                        let pi = viewer.list_state.to_physical(vi);
                        viewer.lines.get(pi)
                    })
                    .is_some_and(|item| item.comment_id().is_some());
                let mut h = if on_casual_comment {
                    vec![
                        HintItem::new(key!(Enter), "edit"),
                        HintItem::new(key!('x'), "delete"),
                    ]
                } else {
                    vec![HintItem::new(key!('c'), "comment")]
                };
                if has_plan_comments {
                    h.push(HintItem::new(key!('s'), "send"));
                }
                if self.vim_mode {
                    h.push(HintItem::paired(key!('j'), key!('k'), "nav"));
                }
                h.push(HintItem::new(key!('v'), "select"));
                h.push(HintItem::new(key!('f', CONTROL), "fullscreen"));
                h.push(HintItem::new(key!('/'), "search"));
                h.push(HintItem::new(key!(Esc), "close"));
                h
            } else {
                let mut h = vec![HintItem::new(key!(Enter), "confirm")];
                if self.vim_mode {
                    h.push(HintItem::paired(key!('j'), key!('k'), "nav"));
                }
                h.push(HintItem::new(key!('v'), "select"));
                h.push(HintItem::new(key!('x'), "clear"));
                if self.vim_mode {
                    h.push(HintItem::new(key!('y'), "copy"));
                    h.push(HintItem::new(key!('Y'), "filename"));
                }
                h.push(HintItem::new(key!(':'), "goto"));
                h.push(HintItem::new(key!('/'), "search"));
                h.push(HintItem::new(key!(Esc), "cancel"));
                h
            };
            let input_bar_active = viewer.list_state.input_mode().is_some();
            if !(plan_prompt_focused || casual_commenting || viewer.fullscreen && input_bar_active)
            {
                ShortcutsBar::new(&viewer_hints).render(layout.shortcuts, buf);
            }
            self.pane_areas = layout.pane_areas();
            let viewer_cursor = if plan_prompt_focused || self.is_casual_commenting() {
                prompt_cursor_pos
            } else {
                None
            };
            return (viewer_cursor, prompt_post_flush);
        }
        if let Some(ref mut viewer) = self.image_viewer {
            use crate::terminal::image::{GraphicsProtocol, detect_graphics_protocol};
            use crate::views::file_search::line_viewer::dim_area;
            use crate::views::shortcuts_bar::HintItem;
            let overlay_area = Rect {
                x: area.x,
                y: area.y,
                width: area.width,
                height: layout.shortcuts.y.saturating_sub(area.y),
            };
            let popup_width = ((overlay_area.width as u32 * 90) / 100)
                .max(28)
                .min(overlay_area.width as u32) as u16;
            let popup_height = ((overlay_area.height as u32 * 90) / 100)
                .max(8)
                .min(overlay_area.height as u32) as u16;
            let popup_x = overlay_area.x + (overlay_area.width.saturating_sub(popup_width)) / 2;
            let popup_y = overlay_area.y + (overlay_area.height.saturating_sub(popup_height)) / 2;
            let popup_rect = Rect::new(popup_x, popup_y, popup_width, popup_height);
            let mut image_escape_emitted = false;
            if popup_rect.width >= 10 && popup_rect.height >= 5 {
                dim_area(buf, overlay_area, theme.bg_base, 0.5);
                ratatui::widgets::Clear.render(popup_rect, buf);
                buf.set_style(
                    popup_rect,
                    Style::default().fg(theme.text_primary).bg(theme.bg_base),
                );
                let block = ratatui::widgets::Block::default()
                    .borders(ratatui::widgets::Borders::ALL)
                    .border_type(ratatui::widgets::BorderType::Rounded)
                    .border_style(Style::default().fg(theme.gray_dim))
                    .style(Style::default().bg(theme.bg_base));
                block.render(popup_rect, buf);
                let inner_cols = popup_rect.width.saturating_sub(2);
                let inner_rows = popup_rect.height.saturating_sub(2);
                let title_style = Style::default()
                    .fg(theme.text_primary)
                    .bg(theme.bg_base)
                    .add_modifier(ratatui::style::Modifier::BOLD);
                let dim_style = Style::default().fg(theme.gray_dim).bg(theme.bg_base);
                let border_style = Style::default().fg(theme.gray_dim).bg(theme.bg_base);
                let title_spans: Vec<ratatui::text::Span> = if viewer.loading {
                    let name = viewer.title.as_deref().unwrap_or("Loading...");
                    vec![
                        ratatui::text::Span::styled("\u{2500} ", border_style),
                        ratatui::text::Span::styled(name.to_owned(), title_style),
                        ratatui::text::Span::styled(" \u{2500}", border_style),
                    ]
                } else {
                    let name = viewer.title.as_deref().unwrap_or(&viewer.mime_type);
                    let dims = format!(" ({}\u{00d7}{})", viewer.image_width, viewer.image_height);
                    vec![
                        ratatui::text::Span::styled("\u{2500} ", border_style),
                        ratatui::text::Span::styled(name.to_owned(), title_style),
                        ratatui::text::Span::styled(dims, dim_style),
                        ratatui::text::Span::styled(" \u{2500}", border_style),
                    ]
                };
                viewer.modal_state.popup_area = Some(popup_rect);
                crate::views::modal_window::render_close_button(
                    buf,
                    popup_rect,
                    &mut viewer.modal_state,
                    &theme,
                );
                let close_width = viewer
                    .modal_state
                    .close_button_rect
                    .map_or(0, |r| r.width + 1);
                let tx = popup_rect.x + 2;
                let max_title_width = popup_rect.width.saturating_sub(2 + close_width + 1);
                {
                    use unicode_width::UnicodeWidthStr;
                    let mut cursor: u16 = 0;
                    for span in &title_spans {
                        let w = span.content.width() as u16;
                        if cursor + w > max_title_width {
                            break;
                        }
                        buf.set_span_safe(tx + cursor, popup_rect.y, span, w);
                        cursor += w;
                    }
                }
                if viewer.loading {
                    if inner_cols > 0 && inner_rows > 0 {
                        use crate::views::turn_status::SPINNER_DIVISOR;
                        use unicode_width::UnicodeWidthStr;
                        let tick = self.scrollback.animation_tick();
                        let frames = crate::glyphs::braille_spinner_frames();
                        let frame = frames[(tick / SPINNER_DIVISOR) as usize % frames.len()];
                        let loading = format!("{} Loading...", frame);
                        let lw = loading.width() as u16;
                        let lx = popup_rect.x + 1 + inner_cols.saturating_sub(lw) / 2;
                        let ly = popup_rect.y + 1 + inner_rows / 2;
                        buf.set_span_safe(
                            lx,
                            ly,
                            &ratatui::text::Span::styled(
                                loading,
                                Style::default().fg(theme.gray_dim).bg(theme.bg_base),
                            ),
                            lw,
                        );
                    }
                } else {
                    let rendered_pixel = crate::terminal::overlay::static_centered(
                        &viewer.display_bytes,
                        viewer.image_width,
                        viewer.image_height,
                        popup_rect,
                        viewer.overlay_owner_id,
                    )
                    .is_some_and(|esc| {
                        prompt_post_flush = Some(esc.into());
                        image_escape_emitted = true;
                        true
                    });
                    if inner_cols > 0 && inner_rows > 0 {
                        let inner_rect = ratatui::layout::Rect::new(
                            popup_rect.x + 1,
                            popup_rect.y + 1,
                            inner_cols,
                            inner_rows,
                        );
                        if !rendered_pixel {
                            let meta_lines = vec![
                                ratatui::text::Line::from(""),
                                ratatui::text::Line::from(format!(
                                    "  {}x{} {}",
                                    viewer.image_width, viewer.image_height, viewer.mime_type,
                                )),
                                ratatui::text::Line::from(""),
                                ratatui::text::Line::from("  Press Esc to close"),
                            ];
                            ratatui::widgets::Paragraph::new(meta_lines)
                                .style(Style::default().fg(theme.gray_dim).bg(theme.bg_base))
                                .render(inner_rect, buf);
                        } else {
                            let loading = "Loading...";
                            let lw = loading.len() as u16;
                            let lx = inner_rect.x + inner_cols.saturating_sub(lw) / 2;
                            let ly = inner_rect.y + inner_rows / 2;
                            buf.set_span_safe(
                                lx,
                                ly,
                                &ratatui::text::Span::styled(
                                    loading,
                                    Style::default().fg(theme.gray_dim).bg(theme.bg_base),
                                ),
                                lw,
                            );
                        }
                    }
                }
            }
            if !image_escape_emitted && detect_graphics_protocol() == GraphicsProtocol::Kitty {
                let clear = crate::terminal::overlay::clear_kitty();
                prompt_post_flush = Some(clear.into());
            }
            let hints = vec![HintItem::new(key!(Esc), "close")];
            ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            self.pane_areas = layout.pane_areas();
            return (None, prompt_post_flush);
        }
        if let Some(ref viewer) = self.video_viewer {
            use crate::terminal::image::{GraphicsProtocol, detect_graphics_protocol};
            use crate::views::shortcuts_bar::HintItem;
            let overlay_area = Rect {
                x: area.x,
                y: area.y,
                width: area.width,
                height: layout.shortcuts.y.saturating_sub(area.y),
            };
            let mut video_escape_emitted = false;
            if let Some(popup_rect) = crate::render::video_overlay::render_video_overlay(
                buf,
                overlay_area,
                viewer,
                theme.bg_base,
                theme.text_primary,
                theme.gray_dim,
            ) {
                if let Some(esc) = crate::terminal::overlay::volatile_centered(
                    viewer.current_frame_data(),
                    viewer.video_width,
                    viewer.video_height,
                    popup_rect,
                ) {
                    prompt_post_flush = Some(esc.into());
                    video_escape_emitted = true;
                }
                if !video_escape_emitted && detect_graphics_protocol() == GraphicsProtocol::Kitty {
                    let clear = crate::terminal::overlay::clear_kitty();
                    prompt_post_flush = Some(clear.into());
                }
            }
            let play_label = if viewer.playing { "pause" } else { "play" };
            let hints = vec![
                HintItem::new(key!(Esc), "close"),
                HintItem::new(key!(' '), play_label),
                HintItem::new(key!(Left), "back"),
                HintItem::new(key!(Right), "fwd"),
            ];
            ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            self.pane_areas = layout.pane_areas();
            return (None, prompt_post_flush);
        }
        if let Some(gboom) = self.gboom.as_mut() {
            use crate::terminal::image::{GraphicsProtocol, detect_graphics_protocol};
            use crate::views::shortcuts_bar::HintItem;
            let overlay_area = Rect {
                x: area.x,
                y: area.y,
                width: area.width,
                height: layout.shortcuts.y.saturating_sub(area.y),
            };
            let mut gboom_escape_emitted = false;
            let hud = gboom.hud();
            if let Some(popup_rect) = crate::render::gboom_overlay::render_gboom_overlay(
                buf,
                overlay_area,
                &hud,
                theme.bg_base,
                theme.text_primary,
                theme.gray_dim,
            ) {
                gboom.set_mouse_region(
                    popup_rect.x,
                    popup_rect.y,
                    popup_rect.width,
                    popup_rect.height,
                );
                let inner_cols = popup_rect.width.saturating_sub(2);
                let inner_rows = popup_rect.height.saturating_sub(2);
                if inner_cols >= 10 && inner_rows >= 4 {
                    let (px_w, px_h) =
                        crate::gboom::GboomState::frame_size_for_cells(inner_cols, inner_rows);
                    if let Some(png) = gboom.frame_png(px_w, px_h)
                        && let Some(esc) = crate::terminal::overlay::volatile_image(
                            png,
                            inner_cols,
                            inner_rows,
                            popup_rect.x + 1,
                            popup_rect.y + 1,
                        )
                    {
                        prompt_post_flush = Some(esc.into());
                        gboom_escape_emitted = true;
                    }
                }
            } else {
                gboom.clear_mouse_region();
            }
            if !gboom_escape_emitted && detect_graphics_protocol() == GraphicsProtocol::Kitty {
                let clear = crate::terminal::overlay::clear_kitty();
                prompt_post_flush = Some(clear.into());
            }
            let hints = vec![
                HintItem::new(key!(Esc), "quit"),
                HintItem::new(key!(' '), "fire"),
            ];
            ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            self.pane_areas = layout.pane_areas();
            return (None, prompt_post_flush);
        }
        let block_viewer_toast = self.active_toast_message().map(|s| s.to_string());
        if let Some(ref mut viewer) = self.block_viewer {
            use ratatui::style::Modifier;
            use ratatui::widgets::{Block as RBlock, BorderType, Borders, Clear, Widget};
            let overlay_area = Rect {
                x: area.x,
                y: area.y,
                width: area.width,
                height: layout.shortcuts.y.saturating_sub(area.y),
            };
            let entry = if viewer.kind == crate::views::block_viewer::ViewerKind::PlainText {
                Some(crate::scrollback::entry::ScrollbackEntry::new(
                    crate::scrollback::block::RenderBlock::system(String::new()),
                ))
            } else {
                self.scrollback.get_by_id(viewer.entry_id).cloned()
            };
            let Some(entry) = entry else {
                self.block_viewer = None;
                self.pane_areas = layout.pane_areas();
                return (prompt_cursor_pos, prompt_post_flush);
            };
            let popup_w = ((overlay_area.width as f32 * 0.95) as u16)
                .max(60)
                .min(overlay_area.width);
            let popup_h = ((overlay_area.height as f32 * 0.92) as u16)
                .max(12)
                .min(overlay_area.height.saturating_sub(2));
            let h_pad = 2u16;
            let inner_w = popup_w.saturating_sub(2);
            let content_w = inner_w.saturating_sub(h_pad * 2);
            if popup_h.saturating_sub(2) < 3 || inner_w < 10 {
                self.pane_areas = layout.pane_areas();
                return (prompt_cursor_pos, prompt_post_flush);
            }
            let popup_x = overlay_area.x + (overlay_area.width.saturating_sub(popup_w)) / 2;
            let popup_y = overlay_area.y + (overlay_area.height.saturating_sub(popup_h)) / 2;
            let popup_area = Rect::new(popup_x, popup_y, popup_w, popup_h);
            let base_style = Style::default().fg(theme.text_primary).bg(theme.bg_base);
            let border = RBlock::default()
                .borders(Borders::ALL)
                .border_type(BorderType::Rounded)
                .border_style(Style::default().fg(theme.gray_dim))
                .style(base_style);
            let inner = border.inner(popup_area);
            if inner.height < 3 || inner.width < 10 {
                self.pane_areas = layout.pane_areas();
                return (prompt_cursor_pos, prompt_post_flush);
            }
            crate::views::file_search::line_viewer::dim_area(buf, overlay_area, theme.bg_base, 0.5);
            Clear.render(popup_area, buf);
            buf.set_style(popup_area, base_style);
            border.render(popup_area, buf);
            let close_chars: &[char] = &['[', 'x', ']'];
            let close_w_px = close_chars.len() as u16;
            let close_x = inner.x + inner.width.saturating_sub(close_w_px + 1);
            let close_y = inner.y;
            let close_rect = Rect::new(close_x, close_y, close_w_px, 1);
            for (i, &ch) in close_chars.iter().enumerate() {
                if let Some(cell) = buf.cell_mut((close_x + i as u16, close_y)) {
                    cell.set_char(ch);
                    let style = if viewer.modal.close_hovered {
                        Style::default()
                            .fg(theme.text_primary)
                            .add_modifier(Modifier::BOLD)
                    } else {
                        Style::default().fg(theme.gray_dim)
                    };
                    cell.set_style(style);
                }
            }
            viewer.modal.close_button_rect = Some(close_rect);
            viewer.modal.popup_area = Some(popup_area);
            let preamble_ctx = crate::scrollback::types::BlockContext {
                mode: crate::scrollback::types::DisplayMode::Expanded,
                is_running: entry.is_running,
                width: content_w,
                raw: entry.raw,
                max_lines: None,
                appearance: appearance.clone(),
                is_selected: false,
                cwd: Some(self.session.cwd.clone()),
            };
            let preamble = entry.block.preamble(&preamble_ctx);
            let mut prepend_lines: Vec<ratatui::text::Line<'static>> = preamble
                .as_ref()
                .map(|text| {
                    text.lines
                        .iter()
                        .flat_map(|line| {
                            crate::render::wrapping::wrap_header_flush(
                                line.clone(),
                                content_w as usize,
                                0,
                            )
                        })
                        .collect()
                })
                .unwrap_or_default();
            if !prepend_lines.is_empty() {
                prepend_lines.push(ratatui::text::Line::from(""));
            }
            let content_x = inner.x + h_pad;
            let content_top = inner.y + 1;
            let content_height = inner.height.saturating_sub(1);
            let content_area = Rect {
                x: content_x,
                y: content_top,
                width: content_w,
                height: content_height,
            };
            viewer.render_content(content_area, buf, &entry, true, &prepend_lines);
            viewer.render_text_drag_overlay(buf);
            let has_input_bar =
                viewer.list_state.input_mode().is_some() || viewer.list_state.matcher().is_some();
            let in_visual = viewer.list_state.visual_mode;
            if (has_input_bar || in_visual) && content_area.height > 2 {
                let div_y = content_area.y + content_area.height - 2;
                for x in inner.x..inner.x + inner.width {
                    if let Some(cell) = buf.cell_mut((x, div_y)) {
                        cell.reset();
                        cell.set_char('\u{2500}');
                        cell.fg = theme.gray_dim;
                        cell.bg = theme.bg_base;
                    }
                }
                if in_visual && !has_input_bar {
                    let status_y = div_y + 1;
                    for x in inner.x..inner.x + inner.width {
                        if let Some(cell) = buf.cell_mut((x, status_y)) {
                            cell.reset();
                            cell.set_char(' ');
                            cell.fg = theme.text_secondary;
                            cell.bg = theme.bg_base;
                        }
                    }
                    let n = viewer.list_state.copy_range().map(|r| r.len()).unwrap_or(1);
                    let s = if n == 1 { "" } else { "s" };
                    let status = format!("Selected: {n} line{s}");
                    let status_style = Style::default().fg(theme.text_secondary).bg(theme.bg_base);
                    buf.set_string(content_x, status_y, &status, status_style);
                }
            }
            if let Some(ref msg) = block_viewer_toast {
                let toast_text = format!(" {msg} ");
                let w = toast_text.len() as u16;
                if popup_area.height > 2 && popup_area.width > w + 2 {
                    let tx = popup_area.right().saturating_sub(w + 2);
                    let ty = popup_area.bottom().saturating_sub(2);
                    for (i, ch) in toast_text.chars().enumerate() {
                        if let Some(cell) = buf.cell_mut((tx + i as u16, ty)) {
                            cell.set_char(ch);
                            cell.fg = theme.accent_user;
                            cell.bg = theme.bg_base;
                            cell.modifier = ratatui::prelude::Modifier::BOLD;
                        }
                    }
                }
            }
            let hints = viewer.shortcuts_hints();
            ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            self.pane_areas = layout.pane_areas();
            return (prompt_cursor_pos, prompt_post_flush);
        }
        if let Some(ref mut modal_state) = self.agents_modal {
            let overlay_area = Rect {
                x: area.x,
                y: area.y,
                width: area.width,
                height: layout.shortcuts.y.saturating_sub(area.y).saturating_sub(1),
            };
            let compact = self.scrollback.appearance().prompt.compact;
            let theme = Theme::current();
            crate::views::agents_modal::render_agents_modal(
                buf,
                overlay_area,
                modal_state,
                compact,
                &theme,
            );
            if let Some(ref mut detail) = self.persona_detail {
                crate::views::persona_detail::render_persona_detail(
                    buf,
                    overlay_area,
                    detail,
                    &theme,
                    compact,
                );
            }
            self.pane_areas = layout.pane_areas();
            return (None, crate::terminal::overlay::clear().map(Into::into));
        }
        if let Some(ref mut modal_state) = self.extensions_modal {
            use crate::views::extensions_modal::render_extensions_modal;
            use crate::views::shortcuts_bar::HintItem;
            let is_fullscreen = matches!(
                modal_state.picker_state.mode,
                crate::views::picker::PickerMode::FullScreen
            );
            let overlay_area = if is_fullscreen {
                area
            } else {
                Rect {
                    x: area.x,
                    y: area.y,
                    width: area.width,
                    height: layout.shortcuts.y.saturating_sub(area.y).saturating_sub(1),
                }
            };
            let compact = self.scrollback.appearance().prompt.compact;
            let tick = self.scrollback.animation_tick();
            render_extensions_modal(
                buf,
                overlay_area,
                modal_state,
                Some(layout.shortcuts),
                compact,
                tick,
            );
            if modal_state.input.is_some() {
                let hints = vec![
                    HintItem::new(key!(Enter), "submit"),
                    HintItem::new(key!(Esc), "cancel"),
                ];
                ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            } else if modal_state.pending_action.is_some() {
                let hints = vec![HintItem::new(key!(Esc), "dismiss")];
                ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            } else if modal_state.picker_state.search_active {
                let hints = vec![
                    HintItem::new(key!(Esc), "clear search"),
                    HintItem::new(key!(Enter), "keep filter"),
                ];
                ShortcutsBar::new(&hints).render(layout.shortcuts, buf);
            }
            self.pane_areas = layout.pane_areas();
            return (None, crate::terminal::overlay::clear().map(Into::into));
        }
        let dropdown_active = self.slash_dropdown_items_area.is_some()
            || self.dropdown_items_area.is_some()
            || self.completion_dropdown_items_area.is_some()
            || self.history_dropdown_area.is_some();
        let placements = if dropdown_active {
            &[][..]
        } else {
            &scrollback_inline_media[..]
        };
        self.inline_media_hits = InlineMediaHitAreas::default();
        if !placements.is_empty() {
            let mut all_escapes = String::new();
            let mut this_frame_ids: HashSet<u32> = HashSet::new();
            for placement in placements {
                let path = &placement.info.path;
                if let Some(fp_rect) = placement.filepath_screen_rect {
                    self.inline_media_hits
                        .filepath_areas
                        .push((fp_rect, path.clone()));
                }
                if let Some(open_rect) = placement.open_button_screen_rect {
                    self.inline_media_hits
                        .open_buttons
                        .push((open_rect, path.clone()));
                    continue;
                }
                if !crate::terminal::image::scrollback_inline_overlay_active() {
                    continue;
                }
                if !self.inline_media_cache.contains_key(path) && placement.screen_rect.height >= 1
                {
                    let rect = placement.screen_rect;
                    let center_x = |len: usize| rect.x + rect.width.saturating_sub(len as u16) / 2;
                    if placement.info.is_video && !crate::inline_media_ffmpeg::ffmpeg_available() {
                        use crate::inline_media_ffmpeg::{FFMPEG_HINT_TEXT, ffmpeg_install_cmd};
                        let warn = Style::default().fg(theme.warning);
                        buf.set_string_safe(
                            center_x(FFMPEG_HINT_TEXT.len()),
                            rect.y,
                            FFMPEG_HINT_TEXT,
                            warn,
                        );
                        if let Some(cmd) = ffmpeg_install_cmd() {
                            let dim = Style::default().fg(theme.gray_dim);
                            buf.set_string_safe(center_x(cmd.len()), rect.y + 1, cmd, dim);
                        }
                    } else {
                        let spinner_frames = crate::glyphs::braille_spinner_frames();
                        let tick = self.scrollback.current_tick() as usize;
                        let spinner = spinner_frames[tick % spinner_frames.len()];
                        let label = format!("{spinner} Loading...");
                        let cy = rect.y + rect.height / 2;
                        buf.set_string_safe(
                            center_x(label.len()),
                            cy,
                            &label,
                            Style::default().fg(theme.gray_dim),
                        );
                    }
                }
                if let Some(esc) = self.build_inline_media_escapes(placement) {
                    all_escapes.push_str(&esc);
                    if let Some(&id) = self.inline_media_ids.get(path) {
                        this_frame_ids.insert(id);
                    }
                    let rect = placement.screen_rect;
                    if !placement.has_button_row {
                        continue;
                    }
                    let button_y = rect.y + rect.height + 1;
                    let sb_area = self.pane_areas.scrollback;
                    let button_visible =
                        button_y >= sb_area.y && button_y < sb_area.y + sb_area.height;
                    if placement.info.is_video {
                        self.inline_media_hits
                            .video_play_areas
                            .push((rect, path.clone()));
                        if button_visible {
                            let is_playing = matches!(
                                self.inline_video, Some(ref vid) if vid.path == * path && !
                                vid.finished
                            );
                            let play_label: String = if is_playing {
                                let vid = self.inline_video.as_ref().unwrap();
                                let dur_s = vid.frames.len() as f64 / vid.fps;
                                let pos_s = vid.current_frame as f64 / vid.fps;
                                format!(
                                    "{}:{:02} / {}:{:02}",
                                    pos_s as u32 / 60,
                                    pos_s as u32 % 60,
                                    dur_s as u32 / 60,
                                    dur_s as u32 % 60,
                                )
                            } else {
                                "[Play]".to_string()
                            };
                            let open_label = "[Open]";
                            let gap = 3u16;
                            let total = play_label.len() as u16 + gap + open_label.len() as u16;
                            let start_x = rect.x + rect.width.saturating_sub(total) / 2;
                            buf.set_string_safe(
                                start_x,
                                button_y,
                                &play_label,
                                Style::default().fg(theme.gray),
                            );
                            if !is_playing {
                                self.inline_media_hits.play_buttons.push((
                                    Rect {
                                        x: start_x,
                                        y: button_y,
                                        width: play_label.len() as u16,
                                        height: 1,
                                    },
                                    path.clone(),
                                ));
                            }
                            let open_x = start_x + play_label.len() as u16 + gap;
                            buf.set_string_safe(
                                open_x,
                                button_y,
                                open_label,
                                Style::default().fg(theme.gray),
                            );
                            self.inline_media_hits.open_buttons.push((
                                Rect {
                                    x: open_x,
                                    y: button_y,
                                    width: open_label.len() as u16,
                                    height: 1,
                                },
                                path.clone(),
                            ));
                        }
                    } else {
                        self.inline_media_hits
                            .media_areas
                            .push((rect, path.clone()));
                        if button_visible {
                            let open_label = "[Open]";
                            let copy_label = "[Copy]";
                            let gap = 3u16;
                            let total = open_label.len() as u16 + gap + copy_label.len() as u16;
                            let start_x = rect.x + rect.width.saturating_sub(total) / 2;
                            buf.set_string_safe(
                                start_x,
                                button_y,
                                open_label,
                                Style::default().fg(theme.gray),
                            );
                            self.inline_media_hits.open_buttons.push((
                                Rect {
                                    x: start_x,
                                    y: button_y,
                                    width: open_label.len() as u16,
                                    height: 1,
                                },
                                path.clone(),
                            ));
                            let copy_x = start_x + open_label.len() as u16 + gap;
                            buf.set_string_safe(
                                copy_x,
                                button_y,
                                copy_label,
                                Style::default().fg(theme.gray),
                            );
                            self.inline_media_hits.copy_image_buttons.push((
                                Rect {
                                    x: copy_x,
                                    y: button_y,
                                    width: copy_label.len() as u16,
                                    height: 1,
                                },
                                path.clone(),
                            ));
                        }
                    }
                }
            }
            for &old_id in &self.last_placed_ids {
                if !this_frame_ids.contains(&old_id) {
                    all_escapes.push_str(&crate::terminal::image::clear_kitty_image(old_id));
                    self.inline_media_ids.retain(|_, &mut v| v != old_id);
                    self.inline_media_iterm_emitted
                        .retain(|p, _| self.inline_media_ids.contains_key(p));
                }
            }
            self.last_placed_ids = this_frame_ids;
            if !all_escapes.is_empty() {
                self.inline_media_active = true;
                match prompt_post_flush.as_mut() {
                    Some(existing) => existing.append_plain(&all_escapes),
                    None => {
                        prompt_post_flush =
                            Some(crate::terminal::overlay::PostFlush::plain(all_escapes));
                    }
                }
            } else if self.inline_media_active {
                self.inline_media_active = false;
                let mut clear_esc = String::new();
                for &id in self.inline_media_ids.values() {
                    clear_esc.push_str(&crate::terminal::image::clear_kitty_image(id));
                }
                self.inline_media_ids.clear();
                self.inline_media_iterm_emitted.clear();
                self.last_placed_ids.clear();
                if !clear_esc.is_empty() {
                    match prompt_post_flush.as_mut() {
                        Some(existing) => existing.append_plain(&clear_esc),
                        None => {
                            prompt_post_flush =
                                Some(crate::terminal::overlay::PostFlush::plain(clear_esc));
                        }
                    }
                }
            }
        } else if self.inline_media_active {
            self.inline_media_active = false;
            self.stop_inline_playback();
            let mut clear_esc = String::new();
            for &id in self.inline_media_ids.values() {
                clear_esc.push_str(&crate::terminal::image::clear_kitty_image(id));
            }
            self.inline_media_iterm_emitted.clear();
            self.inline_media_ids.clear();
            self.last_placed_ids.clear();
            if !clear_esc.is_empty() {
                match prompt_post_flush.as_mut() {
                    Some(existing) => existing.append_plain(&clear_esc),
                    None => {
                        prompt_post_flush =
                            Some(crate::terminal::overlay::PostFlush::plain(clear_esc));
                    }
                }
            }
        }
        if !dropdown_active {
            self.paint_diagram_affordances(buf, scrollback_diagram_affordances, &theme);
        }
        if !self.inline_media_active
            && prompt_post_flush.is_none()
            && crate::terminal::image::detect_graphics_protocol()
                != crate::terminal::image::GraphicsProtocol::None
        {
            let clear = crate::terminal::overlay::clear_kitty();
            prompt_post_flush = Some(clear.into());
        }
        if self.show_goal_detail
            && let Some(ref goal) = self.goal_state
        {
            let todos = self.todo.todos();
            let overlay_rect = crate::views::goal_detail::goal_detail_area(area, goal, todos);
            let tick = self.tasks.tick_count() as usize;
            let active_subagent_tokens: u64 = self
                .subagent_sessions
                .values()
                .filter(|s| !s.finished)
                .filter_map(|s| s.tokens_used)
                .sum();
            let close_rect = crate::views::goal_detail::render_goal_detail(
                buf,
                overlay_rect,
                goal,
                todos,
                tick,
                self.context_state.as_ref().map(|c| c.used),
                active_subagent_tokens,
                self.hit_goal_close.hovered,
            );
            self.hit_goal_close.rect = close_rect;
            self.frame_occluder_rects.push(overlay_rect);
        }
        self.pane_areas = layout.pane_areas();
        {
            let route = crate::hyperlink_route::hyperlink_route();
            if route.emit_osc8 {
                let occluders = &self.frame_occluder_rects;
                let emit_id = route.emit_id;
                *link_spans_out = self
                    .last_link_overlay
                    .links()
                    .iter()
                    .filter(|link| {
                        !occluders.iter().any(|r| {
                            link.screen_row >= r.y
                                && link.screen_row < r.y.saturating_add(r.height)
                                && link.col_start < r.x.saturating_add(r.width)
                                && r.x < link.col_end
                        })
                    })
                    .map(|link| kigi_ratatui_inline::LinkSpan {
                        row: link.screen_row,
                        col_start: link.col_start,
                        col_end: link.col_end,
                        url: link.url.clone(),
                        id: if emit_id { link.id } else { None },
                    })
                    .collect();
            }
        }
        let on_link = self.hovered_link_idx.is_some();
        if supports_osc22() && on_link != self.last_pointer_on_link {
            self.last_pointer_on_link = on_link;
            use crossterm::Command;
            let mut seq = String::new();
            if on_link {
                let _ = crate::terminal::SetPointerCursor.write_ansi(&mut seq);
            } else {
                let _ = crate::terminal::SetDefaultCursor.write_ansi(&mut seq);
            }
            match prompt_post_flush {
                Some(ref mut existing) => existing.append_plain(&seq),
                None => {
                    prompt_post_flush = Some(crate::terminal::overlay::PostFlush::plain(seq));
                }
            }
        }
        let cursor = if self.inline_edit.is_some() {
            inline_edit_cursor
        } else {
            prompt_cursor_pos
        };
        (cursor, prompt_post_flush)
    }
}
#[cfg(test)]
mod selection_state_tests {
    use super::super::test_fixtures::make_agent;
    use super::*;
    use crate::scrollback::text_selection::ResolvedSelectableLine;
    use crate::scrollback::types::SelectionBoundary;
    use std::sync::Arc;
    #[test]
    fn frame_reset_clears_model_and_companion_together() {
        let mut model = ResolvedSelectionModel::default();
        let line = ResolvedSelectableLine {
            entry_idx: 0,
            range_id: 0,
            block_line_idx: 0,
            screen_y: 0,
            screen_x: 0,
            selectable_cols: 0..3,
            text: "foo".to_string(),
            joiner_to_previous: None,
        };
        let mut boundaries = ResolvedSelectionBoundaries::default();
        boundaries.push(
            &line,
            Arc::new(SelectionBoundary::new("   ".to_string(), String::new())),
        );
        model.push_line(line);
        let mut agent = make_agent();
        agent.update_scrollback_selection_state(model, boundaries);
        agent.clear_scrollback_selection_state();
        assert!(agent.last_scrollback_selection_model.ranges.is_empty());
        assert!(agent.last_scrollback_selection_boundaries.is_empty());
    }
}
#[cfg(test)]
mod overlay_post_flush_tests {
    use super::super::test_fixtures::make_agent;
    use crate::actions::ActionRegistry;
    use crate::app::bundle::BundleState;
    use crate::scrollback::render::ScratchBuffer;
    use ratatui::buffer::Buffer;
    use ratatui::layout::Rect;
    fn draw(agent: &mut super::AgentView) -> Option<crate::terminal::overlay::PostFlush> {
        let area = Rect::new(0, 0, 100, 40);
        let mut buf = Buffer::empty(area);
        let mut scratch = ScratchBuffer::new();
        agent
            .draw(
                area,
                &mut buf,
                &ActionRegistry::defaults(),
                &mut scratch,
                None,
                false,
                0,
                None,
                &BundleState::default(),
                false,
                &mut Vec::new(),
            )
            .1
    }
    fn seed_static_owner(owner_id: u64) {
        let _ = crate::terminal::overlay::static_image(&png(), 20, 10, 0, 0, owner_id)
            .unwrap()
            .commit();
    }
    fn png() -> [u8; 8] {
        [0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1a, b'\n']
    }
    #[test]
    fn fullscreen_subagent_propagates_child_clear_to_emitter() {
        let _guard = crate::terminal::image::set_protocol_for_test(
            crate::terminal::image::GraphicsProtocol::Kitty,
        );
        crate::terminal::overlay::reset_owner();
        seed_static_owner(41);
        let mut parent = make_agent();
        parent
            .subagent_views
            .insert("child".into(), Box::new(make_agent()));
        parent.active_subagent = Some("child".into());
        let post_flush = draw(&mut parent).expect("child clear propagates");
        assert!(post_flush.as_str().contains("a=d"));
        let before_emit = crate::terminal::overlay::static_image(&png(), 20, 10, 0, 0, 41).unwrap();
        assert!(!before_emit.as_str().contains("a=t"));
        post_flush.write_to(&mut Vec::new()).unwrap();
        let after_emit = crate::terminal::overlay::static_image(&png(), 20, 10, 0, 0, 41).unwrap();
        assert!(after_emit.as_str().contains("a=t"));
    }
    #[test]
    fn active_modal_returns_clear_without_committing_discarded_state() {
        let _guard = crate::terminal::image::set_protocol_for_test(
            crate::terminal::image::GraphicsProtocol::Kitty,
        );
        crate::terminal::overlay::reset_owner();
        seed_static_owner(42);
        let mut agent = make_agent();
        agent.active_modal = Some(crate::views::modal::ActiveModal::CommandPalette {
            entries: Vec::new(),
            state: crate::views::picker::PickerState::default(),
            window: crate::views::modal_window::ModalWindowState::new(),
        });
        let post_flush = draw(&mut agent).expect("modal clear returned");
        assert!(post_flush.as_str().contains("a=d"));
        let before_emit = crate::terminal::overlay::static_image(&png(), 20, 10, 0, 0, 42).unwrap();
        assert!(!before_emit.as_str().contains("a=t"));
        post_flush.write_to(&mut Vec::new()).unwrap();
        let after_emit = crate::terminal::overlay::static_image(&png(), 20, 10, 0, 0, 42).unwrap();
        assert!(after_emit.as_str().contains("a=t"));
    }
}
