From a90a241ae11061cbed5c9693a04cce1439316c83 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 27 Jul 2026 14:18:32 +0200 Subject: [PATCH] Confirm session deletion --- src/app.rs | 34 +++++++++++++++++++++++++++++++--- src/app/view.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 605b45e..d347d36 100644 --- a/src/app.rs +++ b/src/app.rs @@ -65,6 +65,8 @@ pub(crate) struct App { drafts: HashMap, /// Session whose quick-actions menu is open. session_menu: Option, + /// Session waiting for explicit confirmation before deletion. + pending_session_delete: Option, /// Session being renamed, with the in-progress title. session_rename: Option<(i32, String)>, /// Projects whose archived sessions are expanded in the sidebar. @@ -255,7 +257,8 @@ pub(crate) enum Message { CreateSession(i32), DiscardSession(i32), SelectSession(i32, i32), - DeleteSession(i32), + RequestDeleteSession(i32), + ConfirmDeleteSession, OpenSessionMenu(i32), StartRenameSession(i32), SessionTitleChanged(String), @@ -327,6 +330,7 @@ impl App { selected_session: None, drafts, session_menu: None, + pending_session_delete: None, session_rename: None, expanded_archives: HashSet::new(), sidebar_drag: false, @@ -446,6 +450,7 @@ impl App { selected_session: None, drafts: HashMap::new(), session_menu: None, + pending_session_delete: None, session_rename: None, expanded_archives: HashSet::new(), sidebar_drag: false, @@ -556,7 +561,9 @@ impl App { } } Message::DismissPanel => { - if self.pending_a2ui_dismissal.is_some() { + if self.pending_session_delete.is_some() { + self.pending_session_delete = None; + } else if self.pending_a2ui_dismissal.is_some() { self.pending_a2ui_dismissal = None; } else if self.session_rename.is_some() || self.session_menu.is_some() { self.session_rename = None; @@ -1351,7 +1358,28 @@ impl App { } } } - Message::DeleteSession(session_id) => { + Message::RequestDeleteSession(session_id) => { + if self.generating && self.selected_session == Some(session_id) { + self.error = + Some("Stop the active generation before deleting its session.".into()); + return Task::none(); + } + if self + .projects + .iter() + .flat_map(|project| &project.sessions) + .any(|session| session.id == session_id) + { + self.pending_session_delete = Some(session_id); + self.session_menu = None; + self.session_rename = None; + self.error = None; + } + } + Message::ConfirmDeleteSession => { + let Some(session_id) = self.pending_session_delete.take() else { + return Task::none(); + }; if self.generating && self.selected_session == Some(session_id) { self.error = Some("Stop the active generation before deleting its session.".into()); diff --git a/src/app/view.rs b/src/app/view.rs index b505081..eacd859 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -70,6 +70,7 @@ impl App { pub(super) fn modal_open(&self) -> bool { self.preferences_open || self.pending_project_path.is_some() + || self.pending_session_delete.is_some() || self.session_rename.is_some() || self.menu_session().is_some() || self.pending_a2ui_dismissal.is_some() @@ -136,6 +137,8 @@ impl App { layers.push(self.project_dialog(path)); } else if let Some((_, title)) = &self.session_rename { layers.push(self.rename_dialog(title)); + } else if let Some(session) = self.pending_delete_session() { + layers.push(self.delete_session_panel(session)); } else if let Some(session) = self.menu_session() { layers.push(self.session_menu_panel(session)); } else if let Some(surface_id) = &self.pending_a2ui_dismissal { @@ -150,6 +153,8 @@ impl App { layers.push(self.project_dialog(path)); } else if let Some((_, title)) = &self.session_rename { layers.push(self.rename_dialog(title)); + } else if let Some(session) = self.pending_delete_session() { + layers.push(self.delete_session_panel(session)); } else if let Some(session) = self.menu_session() { layers.push(self.session_menu_panel(session)); } else if let Some(surface_id) = &self.pending_a2ui_dismissal { @@ -389,7 +394,7 @@ impl App { .on_press(Message::OpenSessionMenu(session.id)) .style(button::text), button(icon(ICON_TRASH, 14)) - .on_press(Message::DeleteSession(session.id)) + .on_press(Message::RequestDeleteSession(session.id)) .style(button::text), ] .spacing(3) @@ -508,12 +513,53 @@ impl App { fn menu_session(&self) -> Option<&Session> { let session_id = self.session_menu?; + self.session(session_id) + } + + fn pending_delete_session(&self) -> Option<&Session> { + let session_id = self.pending_session_delete?; + self.session(session_id) + } + + fn session(&self, session_id: i32) -> Option<&Session> { self.projects .iter() .flat_map(|project| &project.sessions) .find(|session| session.id == session_id) } + fn delete_session_panel<'a>(&self, session: &'a Session) -> Element<'a, Message> { + let dialog = container( + column![ + text("Delete session?").size(24), + text(format!( + "Delete “{}” and its conversation history? This cannot be undone.", + session.title + )) + .size(14), + row![ + Space::new().width(Length::Fill), + action_button("Cancel").on_press(Message::DismissPanel), + danger_button("Delete session").on_press(Message::ConfirmDeleteSession), + ] + .spacing(8), + ] + .spacing(12), + ) + .padding(22) + .width(460) + .style(overview_style); + + opaque( + container(dialog) + .center_x(Length::Fill) + .center_y(Length::Fill) + .style(|_| { + container::Style::default().background(Color::from_rgba8(0, 0, 0, 0.68)) + }), + ) + } + /// Quick actions for one session. Which lifecycle moves are offered depends /// on the state the session is in. fn session_menu_panel<'a>(&self, session: &'a Session) -> Element<'a, Message> {