GraphiteEditor/Graphite · error

In `check_layer()`: there should be a `target`

Error message

In `check_layer()`: there should be a `target`

What it means

Panics in `check_layer` when `self.parent_targets` is empty at `.last().expect(...)`. `parent_targets` is a traversal stack seeded by the click-xray entry point (`calculate_click` initializes it with `(ROOT_PARENT, target)` at document_message_handler.rs:3834) and pushed/popped while walking child layers (3863, 3968-3969). The expect encodes the invariant that `check_layer` is only ever called inside an active xray traversal where at least the root target entry exists.

Source

Thrown at editor/src/messages/portfolio/document/document_message_handler.rs:3879

			} else {
				// All child layers will use the new clipped target area
				self.parent_targets.push((layer, XRayTarget::Path(subtracted)));
			}
		}
		XRayResult { clicked, use_children }
	}

	/// Handles the checking of the layer to find if it has been clicked
	fn check_layer(&mut self, layer: LayerNodeIdentifier) -> XRayResult {
		let selected_layers = self.network_interface.selected_nodes();
		// Discard invisible and locked layers
		if !selected_layers.layer_visible(layer, self.network_interface) || selected_layers.layer_locked(layer, self.network_interface) {
			return XRayResult { clicked: false, use_children: false };
		}

		let click_targets = self.network_interface.document_metadata().click_targets(layer);
		let transform = self.network_interface.document_metadata().transform_to_document(layer);
		let target = &self.parent_targets.last().expect("In `check_layer()`: there should be a `target`").1;
		let clip = self.network_interface.document_metadata().is_clip(layer.to_node());

		match target {
			// Single points are much cheaper than paths so have their own special case
			XRayTarget::Point(point) => {
				let intersects = click_targets.is_some_and(|targets| targets.iter().any(|target| target.intersect_point(*point, transform)));
				XRayResult {
					clicked: intersects,
					use_children: !clip || intersects,
				}
			}
			XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_kurbo(*quad), transform),
			XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
			XRayTarget::Polygon(polygon) => {
				let polygon = BezPath::from_path_segments(polygon.iter_closed());
				self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
			}
		}

View on GitHub (pinned to c507b35645)

Solutions

  1. Trace the call path: ensure `check_layer` is only reached via the traversal that seeds `parent_targets` with `(LayerNodeIdentifier::ROOT_PARENT, target)` at line 3834.
  2. Audit the pop logic at lines 3968-3969 — the ancestor comparison must pop exactly the entries the walk pushed; fix the comparison if traversal order or layer identity changed.
  3. If you added a new entry point that hit-tests layers, route it through `calculate_click`/`calculate_click_x_y_layer` instead of calling `check_layer` directly.
  4. Replace the `expect` with a `let-else` that logs and returns `XRayResult { clicked: false, use_children: false }` so a bookkeeping bug cannot hard-crash the editor.

Example fix

// before
let target = &self.parent_targets.last().expect("In `check_layer()`: there should be a `target`").1;

// after
let Some((_, target)) = self.parent_targets.last() else {
    log::error!("check_layer called with empty parent_targets; no active xray traversal");
    return XRayResult { clicked: false, use_children: false };
};
Defensive patterns

Strategy: validation

Validate before calling

// Guard before hit-testing a layer:
if self.parent_targets.is_empty() {
    log::error!("check_layer invoked outside an xray traversal");
    return XRayResult { clicked: false, use_children: false };
}

Type guard

fn has_xray_target(parent_targets: &[(LayerNodeIdentifier, XRayTarget)]) -> bool {
    !parent_targets.is_empty()
}

Try / catch

// Rust has no try/catch; use catch_unwind only to isolate editor crashes:
let result = std::panic::catch_unwind(|| self.check_layer(layer))
);
let xray = result.unwrap_or(XRayResult { clicked: false, use_children: false });

Prevention

When it happens

Trigger: `check_layer` runs during click hit-testing (`XRayMessage`/`calculate_click` handling). The panic occurs when the stack is empty: a code path calls `check_layer` directly without starting a traversal, or push/pop bookkeeping over-pops (an ancestor-pop check at line 3968 mismatches the layer actually being exited, draining the seeded ROOT_PARENT entry).

Common situations: Adding a new message handler that invokes `check_layer` outside the standard traversal; refactoring the traversal so the initial `(ROOT_PARENT, target)` seed is skipped; a mismatch between the layer passed to `calculate_click`'s recursive walk and the ancestor comparison used for popping (e.g. after layer reparenting mid-traversal).

Related errors


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