From 7ee9029fa6d6417d4e499db23c3ff420909b1844 Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 15:44:23 -0500 Subject: [PATCH 1/8] (chore) defined ci workflows --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ .github/workflows/release.yml | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e669307 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --check + - run: cargo clippy -- -D warnings + - run: cargo test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3e9b6a1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo build --release + - name: Create release + uses: softprops/action-gh-release@v2 + with: + files: target/release/crabmail + generate_release_notes: true From 39d5957f718ab96867de9c081e0842adb5f2b9f6 Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 16:39:04 -0500 Subject: [PATCH 2/8] (fix) pinning 3rd party ci steps to hash --- .github/workflows/ci.yml | 9 ++++++--- .github/workflows/release.yml | 12 ++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e669307..5e9ebcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [master] +permissions: + contents: read + env: CARGO_TERM_COLOR: always @@ -13,11 +16,11 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable with: components: clippy, rustfmt - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo fmt --check - run: cargo clippy -- -D warnings - run: cargo test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e9b6a1..e6c9123 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,12 +14,16 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo build --release + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - name: Create release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4aeaf626c5b8d85266b0e65 # v2.6.2 with: files: target/release/crabmail generate_release_notes: true + - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 From 82d85d3ae217f19410bcf4dec7898ddbf4417426 Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 16:40:43 -0500 Subject: [PATCH 3/8] (fix) updated permissions. --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6c9123..ab6754c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,8 +4,7 @@ on: push: tags: ["v*"] -permissions: - contents: write +permissions: {} env: CARGO_TERM_COLOR: always @@ -19,6 +18,8 @@ jobs: - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo build --release - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + permissions: + contents: write - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - name: Create release uses: softprops/action-gh-release@3bb12739c298aeb8a4aeaf626c5b8d85266b0e65 # v2.6.2 From fc6e79edd2e74682909afeba83334e6e2834650e Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 16:41:29 -0500 Subject: [PATCH 4/8] (feat) updated workflow to update tap --- .github/workflows/release.yml | 76 ++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab6754c..1a29eac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,20 +11,92 @@ env: jobs: build: - runs-on: ubuntu-latest + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact: crabmail-linux-amd64 + - os: macos-latest + target: aarch64-apple-darwin + artifact: crabmail-darwin-arm64 + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo build --release + - name: Package + run: | + tar czf ${{ matrix.artifact }}.tar.gz -C target/release crabmail + shasum -a 256 ${{ matrix.artifact }}.tar.gz > ${{ matrix.artifact }}.tar.gz.sha256 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ${{ matrix.artifact }} + path: | + ${{ matrix.artifact }}.tar.gz + ${{ matrix.artifact }}.tar.gz.sha256 + + release: + needs: build + runs-on: ubuntu-latest permissions: contents: write + steps: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + merge-multiple: true - name: Create release uses: softprops/action-gh-release@3bb12739c298aeb8a4aeaf626c5b8d85266b0e65 # v2.6.2 with: - files: target/release/crabmail + files: | + *.tar.gz + *.sha256 generate_release_notes: true + + update-tap: + needs: release + runs-on: ubuntu-latest + steps: - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + id: app-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: CyTechNomad + repositories: homebrew-tap - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + merge-multiple: true + - name: Get version and checksums + id: meta + run: | + VERSION="${GITHUB_REF_NAME#v}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "sha256_darwin=$(awk '{print $1}' crabmail-darwin-arm64.tar.gz.sha256)" >> "$GITHUB_OUTPUT" + echo "sha256_linux=$(awk '{print $1}' crabmail-linux-amd64.tar.gz.sha256)" >> "$GITHUB_OUTPUT" + - name: Update tap formula + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ steps.meta.outputs.version }} + SHA256_DARWIN: ${{ steps.meta.outputs.sha256_darwin }} + SHA256_LINUX: ${{ steps.meta.outputs.sha256_linux }} + run: | + git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/" + git clone https://github.com/CyTechNomad/homebrew-tap.git tap + cd tap + + # Update main formula + sed -i "s/version \".*\"/version \"${VERSION}\"/" Formula/crabmail.rb + sed -i "0,/sha256 \".*\"/{s/sha256 \".*\"/sha256 \"${SHA256_DARWIN}\"/}" Formula/crabmail.rb + sed -i "/sha256 \"${SHA256_DARWIN}\"/!{0,/sha256 \".*\"/{s/sha256 \".*\"/sha256 \"${SHA256_LINUX}\"/}}" Formula/crabmail.rb + + # Create versioned formula + cp Formula/crabmail.rb "Formula/crabmail@${VERSION}.rb" + sed -i "s/class Crabmail Date: Tue, 19 May 2026 16:41:58 -0500 Subject: [PATCH 5/8] (fix) updated dependency for keyring management. --- Cargo.lock | 19 +++++++++++++++++++ Cargo.toml | 11 ++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 008a0ff..3c16b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,6 +251,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" @@ -1004,9 +1010,12 @@ version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ + "byteorder", + "linux-keyutils", "log", "security-framework 2.11.1", "security-framework 3.6.0", + "windows-sys 0.60.2", "zeroize", ] @@ -1059,6 +1068,16 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-keyutils" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" diff --git a/Cargo.toml b/Cargo.toml index ea3db20..6ae3784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ toml = "0.8" dirs = "6" # Credentials -keyring = { version = "3", features = ["apple-native"] } +keyring = "3" secrecy = "0.10" zeroize = "1" @@ -44,3 +44,12 @@ anyhow = "1" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-appender = "0.2" + +[target.'cfg(target_os = "macos")'.dependencies] +keyring = { version = "3", features = ["apple-native"] } + +[target.'cfg(target_os = "linux")'.dependencies] +keyring = { version = "3", features = ["linux-native"] } + +[target.'cfg(target_os = "windows")'.dependencies] +keyring = { version = "3", features = ["windows-native"] } From b8508e20bbf4b4f50ed4e9db1588ec4c7c51849d Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 16:47:34 -0500 Subject: [PATCH 6/8] (fix) set toolchain version to stable. the older version stable was implied. --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e9ebcb..4470e1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable with: + toolchain: stable components: clippy, rustfmt - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo fmt --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a29eac..adee581 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,8 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable + with: + toolchain: stable - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo build --release - name: Package From cf2c8ce954d8476010820b62baac07e43c073b3a Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 16:48:48 -0500 Subject: [PATCH 7/8] (chore) formatted code. --- README.md | 66 +++++++++++++++++----------------- src/app.rs | 23 ++++++------ src/components/message_list.rs | 6 +++- src/components/status_bar.rs | 10 ++++-- src/config.rs | 3 +- src/theme.rs | 64 ++++++++++++++++----------------- 6 files changed, 92 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 00c3ec5..c555edc 100644 --- a/README.md +++ b/README.md @@ -60,50 +60,50 @@ Configuration is stored at `~/.config/crabmail/config.toml`. #### Normal Mode -| Key | Action | -| ----------- | -------------------------------------- | -| `j` / `k` | Navigate down / up | -| `h` / `l` | Focus mailboxes / messages | -| `Enter` | Open selected message / select mailbox | -| `i` | Compose new email | -| `/` | Search | -| `:` | Command mode | -| `r` | Refresh mailbox | -| `d` | Delete message (press `y` to confirm) | -| `Esc` | Back / cancel | -| `q` | Quit | +| Key | Action | +| --------- | -------------------------------------- | +| `j` / `k` | Navigate down / up | +| `h` / `l` | Focus mailboxes / messages | +| `Enter` | Open selected message / select mailbox | +| `i` | Compose new email | +| `/` | Search | +| `:` | Command mode | +| `r` | Refresh mailbox | +| `d` | Delete message (press `y` to confirm) | +| `Esc` | Back / cancel | +| `q` | Quit | #### Reading a Message -| Key | Action | -| ----------- | ------------------- | -| `j` / `k` | Scroll down / up | -| `r` | Reply to message | -| `f` | Forward message | -| `d` | Delete message | -| `q` / `Esc` | Close reader | +| Key | Action | +| ----------- | ---------------- | +| `j` / `k` | Scroll down / up | +| `r` | Reply to message | +| `f` | Forward message | +| `d` | Delete message | +| `q` / `Esc` | Close reader | #### Composing -| Key | Action | -| ------------- | ----------------------------------- | -| `Tab` | Cycle fields (To → Subject → Body) | -| `Shift+Tab` | Cycle fields backward | -| `Ctrl+w` | Send message | -| `Esc` | Cancel compose | +| Key | Action | +| ----------- | ---------------------------------- | +| `Tab` | Cycle fields (To → Subject → Body) | +| `Shift+Tab` | Cycle fields backward | +| `Ctrl+w` | Send message | +| `Esc` | Cancel compose | ![Composer](assets/Composer.png) #### Commands -| Command | Action | -| -------------------- | ------------------- | -| `:q` / `:quit` | Quit | -| `:account ` | Switch account | -| `:add-account` | Add a new account | -| `:edit-account` | Edit active account | -| `:theme ` | Switch color theme | -| `:help` | Show keybind hints | +| Command | Action | +| ----------------- | ------------------- | +| `:q` / `:quit` | Quit | +| `:account ` | Switch account | +| `:add-account` | Add a new account | +| `:edit-account` | Edit active account | +| `:theme ` | Switch color theme | +| `:help` | Show keybind hints | ## Configuration diff --git a/src/app.rs b/src/app.rs index 8e5ed0c..19e7d1e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,6 @@ use std::time::{Duration, Instant}; use crate::action::Action; use crate::auth; -use secrecy::{ExposeSecret, SecretString}; use crate::components::Component; use crate::components::command_bar::{self, CommandBar}; use crate::components::composer::Composer; @@ -21,6 +20,7 @@ use crate::imap_client::ImapClient; use crate::mail; use crate::smtp_client; use crate::theme::Theme; +use secrecy::{ExposeSecret, SecretString}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Mode { @@ -141,7 +141,8 @@ impl App { self.pending_confirm.take().unwrap() } else { self.pending_confirm = None; - self.status_bar.set_temporary_status("Cancelled", Duration::from_secs(2)); + self.status_bar + .set_temporary_status("Cancelled", Duration::from_secs(2)); Action::Noop }; } @@ -409,7 +410,8 @@ impl App { match client.list_mailboxes().await { Ok(mailboxes) => { self.mailbox_list.set_mailboxes(mailboxes); - self.status_bar.set_temporary_status("Connected", Duration::from_secs(2)); + self.status_bar + .set_temporary_status("Connected", Duration::from_secs(2)); } Err(e) => { self.status_bar.error = format!("Failed to list mailboxes: {e}"); @@ -433,10 +435,8 @@ impl App { match imap.fetch_headers(50).await { Ok(msgs) => { self.message_list.set_messages(msgs); - self.status_bar.set_temporary_status( - "Refreshed", - Duration::from_secs(2), - ); + self.status_bar + .set_temporary_status("Refreshed", Duration::from_secs(2)); } Err(e) => self.status_bar.error = format!("Fetch error: {e}"), } @@ -493,7 +493,8 @@ impl App { .await { Ok(()) => { - self.status_bar.set_temporary_status("Message sent!", Duration::from_secs(2)); + self.status_bar + .set_temporary_status("Message sent!", Duration::from_secs(2)); self.composer.clear(); } Err(e) => self.status_bar.error = format!("Send failed: {e}"), @@ -504,7 +505,8 @@ impl App { let Some(imap) = &mut self.imap else { return }; match imap.delete_message(uid).await { Ok(()) => { - self.status_bar.set_temporary_status("Message deleted", Duration::from_secs(2)); + self.status_bar + .set_temporary_status("Message deleted", Duration::from_secs(2)); if self.mode == Mode::Reading { self.mode = Mode::Normal; self.reader.close(); @@ -523,7 +525,8 @@ impl App { match imap.search(query).await { Ok(uids) => { if uids.is_empty() { - self.status_bar.set_temporary_status("No results", Duration::from_secs(2)); + self.status_bar + .set_temporary_status("No results", Duration::from_secs(2)); self.message_list.set_messages(vec![]); return; } diff --git a/src/components/message_list.rs b/src/components/message_list.rs index 26d0f67..8797950 100644 --- a/src/components/message_list.rs +++ b/src/components/message_list.rs @@ -149,7 +149,11 @@ impl Component for MessageList { fn truncate(s: &str, max: usize) -> String { if s.chars().count() > max { - let end = s.char_indices().nth(max - 1).map(|(i, _)| i).unwrap_or(s.len()); + let end = s + .char_indices() + .nth(max - 1) + .map(|(i, _)| i) + .unwrap_or(s.len()); format!("{}…", &s[..end]) } else { s.to_string() diff --git a/src/components/status_bar.rs b/src/components/status_bar.rs index c864155..752b5e9 100644 --- a/src/components/status_bar.rs +++ b/src/components/status_bar.rs @@ -74,7 +74,10 @@ impl Component for StatusBar { Mode::Command => " COMMAND ", Mode::Setup => " SETUP ", }; - let mode_span = Span::styled(mode_str, Style::default().fg(theme.mode_fg).bg(theme.accent)); + let mode_span = Span::styled( + mode_str, + Style::default().fg(theme.mode_fg).bg(theme.accent), + ); let acct = if self.account.is_empty() { "No account".to_string() } else { @@ -89,7 +92,10 @@ impl Component for StatusBar { let mb_span = Span::styled(mb, Style::default().fg(theme.dimmed)); let right = if !self.error.is_empty() { - Span::styled(format!(" {} ", self.error), Style::default().fg(theme.error)) + Span::styled( + format!(" {} ", self.error), + Style::default().fg(theme.error), + ) } else if !self.status.is_empty() { Span::styled( format!(" {} ", self.status), diff --git a/src/config.rs b/src/config.rs index a9a5603..896468b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -50,8 +50,7 @@ impl Config { let path = Self::path(); // Migrate from old platform-specific location if needed if !path.exists() { - if let Some(old) = dirs::config_dir().map(|d| d.join("crabmail").join("config.toml")) - { + if let Some(old) = dirs::config_dir().map(|d| d.join("crabmail").join("config.toml")) { if old.exists() { if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; diff --git a/src/theme.rs b/src/theme.rs index c6f5121..1be772e 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -29,53 +29,53 @@ impl Theme { fn dracula() -> Self { Self { - accent: Color::Rgb(189, 147, 249), // purple - dimmed: Color::Rgb(98, 114, 164), // comment - text: Color::Rgb(248, 248, 242), // foreground - success: Color::Rgb(80, 250, 123), // green - error: Color::Rgb(255, 85, 85), // red - search: Color::Rgb(241, 250, 140), // yellow - bar_bg: Color::Rgb(68, 71, 90), // current line - mode_fg: Color::Rgb(40, 42, 54), // background + accent: Color::Rgb(189, 147, 249), // purple + dimmed: Color::Rgb(98, 114, 164), // comment + text: Color::Rgb(248, 248, 242), // foreground + success: Color::Rgb(80, 250, 123), // green + error: Color::Rgb(255, 85, 85), // red + search: Color::Rgb(241, 250, 140), // yellow + bar_bg: Color::Rgb(68, 71, 90), // current line + mode_fg: Color::Rgb(40, 42, 54), // background } } fn gruvbox() -> Self { Self { - accent: Color::Rgb(215, 153, 33), // yellow - dimmed: Color::Rgb(146, 131, 116), // gray - text: Color::Rgb(235, 219, 178), // fg - success: Color::Rgb(152, 151, 26), // green - error: Color::Rgb(204, 36, 29), // red - search: Color::Rgb(69, 133, 136), // aqua - bar_bg: Color::Rgb(60, 56, 54), // bg1 - mode_fg: Color::Rgb(40, 40, 40), // bg0 + accent: Color::Rgb(215, 153, 33), // yellow + dimmed: Color::Rgb(146, 131, 116), // gray + text: Color::Rgb(235, 219, 178), // fg + success: Color::Rgb(152, 151, 26), // green + error: Color::Rgb(204, 36, 29), // red + search: Color::Rgb(69, 133, 136), // aqua + bar_bg: Color::Rgb(60, 56, 54), // bg1 + mode_fg: Color::Rgb(40, 40, 40), // bg0 } } fn nord() -> Self { Self { - accent: Color::Rgb(136, 192, 208), // frost - dimmed: Color::Rgb(76, 86, 106), // nord3 - text: Color::Rgb(216, 222, 233), // snow storm - success: Color::Rgb(163, 190, 140), // green - error: Color::Rgb(191, 97, 106), // red - search: Color::Rgb(235, 203, 139), // yellow - bar_bg: Color::Rgb(59, 66, 82), // nord1 - mode_fg: Color::Rgb(46, 52, 64), // nord0 + accent: Color::Rgb(136, 192, 208), // frost + dimmed: Color::Rgb(76, 86, 106), // nord3 + text: Color::Rgb(216, 222, 233), // snow storm + success: Color::Rgb(163, 190, 140), // green + error: Color::Rgb(191, 97, 106), // red + search: Color::Rgb(235, 203, 139), // yellow + bar_bg: Color::Rgb(59, 66, 82), // nord1 + mode_fg: Color::Rgb(46, 52, 64), // nord0 } } fn solarized() -> Self { Self { - accent: Color::Rgb(38, 139, 210), // blue - dimmed: Color::Rgb(88, 110, 117), // base01 - text: Color::Rgb(131, 148, 150), // base0 - success: Color::Rgb(133, 153, 0), // green - error: Color::Rgb(220, 50, 47), // red - search: Color::Rgb(181, 137, 0), // yellow - bar_bg: Color::Rgb(7, 54, 66), // base02 - mode_fg: Color::Rgb(0, 43, 54), // base03 + accent: Color::Rgb(38, 139, 210), // blue + dimmed: Color::Rgb(88, 110, 117), // base01 + text: Color::Rgb(131, 148, 150), // base0 + success: Color::Rgb(133, 153, 0), // green + error: Color::Rgb(220, 50, 47), // red + search: Color::Rgb(181, 137, 0), // yellow + bar_bg: Color::Rgb(7, 54, 66), // base02 + mode_fg: Color::Rgb(0, 43, 54), // base03 } } } From 5b1e0cbf9ad42d27c74722052cc09a65f11ca086 Mon Sep 17 00:00:00 2001 From: "Christian C. Howell" Date: Tue, 19 May 2026 17:09:48 -0500 Subject: [PATCH 8/8] (fix) removed collapsible if issue and marked scaffolding for future decoupleing as ingore dead code. --- src/action.rs | 3 +++ src/app.rs | 41 ++++++++++++++++++------------------ src/auth.rs | 1 + src/components/status_bar.rs | 10 ++++----- src/config.rs | 15 ++++++------- src/imap_client.rs | 1 + 6 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/action.rs b/src/action.rs index e925372..2d114d5 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,6 +1,9 @@ use crossterm::event::KeyEvent; +// Some variants are matched but not yet constructed — they exist as scaffolding +// for a future decoupled event dispatch architecture. #[derive(Debug, Clone)] +#[allow(dead_code)] pub enum Action { Quit, Noop, diff --git a/src/app.rs b/src/app.rs index 19e7d1e..5d9261f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -113,11 +113,12 @@ impl App { } let action = self.handle_key(key); self.process_action(action).await; - } else if let Some(secs) = self.config.auto_refresh_seconds { - if secs > 0 && self.last_refresh.elapsed() >= Duration::from_secs(secs) { - self.last_refresh = Instant::now(); - self.process_action(Action::RefreshMailbox).await; - } + } else if let Some(secs) = self.config.auto_refresh_seconds + && secs > 0 + && self.last_refresh.elapsed() >= Duration::from_secs(secs) + { + self.last_refresh = Instant::now(); + self.process_action(Action::RefreshMailbox).await; } } Ok(()) @@ -460,16 +461,16 @@ impl App { } async fn mark_read(&mut self, uid: u32) { - if let Some(imap) = &mut self.imap { - if let Err(e) = imap.mark_read(uid).await { - self.status_bar.error = format!("Mark read failed: {e}"); - return; - } + if let Some(imap) = &mut self.imap + && let Err(e) = imap.mark_read(uid).await + { + self.status_bar.error = format!("Mark read failed: {e}"); + return; } - if let Some(msg) = self.message_list.messages.iter_mut().find(|m| m.uid == uid) { - if !msg.flags.iter().any(|f| f.contains("Seen")) { - msg.flags.push("Seen".to_string()); - } + if let Some(msg) = self.message_list.messages.iter_mut().find(|m| m.uid == uid) + && !msg.flags.iter().any(|f| f.contains("Seen")) + { + msg.flags.push("Seen".to_string()); } } @@ -565,12 +566,12 @@ impl App { let account = self.setup_wizard.build_account(); let password = self.setup_wizard.password(); - if !password.is_empty() { - if let Err(e) = auth::store_password(&account.name, password) { - self.setup_wizard.clear_password(); - self.status_bar.error = format!("Failed to store password: {e}"); - return; - } + if !password.is_empty() + && let Err(e) = auth::store_password(&account.name, password) + { + self.setup_wizard.clear_password(); + self.status_bar.error = format!("Failed to store password: {e}"); + return; } self.setup_wizard.clear_password(); diff --git a/src/auth.rs b/src/auth.rs index 3ac2a84..966c133 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -20,6 +20,7 @@ pub fn get_password(account_name: &str) -> Result { .with_context(|| format!("No password found for {account_name}")) } +#[allow(dead_code)] // Will be used when account deletion is implemented pub fn delete_password(account_name: &str) -> Result<()> { let entry = Entry::new(SERVICE, account_name) .with_context(|| format!("Failed to create keyring entry for {account_name}"))?; diff --git a/src/components/status_bar.rs b/src/components/status_bar.rs index 752b5e9..c041e67 100644 --- a/src/components/status_bar.rs +++ b/src/components/status_bar.rs @@ -41,11 +41,11 @@ impl StatusBar { } pub fn tick(&mut self) { - if let Some(expires) = self.status_expires { - if Instant::now() >= expires { - self.status.clear(); - self.status_expires = None; - } + if let Some(expires) = self.status_expires + && Instant::now() >= expires + { + self.status.clear(); + self.status_expires = None; } } } diff --git a/src/config.rs b/src/config.rs index 896468b..0025a64 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,15 +49,14 @@ impl Config { pub fn load() -> Result { let path = Self::path(); // Migrate from old platform-specific location if needed - if !path.exists() { - if let Some(old) = dirs::config_dir().map(|d| d.join("crabmail").join("config.toml")) { - if old.exists() { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::copy(&old, &path)?; - } + if !path.exists() + && let Some(old) = dirs::config_dir().map(|d| d.join("crabmail").join("config.toml")) + && old.exists() + { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; } + fs::copy(&old, &path)?; } if !path.exists() { let config = Config::default(); diff --git a/src/imap_client.rs b/src/imap_client.rs index 969a9d8..388293c 100644 --- a/src/imap_client.rs +++ b/src/imap_client.rs @@ -40,6 +40,7 @@ pub struct MailboxInfo { #[derive(Debug, Clone)] pub struct MessageSummary { + #[allow(dead_code)] // Retained for future IMAP sequence-based operations pub seq: u32, pub uid: u32, pub from: String,