forked from 1b-agent/opencadstudio-upstream
fix(tabs): close by stable document id instead of vector index
Bug: Message::TabClose carried a vector index captured when the view message was built. Closing several tabs in one batch (Close All / Close Others with unsaved-changes dialogs, or a queue of continuation updates) let an earlier close shift the vector before a later TabClose(idx) was handled: self.tabs.remove(idx) either panicked (index past the end) or closed a different tab than the one the user confirmed. Reproduce: 1. Open several drawings and modify each so they all prompt on close. 2. Use Close All, or close two tabs in quick succession so both messages are handled in one batch. 3. Before: panic in remove(idx), or the wrong tab closes while the tab the user chose stays open (or its work is lost). 4. After: the chosen tab closes; an id already closed by an earlier message is a no-op. Fix: TabClose now carries the tab's stable u64 id, resolved to a vector position when the message is handled, not when it is built; PendingClose::Tab stores ids the same way. All construction sites pass tab.id (a raw usize index no longer type-checks), and DocTabCloseAll/DocTabCloseOthers build their queues from live ids at handling time. A missing id means the tab is already gone, so the handler returns Task::none().
This commit is contained in:
1 parent
9c4aa5f896
commit
2d6f4fcfdf
10 files changed
+178
-25
No files matched your search
@@ -200,8 +200,14 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
|
||||
pub(super) fn on_unsaved_dialog_discard(&mut self) -> Task<Message> {
|
||||
match self.pending_close.take() {
|
||||
Some(crate::app::PendingClose::Tab(idx)) => {
|
||||
Some(crate::app::PendingClose::Tab(tab_id)) => {
|
||||
let close_win = self.close_unsaved_dialog_window();
|
||||
// Resolve the id now: the tab may have closed while
|
||||
// the dialog was open (batched close, automation),
|
||||
// in which case there is nothing left to discard.
|
||||
let Some(idx) = self.tabs.iter().position(|t| t.id == tab_id) else {
|
||||
return Task::batch([close_win, self.continue_tab_close_queue()]);
|
||||
};
|
||||
// Discarded — drop this tab's autosave recovery copy.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let _ = std::fs::remove_file(self.autosave_target(idx));
|
||||
@@ -249,7 +255,17 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
return Task::none();
|
||||
};
|
||||
let (idx, continuation) = match pending {
|
||||
crate::app::PendingClose::Tab(idx) => {
|
||||
crate::app::PendingClose::Tab(tab_id) => {
|
||||
// The pending target is an id; resolve it against the
|
||||
// current tab list so a tab closed in the meantime can
|
||||
// neither panic the save path nor save the wrong file.
|
||||
let Some(idx) = self.tabs.iter().position(|t| t.id == tab_id) else {
|
||||
self.pending_close = None;
|
||||
return Task::batch([
|
||||
self.close_unsaved_dialog_window(),
|
||||
self.continue_tab_close_queue(),
|
||||
]);
|
||||
};
|
||||
(idx, crate::app::SaveContinuation::CloseTab)
|
||||
}
|
||||
crate::app::PendingClose::Quit => {
|
||||
@@ -298,8 +314,14 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
match self.pending_close.take() {
|
||||
Some(crate::app::PendingClose::Tab(idx)) => {
|
||||
self.pending_close = Some(crate::app::PendingClose::Tab(idx));
|
||||
Some(crate::app::PendingClose::Tab(tab_id)) => {
|
||||
let Some(idx) = self.tabs.iter().position(|t| t.id == tab_id) else {
|
||||
return Task::batch([
|
||||
self.close_unsaved_dialog_window(),
|
||||
self.continue_tab_close_queue(),
|
||||
]);
|
||||
};
|
||||
self.pending_close = Some(crate::app::PendingClose::Tab(tab_id));
|
||||
self.save_dialog_for_unsaved = true;
|
||||
let close = self.close_unsaved_dialog_window();
|
||||
let save = self.save_with_default_format(idx);
|
||||
|
||||
Reference in new issue
Block a user