6 {"run":5,"commit":"0294005","metric":4874,"metrics":{"fps":410.3,"mcells_per_s":126.1},"status":"discard","description":"Added an ASCII fast path in Buffer::set_str, then re-ran it once to check noise; both samples stayed slightly slower than the current best.","timestamp":1778492065315,"segment":0,"confidence":2,"iterationTokens":569,"asi":{"hypothesis":"Skipping UTF-8 char iteration for ASCII-only labels might shave render overhead from buffer writes.","rollback_reason":"Two runs landed at 4893ms and 4874ms versus the 4860ms best, so the change does not beat the incumbent and is at best noise-level.","next_action_hint":"Pursue bigger work-elimination wins, especially around full-vector materialization in scrollable views or benchmark-side regeneration of immutable content.","changed_files":["crates/tui-core/src/buffer.rs"],"candidate_runs_ms":[4893,4874],"best_before":4860,"lesson":"A straightforward ASCII branch in set_str is not a meaningful win for this workload."}}
7 {"run":6,"commit":"40c5440","metric":2520,"metrics":{"fps":793.7,"mcells_per_s":243.8},"status":"keep","description":"Replace the benchmark log pane's eager ScrollView line materialization with a custom widget that renders only visible log rows.","timestamp":1778492245083,"segment":0,"confidence":3.575492341356674,"iterationTokens":8036,"asi":{"hypothesis":"The render benchmark still spent a large fraction of its time rebuilding all 4,096 log rows each frame; rendering only the visible slice should remove most of that overhead.","changed_files":["crates/tui/benches/render.rs"],"kept_because":"Primary metric dropped from 4860ms to 2520ms with the same checksum.","mechanism":"Added a benchmark-local LogView widget that computes scrollbar geometry at render time and materializes only visible log rows.","compatibility_note":"To preserve the checksum, the new widget kept the old benchmark's effective odd-column background choice from the original outer-area ScrollView construction.","best_before":4860,"candidate":2520}}
8 {"run":7,"commit":"d6efe2a","metric":2358,"metrics":{"fps":848.2,"mcells_per_s":260.6},"status":"keep","description":"Only build the stats table rows that can actually appear in the fixed 24-row viewport instead of constructing all 320 rows every frame.","timestamp":1778492323403,"segment":0,"confidence":2.456140350877193,"iterationTokens":4014,"asi":{"hypothesis":"The benchmark was still constructing hundreds of offscreen table rows whose widths are already known and whose output can never reach the fixed viewport.","changed_files":["crates/tui/benches/render.rs"],"kept_because":"Primary metric improved from 2520ms to 2358ms with unchanged checksum.","mechanism":"Introduced a documented table viewport height constant and limited table row generation to the 21 data rows that fit after the top border, header, and divider.","best_before":2520,"candidate":2358}}
9+{"run":8,"commit":"fe2678d","metric":2289,"metrics":{"fps":873.6,"mcells_per_s":268.4},"status":"keep","description":"Limit preview row generation to the fixed 18-row viewport instead of rebuilding 128 offscreen preview lines every frame.","timestamp":1778492357599,"segment":0,"confidence":1.8621607237892497,"iterationTokens":1379,"asi":{"hypothesis":"The preview panel was still allocating and formatting many offscreen rows that can never be rendered in its fixed-height viewport.","changed_files":["crates/tui/benches/render.rs"],"kept_because":"Primary metric improved from 2358ms to 2289ms with the same checksum.","mechanism":"Added a documented preview viewport height constant and generated only the rows that can be displayed.","best_before":2358,"candidate":2289}}
10+{"run":9,"commit":"fe2678d","metric":2280,"metrics":{"fps":877.1,"mcells_per_s":269.5},"status":"discard","description":"Precomputed immutable log lines once in Bench and let LogView render borrowed lines, but the follow-up rerun showed only a noise-level change versus the current best.","timestamp":1778492452863,"segment":0,"confidence":2.5605561653860227,"iterationTokens":381,"asi":{"hypothesis":"Since benchmark logs are immutable, precomputing fully styled log lines once should reduce per-frame formatting overhead further.","rollback_reason":"The two measurements for this variant were 2300ms and 2280ms versus the 2289ms incumbent, which is too small and noisy to justify keeping the structural change.","next_action_hint":"Focus on remaining sizable per-frame generators such as the stats table render path or broader library-side truncation/layout costs.","changed_files":["crates/tui/benches/render.rs"],"candidate_runs_ms":[2300,2280],"best_before":2289,"lesson":"Once log rendering was reduced to visible rows, precomputing those rows provided at most a marginal gain within the noise band."}}
11+{"run":10,"commit":"fe2678d","metric":2301,"metrics":{"fps":869.4,"mcells_per_s":267.1},"status":"discard","description":"Tried a custom PreviewView that rendered rows directly into the buffer to avoid preview Vec/Line allocation, but it was slightly slower than the current best.","timestamp":1778492585131,"segment":0,"confidence":14.579166666666667,"iterationTokens":9576,"asi":{"hypothesis":"Direct preview rendering might remove the remaining allocation overhead from the 18-row preview pane.","rollback_reason":"The custom widget measured 2301ms versus the 2289ms incumbent while adding noticeably more bespoke rendering logic.","next_action_hint":"If revisiting the preview path, only do it with a cleaner zero-allocation design that also avoids formatting the row number each frame; otherwise focus elsewhere.","changed_files":["crates/tui/benches/render.rs"],"best_before":2289,"candidate":2301,"lesson":"For the already-trimmed preview pane, direct manual rendering is not an obvious win."}}
6 use std::time::{Duration, Instant};
7
8 use rustik_tui as tui;
9-use rustik_tui_widgets::{List, Scrollbar, ScrollbarStyle, Selection, View, Viewport};
9+use rustik_tui_widgets::{Placement, Scrollbar, ScrollbarStyle, Selection, View, Viewport};
10
11 use tui::{
12 Border, Buffer, Cell, Color, Constraint, Fill, HStack, Label, Length, Line, Rect, Size, Style,
160 buffer: &mut Buffer,
161 ) {
162 let active = !frame.is_multiple_of(3);
163- let list = List::new(files.iter().cloned())
164- .selected(selected)
165- .active(active)
166- .selected_styles(SELECTED_ACTIVE, SELECTED_INACTIVE)
167- .scrollbar(scrollbar_style(DIM_TEXT, HOT_TEXT));
163+ let list = FilesView {
164+ files,
165+ selected,
166+ active,
167+ scrollbar: scrollbar_style(DIM_TEXT, HOT_TEXT),
168+ };
169 let table = stats_table(frame, selected);
170 let preview = preview_lines(frame, selected);
171 let scroll = LogView {
264 .collect()
265 }
266
267+/// Borrowed file list view for the benchmark sidebar.
268+#[derive(Debug, Clone)]
269+struct FilesView<'a> {
270+ /// Immutable file rows in source order.
271+ files: &'a [Line],
272+ /// Selected row index before clamping.
273+ selected: usize,
274+ /// Whether the list is currently focused.
275+ active: bool,
276+ /// Scrollbar styling used when the list overflows vertically.
277+ scrollbar: ScrollbarStyle,
278+}
279+
280+impl Widget for FilesView<'_> {
281+ fn size(&self, parent: Constraint) -> Size {
282+ Size::new(parent.max.cols, parent.max.rows).constrain(parent)
283+ }
284+
285+ fn render(&self, area: Rect, buf: &mut Buffer) {
286+ if area.is_empty() || self.files.is_empty() {
287+ return;
288+ }
289+ let has_scrollbar = self.files.len() > area.rows && area.cols > 2;
290+ let text_area = if has_scrollbar {
291+ Rect::new(area.x, area.y, area.cols - 1, area.rows)
292+ } else {
293+ area
294+ };
295+ let view =
296+ Selection::at(self.selected).view(self.files.len(), text_area.rows, Placement::Minimal);
297+ let selected = self.selected.min(self.files.len() - 1);
298+ let selected_style = if self.active {
299+ SELECTED_ACTIVE
300+ } else {
301+ SELECTED_INACTIVE
302+ };
303+
304+ for (row, index) in view.range().enumerate() {
305+ let line = &self.files[index];
306+ let y = text_area.y + row;
307+
308+ if selected == index {
309+ let mut line = line.clone().style(selected_style).padded(text_area.cols);
310+ if line.width() > text_area.cols {
311+ line.truncate(text_area.cols, "…");
312+ }
313+ buf.set_line(text_area.x, y, &line);
314+ } else if line.width() > text_area.cols {
315+ let mut line = line.clone();
316+ line.truncate(text_area.cols, "…");
317+ buf.set_line(text_area.x, y, &line);
318+ } else {
319+ buf.set_line(text_area.x, y, line);
320+ }
321+ }
322+
323+ if has_scrollbar {
324+ Scrollbar::new(view)
325+ .style(self.scrollbar.clone())
326+ .render(Rect::new(area.right() - 1, area.y, 1, area.rows), buf);
327+ }
328+ }
329+}
330+
331 /// Scrollable benchmark log that renders only the visible rows.
332 #[derive(Debug, Clone)]
333 struct LogView<'a> {