GraphiteEditor/Graphite · error

ROOT_PARENT should have a layer child when clicking

Error message

ROOT_PARENT should have a layer child when clicking

What it means

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.

Source

Thrown at editor/src/messages/tool/tool_messages/select_tool.rs:2030

					.then_some(least_common_ancestor)
					.or_else(|| common_siblings.iter().find(|&&child| clicked_layer == child || child.is_ancestor_of(metadata, &clicked_layer)).copied())
			})
	});

	if final_selection.is_some_and(|layer| selected_layers.iter().any(|selected| layer.is_child_of(metadata, selected))) {
		return None;
	}

	let new_selected = final_selection.unwrap_or_else(|| clicked_layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(clicked_layer));
	Some(new_selected)
}

fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler, remove: bool) {
	let layer = document.find_deepest(&selected).unwrap_or(
		LayerNodeIdentifier::ROOT_PARENT
			.children(document.metadata())
			.next()
			.expect("ROOT_PARENT should have a layer child when clicking"),
	);

	if !remove {
		// Duplicates cause `SelectedNodesSet` to carry the layer twice, breaking the Data panel's single-selection check in `node_to_inspect`
		if !tool_data.layers_dragging.contains(&layer) {
			tool_data.layers_dragging.push(layer);
		}
	} else {
		tool_data.layers_dragging.retain(|&selected_layer| layer != selected_layer);
	}
	responses.add(NodeGraphMessage::SelectedNodesSet {
		nodes: tool_data
			.layers_dragging
			.iter()
			.filter_map(|layer| {
				if *layer != LayerNodeIdentifier::ROOT_PARENT {
					Some(layer.to_node())
				} else {

View on GitHub (pinned to c507b35645)

Solutions

  1. Convert the expect to an Option chain (find_deepest(...).or_else(...)) and early-return with a warning when no target layer exists.
  2. Validate the selected ids against document.metadata() at drag start and prune dead layers from selection state.
  3. Clear or refresh tool_data.layers_dragging whenever structural changes (deletion, undo) mutate the layer tree.
  4. Add a regression test: select a layer, undo its creation, then send the drag-start event.

Example fix

// before
let layer = document.find_deepest(&selected).unwrap_or(
	LayerNodeIdentifier::ROOT_PARENT
		.children(document.metadata())
		.next()
		.expect("ROOT_PARENT should have a layer child when clicking"),
);

// after
let Some(layer) = document.find_deepest(&selected).or_else(|| LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next()) else {
	log::warn!("select tool: no resolvable layer for deepest manipulation; skipping");
	return;
};
Defensive patterns

Strategy: validation

Validate before calling

let Some(layer) = document
	.find_deepest(&selected)
	.or_else(|| LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next())
else {
	// empty document or dead selection ids: nothing to manipulate
	return;
};

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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