authorclover caruso <git@paperclover.net> 2026-10-15 02:46:10-07:00
committerclover caruso <git@paperclover.net> 2026-10-15 02:58:22-07:00
loge3d4066dc407255c595ad64f3ec4aaed85e64628
tree124b617d8f5fae8d69fef9aa985596a0734401d6
parentc355a943bcb3d015015c97abff850d15d654252f
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

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

4 files changed, 24 insertions(+), 13 deletions(-)

crates/onestore/src/op/mod.rs+1-1
......@@ -27,7 +27,7 @@ pub(crate) mod tests;
2727pub use lower::{lower, lower_page};
2828/// `op` applied to `page`, the model of what the section holds: the page `Section::apply`
2929/// leaves, as reading it back shows it.
30pub use model::apply as predict;
30pub use model::{apply as predict, title_line};
3131
3232/// Property identifiers with their encoded values.
3333pub(crate) type Values = Vec<(u32, Vec<u8>)>;
crates/onestore/src/op/model.rs+8-5
......@@ -551,22 +551,25 @@ fn relevel(list: &mut [PageParagraph], index: usize, level: u32) {
551551/// The title a page's writers store after changing it: the title text's first line when
552552/// it has one, which only an edit of that text changes, else the first line of body text.
553553fn retitle(page: &mut Page, edited: Option<ExGuid>) {
554 let line = |text: &str| {
555 crate::edit::without_fields(text.trim_start().split('\r').next().unwrap_or_default())
556 };
557554 match find_title_text(page) {
558555 Some(title) => {
559556 let text = text_of(page, title);
560 if line(&text).is_empty() {
557 if title_line(&text).is_empty() {
561558 automatic(page);
562559 } else if edited == Some(title) {
563 page.title = line(&text);
560 page.title = title_line(&text);
564561 }
565562 }
566563 None => automatic(page),
567564 }
568565}
569566
567/// The title stored for title text `text`: its first line, without leading space or fields.
568/// Empty leaves the page titled by its body.
569pub fn title_line(text: &str) -> String {
570 crate::edit::without_fields(text.trim_start().split('\r').next().unwrap_or_default())
571}
572
570573fn automatic(page: &mut Page) {
571574 let mut roots: Vec<&PageObject> = page
572575 .objects
crates/snowbound/src/macos.rs+8-4
......@@ -1456,16 +1456,20 @@ pub fn text_colors(window: &Window) -> [[f32; 4]; 3] {
14561456 }
14571457}
14581458
1459/// Whether the window takes commands: not while a native dialog is up over it.
1459/// Whether the window takes commands: not while a native dialog is up over it or another
1460/// of the application's windows has the keys.
14601461pub(crate) fn commands_allowed() -> bool {
14611462 let app = NSApplication::sharedApplication(
14621463 MainThreadMarker::new().expect("Commands run on the main thread"),
14631464 );
14641465 WINDOW.with_borrow(|window| unsafe {
14651466 app.modalWindow().is_none()
1466 && window
1467 .as_ref()
1468 .is_none_or(|window| window.attachedSheet().is_none())
1467 && window.as_ref().is_none_or(|window| {
1468 window.attachedSheet().is_none()
1469 && app
1470 .keyWindow()
1471 .is_none_or(|key| std::ptr::eq(&*key, &**window))
1472 })
14691473 })
14701474}
14711475
crates/snowbound/src/main.rs+7-3
......@@ -4304,10 +4304,14 @@ impl State {
43044304 },
43054305 )?;
43064306 // The page list shows the title as it is typed, even when the caret left it since.
4307 let title = (self.view.editor.outlines().iter())
4307 let editor = &self.view.editor;
4308 let title = (editor.outlines().iter())
43084309 .find(|outline| outline.title)
4309 .map(canvas::editor::TextOutline::shown_text);
4310 if title.is_some_and(|title| title != session.title()) {
4310 .and_then(|outline| outline.document().paragraphs().last())
4311 .map(|paragraph| onestore::op::title_line(paragraph.text()));
4312 if editor.active_outline().title
4313 || title.is_some_and(|title| !title.is_empty() && title != session.title())
4314 {
43114315 session.pages = session.section.pages()?;
43124316 }
43134317 self.edited(vec![space]);