Skip to content

feat: Clipboard history lacks search, forcing users to manually cycle through entries and undermining the core usability of ClipWallet. #79

Description

@divyanshim27

💡 Problem Statement

From the roadmap:

  • Clipboard history search

This is the most impactful missing feature for daily use. Currently, to find a specific previously-copied item, a user must:

  1. Press Cmd+Opt+Tab repeatedly to cycle through up to 50 entries in dynamic mode
  2. Visually scan each entry as it's temporarily set as the system clipboard
  3. Miss the desired entry and cycle all the way back around

This defeats the purpose of a 50-entry history. The moment a user has more than ~5 entries, recall becomes a memory exercise rather than a retrieval tool.

Proposed Fix

Implement a search overlay triggered by a hotkey chord that fuzzy-filters clipboard entries in real time:

1. New hotkey: Cmd + Opt + F → Open search overlay

// src/ui/app.rs
pub enum AppMode {
    Normal,
    Search { query: String, filtered_indices: Vec<usize> },
}

// In hotkey handler:
HotkeyAction::OpenSearch => {
    self.mode = AppMode::Search {
        query: String::new(),
        filtered_indices: (0..self.entries.len()).collect(),
    };
}

2. Fuzzy filtering on keypress

// src/ui/app.rs
fn update_search_filter(&mut self, query: &str) {
    let filtered = self.entries.iter()
        .enumerate()
        .filter(|(_, entry)| {
            match entry.data_type() {
                DataType::Text => entry.preview_text()
                    .to_lowercase()
                    .contains(&query.to_lowercase()),
                DataType::Image => query.is_empty() || query.contains("image"),
                DataType::FilePath => entry.file_path_display()
                    .to_lowercase()
                    .contains(&query.to_lowercase()),
                _ => query.is_empty(),
            }
        })
        .map(|(idx, _)| idx)
        .collect();

    if let AppMode::Search { filtered_indices, .. } = &mut self.mode {
        *filtered_indices = filtered;
    }
}

3. Render the search overlay

// src/ui/app.rs render function
if let AppMode::Search { query, filtered_indices } = &self.mode {
    // Render a floating overlay on top of the status bar area
    let overlay = Block::default()
        .title("🔍 Search clipboard history")
        .borders(Borders::ALL)
        .border_style(Style::default().fg(Color::Cyan));

    let search_input = Paragraph::new(format!("> {query}|"))
        .block(overlay);

    // List filtered results
    let items: Vec<ListItem> = filtered_indices.iter().take(8).map(|&idx| {
        let entry = &self.entries[idx];
        ListItem::new(format!("[{}] {}", idx + 1, entry.preview_text()))
    }).collect();

    let result_list = List::new(items)
        .highlight_style(Style::default().bg(Color::DarkGray));

    // ... render to frame
}

4. Pressing Enter in search mode: paste the selected result

KeyCode::Enter => {
    if let AppMode::Search { filtered_indices, .. } = &self.mode {
        if let Some(&target_idx) = filtered_indices.first() {
            self.set_active_entry(target_idx);
            self.paste_to_system_clipboard();
        }
    }
    self.mode = AppMode::Normal;
}

Files to Modify

File Change
src/ui/app.rs Add AppMode::Search variant, update_search_filter(), search overlay render
src/ui/input.rs Handle Cmd+Opt+F to enter search mode; text input in search mode
README.md Document the search hotkey and update roadmap
ROADMAP.md Mark clipboard search as in-progress

Suggested labels: enhancement, feature, ux, rust, good first issue

I would like to work on this. Could you please assign it to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions