{"owner":"aome510","repo":"spotify-player","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file guides Claude Code when working in this repository.\n\n## Project Overview\n\n`spotify-player` is a terminal Spotify client (requires Spotify Premium) written in Rust. It supports playback control, Spotify Connect, direct streaming via librespot, synced lyrics, desktop notifications, OS media controls, album art rendering, a daemon mode, and a full CLI interface.\n\n## Architecture\n\n### Key modules in `spotify_player/src/`\n\n| Module                        | Responsibility                                                              |\n| ----------------------------- | --------------------------------------------------------------------------- |\n| `main.rs`                     | Entry point; wires threads/tasks; CLI arg parsing; logging init             |\n| `state/`                      | Shared app state (`Arc<State>`): UI, player data, library caches, queue     |\n| `state/model.rs`              | Core domain types: `Track`, `Album`, `Artist`, `Playlist`, `Playback`, etc. |\n| `state/player.rs`             | `PlayerState`: current playback, devices, queue, progress estimation        |\n| `state/data.rs`               | `AppData`: user library, TTL memory caches, file-cache persistence          |\n| `state/ui/`                   | `UIState`: page history stack, popup state, key buffer, count prefix        |\n| `client/mod.rs`               | `AppClient`: Spotify API calls, session management                          |\n| `client/request.rs`           | `ClientRequest` / `PlayerRequest` enums (async message types)               |\n| `client/handlers.rs`          | Tokio task: receives `ClientRequest`, dispatches API calls                  |\n| `config/mod.rs`               | `Configs` loaded once into a `OnceLock`; read via `config::get_config()`    |\n| `config/keymap.rs`            | Default keybindings and key sequence lookup                                 |\n| `config/theme.rs`             | Theme definitions and style resolution                                      |\n| `command.rs`                  | `Command` enum (all TUI commands), `Action` / `CommandOrAction`             |\n| `key.rs`                      | `Key` / `KeySequence` types; vim-style key parsing (`C-x`, `M-x`)           |\n| `event/mod.rs`                | crossterm input loop; routes events to page or popup handler                |\n| `event/{page,popup}.rs`       | Key event dispatch per page / popup overlay                                 |\n| `ui/mod.rs`                   | ratatui render loop; main layout dispatch                                   |\n| `ui/{page,playback,popup}.rs` | Render functions for pages, playback bar, popups                            |\n| `ui/streaming.rs`             | FFT audio visualizer: `VisualizationSink`, `VisBands`, bar chart            |\n| `streaming.rs`                | librespot connection + audio backend setup (feature-gated)                  |\n| `cli/`                        | Unix socket server and client for inter-process CLI commands                |\n| `auth.rs`                     | OAuth scopes and librespot credential/session building                      |\n| `media_control.rs`            | OS media key integration via `souvlaki` (feature-gated)                     |\n\n### Concurrency model\n\nMultiple OS threads communicate via `flume` channels and `Arc<State>`:\n\n- **Event thread** — blocking `crossterm::event::read()` loop\n- **UI thread** — poll-based ratatui render loop\n- **Tokio runtime** — async tasks: client handler, socket listener, streaming\n- **Player-event watcher** — polls librespot playback state, sends `ClientRequest`s\n- **Media-control thread** — OS media key events (feature-gated)\n\nEvent/UI threads never call async functions directly. They send a `ClientRequest` over a `flume` channel; the async handler updates `Arc<State>`.\n\n### Feature flags\n\n| Feature                                                                                                                                 | Effect                                                   |\n| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| `streaming`                                                                                                                             | librespot playback, Spotify Connect, audio visualization |\n| `rodio-backend`                                                                                                                         | Default audio sink (rodio)                               |\n| `alsa-backend`, `pulseaudio-backend`, `portaudio-backend`, `jackaudio-backend`, `rodiojack-backend`, `sdl-backend`, `gstreamer-backend` | Alternative audio sinks                                  |\n| `media-control`                                                                                                                         | OS media key integration                                 |\n| `image`                                                                                                                                 | Album art rendering                                      |\n| `sixel`                                                                                                                                 | Sixel terminal image protocol (extends `image`)          |\n| `pixelate`                                                                                                                              | Pixelated image rendering fallback (extends `image`)     |\n| `notify`                                                                                                                                | Desktop notifications                                    |\n| `daemon`                                                                                                                                | Daemonize mode (implies `streaming`)                     |\n| `fzf`                                                                                                                                   | Fuzzy search                                             |\n\nDefault: `rodio-backend` + `media-control`. Gate feature-specific code with `#[cfg(feature = \"...\")]`.\n\n## Verifying changes\n\nCI treats all warnings as errors. Before committing, run what CI runs:\n\n```sh\ncargo fmt --all                      # CI checks: cargo fmt --all -- --check\ncargo test --no-default-features --features rodio-backend,media-control,image,notify,fzf\ncargo clippy --no-default-features --features rodio-backend,media-control,image,notify,fzf -- -D warnings\ncargo clippy --no-default-features -- -D warnings   # core paths, no features\n```\n\nWhen fixing no-feature clippy warnings, you may need `#[allow(dead_code)]` / `#[allow(unused_variables)]` on items only used in feature-gated paths. If you touch `daemon`/`streaming` code, add `daemon` to the feature list above to lint those paths too.\n\n## Conventions\n\n### Error handling\n\n- Return `anyhow::Result<T>` from fallible functions.\n- Add context with `.context(\"...\")` / `.with_context(|| ...)`; early-exit with `anyhow::bail!(\"...\")`.\n- Format error chains with `{err:#}` (alternate Display), never `{err}`.\n- At async task boundaries, log and continue — never let one failure crash a long-running task:\n  ```rust\n  if let Err(err) = do_something().await {\n      tracing::error!(\"Failed to ...: {err:#}\");\n  }\n  ```\n\n### Logging\n\nUse `tracing` exclusively — never `println!`, `eprintln!`, or the `log` crate.\n\n```rust\ntracing::info!(\"...\");\ntracing::error!(\"...: {err:#}\");\ntracing::debug!(\"{value:?}\");\n```\n\n### Comments and doc comments\n\n- Reserve comments for non-obvious intent: invariants, ordering constraints, workarounds, edge cases.\n- Do not narrate the implementation. A doc comment states a type/function's purpose and contract (what callers need to know); leave the mechanics — which branch does what, field-by-field behaviour, control flow — to the code itself. Implementation rationale belongs in a focused inline comment at the relevant line, not in the doc comment.\n- Keep comments concise and clear, avoid long paragraphs and try to keep comments up to date with code changes.\n\n## Keeping docs up to date\n\n`README.md` and `docs/config.md` are the primary user-facing references. Update them on any user-visible change:\n\n- **New feature / config option** — describe it in `README.md` (`Features`, `Configuration`, …), document the field in `docs/config.md`, and update `examples/app.toml` if applicable.\n- **Changed / removed behaviour** — update affected tables, command descriptions, and usage examples in both files.\n- **New feature flag** — add it to the feature-flags table in `README.md`.\n- **New CLI subcommand** — document it under the CLI section of `README.md`.\n\nKeep `.github/copilot-instructions.md` and this `CLAUDE.md` in sync when project structure, architecture, or conventions change significantly.\n\n### Adding a new `Command`\n\n1. Add the variant to `Command` in `command.rs` and update `Command::desc()`.\n2. Add a default keybinding in `config/keymap.rs`.\n3. Update the command table in `README.md`.\n\n## Writing PR descriptions\n\nBase the description on the actual branch diff (`git diff master...<branch>`), not assumptions. Keep it clear and concise:\n\n- **Summary** — 2-4 sentences: what problem the change solves and the approach. State the _why_ (the prior behaviour / bug) before the _what_.\n- **Changes** — a bullet per logical change, each tagged with the affected module/file. Lead with the user-facing or architectural change, not mechanical edits.\n- Prefer plain prose over filler; omit empty sections. Add a short **Notes** section only for non-obvious trade-offs or follow-ups.\n\nOutput the description as raw markdown in a fenced code block so it can be copied and pasted directly into the PR.\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file guides Claude Code when working in this repository.\n\n## Project Overview\n\n`spotify-player` is a terminal Spotify client (requires Spotify Premium) written in Rust. It supports playback control, Spotify Connect, direct streaming via librespot, synced lyrics, desktop notifications, OS media controls, album art rendering, a daemon mode, and a full CLI interface.\n\n## Architecture\n\n### Key modules in `spotify_player/src/`\n\n| Module                        | Responsibility                                                              |\n| ----------------------------- | --------------------------------------------------------------------------- |\n| `main.rs`                     | Entry point; wires threads/tasks; CLI arg parsing; logging init             |\n| `state/`                      | Shared app state (`Arc<State>`): UI, player data, library caches, queue     |\n| `state/model.rs`              | Core domain types: `Track`, `Album`, `Artist`, `Playlist`, `Playback`, etc. |\n| `state/player.rs`             | `PlayerState`: current playback, devices, queue, progress estimation        |\n| `state/data.rs`               | `AppData`: user library, TTL memory caches, file-cache persistence          |\n| `state/ui/`                   | `UIState`: page history stack, popup state, key buffer, count prefix        |\n| `client/mod.rs`               | `AppClient`: Spotify API calls, session management                          |\n| `client/request.rs`           | `ClientRequest` / `PlayerRequest` enums (async message types)               |\n| `client/handlers.rs`          | Tokio task: receives `ClientRequest`, dispatches API calls                  |\n| `config/mod.rs`               | `Configs` loaded once into a `OnceLock`; read via `config::get_config()`    |\n| `config/keymap.rs`            | Default keybindings and key sequence lookup                                 |\n| `config/theme.rs`             | Theme definitions and style resolution                                      |\n| `command.rs`                  | `Command` enum (all TUI commands), `Action` / `CommandOrAction`             |\n| `key.rs`                      | `Key` / `KeySequence` types; vim-style key parsing (`C-x`, `M-x`)           |\n| `event/mod.rs`                | crossterm input loop; routes events to page or popup handler                |\n| `event/{page,popup}.rs`       | Key event dispatch per page / popup overlay                                 |\n| `ui/mod.rs`                   | ratatui render loop; main layout dispatch                                   |\n| `ui/{page,playback,popup}.rs` | Render functions for pages, playback bar, popups                            |\n| `ui/streaming.rs`             | FFT audio visualizer: `VisualizationSink`, `VisBands`, bar chart            |\n| `streaming.rs`                | librespot connection + audio backend setup (feature-gated)                  |\n| `cli/`                        | Unix socket server and client for inter-process CLI commands                |\n| `auth.rs`                     | OAuth scopes and librespot credential/session building                      |\n| `media_control.rs`            | OS media key integration via `souvlaki` (feature-gated)                     |\n\n### Concurrency model\n\nMultiple OS threads communicate via `flume` channels and `Arc<State>`:\n\n- **Event thread** — blocking `crossterm::event::read()` loop\n- **UI thread** — poll-based ratatui render loop\n- **Tokio runtime** — async tasks: client handler, socket listener, streaming\n- **Player-event watcher** — polls librespot playback state, sends `ClientRequest`s\n- **Media-control thread** — OS media key events (feature-gated)\n\nEvent/UI threads never call async functions directly. They send a `ClientRequest` over a `flume` channel; the async handler updates `Arc<State>`.\n\n### Feature flags\n\n| Feature                                                                                                                                 | Effect                                                   |\n| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| `streaming`                                                                                                                             | librespot playback, Spotify Connect, audio visualization |\n| `rodio-backend`                                                                                                                         | Default audio sink (rodio)                               |\n| `alsa-backend`, `pulseaudio-backend`, `portaudio-backend`, `jackaudio-backend`, `rodiojack-backend`, `sdl-backend`, `gstreamer-backend` | Alternative audio sinks                                  |\n| `media-control`                                                                                                                         | OS media key integration                                 |\n| `image`                                                                                                                                 | Album art rendering                                      |\n| `sixel`                                                                                                                                 | Sixel terminal image protocol (extends `image`)          |\n| `pixelate`                                                                                                                              | Pixelated image rendering fallback (extends `image`)     |\n| `notify`                                                                                                                                | Desktop notifications                                    |\n| `daemon`                                                                                                                                | Daemonize mode (implies `streaming`)                     |\n| `fzf`                                                                                                                                   | Fuzzy search                                             |\n\nDefault: `rodio-backend` + `media-control`. Gate feature-specific code with `#[cfg(feature = \"...\")]`.\n\n## Verifying changes\n\nCI treats all warnings as errors. Before committing, run what CI runs:\n\n```sh\ncargo fmt --all                      # CI checks: cargo fmt --all -- --check\ncargo test --no-default-features --features rodio-backend,media-control,image,notify,fzf\ncargo clippy --no-default-features --features rodio-backend,media-control,image,notify,fzf -- -D warnings\ncargo clippy --no-default-features -- -D warnings   # core paths, no features\n```\n\nWhen fixing no-feature clippy warnings, you may need `#[allow(dead_code)]` / `#[allow(unused_variables)]` on items only used in feature-gated paths. If you touch `daemon`/`streaming` code, add `daemon` to the feature list above to lint those paths too.\n\n## Conventions\n\n### Error handling\n\n- Return `anyhow::Result<T>` from fallible functions.\n- Add context with `.context(\"...\")` / `.with_context(|| ...)`; early-exit with `anyhow::bail!(\"...\")`.\n- Format error chains with `{err:#}` (alternate Display), never `{err}`.\n- At async task boundaries, log and continue — never let one failure crash a long-running task:\n  ```rust\n  if let Err(err) = do_something().await {\n      tracing::error!(\"Failed to ...: {err:#}\");\n  }\n  ```\n\n### Logging\n\nUse `tracing` exclusively — never `println!`, `eprintln!`, or the `log` crate.\n\n```rust\ntracing::info!(\"...\");\ntracing::error!(\"...: {err:#}\");\ntracing::debug!(\"{value:?}\");\n```\n\n### Comments and doc comments\n\n- Reserve comments for non-obvious intent: invariants, ordering constraints, workarounds, edge cases.\n- Do not narrate the implementation. A doc comment states a type/function's purpose and contract (what callers need to know); leave the mechanics — which branch does what, field-by-field behaviour, control flow — to the code itself. Implementation rationale belongs in a focused inline comment at the relevant line, not in the doc comment.\n- Keep comments concise and clear, avoid long paragraphs and try to keep comments up to date with code changes.\n\n## Keeping docs up to date\n\n`README.md` and `docs/config.md` are the primary user-facing references. Update them on any user-visible change:\n\n- **New feature / config option** — describe it in `README.md` (`Features`, `Configuration`, …), document the field in `docs/config.md`, and update `examples/app.toml` if applicable.\n- **Changed / removed behaviour** — update affected tables, command descriptions, and usage examples in both files.\n- **New feature flag** — add it to the feature-flags table in `README.md`.\n- **New CLI subcommand** — document it under the CLI section of `README.md`.\n\nKeep `.github/copilot-instructions.md` and this `CLAUDE.md` in sync when project structure, architecture, or conventions change significantly.\n\n### Adding a new `Command`\n\n1. Add the variant to `Command` in `command.rs` and update `Command::desc()`.\n2. Add a default keybinding in `config/keymap.rs`.\n3. Update the command table in `README.md`.\n\n## Writing PR descriptions\n\nBase the description on the actual branch diff (`git diff master...<branch>`), not assumptions. Keep it clear and concise:\n\n- **Summary** — 2-4 sentences: what problem the change solves and the approach. State the _why_ (the prior behaviour / bug) before the _what_.\n- **Changes** — a bullet per logical change, each tagged with the affected module/file. Lead with the user-facing or architectural change, not mechanical edits.\n- Prefer plain prose over filler; omit empty sections. Add a short **Notes** section only for non-obvious trade-offs or follow-ups.\n\nOutput the description as raw markdown in a fenced code block so it can be copied and pasted directly into the PR.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nThis file guides Claude Code when working in this repository.\n\n## Project Overview\n\n`spotify-player` is a terminal Spotify client (requires Spotify Premium) written in Rust. It supports playback control, Spotify Connect, direct streaming via librespot, synced lyrics, desktop notifications, OS media controls, album art rendering, a daemon mode, and a full CLI interface.\n\n## Architecture\n\n### Key modules in `spotify_player/src/`\n\n| Module                        | Responsibility                                                              |\n| ----------------------------- | --------------------------------------------------------------------------- |\n| `main.rs`                     | Entry point; wires threads/tasks; CLI arg parsing; logging init             |\n| `state/`                      | Shared app state (`Arc<State>`): UI, player data, library caches, queue     |\n| `state/model.rs`              | Core domain types: `Track`, `Album`, `Artist`, `Playlist`, `Playback`, etc. |\n| `state/player.rs`             | `PlayerState`: current playback, devices, queue, progress estimation        |\n| `state/data.rs`               | `AppData`: user library, TTL memory caches, file-cache persistence          |\n| `state/ui/`                   | `UIState`: page history stack, popup state, key buffer, count prefix        |\n| `client/mod.rs`               | `AppClient`: Spotify API calls, session management                          |\n| `client/request.rs`           | `ClientRequest` / `PlayerRequest` enums (async message types)               |\n| `client/handlers.rs`          | Tokio task: receives `ClientRequest`, dispatches API calls                  |\n| `config/mod.rs`               | `Configs` loaded once into a `OnceLock`; read via `config::get_config()`    |\n| `config/keymap.rs`            | Default keybindings and key sequence lookup                                 |\n| `config/theme.rs`             | Theme definitions and style resolution                                      |\n| `command.rs`                  | `Command` enum (all TUI commands), `Action` / `CommandOrAction`             |\n| `key.rs`                      | `Key` / `KeySequence` types; vim-style key parsing (`C-x`, `M-x`)           |\n| `event/mod.rs`                | crossterm input loop; routes events to page or popup handler                |\n| `event/{page,popup}.rs`       | Key event dispatch per page / popup overlay                                 |\n| `ui/mod.rs`                   | ratatui render loop; main layout dispatch                                   |\n| `ui/{page,playback,popup}.rs` | Render functions for pages, playback bar, popups                            |\n| `ui/streaming.rs`             | FFT audio visualizer: `VisualizationSink`, `VisBands`, bar chart            |\n| `streaming.rs`                | librespot connection + audio backend setup (feature-gated)                  |\n| `cli/`                        | Unix socket server and client for inter-process CLI commands                |\n| `auth.rs`                     | OAuth scopes and librespot credential/session building                      |\n| `media_control.rs`            | OS media key integration via `souvlaki` (feature-gated)                     |\n\n### Concurrency model\n\nMultiple OS threads communicate via `flume` channels and `Arc<State>`:\n\n- **Event thread** — blocking `crossterm::event::read()` loop\n- **UI thread** — poll-based ratatui render loop\n- **Tokio runtime** — async tasks: client handler, socket listener, streaming\n- **Player-event watcher** — polls librespot playback state, sends `ClientRequest`s\n- **Media-control thread** — OS media key events (feature-gated)\n\nEvent/UI threads never call async functions directly. They send a `ClientRequest` over a `flume` channel; the async handler updates `Arc<State>`.\n\n### Feature flags\n\n| Feature                                                                                                                                 | Effect                                                   |\n| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| `streaming`                                                                                                                             | librespot playback, Spotify Connect, audio visualization |\n| `rodio-backend`                                                                                                                         | Default audio sink (rodio)                               |\n| `alsa-backend`, `pulseaudio-backend`, `portaudio-backend`, `jackaudio-backend`, `rodiojack-backend`, `sdl-backend`, `gstreamer-backend` | Alternative audio sinks                                  |\n| `media-control`                                                                                                                         | OS media key integration                                 |\n| `image`                                                                                                                                 | Album art rendering                                      |\n| `sixel`                                                                                                                                 | Sixel terminal image protocol (extends `image`)          |\n| `pixelate`                                                                                                                              | Pixelated image rendering fallback (extends `image`)     |\n| `notify`                                                                                                                                | Desktop notifications                                    |\n| `daemon`                                                                                                                                | Daemonize mode (implies `streaming`)                     |\n| `fzf`                                                                                                                                   | Fuzzy search                                             |\n\nDefault: `rodio-backend` + `media-control`. Gate feature-specific code with `#[cfg(feature = \"...\")]`.\n\n## Verifying changes\n\nCI treats all warnings as errors. Before committing, run what CI runs:\n\n```sh\ncargo fmt --all                      # CI checks: cargo fmt --all -- --check\ncargo test --no-default-features --features rodio-backend,media-control,image,notify,fzf\ncargo clippy --no-default-features --features rodio-backend,media-control,image,notify,fzf -- -D warnings\ncargo clippy --no-default-features -- -D warnings   # core paths, no features\n```\n\nWhen fixing no-feature clippy warnings, you may need `#[allow(dead_code)]` / `#[allow(unused_variables)]` on items only used in feature-gated paths. If you touch `daemon`/`streaming` code, add `daemon` to the feature list above to lint those paths too.\n\n## Conventions\n\n### Error handling\n\n- Return `anyhow::Result<T>` from fallible functions.\n- Add context with `.context(\"...\")` / `.with_context(|| ...)`; early-exit with `anyhow::bail!(\"...\")`.\n- Format error chains with `{err:#}` (alternate Display), never `{err}`.\n- At async task boundaries, log and continue — never let one failure crash a long-running task:\n  ```rust\n  if let Err(err) = do_something().await {\n      tracing::error!(\"Failed to ...: {err:#}\");\n  }\n  ```\n\n### Logging\n\nUse `tracing` exclusively — never `println!`, `eprintln!`, or the `log` crate.\n\n```rust\ntracing::info!(\"...\");\ntracing::error!(\"...: {err:#}\");\ntracing::debug!(\"{value:?}\");\n```\n\n### Comments and doc comments\n\n- Reserve comments for non-obvious intent: invariants, ordering constraints, workarounds, edge cases.\n- Do not narrate the implementation. A doc comment states a type/function's purpose and contract (what callers need to know); leave the mechanics — which branch does what, field-by-field behaviour, control flow — to the code itself. Implementation rationale belongs in a focused inline comment at the relevant line, not in the doc comment.\n- Keep comments concise and clear, avoid long paragraphs and try to keep comments up to date with code changes.\n\n## Keeping docs up to date\n\n`README.md` and `docs/config.md` are the primary user-facing references. Update them on any user-visible change:\n\n- **New feature / config option** — describe it in `README.md` (`Features`, `Configuration`, …), document the field in `docs/config.md`, and update `examples/app.toml` if applicable.\n- **Changed / removed behaviour** — update affected tables, command descriptions, and usage examples in both files.\n- **New feature flag** — add it to the feature-flags table in `README.md`.\n- **New CLI subcommand** — document it under the CLI section of `README.md`.\n\nKeep `.github/copilot-instructions.md` and this `CLAUDE.md` in sync when project structure, architecture, or conventions change significantly.\n\n### Adding a new `Command`\n\n1. Add the variant to `Command` in `command.rs` and update `Command::desc()`.\n2. Add a default keybinding in `config/keymap.rs`.\n3. Update the command table in `README.md`.\n\n## Writing PR descriptions\n\nBase the description on the actual branch diff (`git diff master...<branch>`), not assumptions. Keep it clear and concise:\n\n- **Summary** — 2-4 sentences: what problem the change solves and the approach. State the _why_ (the prior behaviour / bug) before the _what_.\n- **Changes** — a bullet per logical change, each tagged with the affected module/file. Lead with the user-facing or architectural change, not mechanical edits.\n- Prefer plain prose over filler; omit empty sections. Add a short **Notes** section only for non-obvious trade-offs or follow-ups.\n\nOutput the description as raw markdown in a fenced code block so it can be copied and pasted directly into the PR.\n","category":"root","tokens":2487}]}