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
- 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.
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
- 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.
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
- ROOT_PARENT should have at least one layer when clicking
- Cannot find connected anchor
- Node Id 0 should be a layer
- Star node can't be found
- Brush node does not exist
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/db3d7c4c80736517.
Report an issue: GitHub.