{"record":{"id":"7e61a134700d05f3","repo":"GraphiteEditor/Graphite","slug":"parent-accessed-via-child-should-have-children","errorCode":null,"errorMessage":"Parent accessed via child should have children","messagePattern":"Parent accessed via child should have children","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/portfolio/document/utility_types/document_metadata.rs","lineNumber":362,"sourceCode":"\t/// Does the layer have children? If so, then it is a folder.\n\tpub fn has_children(self, metadata: &DocumentMetadata) -> bool {\n\t\tself.first_child(metadata).is_some()\n\t}\n\n\t/// Is the layer a child of the given layer?\n\tpub fn is_child_of(self, metadata: &DocumentMetadata, parent: &LayerNodeIdentifier) -> bool {\n\t\tparent.children(metadata).any(|child| child == self)\n\t}\n\n\t/// Is the layer an ancestor of the given layer?\n\tpub fn is_ancestor_of(self, metadata: &DocumentMetadata, child: &LayerNodeIdentifier) -> bool {\n\t\tchild.ancestors(metadata).any(|ancestor| ancestor == self)\n\t}\n\n\t/// Is the layer the last child of its stack? Used for clipping\n\tpub fn can_be_clipped(self, metadata: &DocumentMetadata) -> bool {\n\t\tself.parent(metadata)\n\t\t\t.is_some_and(|layer| layer.last_child(metadata).expect(\"Parent accessed via child should have children\") != self)\n\t}\n\n\t/// Iterator over all direct children (excluding self and recursive children)\n\tpub fn children(self, metadata: &DocumentMetadata) -> AxisIter<'_> {\n\t\tAxisIter {\n\t\t\tlayer_node: self.first_child(metadata),\n\t\t\tnext_node: Self::next_sibling,\n\t\t\tmetadata,\n\t\t}\n\t}\n\n\tpub fn downstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter<'_> {\n\t\tAxisIter {\n\t\t\tlayer_node: Some(self),\n\t\t\tnext_node: Self::previous_sibling,\n\t\t\tmetadata,\n\t\t}\n\t}","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/utility_types/document_metadata.rs#L344-L380","documentation":"can_be_clipped checks that a layer is NOT the last child of its stack (used to decide clipping). It does parent(metadata).is_some_and(|layer| layer.last_child(metadata).expect(...)). The expect encodes a DocumentMetadata invariant: if a node has a parent link, that parent must have child links (first_child/last_child). The panic fires when that invariant breaks - the parent's relations exist and point to a parent, but that parent's last_child is None, i.e. corrupted/half-updated layer tree metadata.","triggerScenarios":"Any structural graph edit (delete, move, reorder, undo/redo of layer operations) that writes parent pointers without maintaining the corresponding first_child/last_child links, or deserialized older documents whose metadata was not fully rebuilt.","commonSituations":"Undo of a layer deletion leaving a dangling parent relation; custom code calling NodeGraphMessage handlers out of order; bugs in metadata recomputation after loading migrated documents.","solutions":["Make the check total: layer.last_child(metadata) != Some(self) so a missing child link simply reports 'cannot clip' instead of panicking","Find the mutation path that broke the invariant (log parent/children state when it triggers) and fix its metadata bookkeeping","Add a debug-mode metadata consistency validator that walks parent<->child links after every structural message"],"exampleFix":"// before\nself.parent(metadata).is_some_and(|layer| layer.last_child(metadata).expect(\"Parent accessed via child should have children\") != self)\n\n// after\nself.parent(metadata).is_some_and(|layer| layer.last_child(metadata) != Some(self))","handlingStrategy":"validation","validationCode":"// total version of the check: missing child links simply mean 'cannot clip'\nlet clip_allowed = match self.parent(metadata) {\n\tSome(parent) => parent.last_child(metadata) != Some(self),\n\tNone => false,\n};","typeGuard":"fn parent_has_children(layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {\n\tlayer.first_child(metadata).is_some()\n}","tryCatchPattern":null,"preventionTips":["Never .expect inside tree walks; model missing links as 'condition false'","Maintain parent and child pointers together in every structural mutation (single helper that writes both)","Run a metadata consistency check (parent<->children bidirectionality) in debug builds after each graph message"],"tags":["rust","document-model","layer-tree","invariant","panic"],"backgroundTag":"layer-tree-metadata-corruption","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}