{"record":{"id":"db3d7c4c80736517","repo":"GraphiteEditor/Graphite","slug":"root-parent-should-have-a-layer-child-when-clickin","errorCode":null,"errorMessage":"ROOT_PARENT should have a layer child when clicking","messagePattern":"ROOT_PARENT should have a layer child when clicking","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/tool/tool_messages/select_tool.rs","lineNumber":2030,"sourceCode":"\t\t\t\t\t.then_some(least_common_ancestor)\n\t\t\t\t\t.or_else(|| common_siblings.iter().find(|&&child| clicked_layer == child || child.is_ancestor_of(metadata, &clicked_layer)).copied())\n\t\t\t})\n\t});\n\n\tif final_selection.is_some_and(|layer| selected_layers.iter().any(|selected| layer.is_child_of(metadata, selected))) {\n\t\treturn None;\n\t}\n\n\tlet new_selected = final_selection.unwrap_or_else(|| clicked_layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(clicked_layer));\n\tSome(new_selected)\n}\n\nfn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler, remove: bool) {\n\tlet layer = document.find_deepest(&selected).unwrap_or(\n\t\tLayerNodeIdentifier::ROOT_PARENT\n\t\t\t.children(document.metadata())\n\t\t\t.next()\n\t\t\t.expect(\"ROOT_PARENT should have a layer child when clicking\"),\n\t);\n\n\tif !remove {\n\t\t// Duplicates cause `SelectedNodesSet` to carry the layer twice, breaking the Data panel's single-selection check in `node_to_inspect`\n\t\tif !tool_data.layers_dragging.contains(&layer) {\n\t\t\ttool_data.layers_dragging.push(layer);\n\t\t}\n\t} else {\n\t\ttool_data.layers_dragging.retain(|&selected_layer| layer != selected_layer);\n\t}\n\tresponses.add(NodeGraphMessage::SelectedNodesSet {\n\t\tnodes: tool_data\n\t\t\t.layers_dragging\n\t\t\t.iter()\n\t\t\t.filter_map(|layer| {\n\t\t\t\tif *layer != LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\tSome(layer.to_node())\n\t\t\t\t} else {","sourceCodeStart":2012,"sourceCodeEnd":2048,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/tool/tool_messages/select_tool.rs#L2012-L2048","documentation":"drag_deepest_manipulation mirrors its shallow sibling: it resolves the drag target via find_deepest, falling back to ROOT_PARENT's first child, and expects that fallback to exist. The panic means the selected layers could not be resolved to any layer in the current document metadata and the document root has no children at all. Because the caller only invokes this with a non-empty selection, hitting it implies the selection references layers that no longer exist while the document is empty — a selection/metadata desync rather than a normal user action.","triggerScenarios":"Entering a deepest-manipulation drag with selected layers that find_deepest cannot resolve (ids stale after deletion/undo) in a document whose root child list is empty, so ROOT_PARENT.children(metadata()).next().expect(\"ROOT_PARENT should have a layer child when clicking\") panics.","commonSituations":"Undo restores an empty document while the select tool still tracks removed layers; programmatic selections (tests, scripting bridges) pointing at nonexistent layers; races between layer deletion messages and the drag-start handler reading old metadata.","solutions":["Convert the expect to an Option chain (find_deepest(...).or_else(...)) and early-return with a warning when no target layer exists.","Validate the selected ids against document.metadata() at drag start and prune dead layers from selection state.","Clear or refresh tool_data.layers_dragging whenever structural changes (deletion, undo) mutate the layer tree.","Add a regression test: select a layer, undo its creation, then send the drag-start event."],"exampleFix":"// before\nlet layer = document.find_deepest(&selected).unwrap_or(\n\tLayerNodeIdentifier::ROOT_PARENT\n\t\t.children(document.metadata())\n\t\t.next()\n\t\t.expect(\"ROOT_PARENT should have a layer child when clicking\"),\n);\n\n// after\nlet Some(layer) = document.find_deepest(&selected).or_else(|| LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next()) else {\n\tlog::warn!(\"select tool: no resolvable layer for deepest manipulation; skipping\");\n\treturn;\n};","handlingStrategy":"validation","validationCode":"let Some(layer) = document\n\t.find_deepest(&selected)\n\t.or_else(|| LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next())\nelse {\n\t// empty document or dead selection ids: nothing to manipulate\n\treturn;\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Apply the same Option-chain fallback pattern to every unwrap_or + expect pair over document children.","Clear layers_dragging and related tool state when layers are deleted or restored via undo.","Script test: select layer -> undo creation -> drag start; must not panic."],"tags":["rust","graphite-editor","select-tool","empty-collection","invariant-violation","expect-panic","document-metadata"],"backgroundTag":"empty-collection-invariant","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}