gitbutlerapp/gitbutler · error

FuzzyPickerItem::columns must return a searchable column

Error message

FuzzyPickerItem::columns must return a searchable column

What it means

Same FuzzyPickerItem contract, enforced at query time: once the user types a query, the picker iterates every item and calls .find(|col| col.searchable.is_some()).expect(...). Unlike the layout-time check at fuzzy_picker.rs:114 (which inspects only items[0]), this one covers all items, so it fires on the first keystroke when any item other than the first lacks a searchable column.

Source

Thrown at crates/but/src/command/legacy/status/tui/fuzzy_picker.rs:271

        if query.is_empty() {
            self.items_to_show.extend(
                self.items
                    .iter()
                    .enumerate()
                    .map(|(item_idx, _)| ItemToShow::Plain { item_idx }),
            );
        } else {
            let mut fuzzy_matches = self
                .items
                .iter()
                .enumerate()
                .filter_map(|(item_idx, item)| {
                    let col = item
                        .columns(SearchableToken(()))
                        .into_iter()
                        .find(|col| col.searchable.is_some())
                        .expect("FuzzyPickerItem::columns must return a searchable column");
                    let (score, indices) = self.matcher.fuzzy_indices(&col.text, query)?;
                    Some((item_idx, col, score, indices))
                })
                .collect::<Vec<_>>();
            fuzzy_matches.sort_unstable_by(|(_, _, score_a, _), (_, _, score_b, _)| {
                score_a.cmp(score_b).reverse()
            });
            self.items_to_show.extend(fuzzy_matches.into_iter().map(
                |(item_idx, _, _, indices)| ItemToShow::FuzzyMatch {
                    item_idx,
                    char_indices: indices,
                },
            ));
        }
    }

    pub fn handle_message(
        mut self,

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Make every columns impl unconditionally mark one column searchable; use empty text rather than None when there is nothing to search
  2. Filter out non-searchable items before passing the list to the picker
  3. Soften the query path: treat a missing searchable column as unmatchable (return None from the filter_map) instead of expecting
  4. Add a test that types a query over every item kind used in pickers

Example fix

// before — first keystroke on a non-conforming item panics
let col = item.columns(SearchableToken(())).into_iter()
    .find(|col| col.searchable.is_some())
    .expect("FuzzyPickerItem::columns must return a searchable column");

// after — non-conforming items are simply unmatchable
let Some(col) = item.columns(SearchableToken(())).into_iter()
    .find(|col| col.searchable.is_some()) else { return None };
Defensive patterns

Strategy: validation

Validate before calling

// validate ALL items, not just the first, before the picker starts filtering
let all_searchable = items.iter().all(|i|
    i.columns(SearchableToken(())).into_iter().any(|c| c.searchable.is_some())
);
debug_assert!(all_searchable, "item without searchable column in picker");

Type guard

fn is_fuzzy_searchable(item: &impl FuzzyPickerItem) -> bool {
    item.columns(SearchableToken(()))
        .into_iter()
        .any(|col| col.searchable.is_some())
}

Prevention

When it happens

Trigger: Typing into the fuzzy picker when the list is heterogeneous and some item/variant's columns impl yields no searchable column (header- or separator-like items mixed with real items), or an impl whose searchable column is conditional on item state.

Common situations: Mixed item lists passed to one picker; items in a state (unnamed branch/stack, empty field) where the impl returns all searchable: None; refactoring columns() and dropping the token; item added after the picker was laid out fine (first item still conforms).

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/c935a3d2dec9ae48. Report an issue: GitHub.