# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build and install
```bash
cargo build # debug build
cargo build --release # release build
sudo make install # install to /usr/local/bin/rustpm
cargo check # fast type-check without linking
```
There are no automated tests. Verification is manual — see the README for example commands. `cargo check` is the fastest way to validate changes compile correctly.
## APT interface strategy
The project never calls libapt-pkg. All APT interaction shells out to `apt-get`, `apt-cache`, `dpkg-query`, and `apt-mark`. HTTP requests (kernel.org JSON, Ubuntu mainline HTML, crates.io API) shell out to `curl`. This is intentional — libapt-pkg is C++, not plain C, making FFI impractical.
## Architecture overview
`main.rs` is the entry point and the largest file. It owns all subcommand dispatch and the two TUI launch paths:
- **Bare `rustpm`** → `run_tui()`: loads data for every panel, opens the full-screen TUI
- **`rustpm <subcommand>`** → `run_command()`: runs the operation, may open a minimal TUI for the diff confirmation view (`print_changes_tui`)
### TUI/non-TUI split
`ui::should_use_tui()` checks `std::io::IsTerminal` + `RUSTPM_NO_TUI`. When false, subcommands print plain text via `ui::table` and `nu-ansi-term`. When true, they show the ratatui diff widget.
### APT output streaming
`apt/executor.rs` exposes two functions:
- `dry_run(&AptOperation)` — runs `apt-get --simulate`, parses stdout via `apt/parser.rs` into `Vec<PackageChange>`
- `execute(&AptOperation, Option<mpsc::Sender<String>>)` — spawns real apt-get; two threads drain stdout/stderr and forward lines through the channel; the TUI renders from this channel
### TUI structure (`src/ui/tui/`)
- `app.rs` — `App` struct holding all panel state; `Tab` enum (Packages=0, Updates=1, Kernels=2, Desktops=3, Sources=4, History=5); global key handling (Tab/1-6 to switch, q/Ctrl-C to quit, ? for help overlay)
- `events.rs` — crossterm event loop; dispatches `AppEvent::Key`, `AppEvent::AptOutput`, `AppEvent::Tick`
- `widgets/` — one file per panel; each struct has `handle_key()` and `render()`. Navigation uses `next(step)` / `prev(step)` taking a usize so both j/k (step=1) and PageDown/PageUp (step=10) share the same logic
### Kernel module (`src/kernel/`)
- `detector.rs` — `detect_kernels()` correlates dpkg-query, apt-cache search, apt-mark showhold, and `uname -r` into `Vec<KernelEntry>`
- `manager.rs` — Debian kernel install/remove/pin/update; always calls `trigger_grub_update()` after changes
- `vanilla.rs` — fetches `kernel.org/releases.json` via curl, scrapes Ubuntu mainline HTML for .deb URLs, downloads with curl, installs via `dpkg -i`
### Data flow into the TUI
`run_tui()` in main.rs loads all panel data before constructing `App`. `upgrade_changes` for the Updates tab comes from `apt::executor::dry_run(&AptOperation::Upgrade { full: false })` at startup.
## Key conventions
- All subprocess helpers that may fail gracefully use `.output().map(|o| o.stdout).unwrap_or_default()` — never panic on missing tools
- `mpsc::Sender<String>` threads through kernel/vanilla install functions so progress messages appear in both TUI status bar and plain-text mode
- Desktop profiles are static (`&'static str` fields) defined in `desktop/profiles.rs`; adding a new DE only requires adding an entry there
- Config lives at `~/.config/rustpm/config.toml`; history at `~/.local/share/rustpm/history.json`