Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 34 additions & 21 deletions src/apps/cli/src/ui/chat/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,24 @@ impl ChatView {
return;
}

// Reconcile scroll_offset against the freshly computed total_lines.
// Presentation toggles (e.g. /thinking) invalidate the render cache
// and shrink total_lines, but leave scroll_offset stale — clamping
// here prevents the view from being pinned to the top with a frozen
// scrollbar.
if self.browse_mode {
if total_lines <= visible_lines {
self.browse_mode = false;
self.auto_scroll = true;
self.scroll_offset = 0;
} else {
let max_scroll = total_lines.saturating_sub(visible_lines);
if self.scroll_offset > max_scroll {
self.scroll_offset = max_scroll;
}
}
}

// prefix_sum[i] = total lines of messages 0..i (exclusive end).
let prefix_sum = layout.prefix_sum;

Expand Down Expand Up @@ -397,12 +415,12 @@ impl ChatView {

// ── Scroll indicator ──
if self.browse_mode {
let progress_pct = if self.scroll_offset == 0 {
let max_scroll = total_lines.saturating_sub(visible_lines);
let progress_pct = if max_scroll == 0 {
100
} else if self.scroll_offset >= total_lines {
0
} else {
((total_lines - self.scroll_offset) * 100 / total_lines).min(100)
let effective = self.scroll_offset.min(max_scroll);
((max_scroll - effective) * 100 / max_scroll).min(100)
};

let scroll_indicator = format!("{}%", progress_pct);
Expand Down Expand Up @@ -671,23 +689,21 @@ impl ChatView {
// round-trip instead of being inverted by XOR.
let collapsed = match self.presentation.thinking {
crate::config::ThinkingMode::Show => false,
crate::config::ThinkingMode::Hide => {
self.thinking_disclosures
.is_collapsed(&thinking_block_id, true)
}
crate::config::ThinkingMode::Hide => self
.thinking_disclosures
.is_collapsed(&thinking_block_id, true),
};
// In Show mode the header is non-interactive: hide the
// caret so users don't expect to click-collapse an
// already fully-expanded block.
let caret = if self.presentation.thinking
== crate::config::ThinkingMode::Show
{
""
} else if collapsed {
"\u{25b8}"
} else {
"\u{25be}"
};
let caret =
if self.presentation.thinking == crate::config::ThinkingMode::Show {
""
} else if collapsed {
"\u{25b8}"
} else {
"\u{25be}"
};

let header_y = items.len().min(u16::MAX as usize) as u16;
thinking_regions.push((thinking_block_id.clone(), header_y, header_y));
Expand Down Expand Up @@ -795,10 +811,7 @@ impl ChatView {
};
plain_lines.push(format!("{line_prefix}{wrapped}"));
items.push(ListItem::new(Line::from(vec![
Span::styled(
line_prefix,
self.theme.style(StyleKind::Primary),
),
Span::styled(line_prefix, self.theme.style(StyleKind::Primary)),
Span::raw(wrapped),
])));
}
Expand Down
16 changes: 12 additions & 4 deletions src/apps/cli/src/ui/chat/scroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,17 @@ impl ChatView {

pub(crate) fn scroll_up(&mut self, lines: usize, total_message_lines: usize) {
self.committed_message_anchor = None;
let visible = self
.messages_area
.map(|a| a.height.max(1) as usize)
.unwrap_or(20);
let max_offset = total_message_lines.saturating_sub(visible);
if self.browse_mode {
self.scroll_offset =
(self.scroll_offset + lines).min(total_message_lines.saturating_sub(1));
self.scroll_offset = (self.scroll_offset + lines).min(max_offset);
} else {
self.browse_mode = true;
self.auto_scroll = false;
self.scroll_offset = lines;
self.scroll_offset = lines.min(max_offset);
}
}

Expand All @@ -107,7 +111,11 @@ impl ChatView {
self.committed_message_anchor = None;
self.browse_mode = true;
self.auto_scroll = false;
self.scroll_offset = total_message_lines.saturating_sub(1);
let visible = self
.messages_area
.map(|a| a.height.max(1) as usize)
.unwrap_or(20);
self.scroll_offset = total_message_lines.saturating_sub(visible);
}

pub(crate) fn scroll_to_bottom(&mut self) {
Expand Down