feat(hh): scrollback for chat + sandbox terminal
- Chat history: PgUp/PgDn (page), arrows (line when no sandbox), Home=oldest, End=live. Viewport holds steady when new lines arrive while scrolled up; sending a message jumps back to live. Backlog capped at 4000 lines. - Sandbox terminal: vt100 parser now keeps 2000 rows of scrollback; ↑/↓ scroll it when not driving (arrows still go to the shell while driving). Offset applied each frame; reset on dismiss / End. - Title indicators: 'chat ↑N (End=live)' and 'sandbox · ↑N scrollback'. Termux's extra-keys row has arrows + PgUp/PgDn/Home/End, so it's phone-usable. 9 tests pass; clean build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+15
-5
@@ -46,9 +46,11 @@ fn draw_sandbox(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &T
|
||||
.map(|r| Line::from(Span::styled(r, Style::default().fg(theme.title))))
|
||||
.collect();
|
||||
let drive = if app.driving {
|
||||
" · DRIVING — type here · Esc to release"
|
||||
" · DRIVING — type here · Esc to release".to_string()
|
||||
} else if app.sbx_scroll > 0 {
|
||||
format!(" · ↑{} scrollback (↓/End=live)", app.sbx_scroll)
|
||||
} else {
|
||||
" · type /drive (or F2) to take the shell"
|
||||
" · /drive (or F2) · ↑/↓ scroll".to_string()
|
||||
};
|
||||
let title = format!(" sandbox · {}{} ", sv.backend, drive);
|
||||
let border = if app.driving { theme.accent } else { theme.border };
|
||||
@@ -98,13 +100,21 @@ fn fmt_line<'a>(l: &'a ChatLine, app: &App, theme: &Theme) -> Line<'a> {
|
||||
|
||||
fn draw_chat(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &Theme) {
|
||||
let visible = area.height.saturating_sub(2) as usize;
|
||||
let start = app.lines.len().saturating_sub(visible);
|
||||
let lines: Vec<Line> = app.lines[start..].iter().map(|l| fmt_line(l, app, theme)).collect();
|
||||
let len = app.lines.len();
|
||||
// Window ends `chat_scroll` lines above the live bottom.
|
||||
let end = len.saturating_sub(app.chat_scroll);
|
||||
let start = end.saturating_sub(visible);
|
||||
let lines: Vec<Line> = app.lines[start..end].iter().map(|l| fmt_line(l, app, theme)).collect();
|
||||
let title = if app.chat_scroll > 0 {
|
||||
format!(" chat ↑{} (End=live) ", app.chat_scroll)
|
||||
} else {
|
||||
" chat ".to_string()
|
||||
};
|
||||
let chat = Paragraph::new(lines)
|
||||
.block(
|
||||
Block::bordered()
|
||||
.border_style(Style::default().fg(theme.border))
|
||||
.title(Span::styled(" chat ", Style::default().fg(theme.title))),
|
||||
.title(Span::styled(title, Style::default().fg(theme.title))),
|
||||
)
|
||||
.wrap(Wrap { trim: false });
|
||||
f.render_widget(chat, area);
|
||||
|
||||
Reference in New Issue
Block a user