ommp.git

ommp

~ Oh My Music Player — a standalone terminal music player built with Rust and ratatui
git clone https://github.com/debuman/ommp.git
commit 3de0dadf2030be54bcea7dfd04a7a4a9a8366aaa
parent 9a4c1e8c642bd141661d2a23fe884a1bd00143be
Author: sqzer-x <110329244+sqzer-x@users.noreply.github.com>
Date: Sat, 1 Aug 2026 15:48:11 +0900

Delete code that was never compiled or never read

album_artists_pane.rs and search_pane.rs were not declared in panes/mod.rs,
so 393 lines were never compiled — which is why nothing ever flagged that
search_pane.rs references three AppAction variants that do not exist and
would fail to build if it were wired up. They were also the only reason three
Library album-artist methods carried allow(dead_code).

Their backing state went with them: search_mode was never assigned true by
compiled code, so search_query stayed empty and search_results stayed empty
forever, making two branches unreachable and a remap in replace_library
pointless. QueueState::scroll_offset was written in three places while the
queue pane used its own. Six of the theme's sixteen fields were never read,
hidden behind a blanket allow(dead_code). Track::lyrics survived the removal
of the lyrics feature. The queue pane's mouse handler computed a hit test
whose body was a single comment, duplicating what handler.rs already does.

Event::Resize carried dimensions nobody used (the next draw re-queries the
terminal) and AudioEvent::TrackError a message nobody displayed. No
allow(dead_code) remains in the tree.

Diffstat:
Msrc/app/handler.rs | 5-----
Msrc/app/mod.rs | 17-----------------
Msrc/app/state.rs | 1-
Msrc/audio/player.rs | 4++--
Msrc/event/input.rs | 4++--
Msrc/event/mod.rs | 8++++----
Msrc/library/mod.rs | 32--------------------------------
Msrc/library/track.rs | 6------
Msrc/main.rs | 5++---
Dsrc/ui/panes/album_artists_pane.rs | 186-------------------------------------------------------------------------------
Msrc/ui/panes/queue_pane.rs | 22+++-------------------
Dsrc/ui/panes/search_pane.rs | 207-------------------------------------------------------------------------------
Msrc/ui/theme.rs | 17-----------------
13 files changed, 13 insertion(+), 501 deletion(-)

diff --git a/src/app/handler.rs b/src/app/handler.rs
@@ -221,11 +221,6 @@ pub fn handle_key_event(key: KeyEvent, app: &App, ui: &mut Ui) -> Vec<AppAction>
         return actions;
     }
 
-    // In search input mode, ignore (search handled by modal now)
-    if app.search_mode {
-        return actions;
-    }
-
     // Chord: Ctrl+E pressed, waiting for next key
     if ui.chord_pending {
         ui.chord_pending = false;
diff --git a/src/app/mod.rs b/src/app/mod.rs
@@ -62,9 +62,6 @@ pub struct App {
     pub queue: QueueState,
     pub library: Library,
     pub music_dir: PathBuf,
-    pub search_query: String,
-    pub search_mode: bool,
-    pub search_results: Vec<usize>,
     pub playlists: Vec<state::Playlist>,
     pub sync_state: SyncState,
     pub initial_scan_complete: bool,
@@ -87,9 +84,6 @@ impl App {
             queue: QueueState::default(),
             library: Library::new(),
             music_dir,
-            search_query: String::new(),
-            search_mode: false,
-            search_results: Vec::new(),
             playlists: vec![state::Playlist::new("Bookmarks")],
             sync_state: SyncState::Idle,
             initial_scan_complete: false,
@@ -207,14 +201,12 @@ impl App {
                 self.queue.current_index =
                     if self.queue.tracks.is_empty() { None } else { Some(0) };
                 self.queue.selected_index = 0;
-                self.queue.scroll_offset = 0;
                 self.resync_shuffle_order(false);
             }
             AppAction::ClearQueue => {
                 self.queue.tracks.clear();
                 self.queue.current_index = None;
                 self.queue.selected_index = 0;
-                self.queue.scroll_offset = 0;
                 // Without this the engine keeps playing a track the queue no
                 // longer contains, and its position updates keep the clock running.
                 self.stop_playback();
@@ -372,9 +364,6 @@ impl App {
         self.queue.selected_index = self.queue.selected_index.min(
             self.queue.tracks.len().saturating_sub(1)
         );
-        self.queue.scroll_offset = self.queue.scroll_offset.min(
-            self.queue.tracks.len().saturating_sub(1)
-        );
 
         // Remap playlists. Entries the new library lacks are parked in `missing`
         // rather than dropped, and anything parked earlier is restored if it came
@@ -401,11 +390,6 @@ impl App {
             pl.missing = missing;
         }
 
-        // Remap search results
-        if !self.search_query.is_empty() {
-            self.search_results = new_lib.search(&self.search_query);
-        }
-
         self.library = new_lib;
         self.sync_state = SyncState::Idle;
         self.resync_shuffle_order(true);
@@ -622,7 +606,6 @@ mod tests {
                 sample_rate: None,
                 bit_depth: None,
                 channels: None,
-                lyrics: None,
             })
             .collect();
         app.handle_action(AppAction::ReplaceQueue((0..len).collect()));
diff --git a/src/app/state.rs b/src/app/state.rs
@@ -148,7 +148,6 @@ pub struct QueueState {
     pub tracks: Vec<usize>,
     pub current_index: Option<usize>,
     pub selected_index: usize,
-    pub scroll_offset: usize,
     /// Positions into `tracks`, in shuffled play order — a real permutation, so
     /// a shuffled pass reaches every track exactly once and can end. Empty while
     /// shuffle is off.
diff --git a/src/audio/player.rs b/src/audio/player.rs
@@ -96,10 +96,10 @@ fn player_thread(cmd_rx: Receiver<PlayerCommand>, event_tx: Sender<Event>) {
                                 play_start = Some(Instant::now());
                                 let _ = event_tx.send(Event::Audio(AudioEvent::Playing));
                             }
-                            Err(e) => {
+                            Err(_) => {
                                 duration = 0.0;
                                 play_start = None;
-                                let _ = event_tx.send(Event::Audio(AudioEvent::TrackError(e)));
+                                let _ = event_tx.send(Event::Audio(AudioEvent::TrackError));
                             }
                         }
                     }
diff --git a/src/event/input.rs b/src/event/input.rs
@@ -21,8 +21,8 @@ pub fn spawn_input_thread(tx: Sender<Event>) -> std::thread::JoinHandle<()> {
                             break;
                         }
                     }
-                    Ok(CtEvent::Resize(w, h))
-                        if tx.send(Event::Resize(w, h)).is_err() => {
+                    Ok(CtEvent::Resize(_, _))
+                        if tx.send(Event::Resize).is_err() => {
                             break;
                         }
                     _ => {}
diff --git a/src/event/mod.rs b/src/event/mod.rs
@@ -1,22 +1,22 @@
 pub mod input;
 
 #[derive(Debug)]
-#[allow(dead_code)]
 pub enum Event {
     Key(crossterm::event::KeyEvent),
     Mouse(crossterm::event::MouseEvent),
-    Resize(u16, u16),
+    /// Dimensions are not carried: the next draw re-queries the terminal.
+    Resize,
     Tick,
     Audio(AudioEvent),
     LibraryReady(crate::library::Library),
 }
 
 #[derive(Debug, Clone)]
-#[allow(dead_code)]
 pub enum AudioEvent {
     PositionUpdate { position_secs: f64, duration_secs: f64 },
     TrackFinished,
-    TrackError(String),
+    /// The current track could not be decoded.
+    TrackError,
     /// The output device could not be opened. The player thread exits after
     /// sending this, so every later command is dropped — the UI has to say so
     /// rather than show a progress bar advancing over silence.
diff --git a/src/library/mod.rs b/src/library/mod.rs
@@ -40,17 +40,6 @@ impl Library {
         result
     }
 
-    #[allow(dead_code)]
-    pub fn get_album_artists(&self) -> Vec<String> {
-        let mut set = BTreeSet::new();
-        for t in &self.tracks {
-            if !t.album_artist.is_empty() {
-                set.insert(t.album_artist.clone());
-            }
-        }
-        set.into_iter().collect()
-    }
-
     pub fn get_genres(&self) -> Vec<String> {
         let mut set = BTreeSet::new();
         for t in &self.tracks {
@@ -91,16 +80,6 @@ impl Library {
             .collect()
     }
 
-    #[allow(dead_code)]
-    pub fn get_tracks_by_album_artist(&self, album_artist: &str) -> Vec<usize> {
-        self.tracks
-            .iter()
-            .enumerate()
-            .filter(|(_, t)| t.album_artist == album_artist)
-            .map(|(i, _)| i)
-            .collect()
-    }
-
     pub fn get_tracks_by_album(&self, album: &str) -> Vec<usize> {
         self.tracks
             .iter()
@@ -119,17 +98,6 @@ impl Library {
             .collect()
     }
 
-    #[allow(dead_code)]
-    pub fn get_albums_by_album_artist(&self, album_artist: &str) -> Vec<String> {
-        let mut set = BTreeSet::new();
-        for t in &self.tracks {
-            if t.album_artist == album_artist && !t.album.is_empty() {
-                set.insert(t.album.clone());
-            }
-        }
-        set.into_iter().collect()
-    }
-
     pub fn get_formats(&self) -> Vec<String> {
         let mut set = BTreeSet::new();
         for t in &self.tracks {
diff --git a/src/library/track.rs b/src/library/track.rs
@@ -22,8 +22,6 @@ pub struct Track {
     pub sample_rate: Option<u32>,
     pub bit_depth: Option<u8>,
     pub channels: Option<u8>,
-    #[allow(dead_code)]
-    pub lyrics: Option<String>,
 }
 
 impl Track {
@@ -71,9 +69,6 @@ impl Track {
             sample_rate: properties.sample_rate(),
             bit_depth: properties.bit_depth(),
             channels: properties.channels(),
-            lyrics: tag
-                .and_then(|t| t.get_string(&ItemKey::Lyrics))
-                .map(|s| s.to_string()),
         })
     }
 
@@ -155,7 +150,6 @@ mod tests {
             sample_rate,
             bit_depth,
             channels,
-            lyrics: None,
         }
     }
 
diff --git a/src/main.rs b/src/main.rs
@@ -208,7 +208,6 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()>
                         // Handle queue selection directly for playlist focus
                         // Skip when any modal is open
                         if app.focus == FocusedPane::Playlist
-                            && !app.search_mode
                             && !ui.show_search_modal
                             && !ui.show_help_modal
                             && !ui.show_playlist_modal
@@ -233,7 +232,7 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()>
                             handler::handle_mouse_event(mouse, &app, &mut ui, area)
                         }
                     }
-                    Event::Resize(_, _) => {
+                    Event::Resize => {
                         vec![] // Will re-render on next loop
                     }
                     Event::Tick => {
@@ -272,7 +271,7 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> Result<()>
                                 duration_secs,
                             }],
                             AudioEvent::TrackFinished => vec![app::AppAction::TrackFinished],
-                            AudioEvent::TrackError(_) => vec![app::AppAction::TrackFailed],
+                            AudioEvent::TrackError => vec![app::AppAction::TrackFailed],
                             AudioEvent::DeviceError(msg) => {
                                 app.audio_error = Some(msg);
                                 app.playback.state = app::state::PlayState::Stopped;
diff --git a/src/ui/panes/album_artists_pane.rs b/src/ui/panes/album_artists_pane.rs
@@ -1,186 +0,0 @@
-use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
-use ratatui::layout::Rect;
-use ratatui::style::{Color, Modifier, Style};
-use ratatui::text::{Line, Span};
-use ratatui::widgets::{Block, Borders, List, ListItem, Scrollbar, ScrollbarOrientation, ScrollbarState};
-use ratatui::Frame;
-
-use crate::app::{App, AppAction};
-use crate::ui::pane::Pane;
-use crate::ui::theme::Theme;
-
-const HOVER_BG: Color = Color::Indexed(238);
-
-pub struct AlbumArtistsPane {
-    pub selected: usize,
-    pub scroll_offset: usize,
-    pub hover_row: Option<usize>,
-}
-
-impl AlbumArtistsPane {
-    pub fn new() -> Self {
-        Self {
-            selected: 0,
-            scroll_offset: 0,
-            hover_row: None,
-        }
-    }
-}
-
-impl Pane for AlbumArtistsPane {
-    fn render(&mut self, frame: &mut Frame, area: Rect, focused: bool, app: &App, theme: &Theme) {
-        let album_artists = app.library.get_album_artists();
-        let count = album_artists.len();
-        let border_color = if focused {
-            theme.border_focused
-        } else {
-            theme.border_unfocused
-        };
-
-        let block = Block::default()
-            .borders(Borders::ALL)
-            .border_style(Style::default().fg(border_color))
-            .title_style(Style::default().fg(if focused {
-                theme.border_focused
-            } else {
-                theme.fg
-            }));
-
-        let inner = block.inner(area);
-        let inner_height = inner.height as usize;
-
-        // Auto-scroll
-        if count > 0 {
-            if self.selected < self.scroll_offset {
-                self.scroll_offset = self.selected;
-            }
-            if inner_height > 0 && self.selected >= self.scroll_offset + inner_height {
-                self.scroll_offset = self.selected - inner_height + 1;
-            }
-        }
-
-        let has_scrollbar = count > inner_height;
-        let highlight = Style::default()
-            .bg(theme.highlight_bg)
-            .fg(theme.highlight_fg)
-            .add_modifier(Modifier::BOLD);
-
-        let items: Vec<ListItem> = album_artists
-            .iter()
-            .enumerate()
-            .skip(self.scroll_offset)
-            .take(inner_height)
-            .map(|(i, artist)| {
-                let is_selected = i == self.selected;
-                let is_hovered = self.hover_row == Some(i);
-                let style = if is_selected && focused {
-                    highlight
-                } else if is_hovered {
-                    Style::default().fg(theme.fg).bg(HOVER_BG)
-                } else {
-                    Style::default().fg(theme.fg)
-                };
-                ListItem::new(Line::from(Span::styled(format!("  {}", artist), style)))
-            })
-            .collect();
-
-        let list = List::new(items).block(block);
-        frame.render_widget(list, area);
-
-        if has_scrollbar {
-            let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
-                .begin_symbol(None)
-                .end_symbol(None);
-            let mut scrollbar_state = ScrollbarState::new(count)
-                .position(self.scroll_offset);
-            frame.render_stateful_widget(
-                scrollbar,
-                area.inner(ratatui::layout::Margin { vertical: 1, horizontal: 0 }),
-                &mut scrollbar_state,
-            );
-        }
-    }
-
-    fn handle_key(&mut self, key: KeyEvent, app: &App) -> Option<AppAction> {
-        let album_artists = app.library.get_album_artists();
-        let count = album_artists.len();
-        if count == 0 {
-            return None;
-        }
-
-        match key.code {
-            KeyCode::Char('j') | KeyCode::Down => {
-                if self.selected < count - 1 {
-                    self.selected += 1;
-                }
-                None
-            }
-            KeyCode::Char('k') | KeyCode::Up => {
-                if self.selected > 0 {
-                    self.selected -= 1;
-                }
-                None
-            }
-            KeyCode::Enter => {
-                if self.selected < count {
-                    let tracks =
-                        app.library.get_tracks_by_album_artist(&album_artists[self.selected]);
-                    if !tracks.is_empty() {
-                        return Some(AppAction::AddToQueue(tracks));
-                    }
-                }
-                None
-            }
-            KeyCode::Home | KeyCode::Char('g') => {
-                self.selected = 0;
-                self.scroll_offset = 0;
-                None
-            }
-            KeyCode::End | KeyCode::Char('G') => {
-                self.selected = count.saturating_sub(1);
-                None
-            }
-            _ => None,
-        }
-    }
-
-    fn handle_mouse(&mut self, event: MouseEvent, area: Rect, app: &App) -> Option<AppAction> {
-        let block = Block::default().borders(Borders::ALL);
-        let inner = block.inner(area);
-        let count = app.library.get_album_artists().len();
-
-        match event.kind {
-            MouseEventKind::Down(MouseButton::Left) => {
-                if event.column >= inner.x
-                    && event.column < inner.x + inner.width
-                    && event.row >= inner.y
-                    && event.row < inner.y + inner.height
-                {
-                    let clicked = self.scroll_offset + (event.row - inner.y) as usize;
-                    if clicked < count {
-                        self.selected = clicked;
-                    }
-                }
-                None
-            }
-            MouseEventKind::ScrollDown => self.handle_scroll(false, app),
-            MouseEventKind::ScrollUp => self.handle_scroll(true, app),
-            _ => None,
-        }
-    }
-
-    fn handle_scroll(&mut self, up: bool, app: &App) -> Option<AppAction> {
-        let count = app.library.get_album_artists().len();
-        if count == 0 {
-            return None;
-        }
-        if up {
-            self.scroll_offset = self.scroll_offset.saturating_sub(3);
-            self.selected = self.selected.saturating_sub(3);
-        } else {
-            self.scroll_offset = (self.scroll_offset + 3).min(count.saturating_sub(1));
-            self.selected = (self.selected + 3).min(count.saturating_sub(1));
-        }
-        None
-    }
-}
diff --git a/src/ui/panes/queue_pane.rs b/src/ui/panes/queue_pane.rs
@@ -1,4 +1,4 @@
-use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
+use crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind};
 use ratatui::layout::Rect;
 use ratatui::style::{Color, Modifier, Style};
 use ratatui::text::{Line, Span};
@@ -253,25 +253,9 @@ impl Pane for QueuePane {
         }
     }
 
-    fn handle_mouse(&mut self, event: MouseEvent, area: Rect, app: &App) -> Option<AppAction> {
-        let block = Block::default().borders(Borders::ALL);
-        let inner = block.inner(area);
-        let count = app.queue.tracks.len();
-
+    fn handle_mouse(&mut self, event: MouseEvent, _area: Rect, app: &App) -> Option<AppAction> {
+        // Clicks are handled in handler.rs, which owns queue selection.
         match event.kind {
-            MouseEventKind::Down(MouseButton::Left) => {
-                if event.column >= inner.x
-                    && event.column < inner.x + inner.width
-                    && event.row >= inner.y
-                    && event.row < inner.y + inner.height
-                {
-                    let clicked = self.scroll_offset + (event.row - inner.y) as usize;
-                    if clicked < count {
-                        // Selection handled by handler.rs
-                    }
-                }
-                None
-            }
             MouseEventKind::ScrollDown => self.handle_scroll(false, app),
             MouseEventKind::ScrollUp => self.handle_scroll(true, app),
             _ => None,
diff --git a/src/ui/panes/search_pane.rs b/src/ui/panes/search_pane.rs
@@ -1,207 +0,0 @@
-use crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
-use ratatui::layout::{Constraint, Direction, Layout, Rect};
-use ratatui::style::{Color, Modifier, Style};
-use ratatui::text::{Line, Span};
-use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
-use ratatui::Frame;
-
-use crate::app::{App, AppAction};
-use crate::ui::pane::Pane;
-use crate::ui::theme::Theme;
-
-pub struct SearchPane {
-    pub selected: usize,
-    pub scroll_offset: usize,
-    pub input_mode: bool,
-    pub hover_row: Option<usize>,
-}
-
-impl SearchPane {
-    pub fn new() -> Self {
-        Self {
-            selected: 0,
-            scroll_offset: 0,
-            input_mode: false,
-            hover_row: None,
-        }
-    }
-}
-
-impl Pane for SearchPane {
-    fn render(&mut self, frame: &mut Frame, area: Rect, focused: bool, app: &App, theme: &Theme) {
-        let border_color = if focused {
-            theme.border_focused
-        } else {
-            theme.border_unfocused
-        };
-
-        let block = Block::default()
-            .borders(Borders::ALL)
-            .border_style(Style::default().fg(border_color))
-            .title_style(Style::default().fg(if focused {
-                theme.border_focused
-            } else {
-                theme.fg
-            }));
-
-        let inner = block.inner(area);
-        frame.render_widget(block, area);
-
-        let chunks = Layout::default()
-            .direction(Direction::Vertical)
-            .constraints([Constraint::Length(1), Constraint::Min(1)])
-            .split(inner);
-
-        // Search input line
-        let input_style = if self.input_mode {
-            Style::default().fg(Color::White).bg(Color::DarkGray)
-        } else {
-            Style::default().fg(Color::Gray)
-        };
-
-        let cursor = if self.input_mode { "▎" } else { "" };
-        let input = Paragraph::new(Line::from(vec![
-            Span::styled(" / ", Style::default().fg(Color::Cyan)),
-            Span::styled(&app.search_query, input_style),
-            Span::styled(cursor, Style::default().fg(Color::White)),
-        ]));
-        frame.render_widget(input, chunks[0]);
-
-        // Results
-        let inner_height = chunks[1].height as usize;
-        let items: Vec<ListItem> = app
-            .search_results
-            .iter()
-            .enumerate()
-            .skip(self.scroll_offset)
-            .take(inner_height)
-            .map(|(i, &track_idx)| {
-                let track = &app.library.tracks[track_idx];
-                let is_selected = i == self.selected;
-                let style = if is_selected && focused {
-                    Style::default()
-                        .bg(theme.highlight_bg)
-                        .fg(theme.highlight_fg)
-                        .add_modifier(Modifier::BOLD)
-                } else {
-                    Style::default().fg(theme.fg)
-                };
-
-                let artist_style = if is_selected && focused {
-                    style
-                } else {
-                    Style::default().fg(Color::Gray)
-                };
-
-                ListItem::new(Line::from(vec![
-                    Span::styled(&track.title, style),
-                    Span::styled(format!(" - {}", track.display_artist()), artist_style),
-                ]))
-            })
-            .collect();
-
-        let list = List::new(items);
-        frame.render_widget(list, chunks[1]);
-    }
-
-    fn handle_key(&mut self, key: KeyEvent, app: &App) -> Option<AppAction> {
-        if self.input_mode || app.search_mode {
-            match key.code {
-                KeyCode::Esc => {
-                    self.input_mode = false;
-                    return Some(AppAction::ExitSearchMode);
-                }
-                KeyCode::Enter => {
-                    self.input_mode = false;
-                    // Add selected search result to queue
-                    if self.selected < app.search_results.len() {
-                        let track_idx = app.search_results[self.selected];
-                        return Some(AppAction::AddToQueue(vec![track_idx]));
-                    }
-                    return Some(AppAction::ExitSearchMode);
-                }
-                KeyCode::Backspace => {
-                    let mut q = app.search_query.clone();
-                    q.pop();
-                    return Some(AppAction::SearchQuery(q));
-                }
-                KeyCode::Char(c) => {
-                    let mut q = app.search_query.clone();
-                    q.push(c);
-                    return Some(AppAction::SearchQuery(q));
-                }
-                _ => {}
-            }
-            return None;
-        }
-
-        match key.code {
-            KeyCode::Char('/') | KeyCode::Char('i') => {
-                self.input_mode = true;
-                Some(AppAction::EnterSearchMode)
-            }
-            KeyCode::Char('j') | KeyCode::Down => {
-                if self.selected < app.search_results.len().saturating_sub(1) {
-                    self.selected += 1;
-                }
-                None
-            }
-            KeyCode::Char('k') | KeyCode::Up => {
-                if self.selected > 0 {
-                    self.selected -= 1;
-                }
-                None
-            }
-            KeyCode::Enter => {
-                if self.selected < app.search_results.len() {
-                    let track_idx = app.search_results[self.selected];
-                    Some(AppAction::AddToQueue(vec![track_idx]))
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
-    }
-
-    fn handle_mouse(&mut self, event: MouseEvent, area: Rect, app: &App) -> Option<AppAction> {
-        let block = Block::default().borders(Borders::ALL);
-        let inner = block.inner(area);
-
-        match event.kind {
-            MouseEventKind::Down(MouseButton::Left) => {
-                if event.row == inner.y {
-                    // Click on search input
-                    self.input_mode = true;
-                    return Some(AppAction::EnterSearchMode);
-                }
-                let results_y = inner.y + 1;
-                if event.row >= results_y && event.row < inner.y + inner.height {
-                    let clicked = self.scroll_offset + (event.row - results_y) as usize;
-                    if clicked < app.search_results.len() {
-                        self.selected = clicked;
-                    }
-                }
-                None
-            }
-            MouseEventKind::ScrollDown => self.handle_scroll(false, app),
-            MouseEventKind::ScrollUp => self.handle_scroll(true, app),
-            _ => None,
-        }
-    }
-
-    fn handle_scroll(&mut self, up: bool, app: &App) -> Option<AppAction> {
-        let count = app.search_results.len();
-        if count == 0 {
-            return None;
-        }
-        if up {
-            self.scroll_offset = self.scroll_offset.saturating_sub(3);
-            self.selected = self.selected.saturating_sub(3);
-        } else {
-            self.scroll_offset = (self.scroll_offset + 3).min(count.saturating_sub(1));
-            self.selected = (self.selected + 3).min(count.saturating_sub(1));
-        }
-        None
-    }
-}
diff --git a/src/ui/theme.rs b/src/ui/theme.rs
@@ -1,21 +1,14 @@
 use ratatui::style::{Color, Modifier, Style};
 
-#[allow(dead_code)]
 pub struct Theme {
-    pub bg: Color,
     pub fg: Color,
     pub border_focused: Color,
     pub border_unfocused: Color,
     pub highlight_bg: Color,
     pub highlight_fg: Color,
-    pub tab_active: Style,
     pub tab_inactive: Style,
-    pub status_bar_bg: Color,
     pub progress_filled: Color,
     pub progress_empty: Color,
-    pub playing_indicator: Color,
-    pub title_style: Style,
-    pub artist_style: Style,
     pub dim_style: Style,
     pub current_track_style: Style,
 }
@@ -23,7 +16,6 @@ pub struct Theme {
 impl Default for Theme {
     fn default() -> Self {
         Self {
-            bg: Color::Reset,
             fg: Color::White,
             // These were both Cyan, which made the focus indicator a no-op:
             // every pane picked a border colour from `focused` and got the same
@@ -32,18 +24,9 @@ impl Default for Theme {
             border_unfocused: Color::Rgb(70, 80, 95),
             highlight_bg: Color::Cyan,
             highlight_fg: Color::Black,
-            tab_active: Style::default()
-                .fg(Color::Cyan)
-                .add_modifier(Modifier::BOLD),
             tab_inactive: Style::default().fg(Color::DarkGray),
-            status_bar_bg: Color::DarkGray,
             progress_filled: Color::Rgb(200, 80, 255),
             progress_empty: Color::Indexed(236),
-            playing_indicator: Color::Rgb(80, 255, 120),
-            title_style: Style::default()
-                .fg(Color::White)
-                .add_modifier(Modifier::BOLD),
-            artist_style: Style::default().fg(Color::Gray),
             dim_style: Style::default().fg(Color::DarkGray),
             current_track_style: Style::default()
                 .fg(Color::Rgb(100, 220, 255))