From e3d4066dc407255c595ad64f3ec4aaed85e64628 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Thu, 15 Oct 2026 02:46:10 -0700 Subject: [PATCH] fix(desktop): keep menu shortcuts from acting behind other windows On macOS, commands wait while another of the app's windows, such as the About panel, has the keys. The page list compares titles the way the store derives them, so pages whose stored title differs from their title text no longer reread the list on every edit. Refs #129 Assisted-by: claude-opus-5.5 --- crates/onestore/src/op/mod.rs | 2 +- crates/onestore/src/op/model.rs | 13 ++++++++----- crates/snowbound/src/macos.rs | 12 ++++++++---- crates/snowbound/src/main.rs | 10 +++++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/crates/onestore/src/op/mod.rs b/crates/onestore/src/op/mod.rs index 87c649658b58639d1249351570b16eba83648a7e..df2655772cdfaa38c90fec42edc504b9521376b1 100644 --- a/crates/onestore/src/op/mod.rs +++ b/crates/onestore/src/op/mod.rs @@ -27,7 +27,7 @@ pub(crate) mod tests; pub use lower::{lower, lower_page}; /// `op` applied to `page`, the model of what the section holds: the page `Section::apply` /// leaves, as reading it back shows it. -pub use model::apply as predict; +pub use model::{apply as predict, title_line}; /// Property identifiers with their encoded values. pub(crate) type Values = Vec<(u32, Vec)>; diff --git a/crates/onestore/src/op/model.rs b/crates/onestore/src/op/model.rs index adefb6755d888744748d15a20ea518df5308e55f..b57f54c2e306471a4240941fb25f87220e1f5fdf 100644 --- a/crates/onestore/src/op/model.rs +++ b/crates/onestore/src/op/model.rs @@ -551,22 +551,25 @@ fn relevel(list: &mut [PageParagraph], index: usize, level: u32) { /// The title a page's writers store after changing it: the title text's first line when /// it has one, which only an edit of that text changes, else the first line of body text. fn retitle(page: &mut Page, edited: Option) { - let line = |text: &str| { - crate::edit::without_fields(text.trim_start().split('\r').next().unwrap_or_default()) - }; match find_title_text(page) { Some(title) => { let text = text_of(page, title); - if line(&text).is_empty() { + if title_line(&text).is_empty() { automatic(page); } else if edited == Some(title) { - page.title = line(&text); + page.title = title_line(&text); } } None => automatic(page), } } +/// The title stored for title text `text`: its first line, without leading space or fields. +/// Empty leaves the page titled by its body. +pub fn title_line(text: &str) -> String { + crate::edit::without_fields(text.trim_start().split('\r').next().unwrap_or_default()) +} + fn automatic(page: &mut Page) { let mut roots: Vec<&PageObject> = page .objects diff --git a/crates/snowbound/src/macos.rs b/crates/snowbound/src/macos.rs index ca9a567dd3418f0b68e3ab83b871b541f388515d..105fd0955eb41ff976ea1e0729e96a739606c431 100644 --- a/crates/snowbound/src/macos.rs +++ b/crates/snowbound/src/macos.rs @@ -1456,16 +1456,20 @@ pub fn text_colors(window: &Window) -> [[f32; 4]; 3] { } } -/// Whether the window takes commands: not while a native dialog is up over it. +/// Whether the window takes commands: not while a native dialog is up over it or another +/// of the application's windows has the keys. pub(crate) fn commands_allowed() -> bool { let app = NSApplication::sharedApplication( MainThreadMarker::new().expect("Commands run on the main thread"), ); WINDOW.with_borrow(|window| unsafe { app.modalWindow().is_none() - && window - .as_ref() - .is_none_or(|window| window.attachedSheet().is_none()) + && window.as_ref().is_none_or(|window| { + window.attachedSheet().is_none() + && app + .keyWindow() + .is_none_or(|key| std::ptr::eq(&*key, &**window)) + }) }) } diff --git a/crates/snowbound/src/main.rs b/crates/snowbound/src/main.rs index f79dd3074b10b5a70eaf5435f5c51ab69ecdd4a4..4fdd0fa24b109e247642dd8f862cbb83b3b209fe 100644 --- a/crates/snowbound/src/main.rs +++ b/crates/snowbound/src/main.rs @@ -4304,10 +4304,14 @@ impl State { }, )?; // The page list shows the title as it is typed, even when the caret left it since. - let title = (self.view.editor.outlines().iter()) + let editor = &self.view.editor; + let title = (editor.outlines().iter()) .find(|outline| outline.title) - .map(canvas::editor::TextOutline::shown_text); - if title.is_some_and(|title| title != session.title()) { + .and_then(|outline| outline.document().paragraphs().last()) + .map(|paragraph| onestore::op::title_line(paragraph.text())); + if editor.active_outline().title + || title.is_some_and(|title| !title.is_empty() && title != session.title()) + { session.pages = session.section.pages()?; } self.edited(vec![space]); -- 2.54.0