{"record":{"id":"2777939fb27765d1","repo":"GraphiteEditor/Graphite","slug":"failed-to-create-transform-node","errorCode":null,"errorMessage":"Failed to create transform node","messagePattern":"Failed to create transform node","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"editor/src/messages/tool/common_functionality/graph_modification_utils.rs","lineNumber":150,"sourceCode":"\tif current_and_other_layer_is_spline {\n\t\tlet spline_node_id = NodeId::new();\n\t\tlet spline_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::spline::IDENTIFIER)\n\t\t\t.expect(\"Failed to create Spline node\")\n\t\t\t.default_node_template();\n\t\tresponses.add(NodeGraphMessage::InsertNode {\n\t\t\tnode_id: spline_node_id,\n\t\t\tnode_template: Box::new(spline_node),\n\t\t});\n\t\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\t\tnode_id: spline_node_id,\n\t\t\tparent: first_layer,\n\t\t});\n\t}\n\n\t// Add a transform node to ensure correct tooling modifications\n\tlet transform_node_id = NodeId::new();\n\tlet transform_node = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)\n\t\t.expect(\"Failed to create transform node\")\n\t\t.default_node_template();\n\tresponses.add(NodeGraphMessage::InsertNode {\n\t\tnode_id: transform_node_id,\n\t\tnode_template: Box::new(transform_node),\n\t});\n\tresponses.add(NodeGraphMessage::MoveNodeToChainStart {\n\t\tnode_id: transform_node_id,\n\t\tparent: first_layer,\n\t});\n\n\tresponses.add(NodeGraphMessage::RunDocumentGraph);\n\tresponses.add(DeferMessage::AfterGraphRun {\n\t\tmessages: vec![PenToolMessage::RecalculateLatestPointsPosition.into()],\n\t});\n}\n\n/// Merge the `first_endpoint` with `second_endpoint`.\npub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/tool/common_functionality/graph_modification_utils.rs#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet transform_node = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)\n\t.expect(\"Failed to create transform node\")\n\t.default_node_template();\n// after\nlet Some(transform_node) = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) else {\n\tlog::error!(\"Transform node missing from DOCUMENT_NODE_TYPES; aborting merge\");\n\treturn;\n};\nlet transform_node = transform_node.default_node_template();","handlingStrategy":"validation","validationCode":"#[test]\nfn merge_chain_nodes_registered() {\n\tfor id in [graphene_std::vector::combine_paths::IDENTIFIER, graphene_std::vector::spline::IDENTIFIER, graphene_std::transform_nodes::transform::IDENTIFIER] {\n\t\tassert!(document_node_definitions::resolve_proto_node_type(id).is_some(), \"DOCUMENT_NODE_TYPES missing entry for {id:?}\");\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","graphite","panic","expect","node-registry","layer-merge","transform"],"backgroundTag":"node-definition-not-found","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}