{"record":{"id":"e8872361c37e46b7","repo":"a-b-street/abstreet","slug":"can-t-delete-mid-drag","errorCode":null,"errorMessage":"Can't delete {:?} mid-drag","messagePattern":"Can't delete (.+?) mid-drag","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"widgetry/src/mapspace/world.rs","lineNumber":422,"sourceCode":"    pub fn delete_before_replacement(&mut self, id: ID) {\n        if self.objects.remove(&id).is_some() {\n            if self.quadtree.remove(id).is_none() {\n                // This can happen for objects that're out-of-bounds. One example is intersections\n                // in map_editor.\n                warn!(\"{:?} wasn't in the quadtree\", id);\n            }\n        } else {\n            panic!(\"Can't delete {:?}; it's not in the World\", id);\n        }\n    }\n\n    /// Like delete, but doesn't crash if the object doesn't exist\n    pub fn maybe_delete(&mut self, id: ID) {\n        if self.hovering == Some(id) {\n            self.hovering = None;\n            self.draw_hovering = None;\n            if self.dragging_from.is_some() {\n                panic!(\"Can't delete {:?} mid-drag\", id);\n            }\n        }\n\n        if self.objects.remove(&id).is_some() {\n            if self.quadtree.remove(id).is_none() {\n                // This can happen for objects that're out-of-bounds. One example is intersections\n                // in map_editor.\n                warn!(\"{:?} wasn't in the quadtree\", id);\n            }\n        }\n    }\n\n    /// After adding all objects to a `World`, call this to initially detect if the cursor is\n    /// hovering on an object. This may also be called after adding or deleting objects to\n    /// immediately recalculate hover before the mouse moves.\n    // TODO Maybe we should automatically do this after mutations? Except we don't want to in the\n    // middle of a bulk operation, like initial setup or a many-step mutation. So maybe the caller\n    // really should handle it.","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/widgetry/src/mapspace/world.rs#L404-L440","documentation":"World::maybe_delete panics if asked to delete the object currently being dragged. Deleting mid-drag would leave the drag state (dragging_from) pointing at a removed object, breaking internal invariants, so even the tolerant delete refuses in that case.","triggerScenarios":"Calling maybe_delete(id) while that same ID is hovered AND a drag is in progress (dragging_from is Some) — e.g. deleting an object from a keyboard/timer event during an active mouse drag.","commonSituations":"Key handlers or async events removing objects while the user still holds the mouse button on them; test harnesses simulating drag+delete concurrently.","solutions":["Defer the delete until the drag finishes (end drag handler performs the delete).","Cancel/end the drag state before calling maybe_delete.","Check world's dragging state (if exposed) before deleting a hovered object."],"exampleFix":"// before\nworld.maybe_delete(hovered_id);\n// after\nif !world.is_dragging() { world.maybe_delete(hovered_id); } else { pending_deletes.push(hovered_id); }","handlingStrategy":"validation","validationCode":"// Defer deletes during active drags\nif drag_in_progress && hovered == Some(id) { defer_delete(id); } else { world.maybe_delete(id); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Queue deletions triggered by keyboard/timers until mouse-up","Never mutate the World's object set from inside drag handlers","Document that hovered+dragging objects must not be deleted"],"tags":["rust","panic","drag","widgetry"],"backgroundTag":"invalid-state-transition","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}