authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 20:59:10-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-03 07:05:30-07:00
log5e10e17cd5f73270850258fba1ea45a4b203de7d
tree024366bcd779a0cdc94645ec55a9c5fbad0ecc5b
parent7a0b2662303af3d08687c1a26ff0cf6555c56723
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: Rename on a section tab's menu edits the tab in place

As a double click on the tab does, rather than opening the sidebar on the section's row. A menu counts as the tab's when it opened over the tab row. Assisted-by: claude-opus-5.5

2 files changed, 37 insertions(+), 6 deletions(-)

crates/snowbound/src/menus.rs+29-1
......@@ -376,12 +376,40 @@ impl State {
376376 }
377377 (Some(action), _) => {
378378 self.menu = None;
379 self.act_on(target, action);
379 match target {
380 // From its tab, as a double click there renames it.
381 Target::Section { library, path }
382 if action == Action::Rename && self.on_tab(&library, &path, point) =>
383 {
384 self.rename(crate::rename::Target::Entry {
385 library,
386 path,
387 in_tab: true,
388 });
389 }
390 target => self.act_on(target, action),
391 }
380392 }
381393 (None, _) => {}
382394 }
383395 }
384396
397 /// Whether `point`, where a context menu opened, is on the tab of section `path`.
398 fn on_tab(&self, library: &Arc<Library>, path: &str, point: [f32; 2]) -> bool {
399 let shown = self.session.as_ref().is_some_and(|session| {
400 Arc::ptr_eq(&session.library, library)
401 && session.tabs.iter().any(|tab| tab.path == path)
402 });
403 let [x, y] = point;
404 shown
405 && self
406 .ui
407 .rect(crate::sections())
408 .is_some_and(|[left, top, right, bottom]| {
409 x >= left && x < right && y >= top && y < bottom
410 })
411 }
412
385413 /// What can be done to `target`, in its context menu's order, as each shows there.
386414 pub(crate) fn actions(&self, target: &Target) -> Vec<(Action, Item<'static>)> {
387415 // An action, its label, whether it is disabled, and whether a rule starts its group.
crates/snowbound/tests/replay.rs+8-5
......@@ -564,23 +564,26 @@ fn a_version_copied_into_its_section_is_listed_and_found() {
564564 assert!(!search_results(&found).is_empty(), "{found}");
565565}
566566
567/// Rename on a section tab's menu opens the shut sidebar on a field that takes what is
568/// typed over the old name at once.
567/// Rename on a section tab's menu opens the field on the tab, as a double click there does,
568/// which takes what is typed over the old name at once; the shut sidebar stays shut.
569569#[test]
570fn renaming_a_section_from_its_tab_types_into_the_sidebar_at_once() {
571 let scratch = Scratch::new("rename-shut-sidebar");
570fn renaming_a_section_from_its_tab_types_into_the_tab_at_once() {
571 let scratch = Scratch::new("rename-tab");
572572 let notebook =
573573 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../corpus/cross-container/candidate");
574574 let mut steps = vec!["move 110 53", "press right", "release right", "settle"];
575575 steps.extend(["key Down", "key Enter", "settle"]);
576576 steps.extend(["type Renamed", "accessibility typed"]);
577 let [typed] = replay(&scratch, Some(&notebook), &steps)
577 steps.extend(["key Enter", "settle", "accessibility renamed"]);
578 let [typed, renamed] = replay(&scratch, Some(&notebook), &steps)
578579 .try_into()
579580 .unwrap();
580581 assert!(
581582 typed.contains(r#"TextInput "Name" = "Renamed" [focused]"#),
582583 "{typed}"
583584 );
585 assert!(notebooks(&typed).is_empty(), "the sidebar opened:\n{typed}");
586 assert!(renamed.contains(r#"Tab "Renamed""#), "{renamed}");
584587}
585588
586589/// Finding on the page leaves the query room beside the match count: a press just past