From d73eacf40fcae001af971a179313f1d03d225f5d Mon Sep 17 00:00:00 2001 From: Breadway Date: Wed, 5 Aug 2026 14:02:47 +0800 Subject: [PATCH] ci: build against bread-ecosystem's shared Arch CI image, add check.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same fix as breadpad: build inside the shared pinned Arch container (bread-ecosystem/ci/, cloned at the sha in ci/bread-ecosystem.rev) instead of building natively against whatever's on the runner host. Adds check.yml (clippy + test on feature/**/fix/**) as a fast-fail gate before anything reaches main. Turning on clippy -D warnings for the first time surfaced 10 pre-existing warnings (int_plus_one, ptr_arg on &mut Vec params, collapsible_if, collapsible_match) across layout.rs, mirror.rs, profile.rs, and the config/mirror TUI views — all fixed exactly per clippy's suggested diffs, verified behavior-preserving (the mirror_view.rs collapse in particular: confirmed the "do nothing" fallthrough when mirror.result is None is unchanged, since that was already the fallthrough behavior of the original nested if with no matching else on the outer condition). Verified locally: build, clippy, and test all pass through the new container path. --- .forgejo/workflows/check.yml | 24 ++++++++++++++++ .forgejo/workflows/dev-release.yml | 2 +- .forgejo/workflows/rc-release.yml | 2 +- .forgejo/workflows/release.yml | 2 +- ci/bread-ecosystem.rev | 1 + ci/build.sh | 20 ++++++++++++++ src/layout.rs | 4 +-- src/mirror.rs | 5 ++-- src/profile.rs | 2 +- src/ui/config_view.rs | 40 +++++++++++++-------------- src/ui/layout_view.rs | 4 +-- src/ui/mirror_view.rs | 44 +++++++++++++++--------------- 12 files changed, 97 insertions(+), 53 deletions(-) create mode 100644 .forgejo/workflows/check.yml create mode 100644 ci/bread-ecosystem.rev create mode 100755 ci/build.sh diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml new file mode 100644 index 0000000..b547c34 --- /dev/null +++ b/.forgejo/workflows/check.yml @@ -0,0 +1,24 @@ +name: check + +# Fast-fail lint/test on short-lived work branches, before it ever reaches +# main and triggers a dev-track release build. +on: + push: + branches: ['feature/**', 'fix/**'] + +jobs: + check: + runs-on: [self-hosted, hestia] + steps: + - name: checkout + run: | + set -euo pipefail + rm -rf src && mkdir src + git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src + + - name: clippy + run: cd src && bash ci/build.sh cargo clippy --workspace --all-targets --locked -- -D warnings + + - name: test + run: cd src && bash ci/build.sh cargo test --workspace --locked diff --git a/.forgejo/workflows/dev-release.yml b/.forgejo/workflows/dev-release.yml index 1fbbcd9..3087715 100644 --- a/.forgejo/workflows/dev-release.yml +++ b/.forgejo/workflows/dev-release.yml @@ -19,7 +19,7 @@ jobs: "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - name: build - run: cd src && cargo build --release --locked + run: cd src && bash ci/build.sh cargo build --release --locked - name: compute dev version run: | diff --git a/.forgejo/workflows/rc-release.yml b/.forgejo/workflows/rc-release.yml index b44f054..fd6ba9a 100644 --- a/.forgejo/workflows/rc-release.yml +++ b/.forgejo/workflows/rc-release.yml @@ -21,7 +21,7 @@ jobs: "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - name: build - run: cd src && cargo build --release --locked + run: cd src && bash ci/build.sh cargo build --release --locked - name: prepare artifacts run: | diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index f584b3a..b3b2164 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -17,7 +17,7 @@ jobs: "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - name: build - run: cd src && cargo build --release --locked + run: cd src && bash ci/build.sh cargo build --release --locked - name: prepare artifacts run: | diff --git a/ci/bread-ecosystem.rev b/ci/bread-ecosystem.rev new file mode 100644 index 0000000..474f1fd --- /dev/null +++ b/ci/bread-ecosystem.rev @@ -0,0 +1 @@ +620c5a1317a6b57276eabca961facdb78bf510db diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 0000000..7582664 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Delegates to bread-ecosystem's shared CI build image/script, pinned to +# the commit in ci/bread-ecosystem.rev — not `main`. bread-ecosystem's CI +# files now affect every product's release pipeline, so bumping the pin +# is a deliberate act instead of silent drift. +# +# Usage: ci/build.sh cargo build --release --locked +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REV="$(cat "${ROOT}/ci/bread-ecosystem.rev")" + +CACHE_DIR="/tmp/bread-ecosystem-ci-${REV}" +if [ ! -d "$CACHE_DIR" ]; then + rm -rf /tmp/bread-ecosystem-ci-* + git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "$CACHE_DIR" + git -C "$CACHE_DIR" checkout --quiet "$REV" +fi + +bash "${CACHE_DIR}/ci/build.sh" breadmon "$ROOT" "$@" diff --git a/src/layout.rs b/src/layout.rs index c19923a..0b6e835 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -103,7 +103,7 @@ pub fn snap_position( } /// Move the selected monitor by (dx, dy) pixels, then snap. -pub fn move_selected(state: &LayoutState, monitors: &mut Vec, dx: i32, dy: i32) { +pub fn move_selected(state: &LayoutState, monitors: &mut [Monitor], dx: i32, dy: i32) { let idx = state.selected; if idx >= monitors.len() { return; @@ -116,7 +116,7 @@ pub fn move_selected(state: &LayoutState, monitors: &mut Vec, dx: i32, } /// Place monitors in a left-to-right row with no gaps. -pub fn auto_arrange(monitors: &mut Vec) { +pub fn auto_arrange(monitors: &mut [Monitor]) { let mut cursor = 0i32; for m in monitors.iter_mut() { m.x = cursor; diff --git a/src/mirror.rs b/src/mirror.rs index 6485a58..2e46367 100644 --- a/src/mirror.rs +++ b/src/mirror.rs @@ -62,11 +62,10 @@ pub fn find_mirror_modes(source: &Monitor, target: &Monitor) -> Option Profile { /// Apply a profile's settings onto a list of live monitors (matched by name). /// Monitors not in the profile are left unchanged. -pub fn apply_to_monitors(profile: &Profile, monitors: &mut Vec) { +pub fn apply_to_monitors(profile: &Profile, monitors: &mut [Monitor]) { for pm in &profile.monitors { if let Some(m) = monitors.iter_mut().find(|m| m.name == pm.name) { if let Some(mode) = Mode::parse(&format!("{}Hz", pm.mode)) { diff --git a/src/ui/config_view.rs b/src/ui/config_view.rs index 4ec4026..d3a3352 100644 --- a/src/ui/config_view.rs +++ b/src/ui/config_view.rs @@ -242,14 +242,14 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) { state.dirty = true; } } - KeyCode::Char('l') | KeyCode::Right => { - if state.config.res_idx + 1 < state.config.resolutions.len() { - state.config.res_idx += 1; - let m = &state.monitors[idx]; - state.config.update_refreshes(m); - sync_mode_to_monitor(state, idx); - state.dirty = true; - } + KeyCode::Char('l') | KeyCode::Right + if state.config.res_idx + 1 < state.config.resolutions.len() => + { + state.config.res_idx += 1; + let m = &state.monitors[idx]; + state.config.update_refreshes(m); + sync_mode_to_monitor(state, idx); + state.dirty = true; } _ => {} }, @@ -261,12 +261,12 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) { state.dirty = true; } } - KeyCode::Char('l') | KeyCode::Right => { - if state.config.refresh_idx + 1 < state.config.refreshes.len() { - state.config.refresh_idx += 1; - sync_mode_to_monitor(state, idx); - state.dirty = true; - } + KeyCode::Char('l') | KeyCode::Right + if state.config.refresh_idx + 1 < state.config.refreshes.len() => + { + state.config.refresh_idx += 1; + sync_mode_to_monitor(state, idx); + state.dirty = true; } _ => {} }, @@ -335,12 +335,12 @@ fn handle_field_key(event: KeyEvent, state: &mut AppState) { state.dirty = true; } } - KeyCode::Char('l') | KeyCode::Right => { - if state.config.mirror_idx + 1 < state.config.mirror_options.len() { - state.config.mirror_idx += 1; - sync_mirror_to_monitor(state, idx); - state.dirty = true; - } + KeyCode::Char('l') | KeyCode::Right + if state.config.mirror_idx + 1 < state.config.mirror_options.len() => + { + state.config.mirror_idx += 1; + sync_mirror_to_monitor(state, idx); + state.dirty = true; } _ => {} }, diff --git a/src/ui/layout_view.rs b/src/ui/layout_view.rs index ffa7131..c64d1e3 100644 --- a/src/ui/layout_view.rs +++ b/src/ui/layout_view.rs @@ -259,9 +259,9 @@ pub fn canvas_area(terminal_size: (u16, u16)) -> Rect { } fn in_canvas(col: u16, row: u16, canvas: Rect) -> bool { - col >= canvas.x + 1 + col > canvas.x && col < canvas.x + canvas.width.saturating_sub(1) - && row >= canvas.y + 1 + && row > canvas.y && row < canvas.y + canvas.height.saturating_sub(1) } diff --git a/src/ui/mirror_view.rs b/src/ui/mirror_view.rs index b593dd1..57f0d9d 100644 --- a/src/ui/mirror_view.rs +++ b/src/ui/mirror_view.rs @@ -210,33 +210,33 @@ pub fn handle_mouse(event: MouseEvent, state: &mut AppState) { } } } - r if r >= 7 => { + r if r >= 7 // Result panel: Apply is on the line with buttons. // Rough column check: col < 20 = Apply, col >= 20 = Cancel - if state.mirror.result.is_some() { - let col = event.column; - if col < 20 { - // Activate Apply - state.mirror.focused = FIELDS.iter().position(|&f| f == MirrorField::Apply).unwrap_or(3); - if let Some(result) = state.mirror.result.clone() { - state.push_undo(); - let src_name = state.monitors[state.mirror.source_idx].name.clone(); - let tgt_idx = state.mirror.target_idx; - state.monitors[tgt_idx].active_mode = result.mirror_mode.clone(); - state.monitors[tgt_idx].mirror_of = Some(src_name.clone()); - state.dirty = true; - state.mirror.result = None; - state.mirror.focused = 0; - state.set_status( - format!("Mirror set: {} → {} at {}", src_name, state.monitors[tgt_idx].name, result.mirror_mode), - crate::ui::StatusLevel::Success, - ); - } - } else { - // Cancel + && state.mirror.result.is_some() => + { + let col = event.column; + if col < 20 { + // Activate Apply + state.mirror.focused = FIELDS.iter().position(|&f| f == MirrorField::Apply).unwrap_or(3); + if let Some(result) = state.mirror.result.clone() { + state.push_undo(); + let src_name = state.monitors[state.mirror.source_idx].name.clone(); + let tgt_idx = state.mirror.target_idx; + state.monitors[tgt_idx].active_mode = result.mirror_mode.clone(); + state.monitors[tgt_idx].mirror_of = Some(src_name.clone()); + state.dirty = true; state.mirror.result = None; state.mirror.focused = 0; + state.set_status( + format!("Mirror set: {} → {} at {}", src_name, state.monitors[tgt_idx].name, result.mirror_mode), + crate::ui::StatusLevel::Success, + ); } + } else { + // Cancel + state.mirror.result = None; + state.mirror.focused = 0; } } _ => {}