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
- 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.
- 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.
- 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.
- 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
- Only enter check_layer through the traversal that seeds parent_targets with (ROOT_PARENT, target)
- Keep the debug_assert that parent_targets is empty after traversal completes (line 3978) enabled in CI
- When modifying the pop logic, assert stack depth matches traversal depth each frame
- Never call check_layer from new message handlers; route through calculate_click instead
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
- Handle cannot be converted
- If `selection_shape` is a polygon then subpath is constructe
- Cannot find state for layer
- Solidify Stroke node should exist
- Artboard should have a primary input
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/001a209142853ddb.
Report an issue: GitHub.