GraphiteEditor/Graphite · error
Node Id 0 should be a layer
Error message
Node Id 0 should be a layer
What it means
Alt-drag layer duplication copies the clicked layer's upstream subgraph with remapped ids: upstream_flow_back_from_nodes numbers nodes from the clicked layer (which becomes NodeId(0)), copy_nodes produces the templates, and new_ids maps each copied id to a fresh NodeId. The expect asserts that the copy set contains the root entry NodeId(0) — i.e., that the clicked layer node itself was copied. It panics when copy_nodes skipped the layer node (filtered as non-copyable) or the upstream flow enumeration excluded it, leaving the id map without the 0 key.
Source
Thrown at editor/src/messages/tool/tool_messages/select_tool.rs:633
let mut copy_ids = HashMap::new();
let node_id = layer.to_node();
copy_ids.insert(node_id, NodeId(0));
document
.network_interface
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)
.enumerate()
.for_each(|(index, node_id)| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
let nodes = document.network_interface.copy_nodes(©_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent);
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
let layer = LayerNodeIdentifier::new_unchecked(layer_id);
new_dragging.push(layer);
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
}
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.layers_dragging = new_dragging;
}
/// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated.
fn stop_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
let Some(original) = self.non_duplicated_layers.take() else {
return;
};
// Delete the duplicated layersView on GitHub (pinned to c507b35645)
Solutions
- Guard the lookup: use if let Some(&layer_id) = new_ids.get(&NodeId(0)) and skip the duplication (log a warning) when absent, instead of panicking mid-drag.
- Debug why the root was skipped: print copy_ids and the ids returned by copy_nodes for the clicked layer to find which filter drops NodeId(0).
- If a node class is now intentionally non-copyable, early-out in duplicate_layers... before building copy_ids when the layer's node type is not copyable.
- Check that the layer identifier passed in actually corresponds to a node in document.network_interface metadata (defend against desynced LayerNodeIdentifiers).
Example fix
// before
let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
// after
let Some(&layer_id) = new_ids.get(&NodeId(0)) else {
log::warn!("select tool: layer {:?} produced no copyable root node; skipping alt-drag duplication", layer);
continue;
}; Defensive patterns
Strategy: validation
Validate before calling
let Some(&layer_id) = new_ids.get(&NodeId(0)) else {
// root layer node was not copied; abort this layer's duplication safely
continue;
};
let layer = LayerNodeIdentifier::new_unchecked(layer_id); Prevention
- Validate that an upstream-flow copy produced the expected root entry before indexing remap maps by fixed ids.
- Test alt-drag duplication after any change to copy_nodes filters or upstream flow semantics.
- Defend incoming LayerNodeIdentifiers against metadata desync by checking existence before duplicating.
When it happens
Trigger: Holding Alt and starting to drag a layer: duplicate_layers_into_current_document builds copy_ids, calls document.network_interface.copy_nodes(©_ids, &[]), then new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer"); the get returns None when the copied Vec<(NodeId, NodeTemplate)> has no NodeId(0) entry.
Common situations: Layer node types made non-copyable during a node-graph refactor; metadata desync where the clicked layer's node is no longer in the flow computed by upstream_flow_back_from_nodes; dragging a synthetic layer (e.g., artboard wrappers or locked/impermanent nodes) whose node is excluded by copy filters; version drift between an old document and new copy semantics.
Related errors
- ROOT_PARENT should have at least one layer when clicking
- ROOT_PARENT should have a layer child when clicking
- Star node can't be found
- Brush node does not exist
- Path node does not exist
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/e402c8cc53fb9dfe.
Report an issue: GitHub.