{"record":{"id":"905d43ac4b89957a","repo":"GraphiteEditor/Graphite","slug":"custom-node-should-exist","errorCode":null,"errorMessage":"Custom Node should exist","messagePattern":"Custom Node should exist","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs","lineNumber":668,"sourceCode":"\t\t\t\t\t\t// The output gets connected to all the previous inputs the node was connected to\n\t\t\t\t\t\tlet mut connect_output_to = Vec::new();\n\t\t\t\t\t\tfor downstream_connection in downstream_connections {\n\t\t\t\t\t\t\tif downstream_connection.node_id().is_some_and(|downstream_node_id| selected_node_ids.contains(&downstream_node_id)) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconnect_output_to.push(downstream_connection);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif !connect_output_to.is_empty() {\n\t\t\t\t\t\t\t// Every output connected to some non selected node forms a new export\n\t\t\t\t\t\t\texport_connections.push(current_output_connector);\n\t\t\t\t\t\t\toutput_connections.push(connect_output_to);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Use the network interface to add a default node, then set the imports, exports, paste the nodes inside, and connect them to the imports/exports\n\t\t\t\tlet encapsulating_node_id = NodeId::new();\n\t\t\t\tlet mut default_node_template = resolve_network_node_type(\"Custom Node\").expect(\"Custom Node should exist\").default_node_template();\n\t\t\t\tlet Some(center_of_selected_nodes) = network_interface.selected_nodes_bounding_box(breadcrumb_network_path).map(|[a, b]| (a + b) / 2.) else {\n\t\t\t\t\tlog::error!(\"Could not get center of selected_nodes\");\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tlet center_of_selected_nodes_grid_space = IVec2::new((center_of_selected_nodes.x / 24. + 0.5).floor() as i32, (center_of_selected_nodes.y / 24. + 0.5).floor() as i32);\n\t\t\t\tdefault_node_template.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1));\n\t\t\t\tresponses.add(DocumentMessage::AddTransaction);\n\t\t\t\tresponses.add(NodeGraphMessage::InsertNode {\n\t\t\t\t\tnode_id: encapsulating_node_id,\n\t\t\t\t\tnode_template: Box::new(default_node_template),\n\t\t\t\t});\n\t\t\t\tresponses.add(NodeGraphMessage::SetDisplayNameImpl {\n\t\t\t\t\tnode_id: encapsulating_node_id,\n\t\t\t\t\tnetwork_path: selection_network_path.to_vec(),\n\t\t\t\t\talias: \"Untitled Node\".to_string(),\n\t\t\t\t});\n\n\t\t\t\tresponses.add(DocumentMessage::EnterNestedNetwork { node_id: encapsulating_node_id });","sourceCodeStart":650,"sourceCodeEnd":686,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs#L650-L686","documentation":"resolve_network_node_type(\"Custom Node\") resolves a network (structural) node type from the registry by display-name string (document_node_definitions.rs returns Option<&DocumentNodeDefinition>) and the .expect panics when no entry has exactly that name. This code runs during encapsulation — grouping the selected nodes into a newly inserted Custom Node and wiring imports/exports — so a missing or renamed entry aborts the operation mid-message, after DocumentMessage::AddTransaction was already queued.","triggerScenarios":"Selecting nodes and invoking group-into-custom-node when the network node registry has no type whose display name is exactly 'Custom Node': the display name was renamed or localized, the registration list was edited in a fork, or the registry is not yet populated when the message is handled.","commonSituations":"Renaming the Custom Node's display name without updating this string literal; localization changing the effective lookup key; forks that drop custom-node support but leave the menu action reachable.","solutions":["Check the network node registration list and confirm an entry with display name exactly 'Custom Node' exists and matches this literal byte-for-byte.","Replace the string lookup with a stable identifier/constant so display-name renames cannot silently break resolution.","Degrade the call site: let Some(template) = resolve_network_node_type(\"Custom Node\") else { log::error!(..); return; } before queuing AddTransaction.","Add a startup assertion that every string-resolved network type referenced by the editor actually resolves."],"exampleFix":"// before\nlet mut default_node_template = resolve_network_node_type(\"Custom Node\").expect(\"Custom Node should exist\").default_node_template();\n\n// after\nlet Some(mut default_node_template) = resolve_network_node_type(\"Custom Node\").map(|node_type| node_type.default_node_template()) else {\n\tlog::error!(\"Custom Node network type not registered\");\n\treturn;\n};","handlingStrategy":"validation","validationCode":"fn can_encapsulate_selection() -> bool {\n\tresolve_network_node_type(\"Custom Node\").is_some()\n}\n// gate the 'group into node' menu action on this before dispatching the message","typeGuard":"fn network_type_available(name: &str) -> bool {\n\tresolve_network_node_type(name).is_some()\n}","tryCatchPattern":null,"preventionTips":["Prefer stable identifiers over display-name strings for registry lookups","Add a startup check that every string-resolved network type used by the editor exists","When renaming a node's display name, grep for its string literal in lookups first"],"tags":["rust","graphite","node-registry","string-literal","panic","encapsulate"],"backgroundTag":"node-registry-lookup-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}