From 7d9cc53472b703729e8ab7cd8bc35a93ba78aa2f Mon Sep 17 00:00:00 2001 From: Hal Goff Date: Fri, 9 Oct 2026 14:14:27 +0100 Subject: [PATCH] Issue #225: a closed Comments panel stays closed when picking comment tools (#501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picking any comment tool opened the Comments panel whenever no right panel was open, so closing it was undone by the next tool switch. Now closing Comments (its close button or the rail icon) is remembered, across restarts, and comment tools leave it closed until the user opens it again (rail, View menu, Comments ▸ list, or a Reply action). Switching to or closing another panel doesn't count. Opening a document with comments still shows them, as Acrobat does. Claude-Session: https://claude.ai/code/session_01VNJqS2dEQawHJn1qTRJiKp Co-authored-by: Claude Opus 5.5 --- crates/ui-egui/src/canvas.rs | 6 ++++- crates/ui-egui/src/chrome.rs | 4 ++-- crates/ui-egui/src/commands.rs | 6 ++--- crates/ui-egui/src/lib.rs | 19 +++++++++++++++ crates/ui-egui/src/panels.rs | 2 +- crates/ui-egui/tests/comments.rs | 40 ++++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 4030d7c6e4..065d01fe21 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2001,7 +2001,11 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) { let field_props = field_props.then(|| view.prepare.selected.clone()).flatten(); match canvas_action { Some(comments::CanvasAction::Edit(e)) => view.pending_edit = Some(*e), - Some(comments::CanvasAction::OpenComments) => app.right = Some(RightPanel::Comments), + // `choose_right_panel` would borrow all of `app` while `view` is held; same effect. + Some(comments::CanvasAction::OpenComments) => { + app.right = Some(RightPanel::Comments); + app.comments_panel_closed = false; + } Some(comments::CanvasAction::Properties(p, i)) => open_props = Some((p, i)), None => {} } diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 41df36e7c1..bc4a717a42 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -240,7 +240,7 @@ fn main_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui) { (RightPanel::Compare, tl!("Compare")), ] { if ui.radio(app.right == Some(p), tl!(label)).clicked() { - app.right = Some(p); + app.choose_right_panel(Some(p)); } } }); @@ -278,7 +278,7 @@ pub fn right_rail(app: &mut PdfCraftApp, ui: &mut egui::Ui) { ui.painter().circle_filled(c, 3.0, t.accent); } if r.clicked() { - app.right = if selected { None } else { Some(panel) }; + app.choose_right_panel(if selected { None } else { Some(panel) }); } }; rail_button(ui, RightPanel::Comments, "message-square-text", "Comments", has_comments); diff --git a/crates/ui-egui/src/commands.rs b/crates/ui-egui/src/commands.rs index 6d3f97f9bd..126b7f319c 100644 --- a/crates/ui-egui/src/commands.rs +++ b/crates/ui-egui/src/commands.rs @@ -180,13 +180,13 @@ impl PdfCraftApp { "view.theme.system" => self.set_theme_preference(ThemePreference::System), "view.theme.light" => self.set_theme_preference(ThemePreference::Light), "view.theme.dark" => self.set_theme_preference(ThemePreference::Dark), - "comment.list" => self.right = Some(RightPanel::Comments), + "comment.list" => self.choose_right_panel(Some(RightPanel::Comments)), tool if crate::comments::CommentTool::from_command(tool).is_some() => { let Some(tool) = crate::comments::CommentTool::from_command(tool) else { return false }; self.comment_prefs.group_tool[tool.group()] = tool; self.quick_tool = crate::QuickTool::Comment(tool); - // Acrobat opens the Comments panel with the commenting tools. - if self.right.is_none() { + // Acrobat opens the Comments panel with the commenting tools, unless the user closed it. + if self.right.is_none() && !self.comments_panel_closed { self.right = Some(RightPanel::Comments); } // A text selection made before picking a markup tool is marked right away. diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index db6137b4e0..91f2edd884 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -356,6 +356,9 @@ pub struct PdfCraftApp { pub left: LeftPanel, pub left_open: bool, pub right: Option, + /// The user closed the Comments panel, so picking a comment tool leaves it closed until they + /// open it again (#225). Remembered across restarts. + pub comments_panel_closed: bool, pub quick_tool: QuickTool, /// The tool to go back to when Space, held for a temporary Hand, is released. space_hand: Option, @@ -602,6 +605,7 @@ impl PdfCraftApp { left: LeftPanel::AllTools, left_open: true, right: None, + comments_panel_closed: false, quick_tool: QuickTool::Select, space_hand: None, comment_prefs: Default::default(), @@ -1165,6 +1169,17 @@ impl PdfCraftApp { }; } + /// Opens a right panel, or closes it with `None`, because the user chose to. Closing Comments + /// keeps comment tools from reopening it; opening it again lets them (#225). + pub fn choose_right_panel(&mut self, panel: Option) { + if panel == Some(RightPanel::Comments) { + self.comments_panel_closed = false; + } else if panel.is_none() && self.right == Some(RightPanel::Comments) { + self.comments_panel_closed = true; + } + self.right = panel; + } + /// Serialize the user's persistent state (recent files, theme). Local only. pub fn persist(&self) -> String { let trusted: Vec = self.session.trusted_certificates().iter().map(pdfcraft_engine::sign::x509::to_pem).collect(); @@ -1190,6 +1205,7 @@ impl PdfCraftApp { "javascript": self.session.javascript(), "actions": actions_ui::encode(&self.custom_actions), "combine_columns": self.combine_columns.to_json(), + "comments_panel_closed": self.comments_panel_closed, }) .to_string() } @@ -1217,6 +1233,9 @@ impl PdfCraftApp { if let Some(on) = v["highlight_fields"].as_bool() { self.view_defaults.highlight_fields = on; } + if let Some(closed) = v["comments_panel_closed"].as_bool() { + self.comments_panel_closed = closed; + } if let Some(defaults) = v["default_zoom"].as_str().and_then(|zoom| self.view_defaults.with_zoom(zoom)) { self.view_defaults = defaults; } diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 5475947703..27ac404e8e 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -584,7 +584,7 @@ pub fn right_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) { }); } if close { - app.right = None; + app.choose_right_panel(None); } if let Some(e) = panel_edit { app.apply_edit(e); diff --git a/crates/ui-egui/tests/comments.rs b/crates/ui-egui/tests/comments.rs index 688a962ede..e596e74fcf 100644 --- a/crates/ui-egui/tests/comments.rs +++ b/crates/ui-egui/tests/comments.rs @@ -600,3 +600,43 @@ fn the_opacity_set_for_a_tool_goes_into_its_new_comments() { h.run_steps(2); h.get_by_label("Opacity"); } + +/// #225: comment tools open the Comments panel until the user closes it; then it stays closed, +/// across restarts, until they open it again. +#[test] +fn a_closed_comments_panel_stays_closed_when_picking_comment_tools() { + use pdfcraft_ui_egui::RightPanel; + let mut h = harness(|_| {}); + assert_eq!(h.state().right, None); + assert!(h.state_mut().execute("comment.highlight")); + h.run_steps(2); + assert_eq!(h.state().right, Some(RightPanel::Comments), "Acrobat opens Comments with the tools"); + h.get_by_label("Close").click(); + h.run_steps(2); + assert_eq!(h.state().right, None); + for tool in ["select", "comment.underline", "comment.note", "comment.highlight"] { + if tool == "select" { + h.state_mut().quick_tool = QuickTool::Select; + } else { + assert!(h.state_mut().execute(tool)); + } + h.run_steps(2); + assert_eq!(h.state().right, None, "{tool} reopened the closed Comments panel"); + } + let mut again = PdfCraftApp::new(); + again.restore(&h.state().persist()); + assert!(again.comments_panel_closed, "the choice survives a restart"); + // Opening it again from the rail lets the tools open it once more. + h.get_by_label("Comments").click(); + h.run_steps(2); + assert_eq!(h.state().right, Some(RightPanel::Comments)); + assert!(!h.state().comments_panel_closed); + // What the rail and View menu call; "Bookmarks" labels two controls on screen. + h.state_mut().choose_right_panel(Some(RightPanel::Bookmarks)); + h.state_mut().choose_right_panel(None); + h.run_steps(2); + assert!(!h.state().comments_panel_closed, "switching to and closing another panel isn't closing Comments"); + assert!(h.state_mut().execute("comment.strikeout")); + h.run_steps(2); + assert_eq!(h.state().right, Some(RightPanel::Comments)); +}