Codex Handbook
tui/src/resume_picker.rs 6338 lines
use std::collections::HashMap;use std::collections::HashSet;use std::path::Path;use std::path::PathBuf;use std::sync::Arc;use crate::app_server_session::AppServerSession;use crate::clipboard_paste::normalize_pasted_search_query;use crate::color::blend;use crate::color::is_light;use crate::git_action_directives::parse_assistant_markdown;use crate::key_hint::KeyBindingListExt;use crate::key_hint::is_plain_text_key_event;use crate::keymap::ListKeymap;use crate::keymap::PagerKeymap;use crate::keymap::RuntimeKeymap;use crate::legacy_core::config::Config;use crate::legacy_core::config::edit::ConfigEditsBuilder;use crate::markdown::append_markdown;use crate::pager_overlay::Overlay;use crate::session_resume::resolve_session_thread_id;use crate::status::format_directory_display;use crate::terminal_palette::best_color;use crate::terminal_palette::default_bg;use crate::text_formatting::truncate_text;use crate::thread_transcript::RawReasoningVisibility;use crate::thread_transcript::TranscriptCells;use crate::thread_transcript::load_session_transcript;use crate::tui::FrameRequester;use crate::tui::Tui;use crate::tui::TuiEvent;use crate::wrapping::RtOptions;use crate::wrapping::adaptive_wrap_lines;use chrono::DateTime;use chrono::Utc;use codex_app_server_protocol::Thread;use codex_app_server_protocol::ThreadItem;use codex_app_server_protocol::ThreadListCwdFilter;use codex_app_server_protocol::ThreadListParams;use codex_app_server_protocol::ThreadSortKey;use codex_config::types::SessionPickerViewMode;use codex_protocol::ThreadId;use codex_utils_path as path_utils;use color_eyre::eyre::Result;use crossterm::event::KeyCode;use crossterm::event::KeyEvent;use crossterm::event::KeyEventKind;use crossterm::event::KeyModifiers;use ratatui::layout::Constraint;use ratatui::layout::Layout;use ratatui::layout::Rect;use ratatui::style::Color;use ratatui::style::Style;use ratatui::style::Styled as _;use ratatui::style::Stylize as _;use ratatui::text::Line;use ratatui::text::Span;use ratatui::widgets::Clear;use ratatui::widgets::Widget;use tokio::sync::mpsc;use tokio_stream::StreamExt;use tokio_stream::wrappers::UnboundedReceiverStream;use tracing::warn;use unicode_width::UnicodeWidthStr;const PAGE_SIZE: usize = 25;const LOAD_NEAR_THRESHOLD: usize = 5;const SESSION_META_INDENT_WIDTH: usize = 2;const SESSION_META_DATE_WIDTH: usize = 12;const SESSION_META_FIELD_GAP_WIDTH: usize = 2;const SESSION_META_MIN_CWD_WIDTH: usize = 30;const SESSION_META_MAX_CWD_WIDTH: usize = 72;const SESSION_META_BRANCH_ICON: &str = "";const SESSION_META_CWD_ICON: &str = "⌁";const FOOTER_COMPACT_BREAKPOINT: u16 = 120;const FOOTER_HINT_LEFT_PADDING: usize = 1;const FOOTER_HINT_GAP: usize = 3;const PICKER_CHROME_HEIGHT: u16 = 8;const PICKER_LIST_HORIZONTAL_INSET: u16 = 4;#[derive(Debug, Clone)]pub struct SessionTarget {    pub path: Option<PathBuf>,    pub thread_id: ThreadId,}impl SessionTarget {    pub fn display_label(&self) -> String {        self.path            .as_ref()            .map(|path| path.display().to_string())            .unwrap_or_else(|| format!("thread {}", self.thread_id))    }}#[derive(Debug, Clone)]pub enum SessionSelection {    StartFresh,    Resume(SessionTarget),    Fork(SessionTarget),    Exit,}#[derive(Clone, Copy, Debug)]pub enum SessionPickerAction {    Resume,    Fork,}#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum SessionPickerLaunchContext {    Startup,    ExistingSession,}impl SessionPickerAction {    fn title(self) -> &'static str {        match self {            SessionPickerAction::Resume => "Resume a previous session",            SessionPickerAction::Fork => "Fork a previous session",        }    }    fn action_label(self) -> &'static str {        match self {            SessionPickerAction::Resume => "resume",            SessionPickerAction::Fork => "fork",        }    }    fn selection(self, path: Option<PathBuf>, thread_id: ThreadId) -> SessionSelection {        let target_session = SessionTarget { path, thread_id };        match self {            SessionPickerAction::Resume => SessionSelection::Resume(target_session),            SessionPickerAction::Fork => SessionSelection::Fork(target_session),        }    }}#[derive(Clone)]struct PageLoadRequest {    cursor: Option<PageCursor>,    request_token: usize,    search_token: Option<usize>,    cwd_filter: Option<PathBuf>,    provider_filter: ProviderFilter,    sort_key: ThreadSortKey,}enum PickerLoadRequest {    Page(PageLoadRequest),    Preview { thread_id: ThreadId },    Transcript { thread_id: ThreadId },}#[derive(Clone)]enum ProviderFilter {    Any,    MatchDefault(String),}#[derive(Clone, Copy, Debug, PartialEq, Eq)]enum SessionFilterMode {    Cwd,    All,}impl SessionFilterMode {    fn from_show_all(show_all: bool, filter_cwd: Option<&Path>) -> Self {        if show_all || filter_cwd.is_none() {            Self::All        } else {            Self::Cwd        }    }    fn toggle(self, filter_cwd: Option<&Path>) -> Self {        match self {            Self::Cwd => Self::All,            Self::All if filter_cwd.is_some() => Self::Cwd,            Self::All => Self::All,        }    }}#[derive(Clone, Copy, Debug, PartialEq, Eq)]enum ToolbarControl {    Filter,    Sort,}impl ToolbarControl {    fn previous(self) -> Self {        match self {            Self::Filter => Self::Sort,            Self::Sort => Self::Filter,        }    }    fn next(self) -> Self {        match self {            Self::Filter => Self::Sort,            Self::Sort => Self::Filter,        }    }}#[derive(Clone, Copy, Debug, PartialEq, Eq)]enum SessionListDensity {    Comfortable,    Dense,}impl SessionListDensity {    fn toggle(self) -> Self {        match self {            Self::Comfortable => Self::Dense,            Self::Dense => Self::Comfortable,        }    }}impl From<SessionPickerViewMode> for SessionListDensity {    fn from(mode: SessionPickerViewMode) -> Self {        match mode {            SessionPickerViewMode::Comfortable => Self::Comfortable,            SessionPickerViewMode::Dense => Self::Dense,        }    }}impl From<SessionListDensity> for SessionPickerViewMode {    fn from(density: SessionListDensity) -> Self {        match density {            SessionListDensity::Comfortable => Self::Comfortable,            SessionListDensity::Dense => Self::Dense,        }    }}type PickerLoader = Arc<dyn Fn(PickerLoadRequest) + Send + Sync>;enum BackgroundEvent {    Page {        request_token: usize,        search_token: Option<usize>,        page: std::io::Result<PickerPage>,    },    Preview {        thread_id: ThreadId,        preview: std::io::Result<Vec<TranscriptPreviewLine>>,    },    Transcript {        thread_id: ThreadId,        transcript: std::io::Result<TranscriptCells>,    },}#[derive(Clone)]enum PageCursor {    AppServer(String),}struct PickerPage {    rows: Vec<Row>,    next_cursor: Option<PageCursor>,    num_scanned_files: usize,    reached_scan_cap: bool,}#[derive(Clone)]struct SessionPickerViewPersistence {    codex_home: PathBuf,}struct SessionPickerRunOptions {    show_all: bool,    filter_cwd: Option<PathBuf>,    local_filter_cwd: Option<PathBuf>,    action: SessionPickerAction,    launch_context: SessionPickerLaunchContext,    provider_filter: ProviderFilter,    initial_density: SessionListDensity,    view_persistence: Option<SessionPickerViewPersistence>,    pager_keymap: PagerKeymap,    list_keymap: ListKeymap,}/// Interactive session picker that lists app-server threads with simple search,/// lazy transcript previews, and pagination.////// Sessions render as compact multi-line records with stable metadata first and/// the conversation preview last. Users can focus Sort/Filter toolbar controls/// with Tab, change the focused control with the arrow keys, and expand the/// selected session with Ctrl+E to load recent transcript context on demand.////// Sessions are loaded on-demand via cursor-based pagination. The backend/// `thread/list` API returns pages ordered by the selected sort key, and the/// picker deduplicates across pages to handle overlapping windows when new/// sessions appear during pagination.////// Filtering happens in two layers:/// 1. Provider, source, and eligible working-directory filtering at the backend./// 2. Typed search filtering over loaded rows in the picker.pub async fn run_resume_picker_with_app_server(    tui: &mut Tui,    config: &Config,    show_all: bool,    include_non_interactive: bool,    app_server: AppServerSession,) -> Result<SessionSelection> {    run_resume_picker_with_launch_context(        tui,        config,        show_all,        include_non_interactive,        app_server,        SessionPickerLaunchContext::Startup,    )    .await}pub async fn run_resume_picker_from_existing_session_with_app_server(    tui: &mut Tui,    config: &Config,    show_all: bool,    include_non_interactive: bool,    app_server: AppServerSession,) -> Result<SessionSelection> {    run_resume_picker_with_launch_context(        tui,        config,        show_all,        include_non_interactive,        app_server,        SessionPickerLaunchContext::ExistingSession,    )    .await}async fn run_resume_picker_with_launch_context(    tui: &mut Tui,    config: &Config,    show_all: bool,    include_non_interactive: bool,    app_server: AppServerSession,    launch_context: SessionPickerLaunchContext,) -> Result<SessionSelection> {    let (bg_tx, bg_rx) = mpsc::unbounded_channel();    let uses_remote_workspace = app_server.uses_remote_workspace();    let cwd_filter = picker_cwd_filter(        config.cwd.as_path(),        /*show_all*/ false,        uses_remote_workspace,        app_server.remote_cwd_override(),    );    let local_filter_cwd = local_picker_cwd_filter(&cwd_filter, uses_remote_workspace);    let provider_filter = picker_provider_filter(config, uses_remote_workspace);    let runtime_keymap = picker_runtime_keymap(config)?;    let options = SessionPickerRunOptions {        show_all,        filter_cwd: cwd_filter,        local_filter_cwd,        action: SessionPickerAction::Resume,        launch_context,        provider_filter,        initial_density: SessionListDensity::from(config.tui_session_picker_view),        view_persistence: Some(SessionPickerViewPersistence {            codex_home: config.codex_home.to_path_buf(),        }),        pager_keymap: runtime_keymap.pager,        list_keymap: runtime_keymap.list,    };    run_session_picker_with_loader(        tui,        options,        spawn_app_server_page_loader(            app_server,            include_non_interactive,            raw_reasoning_visibility(config),            bg_tx,        ),        bg_rx,    )    .await}pub async fn run_fork_picker_with_app_server(    tui: &mut Tui,    config: &Config,    show_all: bool,    app_server: AppServerSession,) -> Result<SessionSelection> {    let (bg_tx, bg_rx) = mpsc::unbounded_channel();    let uses_remote_workspace = app_server.uses_remote_workspace();    let cwd_filter = picker_cwd_filter(        config.cwd.as_path(),        /*show_all*/ false,        uses_remote_workspace,        app_server.remote_cwd_override(),    );    let local_filter_cwd = local_picker_cwd_filter(&cwd_filter, uses_remote_workspace);    let provider_filter = picker_provider_filter(config, uses_remote_workspace);    let runtime_keymap = picker_runtime_keymap(config)?;    let options = SessionPickerRunOptions {        show_all,        filter_cwd: cwd_filter,        local_filter_cwd,        action: SessionPickerAction::Fork,        launch_context: SessionPickerLaunchContext::Startup,        provider_filter,        initial_density: SessionListDensity::from(config.tui_session_picker_view),        view_persistence: Some(SessionPickerViewPersistence {            codex_home: config.codex_home.to_path_buf(),        }),        pager_keymap: runtime_keymap.pager,        list_keymap: runtime_keymap.list,    };    run_session_picker_with_loader(        tui,        options,        spawn_app_server_page_loader(            app_server,            /*include_non_interactive*/ false,            raw_reasoning_visibility(config),            bg_tx,        ),        bg_rx,    )    .await}async fn run_session_picker_with_loader(    tui: &mut Tui,    options: SessionPickerRunOptions,    picker_loader: PickerLoader,    bg_rx: mpsc::UnboundedReceiver<BackgroundEvent>,) -> Result<SessionSelection> {    let alt = AltScreenGuard::enter(tui);    let mut state = PickerState::new(        alt.tui.frame_requester(),        picker_loader,        options.provider_filter,        options.show_all,        options.filter_cwd,        options.action,    );    state.local_filter_cwd = options.local_filter_cwd;    state.density = options.initial_density;    state.view_persistence = options.view_persistence;    state.pager_keymap = options.pager_keymap;    state.list_keymap = options.list_keymap;    state.launch_context = options.launch_context;    state.start_initial_load();    state.request_frame();    let mut tui_events = alt.tui.event_stream().fuse();    let mut background_events = UnboundedReceiverStream::new(bg_rx).fuse();    loop {        tokio::select! {            Some(ev) = tui_events.next() => {                if state.overlay.is_some() {                    state.handle_overlay_event(alt.tui, ev)?;                    continue;                }                match ev {                    TuiEvent::Key(key) => {                        if matches!(key.kind, KeyEventKind::Release) {                            continue;                        }                        if let Some(sel) = state.handle_key(key).await? {                            return Ok(sel);                        }                    }                    TuiEvent::Paste(pasted) => {                        state.handle_paste(pasted);                    }                    TuiEvent::Draw | TuiEvent::Resize => {                        if let Ok(size) = alt.tui.terminal.size() {                            let list_height =                                size.height.saturating_sub(PICKER_CHROME_HEIGHT) as usize;                            state.update_viewport(list_height, list_viewport_width(size.width));                            state.ensure_minimum_rows_for_view(list_height);                        }                        draw_picker(alt.tui, &state)?;                        if state.note_transcript_loading_frame_drawn() {                            state.open_pending_transcript_if_ready();                        }                    }                }            }            Some(event) = background_events.next() => {                state.handle_background_event(event).await?;            }            else => break,        }    }    // Fallback – treat as cancel/new    Ok(SessionSelection::StartFresh)}fn raw_reasoning_visibility(config: &Config) -> RawReasoningVisibility {    if config.show_raw_agent_reasoning {        RawReasoningVisibility::Visible    } else {        RawReasoningVisibility::Hidden    }}fn local_picker_cwd_filter(    cwd_filter: &Option<PathBuf>,    uses_remote_workspace: bool,) -> Option<PathBuf> {    if uses_remote_workspace {        None    } else {        cwd_filter.clone()    }}fn picker_provider_filter(config: &Config, uses_remote_workspace: bool) -> ProviderFilter {    if uses_remote_workspace {        ProviderFilter::Any    } else {        ProviderFilter::MatchDefault(config.model_provider_id.to_string())    }}fn picker_runtime_keymap(config: &Config) -> Result<RuntimeKeymap> {    RuntimeKeymap::from_config(&config.tui_keymap)        .map_err(|err| color_eyre::eyre::eyre!("invalid keymap configuration: {err}"))}fn picker_cwd_filter(    config_cwd: &Path,    show_all: bool,    uses_remote_workspace: bool,    remote_cwd_override: Option<&Path>,) -> Option<PathBuf> {    if show_all {        None    } else if uses_remote_workspace {        remote_cwd_override.map(Path::to_path_buf)    } else {        Some(config_cwd.to_path_buf())    }}fn spawn_app_server_page_loader(    app_server: AppServerSession,    include_non_interactive: bool,    raw_reasoning_visibility: RawReasoningVisibility,    bg_tx: mpsc::UnboundedSender<BackgroundEvent>,) -> PickerLoader {    let (request_tx, mut request_rx) = mpsc::unbounded_channel::<PickerLoadRequest>();    tokio::spawn(async move {        let mut app_server = app_server;        while let Some(request) = request_rx.recv().await {            match request {                PickerLoadRequest::Page(request) => {                    let cursor = request.cursor.map(|PageCursor::AppServer(cursor)| cursor);                    let page = load_app_server_page(                        &mut app_server,                        cursor,                        request.cwd_filter.as_deref(),                        request.provider_filter,                        request.sort_key,                        include_non_interactive,                    )                    .await;                    let _ = bg_tx.send(BackgroundEvent::Page {                        request_token: request.request_token,                        search_token: request.search_token,                        page,                    });                }                PickerLoadRequest::Preview { thread_id } => {                    let preview = load_transcript_preview(&mut app_server, thread_id).await;                    let _ = bg_tx.send(BackgroundEvent::Preview { thread_id, preview });                }                PickerLoadRequest::Transcript { thread_id } => {                    let transcript = load_session_transcript(                        &mut app_server,                        thread_id,                        raw_reasoning_visibility,                    )                    .await;                    let _ = bg_tx.send(BackgroundEvent::Transcript {                        thread_id,                        transcript,                    });                }            }        }        if let Err(err) = app_server.shutdown().await {            warn!(%err, "Failed to shut down app-server picker session");        }    });    Arc::new(move |request: PickerLoadRequest| {        let _ = request_tx.send(request);    })}/// Returns the human-readable column header for the given sort key.fn sort_key_label(sort_key: ThreadSortKey) -> &'static str {    match sort_key {        ThreadSortKey::CreatedAt => "Created",        ThreadSortKey::UpdatedAt => "Updated",    }}/// RAII guard that ensures we leave the alt-screen on scope exit.struct AltScreenGuard<'a> {    tui: &'a mut Tui,}impl<'a> AltScreenGuard<'a> {    fn enter(tui: &'a mut Tui) -> Self {        let _ = tui.enter_alt_screen();        Self { tui }    }}impl Drop for AltScreenGuard<'_> {    fn drop(&mut self) {        let _ = self.tui.leave_alt_screen();    }}struct PickerState {    requester: FrameRequester,    relative_time_reference: Option<DateTime<Utc>>,    pagination: PaginationState,    all_rows: Vec<Row>,    filtered_rows: Vec<Row>,    seen_rows: HashSet<SeenRowKey>,    selected: usize,    scroll_top: usize,    pending_page_down_target: Option<usize>,    frozen_footer_percent: Option<u8>,    query: String,    search_state: SearchState,    next_request_token: usize,    next_search_token: usize,    picker_loader: PickerLoader,    view_rows: Option<usize>,    view_width: Option<u16>,    provider_filter: ProviderFilter,    filter_mode: SessionFilterMode,    filter_cwd: Option<PathBuf>,    local_filter_cwd: Option<PathBuf>,    toolbar_focus: ToolbarControl,    density: SessionListDensity,    launch_context: SessionPickerLaunchContext,    view_persistence: Option<SessionPickerViewPersistence>,    action: SessionPickerAction,    sort_key: ThreadSortKey,    inline_error: Option<String>,    expanded_thread_id: Option<ThreadId>,    transcript_previews: HashMap<ThreadId, TranscriptPreviewState>,    transcript_cells: HashMap<ThreadId, SessionTranscriptState>,    pending_transcript_open: Option<ThreadId>,    transcript_loading_frame_shown: bool,    overlay: Option<Overlay>,    pager_keymap: PagerKeymap,    list_keymap: ListKeymap,}struct PaginationState {    next_cursor: Option<PageCursor>,    num_scanned_files: usize,    reached_scan_cap: bool,    loading: LoadingState,}#[derive(Clone, Copy, Debug)]enum LoadingState {    Idle,    Pending(PendingLoad),}#[derive(Clone, Copy, Debug)]struct PendingLoad {    request_token: usize,    search_token: Option<usize>,}#[derive(Clone, Copy, Debug)]enum SearchState {    Idle,    Active { token: usize },}#[derive(Clone)]enum TranscriptPreviewState {    Loading,    Loaded(Vec<TranscriptPreviewLine>),    Failed,}enum SessionTranscriptState {    Loading,    Loaded(TranscriptCells),    Failed,}#[derive(Clone)]struct TranscriptPreviewLine {    speaker: TranscriptPreviewSpeaker,    text: String,}#[derive(Clone, Copy)]enum TranscriptPreviewSpeaker {    User,    Assistant,}enum LoadTrigger {    Scroll,    Search { token: usize },}impl LoadingState {    fn is_pending(&self) -> bool {        matches!(self, LoadingState::Pending(_))    }}async fn load_app_server_page(    app_server: &mut AppServerSession,    cursor: Option<String>,    cwd_filter: Option<&Path>,    provider_filter: ProviderFilter,    sort_key: ThreadSortKey,    include_non_interactive: bool,) -> std::io::Result<PickerPage> {    let response = app_server        .thread_list(thread_list_params(            cursor,            cwd_filter,            provider_filter,            sort_key,            include_non_interactive,        ))        .await        .map_err(std::io::Error::other)?;    let num_scanned_files = response.data.len();    Ok(PickerPage {        rows: response            .data            .into_iter()            .filter_map(row_from_app_server_thread)            .collect(),        next_cursor: response.next_cursor.map(PageCursor::AppServer),        num_scanned_files,        reached_scan_cap: false,    })}async fn load_transcript_preview(    app_server: &mut AppServerSession,    thread_id: ThreadId,) -> std::io::Result<Vec<TranscriptPreviewLine>> {    const MAX_PREVIEW_LINES: usize = 6;    let thread = app_server        .thread_read(thread_id, /*include_turns*/ true)        .await        .map_err(std::io::Error::other)?;    let cwd = thread.cwd.as_path();    let mut lines = thread        .turns        .iter()        .flat_map(|turn| turn.items.iter())        .filter_map(|item| match item {            ThreadItem::UserMessage { content, .. } => Some(TranscriptPreviewLine {                speaker: TranscriptPreviewSpeaker::User,                text: content                    .iter()                    .filter_map(|input| match input {                        codex_app_server_protocol::UserInput::Text { text, .. } => {                            Some(text.as_str())                        }                        _ => None,                    })                    .collect::<Vec<_>>()                    .join(" "),            }),            ThreadItem::AgentMessage { text, .. } => Some(TranscriptPreviewLine {                speaker: TranscriptPreviewSpeaker::Assistant,                text: parse_assistant_markdown(text, cwd).visible_markdown,            }),            _ => None,        })        .flat_map(|line| {            line.text                .lines()                .filter(|text| !text.trim().is_empty())                .map(move |text| TranscriptPreviewLine {                    speaker: line.speaker,                    text: text.trim().to_string(),                })                .collect::<Vec<_>>()        })        .collect::<Vec<_>>();    if lines.len() > MAX_PREVIEW_LINES {        lines.drain(..lines.len() - MAX_PREVIEW_LINES);    }    Ok(lines)}impl SearchState {    fn active_token(&self) -> Option<usize> {        match self {            SearchState::Idle => None,            SearchState::Active { token } => Some(*token),        }    }    fn is_active(&self) -> bool {        self.active_token().is_some()    }}#[derive(Clone)]struct Row {    path: Option<PathBuf>,    preview: String,    thread_id: Option<ThreadId>,    thread_name: Option<String>,    created_at: Option<DateTime<Utc>>,    updated_at: Option<DateTime<Utc>>,    cwd: Option<PathBuf>,    git_branch: Option<String>,}#[derive(Clone, Debug, Eq, Hash, PartialEq)]enum SeenRowKey {    Path(PathBuf),    Thread(ThreadId),}impl Row {    fn seen_key(&self) -> Option<SeenRowKey> {        if let Some(path) = self.path.clone() {            return Some(SeenRowKey::Path(path));        }        self.thread_id.map(SeenRowKey::Thread)    }    fn display_preview(&self) -> &str {        self.thread_name.as_deref().unwrap_or(&self.preview)    }    fn matches_query(&self, query: &str) -> bool {        if self.preview.to_lowercase().contains(query) {            return true;        }        if let Some(thread_name) = self.thread_name.as_ref()            && thread_name.to_lowercase().contains(query)        {            return true;        }        if self            .thread_id            .is_some_and(|thread_id| thread_id.to_string().to_lowercase().contains(query))        {            return true;        }        if self            .git_branch            .as_ref()            .is_some_and(|branch| branch.to_lowercase().contains(query))        {            return true;        }        if self            .cwd            .as_ref()            .is_some_and(|cwd| cwd.to_string_lossy().to_lowercase().contains(query))        {            return true;        }        false    }}impl PickerState {    fn new(        requester: FrameRequester,        picker_loader: PickerLoader,        provider_filter: ProviderFilter,        show_all: bool,        filter_cwd: Option<PathBuf>,        action: SessionPickerAction,    ) -> Self {        Self {            requester,            relative_time_reference: None,            pagination: PaginationState {                next_cursor: None,                num_scanned_files: 0,                reached_scan_cap: false,                loading: LoadingState::Idle,            },            all_rows: Vec::new(),            filtered_rows: Vec::new(),            seen_rows: HashSet::new(),            selected: 0,            scroll_top: 0,            pending_page_down_target: None,            frozen_footer_percent: None,            query: String::new(),            search_state: SearchState::Idle,            next_request_token: 0,            next_search_token: 0,            picker_loader,            view_rows: None,            view_width: None,            provider_filter,            filter_mode: SessionFilterMode::from_show_all(show_all, filter_cwd.as_deref()),            local_filter_cwd: filter_cwd.clone(),            filter_cwd,            toolbar_focus: ToolbarControl::Filter,            density: SessionListDensity::Comfortable,            launch_context: SessionPickerLaunchContext::Startup,            view_persistence: None,            action,            sort_key: ThreadSortKey::UpdatedAt,            inline_error: None,            expanded_thread_id: None,            transcript_previews: HashMap::new(),            transcript_cells: HashMap::new(),            pending_transcript_open: None,            transcript_loading_frame_shown: false,            overlay: None,            pager_keymap: RuntimeKeymap::defaults().pager,            list_keymap: RuntimeKeymap::defaults().list,        }    }    fn request_frame(&self) {        self.requester.schedule_frame();    }    fn is_transcript_loading(&self) -> bool {        self.pending_transcript_open.is_some()    }    fn note_transcript_loading_frame_drawn(&mut self) -> bool {        if self.pending_transcript_open.is_some() {            self.transcript_loading_frame_shown = true;            true        } else {            false        }    }    fn open_pending_transcript_if_ready(&mut self) {        if !self.transcript_loading_frame_shown {            return;        }        let Some(thread_id) = self.pending_transcript_open else {            return;        };        let Some(SessionTranscriptState::Loaded(cells)) = self.transcript_cells.get(&thread_id)        else {            return;        };        self.overlay = Some(Overlay::new_transcript(            cells.clone(),            self.pager_keymap.clone(),        ));        self.pending_transcript_open = None;        self.transcript_loading_frame_shown = false;        self.request_frame();    }    fn begin_transcript_loading(&mut self, thread_id: ThreadId) {        self.pending_transcript_open = Some(thread_id);        self.transcript_loading_frame_shown = false;        self.request_frame();    }    fn handle_overlay_event(&mut self, tui: &mut Tui, event: TuiEvent) -> Result<()> {        let Some(overlay) = &mut self.overlay else {            return Ok(());        };        overlay.handle_event(tui, event)?;        if overlay.is_done() {            self.overlay = None;            self.request_frame();        }        Ok(())    }    fn open_selected_transcript(&mut self) {        let Some(row) = self.filtered_rows.get(self.selected) else {            return;        };        let Some(thread_id) = row.thread_id else {            self.inline_error = Some("No transcript available for this session".to_string());            self.request_frame();            return;        };        match self.transcript_cells.get(&thread_id) {            Some(SessionTranscriptState::Loaded(_)) => {                self.begin_transcript_loading(thread_id);            }            Some(SessionTranscriptState::Loading) => {                self.begin_transcript_loading(thread_id);            }            Some(SessionTranscriptState::Failed) | None => {                self.transcript_cells                    .insert(thread_id, SessionTranscriptState::Loading);                self.begin_transcript_loading(thread_id);                (self.picker_loader)(PickerLoadRequest::Transcript { thread_id });            }        }    }    fn handle_transcript_loading_key(&mut self, key: KeyEvent) -> Option<SessionSelection> {        match key {            KeyEvent {                code: KeyCode::Char('c'),                modifiers,                ..            } if modifiers.contains(KeyModifiers::CONTROL) => Some(SessionSelection::Exit),            _ => None,        }    }    async fn handle_key(&mut self, key: KeyEvent) -> Result<Option<SessionSelection>> {        self.inline_error = None;        if self.is_transcript_loading() {            return Ok(self.handle_transcript_loading_key(key));        }        if !self.list_keymap.page_down.is_pressed(key) {            self.pending_page_down_target = None;        }        // The session picker is always searchable, so plain text belongs to        // the query first. Modified list bindings still route through the        // runtime keymap below.        let allow_plain_char_navigation = !is_plain_text_key_event(key);        match key {            KeyEvent {                code: KeyCode::Char('c'),                modifiers,                ..            } if modifiers.contains(KeyModifiers::CONTROL) => {                return Ok(Some(SessionSelection::Exit));            }            _ if self.list_keymap.cancel.is_pressed(key) => {                if self.query.is_empty() {                    return Ok(Some(SessionSelection::StartFresh));                }                self.clear_query_preserving_selection();            }            KeyEvent {                code: KeyCode::Char('t'),                modifiers,                ..            } if modifiers.contains(KeyModifiers::CONTROL) => {                self.open_selected_transcript();            }            KeyEvent {                code: KeyCode::Char('e'),                modifiers,                ..            } if modifiers.contains(KeyModifiers::CONTROL) => {                self.toggle_selected_expansion();            }            KeyEvent {                code: KeyCode::Char('\u{0014}'),                modifiers: KeyModifiers::NONE,                ..            } /* ^T */ => {                self.open_selected_transcript();            }            KeyEvent {                code: KeyCode::Char('\u{0005}'),                modifiers: KeyModifiers::NONE,                ..            } /* ^E */ => {                self.toggle_selected_expansion();            }            KeyEvent {                code: KeyCode::Char('o'),                modifiers,                ..            } if modifiers.contains(KeyModifiers::CONTROL) => {                self.toggle_density().await;            }            KeyEvent {                code: KeyCode::Char('\u{000f}'),                modifiers: KeyModifiers::NONE,                ..            } /* ^O */ => {                self.toggle_density().await;            }            _ if self.list_keymap.accept.is_pressed(key) => {                if let Some(row) = self.filtered_rows.get(self.selected) {                    let path = row.path.clone();                    let thread_id = match row.thread_id {                        Some(thread_id) => Some(thread_id),                        None => match path.as_ref() {                            Some(path) => {                                resolve_session_thread_id(path.as_path(), /*id_str_if_uuid*/ None)                                    .await                            }                            None => None,                        },                    };                    if let Some(thread_id) = thread_id {                        return Ok(Some(self.action.selection(path, thread_id)));                    }                    self.inline_error = Some(match path {                        Some(path) => {                            format!("Failed to read session metadata from {}", path.display())                        }                        None => {                            String::from("Failed to read session metadata from selected session")                        }                    });                    self.request_frame();                }            }            _ if allow_plain_char_navigation && self.list_keymap.move_up.is_pressed(key) => {                if self.selected > 0 {                    self.selected -= 1;                    self.ensure_selected_visible();                }                self.request_frame();            }            _ if allow_plain_char_navigation && self.list_keymap.move_down.is_pressed(key) => {                if self.selected + 1 < self.filtered_rows.len() {                    self.selected += 1;                    self.ensure_selected_visible();                }                self.maybe_load_more_for_scroll();                self.request_frame();            }            _ if allow_plain_char_navigation && self.list_keymap.page_up.is_pressed(key) => {                let step = self.view_rows.unwrap_or(10).max(1);                if self.selected > 0 {                    self.selected = self.selected.saturating_sub(step);                    self.ensure_selected_visible();                    self.request_frame();                }            }            _ if allow_plain_char_navigation && self.list_keymap.jump_top.is_pressed(key)                && !self.filtered_rows.is_empty() => {                    self.selected = 0;                    self.ensure_selected_visible();                    self.request_frame();                }            _ if allow_plain_char_navigation && self.list_keymap.jump_bottom.is_pressed(key)                && !self.filtered_rows.is_empty() => {                    self.selected = self.filtered_rows.len().saturating_sub(1);                    self.ensure_selected_visible();                    self.maybe_load_more_for_scroll();                    self.request_frame();                }            _ if allow_plain_char_navigation && self.list_keymap.page_down.is_pressed(key)                && !self.filtered_rows.is_empty() => {                    let step = self.view_rows.unwrap_or(10).max(1);                    let target = self.selected.saturating_add(step);                    let max_index = self.filtered_rows.len().saturating_sub(1);                    if target > max_index && self.pagination.next_cursor.is_some() {                        self.pending_page_down_target = Some(target);                        self.load_more_if_needed(LoadTrigger::Scroll);                    } else {                        self.selected = target.min(max_index);                        self.ensure_selected_visible();                        self.maybe_load_more_for_scroll();                    }                    self.request_frame();                }            KeyEvent {                code: KeyCode::Tab, ..            } => {                self.focus_next_toolbar_control();                self.request_frame();            }            KeyEvent {                code: KeyCode::BackTab,                ..            } => {                self.focus_previous_toolbar_control();                self.request_frame();            }            _ if allow_plain_char_navigation                && (self.list_keymap.move_left.is_pressed(key)                    || self.list_keymap.move_right.is_pressed(key)) =>            {                self.change_focused_toolbar_value();                self.request_frame();            }            KeyEvent {                code: KeyCode::Backspace,                ..            } => {                let mut new_query = self.query.clone();                new_query.pop();                self.set_query(new_query);            }            KeyEvent {                code: KeyCode::Char(c),                modifiers,                ..            }                // basic text input for search                if !modifiers.contains(KeyModifiers::CONTROL)                    && !modifiers.contains(KeyModifiers::ALT)                => {                    let mut new_query = self.query.clone();                    new_query.push(c);                    self.set_query(new_query);                }            _ => {}        }        Ok(None)    }    fn handle_paste(&mut self, pasted: String) {        if self.is_transcript_loading() {            return;        }        let Some(pasted) = normalize_pasted_search_query(&pasted) else {            return;        };        let mut new_query = self.query.clone();        if !new_query.is_empty() && !new_query.ends_with(char::is_whitespace) {            new_query.push(' ');        }        new_query.push_str(&pasted);        self.set_query(new_query);    }    fn start_initial_load(&mut self) {        self.relative_time_reference = Some(Utc::now());        self.reset_pagination();        self.all_rows.clear();        self.filtered_rows.clear();        self.seen_rows.clear();        self.selected = 0;        self.pending_page_down_target = None;        self.frozen_footer_percent = None;        let search_token = if self.query.is_empty() {            self.search_state = SearchState::Idle;            None        } else {            let token = self.allocate_search_token();            self.search_state = SearchState::Active { token };            Some(token)        };        let request_token = self.allocate_request_token();        self.pagination.loading = LoadingState::Pending(PendingLoad {            request_token,            search_token,        });        self.request_frame();        (self.picker_loader)(PickerLoadRequest::Page(PageLoadRequest {            cursor: None,            request_token,            search_token,            cwd_filter: self.active_cwd_filter(),            provider_filter: self.provider_filter.clone(),            sort_key: self.sort_key,        }));    }    async fn handle_background_event(&mut self, event: BackgroundEvent) -> Result<()> {        match event {            BackgroundEvent::Page {                request_token,                search_token,                page,            } => {                let pending = match self.pagination.loading {                    LoadingState::Pending(pending) => pending,                    LoadingState::Idle => return Ok(()),                };                if pending.request_token != request_token {                    return Ok(());                }                self.pagination.loading = LoadingState::Idle;                let page = page.map_err(color_eyre::Report::from)?;                self.ingest_page(page);                self.complete_pending_page_down();                let completed_token = pending.search_token.or(search_token);                self.continue_search_if_token_matches(completed_token);            }            BackgroundEvent::Preview { thread_id, preview } => {                self.transcript_previews.insert(                    thread_id,                    match preview {                        Ok(lines) => TranscriptPreviewState::Loaded(lines),                        Err(_) => TranscriptPreviewState::Failed,                    },                );                self.request_frame();            }            BackgroundEvent::Transcript {                thread_id,                transcript,            } => match transcript {                Ok(cells) => {                    let should_open = self.pending_transcript_open == Some(thread_id);                    self.transcript_cells                        .insert(thread_id, SessionTranscriptState::Loaded(cells.clone()));                    if should_open {                        self.open_pending_transcript_if_ready();                    }                    self.request_frame();                }                Err(_) => {                    self.transcript_cells                        .insert(thread_id, SessionTranscriptState::Failed);                    if self.pending_transcript_open == Some(thread_id) {                        self.pending_transcript_open = None;                        self.transcript_loading_frame_shown = false;                        self.inline_error = Some("Could not load transcript preview".to_string());                    }                    self.request_frame();                }            },        }        Ok(())    }    fn reset_pagination(&mut self) {        self.pagination.next_cursor = None;        self.pagination.num_scanned_files = 0;        self.pagination.reached_scan_cap = false;        self.pagination.loading = LoadingState::Idle;        self.frozen_footer_percent = None;    }    fn ingest_page(&mut self, page: PickerPage) {        if let Some(cursor) = page.next_cursor.clone() {            self.pagination.next_cursor = Some(cursor);        } else {            self.pagination.next_cursor = None;        }        self.pagination.num_scanned_files = self            .pagination            .num_scanned_files            .saturating_add(page.num_scanned_files);        if page.reached_scan_cap {            self.pagination.reached_scan_cap = true;        }        for row in page.rows {            if let Some(seen_key) = row.seen_key() {                if self.seen_rows.insert(seen_key) {                    self.all_rows.push(row);                }            } else {                self.all_rows.push(row);            }        }        self.apply_filter();    }    fn complete_pending_page_down(&mut self) {        let Some(target) = self.pending_page_down_target else {            return;        };        if self.filtered_rows.is_empty() {            return;        }        let max_index = self.filtered_rows.len().saturating_sub(1);        if target > max_index && self.pagination.next_cursor.is_some() {            self.load_more_if_needed(LoadTrigger::Scroll);            return;        }        self.pending_page_down_target = None;        self.selected = target.min(max_index);        self.ensure_selected_visible();        self.maybe_load_more_for_scroll();        self.request_frame();    }    fn apply_filter(&mut self) {        let base_iter = self            .all_rows            .iter()            .filter(|row| self.row_matches_filter(row));        if self.query.is_empty() {            self.filtered_rows = base_iter.cloned().collect();        } else {            let q = self.query.to_lowercase();            self.filtered_rows = base_iter.filter(|r| r.matches_query(&q)).cloned().collect();        }        if self.selected >= self.filtered_rows.len() {            self.selected = self.filtered_rows.len().saturating_sub(1);        }        if self.filtered_rows.is_empty() {            self.scroll_top = 0;        }        self.ensure_selected_visible();        self.request_frame();    }    fn row_matches_filter(&self, row: &Row) -> bool {        if self.filter_mode == SessionFilterMode::All {            return true;        }        let Some(filter_cwd) = self.local_filter_cwd.as_ref() else {            return true;        };        let Some(row_cwd) = row.cwd.as_ref() else {            return false;        };        paths_match(row_cwd, filter_cwd)    }    fn set_query(&mut self, new_query: String) {        if self.query == new_query {            return;        }        self.query = new_query;        self.selected = 0;        self.apply_filter();        if self.query.is_empty() {            self.search_state = SearchState::Idle;            return;        }        if !self.filtered_rows.is_empty() {            self.search_state = SearchState::Idle;            return;        }        if self.pagination.reached_scan_cap || self.pagination.next_cursor.is_none() {            self.search_state = SearchState::Idle;            return;        }        let token = self.allocate_search_token();        self.search_state = SearchState::Active { token };        self.load_more_if_needed(LoadTrigger::Search { token });    }    fn clear_query_preserving_selection(&mut self) {        let selected_key = self            .filtered_rows            .get(self.selected)            .and_then(Row::seen_key);        self.query.clear();        self.search_state = SearchState::Idle;        self.apply_filter();        if let Some(selected_key) = selected_key            && let Some(index) = self                .filtered_rows                .iter()                .position(|row| row.seen_key().as_ref() == Some(&selected_key))        {            self.selected = index;            self.ensure_selected_visible();            self.request_frame();        }    }    fn continue_search_if_needed(&mut self) {        let Some(token) = self.search_state.active_token() else {            return;        };        if !self.filtered_rows.is_empty() {            self.search_state = SearchState::Idle;            return;        }        if self.pagination.reached_scan_cap || self.pagination.next_cursor.is_none() {            self.search_state = SearchState::Idle;            return;        }        self.load_more_if_needed(LoadTrigger::Search { token });    }    fn continue_search_if_token_matches(&mut self, completed_token: Option<usize>) {        let Some(active) = self.search_state.active_token() else {            return;        };        if let Some(token) = completed_token            && token != active        {            return;        }        self.continue_search_if_needed();    }    fn ensure_selected_visible(&mut self) {        if self.filtered_rows.is_empty() {            self.scroll_top = 0;            return;        }        let viewport_rows = self.view_rows.unwrap_or(usize::MAX).max(1);        if self.selected < self.scroll_top {            self.scroll_top = self.selected;        }        while self.rendered_height_between(self.scroll_top, self.selected)            > self.available_content_rows(viewport_rows)            && self.scroll_top < self.selected        {            self.scroll_top += 1;        }    }    fn ensure_minimum_rows_for_view(&mut self, minimum_rows: usize) {        if minimum_rows == 0 {            return;        }        if self.pagination.loading.is_pending() || self.pagination.next_cursor.is_none() {            return;        }        let rendered_rows = if self.filtered_rows.is_empty() {            0        } else {            self.rendered_height_between(/*start*/ 0, self.filtered_rows.len() - 1)        };        if rendered_rows >= self.available_content_rows(minimum_rows) {            return;        }        if let Some(token) = self.search_state.active_token() {            self.load_more_if_needed(LoadTrigger::Search { token });        } else {            self.load_more_if_needed(LoadTrigger::Scroll);        }    }    fn update_viewport(&mut self, rows: usize, width: u16) {        self.view_rows = if rows == 0 { None } else { Some(rows) };        self.view_width = Some(width);        self.ensure_selected_visible();    }    fn maybe_load_more_for_scroll(&mut self) {        if self.pagination.loading.is_pending() {            return;        }        if self.pagination.next_cursor.is_none() {            return;        }        if self.filtered_rows.is_empty() {            return;        }        let remaining = self.filtered_rows.len().saturating_sub(self.selected + 1);        if remaining <= LOAD_NEAR_THRESHOLD {            self.load_more_if_needed(LoadTrigger::Scroll);        }    }    fn load_more_if_needed(&mut self, trigger: LoadTrigger) {        if self.pagination.loading.is_pending() {            return;        }        let Some(cursor) = self.pagination.next_cursor.clone() else {            return;        };        self.freeze_footer_percent();        let request_token = self.allocate_request_token();        let search_token = match trigger {            LoadTrigger::Scroll => None,            LoadTrigger::Search { token } => Some(token),        };        self.pagination.loading = LoadingState::Pending(PendingLoad {            request_token,            search_token,        });        self.request_frame();        (self.picker_loader)(PickerLoadRequest::Page(PageLoadRequest {            cursor: Some(cursor),            request_token,            search_token,            cwd_filter: self.active_cwd_filter(),            provider_filter: self.provider_filter.clone(),            sort_key: self.sort_key,        }));    }    fn freeze_footer_percent(&mut self) {        let list_height = self.view_rows.unwrap_or_default().min(u16::MAX as usize) as u16;        self.frozen_footer_percent = Some(picker_footer_scroll_percent(self, list_height));    }    fn allocate_request_token(&mut self) -> usize {        let token = self.next_request_token;        self.next_request_token = self.next_request_token.wrapping_add(1);        token    }    fn allocate_search_token(&mut self) -> usize {        let token = self.next_search_token;        self.next_search_token = self.next_search_token.wrapping_add(1);        token    }    /// Cycles the sort order between creation time and last-updated time.    ///    /// Triggers a full reload because the backend must re-sort all sessions.    /// The existing `all_rows` are cleared and pagination restarts from the    /// beginning with the new sort key.    fn toggle_sort_key(&mut self) {        self.sort_key = match self.sort_key {            ThreadSortKey::CreatedAt => ThreadSortKey::UpdatedAt,            ThreadSortKey::UpdatedAt => ThreadSortKey::CreatedAt,        };        self.start_initial_load();    }    fn toggle_filter_mode(&mut self) {        let next_filter_mode = self.filter_mode.toggle(self.filter_cwd.as_deref());        if self.filter_mode == next_filter_mode {            return;        }        self.filter_mode = next_filter_mode;        self.start_initial_load();    }    fn active_cwd_filter(&self) -> Option<PathBuf> {        match self.filter_mode {            SessionFilterMode::Cwd => self.filter_cwd.clone(),            SessionFilterMode::All => None,        }    }    fn focus_previous_toolbar_control(&mut self) {        self.toolbar_focus = self.toolbar_focus.previous();    }    fn focus_next_toolbar_control(&mut self) {        self.toolbar_focus = self.toolbar_focus.next();    }    fn change_focused_toolbar_value(&mut self) {        match self.toolbar_focus {            ToolbarControl::Sort => self.toggle_sort_key(),            ToolbarControl::Filter => self.toggle_filter_mode(),        }    }    async fn toggle_density(&mut self) {        self.density = self.density.toggle();        self.ensure_selected_visible();        if let Err(err) = self.persist_density().await {            warn!(error = %err, "failed to persist session picker view mode");            self.inline_error = Some(format!("Failed to save view mode: {err}"));        }        self.request_frame();    }    async fn persist_density(&self) -> Result<()> {        let Some(persistence) = &self.view_persistence else {            return Ok(());        };        ConfigEditsBuilder::new(&persistence.codex_home)            .set_session_picker_view(SessionPickerViewMode::from(self.density))            .apply()            .await            .map_err(|err| color_eyre::eyre::eyre!("failed to write config.toml: {err}"))?;        Ok(())    }    fn toggle_selected_expansion(&mut self) {        let Some(row) = self.filtered_rows.get(self.selected) else {            return;        };        let Some(thread_id) = row.thread_id else {            return;        };        if self.expanded_thread_id == Some(thread_id) {            self.expanded_thread_id = None;            self.request_frame();            return;        }        self.expanded_thread_id = Some(thread_id);        if let std::collections::hash_map::Entry::Vacant(e) =            self.transcript_previews.entry(thread_id)        {            e.insert(TranscriptPreviewState::Loading);            (self.picker_loader)(PickerLoadRequest::Preview { thread_id });        }        self.request_frame();    }    fn rendered_height_between(&self, start: usize, end_inclusive: usize) -> usize {        self.filtered_rows            .get(start..=end_inclusive)            .unwrap_or_default()            .iter()            .enumerate()            .map(|(offset, row)| {                let row_idx = start + offset;                let is_selected = row_idx == self.selected;                let is_expanded = is_selected                    && row.thread_id.is_some()                    && self.expanded_thread_id == row.thread_id;                render_session_lines(                    row,                    self,                    is_selected,                    is_expanded,                    /*is_zebra*/ false,                    self.view_width.unwrap_or(u16::MAX),                )                .len()            })            .sum::<usize>()            + self.row_separator_height() * end_inclusive.saturating_sub(start)    }    fn has_more_above(&self) -> bool {        self.scroll_top > 0    }    fn has_more_below(&self, viewport_height: usize) -> bool {        if self.filtered_rows.is_empty() {            return false;        }        if self.pagination.next_cursor.is_some() {            return true;        }        let capacity = self.available_content_rows(viewport_height);        let mut used = 0usize;        for (offset, row) in self.filtered_rows[self.scroll_top..].iter().enumerate() {            let row_idx = self.scroll_top + offset;            let is_selected = row_idx == self.selected;            let is_expanded =                is_selected && row.thread_id.is_some() && self.expanded_thread_id == row.thread_id;            let row_height = render_session_lines(                row,                self,                is_selected,                is_expanded,                /*is_zebra*/ false,                self.view_width.unwrap_or(u16::MAX),            )            .len();            let separator_height = usize::from(offset > 0) * self.row_separator_height();            if used + separator_height + row_height > capacity {                return true;            }            used += separator_height + row_height;        }        false    }    fn available_content_rows(&self, viewport_height: usize) -> usize {        viewport_height            .saturating_sub(usize::from(self.has_more_above()))            .saturating_sub(usize::from(                self.pagination.next_cursor.is_some()                    || self.selected + 1 < self.filtered_rows.len(),            ))            .max(1)    }    fn row_separator_height(&self) -> usize {        match self.density {            SessionListDensity::Comfortable => 1,            SessionListDensity::Dense => 0,        }    }}fn row_from_app_server_thread(thread: Thread) -> Option<Row> {    let thread_id = match ThreadId::from_string(&thread.id) {        Ok(thread_id) => thread_id,        Err(err) => {            warn!(thread_id = thread.id, %err, "Skipping app-server picker row with invalid id");            return None;        }    };    let preview = thread.preview.trim();    Some(Row {        path: thread.path,        preview: if preview.is_empty() {            String::from("(no message yet)")        } else {            preview.to_string()        },        thread_id: Some(thread_id),        thread_name: thread.name,        created_at: chrono::DateTime::from_timestamp(thread.created_at, 0)            .map(|dt| dt.with_timezone(&Utc)),        updated_at: chrono::DateTime::from_timestamp(thread.updated_at, 0)            .map(|dt| dt.with_timezone(&Utc)),        cwd: Some(thread.cwd.to_path_buf()),        git_branch: thread.git_info.and_then(|git_info| git_info.branch),    })}fn thread_list_params(    cursor: Option<String>,    cwd_filter: Option<&Path>,    provider_filter: ProviderFilter,    sort_key: ThreadSortKey,    include_non_interactive: bool,) -> ThreadListParams {    ThreadListParams {        cursor,        limit: Some(PAGE_SIZE as u32),        sort_key: Some(sort_key),        sort_direction: None,        model_providers: match provider_filter {            ProviderFilter::Any => None,            ProviderFilter::MatchDefault(default_provider) => Some(vec![default_provider]),        },        source_kinds: Some(crate::resume_source_kinds(include_non_interactive)),        archived: Some(false),        parent_thread_id: None,        cwd: cwd_filter.map(|cwd| ThreadListCwdFilter::One(cwd.to_string_lossy().into_owned())),        use_state_db_only: false,        search_term: None,    }}fn paths_match(a: &Path, b: &Path) -> bool {    path_utils::paths_match_after_normalization(a, b)}#[cfg_attr(not(test), allow(dead_code))]fn parse_timestamp_str(ts: &str) -> Option<DateTime<Utc>> {    chrono::DateTime::parse_from_rfc3339(ts)        .map(|dt| dt.with_timezone(&Utc))        .ok()}fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> {    // Render full-screen overlay    let height = tui.terminal.size()?.height;    tui.draw(height, |frame| {        let area = frame.area();        let [header, _header_gap, search, _search_gap, list, footer] = Layout::vertical([            Constraint::Length(1),            Constraint::Length(1),            Constraint::Length(1),            Constraint::Length(1),            Constraint::Min(area.height.saturating_sub(PICKER_CHROME_HEIGHT)),            Constraint::Length(4),        ])        .areas(area);        let chrome = |area: Rect| {            Rect::new(                area.x.saturating_add(1),                area.y,                area.width.saturating_sub(2),                area.height,            )        };        // Header        let header_title = if default_bg().is_some_and(is_light) {            state.action.title().bold().fg(best_color((0, 100, 0)))        } else {            state.action.title().bold().cyan()        };        let header_line: Line = vec![header_title].into();        frame.render_widget_ref(header_line, chrome(header));        // Search line        let search = chrome(search);        frame.render_widget_ref(search_line(state, search.width), search);        let list = Rect::new(            list.x.saturating_add(2),            list.y,            list_viewport_width(list.width),            list.height,        );        render_list(frame, list, state);        if state.is_transcript_loading() {            render_transcript_loading_overlay(frame, list);        }        render_picker_footer(frame, footer, state, list.height);    })}fn list_viewport_width(width: u16) -> u16 {    width.saturating_sub(PICKER_LIST_HORIZONTAL_INSET)}fn search_line(state: &PickerState, width: u16) -> Line<'_> {    if let Some(error) = state.inline_error.as_deref() {        return Line::from(error.red());    }    let search = if state.query.is_empty() {        "Type to search".dim()    } else {        format!("Search: {}", state.query).into()    };    let mut toolbar = toolbar_line(state, /*compact*/ false);    if toolbar.width() as u16 > width.saturating_sub(2) {        toolbar = toolbar_line(state, /*compact*/ true);    }    let search_width = UnicodeWidthStr::width(search.content.as_ref());    let toolbar_width = toolbar.width();    let spacer_width = width        .saturating_sub((search_width + toolbar_width) as u16)        .max(2) as usize;    let available_search_width = width        .saturating_sub(toolbar_width as u16)        .saturating_sub(spacer_width as u16) as usize;    let search = if search_width > available_search_width {        let truncated = truncate_text(search.content.as_ref(), available_search_width);        if state.query.is_empty() {            truncated.dim()        } else {            truncated.into()        }    } else {        search    };    let mut spans = vec![search, " ".repeat(spacer_width).into()];    spans.extend(toolbar.spans);    spans.into()}fn toolbar_line(state: &PickerState, compact: bool) -> Line<'static> {    let mut spans = Vec::new();    spans.extend(filter_control_spans(state, compact));    spans.push("   ".dim());    spans.extend(sort_control_spans(state, compact));    spans.into()}fn sort_control_spans(state: &PickerState, compact: bool) -> Vec<Span<'static>> {    let sort_focused = state.toolbar_focus == ToolbarControl::Sort;    if compact {        return vec![            "Sort:".dim(),            toolbar_value(                sort_key_label(state.sort_key),                /*active*/ true,                sort_focused,            ),        ];    }    vec![        "Sort: ".dim(),        toolbar_value(            sort_key_label(ThreadSortKey::UpdatedAt),            state.sort_key == ThreadSortKey::UpdatedAt,            sort_focused,        ),        toolbar_value(            sort_key_label(ThreadSortKey::CreatedAt),            state.sort_key == ThreadSortKey::CreatedAt,            sort_focused,        ),    ]}fn filter_control_spans(state: &PickerState, compact: bool) -> Vec<Span<'static>> {    let filter_focused = state.toolbar_focus == ToolbarControl::Filter;    if compact || state.filter_cwd.is_none() {        return vec![            "Filter:".dim(),            toolbar_value(                filter_mode_label(state.filter_mode),                /*active*/ true,                filter_focused,            ),        ];    }    vec![        "Filter: ".dim(),        toolbar_value(            filter_mode_label(SessionFilterMode::Cwd),            state.filter_mode == SessionFilterMode::Cwd,            filter_focused,        ),        toolbar_value(            filter_mode_label(SessionFilterMode::All),            state.filter_mode == SessionFilterMode::All,            filter_focused,        ),    ]}fn toolbar_value(label: &'static str, active: bool, focused: bool) -> Span<'static> {    if active {        let value = format!("[{label}]");        if focused {            value.magenta()        } else {            value.into()        }    } else {        format!(" {label} ").dim()    }}fn filter_mode_label(filter_mode: SessionFilterMode) -> &'static str {    match filter_mode {        SessionFilterMode::Cwd => "Cwd",        SessionFilterMode::All => "All",    }}struct PickerFooterHint {    key: &'static str,    wide_label: String,    compact_label: String,    priority: u8,}fn render_picker_footer(    frame: &mut crate::custom_terminal::Frame,    area: Rect,    state: &PickerState,    list_height: u16,) {    if area.width == 0 || area.height == 0 {        return;    }    let separator = Rect::new(area.x, area.y, area.width, 1);    render_picker_footer_separator(        frame,        separator,        picker_footer_progress_label(state, list_height, area.width),    );    let lines = footer_hint_lines(state, area.width);    for (idx, line) in lines.into_iter().enumerate() {        let y = area.y.saturating_add(1 + idx as u16);        if y >= area.bottom() {            break;        }        frame.render_widget_ref(line, Rect::new(area.x, y, area.width, 1));    }}fn render_picker_footer_separator(    frame: &mut crate::custom_terminal::Frame,    area: Rect,    progress_label: String,) {    if area.width == 0 {        return;    }    let separator = "─".repeat(area.width as usize);    frame.render_widget_ref(Line::from(separator.dim()), area);    let progress_width = UnicodeWidthStr::width(progress_label.as_str()) as u16;    if progress_width < area.width {        let percent_area = Rect::new(            area.x + area.width - progress_width - 1,            area.y,            progress_width,            1,        );        frame.render_widget_ref(Line::from(progress_label.dim()), percent_area);    }}fn picker_footer_progress_label(state: &PickerState, list_height: u16, width: u16) -> String {    let position = if state.filtered_rows.is_empty() {        0    } else {        state.selected.saturating_add(1)    };    let total = if state.pagination.loading.is_pending() {        format!("{}…", state.filtered_rows.len())    } else {        state.filtered_rows.len().to_string()    };    let percent = picker_footer_percent(state, list_height);    let labels = [        format!(" {position} / {total} · {percent}% "),        format!(" {position}/{total} · {percent}% "),        format!(" {percent}% "),    ];    labels        .into_iter()        .find(|label| UnicodeWidthStr::width(label.as_str()) < width as usize)        .unwrap_or_default()}fn picker_footer_percent(state: &PickerState, list_height: u16) -> u8 {    if state.pagination.loading.is_pending() {        return state.frozen_footer_percent.unwrap_or_else(|| {            if state.filtered_rows.is_empty() {                0            } else {                picker_footer_scroll_percent(state, list_height)            }        });    }    picker_footer_scroll_percent(state, list_height)}fn picker_footer_scroll_percent(state: &PickerState, list_height: u16) -> u8 {    if state.filtered_rows.is_empty() {        return 100;    }    let content_rows = state.available_content_rows(list_height as usize);    let total_height =        state.rendered_height_between(/*start*/ 0, state.filtered_rows.len() - 1);    let max_scroll = total_height.saturating_sub(content_rows);    if max_scroll == 0 {        return 100;    }    let remaining_height =        state.rendered_height_between(state.scroll_top, state.filtered_rows.len() - 1);    if remaining_height <= content_rows {        return 100;    }    let skipped_height = if state.scroll_top == 0 {        0    } else {        state.rendered_height_between(/*start*/ 0, state.scroll_top - 1)    };    (((skipped_height.min(max_scroll)) as f32 / max_scroll as f32) * 100.0).round() as u8}fn footer_hint_lines(state: &PickerState, width: u16) -> Vec<Line<'static>> {    if state.is_transcript_loading() {        let hints = [            PickerFooterHint {                key: "loading",                wide_label: String::from("transcript"),                compact_label: String::from("transcript"),                priority: 0,            },            PickerFooterHint {                key: "ctrl+c",                wide_label: String::from("quit"),                compact_label: String::from("quit"),                priority: 1,            },        ];        let line = fit_footer_hints(&hints, FooterHintLabelMode::Wide, width)            .or_else(|| fit_footer_hints(&hints, FooterHintLabelMode::Compact, width))            .or_else(|| fit_footer_hints(&hints, FooterHintLabelMode::KeyOnly, width))            .unwrap_or_default();        return vec![line, Line::default()];    }    let action_label = state.action.action_label();    let (esc_label, esc_compact_label) = if state.query.is_empty() {        match state.launch_context {            SessionPickerLaunchContext::Startup => ("start new", "new"),            SessionPickerLaunchContext::ExistingSession => ("exit", "exit"),        }    } else {        ("clear search", "clear")    };    let ctrl_c_label = match state.launch_context {        SessionPickerLaunchContext::Startup => "quit",        SessionPickerLaunchContext::ExistingSession => "exit",    };    let density_label = match state.density {        SessionListDensity::Comfortable => "dense view",        SessionListDensity::Dense => "comfortable view",    };    let density_compact_label = match state.density {        SessionListDensity::Comfortable => "dense",        SessionListDensity::Dense => "comfy",    };    let first_row_hints = vec![        PickerFooterHint {            key: "enter",            wide_label: action_label.to_string(),            compact_label: action_label.to_string(),            priority: 0,        },        PickerFooterHint {            key: "esc",            wide_label: esc_label.to_string(),            compact_label: esc_compact_label.to_string(),            priority: 1,        },        PickerFooterHint {            key: "ctrl+c",            wide_label: ctrl_c_label.to_string(),            compact_label: ctrl_c_label.to_string(),            priority: 2,        },        PickerFooterHint {            key: "tab",            wide_label: String::from("focus sort/filter"),            compact_label: String::from("focus"),            priority: 7,        },        PickerFooterHint {            key: "←/→",            wide_label: String::from("change option"),            compact_label: String::from("option"),            priority: 8,        },    ];    let second_row_hints = vec![        PickerFooterHint {            key: "ctrl+o",            wide_label: density_label.to_string(),            compact_label: density_compact_label.to_string(),            priority: 3,        },        PickerFooterHint {            key: "ctrl+t",            wide_label: String::from("transcript"),            compact_label: String::from("preview"),            priority: 4,        },        PickerFooterHint {            key: "ctrl+e",            wide_label: String::from("expand"),            compact_label: String::from("exp"),            priority: 6,        },        PickerFooterHint {            key: "↑/↓",            wide_label: String::from("browse"),            compact_label: String::from("browse"),            priority: 5,        },    ];    vec![        hint_line_for_row(&first_row_hints, width),        hint_line_for_row(&second_row_hints, width),    ]}fn hint_line_for_row(hints: &[PickerFooterHint], width: u16) -> Line<'static> {    if width >= FOOTER_COMPACT_BREAKPOINT        && let Some(line) = fit_footer_hints(hints, FooterHintLabelMode::Wide, width)    {        return line;    }    if let Some(line) = fit_footer_hints(hints, FooterHintLabelMode::Compact, width) {        return line;    }    if let Some(line) = fit_footer_hints(hints, FooterHintLabelMode::KeyOnly, width) {        return line;    }    let mut retained = (0..hints.len()).collect::<Vec<_>>();    retained.sort_by_key(|idx| hints[*idx].priority);    for retain_count in (1..=retained.len()).rev() {        let mut candidate_indices = retained[..retain_count].to_vec();        candidate_indices.sort_unstable();        let candidate = candidate_indices            .iter()            .map(|idx| &hints[*idx])            .collect::<Vec<_>>();        if let Some(line) = fit_footer_hint_refs(&candidate, FooterHintLabelMode::KeyOnly, width) {            return line;        }    }    Line::default()}fn render_transcript_loading_overlay(frame: &mut crate::custom_terminal::Frame, area: Rect) {    if area.width == 0 || area.height == 0 {        return;    }    let message = "Loading transcript…";    let message_width = UnicodeWidthStr::width(message) as u16;    let overlay_width = if area.width >= message_width.saturating_add(10) {        message_width + 10    } else {        area.width    };    let overlay_height = if area.height >= 3 { 3 } else { 1 };    let overlay = Rect::new(        area.x + area.width.saturating_sub(overlay_width) / 2,        area.y + area.height.saturating_sub(overlay_height) / 2,        overlay_width,        overlay_height,    );    let style = transcript_loading_overlay_style();    for y in overlay.y..overlay.bottom() {        for x in overlay.x..overlay.right() {            frame.buffer[(x, y)].set_symbol(" ").set_style(style);        }    }    let message = truncate_text(message, overlay.width as usize);    let message_width = UnicodeWidthStr::width(message.as_str()) as u16;    let line = Rect::new(        overlay.x + overlay.width.saturating_sub(message_width) / 2,        overlay.y + overlay.height / 2,        message_width.min(overlay.width),        1,    );    frame.render_widget_ref(Line::from(message.bold()), line);}fn transcript_loading_overlay_style() -> Style {    let Some(bg) = default_bg() else {        return Style::default().bg(Color::DarkGray);    };    let (overlay, alpha) = if is_light(bg) {        ((0, 0, 0), 0.08)    } else {        ((255, 255, 255), 0.14)    };    Style::default().bg(best_color(blend(overlay, bg, alpha)))}#[derive(Clone, Copy)]enum FooterHintLabelMode {    Wide,    Compact,    KeyOnly,}fn fit_footer_hints(    hints: &[PickerFooterHint],    mode: FooterHintLabelMode,    width: u16,) -> Option<Line<'static>> {    let hint_refs = hints.iter().collect::<Vec<_>>();    fit_footer_hint_refs(&hint_refs, mode, width)}fn fit_footer_hint_refs(    hints: &[&PickerFooterHint],    mode: FooterHintLabelMode,    width: u16,) -> Option<Line<'static>> {    let gap_width = FOOTER_HINT_GAP;    if footer_hints_width(hints, mode, gap_width) > width as usize {        return None;    }    let mut spans = vec![        " ".repeat(FOOTER_HINT_LEFT_PADDING)            .set_style(footer_hint_label_style()),    ];    for (idx, hint) in hints.iter().enumerate() {        if idx > 0 {            spans.push(" ".repeat(gap_width).set_style(footer_hint_label_style()));        }        spans.push(hint.key.set_style(footer_hint_key_style()));        let label = match mode {            FooterHintLabelMode::Wide => Some(hint.wide_label.as_str()),            FooterHintLabelMode::Compact => Some(hint.compact_label.as_str()),            FooterHintLabelMode::KeyOnly => None,        };        if let Some(label) = label {            spans.push(" ".set_style(footer_hint_label_style()));            spans.push(label.to_string().set_style(footer_hint_label_style()));        }    }    Some(spans.into())}fn footer_hint_key_style() -> Style {    if default_bg().is_some_and(is_light) {        Style::default().fg(Color::Black)    } else {        Style::default()    }}fn footer_hint_label_style() -> Style {    if default_bg().is_some_and(is_light) {        Style::default().fg(Color::DarkGray)    } else {        Style::default().dim()    }}fn footer_hints_width(    hints: &[&PickerFooterHint],    mode: FooterHintLabelMode,    gap_width: usize,) -> usize {    FOOTER_HINT_LEFT_PADDING        + hints            .iter()            .enumerate()            .map(|(idx, hint)| {                let label_width = match mode {                    FooterHintLabelMode::Wide => {                        1 + UnicodeWidthStr::width(hint.wide_label.as_str())                    }                    FooterHintLabelMode::Compact => {                        1 + UnicodeWidthStr::width(hint.compact_label.as_str())                    }                    FooterHintLabelMode::KeyOnly => 0,                };                let hint_width = UnicodeWidthStr::width(hint.key) + label_width;                if idx == 0 {                    hint_width                } else {                    hint_width + gap_width                }            })            .sum::<usize>()}fn render_list(frame: &mut crate::custom_terminal::Frame, area: Rect, state: &PickerState) {    if area.height == 0 {        return;    }    Clear.render(area, frame.buffer);    let rows = &state.filtered_rows;    if rows.is_empty() {        let message = render_empty_state_line(state);        frame.render_widget_ref(message, area);        return;    }    let show_more_above = state.has_more_above();    let show_more_below = state.has_more_below(area.height as usize);    let content_area = Rect::new(        area.x,        area.y.saturating_add(u16::from(show_more_above)),        area.width,        area.height            .saturating_sub(u16::from(show_more_above))            .saturating_sub(u16::from(show_more_below)),    );    if show_more_above {        frame.render_widget_ref(            more_line("↑ more"),            Rect::new(area.x, area.y, area.width, 1),        );    }    let start = state.scroll_top.min(rows.len().saturating_sub(1));    let mut y = content_area.y;    for (idx, row) in rows[start..].iter().enumerate() {        if y >= content_area.y.saturating_add(content_area.height) {            break;        }        let row_idx = start + idx;        let is_selected = row_idx == state.selected;        let is_expanded =            is_selected && row.thread_id.is_some() && state.expanded_thread_id == row.thread_id;        let is_zebra = row_idx.is_multiple_of(2);        for line in render_session_lines(row, state, is_selected, is_expanded, is_zebra, area.width)        {            if y >= content_area.y.saturating_add(content_area.height) {                break;            }            frame.render_widget_ref(line, Rect::new(area.x, y, area.width, 1));            y = y.saturating_add(1);        }        if state.density == SessionListDensity::Comfortable            && y < content_area.y.saturating_add(content_area.height)            && start + idx + 1 < rows.len()        {            y = y.saturating_add(1);        }    }    if state.pagination.loading.is_pending()        && y < content_area.y.saturating_add(content_area.height)    {        let loading_line: Line = vec!["  ".into(), "Loading older sessions…".italic().dim()].into();        let rect = Rect::new(area.x, y, area.width, 1);        frame.render_widget_ref(loading_line, rect);    }    if show_more_below {        let label = if state.pagination.loading.is_pending() {            "↓ loading more"        } else {            "↓ more"        };        frame.render_widget_ref(            more_line(label),            Rect::new(                area.x,                area.y.saturating_add(area.height.saturating_sub(1)),                area.width,                1,            ),        );    }}fn more_line(label: &'static str) -> Line<'static> {    vec![label.dim()].into()}fn render_session_lines(    row: &Row,    state: &PickerState,    is_selected: bool,    is_expanded: bool,    is_zebra: bool,    width: u16,) -> Vec<Line<'static>> {    match state.density {        SessionListDensity::Comfortable => {            render_comfortable_session_lines(row, state, is_selected, is_expanded, is_zebra, width)        }        SessionListDensity::Dense => {            render_dense_session_lines(row, state, is_selected, is_expanded, is_zebra, width)        }    }}fn render_comfortable_session_lines(    row: &Row,    state: &PickerState,    is_selected: bool,    is_expanded: bool,    is_zebra: bool,    width: u16,) -> Vec<Line<'static>> {    let marker = selection_marker(is_selected, is_expanded);    let title = truncate_text(row.display_preview(), width.saturating_sub(2) as usize);    let title = if is_selected {        selected_session_title_span(title)    } else {        title.into()    };    let title_line = Line::from(vec![marker, title]);    let mut lines = vec![title_line];    let row_style = if is_selected {        Some(dense_selected_style())    } else if is_zebra {        Some(dense_zebra_style())    } else {        None    };    if let Some(style) = row_style {        lines = apply_session_row_background(lines, style, width);    }    if is_expanded {        lines.extend(render_transcript_preview_lines(row, state, width));        return lines;    }    let reference = state.relative_time_reference.unwrap_or_else(Utc::now);    let created = format_relative_time(reference, row.created_at);    let updated = format_relative_time(reference, row.updated_at.or(row.created_at));    let branch = row.git_branch.as_deref();    let cwd = row        .cwd        .as_ref()        .map(|path| format_directory_display(path, /*max_width*/ None));    let footer_lines = render_footer_lines(        state.sort_key,        &created,        &updated,        branch,        cwd.as_deref(),        state.filter_mode == SessionFilterMode::All,        width,    );    if let Some(style) = row_style {        lines.extend(apply_session_row_background(footer_lines, style, width));    } else {        lines.extend(footer_lines);    }    lines}fn apply_session_row_background(    lines: Vec<Line<'static>>,    style: Style,    width: u16,) -> Vec<Line<'static>> {    lines        .into_iter()        .map(|line| apply_line_background(line, style, width))        .collect()}fn apply_line_background(mut line: Line<'static>, style: Style, width: u16) -> Line<'static> {    let padding = (width as usize).saturating_sub(line.width());    if padding > 0 {        line.spans.push(" ".repeat(padding).set_style(style));    }    line.style = line.style.patch(style);    for span in &mut line.spans {        span.style = span.style.patch(style);    }    line}fn render_dense_session_lines(    row: &Row,    state: &PickerState,    is_selected: bool,    is_expanded: bool,    is_zebra: bool,    width: u16,) -> Vec<Line<'static>> {    let marker = selection_marker(is_selected, is_expanded);    let reference = state.relative_time_reference.unwrap_or_else(Utc::now);    let created = format_relative_time(reference, row.created_at);    let updated = format_relative_time(reference, row.updated_at.or(row.created_at));    let date = match state.sort_key {        ThreadSortKey::CreatedAt => created,        ThreadSortKey::UpdatedAt => updated,    };    let mut lines = vec![dense_summary_line(DenseSummaryInput {        marker,        date: &date,        title: row.display_preview(),        is_selected,        is_zebra,        width,    })];    if is_expanded {        lines.extend(render_transcript_preview_lines(row, state, width));    }    lines}struct DenseSummaryInput<'a> {    marker: Span<'static>,    date: &'a str,    title: &'a str,    is_selected: bool,    is_zebra: bool,    width: u16,}fn dense_summary_line(input: DenseSummaryInput<'_>) -> Line<'static> {    let marker_width = input.marker.width();    let available = (input.width as usize).saturating_sub(marker_width);    let columns = dense_columns(available);    let title = if input.is_selected {        selected_session_title_span(dense_column_text(input.title, columns.title_width))    } else {        dense_column_text(input.title, columns.title_width).into()    };    let spans = vec![        input.marker,        dense_column_text(input.date, columns.date_width).dim(),        title,    ];    let mut line = Line::from(spans);    if input.is_selected {        let padding = (input.width as usize).saturating_sub(line.width());        if padding > 0 {            line.spans                .push(" ".repeat(padding).set_style(dense_selected_style()));        }        line = line.style(dense_selected_style());    } else if input.is_zebra {        let padding = (input.width as usize).saturating_sub(line.width());        if padding > 0 {            line.spans                .push(" ".repeat(padding).set_style(dense_zebra_style()));        }        line = line.style(dense_zebra_style());    }    line}struct DenseColumns {    date_width: usize,    title_width: usize,}fn dense_columns(width: usize) -> DenseColumns {    let date_width = SESSION_META_DATE_WIDTH;    DenseColumns {        date_width,        title_width: width.saturating_sub(date_width),    }}fn dense_zebra_style() -> Style {    dense_row_background_style(/*selected*/ false)}fn dense_selected_style() -> Style {    selected_session_style().patch(dense_row_background_style(/*selected*/ true))}fn dense_row_background_style(selected: bool) -> Style {    let Some(bg) = default_bg() else {        return Style::default();    };    let (overlay, alpha) = if is_light(bg) {        ((0, 0, 0), if selected { 0.12 } else { 0.04 })    } else {        ((255, 255, 255), if selected { 0.12 } else { 0.055 })    };    Style::default().bg(best_color(blend(overlay, bg, alpha)))}fn dense_column_text(text: &str, width: usize) -> String {    let text = truncate_text(text, width.saturating_sub(1));    let padding = width.saturating_sub(UnicodeWidthStr::width(text.as_str()));    format!("{text}{}", " ".repeat(padding))}fn selection_marker(is_selected: bool, is_expanded: bool) -> Span<'static> {    match (is_selected, is_expanded) {        (true, true) => "⌄ ".set_style(selected_session_style().bold()),        (true, false) => "❯ ".set_style(selected_session_style().bold()),        (false, _) => "  ".into(),    }}fn selected_session_style() -> Style {    if default_bg().is_some_and(is_light) {        Style::default().fg(Color::Magenta)    } else {        Style::default().fg(Color::Yellow)    }}fn selected_session_title_span(title: String) -> Span<'static> {    title.set_style(selected_session_style())}fn render_footer_lines(    sort_key: ThreadSortKey,    created: &str,    updated: &str,    branch: Option<&str>,    cwd: Option<&str>,    show_cwd: bool,    width: u16,) -> Vec<Line<'static>> {    let date = match sort_key {        ThreadSortKey::CreatedAt => created,        ThreadSortKey::UpdatedAt => updated,    };    let mut parts = vec![FooterPart::Date(date.to_string())];    if show_cwd {        parts.push(FooterPart::Cwd(cwd.map(str::to_string)));    }    parts.push(FooterPart::Branch(branch.map(str::to_string)));    pack_footer_parts(parts, width)}enum FooterPart {    Date(String),    Branch(Option<String>),    Cwd(Option<String>),}impl FooterPart {    fn text(&self) -> &str {        match self {            FooterPart::Date(text) => text,            FooterPart::Branch(Some(text)) | FooterPart::Cwd(Some(text)) => text,            FooterPart::Branch(None) => "no branch",            FooterPart::Cwd(None) => "no cwd",        }    }    fn prefix(&self) -> Option<&'static str> {        match self {            FooterPart::Date(_) => None,            FooterPart::Branch(_) => Some(SESSION_META_BRANCH_ICON),            FooterPart::Cwd(_) => Some(SESSION_META_CWD_ICON),        }    }}fn pack_footer_parts(parts: Vec<FooterPart>, width: u16) -> Vec<Line<'static>> {    let available_width = width as usize;    if available_width <= SESSION_META_INDENT_WIDTH {        return Vec::new();    }    let cwd_width = cwd_column_width(available_width);    let all_parts_width = footer_parts_width(&parts, cwd_width);    if all_parts_width <= available_width {        return vec![footer_line(parts, available_width, cwd_width)];    }    let mut lines = Vec::with_capacity(parts.len());    let mut current_parts = Vec::new();    for part in parts {        let mut candidate_parts = std::mem::take(&mut current_parts);        candidate_parts.push(part);        if candidate_parts.len() > 1            && footer_parts_width(&candidate_parts, cwd_width) > available_width        {            let previous_parts = candidate_parts                .drain(..candidate_parts.len().saturating_sub(1))                .collect();            lines.push(footer_line(previous_parts, available_width, cwd_width));        }        current_parts = candidate_parts;    }    if !current_parts.is_empty() {        lines.push(footer_line(current_parts, available_width, cwd_width));    }    lines}fn cwd_column_width(width: usize) -> usize {    let available = width.saturating_sub(        SESSION_META_INDENT_WIDTH + SESSION_META_DATE_WIDTH + 2 * SESSION_META_FIELD_GAP_WIDTH,    );    (available / 2).clamp(SESSION_META_MIN_CWD_WIDTH, SESSION_META_MAX_CWD_WIDTH)}fn footer_parts_width(parts: &[FooterPart], cwd_width: usize) -> usize {    let content_width: usize = parts        .iter()        .enumerate()        .map(|(idx, part)| footer_part_width(part, idx + 1 < parts.len(), cwd_width))        .sum();    SESSION_META_INDENT_WIDTH + content_width}fn footer_part_width(part: &FooterPart, padded: bool, cwd_width: usize) -> usize {    let prefix_width = part.prefix().map_or(0, UnicodeWidthStr::width);    let prefix_gap_width = usize::from(part.prefix().is_some() && !part.text().is_empty());    let text_width = UnicodeWidthStr::width(part.text());    let actual_width = prefix_width + prefix_gap_width + text_width;    match part {        FooterPart::Date(_) if padded => SESSION_META_DATE_WIDTH.max(actual_width),        FooterPart::Cwd(_) if padded => cwd_width,        _ => actual_width,    }}fn footer_line(parts: Vec<FooterPart>, width: usize, cwd_width: usize) -> Line<'static> {    let mut spans: Vec<Span<'static>> = vec!["  ".into()];    let mut remaining_width = width.saturating_sub(SESSION_META_INDENT_WIDTH);    let part_count = parts.len();    for (idx, part) in parts.into_iter().enumerate() {        if idx > 0 {            let gap_width = SESSION_META_FIELD_GAP_WIDTH.min(remaining_width);            if gap_width > 0 {                spans.push(" ".repeat(gap_width).dim());                remaining_width = remaining_width.saturating_sub(gap_width);            }        }        let padded = idx + 1 < part_count;        let target_width = match part {            FooterPart::Date(_) if padded => Some(SESSION_META_DATE_WIDTH),            FooterPart::Cwd(_) if padded => Some(cwd_width),            FooterPart::Date(_) | FooterPart::Branch(_) | FooterPart::Cwd(_) => None,        };        let used_width = push_footer_part(&mut spans, part, target_width, remaining_width);        remaining_width = remaining_width.saturating_sub(used_width);        if let Some(target_width) = target_width {            let padding = target_width.saturating_sub(used_width);            if padding > 0 {                spans.push(" ".repeat(padding).dim());                remaining_width = remaining_width.saturating_sub(padding);            }        }    }    spans.into()}fn push_footer_part(    spans: &mut Vec<Span<'static>>,    part: FooterPart,    target_width: Option<usize>,    available_width: usize,) -> usize {    let text = part.text().to_string();    let Some(prefix) = part.prefix() else {        let text = truncate_text(&text, available_width);        let width = UnicodeWidthStr::width(text.as_str());        spans.push(text.dim());        return width;    };    let prefix_width = UnicodeWidthStr::width(prefix);    if available_width <= prefix_width {        let prefix = truncate_text(prefix, available_width);        let width = UnicodeWidthStr::width(prefix.as_str());        spans.push(prefix.dim());        return width;    }    spans.push(prefix.dim());    let mut used_width = prefix_width;    if !text.is_empty() && used_width < available_width {        spans.push(" ".dim());        used_width += 1;    }    let text_width = target_width        .unwrap_or(available_width)        .saturating_sub(used_width)        .min(available_width.saturating_sub(used_width));    let text = truncate_text(&text, text_width);    let rendered_text_width = UnicodeWidthStr::width(text.as_str());    match part {        FooterPart::Branch(None) | FooterPart::Cwd(None) => spans.push(text.dim().italic()),        _ => spans.push(text.dim()),    }    used_width + rendered_text_width}fn render_transcript_preview_lines(    row: &Row,    state: &PickerState,    width: u16,) -> Vec<Line<'static>> {    let mut details = render_expanded_session_details(row, state, width);    let Some(thread_id) = row.thread_id else {        return details;    };    let preview_lines = match state.transcript_previews.get(&thread_id) {        Some(TranscriptPreviewState::Loading) => {            vec![vec!["  │ ".dim(), "Loading recent transcript...".italic().dim()].into()]        }        Some(TranscriptPreviewState::Failed) => vec![            vec![                "  │ ".dim(),                "Could not load transcript preview".italic().red(),            ]            .into(),        ],        Some(TranscriptPreviewState::Loaded(lines)) => {            render_conversation_preview_lines(lines, width)        }        None => Vec::new(),    };    details.extend(preview_lines);    details}fn render_expanded_session_details(    row: &Row,    state: &PickerState,    width: u16,) -> Vec<Line<'static>> {    let reference = state.relative_time_reference.unwrap_or_else(Utc::now);    let session = match (row.thread_name.as_deref(), row.thread_id) {        (Some(thread_name), Some(thread_id)) => format!("{thread_name} ({thread_id})"),        (Some(thread_name), None) => thread_name.to_string(),        (None, Some(thread_id)) => thread_id.to_string(),        (None, None) => "-".to_string(),    };    let directory = row        .cwd        .as_ref()        .map(|path| format_directory_display(path, /*max_width*/ None))        .unwrap_or_else(|| "-".to_string());    let branch = row        .git_branch        .as_ref()        .map(|branch| format!("{SESSION_META_BRANCH_ICON} {branch}"))        .unwrap_or_else(|| format!("{SESSION_META_BRANCH_ICON} no branch"));    vec![        expanded_detail_line("Session:", &session, width),        expanded_time_detail_line("Created:", reference, row.created_at, width),        expanded_time_detail_line(            "Updated:",            reference,            row.updated_at.or(row.created_at),            width,        ),        expanded_detail_line("Directory:", &directory, width),        expanded_detail_line("Branch:", &branch, width),        vec!["  │".dim()].into(),        vec!["  │ ".dim(), "Conversation:".dim()].into(),    ]}fn render_conversation_preview_lines(    lines: &[TranscriptPreviewLine],    width: u16,) -> Vec<Line<'static>> {    if lines.is_empty() {        return vec![            vec![                "  └ ".dim(),                "No transcript preview available".italic().dim(),            ]            .into(),        ];    }    let mut rendered = Vec::new();    for line in lines {        rendered.extend(render_transcript_content_lines(line, width));    }    let rendered_len = rendered.len();    rendered        .into_iter()        .enumerate()        .map(|(idx, line)| {            let prefix = if idx + 1 == rendered_len {                "  └ "            } else {                "  │ "            };            prefix_transcript_line(prefix, line)        })        .collect()}fn render_transcript_content_lines(line: &TranscriptPreviewLine, width: u16) -> Vec<Line<'static>> {    let content_width = width.saturating_sub(4) as usize;    let lines = match line.speaker {        TranscriptPreviewSpeaker::User => vec![conversation_content_line(            Line::from(line.text.clone()),            conversation_user_style(),        )],        TranscriptPreviewSpeaker::Assistant => {            let mut lines = Vec::new();            append_markdown(                &line.text, /*width*/ None, /*cwd*/ None, &mut lines,            );            for line in &mut lines {                *line = conversation_content_line(line.clone(), conversation_assistant_style());            }            lines        }    };    adaptive_wrap_lines(lines, RtOptions::new(content_width.max(/*other*/ 1)))}fn conversation_content_line(mut line: Line<'static>, style: Style) -> Line<'static> {    line.style = line.style.patch(style);    for span in &mut line.spans {        span.style = span.style.patch(style);    }    line}fn prefix_transcript_line(prefix: &'static str, line: Line<'static>) -> Line<'static> {    let mut spans = vec![prefix.set_style(transcript_prefix_style(&line))];    spans.extend(line.spans);    Line::from(spans).style(line.style)}fn transcript_prefix_style(line: &Line<'_>) -> Style {    let style = line        .spans        .iter()        .find(|span| !span.content.trim().is_empty())        .map(|span| line.style.patch(span.style))        .unwrap_or(line.style);    connector_style_from_content(style)}fn connector_style_from_content(style: Style) -> Style {    Style {        fg: style.fg,        bg: style.bg,        ..Style::default()    }}fn conversation_assistant_style() -> Style {    if default_bg().is_some_and(is_light) {        Style::default().fg(Color::Gray)    } else {        Style::default().fg(Color::DarkGray)    }}fn conversation_user_style() -> Style {    if default_bg().is_some_and(is_light) {        Style::default().fg(Color::DarkGray).italic()    } else {        Style::default().fg(Color::Gray).italic()    }}fn expanded_detail_line(label: &'static str, value: &str, width: u16) -> Line<'static> {    const LABEL_WIDTH: usize = 10;    let prefix_width = 4;    let gap_width = 2;    let value_width = (width as usize)        .saturating_sub(prefix_width + LABEL_WIDTH + gap_width)        .max(1);    vec![        "  │ ".dim(),        format!("{label:<LABEL_WIDTH$}").dim(),        "  ".dim(),        truncate_text(value, value_width).into(),    ]    .into()}fn expanded_time_detail_line(    label: &'static str,    reference: DateTime<Utc>,    ts: Option<DateTime<Utc>>,    width: u16,) -> Line<'static> {    let Some(ts) = ts else {        return expanded_detail_line(label, "-", width);    };    let value = format!(        "{} · {}",        format_relative_time_long(reference, ts),        format_timestamp(ts)    );    expanded_detail_line(label, &value, width)}fn format_relative_time(reference: DateTime<Utc>, ts: Option<DateTime<Utc>>) -> String {    let Some(ts) = ts else {        return "-".to_string();    };    let seconds = (reference - ts).num_seconds().max(0);    if seconds == 0 {        return "now".to_string();    }    if seconds < 60 {        return format!("{seconds}s ago");    }    let minutes = seconds / 60;    if minutes < 60 {        return format!("{minutes}m ago");    }    let hours = minutes / 60;    if hours < 24 {        return format!("{hours}h ago");    }    let days = hours / 24;    format!("{days}d ago")}fn format_relative_time_long(reference: DateTime<Utc>, ts: DateTime<Utc>) -> String {    let seconds = (reference - ts).num_seconds().max(0);    if seconds == 0 {        return "now".to_string();    }    if seconds < 60 {        return plural_time(seconds, "second");    }    let minutes = seconds / 60;    if minutes < 60 {        return plural_time(minutes, "minute");    }    let hours = minutes / 60;    if hours < 24 {        return plural_time(hours, "hour");    }    plural_time(hours / 24, "day")}fn plural_time(value: i64, unit: &str) -> String {    if value == 1 {        format!("1 {unit} ago")    } else {        format!("{value} {unit}s ago")    }}fn format_timestamp(ts: DateTime<Utc>) -> String {    ts.format("%Y-%m-%d %H:%M:%S").to_string()}fn render_empty_state_line(state: &PickerState) -> Line<'static> {    if !state.query.is_empty() {        if state.search_state.is_active()            || (state.pagination.loading.is_pending() && state.pagination.next_cursor.is_some())        {            return vec!["Searching…".italic().dim()].into();        }        if state.pagination.reached_scan_cap {            let msg = format!(                "Search scanned first {} sessions; more may exist",                state.pagination.num_scanned_files            );            return vec![Span::from(msg).italic().dim()].into();        }        return vec!["No results for your search".italic().dim()].into();    }    if state.pagination.loading.is_pending() {        if state.all_rows.is_empty() && state.pagination.num_scanned_files == 0 {            return vec!["Loading sessions…".italic().dim()].into();        }        return vec!["Loading older sessions…".italic().dim()].into();    }    vec!["No sessions yet".italic().dim()].into()}#[cfg(test)]mod tests {    use super::*;    use chrono::Duration;    use codex_app_server_protocol::ThreadSourceKind;    use codex_config::CONFIG_TOML_FILE;    use codex_protocol::ThreadId;    use codex_utils_absolute_path::test_support::PathBufExt;    use codex_utils_absolute_path::test_support::test_path_buf;    use crossterm::event::KeyCode;    use crossterm::event::KeyEvent;    use crossterm::event::KeyModifiers;    use insta::assert_snapshot;    use pretty_assertions::assert_eq;    use std::path::Path;    use std::path::PathBuf;    use std::sync::Arc;    use std::sync::Mutex;    use tempfile::tempdir;    fn page(        rows: Vec<Row>,        next_cursor: Option<&str>,        num_scanned_files: usize,        reached_scan_cap: bool,    ) -> PickerPage {        PickerPage {            rows,            next_cursor: next_cursor.map(|cursor| PageCursor::AppServer(cursor.to_string())),            num_scanned_files,            reached_scan_cap,        }    }    fn page_only_loader(loader: impl Fn(PageLoadRequest) + Send + Sync + 'static) -> PickerLoader {        Arc::new(move |request| {            if let PickerLoadRequest::Page(request) = request {                loader(request);            }        })    }    fn make_row(path: &str, ts: &str, preview: &str) -> Row {        let timestamp = parse_timestamp_str(ts).expect("timestamp should parse");        Row {            path: Some(PathBuf::from(path)),            preview: preview.to_string(),            thread_id: None,            thread_name: None,            created_at: Some(timestamp),            updated_at: Some(timestamp),            cwd: None,            git_branch: None,        }    }    fn footer_lines_text(state: &PickerState, width: u16) -> String {        footer_hint_lines(state, width)            .into_iter()            .map(|line| line.to_string())            .collect::<Vec<_>>()            .join("\n")    }    fn footer_snapshot(state: &PickerState, width: u16, list_height: u16) -> String {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let backend = VT100Backend::new(width, /*height*/ 4);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, 4));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_picker_footer(&mut frame, area, state, list_height);        }        terminal.flush().expect("flush");        terminal            .backend()            .to_string()            .lines()            .map(str::trim_end)            .collect::<Vec<_>>()            .join("\n")    }    #[test]    fn row_display_preview_prefers_thread_name() {        let row = Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from("first message"),            thread_id: None,            thread_name: Some(String::from("My session")),            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        };        assert_eq!(row.display_preview(), "My session");    }    #[test]    fn local_picker_thread_list_params_include_cwd_filter() {        let cwd_filter = picker_cwd_filter(            Path::new("/tmp/project"),            /*show_all*/ false,            /*uses_remote_workspace*/ false,            /*remote_cwd_override*/ None,        );        let params = thread_list_params(            Some(String::from("cursor-1")),            cwd_filter.as_deref(),            ProviderFilter::MatchDefault(String::from("openai")),            ThreadSortKey::UpdatedAt,            /*include_non_interactive*/ false,        );        assert_eq!(            params.cwd,            Some(ThreadListCwdFilter::One(String::from("/tmp/project")))        );    }    #[test]    fn row_search_matches_metadata_fields() {        let thread_id =            ThreadId::from_string("019dabc1-0ef5-7431-b81c-03037f51f62c").expect("thread id");        let row = Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from("first message"),            thread_id: Some(thread_id),            thread_name: Some(String::from("My session")),            created_at: None,            updated_at: None,            cwd: Some(PathBuf::from("/tmp/codex-session-picker")),            git_branch: Some(String::from("fcoury/session-picker")),        };        assert!(row.matches_query("session-picker"));        assert!(row.matches_query("fcoury"));        assert!(row.matches_query(&thread_id.to_string()[..8]));    }    #[test]    fn relative_time_formats_zero_seconds_as_now() {        let reference = DateTime::parse_from_rfc3339("2026-05-02T12:00:00Z")            .expect("valid timestamp")            .with_timezone(&Utc);        assert_eq!(format_relative_time(reference, Some(reference)), "now");        assert_eq!(            format_relative_time(reference, Some(reference - Duration::seconds(1))),            "1s ago"        );    }    #[test]    fn long_relative_time_uses_words() {        let reference = DateTime::parse_from_rfc3339("2026-05-02T12:00:00Z")            .expect("valid timestamp")            .with_timezone(&Utc);        assert_eq!(format_relative_time_long(reference, reference), "now");        assert_eq!(            format_relative_time_long(reference, reference - Duration::minutes(20)),            "20 minutes ago"        );        assert_eq!(            format_relative_time_long(reference, reference - Duration::hours(1)),            "1 hour ago"        );    }    #[test]    fn expanded_session_details_include_metadata() {        let thread_id =            ThreadId::from_string("019dabc1-0ef5-7431-b81c-03037f51f62c").expect("thread id");        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.relative_time_reference = parse_timestamp_str("2026-05-02T14:48:19Z");        let row = Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from("first message"),            thread_id: Some(thread_id),            thread_name: Some(String::from("feat(tui): add raw scrollback mode")),            created_at: parse_timestamp_str("2026-05-02T14:31:08Z"),            updated_at: parse_timestamp_str("2026-05-02T14:48:19Z"),            cwd: Some(PathBuf::from("/Users/felipe.coury/code/codex")),            git_branch: Some(String::from("codex/raw-scrollback-mode")),        };        let rendered = render_expanded_session_details(&row, &state, /*width*/ 120)            .into_iter()            .map(|line| line.to_string())            .collect::<Vec<_>>()            .join("\n");        let expected_directory =            format_directory_display(row.cwd.as_deref().expect("cwd"), /*max_width*/ None);        assert!(rendered.contains(            "Session:    feat(tui): add raw scrollback mode (019dabc1-0ef5-7431-b81c-03037f51f62c)"        ));        assert!(rendered.contains("Created:    17 minutes ago · 2026-05-02 14:31:08"));        assert!(rendered.contains("Updated:    now · 2026-05-02 14:48:19"));        assert!(rendered.contains(&format!("Directory:  {expected_directory}")));        assert!(rendered.contains("Branch:      codex/raw-scrollback-mode"));        assert!(rendered.contains("Conversation:"));    }    #[test]    fn footer_prioritizes_active_sort_timestamp() {        let updated = render_footer_lines(            ThreadSortKey::UpdatedAt,            "5h ago",            "3h ago",            Some("main"),            Some("tmp/codex"),            /*show_cwd*/ true,            /*width*/ 80,        );        let created = render_footer_lines(            ThreadSortKey::CreatedAt,            "5h ago",            "3h ago",            Some("main"),            Some("tmp/codex"),            /*show_cwd*/ true,            /*width*/ 80,        );        assert_eq!(updated.len(), 1);        assert_eq!(created.len(), 1);        assert!(updated[0].to_string().starts_with("  3h ago"));        assert!(created[0].to_string().starts_with("  5h ago"));        assert!(!updated[0].to_string().contains("created 5h ago"));        assert!(!created[0].to_string().contains("updated 3h ago"));        assert_metadata_order(&updated[0], "⌁ tmp/codex", " main");        assert_metadata_order(&created[0], "⌁ tmp/codex", " main");    }    #[test]    fn footer_marks_missing_branch() {        let footer = render_footer_lines(            ThreadSortKey::UpdatedAt,            "5h ago",            "3h ago",            /*branch*/ None,            Some("/tmp/codex"),            /*show_cwd*/ true,            /*width*/ 80,        );        assert_eq!(footer.len(), 1);        let rendered = footer[0].to_string();        assert!(rendered.contains("⌁ /tmp/codex"));        assert!(rendered.contains(" no branch"));        assert_metadata_order(&footer[0], "⌁ /tmp/codex", " no branch");    }    #[test]    fn footer_branch_expands_when_line_has_room() {        let branch = "etraut/animations-false-improvements";        let footer = render_footer_lines(            ThreadSortKey::UpdatedAt,            "5h ago",            "4h ago",            Some(branch),            Some("~/code/codex.etraut-animations-false-improvements/codex-rs"),            /*show_cwd*/ true,            /*width*/ 140,        );        assert_eq!(footer.len(), 1);        assert!(footer[0].to_string().contains(branch));    }    #[test]    fn footer_cwd_truncates_to_responsive_column() {        let cwd = "~/code/codex.owner-extremely-long-worktree-name-that-needs-truncating/codex-rs";        let branch = "owner/branch";        let footer = render_footer_lines(            ThreadSortKey::UpdatedAt,            "5h ago",            "4h ago",            Some(branch),            Some(cwd),            /*show_cwd*/ true,            /*width*/ 80,        );        assert_eq!(footer.len(), 1);        let footer = footer[0].to_string();        assert!(!footer.contains(cwd));        assert!(footer.contains("⌁ ~/code/codex."));        assert!(footer.contains("..."));        assert!(footer.contains(" owner/branch"));    }    #[test]    fn footer_omits_cwd_when_hidden() {        let footer = render_footer_lines(            ThreadSortKey::UpdatedAt,            "5h ago",            "4h ago",            Some("owner/branch"),            Some("~/code/codex.owner-worktree/codex-rs"),            /*show_cwd*/ false,            /*width*/ 80,        );        assert_eq!(footer.len(), 1);        let footer = footer[0].to_string();        assert!(footer.contains("4h ago"));        assert!(footer.contains(" owner/branch"));        assert!(!footer.contains("⌁"));        assert!(!footer.contains("~/code"));    }    fn assert_metadata_order(line: &Line<'_>, first: &str, second: &str) {        let rendered = line.to_string();        let first_index = rendered.find(first).expect("first metadata item");        let second_index = rendered.find(second).expect("second metadata item");        assert!(first_index < second_index);    }    #[test]    fn remote_thread_list_params_omit_provider_filter() {        let params = thread_list_params(            Some(String::from("cursor-1")),            Some(Path::new("repo/on/server")),            ProviderFilter::Any,            ThreadSortKey::UpdatedAt,            /*include_non_interactive*/ false,        );        assert_eq!(params.cursor, Some(String::from("cursor-1")));        assert_eq!(params.model_providers, None);        assert_eq!(            params.source_kinds,            Some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode])        );        assert_eq!(            params.cwd,            Some(ThreadListCwdFilter::One(String::from("repo/on/server")))        );    }    #[test]    fn remote_thread_list_params_can_include_non_interactive_sources() {        let params = thread_list_params(            Some(String::from("cursor-1")),            /*cwd_filter*/ None,            ProviderFilter::Any,            ThreadSortKey::UpdatedAt,            /*include_non_interactive*/ true,        );        assert_eq!(params.cursor, Some(String::from("cursor-1")));        assert_eq!(params.model_providers, None);        let source_kinds = crate::resume_source_kinds(/*include_non_interactive*/ true);        assert_eq!(params.source_kinds, Some(source_kinds));    }    #[test]    fn remote_picker_sends_cwd_filter_without_local_post_filtering() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let remote_cwd = Some(PathBuf::from("/srv/link-project"));        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::Any,            /*show_all*/ false,            remote_cwd.clone(),            SessionPickerAction::Resume,        );        state.local_filter_cwd =            local_picker_cwd_filter(&remote_cwd, /*uses_remote_workspace*/ true);        state.start_initial_load();        {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 1);            assert_eq!(guard[0].cwd_filter, remote_cwd);        }        let row = Row {            path: None,            preview: String::from("remote session"),            thread_id: Some(ThreadId::new()),            thread_name: None,            created_at: None,            updated_at: None,            cwd: Some(PathBuf::from("/srv/real-project")),            git_branch: None,        };        assert!(state.row_matches_filter(&row));    }    #[test]    fn remote_picker_does_not_filter_rows_by_local_cwd() {        let loader = page_only_loader(|_| {});        let state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::Any,            /*show_all*/ false,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let row = Row {            path: None,            preview: String::from("remote session"),            thread_id: Some(ThreadId::new()),            thread_name: None,            created_at: None,            updated_at: None,            cwd: Some(PathBuf::from("/srv/remote-project")),            git_branch: None,        };        assert!(state.row_matches_filter(&row));    }    #[test]    fn resume_table_snapshot() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let now = parse_timestamp_str("2026-04-28T16:30:00Z").expect("timestamp");        let rows = vec![            Row {                path: Some(PathBuf::from("/tmp/a.jsonl")),                preview: String::from("Fix resume picker timestamps"),                thread_id: None,                thread_name: None,                created_at: Some(now - Duration::minutes(16)),                updated_at: Some(now - Duration::seconds(42)),                cwd: None,                git_branch: None,            },            Row {                path: Some(PathBuf::from("/tmp/b.jsonl")),                preview: String::from("Investigate lazy pagination cap"),                thread_id: None,                thread_name: None,                created_at: Some(now - Duration::hours(1)),                updated_at: Some(now - Duration::minutes(35)),                cwd: None,                git_branch: None,            },            Row {                path: Some(PathBuf::from("/tmp/c.jsonl")),                preview: String::from("Explain the codebase"),                thread_id: None,                thread_name: None,                created_at: Some(now - Duration::hours(2)),                updated_at: Some(now - Duration::hours(2)),                cwd: None,                git_branch: None,            },        ];        state.all_rows = rows.clone();        state.filtered_rows = rows;        state.relative_time_reference = Some(now);        state.selected = 1;        state.scroll_top = 0;        state.update_viewport(/*rows*/ 12, /*width*/ 80);        let width: u16 = 80;        let height: u16 = 12;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        let snapshot = terminal.backend().to_string();        assert_snapshot!("resume_picker_table", snapshot);    }    #[test]    fn resume_search_error_snapshot() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.inline_error = Some(String::from(            "Failed to read session metadata from /tmp/missing.jsonl",        ));        let width: u16 = 80;        let height: u16 = 1;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        {            let mut frame = terminal.get_frame();            let line = search_line(&state, frame.area().width);            frame.render_widget_ref(line, frame.area());        }        terminal.flush().expect("flush");        let snapshot = terminal.backend().to_string();        assert_snapshot!("resume_picker_search_error", snapshot);    }    #[test]    fn hint_line_switches_esc_label_for_search_mode() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        assert!(footer_lines_text(&state, /*width*/ 220).contains("esc start new"));        state.query = String::from("picker");        assert!(footer_lines_text(&state, /*width*/ 220).contains("esc clear search"));    }    #[test]    fn hint_line_labels_cancel_keys_as_exit_for_existing_session_resume_picker() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.launch_context = SessionPickerLaunchContext::ExistingSession;        let wide = footer_lines_text(&state, /*width*/ 220);        assert!(wide.contains("esc exit"));        assert!(wide.contains("ctrl+c exit"));        let compact = footer_lines_text(&state, /*width*/ 119);        assert!(compact.contains("esc exit"));        assert!(compact.contains("ctrl+c exit"));        state.query = String::from("picker");        assert!(footer_lines_text(&state, /*width*/ 220).contains("esc clear search"));    }    #[test]    fn hint_line_switches_density_label() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        assert!(footer_lines_text(&state, /*width*/ 220).contains("ctrl+o dense view"));        assert!(footer_lines_text(&state, /*width*/ 220).contains("ctrl+t transcript"));        assert!(footer_lines_text(&state, /*width*/ 220).contains("ctrl+e expand"));        state.density = SessionListDensity::Dense;        assert!(footer_lines_text(&state, /*width*/ 220).contains("ctrl+o comfortable view"));    }    #[test]    fn hint_line_compacts_on_narrow_width() {        let loader = page_only_loader(|_| {});        let state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let rendered = footer_lines_text(&state, /*width*/ 119);        assert!(rendered.contains("esc new"));        assert!(rendered.contains("tab focus"));        assert!(rendered.contains("←/→ option"));        assert!(rendered.contains("ctrl+o dense"));        assert!(rendered.contains("ctrl+t preview"));        assert!(rendered.contains("ctrl+e exp"));        assert!(!rendered.contains("focus sort/filter"));    }    #[test]    fn hint_line_snapshot_uses_distributed_wide_footer() {        let loader = page_only_loader(|_| {});        let state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        assert_snapshot!(            "resume_picker_footer_wide",            footer_snapshot(&state, /*width*/ 220, /*list_height*/ 20)        );    }    #[test]    fn hint_line_snapshot_uses_compact_footer() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.query = String::from("picker");        state.density = SessionListDensity::Dense;        assert_snapshot!(            "resume_picker_footer_compact",            footer_snapshot(&state, /*width*/ 96, /*list_height*/ 20)        );    }    #[test]    fn hint_line_prioritizes_keybinds_when_very_narrow() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.density = SessionListDensity::Dense;        let width = 38;        let lines = footer_hint_lines(&state, width);        let rendered = lines            .iter()            .map(Line::to_string)            .collect::<Vec<_>>()            .join("\n");        assert!(lines.iter().all(|line| line.width() <= width as usize));        assert!(rendered.contains("enter"));        assert!(rendered.contains("esc"));        assert!(rendered.contains("ctrl+c"));        assert!(rendered.contains("ctrl+o"));        assert!(rendered.contains("ctrl+t"));        assert!(rendered.contains("ctrl+e"));        assert!(rendered.contains("↑/↓"));    }    #[test]    fn hint_line_shows_loading_transcript_mode() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.pending_transcript_open = Some(ThreadId::new());        let rendered = footer_lines_text(&state, /*width*/ 80);        assert!(rendered.contains("loading transcript"));        assert!(rendered.contains("ctrl+c quit"));        assert!(!rendered.contains("enter"));    }    #[test]    fn picker_footer_percent_reports_scroll_progress() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = (0..10)            .map(|idx| {                make_row(                    &format!("/tmp/{idx}.jsonl"),                    "2026-05-02T12:00:00Z",                    &format!("row {idx}"),                )            })            .collect();        state.scroll_top = 0;        assert_eq!(picker_footer_percent(&state, /*list_height*/ 6), 0);        state.scroll_top = state.filtered_rows.len() - 1;        assert_eq!(picker_footer_percent(&state, /*list_height*/ 6), 100);    }    #[test]    fn picker_footer_progress_label_shows_position_total_and_percent() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = (0..10)            .map(|idx| {                make_row(                    &format!("/tmp/{idx}.jsonl"),                    "2026-05-02T12:00:00Z",                    &format!("row {idx}"),                )            })            .collect();        state.selected = 2;        let label = picker_footer_progress_label(&state, /*list_height*/ 6, /*width*/ 80);        assert_eq!(label, " 3 / 10 · 0% ");        assert!(!label.contains('-'));    }    #[test]    fn picker_footer_progress_label_uses_known_count_when_more_pages_exist() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = (0..10)            .map(|idx| {                make_row(                    &format!("/tmp/{idx}.jsonl"),                    "2026-05-02T12:00:00Z",                    &format!("row {idx}"),                )            })            .collect();        state.selected = 2;        state.pagination.next_cursor = Some(PageCursor::AppServer(String::from("cursor-1")));        let label = picker_footer_progress_label(&state, /*list_height*/ 6, /*width*/ 80);        assert_eq!(label, " 3 / 10 · 0% ");    }    #[test]    fn picker_footer_progress_label_freezes_percent_while_loading() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = (0..10)            .map(|idx| {                make_row(                    &format!("/tmp/{idx}.jsonl"),                    "2026-05-02T12:00:00Z",                    &format!("row {idx}"),                )            })            .collect();        state.selected = 9;        state.scroll_top = 9;        state.pagination.next_cursor = Some(PageCursor::AppServer(String::from("cursor-1")));        state.pagination.loading = LoadingState::Pending(PendingLoad {            request_token: 1,            search_token: None,        });        state.frozen_footer_percent = Some(37);        let label = picker_footer_progress_label(&state, /*list_height*/ 6, /*width*/ 80);        assert_eq!(label, " 10 / 10… · 37% ");    }    #[test]    fn picker_footer_percent_is_complete_when_not_scrollable() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        assert_eq!(picker_footer_percent(&state, /*list_height*/ 20), 100);        state.filtered_rows = vec![make_row(            "/tmp/1.jsonl",            "2026-05-02T12:00:00Z",            "single row",        )];        assert_eq!(picker_footer_percent(&state, /*list_height*/ 20), 100);    }    #[tokio::test]    async fn ctrl_o_toggles_density_without_typing_into_search() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.query = String::from("pick");        state            .handle_key(KeyEvent::new(KeyCode::Char('o'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.density, SessionListDensity::Dense);        assert_eq!(state.query, "pick");    }    #[tokio::test]    async fn ctrl_t_requests_selected_session_transcript() {        let thread_id = ThreadId::new();        let recorded_requests: Arc<Mutex<Vec<ThreadId>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader: PickerLoader = Arc::new(move |request| {            if let PickerLoadRequest::Transcript { thread_id } = request {                request_sink.lock().unwrap().push(thread_id);            }        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![Row {            path: None,            preview: String::from("preview"),            thread_id: Some(thread_id),            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        }];        state            .handle_key(KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.density, SessionListDensity::Comfortable);        assert_eq!(*recorded_requests.lock().unwrap(), vec![thread_id]);        assert_eq!(state.pending_transcript_open, Some(thread_id));        assert!(matches!(            state.transcript_cells.get(&thread_id),            Some(SessionTranscriptState::Loading)        ));    }    #[tokio::test]    async fn transcript_loading_consumes_picker_input() {        let loader = page_only_loader(|_| {});        let thread_id = ThreadId::new();        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![            Row {                path: None,                preview: String::from("one"),                thread_id: Some(ThreadId::new()),                thread_name: None,                created_at: None,                updated_at: None,                cwd: None,                git_branch: None,            },            Row {                path: None,                preview: String::from("two"),                thread_id: Some(ThreadId::new()),                thread_name: None,                created_at: None,                updated_at: None,                cwd: None,                git_branch: None,            },        ];        state.pending_transcript_open = Some(thread_id);        let selection = state            .handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE))            .await            .unwrap();        assert!(selection.is_none());        assert_eq!(state.selected, 0);        assert_eq!(state.query, "");        let selection = state            .handle_key(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE))            .await            .unwrap();        assert!(selection.is_none());        assert_eq!(state.query, "");    }    #[tokio::test]    async fn transcript_loading_still_allows_ctrl_c_exit() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.pending_transcript_open = Some(ThreadId::new());        let selection = state            .handle_key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL))            .await            .unwrap();        assert!(matches!(selection, Some(SessionSelection::Exit)));    }    #[test]    fn transcript_loading_overlay_snapshot() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let thread_id = ThreadId::new();        state.pending_transcript_open = Some(thread_id);        state.filtered_rows = vec![            Row {                path: None,                preview: String::from("Find pending threads and emails"),                thread_id: Some(thread_id),                thread_name: None,                created_at: None,                updated_at: None,                cwd: None,                git_branch: None,            },            Row {                path: None,                preview: String::from("Plan raw scrollback mode"),                thread_id: Some(ThreadId::new()),                thread_name: None,                created_at: None,                updated_at: None,                cwd: None,                git_branch: None,            },        ];        state.update_viewport(/*rows*/ 7, /*width*/ 80);        let width: u16 = 80;        let height: u16 = 7;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);            render_transcript_loading_overlay(&mut frame, area);        }        terminal.flush().expect("flush");        let snapshot = terminal            .backend()            .to_string()            .lines()            .map(str::trim_end)            .collect::<Vec<_>>()            .join("\n");        assert_snapshot!("resume_picker_transcript_loading_overlay", snapshot);    }    #[tokio::test]    async fn raw_ctrl_t_requests_selected_session_transcript() {        let thread_id = ThreadId::new();        let recorded_requests: Arc<Mutex<Vec<ThreadId>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader: PickerLoader = Arc::new(move |request| {            if let PickerLoadRequest::Transcript { thread_id } = request {                request_sink.lock().unwrap().push(thread_id);            }        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![Row {            path: None,            preview: String::from("preview"),            thread_id: Some(thread_id),            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        }];        state            .handle_key(KeyEvent::new(KeyCode::Char('\u{0014}'), KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(*recorded_requests.lock().unwrap(), vec![thread_id]);    }    #[tokio::test]    async fn ctrl_t_on_row_without_thread_id_shows_inline_error() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from("preview"),            thread_id: None,            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        }];        state            .handle_key(KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(            state.inline_error.as_deref(),            Some("No transcript available for this session")        );    }    #[tokio::test]    async fn loaded_transcript_waits_for_loading_frame_before_opening_overlay() {        use crate::history_cell::PlainHistoryCell;        let thread_id = ThreadId::new();        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.pending_transcript_open = Some(thread_id);        let cells: TranscriptCells =            vec![Arc::new(PlainHistoryCell::new(vec!["transcript".into()]))];        state            .handle_background_event(BackgroundEvent::Transcript {                thread_id,                transcript: Ok(cells),            })            .await            .unwrap();        assert!(state.overlay.is_none());        assert_eq!(state.pending_transcript_open, Some(thread_id));        assert!(matches!(            state.transcript_cells.get(&thread_id),            Some(SessionTranscriptState::Loaded(_))        ));        assert!(state.note_transcript_loading_frame_drawn());        state.open_pending_transcript_if_ready();        assert!(matches!(state.overlay, Some(Overlay::Transcript(_))));        assert_eq!(state.pending_transcript_open, None);    }    #[tokio::test]    async fn cached_transcript_still_shows_loading_frame_before_opening_overlay() {        use crate::history_cell::PlainHistoryCell;        let thread_id = ThreadId::new();        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![Row {            path: None,            preview: String::from("preview"),            thread_id: Some(thread_id),            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        }];        state.transcript_cells.insert(            thread_id,            SessionTranscriptState::Loaded(vec![Arc::new(PlainHistoryCell::new(vec![                "transcript".into(),            ]))]),        );        state            .handle_key(KeyEvent::new(KeyCode::Char('t'), KeyModifiers::CONTROL))            .await            .unwrap();        assert!(state.overlay.is_none());        assert_eq!(state.pending_transcript_open, Some(thread_id));        assert!(state.note_transcript_loading_frame_drawn());        state.open_pending_transcript_if_ready();        assert!(matches!(state.overlay, Some(Overlay::Transcript(_))));        assert_eq!(state.pending_transcript_open, None);    }    #[tokio::test]    async fn ctrl_o_persists_density_preference() {        let tmp = tempdir().expect("tmpdir");        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.view_persistence = Some(SessionPickerViewPersistence {            codex_home: tmp.path().to_path_buf(),        });        state            .handle_key(KeyEvent::new(KeyCode::Char('o'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.density, SessionListDensity::Dense);        let contents =            std::fs::read_to_string(tmp.path().join(CONFIG_TOML_FILE)).expect("read config");        assert_eq!(            contents,            r#"[tui]session_picker_view = "dense""#        );    }    #[tokio::test]    async fn ctrl_o_keeps_toggled_density_when_persistence_fails() {        let tmp = tempdir().expect("tmpdir");        let codex_home_file = tmp.path().join("codex-home-file");        std::fs::write(&codex_home_file, "not a directory").expect("write codex home file");        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.view_persistence = Some(SessionPickerViewPersistence {            codex_home: codex_home_file,        });        state            .handle_key(KeyEvent::new(KeyCode::Char('o'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.density, SessionListDensity::Dense);        assert!(            state                .inline_error                .as_deref()                .is_some_and(|error| error.contains("Failed to save view mode")),            "expected persistence error, got {:?}",            state.inline_error        );    }    #[tokio::test]    async fn raw_ctrl_o_toggles_density_without_typing_into_search() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.query = String::from("pick");        state            .handle_key(KeyEvent::new(KeyCode::Char('\u{000f}'), KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.density, SessionListDensity::Dense);        assert_eq!(state.query, "pick");    }    #[tokio::test]    async fn space_appends_to_search_query() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.query = String::from("resize");        state            .handle_key(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE))            .await            .unwrap();        state            .handle_key(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.query, "resize r");        assert_eq!(state.expanded_thread_id, None);    }    #[tokio::test]    async fn ctrl_e_toggles_selected_session_expansion() {        let thread_id = ThreadId::new();        let recorded_requests: Arc<Mutex<Vec<ThreadId>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader: PickerLoader = Arc::new(move |request| {            if let PickerLoadRequest::Preview { thread_id } = request {                request_sink.lock().unwrap().push(thread_id);            }        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![Row {            path: None,            preview: String::from("preview"),            thread_id: Some(thread_id),            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        }];        state            .handle_key(KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.expanded_thread_id, Some(thread_id));        assert_eq!(*recorded_requests.lock().unwrap(), vec![thread_id]);        state            .handle_key(KeyEvent::new(KeyCode::Char('e'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.expanded_thread_id, None);    }    #[tokio::test]    async fn raw_ctrl_e_toggles_selected_session_expansion() {        let thread_id = ThreadId::new();        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.filtered_rows = vec![Row {            path: None,            preview: String::from("preview"),            thread_id: Some(thread_id),            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        }];        state            .handle_key(KeyEvent::new(KeyCode::Char('\u{0005}'), KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.expanded_thread_id, Some(thread_id));    }    #[test]    fn search_line_renders_sort_and_filter_tabs() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ false,            Some(PathBuf::from("/tmp/project")),            SessionPickerAction::Resume,        );        let width: u16 = 100;        let backend = VT100Backend::new(width, /*height*/ 1);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, 1));        {            let mut frame = terminal.get_frame();            let line = search_line(&state, frame.area().width);            frame.render_widget_ref(line, frame.area());        }        terminal.flush().expect("flush");        assert_snapshot!(            "resume_picker_search_line_sort_filter_tabs",            terminal.backend().to_string()        );    }    #[test]    fn search_line_compacts_toolbar_on_narrow_width() {        let loader = page_only_loader(|_| {});        let state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ false,            Some(PathBuf::from("/tmp/project")),            SessionPickerAction::Resume,        );        let line = search_line(&state, /*width*/ 40).to_string();        assert!(line.contains("Filter:[Cwd]"));        assert!(line.contains("Sort:[Updated]"));        assert!(line.find("Filter:[Cwd]") < line.find("Sort:[Updated]"));    }    fn dense_snapshot_row() -> Row {        Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from(                "Propose session picker redesign with enough title text to exercise truncation",            ),            thread_id: Some(                ThreadId::from_string("019dabc1-0ef5-7431-b81c-03037f51f62c").expect("thread id"),            ),            thread_name: None,            created_at: parse_timestamp_str("2026-04-28T16:30:00Z"),            updated_at: parse_timestamp_str("2026-04-28T17:45:00Z"),            cwd: Some(PathBuf::from(                "/Users/felipe.coury/code/codex.fcoury-session-picker/codex-rs",            )),            git_branch: Some(String::from("fcoury/session-picker")),        }    }    fn render_dense_row_snapshot(        show_all: bool,        filter_cwd: Option<PathBuf>,        width: u16,    ) -> String {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let row = dense_snapshot_row();        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            show_all,            filter_cwd,            SessionPickerAction::Resume,        );        state.density = SessionListDensity::Dense;        state.all_rows = vec![row.clone()];        state.filtered_rows = vec![row];        state.relative_time_reference =            Some(parse_timestamp_str("2026-04-28T18:00:00Z").expect("timestamp"));        let backend = VT100Backend::new(width, /*height*/ 3);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, 3));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        terminal.backend().to_string()    }    #[test]    fn dense_session_snapshot_omits_cwd_in_cwd_filter() {        assert_snapshot!(            "resume_picker_dense_cwd",            render_dense_row_snapshot(                /*show_all*/ false,                Some(PathBuf::from(                    "/Users/felipe.coury/code/codex.fcoury-session-picker/codex-rs"                )),                /*width*/ 100,            )        );    }    #[test]    fn dense_session_snapshot_includes_cwd_in_all_filter() {        assert_snapshot!(            "resume_picker_dense_all",            render_dense_row_snapshot(                /*show_all*/ true, /*filter_cwd*/ None, /*width*/ 120,            )        );    }    #[test]    fn dense_session_snapshot_auto_hides_cwd_when_narrow() {        assert_snapshot!(            "resume_picker_dense_all_auto_hidden_cwd",            render_dense_row_snapshot(                /*show_all*/ true, /*filter_cwd*/ None, /*width*/ 100,            )        );    }    #[test]    fn dense_session_snapshot_forces_cwd_when_narrow() {        assert_snapshot!(            "resume_picker_dense_all_forced_cwd",            render_dense_row_snapshot(                /*show_all*/ true, /*filter_cwd*/ None, /*width*/ 48,            )        );    }    #[test]    fn dense_session_snapshot_drops_metadata_when_narrow() {        assert_snapshot!(            "resume_picker_dense_narrow",            render_dense_row_snapshot(                /*show_all*/ true, /*filter_cwd*/ None, /*width*/ 48,            )        );    }    #[test]    fn dense_session_line_prefers_thread_name_over_preview() {        let mut row = dense_snapshot_row();        row.preview = String::from("Raw conversation preview");        row.thread_name = Some(String::from("Named session"));        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.relative_time_reference =            Some(parse_timestamp_str("2026-04-28T18:00:00Z").expect("timestamp"));        let rendered = render_dense_session_lines(            &row, &state, /*is_selected*/ false, /*is_expanded*/ false,            /*is_zebra*/ false, /*width*/ 100,        )        .into_iter()        .map(|line| line.to_string())        .collect::<Vec<_>>()        .join("\n");        assert!(rendered.contains("Named session"));        assert!(!rendered.contains("Raw conversation preview"));    }    #[test]    fn dense_selected_summary_line_uses_full_width_selection_style() {        let line = dense_summary_line(DenseSummaryInput {            marker: selection_marker(/*is_selected*/ true, /*is_expanded*/ false),            date: "15m ago",            title: "Selected dense row",            is_selected: true,            is_zebra: false,            width: 80,        });        assert_eq!(line.width(), 80);        assert_eq!(line.style.fg, selected_session_style().fg);        assert_eq!(line.spans[0].content, "❯ ");    }    #[test]    fn dense_zebra_summary_line_uses_full_width_background() {        let line = dense_summary_line(DenseSummaryInput {            marker: selection_marker(/*is_selected*/ false, /*is_expanded*/ false),            date: "15m ago",            title: "Zebra dense row",            is_selected: false,            is_zebra: true,            width: 80,        });        assert_eq!(line.width(), 80);        assert_eq!(line.style.bg, dense_zebra_style().bg);    }    #[test]    fn comfortable_zebra_lines_use_full_width_background() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.relative_time_reference =            Some(parse_timestamp_str("2026-05-02T12:00:00Z").expect("timestamp"));        let row = make_row(            "/tmp/a.jsonl",            "2026-05-02T11:45:00Z",            "Zebra comfortable row",        );        let lines = render_comfortable_session_lines(            &row, &state, /*is_selected*/ false, /*is_expanded*/ false,            /*is_zebra*/ true, /*width*/ 100,        );        assert_eq!(lines.len(), 2);        assert!(lines.iter().all(|line| line.width() == 100));        assert!(            lines                .iter()                .all(|line| line.style.bg == dense_zebra_style().bg)        );    }    #[test]    fn dense_session_snapshot_uses_no_blank_lines_between_rows() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let mut first = dense_snapshot_row();        first.preview = String::from("First dense row");        let mut second = dense_snapshot_row();        second.preview = String::from("Second dense row");        second.git_branch = Some(String::from("fcoury/other-branch"));        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ false,            Some(PathBuf::from(                "/Users/felipe.coury/code/codex.fcoury-session-picker/codex-rs",            )),            SessionPickerAction::Resume,        );        state.density = SessionListDensity::Dense;        state.all_rows = vec![first.clone(), second.clone()];        state.filtered_rows = vec![first, second];        state.selected = 1;        state.relative_time_reference =            Some(parse_timestamp_str("2026-04-28T18:00:00Z").expect("timestamp"));        let backend = VT100Backend::new(/*width*/ 80, /*height*/ 2);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, 80, 2));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        assert_snapshot!(            "resume_picker_dense_no_blank_lines",            terminal.backend().to_string()        );    }    #[test]    fn expanded_session_snapshot() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let thread_id =            ThreadId::from_string("019dabc1-0ef5-7431-b81c-03037f51f62c").expect("thread id");        let row = Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from("Investigate picker expansion"),            thread_id: Some(thread_id),            thread_name: None,            created_at: parse_timestamp_str("2026-04-28T16:30:00Z"),            updated_at: parse_timestamp_str("2026-04-28T17:45:00Z"),            cwd: Some(PathBuf::from("/tmp/codex")),            git_branch: Some(String::from("fcoury/session-picker")),        };        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.all_rows = vec![row.clone()];        state.filtered_rows = vec![row];        state.relative_time_reference =            Some(parse_timestamp_str("2026-04-28T18:00:00Z").expect("timestamp"));        state.expanded_thread_id = Some(thread_id);        state.transcript_previews.insert(            thread_id,            TranscriptPreviewState::Loaded(vec![                TranscriptPreviewLine {                    speaker: TranscriptPreviewSpeaker::User,                    text: String::from("Show me the recent transcript"),                },                TranscriptPreviewLine {                    speaker: TranscriptPreviewSpeaker::Assistant,                    text: String::from("Here are the *last* few lines."),                },            ]),        );        let width: u16 = 90;        let height: u16 = 11;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        let rendered = terminal            .backend()            .to_string()            .lines()            .map(str::trim_end)            .collect::<Vec<_>>()            .join("\n");        assert_snapshot!("resume_picker_expanded_session", rendered);    }    #[test]    fn narrow_session_snapshot() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let row = Row {            path: Some(PathBuf::from("/tmp/a.jsonl")),            preview: String::from("Investigate picker expansion"),            thread_id: Some(                ThreadId::from_string("019dabc1-0ef5-7431-b81c-03037f51f62c").expect("thread id"),            ),            thread_name: None,            created_at: parse_timestamp_str("2026-04-28T16:30:00Z"),            updated_at: parse_timestamp_str("2026-04-28T17:45:00Z"),            cwd: Some(PathBuf::from("/tmp/codex")),            git_branch: Some(String::from("fcoury/session-picker")),        };        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.all_rows = vec![row.clone()];        state.filtered_rows = vec![row];        state.relative_time_reference =            Some(parse_timestamp_str("2026-04-28T18:00:00Z").expect("timestamp"));        let width: u16 = 58;        let height: u16 = 6;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        assert_snapshot!(            "resume_picker_narrow_session",            terminal.backend().to_string()        );    }    #[test]    fn session_list_more_indicators_snapshot() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let now = parse_timestamp_str("2026-04-28T16:30:00Z").expect("timestamp");        state.all_rows = (0..5)            .map(|idx| Row {                path: Some(PathBuf::from(format!("/tmp/{idx}.jsonl"))),                preview: format!("item-{idx}"),                thread_id: None,                thread_name: None,                created_at: Some(now - Duration::hours(idx)),                updated_at: Some(now - Duration::minutes(idx * 5)),                cwd: None,                git_branch: None,            })            .collect();        state.filtered_rows = state.all_rows.clone();        state.relative_time_reference = Some(now);        state.selected = 2;        state.scroll_top = 1;        state.update_viewport(/*rows*/ 6, /*width*/ 80);        let width: u16 = 80;        let height: u16 = 6;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        assert_snapshot!(            "resume_picker_more_indicators",            terminal.backend().to_string()        );    }    #[test]    fn density_toggle_clears_stale_more_indicator() {        use crate::custom_terminal::Terminal;        use crate::test_backend::VT100Backend;        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let now = parse_timestamp_str("2026-04-28T16:30:00Z").expect("timestamp");        state.all_rows = (0..4)            .map(|idx| Row {                path: Some(PathBuf::from(format!("/tmp/{idx}.jsonl"))),                preview: format!("item-{idx}"),                thread_id: None,                thread_name: None,                created_at: Some(now - Duration::hours(idx)),                updated_at: Some(now - Duration::minutes(idx * 5)),                cwd: None,                git_branch: None,            })            .collect();        state.filtered_rows = state.all_rows.clone();        state.relative_time_reference = Some(now);        let width: u16 = 80;        let height: u16 = 6;        let backend = VT100Backend::new(width, height);        let mut terminal = Terminal::with_options(backend).expect("terminal");        terminal.set_viewport_area(Rect::new(0, 0, width, height));        state.update_viewport(height as usize, width);        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        assert!(terminal.backend().to_string().contains("↓ more"));        state.density = SessionListDensity::Dense;        state.update_viewport(height as usize, width);        {            let mut frame = terminal.get_frame();            let area = frame.area();            render_list(&mut frame, area, &state);        }        terminal.flush().expect("flush");        assert!(!terminal.backend().to_string().contains("↓ more"));    }    #[test]    fn pageless_scrolling_deduplicates_and_keeps_order() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.reset_pagination();        state.ingest_page(page(            vec![                make_row("/tmp/a.jsonl", "2025-01-03T00:00:00Z", "third"),                make_row("/tmp/b.jsonl", "2025-01-02T00:00:00Z", "second"),            ],            Some("2025-01-02T00:00:00Z"),            /*num_scanned_files*/ 2,            /*reached_scan_cap*/ false,        ));        state.ingest_page(page(            vec![                make_row("/tmp/a.jsonl", "2025-01-03T00:00:00Z", "duplicate"),                make_row("/tmp/c.jsonl", "2025-01-01T00:00:00Z", "first"),            ],            Some("2025-01-01T00:00:00Z"),            /*num_scanned_files*/ 2,            /*reached_scan_cap*/ false,        ));        state.ingest_page(page(            vec![make_row("/tmp/d.jsonl", "2024-12-31T23:00:00Z", "very old")],            /*next_cursor*/ None,            /*num_scanned_files*/ 1,            /*reached_scan_cap*/ false,        ));        let previews: Vec<_> = state            .filtered_rows            .iter()            .map(|row| row.preview.as_str())            .collect();        assert_eq!(previews, vec!["third", "second", "first", "very old"]);        let unique_paths = state            .filtered_rows            .iter()            .map(|row| row.path.clone())            .collect::<std::collections::HashSet<_>>();        assert_eq!(unique_paths.len(), 4);    }    #[test]    fn ensure_minimum_rows_prefetches_when_underfilled() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.reset_pagination();        state.ingest_page(page(            vec![                make_row("/tmp/a.jsonl", "2025-01-01T00:00:00Z", "one"),                make_row("/tmp/b.jsonl", "2025-01-02T00:00:00Z", "two"),            ],            Some("2025-01-03T00:00:00Z"),            /*num_scanned_files*/ 2,            /*reached_scan_cap*/ false,        ));        assert!(recorded_requests.lock().unwrap().is_empty());        state.ensure_minimum_rows_for_view(/*minimum_rows*/ 10);        let guard = recorded_requests.lock().unwrap();        assert_eq!(guard.len(), 1);        assert!(guard[0].search_token.is_none());    }    #[test]    fn ensure_minimum_rows_does_not_prefetch_when_comfortable_cards_fill_view() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.reset_pagination();        state.ingest_page(page(            vec![                make_row("/tmp/a.jsonl", "2025-01-01T00:00:00Z", "one"),                make_row("/tmp/b.jsonl", "2025-01-02T00:00:00Z", "two"),                make_row("/tmp/c.jsonl", "2025-01-03T00:00:00Z", "three"),                make_row("/tmp/d.jsonl", "2025-01-04T00:00:00Z", "four"),            ],            Some("2025-01-05T00:00:00Z"),            /*num_scanned_files*/ 4,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 6, /*width*/ 80);        state.ensure_minimum_rows_for_view(/*minimum_rows*/ 6);        assert!(recorded_requests.lock().unwrap().is_empty());    }    #[test]    fn ensure_minimum_rows_still_prefetches_when_dense_rows_underfill_view() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.density = SessionListDensity::Dense;        state.reset_pagination();        state.ingest_page(page(            vec![                make_row("/tmp/a.jsonl", "2025-01-01T00:00:00Z", "one"),                make_row("/tmp/b.jsonl", "2025-01-02T00:00:00Z", "two"),            ],            Some("2025-01-03T00:00:00Z"),            /*num_scanned_files*/ 2,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 10, /*width*/ 80);        state.ensure_minimum_rows_for_view(/*minimum_rows*/ 10);        let guard = recorded_requests.lock().unwrap();        assert_eq!(guard.len(), 1);        assert!(guard[0].search_token.is_none());    }    #[test]    fn list_viewport_width_matches_rendered_list_inset() {        assert_eq!(list_viewport_width(/*width*/ 80), 76);        assert_eq!(list_viewport_width(/*width*/ 3), 0);    }    #[tokio::test]    async fn toggle_sort_key_reloads_with_new_sort() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.start_initial_load();        {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 1);            assert_eq!(guard[0].sort_key, ThreadSortKey::UpdatedAt);        }        state            .handle_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE))            .await            .unwrap();        state            .handle_key(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL))            .await            .unwrap();        let guard = recorded_requests.lock().unwrap();        assert_eq!(guard.len(), 2);        assert_eq!(guard[1].sort_key, ThreadSortKey::CreatedAt);    }    #[tokio::test]    async fn default_filter_focus_arrows_reload_with_new_filter() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ false,            Some(PathBuf::from("/tmp/project")),            SessionPickerAction::Resume,        );        state.start_initial_load();        {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 1);            assert_eq!(guard[0].cwd_filter, Some(PathBuf::from("/tmp/project")));        }        state            .handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE))            .await            .unwrap();        let guard = recorded_requests.lock().unwrap();        assert_eq!(guard.len(), 2);        assert_eq!(guard[1].cwd_filter, None);    }    #[tokio::test]    async fn all_filter_can_switch_back_to_cwd_when_cwd_candidate_exists() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            Some(PathBuf::from("/tmp/project")),            SessionPickerAction::Resume,        );        state.start_initial_load();        {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 1);            assert_eq!(guard[0].cwd_filter, None);        }        state            .handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE))            .await            .unwrap();        let guard = recorded_requests.lock().unwrap();        assert_eq!(guard.len(), 2);        assert_eq!(guard[1].cwd_filter, Some(PathBuf::from("/tmp/project")));    }    #[tokio::test]    async fn filter_stays_all_when_no_cwd_candidate_exists() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::Any,            /*show_all*/ false,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        assert_eq!(            search_line(&state, /*width*/ 80)                .to_string()                .matches("Cwd")                .count(),            0        );        state.start_initial_load();        state            .handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE))            .await            .unwrap();        let guard = recorded_requests.lock().unwrap();        assert_eq!(guard.len(), 1);        assert_eq!(guard[0].cwd_filter, None);    }    #[tokio::test]    async fn page_navigation_uses_view_rows() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let mut items = Vec::new();        for idx in 0..20 {            let ts = format!("2025-01-{:02}T00:00:00Z", idx + 1);            let preview = format!("item-{idx}");            let path = format!("/tmp/item-{idx}.jsonl");            items.push(make_row(&path, &ts, &preview));        }        state.reset_pagination();        state.ingest_page(page(            items, /*next_cursor*/ None, /*num_scanned_files*/ 20,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        assert_eq!(state.selected, 0);        state            .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 5);        state            .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 10);        state            .handle_key(KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 5);        state            .handle_key(KeyEvent::new(KeyCode::End, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 19);        state            .handle_key(KeyEvent::new(KeyCode::Home, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 0);    }    #[tokio::test]    async fn page_and_jump_navigation_use_list_keymap() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.list_keymap.page_down = vec![crate::key_hint::ctrl(KeyCode::Char('d'))];        state.list_keymap.page_up = vec![crate::key_hint::ctrl(KeyCode::Char('u'))];        state.list_keymap.jump_bottom = vec![crate::key_hint::ctrl(KeyCode::Char('y'))];        state.list_keymap.jump_top = vec![crate::key_hint::ctrl(KeyCode::Char('a'))];        let mut items = Vec::new();        for idx in 0..20 {            let ts = format!("2025-01-{:02}T00:00:00Z", idx + 1);            let preview = format!("item-{idx}");            let path = format!("/tmp/item-{idx}.jsonl");            items.push(make_row(&path, &ts, &preview));        }        state.reset_pagination();        state.ingest_page(page(            items, /*next_cursor*/ None, /*num_scanned_files*/ 20,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        state            .handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 0);        state            .handle_key(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.selected, 5);        state            .handle_key(KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.selected, 0);        state            .handle_key(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.selected, 19);        state            .handle_key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL))            .await            .unwrap();        assert_eq!(state.selected, 0);    }    #[tokio::test]    async fn ctrl_c_exits_even_when_cancel_is_remapped_to_ctrl_c() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.list_keymap.cancel = vec![crate::key_hint::ctrl(KeyCode::Char('c'))];        let selection = state            .handle_key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL))            .await            .unwrap();        assert!(matches!(selection, Some(SessionSelection::Exit)));    }    #[tokio::test]    async fn end_jumps_to_last_known_row_and_starts_loading_more() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let items = (0..10)            .map(|idx| {                make_row(                    &format!("/tmp/{idx}.jsonl"),                    "2026-05-02T12:00:00Z",                    &format!("row {idx}"),                )            })            .collect();        state.reset_pagination();        state.ingest_page(page(            items,            Some("cursor-1"),            /*num_scanned_files*/ 10,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        state            .handle_key(KeyEvent::new(KeyCode::End, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 9);        assert!(state.pagination.loading.is_pending());        assert_eq!(recorded_requests.lock().unwrap().len(), 1);        assert_eq!(            picker_footer_progress_label(&state, /*list_height*/ 5, /*width*/ 80),            " 10 / 10… · 100% "        );    }    #[tokio::test]    async fn enter_on_row_without_resolvable_thread_id_shows_inline_error() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let row = Row {            path: Some(PathBuf::from("/tmp/missing.jsonl")),            preview: String::from("missing metadata"),            thread_id: None,            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        };        state.all_rows = vec![row.clone()];        state.filtered_rows = vec![row];        let selection = state            .handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))            .await            .expect("enter should not abort the picker");        assert!(selection.is_none());        assert_eq!(            state.inline_error,            Some(String::from(                "Failed to read session metadata from /tmp/missing.jsonl"            ))        );    }    #[tokio::test]    async fn enter_on_pathless_thread_uses_thread_id() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let thread_id = ThreadId::new();        let row = Row {            path: None,            preview: String::from("pathless thread"),            thread_id: Some(thread_id),            thread_name: None,            created_at: None,            updated_at: None,            cwd: None,            git_branch: None,        };        state.all_rows = vec![row.clone()];        state.filtered_rows = vec![row];        let selection = state            .handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))            .await            .expect("enter should not abort the picker");        match selection {            Some(SessionSelection::Resume(SessionTarget {                path: None,                thread_id: selected_thread_id,            })) => assert_eq!(selected_thread_id, thread_id),            other => panic!("unexpected selection: {other:?}"),        }    }    #[test]    fn app_server_row_keeps_pathless_threads() {        let thread_id = ThreadId::new();        let thread = Thread {            id: thread_id.to_string(),            session_id: thread_id.to_string(),            forked_from_id: None,            parent_thread_id: None,            preview: String::from("remote thread"),            ephemeral: false,            model_provider: String::from("openai"),            created_at: 1,            updated_at: 2,            status: codex_app_server_protocol::ThreadStatus::Idle,            path: None,            cwd: test_path_buf("/tmp").abs(),            cli_version: String::from("0.0.0"),            source: codex_app_server_protocol::SessionSource::Cli,            thread_source: None,            agent_nickname: None,            agent_role: None,            git_info: None,            name: Some(String::from("Named thread")),            turns: Vec::new(),        };        let row = row_from_app_server_thread(thread).expect("row should be preserved");        assert_eq!(row.path, None);        assert_eq!(row.thread_id, Some(thread_id));        assert_eq!(row.thread_name, Some(String::from("Named thread")));    }    #[test]    fn thread_to_transcript_cells_renders_core_message_types() {        use crate::thread_transcript::thread_to_transcript_cells;        let thread_id = ThreadId::new();        let thread = Thread {            id: thread_id.to_string(),            session_id: thread_id.to_string(),            forked_from_id: None,            parent_thread_id: None,            preview: String::from("preview"),            ephemeral: false,            model_provider: String::from("openai"),            created_at: 1,            updated_at: 2,            status: codex_app_server_protocol::ThreadStatus::Idle,            path: None,            cwd: test_path_buf("/tmp").abs(),            cli_version: String::from("0.0.0"),            source: codex_app_server_protocol::SessionSource::Cli,            thread_source: None,            agent_nickname: None,            agent_role: None,            git_info: None,            name: None,            turns: vec![codex_app_server_protocol::Turn {                id: String::from("turn-1"),                items_view: codex_app_server_protocol::TurnItemsView::Full,                items: vec![                    ThreadItem::UserMessage {                        id: String::from("user-1"),                        client_id: None,                        content: vec![codex_app_server_protocol::UserInput::Text {                            text: String::from("hello from user"),                            text_elements: Vec::new(),                        }],                    },                    ThreadItem::AgentMessage {                        id: String::from("agent-1"),                        text: String::from("hello from assistant"),                        phase: None,                        memory_citation: None,                    },                    ThreadItem::Plan {                        id: String::from("plan-1"),                        text: String::from("1. Do the thing"),                    },                ],                status: codex_app_server_protocol::TurnStatus::Completed,                error: None,                started_at: None,                completed_at: None,                duration_ms: None,            }],        };        let rendered = thread_to_transcript_cells(&thread, RawReasoningVisibility::Visible)            .into_iter()            .flat_map(|cell| cell.transcript_lines(/*width*/ 80))            .map(|line| line.to_string())            .collect::<Vec<_>>()            .join("\n");        assert!(rendered.contains("hello from user"));        assert!(rendered.contains("hello from assistant"));        assert!(rendered.contains("Proposed Plan"));        assert!(rendered.contains("Do the thing"));    }    #[test]    fn thread_to_transcript_cells_hides_raw_reasoning_when_not_enabled() {        use crate::thread_transcript::thread_to_transcript_cells;        let thread_id = ThreadId::new();        let thread = Thread {            id: thread_id.to_string(),            session_id: thread_id.to_string(),            forked_from_id: None,            parent_thread_id: None,            preview: String::from("preview"),            ephemeral: false,            model_provider: String::from("openai"),            created_at: 1,            updated_at: 2,            status: codex_app_server_protocol::ThreadStatus::Idle,            path: None,            cwd: test_path_buf("/tmp").abs(),            cli_version: String::from("0.0.0"),            source: codex_app_server_protocol::SessionSource::Cli,            thread_source: None,            agent_nickname: None,            agent_role: None,            git_info: None,            name: None,            turns: vec![codex_app_server_protocol::Turn {                id: String::from("turn-1"),                items_view: codex_app_server_protocol::TurnItemsView::Full,                items: vec![ThreadItem::Reasoning {                    id: String::from("reasoning-1"),                    summary: Vec::new(),                    content: vec![String::from("private raw chain of thought")],                }],                status: codex_app_server_protocol::TurnStatus::Completed,                error: None,                started_at: None,                completed_at: None,                duration_ms: None,            }],        };        let hidden = thread_to_transcript_cells(&thread, RawReasoningVisibility::Hidden)            .into_iter()            .flat_map(|cell| cell.transcript_lines(/*width*/ 80))            .map(|line| line.to_string())            .collect::<Vec<_>>()            .join("\n");        let visible = thread_to_transcript_cells(&thread, RawReasoningVisibility::Visible)            .into_iter()            .flat_map(|cell| cell.transcript_lines(/*width*/ 80))            .map(|line| line.to_string())            .collect::<Vec<_>>()            .join("\n");        assert!(!hidden.contains("private raw chain of thought"));        assert!(visible.contains("private raw chain of thought"));    }    #[test]    fn thread_to_transcript_cells_shows_raw_reasoning_over_summary_when_enabled() {        use crate::thread_transcript::thread_to_transcript_cells;        let thread_id = ThreadId::new();        let thread = Thread {            id: thread_id.to_string(),            session_id: thread_id.to_string(),            forked_from_id: None,            parent_thread_id: None,            preview: String::from("preview"),            ephemeral: false,            model_provider: String::from("openai"),            created_at: 1,            updated_at: 2,            status: codex_app_server_protocol::ThreadStatus::Idle,            path: None,            cwd: test_path_buf("/tmp").abs(),            cli_version: String::from("0.0.0"),            source: codex_app_server_protocol::SessionSource::Cli,            thread_source: None,            agent_nickname: None,            agent_role: None,            git_info: None,            name: None,            turns: vec![codex_app_server_protocol::Turn {                id: String::from("turn-1"),                items_view: codex_app_server_protocol::TurnItemsView::Full,                items: vec![ThreadItem::Reasoning {                    id: String::from("reasoning-1"),                    summary: vec![String::from("public summary")],                    content: vec![String::from("raw reasoning content")],                }],                status: codex_app_server_protocol::TurnStatus::Completed,                error: None,                started_at: None,                completed_at: None,                duration_ms: None,            }],        };        let rendered = thread_to_transcript_cells(&thread, RawReasoningVisibility::Visible)            .into_iter()            .flat_map(|cell| cell.transcript_lines(/*width*/ 80))            .map(|line| line.to_string())            .collect::<Vec<_>>()            .join("\n");        assert!(rendered.contains("raw reasoning content"));        assert!(!rendered.contains("public summary"));    }    #[tokio::test]    async fn moving_to_last_card_scrolls_when_cards_exceed_viewport() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let mut items = Vec::new();        for idx in 0..3 {            let ts = format!("2025-02-{:02}T00:00:00Z", idx + 1);            let preview = format!("item-{idx}");            let path = format!("/tmp/item-{idx}.jsonl");            items.push(make_row(&path, &ts, &preview));        }        state.reset_pagination();        state.ingest_page(page(            items, /*next_cursor*/ None, /*num_scanned_files*/ 3,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        state            .handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.scroll_top, 1);        state            .handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 2);        assert_eq!(state.scroll_top, 2);    }    #[tokio::test]    async fn up_from_bottom_keeps_viewport_stable_when_card_remains_visible() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let mut items = Vec::new();        for idx in 0..10 {            let ts = format!("2025-02-{:02}T00:00:00Z", idx + 1);            let preview = format!("item-{idx}");            let path = format!("/tmp/item-{idx}.jsonl");            items.push(make_row(&path, &ts, &preview));        }        state.reset_pagination();        state.ingest_page(page(            items, /*next_cursor*/ None, /*num_scanned_files*/ 10,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        state.selected = state.filtered_rows.len().saturating_sub(1);        state.ensure_selected_visible();        let initial_top = state.scroll_top;        assert_eq!(initial_top, state.filtered_rows.len().saturating_sub(1));        state            .handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.scroll_top, initial_top.saturating_sub(1));        assert_eq!(state.selected, state.filtered_rows.len().saturating_sub(2));    }    #[tokio::test]    async fn up_scrolls_only_after_crossing_top_edge() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let mut items = Vec::new();        for idx in 0..10 {            let ts = format!("2025-02-{:02}T00:00:00Z", idx + 1);            let preview = format!("item-{idx}");            let path = format!("/tmp/item-{idx}.jsonl");            items.push(make_row(&path, &ts, &preview));        }        state.reset_pagination();        state.ingest_page(page(            items, /*next_cursor*/ None, /*num_scanned_files*/ 10,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        state.selected = 8;        state.scroll_top = 8;        state            .handle_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE))            .await            .unwrap();        assert_eq!(state.selected, 7);        assert_eq!(state.scroll_top, 7);    }    #[test]    fn list_reports_more_rows_above_and_below() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let mut items = Vec::new();        for idx in 0..5 {            let ts = format!("2025-02-{:02}T00:00:00Z", idx + 1);            let preview = format!("item-{idx}");            let path = format!("/tmp/item-{idx}.jsonl");            items.push(make_row(&path, &ts, &preview));        }        state.reset_pagination();        state.ingest_page(page(            items, /*next_cursor*/ None, /*num_scanned_files*/ 5,            /*reached_scan_cap*/ false,        ));        state.update_viewport(/*rows*/ 5, /*width*/ 80);        assert!(!state.has_more_above());        assert!(state.has_more_below(/*viewport_height*/ 5));        state.scroll_top = 2;        assert!(state.has_more_above());        assert!(state.has_more_below(/*viewport_height*/ 5));    }    #[tokio::test]    async fn set_query_loads_until_match_and_respects_scan_cap() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.reset_pagination();        state.ingest_page(page(            vec![make_row(                "/tmp/start.jsonl",                "2025-01-01T00:00:00Z",                "alpha",            )],            Some("2025-01-02T00:00:00Z"),            /*num_scanned_files*/ 1,            /*reached_scan_cap*/ false,        ));        recorded_requests.lock().unwrap().clear();        state.set_query("target".to_string());        let first_request = {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 1);            guard[0].clone()        };        state            .handle_background_event(BackgroundEvent::Page {                request_token: first_request.request_token,                search_token: first_request.search_token,                page: Ok(page(                    vec![make_row("/tmp/beta.jsonl", "2025-01-02T00:00:00Z", "beta")],                    Some("2025-01-03T00:00:00Z"),                    /*num_scanned_files*/ 5,                    /*reached_scan_cap*/ false,                )),            })            .await            .unwrap();        let second_request = {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 2);            guard[1].clone()        };        assert!(state.search_state.is_active());        assert!(state.filtered_rows.is_empty());        state            .handle_background_event(BackgroundEvent::Page {                request_token: second_request.request_token,                search_token: second_request.search_token,                page: Ok(page(                    vec![make_row(                        "/tmp/match.jsonl",                        "2025-01-03T00:00:00Z",                        "target log",                    )],                    Some("2025-01-04T00:00:00Z"),                    /*num_scanned_files*/ 7,                    /*reached_scan_cap*/ false,                )),            })            .await            .unwrap();        assert!(!state.filtered_rows.is_empty());        assert!(!state.search_state.is_active());        recorded_requests.lock().unwrap().clear();        state.set_query("missing".to_string());        let active_request = {            let guard = recorded_requests.lock().unwrap();            assert_eq!(guard.len(), 1);            guard[0].clone()        };        state            .handle_background_event(BackgroundEvent::Page {                request_token: second_request.request_token,                search_token: second_request.search_token,                page: Ok(page(                    Vec::new(),                    /*next_cursor*/ None,                    /*num_scanned_files*/ 0,                    /*reached_scan_cap*/ false,                )),            })            .await            .unwrap();        assert_eq!(recorded_requests.lock().unwrap().len(), 1);        state            .handle_background_event(BackgroundEvent::Page {                request_token: active_request.request_token,                search_token: active_request.search_token,                page: Ok(page(                    Vec::new(),                    /*next_cursor*/ None,                    /*num_scanned_files*/ 3,                    /*reached_scan_cap*/ true,                )),            })            .await            .unwrap();        assert!(state.filtered_rows.is_empty());        assert!(!state.search_state.is_active());        assert!(state.pagination.reached_scan_cap);    }    #[tokio::test]    async fn paste_appends_to_existing_query() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.query = String::from("resize");        state.handle_paste(String::from("results"));        assert_eq!(state.query, "resize results");    }    #[tokio::test]    async fn whitespace_only_paste_is_ignored() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.query = String::from("resize");        state.handle_paste(String::from("  \n\t  "));        assert_eq!(state.query, "resize");    }    #[tokio::test]    async fn paste_uses_existing_search_loading_path() {        let recorded_requests: Arc<Mutex<Vec<PageLoadRequest>>> = Arc::new(Mutex::new(Vec::new()));        let request_sink = recorded_requests.clone();        let loader = page_only_loader(move |req: PageLoadRequest| {            request_sink.lock().unwrap().push(req);        });        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.reset_pagination();        state.ingest_page(page(            vec![make_row(                "/tmp/start.jsonl",                "2025-01-01T00:00:00Z",                "alpha",            )],            Some("2025-01-02T00:00:00Z"),            /*num_scanned_files*/ 1,            /*reached_scan_cap*/ false,        ));        recorded_requests.lock().unwrap().clear();        state.handle_paste(String::from("target"));        let guard = recorded_requests.lock().unwrap();        assert_eq!(state.query, "target");        assert_eq!(guard.len(), 1);        assert!(guard[0].search_token.is_some());    }    #[tokio::test]    async fn esc_with_empty_query_starts_fresh() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        let selection = state            .handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))            .await            .expect("handle key");        assert!(matches!(selection, Some(SessionSelection::StartFresh)));    }    #[tokio::test]    async fn esc_with_query_clears_search_and_preserves_selected_result() {        let loader = page_only_loader(|_| {});        let mut state = PickerState::new(            FrameRequester::test_dummy(),            loader,            ProviderFilter::MatchDefault(String::from("openai")),            /*show_all*/ true,            /*filter_cwd*/ None,            SessionPickerAction::Resume,        );        state.reset_pagination();        state.ingest_page(page(            vec![                make_row("/tmp/alpha.jsonl", "2025-01-03T00:00:00Z", "alpha"),                make_row("/tmp/beta.jsonl", "2025-01-02T00:00:00Z", "beta"),            ],            /*next_cursor*/ None,            /*num_scanned_files*/ 2,            /*reached_scan_cap*/ false,        ));        state.set_query(String::from("beta"));        let selection = state            .handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))            .await            .expect("handle key");        assert!(selection.is_none());        assert!(state.query.is_empty());        assert_eq!(state.filtered_rows.len(), 2);        assert_eq!(            state.filtered_rows[state.selected].path.as_deref(),            Some(Path::new("/tmp/beta.jsonl"))        );    }}