{"record":{"id":"15e71be450e6bb37","repo":"gitbutlerapp/gitbutler","slug":"no-searchable-columns","errorCode":null,"errorMessage":"no searchable columns","messagePattern":"no searchable columns","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/status/tui/fuzzy_picker.rs","lineNumber":118,"sourceCode":"        let input_height: u16 = 1;\n\n        let col_widths = self\n            .items\n            .iter()\n            .map(|item| item.columns(SearchableToken(())))\n            .map(|cols| {\n                cols.into_iter()\n                    .map(|col| col.text.width())\n                    .collect::<Vec<_>>()\n            })\n            .collect::<Vec<_>>();\n\n        // assume the searchable column is at the same index on every row\n        let searchable_col_idx = self.items[0]\n            .columns(SearchableToken(()))\n            .into_iter()\n            .position(|col| col.searchable.is_some())\n            .expect(\"no searchable columns\");\n\n        // assume each row contains the same number of columns\n        let num_cols = col_widths[0].len();\n\n        let column_spacing = 2;\n\n        let mut col_constraints = (0..num_cols)\n            .map(|n| col_widths.iter().map(|c| &c[n]).collect::<Vec<_>>())\n            .map(|col| col.iter().copied().max().unwrap())\n            .map(|&width| Constraint::Length(width as u16))\n            .collect::<Vec<_>>();\n        for (i, constraint) in col_constraints.iter_mut().enumerate() {\n            if i == searchable_col_idx {\n                *constraint = Constraint::Min(1);\n                break;\n            }\n        }\n","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/command/legacy/status/tui/fuzzy_picker.rs#L100-L136","documentation":"The TUI fuzzy picker enforces a trait contract: FuzzyPickerItem::columns(SearchableToken(())) must return at least one Col with searchable == Some. During layout the picker inspects only the first item (self.items[0]) to locate the searchable column index and panics if none of its columns is searchable. Because it also indexes items[0], an empty items list panics with index-out-of-bounds just before this expect.","triggerScenarios":"Opening any fuzzy picker in the TUI (copy-selection picker, stack picker) where the item type's columns impl returns every Col with searchable: None, or drops the SearchableToken parameter instead of attaching it to exactly one column; also constructing a picker with an empty items vec (empty workspace/stack).","commonSituations":"Implementing FuzzyPickerItem for a new TUI list type and forgetting to mark the searchable column (reference impls: copy_selection_picker.rs:236, app/mod.rs:2066 set searchable: Some on the searched column); conditional impls that only sometimes mark a column searchable; empty result lists passed to the picker.","solutions":["In the FuzzyPickerItem::columns impl, set searchable: Some(searchable) on exactly one Col (copy the pattern from fuzzy_picker.rs sibling copy_selection_picker.rs:236-240)","Guard the caller: don't open the picker when items.is_empty() — show a 'nothing to pick' message instead","Replace the expect with a fallback (search column 0) or skip rendering, so contract violations surface as wrong UI instead of a crash","Add a unit test that builds the picker for each item type and asserts a searchable column exists"],"exampleFix":"// before — new item impl, nothing searchable → panic at layout\nfn columns(&self, _searchable: SearchableToken) -> impl IntoIterator<Item = Col<'_>> {\n    [Col { text: self.name.clone(), searchable: None }]\n}\n\n// after — mark the column users fuzzy-search on\nfn columns(&self, searchable: SearchableToken) -> impl IntoIterator<Item = Col<'_>> {\n    [Col { text: self.name.clone(), searchable: Some(searchable) }]\n}","handlingStrategy":"validation","validationCode":"// before constructing/opening the picker\nfn picker_items_valid(items: &[impl FuzzyPickerItem]) -> bool {\n    !items.is_empty()\n        && items.iter().all(|i| i.columns(SearchableToken(())).into_iter().any(|c| c.searchable.is_some()))\n}\nif !picker_items_valid(&items) { /* show 'nothing to pick' instead of opening the picker */ }","typeGuard":"fn has_searchable_column(item: &impl FuzzyPickerItem) -> bool {\n    item.columns(SearchableToken(()))\n        .into_iter()\n        .any(|col| col.searchable.is_some())\n}","tryCatchPattern":null,"preventionTips":["Always attach the SearchableToken parameter to exactly one column in every FuzzyPickerItem impl","Copy a known-good impl (copy_selection_picker.rs:236) when adding item types","Never pass an empty items vec to the picker; short-circuit with an empty-state message","Add a per-item-type unit test asserting a searchable column exists"],"tags":["rust","tui","fuzzy-picker","trait-contract","ratatui"],"backgroundTag":"trait-contract-violation","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}