atuinsh/atuin · error
Drawing inspector, but no stats
Error message
Drawing inspector, but no stats
What it means
A panic from `.expect()` on an `Option<HistoryStats>` in the interactive search TUI's draw function (interactive.rs:1088). The event loop keeps `stats` as an `Option` that starts as `None` (line 1949), is reset to `None` on every loop pass where `tab_index == 0` (lines 2100-2102), and is only filled at the END of a pass via the async `db.stats(&selected).await` (line 2115). The draw call at the TOP of each pass assumes that whenever the inspector tab (`tab_index == 1`) renders with non-empty results, stats must already be `Some` — any path that reaches `terminal.draw` with the inspector active before the stats recompute has run violates that assumption and panics.
Source
Thrown at crates/atuin/src/command/client/search/interactive.rs:1088
.block(
Block::new()
.title(Line::from(" Info ".to_string()))
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.padding(Padding::vertical(2)),
)
.alignment(Alignment::Center);
f.render_widget(message, results_list_chunk);
} else {
let inspecting = match inspecting {
Some(inspecting) => inspecting,
None => &results[self.results_state.selected()],
};
super::inspector::draw(
f,
results_list_chunk,
inspecting,
&stats.expect("Drawing inspector, but no stats"),
settings,
theme,
settings.timezone,
);
}
// HACK: I'm following up with abstracting this into the UI container, with a
// sub-widget for search + for inspector
let feedback = Paragraph::new(
"The inspector is new - please give feedback (good, or bad) at https://forum.atuin.sh",
);
f.render_widget(feedback, input_chunk);
return;
}
_ => {
panic!("invalid tab index");View on GitHub (pinned to 202f6ad98e)
Solutions
- Replace the `.expect` with a placeholder render when stats is not ready yet, mirroring the existing 'Nothing to inspect' branch: draw a 'Computing stats…' paragraph instead of panicking
- Compute stats before leaving tab 0 (eagerly on tab switch, in the input handler that sets `tab_index = 1`) so draw can never observe tab 1 with `None`
- Guard the inspector branch with `if let Some(stats) = stats.as_ref()` and skip the `super::inspector::draw` call when `None`
- Add a regression test that drives the render loop with `tab_index = 1` and `stats = None`
Example fix
// before
super::inspector::draw(
f,
results_list_chunk,
inspecting,
&stats.expect("Drawing inspector, but no stats"),
settings,
theme,
settings.timezone,
);
// after
if let Some(stats) = stats.as_ref() {
super::inspector::draw(
f,
results_list_chunk,
inspecting,
stats,
settings,
theme,
settings.timezone,
);
} else {
f.render_widget(Paragraph::new("Computing stats…"), results_list_chunk);
} Defensive patterns
Strategy: fallback
Validate before calling
// Before drawing the inspector tab, check both preconditions the expect assumes
if app.tab_index == 1 && !results.is_empty() && stats.is_none() {
// compute now (or defer the tab render) rather than letting draw panic
let selected = inspecting.cloned().unwrap_or_else(|| results[app.results_state.selected()].clone());
stats = Some(db.stats(&selected).await?);
} Type guard
fn can_draw_inspector(results: &[History], stats: &Option<HistoryStats>) -> bool {
!results.is_empty() && stats.is_some()
} Try / catch
// In the draw closure, branch on the Option instead of expect:
match stats.as_ref() {
Some(stats) => super::inspector::draw(f, chunk, inspecting, stats, settings, theme, settings.timezone),
None => f.render_widget(Paragraph::new("Computing stats…"), chunk),
} Prevention
- Never `.expect` on async-produced state inside a render function; the renderer must always have a frame for 'not ready yet'
- Compute/refresh dependent state at the point where the mode switches (in the input handler setting tab_index), not only at the end of the event-loop pass
- Keep 'loading' placeholder branches alongside 'empty' branches (copy the 'Nothing to inspect' pattern) for every data-dependent widget
- Reproduce timing bugs by adding an artificial delay to the stats query in a debug build before shipping
When it happens
Trigger: Running `atuin search` (interactive TUI), getting non-empty results, and switching to the inspector tab such that a draw happens while `stats == None`: rapid key input processed in the inner `event::read()` loop that flips `app.tab_index` to 1 after the pass where stats was reset to `None`, or any control flow that reaches the next `terminal.draw` before line 2100's recompute (e.g. loop `break`/`continue` paths, slow first `db.stats` query). The panic happens inside ratatui's draw closure, crashing the whole UI.
Common situations: Users pressing the tab-switch key quickly right after results load, or on the very first inspector render after switching tabs while the per-history-entry stats queries (`db.stats(&selected)`) are still cold; larger local databases make the stats query slower and widen the window. Reported as an atuin TUI crash ('Drawing inspector, but no stats').
Related errors
- failed to format sqlite date
- Failed to compute stats
- key file vanished immediately after a concurrent write
- Interactive mode requires a terminal
- bug in list query. please report
AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16).
Data as JSON: /api/errors/ef515931c57ca6f0.
Report an issue: GitHub.