Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 40 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ Never create a new file without this header.

---

## Theme & UI Elements (`src/theme.rs`)
## Theme & UI Elements (`crate::theme`)

All visual styling lives in `src/theme.rs`. Import only from there — never hard-code
colors, fonts, or sizes inline.
All visual styling lives in the public `crate::theme` module, implemented by
`src/theme.rs` plus private `src/theme/*` helpers. Import only from `crate::theme`
— never hard-code colors, fonts, or sizes inline.

### When to use each helper

Expand Down Expand Up @@ -112,18 +113,20 @@ Do **not** keep a separate `bool` flag for this purpose.
```text
src/
├── main.rs # Entry point only — window setup, font registration, tracing init
├── app.rs # AppState, Screen router, top-level update/view/subscription
├── app.rs # AppState, Screen router, top-level facade for update/view/subscription
├── app/ # Private routing, settings-bridge, and keyboard helpers backing crate::app
├── rpc/ # Transmission RPC: api, models, transport, worker (serialized queue)
├── screens/
│ ├── connection.rs # Profile selection / quick-connect
│ ├── main_screen.rs # Root layout: torrent list + inspector split
│ ├── inspector.rs # Detail inspector panel
── torrent_list/ # List, sort, add-torrent dialog (split into view/update/worker)
│ ├── inspector/ # Detail inspector panel (state / update / view)
── torrent_list/ # List screen: view orchestration, toolbar/header/dialog helpers, sort, add-torrent, update, worker
│ └── settings/ # Profile editing (split into state/draft/update/view)
├── auth.rs # Passphrase setup and unlock flows
├── auth/ # Passphrase setup and unlock flows (mod / update / view)
├── crypto.rs # Argon2id KDF + ChaCha20-Poly1305 AEAD
├── profile.rs # TOML config persistence
├── theme.rs # All styling (colors, fonts, widget helpers)
├── theme.rs # Public theme facade (colors, fonts, widget helpers)
├── theme/ # Private theme submodules backing crate::theme
└── format.rs # Human-readable formatting (sizes, speeds, ETA)
```

Expand All @@ -137,16 +140,18 @@ When adding a new screen:

## Commit Messages

Follow the Go commit style with a component prefix:
Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit
messages:

```text
component: short imperative summary
<type>(<scope>): short imperative summary

Optional body explaining *why*, not *what* (the diff shows that).
```

Component examples: `clutch/theme`, `clutch/rpc`, `clutch/torrent-list`, `clutch/settings`,
`clutch/ci`, `clutch/packaging`.
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`. Scope is optional but
encouraged for larger projects; use the name of the module or feature being changed (e.g.
`torrent-list`, `rpc`, `theme`).

---

Expand Down Expand Up @@ -182,8 +187,29 @@ Component examples: `clutch/theme`, `clutch/rpc`, `clutch/torrent-list`, `clutch

Keep the system_architecture.md and README.md up to date with any architectural changes or new
features. The README should be a user-friendly overview, while system_architecture.md can go into
technical depth for future maintainers. Change logs must be recorded in CHANGELOG.md following the
[Keep a Changelog format](https://keepachangelog.com/en/1.0.0/).
technical depth for future maintainers.

## Changelog

Change logs must be recorded in CHANGELOG.md following the [Keep a Changelog
format](https://keepachangelog.com/en/1.0.0/).

### Guiding Principles

- Changelog are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- The latest version comes first.
- The release date of each version is displayed.

### Types of changes

- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` for vulnerabilities.

## Markdown formatting

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Refactored some large monolithic modules into smaller submodules for better organization and
maintainability.
- Eliminated per-torrent heap allocation from the render hot path.

## [0.12.0] - 2026-04-08

### Added
Expand Down
5 changes: 3 additions & 2 deletions openspec/specs/add-torrent/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
## Purpose

TBD - created by archiving change add-torrents. Update Purpose after archive.

## Requirements

### Requirement: Add Torrent toolbar entry points

The main-screen toolbar SHALL provide two buttons: **Add Torrent** and **Add Link**. These are
Expand Down Expand Up @@ -197,7 +199,7 @@ in the `torrent-add` RPC call so the daemon never starts downloading those files
### Requirement: Select All tri-state header in file add dialog

The file add dialog SHALL display a **tri-state checkbox** header row above the file list,
using the `m3_tristate_checkbox` helper from `src/theme.rs`. Its state SHALL be derived
using the `m3_tristate_checkbox` helper from the public `crate::theme` module. Its state SHALL be derived
from the `selected: Vec<bool>` state:

- All selected \u2192 **Checked**
Expand Down Expand Up @@ -231,4 +233,3 @@ Checked emits `AddDialogDeselectAll`.

- **WHEN** the header is Checked and the user clicks it
- **THEN** all file checkboxes are set to unchecked

2 changes: 1 addition & 1 deletion openspec/specs/connection-screen/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ All connection errors SHALL be logged to stdout/stderr in addition to being show

### Requirement: Saved Profiles and Quick Connect tab navigation

The connection screen SHALL present two modes — Saved Profiles and Quick Connect — as a segmented control component (M3 style) using the `segmented_control` helper from `src/theme.rs`. The segmented control SHALL be centered in a fixed-width (400 px) container.
The connection screen SHALL present two modes — Saved Profiles and Quick Connect — as a segmented control component (M3 style) using the `segmented_control` helper from the public `crate::theme` module. The segmented control SHALL be centered in a fixed-width (400 px) container.

#### Scenario: Segmented control shown on connection screen

Expand Down
15 changes: 9 additions & 6 deletions openspec/specs/file-selection/spec.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# file-selection Specification

## Purpose

TBD - created by archiving change selective-file-download. Update Purpose after archive.

## Requirements

### Requirement: Inspector Files tab shows per-file wanted checkboxes

The inspector Files tab SHALL display a checkbox at the start of each file row. The displayed
Expand Down Expand Up @@ -91,7 +94,8 @@ effective wanted values (incorporating `pending_wanted` overrides):
- No files effectively wanted → **Unchecked**
- Some but not all files effectively wanted → **Mixed** (indeterminate)

The tri-state checkbox SHALL use the `m3_tristate_checkbox` helper from `src/theme.rs`
The tri-state checkbox SHALL use the `m3_tristate_checkbox` helper from the public
`crate::theme` module
(see Design D5). Clicking the header when Mixed or Unchecked emits
`AllFilesWantedToggled { wanted: true }`; clicking when Checked emits
`AllFilesWantedToggled { wanted: false }`.
Expand Down Expand Up @@ -129,9 +133,9 @@ and enqueue **exactly one** `RpcWork::SetFileWanted` with the full index range

### Requirement: CheckState enum and m3_tristate_checkbox theme helper

A `CheckState` enum with variants `Checked`, `Unchecked`, and `Mixed` SHALL be defined in
`src/theme.rs`. A `m3_tristate_checkbox(state, label, on_toggle)` helper function SHALL
also live in `src/theme.rs`. It SHALL render a borderless button composed of a Material
A `CheckState` enum with variants `Checked`, `Unchecked`, and `Mixed` SHALL be exposed from
the public `crate::theme` module. A `m3_tristate_checkbox(state, label, on_toggle)` helper
function SHALL also be exposed from `crate::theme`. It SHALL render a borderless button composed of a Material
Icons glyph and a text label:

| `CheckState` | Icon constant | Next state on click |
Expand All @@ -142,7 +146,7 @@ Icons glyph and a text label:

The icon SHALL be coloured with `palette.primary.base.color` for `Checked` and `Mixed`
states, and `palette.background.base.text` for `Unchecked`, using the same dark/light
detection pattern already used throughout `theme.rs`.
detection pattern already used throughout the theme module.

#### Scenario: Checked icon renders with primary colour

Expand Down Expand Up @@ -202,4 +206,3 @@ when absent (e.g. older Transmission versions or incomplete responses).

- **WHEN** `torrent-get` returns `fileStats` without a `wanted` key
- **THEN** `TorrentFileStats::wanted` defaults to `true`

4 changes: 2 additions & 2 deletions openspec/specs/m3-components/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Requirement: Primary filled button helper

The application SHALL provide an `m3_primary_button` style function in `src/theme.rs`. The style SHALL produce a solid primary-color background with white (on-primary) text and a fully rounded pill shape (radius 100.0). Padding SHALL be at least `[10, 24]`. This style is used for all primary CTAs (Connect, Save, Add).
The application SHALL provide an `m3_primary_button` style function from the public `crate::theme` module. The style SHALL produce a solid primary-color background with white (on-primary) text and a fully rounded pill shape (radius 100.0). Padding SHALL be at least `[10, 24]`. This style is used for all primary CTAs (Connect, Save, Add).

#### Scenario: Primary button renders with solid brand color

Expand All @@ -16,7 +16,7 @@ The application SHALL provide an `m3_primary_button` style function in `src/them

### Requirement: Tonal (secondary) button helper

The application SHALL provide an `m3_tonal_button` style function in `src/theme.rs`. The style SHALL produce a 15 % alpha primary color wash background with primary-color text and a fully rounded pill shape (radius 100.0). This style is used for all secondary/cancel actions (Cancel, Manage Profiles, Test Connection, Undo).
The application SHALL provide an `m3_tonal_button` style function from the public `crate::theme` module. The style SHALL produce a 15 % alpha primary color wash background with primary-color text and a fully rounded pill shape (radius 100.0). This style is used for all secondary/cancel actions (Cancel, Manage Profiles, Test Connection, Undo).

#### Scenario: Tonal button renders with brand wash background

Expand Down
4 changes: 2 additions & 2 deletions openspec/specs/material-theme/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Requirement: Material Design 3 theme

The application SHALL support two built-in themes — Light and Dark — both derived from a Material Design 3 palette seeded from the Clutch brand color Magnetic Blue (`#2A64A7`). Each theme SHALL be implemented as a custom `iced::Theme` via a single `clutch_theme(is_dark: bool) -> Theme` function. The dark palette SHALL use a lightened primary (`#5B9FD4`) for sufficient contrast on dark surfaces. The following named color constants SHALL be defined in `src/theme.rs`:
The application SHALL support two built-in themes — Light and Dark — both derived from a Material Design 3 palette seeded from the Clutch brand color Magnetic Blue (`#2A64A7`). Each theme SHALL be implemented as a custom `iced::Theme` via a single `clutch_theme(is_dark: bool) -> Theme` function. The dark palette SHALL use a lightened primary (`#5B9FD4`) for sufficient contrast on dark surfaces. The following named color constants SHALL be available from the public `crate::theme` module:

- `MAGNETIC_BLUE`: `Color::from_rgb(0.16, 0.39, 0.65)` — base brand blue
- `MAGNETIC_BLUE_LIGHT`: `Color::from_rgb(0.36, 0.62, 0.83)` — lightened primary for dark mode
Expand Down Expand Up @@ -44,7 +44,7 @@ The active theme SHALL apply to all standard iced widgets automatically via the

#### Scenario: Named constants are the sole source of brand colors

- **WHEN** a developer inspects `src/theme.rs`
- **WHEN** a developer inspects the public `crate::theme` module
- **THEN** all brand colors are defined as named `const Color` values at the top of the file and nowhere else

### Requirement: Material Icons font
Expand Down
3 changes: 2 additions & 1 deletion openspec/specs/rpc-client/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
Provide a safe, async Transmission JSON-RPC client that serializes all daemon
calls through a single MPSC worker, guaranteeing at most one in-flight HTTP
connection at any time while remaining fully non-blocking on the iced UI thread.

## Requirements

### Requirement: Session-Id lifecycle handling

The RPC client SHALL implement the Transmission `X-Transmission-Session-Id` handshake. On receiving a 409 response, the client SHALL extract the new session ID from the response header and retry the request exactly once with the updated ID.
Expand Down Expand Up @@ -267,4 +269,3 @@ The `torrent-get` fields list SHALL include `"error"`, `"errorString"`, and `"do

- **WHEN** the daemon omits `"error"` and `"errorString"` fields
- **THEN** `TorrentData::error == 0` and `TorrentData::error_string` is empty

Loading
Loading