diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index eaf9b75..3419662 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -281,9 +281,37 @@ impl App { self.viewport_cache.entries.push(entry); } + // Post-check: ensure cursor_line is within rendered entries (Loading + JSON expansion) + if self.is_loading() { + let entries = &self.viewport_cache.entries; + let last_entry_end = self.viewport_cache.logical_start + + entries.len() + + entries.last().map(|e| e.visual_height).unwrap_or(0).saturating_sub(1); + let first_entry_start = self.viewport_cache.logical_start; + if self.cursor_line >= last_entry_end || self.cursor_line < first_entry_start { + self.v_offset = self.cursor_line; + self.viewport_cache.entries.clear(); + self.viewport_cache.logical_start = self.cursor_line; + self.fill_viewport_entries(self.cursor_line, width, viewport_height); + } + } + (start_logical, offset_in_line) } + fn fill_viewport_entries(&mut self, start_logical: usize, width: usize, viewport_height: usize) { + let total = self.total_lines(); + let mut rows_remaining = viewport_height; + for line_idx in start_logical..total { + if rows_remaining == 0 { + break; + } + let entry = self.compute_line_entry(line_idx, width); + rows_remaining = rows_remaining.saturating_sub(entry.visual_height); + self.viewport_cache.entries.push(entry); + } + } + /// Compute total visual rows (cached, lazily evaluated). /// Returns `total_lines` for sampling mode (1:1 mapping). fn total_visual_rows(&mut self) -> usize {