# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Feedr is a terminal-based RSS/Atom feed reader built with Rust, using ratatui/crossterm for the TUI. It supports feed management, categorization, filtering, dual themes, OPML import, and auto-refresh with per-domain rate limiting.
## Build & Development Commands
```bash
cargo build --release # Build optimized binary (LTO enabled)
cargo run --release # Run the app
cargo test --verbose # Run all tests
cargo test --all-features --verbose # Run tests with all features
cargo test <test_name> # Run a single test
cargo clippy --all-targets --all-features -- -D warnings # Lint (CI-strict)
cargo fmt --all # Format code
cargo fmt --all -- --check # Check formatting without changing files
```
MSRV: 1.75.0. CI runs tests on stable, beta, and 1.75.0.
## Architecture
**Single-threaded synchronous TUI** — no async runtime. The main loop in `tui.rs` polls for keyboard events, mutates state, then renders.
### Core modules
- **`app.rs`** — `App` struct holding all application state. All state mutations (feed ops, filtering, categorization, persistence) happen through its methods. This is the largest and most central file.
- **`tui.rs`** — Terminal setup/teardown, main event loop (`run_app`), and all keyboard event handling (`handle_events`). Input dispatches based on `View` × `InputMode` enums.
- **`ui.rs`** — All rendering logic. Dispatches to view-specific render functions. Contains `ColorScheme` with two themes (dark cyberpunk, light zen). Largest file (~2k lines).
- **`feed.rs`** — Data models (`Feed`, `FeedItem`, `FeedCategory`) and RSS/Atom parsing via the `feed-rs` crate.
- **`config.rs`** — XDG-compliant config loading/saving (`~/.config/feedr/config.toml`). Auto-generates defaults on first run.
- **`main.rs`** — CLI arg parsing (clap) and OPML import entry point.
### Key patterns
- **View + InputMode dispatch**: Event handling in `tui.rs` matches on `(app.view, key.code)` nested inside `app.input_mode`. When adding new keybindings, place them in the correct View/InputMode branch.
- **`q` key goes back, not quit**: `q` navigates back one view (e.g., FeedItems → FeedList → Dashboard). Only quits from Dashboard. `Ctrl+Q` is the universal quit from any view. The `Ctrl+Q` check is a guard at the top of `handle_events`, before the `match app.input_mode` block.
- **Dashboard items**: `dashboard_items: Vec<(feed_idx, item_idx)>` is a derived index into `feeds`, rebuilt by `apply_filters()` whenever filters change.
- **Data persistence**: Saved to `~/.local/share/feedr/feedr_data.json` — bookmarks, categories, and read item tracking.
- **Error display is modal**: When `app.error` is `Some`, the keypress is consumed to dismiss it (not passed through to handlers). See the guard at the top of `handle_events`.
- **Rate limiting**: `last_domain_fetch: HashMap` throttles per-domain HTTP requests.
- **Authenticated feeds**: `feed_headers: HashMap<String, HashMap<String, String>>` in `App` maps feed URLs to custom HTTP headers. Built from `config.default_feeds` entries that have `headers`. Passed to `Feed::from_url_with_client()` at all fetch call sites.
- **Compact mode**: `app.compact` bool is updated each frame by `update_compact_mode(terminal_height)`. Rendering in `ui.rs` branches on `app.compact` for layout, title bar, help bar, and dashboard item format. Controlled by `config.ui.compact_mode` (`Auto`/`Always`/`Never`). Dialog modals use `centered_rect_with_min()` to enforce minimum dimensions regardless of compact mode.
## Commit Conventions
Uses **conventional commits** — `feat:`, `fix:`, `refactor:`, `docs:`, `perf:`, `test:`, `chore:`, `build:`, `style:`. Changelog is generated by git-cliff (`cliff.toml`).
## Testing
Integration tests live in `/tests/integration_test.rs` and test feed parsing against real URLs. Unit tests are inline in `config.rs` and `app.rs`.