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:
Karim Jerbi committed 2026-09-30 19:15:49 +01:00
1 parent 9c4aa5f896
commit 2d6f4fcfdf
10 files changed
+178 -25

No files matched your search

+1 -1
View File
@@ -727,7 +727,7 @@ impl OpenCADStudio {
Some(crate::app::ModalKind::BlockDefinition) => Message::BlockDefApply,
_ => return Err(failure("no_dialog", "No dialog with an OK button is open")),
},
"close_document" => Message::TabClose(self.active_tab),
"close_document" => Message::TabClose(self.tabs[self.active_tab].id),
"toggle_properties" => Message::ToggleProperties,
"toggle_layers" => Message::ToggleLayers,
"toggle_grid" => Message::ToggleGrid,
+4 -1
View File
@@ -28,9 +28,12 @@ impl OpenCADStudio {
}
self.tabs[index].dirty = false;
let remaining = self.tabs.len();
// Resolve the stable id now; the message may be handled after other
// batched messages have shifted `index`.
let tab_id = self.tabs[index].id;
// The close may queue async work (autosave cleanup) — return the
// task so the runtime drives it.
let close_task = self.update(Message::TabClose(index));
let close_task = self.update(Message::TabClose(tab_id));
let closed_document = req["document_id"].as_u64();
self.set_control_result(json!({
"closed": true,