rustpm 0.2.3

A fast, friendly APT frontend with kernel, desktop, and sources management
# 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`