authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 22:14:00-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-03 01:50:33-07:00
logec793693c96b0c8395e2a0d8f93bc6fd6af9b469
tree9bfb50f054f8660c2537582e1baad5e98a4bf7c2
parent2b462d10dda0898b5f553701b46982d68bfc8661
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: Lock All Sections, Close This Notebook and Notebook Recycle Bin are disabled where they would do nothing

Lock All Sections stood enabled with no protected section unlocked; the File menu's and palette's Close This Notebook on an iCloud notebook, which its context menu already disables, since the app's iCloud folder lists every notebook in it; and Notebook Recycle Bin on a notebook whose bin holds no sections, where it ended in an alert. Assisted-by: claude-opus-5.5

4 files changed, 28 insertions(+), 4 deletions(-)

crates/snowbound/src/commands.rs+11-3
......@@ -1152,7 +1152,13 @@ impl State {
11521152 | Id::CustomizeTags
11531153 | Id::Help
11541154 | Id::CheckForUpdates => enabled(!modal),
1155 Id::CloseNotebook => enabled(!modal && self.notebook().is_some()),
1155 // The app's iCloud Drive folder lists every notebook in it.
1156 Id::CloseNotebook => enabled(
1157 !modal
1158 && self
1159 .notebook()
1160 .is_some_and(|library| !crate::manage::in_icloud_folder(&library.location)),
1161 ),
11561162 Id::OpenShared => enabled(!modal && cfg!(feature = "live")),
11571163 #[cfg(feature = "live")]
11581164 Id::LiveShare => enabled(
......@@ -1169,7 +1175,7 @@ impl State {
11691175 enabled: !modal
11701176 && self
11711177 .notebook()
1172 .is_some_and(|library| library.catalog().is_some()),
1178 .is_some_and(|library| self.recycle_bin_available(library)),
11731179 checked: Some(self.in_recycle_bin()),
11741180 },
11751181 Id::EmptyRecycleBin => enabled(
......@@ -1195,7 +1201,9 @@ impl State {
11951201 .is_some_and(|library| self.reads.shown(&library.location)),
11961202 ),
11971203 },
1198 Id::LockAll => enabled(!modal),
1204 Id::LockAll => {
1205 enabled(!modal && self.notebooks.iter().any(|library| library.any_unlocked()))
1206 }
11991207 Id::ShowNotebook => enabled(
12001208 !modal
12011209 && self
crates/snowbound/src/library.rs+5
......@@ -1105,6 +1105,11 @@ impl Library {
11051105 keys.get(&identity).map(|(key, _)| key.clone())
11061106 }
11071107
1108 /// Whether any of its password-protected sections is unlocked, which Lock All would lock.
1109 pub fn any_unlocked(&self) -> bool {
1110 self.keys.0.lock().is_ok_and(|keys| !keys.is_empty())
1111 }
1112
11081113 /// Marks the unlocked section at catalog `path` worked in now.
11091114 pub fn touch(&self, path: &str) {
11101115 if let (Some(identity), Ok(mut keys)) = (self.section_identity(path), self.keys.0.lock())
crates/snowbound/src/menus.rs+1-1
......@@ -587,7 +587,7 @@ impl State {
587587 item(
588588 Action::RecycleBin,
589589 "Notebook Recycle Bin",
590 library.catalog().is_none(),
590 !self.recycle_bin_available(library),
591591 false,
592592 ),
593593 item(
crates/snowbound/src/recycle.rs+11
......@@ -38,6 +38,17 @@ impl State {
3838 (self.session.as_ref()).is_some_and(|session| binned(&session.tabs[session.tab].path))
3939 }
4040
41 /// Whether Notebook Recycle Bin can do anything on `library`: leave its bin, shown, or
42 /// show one holding sections.
43 pub(crate) fn recycle_bin_available(&self, library: &Arc<Library>) -> bool {
44 let shown = self
45 .session
46 .as_ref()
47 .is_some_and(|session| Arc::ptr_eq(&session.library, library));
48 library.catalog().is_some()
49 && (shown && self.in_recycle_bin() || !library.tabs(BIN).is_empty())
50 }
51
4152 /// Notebook Recycle Bin: shows `library`'s bin at its first section holding pages, or
4253 /// while it shows, goes back to the notebook.
4354 pub(crate) fn toggle_recycle_bin(&mut self, library: Arc<Library>) {