GraphiteEditor/Graphite · error
No anchor position for handle
Error message
No anchor position for handle
What it means
In the shape editor's opposite-handle pick logic, candidate handles are sorted by angle around the current handle's anchor, and the sort comparator calls point.get_anchor_position(&vector).expect("No anchor position for handle") (shape_editor.rs:1159). get_anchor_position (vector-types/src/vector/misc.rs:457) chains segment-domain lookups with point_domain.position_from_id, all Options — it returns None when the selected handle's SegmentId, or the anchor PointId it maps to, no longer resolves in the freshly computed VectorData. The expect panics whenever selection state references manipulators the current vector does not contain.
Source
Thrown at editor/src/messages/tool/common_functionality/shape_editor.rs:1159
if let ManipulatorPointId::Anchor(anchor) = point
&& vector.all_connected(anchor).count() > 2
{
continue;
}
// Here we take handles as the current handle and the most opposite non-colinear-handle
let is_handle_colinear = |handle: HandleId| -> bool { vector.colinear_manipulators.iter().any(|&handles| handles[0] == handle || handles[1] == handle) };
let other_handles = if matches!(point, ManipulatorPointId::Anchor(_)) {
point.get_handle_pair(&vector)
} else {
point.get_all_connected_handles(&vector).and_then(|handles| {
let mut non_colinear_handles = handles.iter().filter(|&handle| !is_handle_colinear(*handle)).clone().collect::<Vec<_>>();
// Sort these by angle from the current handle
non_colinear_handles.sort_by(|&handle_a, &handle_b| {
let anchor = point.get_anchor_position(&vector).expect("No anchor position for handle");
let orig_handle_pos = point.get_position(&vector).expect("No handle position");
let a_pos = handle_a.to_manipulator_point().get_position(&vector).expect("No handle position");
let b_pos = handle_b.to_manipulator_point().get_position(&vector).expect("No handle position");
let v_orig = (orig_handle_pos - anchor).normalize_or_zero();
let v_a = (a_pos - anchor).normalize_or_zero();
let v_b = (b_pos - anchor).normalize_or_zero();
let angle_a = v_orig.angle_to(v_a).abs();
let angle_b = v_orig.angle_to(v_b).abs();
// Sort by descending angle (180° is furthest)
angle_b.partial_cmp(&angle_a).unwrap_or(std::cmp::Ordering::Equal)
});
let current = match point {View on GitHub (pinned to c507b35645)
Solutions
- Filter candidates before sorting: retain only handles h where h.to_manipulator_point().get_position(&vector).is_some() and the current point's anchor resolves
- Replace the expects inside the comparator with a let-else returning std::cmp::Ordering::Equal so the comparator stays total and non-panicking on stale ids
- Revalidate selected_shape_state against compute_modified_vector output at interaction start and drop manipulator ids that no longer resolve
- Add a regression test: select a handle, delete its anchor via the graph, then drag — it must not panic
Example fix
// before (inside sort_by comparator)
let anchor = point.get_anchor_position(&vector).expect("No anchor position for handle");
let orig_handle_pos = point.get_position(&vector).expect("No handle position");
// after
let (Some(anchor), Some(orig_handle_pos)) = (point.get_anchor_position(&vector), point.get_position(&vector)) else {
return std::cmp::Ordering::Equal; // stale manipulator id: skip instead of panicking
}; Defensive patterns
Strategy: validation
Validate before calling
// Before sorting candidates, keep only resolvable ones:
non_colinear_handles.retain(|&h| h.to_manipulator_point().get_position(&vector).is_some());
if point.get_anchor_position(&vector).is_none() { return None; } // bail: current handle is stale Type guard
fn has_resolvable_geometry(point: &ManipulatorPointId, vector: &Vector) -> bool {
point.get_position(vector).is_some() && point.get_anchor_position(vector).is_some()
} Prevention
- Never call expect inside a sort_by comparator — comparators must be total and panic-free on any input
- Reconcile selected_shape_state against compute_modified_vector output at interaction start and drop ids that no longer resolve
- Cover undo-during-selection and mutate-then-drag sequences in tests for the path editor
When it happens
Trigger: Drag-selecting or dragging handles in the path editor on a layer whose vector changed after selected_shape_state was populated (undo, upstream node edit, layer restructure), or a selected handle whose segment start/end point is missing from point_domain, so get_all_connected_handles returns candidates the comparator cannot position.
Common situations: Undo or rapid tool switching while handles are selected; scripted test sequences that mutate the graph between selection and drag; degenerate geometry where points were deleted but the selection retained their ids; upstream generator nodes changing segment topology between frames.
Related errors
- No handle position
- Handle cannot be converted
- If `selection_shape` is a polygon then subpath is constructe
- Cannot find state for layer
- Solidify Stroke node should exist
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/487551eb03a341d4.
Report an issue: GitHub.