{"record":{"id":"c935a3d2dec9ae48","repo":"gitbutlerapp/gitbutler","slug":"fuzzypickeritem-columns-must-return-a-searchable-column","errorCode":null,"errorMessage":"FuzzyPickerItem::columns must return a searchable column","messagePattern":"FuzzyPickerItem::columns must return a searchable column","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/status/tui/fuzzy_picker.rs","lineNumber":271,"sourceCode":"\n        if query.is_empty() {\n            self.items_to_show.extend(\n                self.items\n                    .iter()\n                    .enumerate()\n                    .map(|(item_idx, _)| ItemToShow::Plain { item_idx }),\n            );\n        } else {\n            let mut fuzzy_matches = self\n                .items\n                .iter()\n                .enumerate()\n                .filter_map(|(item_idx, item)| {\n                    let col = item\n                        .columns(SearchableToken(()))\n                        .into_iter()\n                        .find(|col| col.searchable.is_some())\n                        .expect(\"FuzzyPickerItem::columns must return a searchable column\");\n                    let (score, indices) = self.matcher.fuzzy_indices(&col.text, query)?;\n                    Some((item_idx, col, score, indices))\n                })\n                .collect::<Vec<_>>();\n            fuzzy_matches.sort_unstable_by(|(_, _, score_a, _), (_, _, score_b, _)| {\n                score_a.cmp(score_b).reverse()\n            });\n            self.items_to_show.extend(fuzzy_matches.into_iter().map(\n                |(item_idx, _, _, indices)| ItemToShow::FuzzyMatch {\n                    item_idx,\n                    char_indices: indices,\n                },\n            ));\n        }\n    }\n\n    pub fn handle_message(\n        mut self,","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/legacy/status/tui/fuzzy_picker.rs#L253-L289","documentation":"filter_items in the TUI fuzzy picker calls FuzzyPickerItem::columns(SearchableToken(())) and requires that at least one returned column has a searchable token. If an item implementation returns no column marked searchable, `.find(...)` yields None and the `.expect(...)` panics. This is an internal contract between the FuzzyPickerItem trait and the picker's filtering logic: every item must expose at least one searchable column so the matcher can score it.","triggerScenarios":"Pressing a key that triggers fuzzy filtering in the `but` status TUI while any item in the picker violates the trait contract — i.e. its `columns()` implementation returns no column with `searchable: Some(_)`.","commonSituations":"Adding a new FuzzyPickerItem variant (or modifying columns()) and forgetting to tag at least one column as searchable; a refactor changes column construction so `searchable` becomes None; a column list becomes empty for a particular data state.","solutions":["Open the FuzzyPickerItem implementation for the displayed item type and ensure at least one column returned has `searchable: Some(...)`","Change the code to fall back gracefully instead of panicking: use `.find(...).ok()?` / `filter_map` to skip non-searchable items","Add a debug_assert or unit test per item type asserting columns() yields a searchable column"],"exampleFix":"// before\nlet col = item\n    .columns(SearchableToken(()))\n    .into_iter()\n    .find(|col| col.searchable.is_some())\n    .expect(\"FuzzyPickerItem::columns must return a searchable column\");\n// after\nlet col = item\n    .columns(SearchableToken(()))\n    .into_iter()\n    .find(|col| col.searchable.is_some())?;","handlingStrategy":"type-guard","validationCode":"// Rust: verify an item is searchable before adding it to the picker\nfn assert_searchable(item: &dyn FuzzyPickerItem) {\n    debug_assert!(\n        item.columns(SearchableToken(())).iter().any(|c| c.searchable.is_some()),\n        \"FuzzyPickerItem must return at least one searchable column\"\n    );\n}","typeGuard":"fn has_searchable_column(item: &dyn FuzzyPickerItem) -> bool {\n    item.columns(SearchableToken(())).iter().any(|c| c.searchable.is_some())\n}","tryCatchPattern":"// Prefer skipping unfilterable items over panicking:\nlet col = match item.columns(SearchableToken(())).into_iter().find(|c| c.searchable.is_some()) {\n    Some(c) => c,\n    None => return None, // in filter_map\n};","preventionTips":["Add a unit test per FuzzyPickerItem implementation asserting a searchable column exists","Make the searchable column the first element by convention and review new columns() impls","Use debug_assert in the trait contract docs so CI debug builds catch violations early"],"tags":["rust","panic","tui","fuzzy-search","invariant-violation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}