GraphiteEditor/Graphite · critical
Failed to create transform node
Error message
Failed to create transform node
What it means
Same registry mechanism as the Spline case: resolve_proto_node_type() (document_node_definitions.rs:1480) looks up graphene_std::transform_nodes::transform::IDENTIFIER in the static DOCUMENT_NODE_TYPES map and returns None if the definition is absent. This .expect() (graph_modification_utils.rs:149-151) runs unconditionally at the end of the layer-merge helper to insert the Transform node that anchors future tool modifications, so a missing registration panics every merge, not just spline merges.
Source
Thrown at editor/src/messages/tool/common_functionality/graph_modification_utils.rs:150
if current_and_other_layer_is_spline {
let spline_node_id = NodeId::new();
let spline_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::spline::IDENTIFIER)
.expect("Failed to create Spline node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: spline_node_id,
node_template: Box::new(spline_node),
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: spline_node_id,
parent: first_layer,
});
}
// Add a transform node to ensure correct tooling modifications
let transform_node_id = NodeId::new();
let transform_node = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
.expect("Failed to create transform node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: transform_node_id,
node_template: Box::new(transform_node),
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: transform_node_id,
parent: first_layer,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(DeferMessage::AfterGraphRun {
messages: vec![PenToolMessage::RecalculateLatestPointsPosition.into()],
});
}
/// Merge the `first_endpoint` with `second_endpoint`.
pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {View on GitHub (pinned to c507b35645)
Solutions
- Verify resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) is Some by checking the registration entry in document_node_definitions.rs matches the const exactly
- Restore or rename the registration so the identifier and the const agree, then rebuild
- Convert the expect to let-else with an error log so the merge can proceed without the transform node (or abort cleanly) instead of unwinding
- Cover the whole merge chain (Combine Paths, Path, Spline, Transform) in one registry-drift unit test so a single edit cannot break them silently
Example fix
// before
let transform_node = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
.expect("Failed to create transform node")
.default_node_template();
// after
let Some(transform_node) = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) else {
log::error!("Transform node missing from DOCUMENT_NODE_TYPES; aborting merge");
return;
};
let transform_node = transform_node.default_node_template(); Defensive patterns
Strategy: validation
Validate before calling
#[test]
fn merge_chain_nodes_registered() {
for id in [graphene_std::vector::combine_paths::IDENTIFIER, graphene_std::vector::spline::IDENTIFIER, graphene_std::transform_nodes::transform::IDENTIFIER] {
assert!(document_node_definitions::resolve_proto_node_type(id).is_some(), "DOCUMENT_NODE_TYPES missing entry for {id:?}");
}
} Prevention
- Run the registry tests after any change to document_node_definitions.rs
- Keep tool-side IDENTIFIER references in one shared location per node instead of scattering them
- Prefer let-else over expect for lookups whose failure is environmental (registration drift), reserving expect for true logic invariants
When it happens
Trigger: Any layer-merge operation (tool flow reaching new_merged_layer_flow) when the Transform proto node identifier has no DOCUMENT_NODE_TYPES entry — renamed const, deleted or relocated transform implementation, or an edited registration list.
Common situations: Renaming the transform node during a graph refactor while tool code keeps the old IDENTIFIER; upgrading between Graphite revisions where Transform changed namespace (graphene_std::transform_nodes); building with feature flags that exclude the transform module.
Related errors
- Failed to create Spline node
- Solidify Stroke node should exist
- Merge node
- Node
- Boolean node does not exist
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/2777939fb27765d1.
Report an issue: GitHub.