feat(ui): add configurable scrollback lines and new‑message alert badge

Introduce `ui.scrollback_lines` (default 2000) to cap the number of chat lines kept in memory, with `0` disabling trimming. Implement automatic trimming of older lines, maintain a scroll offset, and show a “↓ New messages (press G)” badge when new messages arrive off‑screen. Update core UI settings, TUI rendering, chat app state, migrations, documentation, and changelog to reflect the new feature.
This commit is contained in:
2025-10-12 14:23:04 +02:00
parent 82078afd6d
commit 60c859b3ab
6 changed files with 170 additions and 3 deletions

View File

@@ -826,6 +826,15 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
lines.push(Line::from("No messages yet. Press 'i' to start typing."));
}
let scrollback_limit = app.scrollback_limit();
if scrollback_limit != usize::MAX && lines.len() > scrollback_limit {
let removed = lines.len() - scrollback_limit;
lines = lines.into_iter().skip(removed).collect();
app.apply_chat_scrollback_trim(removed, lines.len());
} else {
app.apply_chat_scrollback_trim(0, lines.len());
}
// Apply visual selection highlighting if in visual mode and Chat panel is focused
if matches!(app.mode(), InputMode::Visual)
&& matches!(app.focused_panel(), FocusedPanel::Chat)
@@ -860,6 +869,31 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
frame.render_widget(paragraph, area);
if app.has_new_message_alert() {
let badge_text = "↓ New messages (press G)";
let text_width = badge_text.chars().count() as u16;
let badge_width = text_width.saturating_add(2);
if area.width > badge_width + 1 && area.height > 2 {
let badge_x = area.x + area.width.saturating_sub(badge_width + 1);
let badge_y = area.y + 1;
let badge_area = Rect::new(badge_x, badge_y, badge_width, 1);
frame.render_widget(Clear, badge_area);
let badge_line = Line::from(Span::styled(
format!(" {badge_text} "),
Style::default()
.fg(theme.background)
.bg(theme.info)
.add_modifier(Modifier::BOLD),
));
frame.render_widget(
Paragraph::new(badge_line)
.style(Style::default().bg(theme.info).fg(theme.background))
.alignment(Alignment::Center),
badge_area,
);
}
}
// Render cursor if Chat panel is focused and in Normal mode
if matches!(app.focused_panel(), FocusedPanel::Chat) && matches!(app.mode(), InputMode::Normal)
{