GraphiteEditor/Graphite · critical
Ungrouped folder must have a parent
Error message
Ungrouped folder must have a parent
What it means
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.
Source
Thrown at editor/src/messages/portfolio/document/document_message_handler.rs:1556
if folder == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be selected when ungrouping selected layers");
continue;
}
// Cannot ungroup artboard
if self.network_interface.is_artboard(&folder.to_node(), &self.selection_network_path) {
return;
}
responses.add(DocumentMessage::UngroupLayer { layer: folder });
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph);
}
DocumentMessage::UngroupLayer { layer } => {
let parent = layer.parent(self.metadata()).expect("Ungrouped folder must have a parent");
let folder_index = parent.children(self.metadata()).position(|child| child == layer).unwrap_or(0);
// Move all children of the folder above the folder in reverse order since each children is moved above the previous one
for child in layer.children(self.metadata()).collect::<Vec<_>>().into_iter().rev() {
responses.add(NodeGraphMessage::MoveLayerToStack {
layer: child,
parent,
insert_index: folder_index,
});
let metadata = self.network_interface.document_metadata();
let layer_local_transform = metadata.transform_to_viewport(child);
let undo_parent_transform = if parent == LayerNodeIdentifier::ROOT_PARENT {
// This is functionally the same as transform_to_viewport for the root, however to_node cannot run on the root in debug mode.
metadata.document_to_viewport.inverse()
} else {
metadata.transform_to_viewport(parent).inverse()
};View on GitHub (pinned to c507b35645)
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
Example fix
// before
let parent = layer.parent(self.metadata()).expect("Ungrouped folder must have a parent");
// after
let Some(parent) = layer.parent(self.metadata()) else {
tracing::warn!("UngroupLayer ignored for parentless layer {layer:?}");
return;
}; Defensive patterns
Strategy: type-guard
Validate before calling
// Before emitting UngroupLayer, confirm the layer is ungroupable against current metadata
let ungroupable = layer != LayerNodeIdentifier::ROOT && layer.parent(&metadata).is_some();
if ungroupable {
responses.add(DocumentMessage::UngroupLayer { layer });
} Type guard
fn can_ungroup(layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
layer != LayerNodeIdentifier::ROOT && layer.parent(metadata).is_some()
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Parent accessed via child should have children
- Failed to create artboard node
- In `check_layer()`: there should be a `target`
- Path should not be empty
- Handle cannot be converted
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/770ce0e1f89e72e5.
Report an issue: GitHub.