{"record":{"id":"ef515931c57ca6f0","repo":"atuinsh/atuin","slug":"drawing-inspector-but-no-stats","errorCode":null,"errorMessage":"Drawing inspector, but no stats","messagePattern":"Drawing inspector, but no stats","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin/src/command/client/search/interactive.rs","lineNumber":1088,"sourceCode":"                        .block(\n                            Block::new()\n                                .title(Line::from(\" Info \".to_string()))\n                                .title_alignment(Alignment::Center)\n                                .borders(Borders::ALL)\n                                .padding(Padding::vertical(2)),\n                        )\n                        .alignment(Alignment::Center);\n                    f.render_widget(message, results_list_chunk);\n                } else {\n                    let inspecting = match inspecting {\n                        Some(inspecting) => inspecting,\n                        None => &results[self.results_state.selected()],\n                    };\n                    super::inspector::draw(\n                        f,\n                        results_list_chunk,\n                        inspecting,\n                        &stats.expect(\"Drawing inspector, but no stats\"),\n                        settings,\n                        theme,\n                        settings.timezone,\n                    );\n                }\n\n                // HACK: I'm following up with abstracting this into the UI container, with a\n                // sub-widget for search + for inspector\n                let feedback = Paragraph::new(\n                    \"The inspector is new - please give feedback (good, or bad) at https://forum.atuin.sh\",\n                );\n                f.render_widget(feedback, input_chunk);\n\n                return;\n            }\n\n            _ => {\n                panic!(\"invalid tab index\");","sourceCodeStart":1070,"sourceCodeEnd":1106,"githubUrl":"https://github.com/atuinsh/atuin/blob/202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1/crates/atuin/src/command/client/search/interactive.rs#L1070-L1106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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').","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`"],"exampleFix":"// before\nsuper::inspector::draw(\n    f,\n    results_list_chunk,\n    inspecting,\n    &stats.expect(\"Drawing inspector, but no stats\"),\n    settings,\n    theme,\n    settings.timezone,\n);\n\n// after\nif let Some(stats) = stats.as_ref() {\n    super::inspector::draw(\n        f,\n        results_list_chunk,\n        inspecting,\n        stats,\n        settings,\n        theme,\n        settings.timezone,\n    );\n} else {\n    f.render_widget(Paragraph::new(\"Computing stats…\"), results_list_chunk);\n}","handlingStrategy":"fallback","validationCode":"// Before drawing the inspector tab, check both preconditions the expect assumes\nif app.tab_index == 1 && !results.is_empty() && stats.is_none() {\n    // compute now (or defer the tab render) rather than letting draw panic\n    let selected = inspecting.cloned().unwrap_or_else(|| results[app.results_state.selected()].clone());\n    stats = Some(db.stats(&selected).await?);\n}","typeGuard":"fn can_draw_inspector(results: &[History], stats: &Option<HistoryStats>) -> bool {\n    !results.is_empty() && stats.is_some()\n}","tryCatchPattern":"// In the draw closure, branch on the Option instead of expect:\nmatch stats.as_ref() {\n    Some(stats) => super::inspector::draw(f, chunk, inspecting, stats, settings, theme, settings.timezone),\n    None => f.render_widget(Paragraph::new(\"Computing stats…\"), chunk),\n}","preventionTips":["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"],"tags":["rust","ratatui","tui","race-condition","option","panic","inspector"],"backgroundTag":"unwrap-none-panic","analyzedSha":"202f6ad98ee0da165c35cdb2afbc5b13d6ab81a1","analyzedAt":"2026-08-16T19:30:24.731Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}