GraphiteEditor/Graphite · error

No handle position

Error message

No handle position

What it means

The same comparator as error 122 also unwraps the current handle's own position with point.get_position(&vector).expect("No handle position") (shape_editor.rs:1162). get_position (vector-types/src/vector/misc.rs:449) resolves PrimaryHandle/EndHandle via vector.segment_from_id(id).and_then(|bezier| bezier.handle_start()/handle_end()) — it is None when the SegmentId is gone from segment_domain or the bezier reports no distinct handle for that end. Because the comparator runs for every pair of candidates, one stale or handle-less segment among the selection is enough to panic.

Source

Thrown at editor/src/messages/tool/common_functionality/shape_editor.rs:1162

					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 {
							ManipulatorPointId::EndHandle(segment) => HandleId::end(segment),
							ManipulatorPointId::PrimaryHandle(segment) => HandleId::primary(segment),
							ManipulatorPointId::Anchor(_) => unreachable!(),

View on GitHub (pinned to c507b35645)

Solutions

  1. Pre-filter non_colinear_handles (and bail out early if the current point's own position is None) before entering sort_by
  2. Use a let-else returning Ordering::Equal for missing positions so stale ids are skipped, not fatal
  3. Refresh or prune selection state from compute_modified_vector before the interaction that triggers the sort

Example fix

// before
let orig_handle_pos = point.get_position(&vector).expect("No handle position");
// after
let Some(orig_handle_pos) = point.get_position(&vector) else {
	return std::cmp::Ordering::Equal; // no distinct handle position; keep current order
};
Defensive patterns

Strategy: validation

Validate before calling

// Early-exit when the dragged handle itself cannot be positioned:
let (Some(_anchor), Some(orig_handle_pos)) = (point.get_anchor_position(&vector), point.get_position(&vector)) else {
	return None; // stale or handle-less segment; skip opposite-handle logic
};

Type guard

fn is_positionable(point: &ManipulatorPointId, vector: &Vector) -> bool {
	point.get_position(vector).is_some()
}

Prevention

When it happens

Trigger: Sorting non-colinear handles for the opposite-handle behavior when the dragged handle's own segment no longer exists in the recomputed vector, or when its bezier has no stored start/end handle (e.g. segment reduced to a line after colinear-handle operations).

Common situations: Dragging a handle right after an undo that removed its segment; converting segments between curve types while selected; fuzz/automation sequences issuing drags between graph mutations.

Related errors


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