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

  1. 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
  2. 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`
  3. Guard the inspector branch with `if let Some(stats) = stats.as_ref()` and skip the `super::inspector::draw` call when `None`
  4. 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

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


AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16). Data as JSON: /api/errors/ef515931c57ca6f0. Report an issue: GitHub.