GraphiteEditor/Graphite · error

Artboard should have a primary input

Error message

Artboard should have a primary input

What it means

Panics when an `Artboard` node fetched from the document network has an empty `inputs` vec, so `.inputs.first()` returns `None`. The handler (`GraphOperationMessage::CreateArtboard` flow) assumes every artboard node carries at least a primary input; the template registered for `"Artboard"` in `DOCUMENT_NODE_TYPES` defines that input. The expect guards against definition drift where the Artboard node template is created or serialized with zero inputs.

Source

Thrown at editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs:206

				id,
				location,
				dimensions,
				background,
				clip,
			} => {
				let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);

				let artboard_layer = modify_inputs.create_artboard(id, location, dimensions, background, clip);
				network_interface.move_layer_to_stack(artboard_layer, LayerNodeIdentifier::ROOT_PARENT, 0, &[]);

				// If there is a non artboard feeding into the primary input of the artboard, move it to the secondary input
				let Some(artboard) = network_interface.document_network().nodes.get(&id) else {
					log::error!("Artboard not created");
					return;
				};
				let document_metadata = network_interface.document_metadata();

				let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
				if let NodeInput::Node { node_id, .. } = &primary_input {
					if network_interface.is_artboard(node_id, &[]) {
						// Nothing to do here: we have a stack full of artboards!
					} else if network_interface.is_layer(node_id, &[]) {
						// We have a stack of non-layer artboards.
						for (insert_index, layer) in LayerNodeIdentifier::ROOT_PARENT.children(document_metadata).filter(|&layer| layer != artboard_layer).enumerate() {
							// Parent the layer to our new artboard (retaining ordering)
							responses.add(NodeGraphMessage::MoveLayerToStack {
								layer,
								parent: artboard_layer,
								insert_index,
							});
							// Apply a translation to prevent the content from shifting
							responses.add(GraphOperationMessage::TransformChange {
								layer,
								transform: DAffine2::from_translation(-location),
								transform_in: TransformIn::Local,
								skip_rerender: true,

View on GitHub (pinned to c507b35645)

Solutions

  1. Check the `"Artboard"` definition in `document_node_definitions.rs` — its `node_template.inputs` must contain at least the primary `Artboard`-typed input; restore it if a refactor removed it.
  2. If loading user files, validate/repair the deserialized node before this handler runs (reject or re-template artboards with zero inputs).
  3. Replace `expect` with `let-else`: log `"Artboard has no primary input"` and return early so one bad node cannot crash the document.
  4. Add a test asserting `resolve_network_node_type("Artboard").node_template.inputs` is non-empty to catch registry drift at CI time.

Example fix

// before
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();

// after
let Some(primary_input) = artboard.inputs.first() else {
    log::error!("Artboard node {id} has no primary input; skipping input reshuffle");
    return;
};
let primary_input = primary_input.clone();
Defensive patterns

Strategy: validation

Validate before calling

// Before creating an artboard, verify the definition provides a primary input:
let has_primary_input = document_node_definitions::resolve_network_node_type("Artboard")
    .is_some_and(|def| !def.node_template.inputs.is_empty());
if !has_primary_input {
    log::error!("Artboard definition lacks a primary input; aborting artboard creation");
    return;
}

Type guard

fn node_has_primary_input(node: &DocumentNode) -> bool {
    !node.inputs.is_empty()
}

Try / catch

// Rust has no try/catch; convert the invariant into a checked branch:
match artboard.inputs.first() {
    Some(primary_input) => { /* existing logic */ }
    None => log::error!("Artboard {} has no primary input", id),
}

Prevention

When it happens

Trigger: Creating an artboard (or artboard-containing document import) triggers `create_artboard` + this handler. The panic fires if the node stored at `id` has no inputs: a hand-edited/corrupted `.graphite` file, a deserialization path that builds an Artboard node with no template inputs, or an Artboard `DocumentNodeDefinition` whose `node_template.inputs` was emptied during a registry refactor.

Common situations: Editing the Artboard definition's template and removing/reordering the primary input; loading old documents serialized against a different node schema; programmatic document construction that inserts an Artboard node via a raw template instead of `resolve_network_node_type("Artboard")`.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/e8747c14d9a52690. Report an issue: GitHub.