From d810cd5fdb662a953f40822be3204465cf15bdaf Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Sat, 4 Apr 2026 16:30:33 +0800 Subject: [PATCH 1/4] {"schema":"delivery/1","type":"fix","scope":"settings-window","summary":"refine settings defaults and about panel copy","intent":"align the Settings window defaults with startup-only permission recovery and make the About section informative without diverging from the existing settings layout","impact":"startup permission recovery now opens with Permissions expanded while normal Settings opens keep Permissions collapsed and other sections expanded; the placeholder Advanced section is removed; About now shows repo and X links plus a theme-aware follow callout; regression tests cover both section defaults and entry routing","breaking":false,"risk":"low","authority":"review","delivery_mode":"status-only","refs":[]} --- apps/rsnap/src/app.rs | 37 ++++++++- apps/rsnap/src/settings_window.rs | 18 +++- apps/rsnap/src/settings_window/chrome.rs | 8 +- apps/rsnap/src/settings_window/sections.rs | 97 ++++++++++++++++------ 4 files changed, 129 insertions(+), 31 deletions(-) diff --git a/apps/rsnap/src/app.rs b/apps/rsnap/src/app.rs index 06a8a4d3..4e243cbd 100644 --- a/apps/rsnap/src/app.rs +++ b/apps/rsnap/src/app.rs @@ -30,7 +30,7 @@ use self::scroll_input_macos::SharedScrollInputState; #[cfg(target_os = "macos")] use crate::permissions_macos; use crate::settings::AppSettings; -use crate::settings_window::SettingsWindow; +use crate::settings_window::{SettingsWindow, SettingsWindowEntry}; use rsnap_overlay::OverlaySession; pub(crate) enum UserEvent { @@ -85,6 +85,14 @@ struct App { #[cfg(target_os = "macos")] startup_permissions_checked: bool, } + +fn settings_window_entry(requested_by: &'static str) -> SettingsWindowEntry { + match requested_by { + "startup-permission-check" => SettingsWindowEntry::Permissions, + _ => SettingsWindowEntry::Standard, + } +} + impl App { #[allow(clippy::too_many_arguments)] fn new( @@ -152,7 +160,9 @@ impl App { return; } - match SettingsWindow::open(event_loop) { + let entry = settings_window_entry(requested_by); + + match SettingsWindow::open(event_loop, entry) { Ok(window) => { tracing::info!(requested_by = %requested_by, "Settings window opened."); @@ -205,3 +215,26 @@ impl App { pub fn run() -> Result<()> { runtime::run() } + +#[cfg(test)] +mod tests { + use super::{SettingsWindowEntry, settings_window_entry}; + + #[test] + fn startup_permission_check_uses_permissions_entry() { + assert_eq!( + settings_window_entry("startup-permission-check"), + SettingsWindowEntry::Permissions + ); + } + + #[test] + fn non_startup_settings_entries_use_standard_entry() { + assert_eq!(settings_window_entry("tray-permissions-menu"), SettingsWindowEntry::Standard); + assert_eq!( + settings_window_entry("menubar-permissions-menu"), + SettingsWindowEntry::Standard + ); + assert_eq!(settings_window_entry("tray-settings-menu"), SettingsWindowEntry::Standard); + } +} diff --git a/apps/rsnap/src/settings_window.rs b/apps/rsnap/src/settings_window.rs index ca0d12dc..d920aaa7 100644 --- a/apps/rsnap/src/settings_window.rs +++ b/apps/rsnap/src/settings_window.rs @@ -48,6 +48,20 @@ pub(crate) enum SettingsControl { CloseRequested, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum SettingsWindowEntry { + Standard, + Permissions, +} +impl SettingsWindowEntry { + const fn section_defaults(self) -> sections::SettingsUiSectionDefaults { + match self { + Self::Standard => sections::SettingsUiSectionDefaults::standard(), + Self::Permissions => sections::SettingsUiSectionDefaults::permissions_focused(), + } + } +} + #[derive(Clone, Debug)] pub(crate) enum SettingsWindowAction { Begin, @@ -74,6 +88,7 @@ pub(crate) struct SettingsWindow { last_redraw: Instant, did_autosize: bool, combo_width: f32, + section_defaults: sections::SettingsUiSectionDefaults, requested_theme: Option, effective_theme: Option, theme_icon_system: String, @@ -84,7 +99,7 @@ pub(crate) struct SettingsWindow { action_queue: VecDeque, } impl SettingsWindow { - pub(crate) fn open(event_loop: &ActiveEventLoop) -> Result { + pub(crate) fn open(event_loop: &ActiveEventLoop, entry: SettingsWindowEntry) -> Result { let attrs = platform::settings_window_attributes(); let window = event_loop.create_window(attrs).wrap_err("create settings window")?; let window = std::sync::Arc::new(window); @@ -131,6 +146,7 @@ impl SettingsWindow { last_redraw: Instant::now(), did_autosize: false, combo_width: SETTINGS_COMBO_WIDTH, + section_defaults: entry.section_defaults(), requested_theme: None, effective_theme: None, theme_icon_system, diff --git a/apps/rsnap/src/settings_window/chrome.rs b/apps/rsnap/src/settings_window/chrome.rs index 056fd583..7cf589b0 100644 --- a/apps/rsnap/src/settings_window/chrome.rs +++ b/apps/rsnap/src/settings_window/chrome.rs @@ -29,7 +29,13 @@ impl SettingsWindow { sections::with_settings_density(ui, combo_width, |ui| { changed |= self.render_titlebar_controls(ui, &ctx, settings); ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| { - changed |= sections::render_all_sections(self, ui, &ctx, settings); + changed |= sections::render_all_sections_with_defaults( + self, + ui, + &ctx, + settings, + self.section_defaults, + ); }); }); }); diff --git a/apps/rsnap/src/settings_window/sections.rs b/apps/rsnap/src/settings_window/sections.rs index ed7418a8..a15a0c81 100644 --- a/apps/rsnap/src/settings_window/sections.rs +++ b/apps/rsnap/src/settings_window/sections.rs @@ -40,24 +40,22 @@ pub(super) struct SettingsUiSectionDefaults { hotkeys: bool, capture: bool, output: bool, - advanced: bool, about: bool, } impl SettingsUiSectionDefaults { pub(super) const fn standard() -> Self { Self { - permissions: true, + permissions: false, general: true, overlay: true, - hotkeys: false, - capture: false, - output: false, - advanced: false, - about: false, + hotkeys: true, + capture: true, + output: true, + about: true, } } - pub(super) const fn all_open() -> Self { + pub(super) const fn permissions_focused() -> Self { Self { permissions: true, general: true, @@ -65,20 +63,30 @@ impl SettingsUiSectionDefaults { hotkeys: true, capture: true, output: true, - advanced: true, about: true, } } - pub(super) const fn hotkeys_expanded() -> Self { + pub(super) const fn all_open() -> Self { Self { permissions: true, general: true, overlay: true, hotkeys: true, + capture: true, + output: true, + about: true, + } + } + + pub(super) const fn hotkeys_expanded() -> Self { + Self { + permissions: false, + general: false, + overlay: false, + hotkeys: true, capture: false, output: false, - advanced: false, about: false, } } @@ -116,15 +124,6 @@ pub(super) fn with_settings_density( .inner } -pub(super) fn render_all_sections( - host: &mut impl SettingsUiHost, - ui: &mut Ui, - ctx: &Context, - settings: &mut AppSettings, -) -> bool { - render_all_sections_with_defaults(host, ui, ctx, settings, SettingsUiSectionDefaults::default()) -} - pub(super) fn render_all_sections_with_defaults( host: &mut impl SettingsUiHost, ui: &mut Ui, @@ -171,14 +170,8 @@ pub(super) fn render_all_sections_with_defaults( ui.add_space(SETTINGS_SECTION_GAP); - CollapsingHeader::new("Advanced").default_open(defaults.advanced).show(ui, |ui| { - ui.label("Advanced options are coming soon."); - }); - - ui.add_space(SETTINGS_SECTION_GAP); - CollapsingHeader::new("About").default_open(defaults.about).show(ui, |ui| { - ui.label(format!("rsnap {}", env!("CARGO_PKG_VERSION"))); + render_about_section(ui); }); changed @@ -469,6 +462,25 @@ fn render_output_section(combo_width: f32, ui: &mut Ui, settings: &mut AppSettin changed } +fn render_about_section(ui: &mut Ui) { + ui.label(format!("Version {}", env!("CARGO_PKG_VERSION"))); + ui.small("Fast screenshots for macOS, built in Rust."); + ui.scope(|ui| { + ui.style_mut().override_text_style = Some(egui::TextStyle::Small); + ui.hyperlink_to("Repository: github.com/hack-ink/rsnap", env!("CARGO_PKG_REPOSITORY")); + ui.hyperlink_to("X: @YvetteCipher", "https://x.com/YvetteCipher"); + }); + ui.small( + "Star the repo, follow Yvette on X, or reach out there if you want to sponsor development.", + ); + ui.label( + egui::RichText::new("Following on X also helps Yvette qualify for X revenue share.") + .small() + .strong() + .color(ui.visuals().warn_fg_color), + ); +} + fn render_general_section( combo_width: f32, ui: &mut Ui, @@ -908,3 +920,34 @@ fn toolbar_placement_label(placement: ToolbarPlacement) -> &'static str { ToolbarPlacement::Bottom => "Bottom", } } + +#[cfg(test)] +mod tests { + use super::SettingsUiSectionDefaults; + + #[test] + fn standard_defaults_focus_regular_capture_sections() { + let defaults = SettingsUiSectionDefaults::standard(); + + assert!(!defaults.permissions); + assert!(defaults.general); + assert!(defaults.overlay); + assert!(defaults.hotkeys); + assert!(defaults.capture); + assert!(defaults.output); + assert!(defaults.about); + } + + #[test] + fn permissions_focused_defaults_expand_permissions_without_collapsing_other_sections() { + let defaults = SettingsUiSectionDefaults::permissions_focused(); + + assert!(defaults.permissions); + assert!(defaults.general); + assert!(defaults.overlay); + assert!(defaults.hotkeys); + assert!(defaults.capture); + assert!(defaults.output); + assert!(defaults.about); + } +} From 982a33b06f74a15f63aaa4285c28275699965dbe Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Sat, 4 Apr 2026 16:39:02 +0800 Subject: [PATCH 2/4] {"schema":"delivery/1","type":"fix","scope":"settings-window","summary":"repair settings-window style lint for CI","intent":"bring the settings defaults lane back into repo style compliance after the behavior and copy changes introduced vstyle-only violations on the PR head","impact":"reorders app module items to match repository layout rules, switches test imports to crate-absolute paths, and normalizes About-section short-path and spacing usage without changing runtime behavior","breaking":false,"risk":"low","authority":"ci","delivery_mode":"status-only","refs":[{"system":"github","repo":"hack-ink/rsnap","number":56,"role":"fixes-head"}]} --- apps/rsnap/src/app.rs | 28 ++++++++++++---------- apps/rsnap/src/settings_window/sections.rs | 14 +++++++---- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/apps/rsnap/src/app.rs b/apps/rsnap/src/app.rs index 4e243cbd..84ab6808 100644 --- a/apps/rsnap/src/app.rs +++ b/apps/rsnap/src/app.rs @@ -85,14 +85,6 @@ struct App { #[cfg(target_os = "macos")] startup_permissions_checked: bool, } - -fn settings_window_entry(requested_by: &'static str) -> SettingsWindowEntry { - match requested_by { - "startup-permission-check" => SettingsWindowEntry::Permissions, - _ => SettingsWindowEntry::Standard, - } -} - impl App { #[allow(clippy::too_many_arguments)] fn new( @@ -216,25 +208,35 @@ pub fn run() -> Result<()> { runtime::run() } +fn settings_window_entry(requested_by: &'static str) -> SettingsWindowEntry { + match requested_by { + "startup-permission-check" => SettingsWindowEntry::Permissions, + _ => SettingsWindowEntry::Standard, + } +} + #[cfg(test)] mod tests { - use super::{SettingsWindowEntry, settings_window_entry}; + use crate::app::{self, SettingsWindowEntry}; #[test] fn startup_permission_check_uses_permissions_entry() { assert_eq!( - settings_window_entry("startup-permission-check"), + app::settings_window_entry("startup-permission-check"), SettingsWindowEntry::Permissions ); } #[test] fn non_startup_settings_entries_use_standard_entry() { - assert_eq!(settings_window_entry("tray-permissions-menu"), SettingsWindowEntry::Standard); assert_eq!( - settings_window_entry("menubar-permissions-menu"), + app::settings_window_entry("tray-permissions-menu"), + SettingsWindowEntry::Standard + ); + assert_eq!( + app::settings_window_entry("menubar-permissions-menu"), SettingsWindowEntry::Standard ); - assert_eq!(settings_window_entry("tray-settings-menu"), SettingsWindowEntry::Standard); + assert_eq!(app::settings_window_entry("tray-settings-menu"), SettingsWindowEntry::Standard); } } diff --git a/apps/rsnap/src/settings_window/sections.rs b/apps/rsnap/src/settings_window/sections.rs index a15a0c81..07549830 100644 --- a/apps/rsnap/src/settings_window/sections.rs +++ b/apps/rsnap/src/settings_window/sections.rs @@ -6,14 +6,15 @@ use egui::Context; use egui::DragValue; use egui::Pos2; use egui::Rect; -#[cfg(target_os = "macos")] -use egui::RichText; use egui::Sense; use egui::Slider; use egui::Stroke; use egui::TextEdit; +use egui::TextStyle; use egui::Ui; use egui::style::HandleShape; +#[cfg(target_os = "macos")] +use egui::{self, RichText}; #[cfg(target_os = "macos")] use crate::permissions_macos; @@ -465,16 +466,19 @@ fn render_output_section(combo_width: f32, ui: &mut Ui, settings: &mut AppSettin fn render_about_section(ui: &mut Ui) { ui.label(format!("Version {}", env!("CARGO_PKG_VERSION"))); ui.small("Fast screenshots for macOS, built in Rust."); + ui.scope(|ui| { - ui.style_mut().override_text_style = Some(egui::TextStyle::Small); + ui.style_mut().override_text_style = Some(TextStyle::Small); + ui.hyperlink_to("Repository: github.com/hack-ink/rsnap", env!("CARGO_PKG_REPOSITORY")); ui.hyperlink_to("X: @YvetteCipher", "https://x.com/YvetteCipher"); }); + ui.small( "Star the repo, follow Yvette on X, or reach out there if you want to sponsor development.", ); ui.label( - egui::RichText::new("Following on X also helps Yvette qualify for X revenue share.") + RichText::new("Following on X also helps Yvette qualify for X revenue share.") .small() .strong() .color(ui.visuals().warn_fg_color), @@ -923,7 +927,7 @@ fn toolbar_placement_label(placement: ToolbarPlacement) -> &'static str { #[cfg(test)] mod tests { - use super::SettingsUiSectionDefaults; + use crate::settings_window::sections::SettingsUiSectionDefaults; #[test] fn standard_defaults_focus_regular_capture_sections() { From 139e194e456d4f6b0d1f92e74ba71929c842b3b0 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Sat, 4 Apr 2026 16:45:45 +0800 Subject: [PATCH 3/4] {"schema":"delivery/1","type":"fix","scope":"settings-window","summary":"fix linux lint import for about callout","intent":"restore the PR head after the style-only CI repair moved RichText behind a macOS-only import even though the About section compiles on all targets","impact":"the About callout now imports RichText on every platform so Linux Rust checks can compile the settings window again without changing runtime behavior or copy","breaking":false,"risk":"low","authority":"ci","delivery_mode":"status-only","refs":[{"system":"github","repo":"hack-ink/rsnap","number":56,"role":"fixes-head"}]} --- apps/rsnap/src/settings_window/sections.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/rsnap/src/settings_window/sections.rs b/apps/rsnap/src/settings_window/sections.rs index 07549830..faadeb81 100644 --- a/apps/rsnap/src/settings_window/sections.rs +++ b/apps/rsnap/src/settings_window/sections.rs @@ -10,11 +10,10 @@ use egui::Sense; use egui::Slider; use egui::Stroke; use egui::TextEdit; +use egui::RichText; use egui::TextStyle; use egui::Ui; use egui::style::HandleShape; -#[cfg(target_os = "macos")] -use egui::{self, RichText}; #[cfg(target_os = "macos")] use crate::permissions_macos; From f493575fb1d7c55d88b607b86c0355bdcb835c3b Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Sat, 4 Apr 2026 16:53:07 +0800 Subject: [PATCH 4/4] {"schema":"delivery/1","type":"fix","scope":"settings-window","summary":"fix nightly rustfmt import order for ci","intent":"restore the PR head after the Linux import repair left the sections import block in a stable-format shape that still disagreed with nightly rustfmt in CI","impact":"reorders the About-section RichText import to match the nightly formatter used by Rust checks without changing runtime behavior, copy, or test coverage","breaking":false,"risk":"low","authority":"ci","delivery_mode":"status-only","refs":[{"system":"github","repo":"hack-ink/rsnap","number":56,"role":"fixes-head"}]} --- apps/rsnap/src/settings_window/sections.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rsnap/src/settings_window/sections.rs b/apps/rsnap/src/settings_window/sections.rs index faadeb81..f295b485 100644 --- a/apps/rsnap/src/settings_window/sections.rs +++ b/apps/rsnap/src/settings_window/sections.rs @@ -6,11 +6,11 @@ use egui::Context; use egui::DragValue; use egui::Pos2; use egui::Rect; +use egui::RichText; use egui::Sense; use egui::Slider; use egui::Stroke; use egui::TextEdit; -use egui::RichText; use egui::TextStyle; use egui::Ui; use egui::style::HandleShape;