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

  1. In the code that emits UngroupLayer, skip when layer == LayerNodeIdentifier::ROOT or layer.parent(metadata).is_none()
  2. Refresh the selection/layer references from current metadata right before emitting the message
  3. Guard the handler itself: replace the expect with an early return when no parent exists
  4. 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

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


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/770ce0e1f89e72e5. Report an issue: GitHub.