{"record":{"id":"3419996c9b619fdc","repo":"GraphiteEditor/Graphite","slug":"brush-node-does-not-exist","errorCode":null,"errorMessage":"Brush node does not exist","messagePattern":"Brush node does not exist","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/tool/tool_messages/brush_tool.rs","lineNumber":465,"sourceCode":"\t\t\t\tHintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, \"Draw\")]),\n\t\t\t\tHintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], \"Shrink/Grow Brush\")]),\n\t\t\t]),\n\t\t\tBrushToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, \"\"), HintInfo::keys([Key::Escape], \"Cancel\").prepend_slash()])]),\n\t\t};\n\n\t\thint_data.send_layout(responses);\n\t}\n\n\tfn update_cursor(&self, responses: &mut VecDeque<Message>) {\n\t\tresponses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });\n\t}\n}\n\nfn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {\n\tresponses.add(DocumentMessage::DeselectAllLayers);\n\n\tlet brush_node = resolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER)\n\t\t.expect(\"Brush node does not exist\")\n\t\t.default_node_template();\n\n\tlet id = NodeId::new();\n\tresponses.add(GraphOperationMessage::NewCustomLayer {\n\t\tid,\n\t\tnodes: vec![(NodeId(0), brush_node)],\n\t\tparent: document.new_layer_parent(true),\n\t\tinsert_index: 0,\n\t});\n\tresponses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });\n\n\tLayerNodeIdentifier::new_unchecked(id)\n}\n","sourceCodeStart":447,"sourceCodeEnd":479,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/tool/tool_messages/brush_tool.rs#L447-L479","documentation":"new_brush_layer resolves the Brush proto node (graphene_std::brush::brush::brush::IDENTIFIER) in the DOCUMENT_NODE_TYPES static registry and panics on a miss. The brush node lives in a separate graphene_std::brush module tree, so this expect guards that the brush crate's node implementations were compiled into and registered by the editor build. It fires on the very first brush stroke in a session, because that is when the tool creates the hidden brush layer holding the brush node. Like all registry expects it signals a version/build mismatch between the call site and the registry, not bad user data.","triggerScenarios":"Starting the first brush stroke when no brush layer exists yet: the brush tool FSM calls new_brush_layer(), which runs resolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER).expect(\"Brush node does not exist\").default_node_template() before inserting the layer via NewCustomLayer.","commonSituations":"The brush node's IDENTIFIER or triple-nested brush::brush::brush module path was renamed in graphene_std; the brush node registration was dropped from document_node_definitions(); the brush feature is compiled out; a stale build mixes an old editor crate with new libraries.","solutions":["Confirm the exact identifier: print resolve keys via collect_node_types() and compare against graphene_std::brush::brush::brush::IDENTIFIER.","If the module was restructured (the brush::brush::brush path is fragile), update the import at brush_tool.rs to the new path and keep referencing the IDENTIFIER constant, never a literal string.","If the node was removed from the registry intentionally, gate the brush tool's availability on the lookup succeeding instead of expecting.","Clean rebuild (cargo clean + build) after moving nodes between crates so the registry macro inputs are regenerated."],"exampleFix":"// before\nlet brush_node = resolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER)\n\t.expect(\"Brush node does not exist\")\n\t.default_node_template();\n\n// after\nlet Some(brush_def) = resolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER) else {\n\tlog::error!(\"Brush proto node not registered; cannot create brush layer\");\n\treturn LayerNodeIdentifier::ROOT_PARENT;\n};\nlet brush_node = brush_def.default_node_template();","handlingStrategy":"validation","validationCode":"fn brush_node_registered() -> bool {\n\tresolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER).is_some()\n}\n\n// before starting a brush session:\nif !brush_node_registered() {\n\t// disable brush tool / show error instead of expecting\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the brush node registration and the brush::brush::brush import path in the same module-ownership review set.","Smoke-test the first brush stroke after touching graphene_std::brush or the node registry.","Gate the brush tool's availability on the registry lookup result so a missing node disables the tool rather than crashing."],"tags":["rust","graphite-editor","node-registry","expect-panic","brush-tool","proto-node"],"backgroundTag":"static-registry-lookup-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}