{"record":{"id":"770ce0e1f89e72e5","repo":"GraphiteEditor/Graphite","slug":"ungrouped-folder-must-have-a-parent","errorCode":null,"errorMessage":"Ungrouped folder must have a parent","messagePattern":"Ungrouped folder must have a parent","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"editor/src/messages/portfolio/document/document_message_handler.rs","lineNumber":1556,"sourceCode":"\t\t\t\t\tif folder == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\tlog::error!(\"ROOT_PARENT cannot be selected when ungrouping selected layers\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Cannot ungroup artboard\n\t\t\t\t\tif self.network_interface.is_artboard(&folder.to_node(), &self.selection_network_path) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tresponses.add(DocumentMessage::UngroupLayer { layer: folder });\n\t\t\t\t}\n\n\t\t\t\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\t\t\t\tresponses.add(DocumentMessage::DocumentStructureChanged);\n\t\t\t\tresponses.add(NodeGraphMessage::SendGraph);\n\t\t\t}\n\t\t\tDocumentMessage::UngroupLayer { layer } => {\n\t\t\t\tlet parent = layer.parent(self.metadata()).expect(\"Ungrouped folder must have a parent\");\n\t\t\t\tlet folder_index = parent.children(self.metadata()).position(|child| child == layer).unwrap_or(0);\n\n\t\t\t\t// Move all children of the folder above the folder in reverse order since each children is moved above the previous one\n\t\t\t\tfor child in layer.children(self.metadata()).collect::<Vec<_>>().into_iter().rev() {\n\t\t\t\t\tresponses.add(NodeGraphMessage::MoveLayerToStack {\n\t\t\t\t\t\tlayer: child,\n\t\t\t\t\t\tparent,\n\t\t\t\t\t\tinsert_index: folder_index,\n\t\t\t\t\t});\n\n\t\t\t\t\tlet metadata = self.network_interface.document_metadata();\n\t\t\t\t\tlet layer_local_transform = metadata.transform_to_viewport(child);\n\t\t\t\t\tlet undo_parent_transform = if parent == LayerNodeIdentifier::ROOT_PARENT {\n\t\t\t\t\t\t// This is functionally the same as transform_to_viewport for the root, however to_node cannot run on the root in debug mode.\n\t\t\t\t\t\tmetadata.document_to_viewport.inverse()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmetadata.transform_to_viewport(parent).inverse()\n\t\t\t\t\t};","sourceCodeStart":1538,"sourceCodeEnd":1574,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/document_message_handler.rs#L1538-L1574","documentation":"Internal invariant panic in the editor's DocumentMessage::UngroupLayer handler: layer.parent(metadata) returns an Option that is None when the layer is the document root (which has no parent) or its parent cannot be resolved in the current metadata. Ungrouping logically requires a destination folder for the children, so a parentless layer breaks the operation's assumptions and aborts the handler.","triggerScenarios":"Dispatching UngroupLayer for LayerNodeIdentifier::ROOT or for a layer that was already deleted from the document; a stale LayerNodeIdentifier captured before a node graph edit (selection state desync between the UI and the document); scripts/plugins emitting the message with outdated ids.","commonSituations":"Races where the layers panel or a frontend still holds a selection from before the graph was restructured (deleted folder, reordered graph); double-processing of the same ungroup request after the first one removed the folder; extensions built against an older document structure.","solutions":["In the code that emits UngroupLayer, skip when layer == LayerNodeIdentifier::ROOT or layer.parent(metadata).is_none()","Refresh the selection/layer references from current metadata right before emitting the message","Guard the handler itself: replace the expect with an early return when no parent exists","If reproducing from a plugin, re-resolve layer ids from the latest document snapshot instead of caching them"],"exampleFix":"// before\nlet parent = layer.parent(self.metadata()).expect(\"Ungrouped folder must have a parent\");\n\n// after\nlet Some(parent) = layer.parent(self.metadata()) else {\n\ttracing::warn!(\"UngroupLayer ignored for parentless layer {layer:?}\");\n\treturn;\n};","handlingStrategy":"type-guard","validationCode":"// Before emitting UngroupLayer, confirm the layer is ungroupable against current metadata\nlet ungroupable = layer != LayerNodeIdentifier::ROOT && layer.parent(&metadata).is_some();\nif ungroupable {\n\tresponses.add(DocumentMessage::UngroupLayer { layer });\n}","typeGuard":"fn can_ungroup(layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {\n\tlayer != LayerNodeIdentifier::ROOT && layer.parent(metadata).is_some()\n}","tryCatchPattern":null,"preventionTips":["Never cache LayerNodeIdentifier values across document mutations; re-resolve from fresh metadata","Filter ROOT and parentless layers out of any batch operation before dispatching messages","Harden the handler with a let-else early return so a stale id logs a warning instead of crashing the editor"],"tags":["rust","editor","invariant","panic","layer-tree","message-dispatch"],"backgroundTag":"optional-parent-none","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}