use std::cell::Cell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::ops::ControlFlow;

use indexmap::IndexMap;
use kcl_api::UnitLength;
use kcl_error::CompilationIssue;
use kcl_error::SourceRange;
use serde::Serialize;

use crate::ExecOutcome;
use crate::ExecutorContext;
use crate::KclError;
use crate::KclErrorWithOutputs;
use crate::Program;
use crate::SegmentDragAnchor;
use crate::collections::AhashIndexSet;
use crate::execution::Artifact;
use crate::execution::ArtifactGraph;
use crate::execution::ArtifactId;
use crate::execution::CapSubType;
use crate::execution::CodeRef;
use crate::execution::MockConfig;
use crate::execution::SKETCH_BLOCK_PARAM_ON;
use crate::execution::annotations::WarningLevel;
use crate::execution::cache::SketchModeState;
use crate::execution::cache::clear_mem_cache;
use crate::execution::cache::read_old_memory;
use crate::execution::cache::write_old_memory;
use crate::execution::types::adjust_length;
use crate::fmt::format_number_literal;
use crate::front::Angle;
use crate::front::ArcCtor;
use crate::front::CircleCtor;
use crate::front::ControlPointSplineCtor;
use crate::front::Distance;
use crate::front::EqualRadius;
use crate::front::Error;
use crate::front::ExecResult;
use crate::front::FixedPoint;
use crate::front::Freedom;
use crate::front::LinesEqualLength;
use crate::front::Midpoint;
use crate::front::Object;
use crate::front::Parallel;
use crate::front::Perpendicular;
use crate::front::PointCtor;
use crate::front::Symmetric;
use crate::front::Tangent;
use crate::frontend::api::CapSource;
use crate::frontend::api::Expr;
use crate::frontend::api::FileId;
use crate::frontend::api::Number;
use crate::frontend::api::ObjectId;
use crate::frontend::api::ObjectKind;
use crate::frontend::api::Plane;
use crate::frontend::api::ProjectId;
use crate::frontend::api::RestoreSketchCheckpointOutcome;
use crate::frontend::api::SceneGraph;
use crate::frontend::api::SceneGraphDelta;
use crate::frontend::api::SketchCheckpointId;
use crate::frontend::api::SourceDelta;
use crate::frontend::api::SourceRef;
use crate::frontend::api::SourceRefRange;
use crate::frontend::api::Version;
use crate::frontend::api::WallSource;
use crate::frontend::modify::find_defined_names;
use crate::frontend::modify::next_free_name;
use crate::frontend::modify::next_free_name_with_padding;
use crate::frontend::sketch::Coincident;
use crate::frontend::sketch::Constraint;
use crate::frontend::sketch::ConstraintSegment;
use crate::frontend::sketch::Diameter;
use crate::frontend::sketch::ExistingSegmentCtor;
use crate::frontend::sketch::Horizontal;
use crate::frontend::sketch::LineCtor;
use crate::frontend::sketch::Point2d;
use crate::frontend::sketch::Radius;
use crate::frontend::sketch::Segment;
use crate::frontend::sketch::SegmentCtor;
use crate::frontend::sketch::SketchApi;
use crate::frontend::sketch::SketchCtor;
use crate::frontend::sketch::Vertical;
use crate::frontend::traverse::MutateBodyItem;
use crate::frontend::traverse::TraversalReturn;
use crate::frontend::traverse::Visitor;
use crate::frontend::traverse::dfs_mut;
use crate::id::IncIdGenerator;
use crate::parsing::ast::types as ast;
use crate::parsing::ast::types::NodePathExt;
use crate::pretty::NumericSuffix;
use crate::std::constraints::LinesAtAngleKind;
use crate::walk::NodeMut;
use crate::walk::Visitable;

pub(crate) mod api;
pub(crate) mod modify;
pub(crate) mod sketch;

pub const MAX_SKETCH_CHECKPOINTS: usize = 100;

#[derive(Debug, Clone)]
struct SketchCheckpoint {
    id: SketchCheckpointId,
    source: SourceDelta,
    program: Program,
    scene_graph: SceneGraph,
    exec_outcome: ExecOutcome,
    point_freedom_cache: HashMap<ObjectId, Freedom>,
    mock_memory: Option<SketchModeState>,
}
mod traverse;
pub(crate) mod trim;

struct ArcSizeConstraintParams {
    points: Vec<ObjectId>,
    function_name: &'static str,
    value: f64,
    units: NumericSuffix,
    label_position: Option<Point2d<Number>>,
    constraint_type_name: &'static str,
}

const POINT_FN: &str = "point";
const POINT_AT_PARAM: &str = "at";
const LINE_FN: &str = "line";
const LINE_VARIABLE: &str = "line";
const LINE_START_PARAM: &str = "start";
const LINE_END_PARAM: &str = "end";
const ARC_FN: &str = "arc";
const ARC_VARIABLE: &str = "arc";
const ARC_START_PARAM: &str = "start";
const ARC_END_PARAM: &str = "end";
const ARC_CENTER_PARAM: &str = "center";
const CIRCLE_FN: &str = "circle";
const CIRCLE_VARIABLE: &str = "circle";
const CIRCLE_START_PARAM: &str = "start";
const CIRCLE_CENTER_PARAM: &str = "center";
const CONTROL_POINT_SPLINE_FN: &str = "controlPointSpline";
const CONTROL_POINT_SPLINE_POINTS_PARAM: &str = "points";
const LABEL_POSITION_PARAM: &str = "labelPosition";

const COINCIDENT_FN: &str = "coincident";
const DIAMETER_FN: &str = "diameter";
const DISTANCE_FN: &str = "distance";
const FIXED_FN: &str = "fixed";
const ANGLE_FN: &str = "angle";
const HORIZONTAL_DISTANCE_FN: &str = "horizontalDistance";
const VERTICAL_DISTANCE_FN: &str = "verticalDistance";
const EQUAL_LENGTH_FN: &str = "equalLength";
const EQUAL_RADIUS_FN: &str = "equalRadius";
const HORIZONTAL_FN: &str = "horizontal";
const MIDPOINT_FN: &str = "midpoint";
const MIDPOINT_POINT_PARAM: &str = "point";
const RADIUS_FN: &str = "radius";
const SYMMETRIC_FN: &str = "symmetric";
const SYMMETRIC_AXIS_PARAM: &str = "axis";
const TANGENT_FN: &str = "tangent";
const VERTICAL_FN: &str = "vertical";

const LINE_PROPERTY_START: &str = "start";
const LINE_PROPERTY_END: &str = "end";

const ARC_PROPERTY_START: &str = "start";
const ARC_PROPERTY_END: &str = "end";
const ARC_PROPERTY_CENTER: &str = "center";
const CIRCLE_PROPERTY_START: &str = "start";
const CIRCLE_PROPERTY_CENTER: &str = "center";
const CONTROL_POINT_SPLINE_PROPERTY_CONTROLS: &str = "controls";
const CONTROL_POINT_SPLINE_PROPERTY_EDGES: &str = "edges";

const CONSTRUCTION_PARAM: &str = "construction";

#[derive(Debug, Clone, Copy)]
enum EditDeleteKind {
    Edit,
    DeleteNonSketch,
}

/// Options that control how an edit is re-executed and written back.
struct ExecuteAfterEditOptions {
    segment_ids_edited: AhashIndexSet<ObjectId>,
    edit_kind: EditDeleteKind,
    commit_solved_initial_guesses: bool,
}

impl EditDeleteKind {
    /// Returns true if this edit is any type of deletion.
    fn is_delete(&self) -> bool {
        match self {
            EditDeleteKind::Edit => false,
            EditDeleteKind::DeleteNonSketch => true,
        }
    }

    fn to_change_kind(self) -> ChangeKind {
        match self {
            EditDeleteKind::Edit => ChangeKind::Edit,
            EditDeleteKind::DeleteNonSketch => ChangeKind::Delete,
        }
    }
}

#[derive(Debug, Clone, Copy)]
enum ChangeKind {
    Add,
    Edit,
    Delete,
    None,
}

#[derive(Debug, Clone, Serialize, ts_rs::TS)]
#[ts(export, export_to = "FrontendApi.ts")]
#[serde(tag = "type")]
pub enum SetProgramOutcome {
    #[serde(rename_all = "camelCase")]
    Success {
        scene_graph: Box<SceneGraph>,
        exec_outcome: Box<ExecOutcome>,
        checkpoint_id: Option<SketchCheckpointId>,
    },
    #[serde(rename_all = "camelCase")]
    ExecFailure { error: Box<KclErrorWithOutputs> },
}

/// Options for a sketch segment edit that participates in drag solving.
pub struct EditSegmentsOptions {
    /// Narrows which edited scene objects receive temporary fixed constraints.
    ///
    /// `None` keeps the default of anchoring every edited segment. `Some(vec![])`
    /// disables those fixed constraints, which is useful for semantic edits such
    /// as toggling construction state.
    pub anchor_segment_ids: Option<Vec<ObjectId>>,
    /// Hidden fixed cursor points that the referenced segment bodies must pass
    /// through during solve.
    pub drag_anchors: Vec<SegmentDragAnchor>,
    /// Whether solver-updated initial guesses should be written back to KCL.
    pub commit_solved_initial_guesses: bool,
}

/// Options for a distance-constraint label edit during sketch dragging.
pub struct EditDistanceConstraintLabelPositionOptions {
    /// Edited scene objects to keep anchored while previewing the label edit.
    pub anchor_segment_ids: Vec<ObjectId>,
    /// Whether solver-updated initial guesses should be written back to KCL.
    pub commit_solved_initial_guesses: bool,
}

#[derive(Debug, Clone)]
pub struct FrontendState {
    program: Program,
    scene_graph: SceneGraph,
    /// Stores the last known freedom value for each point object.
    /// This allows us to preserve freedom values when freedom analysis isn't run.
    point_freedom_cache: HashMap<ObjectId, Freedom>,
    /// One-shot drag anchors for the next segment edit. These ids define which
    /// edited points/segments become temporary fixed constraints during solve.
    next_drag_anchor_segment_ids: Option<AhashIndexSet<ObjectId>>,
    /// One-shot segment-body drag anchors for the next segment edit. These add
    /// a temporary solver point on the dragged segment that follows the cursor.
    next_segment_drag_anchors: Option<Vec<SegmentDragAnchor>>,
    /// One-shot override for whether the next edit commits solver-updated
    /// initial guesses back into KCL. Drag previews keep this off so only the
    /// explicit drag edit feeds the next solve.
    next_edit_commits_solver_solutions: Option<bool>,
    sketch_checkpoints: VecDeque<SketchCheckpoint>,
    sketch_checkpoint_id_gen: IncIdGenerator<u64>,
}

impl Default for FrontendState {
    fn default() -> Self {
        Self::new()
    }
}

impl FrontendState {
    pub fn new() -> Self {
        Self {
            program: Program::empty(),
            scene_graph: SceneGraph {
                project: ProjectId(0),
                file: FileId(0),
                version: Version(0),
                objects: Default::default(),
                settings: Default::default(),
                sketch_mode: Default::default(),
            },
            point_freedom_cache: HashMap::new(),
            next_drag_anchor_segment_ids: None,
            next_segment_drag_anchors: None,
            next_edit_commits_solver_solutions: None,
            sketch_checkpoints: VecDeque::new(),
            sketch_checkpoint_id_gen: IncIdGenerator::new(1),
        }
    }

    /// Get a reference to the scene graph
    pub fn scene_graph(&self) -> &SceneGraph {
        &self.scene_graph
    }

    pub fn default_length_unit(&self) -> UnitLength {
        self.program
            .meta_settings()
            .ok()
            .flatten()
            .map(|settings| settings.default_length_units)
            .unwrap_or(UnitLength::Millimeters)
    }

    pub async fn create_sketch_checkpoint(&mut self, exec_outcome: ExecOutcome) -> api::Result<SketchCheckpointId> {
        let checkpoint_id = SketchCheckpointId::new(self.sketch_checkpoint_id_gen.next_id());

        let checkpoint = SketchCheckpoint {
            id: checkpoint_id,
            source: SourceDelta {
                text: source_from_ast(&self.program.ast),
            },
            program: self.program.clone(),
            scene_graph: self.scene_graph.clone(),
            exec_outcome,
            point_freedom_cache: self.point_freedom_cache.clone(),
            mock_memory: read_old_memory().await,
        };

        self.sketch_checkpoints.push_back(checkpoint);
        while self.sketch_checkpoints.len() > MAX_SKETCH_CHECKPOINTS {
            self.sketch_checkpoints.pop_front();
        }

        Ok(checkpoint_id)
    }

    /// Edit sketch segments with optional drag-solve overrides.
    ///
    /// Drag anchors add hidden fixed cursor points and constrain the referenced
    /// segment bodies to pass through them, which lets body drags use the same
    /// anchor model without pinning all child points. Preview callers disable
    /// solver writeback so solved geometry can be returned without feeding every
    /// solver value back into KCL.
    pub async fn edit_segments_with_options(
        &mut self,
        ctx: &ExecutorContext,
        version: Version,
        sketch: ObjectId,
        segments: Vec<ExistingSegmentCtor>,
        options: EditSegmentsOptions,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        let previous_anchor_ids = options.anchor_segment_ids.map(|anchor_ids| {
            self.next_drag_anchor_segment_ids
                .replace(anchor_ids.into_iter().collect())
        });
        let previous_drag_anchors = self.next_segment_drag_anchors.replace(options.drag_anchors);
        let previous_commit_mode = self
            .next_edit_commits_solver_solutions
            .replace(options.commit_solved_initial_guesses);
        let result = SketchApi::edit_segments(self, ctx, version, sketch, segments).await;
        if let Some(previous_anchor_ids) = previous_anchor_ids {
            self.next_drag_anchor_segment_ids = previous_anchor_ids;
        }
        self.next_segment_drag_anchors = previous_drag_anchors;
        self.next_edit_commits_solver_solutions = previous_commit_mode;
        result
    }

    /// Edit a distance-constraint label position with optional solver writeback.
    ///
    /// Drag previews set `commit_solved_initial_guesses` to false so label
    /// placement can be previewed against solved geometry without advancing
    /// persistent KCL state until drag completion.
    pub async fn edit_distance_constraint_label_position_with_options(
        &mut self,
        ctx: &ExecutorContext,
        version: Version,
        sketch: ObjectId,
        constraint_id: ObjectId,
        label_position: Point2d<Number>,
        options: EditDistanceConstraintLabelPositionOptions,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        let previous_commit_mode = self
            .next_edit_commits_solver_solutions
            .replace(options.commit_solved_initial_guesses);
        let result = SketchApi::edit_distance_constraint_label_position(
            self,
            ctx,
            version,
            sketch,
            constraint_id,
            label_position,
            options.anchor_segment_ids,
        )
        .await;
        self.next_edit_commits_solver_solutions = previous_commit_mode;
        result
    }

    pub async fn restore_sketch_checkpoint(
        &mut self,
        checkpoint_id: SketchCheckpointId,
    ) -> api::Result<RestoreSketchCheckpointOutcome> {
        let checkpoint = self
            .sketch_checkpoints
            .iter()
            .find(|checkpoint| checkpoint.id == checkpoint_id)
            .cloned()
            .ok_or_else(|| Error {
                msg: format!("Sketch checkpoint not found: {checkpoint_id:?}"),
            })?;

        self.program = checkpoint.program;
        self.scene_graph = checkpoint.scene_graph.clone();
        self.point_freedom_cache = checkpoint.point_freedom_cache;
        self.next_drag_anchor_segment_ids = None;
        self.next_segment_drag_anchors = None;
        self.next_edit_commits_solver_solutions = None;

        if let Some(mock_memory) = checkpoint.mock_memory {
            write_old_memory(mock_memory).await;
        } else {
            clear_mem_cache().await;
        }

        Ok(RestoreSketchCheckpointOutcome {
            source_delta: checkpoint.source,
            scene_graph_delta: SceneGraphDelta {
                new_graph: self.scene_graph_for_ui(),
                new_objects: Vec::new(),
                invalidates_ids: true,
                exec_outcome: checkpoint.exec_outcome,
            },
        })
    }

    pub fn clear_sketch_checkpoints(&mut self) {
        self.sketch_checkpoints.clear();
    }
    fn scene_graph_for_ui(&self) -> SceneGraph {
        let has_control_point_splines = self.scene_graph.objects.iter().any(|object| {
            matches!(
                object.kind,
                ObjectKind::Segment {
                    segment: Segment::ControlPointSpline(_)
                }
            )
        });

        if !has_control_point_splines {
            return self.scene_graph.clone();
        }

        let hidden_constraint_ids = self
            .scene_graph
            .objects
            .iter()
            .filter_map(|object| match &object.kind {
                ObjectKind::Constraint {
                    constraint: Constraint::Coincident(coincident),
                } if coincident_is_internal_to_same_control_point_spline(coincident, &self.scene_graph) => {
                    Some(object.id)
                }
                _ => None,
            })
            .collect::<HashSet<_>>();

        if hidden_constraint_ids.is_empty() {
            return self.scene_graph.clone();
        }

        let mut scene_graph = self.scene_graph.clone();
        for object in &mut scene_graph.objects {
            match &mut object.kind {
                ObjectKind::Constraint { .. } if hidden_constraint_ids.contains(&object.id) => {
                    object.kind = ObjectKind::Nil;
                }
                ObjectKind::Sketch(sketch) => {
                    sketch
                        .constraints
                        .retain(|constraint_id| !hidden_constraint_ids.contains(constraint_id));
                }
                _ => {}
            }
        }

        scene_graph
    }
}

fn coincident_is_internal_to_same_control_point_spline(coincident: &Coincident, scene_graph: &SceneGraph) -> bool {
    let mut first_owner_id = None;
    for segment_id in coincident.segment_ids() {
        let Some(owner_id) = owning_control_point_spline_id(segment_id, scene_graph) else {
            return false;
        };

        match first_owner_id {
            Some(first_owner_id) if first_owner_id != owner_id => return false,
            Some(_) => {}
            None => first_owner_id = Some(owner_id),
        }
    }

    first_owner_id.is_some()
}

fn owning_control_point_spline_id(segment_id: ObjectId, scene_graph: &SceneGraph) -> Option<ObjectId> {
    let object = scene_graph.objects.get(segment_id.0)?;
    let ObjectKind::Segment { segment } = &object.kind else {
        return None;
    };

    match segment {
        Segment::ControlPointSpline(_) => Some(segment_id),
        Segment::Point(point) => point
            .owner
            .filter(|owner_id| matches_control_point_spline_owner(*owner_id, scene_graph)),
        Segment::Line(line) => line
            .owner
            .filter(|owner_id| matches_control_point_spline_owner(*owner_id, scene_graph)),
        _ => None,
    }
}

fn matches_control_point_spline_owner(owner_id: ObjectId, scene_graph: &SceneGraph) -> bool {
    matches!(
        scene_graph.objects.get(owner_id.0).map(|object| &object.kind),
        Some(ObjectKind::Segment {
            segment: Segment::ControlPointSpline(_)
        })
    )
}

fn ensure_control_point_spline_experimental_features(program: &Program) -> Result<Program, KclError> {
    let experimental_features_allowed = program
        .meta_settings()
        .ok()
        .flatten()
        .map(|settings| settings.experimental_features == WarningLevel::Allow)
        .unwrap_or(false);
    if experimental_features_allowed {
        return Ok(program.clone());
    }

    program.change_experimental_features(Some(WarningLevel::Allow))
}

impl SketchApi for FrontendState {
    async fn execute_mock(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        let mut truncated_program = self.program.clone();
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::None)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute.
        let outcome = ctx
            .run_mock(&truncated_program, &MockConfig::new_sketch_mode(sketch))
            .await?;
        let new_source = source_from_ast(&self.program.ast);
        let src_delta = SourceDelta { text: new_source };
        // MockConfig::default() has freedom_analysis: true
        let outcome = self.update_state_after_exec(outcome, true);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph.clone(),
            new_objects: Default::default(),
            invalidates_ids: false,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    async fn new_sketch(
        &mut self,
        ctx: &ExecutorContext,
        _project: ProjectId,
        _file: FileId,
        _version: Version,
        args: SketchCtor,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta, ObjectId)> {
        // TODO: Check version.

        let mut new_ast = self.program.ast.clone();
        // Create updated KCL source from args.
        let mut plane_ast =
            sketch_on_ast_expr(&mut new_ast, &self.scene_graph, &args.on).map_err(KclErrorWithOutputs::no_outputs)?;
        let mut defined_names = find_defined_names(&new_ast);
        let is_face_of_expr = matches!(
            &plane_ast,
            ast::Expr::CallExpressionKw(call) if call.callee.name.name == "faceOf"
        );
        if is_face_of_expr {
            let face_name = next_free_name_with_padding("face", &defined_names)
                .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.msg)))?;
            let face_decl = ast::VariableDeclaration::new(
                ast::VariableDeclarator::new(&face_name, plane_ast),
                ast::ItemVisibility::Default,
                ast::VariableKind::Const,
            );
            new_ast
                .body
                .push(ast::BodyItem::VariableDeclaration(Box::new(ast::Node::no_src(
                    face_decl,
                ))));
            defined_names.insert(face_name.clone());
            plane_ast = ast::Expr::Name(Box::new(ast::Name::new(&face_name)));
        }
        let sketch_ast = ast::SketchBlock {
            arguments: vec![ast::LabeledArg {
                label: Some(ast::Identifier::new(SKETCH_BLOCK_PARAM_ON)),
                arg: plane_ast,
            }],
            body: Default::default(),
            is_being_edited: false,
            non_code_meta: Default::default(),
            digest: None,
        };
        // Add a sketch block as a variable declaration directly, avoiding
        // source-range mutation on a no-src node.
        let sketch_name = next_free_name_with_padding("sketch", &defined_names)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.msg)))?;
        let sketch_decl = ast::VariableDeclaration::new(
            ast::VariableDeclarator::new(
                &sketch_name,
                ast::Expr::SketchBlock(Box::new(ast::Node::no_src(sketch_ast))),
            ),
            ast::ItemVisibility::Default,
            ast::VariableKind::Const,
        );
        new_ast
            .body
            .push(ast::BodyItem::VariableDeclaration(Box::new(ast::Node::no_src(
                sketch_decl,
            ))));
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(&new_ast);
        // Parse the new source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding sketch: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding sketch".to_owned(),
            )));
        };

        // Make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // We need to do an engine execute so that the plane object gets created
        // and is cached.
        let outcome = ctx.run_with_caching(new_program.clone()).await?;
        let freedom_analysis_ran = true;

        let outcome = self.update_state_after_exec(outcome, freedom_analysis_ran);

        let Some(sketch_id) = self
            .scene_graph
            .objects
            .iter()
            .filter_map(|object| match object.kind {
                ObjectKind::Sketch(_) => Some(object.id),
                _ => None,
            })
            .max_by_key(|id| id.0)
        else {
            return Err(KclErrorWithOutputs::from_error_outcome(
                KclError::refactor("No objects in scene graph after adding sketch".to_owned()),
                outcome,
            ));
        };
        // Store the object in the scene.
        self.scene_graph.sketch_mode = Some(sketch_id);

        let src_delta = SourceDelta { text: new_source };
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: vec![sketch_id],
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta, sketch_id))
    }

    async fn edit_sketch(
        &mut self,
        ctx: &ExecutorContext,
        _project: ProjectId,
        _file: FileId,
        _version: Version,
        sketch: ObjectId,
    ) -> ExecResult<SceneGraphDelta> {
        // TODO: Check version.

        // Look up existing sketch.
        let sketch_object = self.scene_graph.objects.get(sketch.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article()
            ))));
        };
        let sketch_block_ref = expect_single_node_ref(sketch_object).map_err(KclErrorWithOutputs::no_outputs)?;

        // Enter sketch mode by setting the sketch_mode.
        self.scene_graph.sketch_mode = Some(sketch);

        // Truncate after the sketch block for mock execution.
        let mut truncated_program = self.program.clone();
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::None)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute in mock mode to ensure state is up to date. The caller will
        // want freedom analysis to display segments correctly.
        let outcome = ctx
            .run_mock(&truncated_program, &MockConfig::new_sketch_mode(sketch))
            .await?;

        // MockConfig::default() has freedom_analysis: true
        let outcome = self.update_state_after_exec(outcome, true);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: Vec::new(),
            exec_outcome: outcome,
        };
        Ok(scene_graph_delta)
    }

    async fn exit_sketch(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
    ) -> ExecResult<SceneGraph> {
        // TODO: Check version.
        #[cfg(not(target_arch = "wasm32"))]
        let _ = sketch;
        #[cfg(target_arch = "wasm32")]
        if self.scene_graph.sketch_mode != Some(sketch) {
            web_sys::console::warn_1(
                &format!(
                    "WARNING: exit_sketch: current state's sketch mode ID doesn't match the given sketch ID; state={:#?}, given={sketch:?}",
                    &self.scene_graph.sketch_mode
                )
                .into(),
            );
        }
        self.scene_graph.sketch_mode = None;

        // Execute.
        let outcome = ctx.run_with_caching(self.program.clone()).await?;

        // exit_sketch doesn't run freedom analysis, just clears sketch_mode
        self.update_state_after_exec(outcome, false);

        Ok(self.scene_graph_for_ui())
    }

    async fn delete_sketch(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.

        let mut new_ast = self.program.ast.clone();

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self.scene_graph.objects.get(sketch_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article(),
            ))));
        };

        // Modify the AST to remove the sketch.
        self.mutate_ast(&mut new_ast, sketch_id, AstMutateCommand::DeleteNode)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        self.execute_after_delete_sketch(ctx, &mut new_ast).await
    }

    async fn add_segment(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        segment: SegmentCtor,
        _label: Option<String>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.
        match segment {
            SegmentCtor::Point(ctor) => self.add_point(ctx, sketch, ctor).await,
            SegmentCtor::Line(ctor) => self.add_line(ctx, sketch, ctor).await,
            SegmentCtor::Arc(ctor) => self.add_arc(ctx, sketch, ctor).await,
            SegmentCtor::Circle(ctor) => self.add_circle(ctx, sketch, ctor).await,
            SegmentCtor::ControlPointSpline(ctor) => self.add_control_point_spline(ctx, sketch, ctor).await,
        }
    }

    async fn edit_segments(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        segments: Vec<ExistingSegmentCtor>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        let mut new_ast = self.program.ast.clone();
        let mut edited_segment_ids = AhashIndexSet::with_capacity_and_hasher(segments.len(), Default::default());
        let mut invalidates_ids = false;

        // edited_segment_ids still has to be the original segments (not final_edits), otherwise the owner segments
        // are passed to `execute_after_edit` which changes the result of the solver, causing tests to fail.
        for segment in &segments {
            edited_segment_ids.insert(segment.id);
            if let SegmentCtor::ControlPointSpline(new_ctor) = &segment.ctor
                && let Some(existing_object) = self.scene_graph.objects.get(segment.id.0)
                && let ObjectKind::Segment {
                    segment: Segment::ControlPointSpline(existing_spline),
                } = &existing_object.kind
                && existing_spline.controls.len() != new_ctor.points.len()
            {
                invalidates_ids = true;
            }
        }
        let drag_anchor_segment_ids = self
            .next_drag_anchor_segment_ids
            .take()
            .unwrap_or_else(|| edited_segment_ids.clone());
        let commit_solved_initial_guesses = self.next_edit_commits_solver_solutions.take().unwrap_or(true);

        // Preprocess segments into a final_edits vector to handle if segments contains:
        // - edit start point of line1 (as SegmentCtor::Point)
        // - edit end point of line1 (as SegmentCtor::Point)
        //
        // This would result in only the end point to be updated because edit_point() clones line1's ctor from
        // scene_graph, but this is still the old ctor because self.scene_graph is only updated after the loop finishes.
        //
        // To fix this, and other cases when the same point is edited from multiple elements in the segments Vec
        // we apply all edits in order to final_edits in a way that owned point edits result in line edits,
        // so the above example would result in a single line1 edit:
        // - the first start point edit creates a new line edit entry in final_edits
        // - the second end point edit finds this line edit and mutates the end position only.
        //
        // The result is that segments are flattened into a single IndexMap of edits by their owners, later edits overriding earlier ones.
        let mut final_edits: IndexMap<ObjectId, SegmentCtor> = IndexMap::new();

        for segment in segments {
            let segment_id = segment.id;
            match segment.ctor {
                SegmentCtor::Point(ctor) => {
                    // Find the owner, if any (point -> line / arc)
                    if let Some(segment_object) = self.scene_graph.objects.get(segment_id.0)
                        && let ObjectKind::Segment { segment } = &segment_object.kind
                        && let Segment::Point(point) = segment
                        && let Some(owner_id) = point.owner
                        && let Some(owner_object) = self.scene_graph.objects.get(owner_id.0)
                        && let ObjectKind::Segment { segment: owner_segment } = &owner_object.kind
                    {
                        match owner_segment {
                            Segment::Line(line) if line.start == segment_id || line.end == segment_id => {
                                if let Some(existing) = final_edits.get_mut(&owner_id) {
                                    let SegmentCtor::Line(line_ctor) = existing else {
                                        return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                            "Internal: Expected line ctor for owner, but found {}",
                                            existing.human_friendly_kind_with_article()
                                        ))));
                                    };
                                    // Line owner is already in final_edits -> apply this point edit
                                    if line.start == segment_id {
                                        line_ctor.start = ctor.position;
                                    } else {
                                        line_ctor.end = ctor.position;
                                    }
                                } else if let SegmentCtor::Line(line_ctor) = &line.ctor {
                                    // Line owner is not in final_edits yet -> create it
                                    let mut line_ctor = line_ctor.clone();
                                    if line.start == segment_id {
                                        line_ctor.start = ctor.position;
                                    } else {
                                        line_ctor.end = ctor.position;
                                    }
                                    final_edits.insert(owner_id, SegmentCtor::Line(line_ctor));
                                } else {
                                    // This should never run..
                                    return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                        "Internal: Line does not have line ctor, but found {}",
                                        line.ctor.human_friendly_kind_with_article()
                                    ))));
                                }
                                continue;
                            }
                            Segment::Arc(arc)
                                if arc.start == segment_id || arc.end == segment_id || arc.center == segment_id =>
                            {
                                if let Some(existing) = final_edits.get_mut(&owner_id) {
                                    let SegmentCtor::Arc(arc_ctor) = existing else {
                                        return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                            "Internal: Expected arc ctor for owner, but found {}",
                                            existing.human_friendly_kind_with_article()
                                        ))));
                                    };
                                    if arc.start == segment_id {
                                        arc_ctor.start = ctor.position;
                                    } else if arc.end == segment_id {
                                        arc_ctor.end = ctor.position;
                                    } else {
                                        arc_ctor.center = ctor.position;
                                    }
                                } else if let SegmentCtor::Arc(arc_ctor) = &arc.ctor {
                                    let mut arc_ctor = arc_ctor.clone();
                                    if arc.start == segment_id {
                                        arc_ctor.start = ctor.position;
                                    } else if arc.end == segment_id {
                                        arc_ctor.end = ctor.position;
                                    } else {
                                        arc_ctor.center = ctor.position;
                                    }
                                    final_edits.insert(owner_id, SegmentCtor::Arc(arc_ctor));
                                } else {
                                    return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                        "Internal: Arc does not have arc ctor, but found {}",
                                        arc.ctor.human_friendly_kind_with_article()
                                    ))));
                                }
                                continue;
                            }
                            Segment::Circle(circle) if circle.start == segment_id || circle.center == segment_id => {
                                if let Some(existing) = final_edits.get_mut(&owner_id) {
                                    let SegmentCtor::Circle(circle_ctor) = existing else {
                                        return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                            "Internal: Expected circle ctor for owner, but found {}",
                                            existing.human_friendly_kind_with_article()
                                        ))));
                                    };
                                    if circle.start == segment_id {
                                        circle_ctor.start = ctor.position;
                                    } else {
                                        circle_ctor.center = ctor.position;
                                    }
                                } else if let SegmentCtor::Circle(circle_ctor) = &circle.ctor {
                                    let mut circle_ctor = circle_ctor.clone();
                                    if circle.start == segment_id {
                                        circle_ctor.start = ctor.position;
                                    } else {
                                        circle_ctor.center = ctor.position;
                                    }
                                    final_edits.insert(owner_id, SegmentCtor::Circle(circle_ctor));
                                } else {
                                    return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                        "Internal: Circle does not have circle ctor, but found {}",
                                        circle.ctor.human_friendly_kind_with_article()
                                    ))));
                                }
                                continue;
                            }
                            Segment::ControlPointSpline(spline) if spline.controls.contains(&segment_id) => {
                                let Some(control_index) =
                                    spline.controls.iter().position(|control_id| *control_id == segment_id)
                                else {
                                    return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                        "Internal: Point is not part of owner's controlPointSpline segment: point={segment_id:?}, spline={owner_id:?}"
                                    ))));
                                };
                                if let Some(existing) = final_edits.get_mut(&owner_id) {
                                    let SegmentCtor::ControlPointSpline(spline_ctor) = existing else {
                                        return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                            "Internal: Expected controlPointSpline ctor for owner, but found {}",
                                            existing.human_friendly_kind_with_article()
                                        ))));
                                    };
                                    spline_ctor.points[control_index] = ctor.position;
                                } else if let SegmentCtor::ControlPointSpline(spline_ctor) = &spline.ctor {
                                    let mut spline_ctor = spline_ctor.clone();
                                    spline_ctor.points[control_index] = ctor.position;
                                    final_edits.insert(owner_id, SegmentCtor::ControlPointSpline(spline_ctor));
                                } else {
                                    return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                                        "Internal: Control point spline does not have controlPointSpline ctor, but found {}",
                                        spline.ctor.human_friendly_kind_with_article()
                                    ))));
                                }
                                continue;
                            }
                            _ => {}
                        }
                    }

                    // No owner, it's an individual point
                    final_edits.insert(segment_id, SegmentCtor::Point(ctor));
                }
                SegmentCtor::Line(ctor) => {
                    final_edits.insert(segment_id, SegmentCtor::Line(ctor));
                }
                SegmentCtor::Arc(ctor) => {
                    final_edits.insert(segment_id, SegmentCtor::Arc(ctor));
                }
                SegmentCtor::Circle(ctor) => {
                    final_edits.insert(segment_id, SegmentCtor::Circle(ctor));
                }
                SegmentCtor::ControlPointSpline(ctor) => {
                    final_edits.insert(segment_id, SegmentCtor::ControlPointSpline(ctor));
                }
            }
        }

        for (segment_id, ctor) in final_edits {
            match ctor {
                SegmentCtor::Point(ctor) => self
                    .edit_point(&mut new_ast, sketch, segment_id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Line(ctor) => self
                    .edit_line(&mut new_ast, sketch, segment_id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Arc(ctor) => self
                    .edit_arc(&mut new_ast, sketch, segment_id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Circle(ctor) => self
                    .edit_circle(&mut new_ast, sketch, segment_id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::ControlPointSpline(ctor) => self
                    .edit_control_point_spline(&mut new_ast, sketch, segment_id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
            }
        }
        let (source_delta, mut scene_graph_delta) = self
            .execute_after_edit(
                ctx,
                sketch,
                sketch_block_ref,
                &mut new_ast,
                ExecuteAfterEditOptions {
                    segment_ids_edited: drag_anchor_segment_ids,
                    edit_kind: EditDeleteKind::Edit,
                    commit_solved_initial_guesses,
                },
            )
            .await?;
        if invalidates_ids {
            scene_graph_delta.invalidates_ids = true;
        }
        Ok((source_delta, scene_graph_delta))
    }

    async fn delete_objects(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        constraint_ids: Vec<ObjectId>,
        segment_ids: Vec<ObjectId>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        // Deduplicate IDs.
        let mut constraint_ids_set = constraint_ids.into_iter().collect::<AhashIndexSet<_>>();
        let segment_ids_set = segment_ids.into_iter().collect::<AhashIndexSet<_>>();

        // If a point is owned by a Line/Arc, we want to delete the owner, which will
        // also delete the point, as well as other points that are owned by the owner.
        let mut resolved_segment_ids_to_delete = AhashIndexSet::default();

        for segment_id in segment_ids_set.iter().copied() {
            let owner_id = self.scene_graph.objects.get(segment_id.0).and_then(|segment_object| {
                let ObjectKind::Segment { segment } = &segment_object.kind else {
                    return None;
                };
                match segment {
                    Segment::Point(point) => point.owner,
                    Segment::Line(line) => line.owner,
                    _ => None,
                }
            });

            if let Some(owner_id) = owner_id
                && let Some(owner_object) = self.scene_graph.objects.get(owner_id.0)
                && let ObjectKind::Segment { segment: owner_segment } = &owner_object.kind
                && matches!(
                    owner_segment,
                    Segment::Line(_) | Segment::Arc(_) | Segment::Circle(_) | Segment::ControlPointSpline(_)
                )
            {
                // segment is owned -> delete the owner
                resolved_segment_ids_to_delete.insert(owner_id);
            } else {
                // segment is not owned by anything -> can be deleted
                resolved_segment_ids_to_delete.insert(segment_id);
            }
        }
        let referenced_constraint_ids = self
            .find_referenced_constraints(sketch, &resolved_segment_ids_to_delete)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        let mut new_ast = self.program.ast.clone();

        for constraint_id in referenced_constraint_ids {
            if constraint_ids_set.contains(&constraint_id) {
                continue;
            }

            let constraint_object = self.scene_graph.objects.get(constraint_id.0).ok_or_else(|| {
                KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Constraint not found: {constraint_id:?}")))
            })?;
            let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
                return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                    "Object is not a constraint, it is {}",
                    constraint_object.kind.human_friendly_kind_with_article()
                ))));
            };

            match constraint {
                Constraint::Coincident(coincident) => {
                    let remaining_segments =
                        self.remaining_constraint_segments(&coincident.segments, &resolved_segment_ids_to_delete);

                    // If there are at least 2 segments left in the constraint: keep it, otherwise delete it.
                    if remaining_segments.len() >= 2 {
                        self.edit_coincident_constraint(&mut new_ast, constraint_id, remaining_segments)
                            .map_err(KclErrorWithOutputs::no_outputs)?;
                    } else {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                Constraint::EqualRadius(equal_radius) => {
                    let remaining_input = equal_radius
                        .input
                        .iter()
                        .copied()
                        .filter(|segment_id| {
                            !self.segment_will_be_deleted(*segment_id, &resolved_segment_ids_to_delete)
                        })
                        .collect::<Vec<_>>();

                    if remaining_input.len() >= 2 {
                        self.edit_equal_radius_constraint(&mut new_ast, constraint_id, remaining_input)
                            .map_err(KclErrorWithOutputs::no_outputs)?;
                    } else {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                Constraint::LinesEqualLength(lines_equal_length) => {
                    let remaining_lines = lines_equal_length
                        .lines
                        .iter()
                        .copied()
                        .filter(|line_id| !self.segment_will_be_deleted(*line_id, &resolved_segment_ids_to_delete))
                        .collect::<Vec<_>>();

                    // Equal length constraint is only valid with at least 2 lines
                    if remaining_lines.len() >= 2 {
                        self.edit_equal_length_constraint(&mut new_ast, constraint_id, remaining_lines)
                            .map_err(KclErrorWithOutputs::no_outputs)?;
                    } else {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                Constraint::Parallel(parallel) => {
                    let remaining_lines = parallel
                        .lines
                        .iter()
                        .copied()
                        .filter(|line_id| !self.segment_will_be_deleted(*line_id, &resolved_segment_ids_to_delete))
                        .collect::<Vec<_>>();

                    if remaining_lines.len() >= 2 {
                        self.edit_parallel_constraint(&mut new_ast, constraint_id, remaining_lines)
                            .map_err(KclErrorWithOutputs::no_outputs)?;
                    } else {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                Constraint::Horizontal(Horizontal::Points { points }) => {
                    let remaining_points = self.remaining_constraint_segments(points, &resolved_segment_ids_to_delete);

                    if remaining_points.len() >= 2 {
                        self.edit_horizontal_points_constraint(&mut new_ast, constraint_id, remaining_points)
                            .map_err(KclErrorWithOutputs::no_outputs)?;
                    } else {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                Constraint::Vertical(Vertical::Points { points }) => {
                    let remaining_points = self.remaining_constraint_segments(points, &resolved_segment_ids_to_delete);

                    if remaining_points.len() >= 2 {
                        self.edit_vertical_points_constraint(&mut new_ast, constraint_id, remaining_points)
                            .map_err(KclErrorWithOutputs::no_outputs)?;
                    } else {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                Constraint::Fixed(fixed) => {
                    if fixed.points.iter().any(|fixed_point| {
                        self.segment_will_be_deleted(fixed_point.point, &resolved_segment_ids_to_delete)
                    }) {
                        constraint_ids_set.insert(constraint_id);
                    }
                }
                _ => {
                    // All other constraint types: if referenced by a segment -> delete the constraint
                    constraint_ids_set.insert(constraint_id);
                }
            }
        }

        for constraint_id in constraint_ids_set {
            self.delete_constraint(&mut new_ast, sketch, constraint_id)
                .map_err(KclErrorWithOutputs::no_outputs)?;
        }
        for segment_id in resolved_segment_ids_to_delete {
            self.delete_segment(&mut new_ast, sketch, segment_id)
                .map_err(KclErrorWithOutputs::no_outputs)?;
        }

        self.execute_after_edit(
            ctx,
            sketch,
            sketch_block_ref,
            &mut new_ast,
            ExecuteAfterEditOptions {
                segment_ids_edited: Default::default(),
                edit_kind: EditDeleteKind::DeleteNonSketch,
                commit_solved_initial_guesses: true,
            },
        )
        .await
    }

    async fn add_constraint(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        constraint: Constraint,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.

        // Save the original state as a backup - we'll restore it if anything fails
        let original_program = self.program.clone();
        let original_scene_graph = self.scene_graph.clone();

        let mut new_ast = self.program.ast.clone();
        let sketch_block_ref = match constraint {
            Constraint::Coincident(coincident) => self
                .add_coincident(sketch, coincident, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Distance(distance) => self
                .add_distance(sketch, distance, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::EqualRadius(equal_radius) => self
                .add_equal_radius(sketch, equal_radius, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Fixed(fixed) => self
                .add_fixed_constraints(sketch, fixed.points, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::HorizontalDistance(distance) => self
                .add_horizontal_distance(sketch, distance, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::VerticalDistance(distance) => self
                .add_vertical_distance(sketch, distance, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Horizontal(horizontal) => self
                .add_horizontal(sketch, horizontal, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::LinesEqualLength(lines_equal_length) => self
                .add_lines_equal_length(sketch, lines_equal_length, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Midpoint(midpoint) => self
                .add_midpoint(sketch, midpoint, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Parallel(parallel) => self
                .add_parallel(sketch, parallel, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Perpendicular(perpendicular) => self
                .add_perpendicular(sketch, perpendicular, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Radius(radius) => self
                .add_radius(sketch, radius, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Diameter(diameter) => self
                .add_diameter(sketch, diameter, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Symmetric(symmetric) => self
                .add_symmetric(sketch, symmetric, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Vertical(vertical) => self
                .add_vertical(sketch, vertical, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Angle(lines_at_angle) => self
                .add_angle(sketch, lines_at_angle, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
            Constraint::Tangent(tangent) => self
                .add_tangent(sketch, tangent, &mut new_ast)
                .await
                .map_err(KclErrorWithOutputs::no_outputs)?,
        };

        let result = self
            .execute_after_add_constraint(ctx, sketch, sketch_block_ref, &mut new_ast)
            .await;

        // If execution failed, restore the original state to prevent corruption
        if result.is_err() {
            self.program = original_program;
            self.scene_graph = original_scene_graph;
        }

        result
    }

    async fn chain_segment(
        &mut self,
        ctx: &ExecutorContext,
        version: Version,
        sketch: ObjectId,
        previous_segment_end_point_id: ObjectId,
        segment: SegmentCtor,
        _label: Option<String>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.

        // First, add the segment (line) to get its start point ID
        let SegmentCtor::Line(line_ctor) = segment else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "chain_segment currently only supports Line segments, got {}",
                segment.human_friendly_kind_with_article(),
            ))));
        };

        // Add the line segment first - this updates self.program and self.scene_graph
        let (_first_src_delta, first_scene_delta) = self.add_line(ctx, sketch, line_ctor).await?;

        // Find the new line's start point ID from the updated scene graph
        // add_line updates self.scene_graph, so we can use that
        let new_line_id = first_scene_delta
            .new_objects
            .iter()
            .find(|&obj_id| {
                let obj = self.scene_graph.objects.get(obj_id.0);
                if let Some(obj) = obj {
                    matches!(
                        &obj.kind,
                        ObjectKind::Segment {
                            segment: Segment::Line(_)
                        }
                    )
                } else {
                    false
                }
            })
            .ok_or_else(|| {
                KclErrorWithOutputs::no_outputs(KclError::refactor(
                    "Failed to find new line segment in scene graph".to_string(),
                ))
            })?;

        let new_line_obj = self.scene_graph.objects.get(new_line_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "New line object not found: {new_line_id:?}"
            )))
        })?;

        let ObjectKind::Segment {
            segment: new_line_segment,
        } = &new_line_obj.kind
        else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a segment: {new_line_obj:?}"
            ))));
        };

        let Segment::Line(new_line) = new_line_segment else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Segment is not a line: {new_line_segment:?}"
            ))));
        };

        let new_line_start_point_id = new_line.start;

        // Now add the coincident constraint between the previous end point and the new line's start point.
        let coincident = Coincident {
            segments: vec![previous_segment_end_point_id.into(), new_line_start_point_id.into()],
        };

        let (final_src_delta, final_scene_delta) = self
            .add_constraint(ctx, version, sketch, Constraint::Coincident(coincident))
            .await?;

        // Combine new objects from the line addition and the constraint addition.
        // Both add_line and add_constraint now populate new_objects correctly.
        let mut combined_new_objects = first_scene_delta.new_objects.clone();
        combined_new_objects.extend(final_scene_delta.new_objects);

        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: combined_new_objects,
            exec_outcome: final_scene_delta.exec_outcome,
        };

        Ok((final_src_delta, scene_graph_delta))
    }

    async fn edit_constraint(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        constraint_id: ObjectId,
        value_expression: String,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        let object = self.scene_graph.objects.get(constraint_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Object not found: {constraint_id:?}")))
        })?;
        if !matches!(&object.kind, ObjectKind::Constraint { .. }) {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a constraint: {constraint_id:?}"
            ))));
        }

        let mut new_ast = self.program.ast.clone();

        // Parse the expression string into an AST node.
        let (parsed, errors) = Program::parse(&value_expression)
            .map_err(|e| KclErrorWithOutputs::no_outputs(KclError::refactor(e.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing value expression: {errors:?}"
            ))));
        }
        let mut parsed = parsed.ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor("No AST produced from value expression".to_string()))
        })?;
        if parsed.ast.body.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "Empty value expression".to_string(),
            )));
        }
        let first = parsed.ast.body.remove(0);
        let ast::BodyItem::ExpressionStatement(expr_stmt) = first else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "Value expression must be a simple expression".to_string(),
            )));
        };

        let new_value: ast::BinaryPart = expr_stmt
            .inner
            .expression
            .try_into()
            .map_err(|e: String| KclErrorWithOutputs::no_outputs(KclError::refactor(e)))?;

        self.mutate_ast(
            &mut new_ast,
            constraint_id,
            AstMutateCommand::EditConstraintValue { value: new_value },
        )
        .map_err(KclErrorWithOutputs::no_outputs)?;

        self.execute_after_edit(
            ctx,
            sketch,
            sketch_block_ref,
            &mut new_ast,
            ExecuteAfterEditOptions {
                segment_ids_edited: Default::default(),
                edit_kind: EditDeleteKind::Edit,
                commit_solved_initial_guesses: true,
            },
        )
        .await
    }

    async fn edit_distance_constraint_label_position(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        constraint_id: ObjectId,
        label_position: Point2d<Number>,
        anchor_segment_ids: Vec<ObjectId>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        let object = self.scene_graph.objects.get(constraint_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Object not found: {constraint_id:?}")))
        })?;
        if !matches!(
            &object.kind,
            ObjectKind::Constraint {
                constraint: Constraint::Distance(_)
                    | Constraint::HorizontalDistance(_)
                    | Constraint::VerticalDistance(_)
                    | Constraint::Radius(_)
                    | Constraint::Diameter(_),
            }
        ) {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object does not support labelPosition: {constraint_id:?}"
            ))));
        }

        let label_position = to_ast_point2d_number(&label_position).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Could not convert label position to AST: {err}"
            )))
        })?;
        let mut new_ast = self.program.ast.clone();
        self.mutate_ast(
            &mut new_ast,
            constraint_id,
            AstMutateCommand::EditDistanceConstraintLabelPosition { label_position },
        )
        .map_err(KclErrorWithOutputs::no_outputs)?;
        let commit_solved_initial_guesses = self.next_edit_commits_solver_solutions.take().unwrap_or(true);

        self.execute_after_edit(
            ctx,
            sketch,
            sketch_block_ref,
            &mut new_ast,
            ExecuteAfterEditOptions {
                segment_ids_edited: anchor_segment_ids.into_iter().collect(),
                edit_kind: EditDeleteKind::Edit,
                commit_solved_initial_guesses,
            },
        )
        .await
    }

    /// Splitting a segment means creating a new segment, editing the old one, and then
    /// migrating a bunch of the constraints from the original segment to the new one
    /// (i.e. deleting them and re-adding them on the other segment).
    ///
    /// To keep this efficient we require as few executions as possible: we create the
    /// new segment first (to get its id), then do all edits and new constraints, and
    /// do all deletes at the end (since deletes invalidate ids).
    async fn batch_split_segment_operations(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        edit_segments: Vec<ExistingSegmentCtor>,
        add_constraints: Vec<Constraint>,
        delete_constraint_ids: Vec<ObjectId>,
        _new_segment_info: sketch::NewSegmentInfo,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // TODO: Check version.
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        let mut new_ast = self.program.ast.clone();
        let mut segment_ids_edited = AhashIndexSet::with_capacity_and_hasher(edit_segments.len(), Default::default());

        // Step 1: Edit segments
        for segment in edit_segments {
            segment_ids_edited.insert(segment.id);
            match segment.ctor {
                SegmentCtor::Point(ctor) => self
                    .edit_point(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Line(ctor) => self
                    .edit_line(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Arc(ctor) => self
                    .edit_arc(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Circle(ctor) => self
                    .edit_circle(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::ControlPointSpline(ctor) => self
                    .edit_control_point_spline(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
            }
        }

        // Step 2: Add all constraints
        for constraint in add_constraints {
            match constraint {
                Constraint::Coincident(coincident) => {
                    self.add_coincident(sketch, coincident, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Distance(distance) => {
                    self.add_distance(sketch, distance, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::EqualRadius(equal_radius) => {
                    self.add_equal_radius(sketch, equal_radius, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Fixed(fixed) => {
                    self.add_fixed_constraints(sketch, fixed.points, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::HorizontalDistance(distance) => {
                    self.add_horizontal_distance(sketch, distance, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::VerticalDistance(distance) => {
                    self.add_vertical_distance(sketch, distance, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Horizontal(horizontal) => {
                    self.add_horizontal(sketch, horizontal, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::LinesEqualLength(lines_equal_length) => {
                    self.add_lines_equal_length(sketch, lines_equal_length, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Midpoint(midpoint) => {
                    self.add_midpoint(sketch, midpoint, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Parallel(parallel) => {
                    self.add_parallel(sketch, parallel, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Perpendicular(perpendicular) => {
                    self.add_perpendicular(sketch, perpendicular, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Vertical(vertical) => {
                    self.add_vertical(sketch, vertical, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Diameter(diameter) => {
                    self.add_diameter(sketch, diameter, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Radius(radius) => {
                    self.add_radius(sketch, radius, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Symmetric(symmetric) => {
                    self.add_symmetric(sketch, symmetric, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Angle(angle) => {
                    self.add_angle(sketch, angle, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                Constraint::Tangent(tangent) => {
                    self.add_tangent(sketch, tangent, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
            }
        }

        // Step 3: Delete constraints (must be last since deletes can invalidate IDs)
        let constraint_ids_set = delete_constraint_ids.into_iter().collect::<AhashIndexSet<_>>();

        let has_constraint_deletions = !constraint_ids_set.is_empty();
        for constraint_id in constraint_ids_set {
            self.delete_constraint(&mut new_ast, sketch, constraint_id)
                .map_err(KclErrorWithOutputs::no_outputs)?;
        }

        // Step 4: Execute once at the end
        // Always use Edit (not DeleteNonSketch) because we're editing the sketch block, not deleting it
        // But we'll manually set invalidates_ids: true if we deleted constraints
        let (source_delta, mut scene_graph_delta) = self
            .execute_after_edit(
                ctx,
                sketch,
                sketch_block_ref,
                &mut new_ast,
                ExecuteAfterEditOptions {
                    segment_ids_edited,
                    edit_kind: EditDeleteKind::Edit,
                    commit_solved_initial_guesses: true,
                },
            )
            .await?;

        // If we deleted constraints, set invalidates_ids: true
        // This is because constraint deletion invalidates IDs, even though we're not deleting the sketch block
        if has_constraint_deletions {
            scene_graph_delta.invalidates_ids = true;
        }

        Ok((source_delta, scene_graph_delta))
    }

    async fn batch_tail_cut_operations(
        &mut self,
        ctx: &ExecutorContext,
        _version: Version,
        sketch: ObjectId,
        edit_segments: Vec<ExistingSegmentCtor>,
        add_constraints: Vec<Constraint>,
        delete_constraint_ids: Vec<ObjectId>,
        additional_edited_segment_ids: Vec<ObjectId>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        let sketch_block_ref =
            sketch_block_ref_from_id(&self.scene_graph, sketch).map_err(KclErrorWithOutputs::no_outputs)?;

        let mut new_ast = self.program.ast.clone();
        let mut segment_ids_edited = AhashIndexSet::with_capacity_and_hasher(edit_segments.len(), Default::default());

        // Step 1: Edit segments (usually a single segment for tail cut)
        for segment in edit_segments {
            segment_ids_edited.insert(segment.id);
            match segment.ctor {
                SegmentCtor::Point(ctor) => self
                    .edit_point(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Line(ctor) => self
                    .edit_line(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Arc(ctor) => self
                    .edit_arc(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::Circle(ctor) => self
                    .edit_circle(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
                SegmentCtor::ControlPointSpline(ctor) => self
                    .edit_control_point_spline(&mut new_ast, sketch, segment.id, ctor)
                    .map_err(KclErrorWithOutputs::no_outputs)?,
            }
        }

        segment_ids_edited.extend(additional_edited_segment_ids);

        // Step 2: Add coincident constraints
        for constraint in add_constraints {
            match constraint {
                Constraint::Coincident(coincident) => {
                    self.add_coincident(sketch, coincident, &mut new_ast)
                        .await
                        .map_err(KclErrorWithOutputs::no_outputs)?;
                }
                other => {
                    return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                        "unsupported constraint in tail cut batch: {other:?}"
                    ))));
                }
            }
        }

        // Step 3: Delete constraints (if any)
        let constraint_ids_set = delete_constraint_ids.into_iter().collect::<AhashIndexSet<_>>();

        let has_constraint_deletions = !constraint_ids_set.is_empty();
        for constraint_id in constraint_ids_set {
            self.delete_constraint(&mut new_ast, sketch, constraint_id)
                .map_err(KclErrorWithOutputs::no_outputs)?;
        }

        // Step 4: Single execute_after_edit
        // Always use Edit (not DeleteNonSketch) because we're editing the sketch block, not deleting it
        // But we'll manually set invalidates_ids: true if we deleted constraints
        let (source_delta, mut scene_graph_delta) = self
            .execute_after_edit(
                ctx,
                sketch,
                sketch_block_ref,
                &mut new_ast,
                ExecuteAfterEditOptions {
                    segment_ids_edited,
                    edit_kind: EditDeleteKind::Edit,
                    commit_solved_initial_guesses: true,
                },
            )
            .await?;

        // If we deleted constraints, set invalidates_ids: true
        // This is because constraint deletion invalidates IDs, even though we're not deleting the sketch block
        if has_constraint_deletions {
            scene_graph_delta.invalidates_ids = true;
        }

        Ok((source_delta, scene_graph_delta))
    }
}

impl FrontendState {
    pub async fn hack_set_program(&mut self, ctx: &ExecutorContext, program: Program) -> ExecResult<SetProgramOutcome> {
        self.program = program.clone();

        // Execute so that the objects are updated and available for the next
        // API call.
        // This always uses engine execution (not mock) so that things are cached.
        // Engine execution now runs freedom analysis automatically.
        // Keep existing checkpoints alive here. History may still reference
        // older committed sketch states across a direct-edit boundary, and a
        // checkpoint restore is a full state replacement anyway. We append a
        // fresh baseline checkpoint after the full execution below.
        // Clear the freedom cache since IDs might have changed after direct editing
        // and we're about to run freedom analysis which will repopulate it.
        self.point_freedom_cache.clear();
        match ctx.run_with_caching(program).await {
            Ok(outcome) => {
                let outcome = self.update_state_after_exec(outcome, true);
                let checkpoint_id = self
                    .create_sketch_checkpoint(outcome.clone())
                    .await
                    .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.msg)))?;
                Ok(SetProgramOutcome::Success {
                    scene_graph: Box::new(self.scene_graph_for_ui()),
                    exec_outcome: Box::new(outcome),
                    checkpoint_id: Some(checkpoint_id),
                })
            }
            Err(mut err) => {
                // Don't return an error just because execution failed. Instead,
                // update state as much as possible.
                let outcome = self.exec_outcome_from_exec_error(err.clone())?;
                self.update_state_after_exec(outcome, true);
                err.scene_graph = Some(self.scene_graph_for_ui());
                Ok(SetProgramOutcome::ExecFailure { error: Box::new(err) })
            }
        }
    }

    /// Decorate engine execution such that our state is updated and the scene
    /// graph is added to the return.
    pub async fn engine_execute(
        &mut self,
        ctx: &ExecutorContext,
        program: Program,
    ) -> Result<SceneGraphDelta, KclErrorWithOutputs> {
        self.program = program.clone();

        // Engine execution now runs freedom analysis automatically. Clear the
        // freedom cache since IDs might have changed after direct editing, and
        // we're about to run freedom analysis which will repopulate it.
        self.point_freedom_cache.clear();
        match ctx.run_with_caching(program).await {
            Ok(outcome) => {
                let outcome = self.update_state_after_exec(outcome, true);
                Ok(SceneGraphDelta {
                    new_graph: self.scene_graph_for_ui(),
                    exec_outcome: outcome,
                    // We don't know what the new objects are.
                    new_objects: Default::default(),
                    // We don't know if IDs were invalidated.
                    invalidates_ids: Default::default(),
                })
            }
            Err(mut err) => {
                // Update state as much as possible, even when there's an error.
                let outcome = self.exec_outcome_from_exec_error(err.clone())?;
                self.update_state_after_exec(outcome, true);
                err.scene_graph = Some(self.scene_graph_for_ui());
                Err(err)
            }
        }
    }

    fn exec_outcome_from_exec_error(&self, err: KclErrorWithOutputs) -> Result<ExecOutcome, KclErrorWithOutputs> {
        if matches!(err.error, KclError::EngineHangup { .. }) {
            // It's not ideal to special-case this, but this error is very
            // common during development, and it causes confusing downstream
            // errors that have nothing to do with the actual problem.
            return Err(err);
        }

        let KclErrorWithOutputs {
            error,
            mut non_fatal,
            variables,
            operations,
            artifact_graph,
            scene_objects,
            source_range_to_object,
            var_solutions,
            filenames,
            default_planes,
            ..
        } = err;

        if let Some(source_range) = error.source_ranges().first() {
            non_fatal.push(CompilationIssue::fatal(*source_range, error.get_message()));
        } else {
            non_fatal.push(CompilationIssue::fatal(SourceRange::synthetic(), error.get_message()));
        }

        Ok(ExecOutcome {
            variables,
            filenames,
            operations,
            artifact_graph,
            scene_objects,
            source_range_to_object,
            var_solutions,
            issues: non_fatal,
            default_planes,
        })
    }

    async fn add_point(
        &mut self,
        ctx: &ExecutorContext,
        sketch: ObjectId,
        ctor: PointCtor,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // Create updated KCL source from args.
        let at_ast = to_ast_point2d(&ctor.position)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let point_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(POINT_FN)),
            unlabeled: None,
            arguments: vec![ast::LabeledArg {
                label: Some(ast::Identifier::new(POINT_AT_PARAM)),
                arg: at_ast,
            }],
            digest: None,
            non_code_meta: Default::default(),
        })));

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self.scene_graph.objects.get(sketch_id.0).ok_or_else(|| {
            #[cfg(target_arch = "wasm32")]
            web_sys::console::error_1(
                &format!(
                    "Sketch not found; sketch_id={sketch_id:?}, self.scene_graph.objects={:#?}",
                    &self.scene_graph.objects
                )
                .into(),
            );
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article(),
            ))));
        };
        // Add the point to the AST of the sketch block.
        let mut new_ast = self.program.ast.clone();
        let (sketch_block_ref, _) = self
            .mutate_ast(
                &mut new_ast,
                sketch_id,
                AstMutateCommand::AddSketchBlockExprStmt { expr: point_ast },
            )
            .map_err(KclErrorWithOutputs::no_outputs)?;
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(&new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding point: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding point".to_string(),
            )));
        };

        let point_node_ref = find_sketch_block_added_item(&new_program.ast, &sketch_block_ref).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Source range of point not found in sketch block: {sketch_block_ref:?}; {err:?}"
            )))
        })?;

        // Make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // Truncate after the sketch block for mock execution.
        let mut truncated_program = new_program;
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::Add)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute.
        let outcome = ctx
            .run_mock(
                &truncated_program,
                &MockConfig::new_sketch_mode(sketch).no_freedom_analysis(),
            )
            .await?;

        let new_object_ids = {
            let make_err =
                |msg: String| KclErrorWithOutputs::from_error_outcome(KclError::refactor(msg), outcome.clone());
            let segment_id = outcome
                .source_range_to_object
                .get(&point_node_ref.range)
                .copied()
                .ok_or_else(|| make_err(format!("Source range of point not found: {point_node_ref:?}")))?;
            let segment_object = outcome
                .scene_objects
                .get(segment_id.0)
                .ok_or_else(|| make_err(format!("Segment not found: {segment_id:?}")))?;
            let ObjectKind::Segment { segment } = &segment_object.kind else {
                return Err(make_err(format!(
                    "Object is not a segment, it is {}",
                    segment_object.kind.human_friendly_kind_with_article()
                )));
            };
            let Segment::Point(_) = segment else {
                return Err(make_err(format!(
                    "Segment is not a point, it is {}",
                    segment.human_friendly_kind_with_article()
                )));
            };
            vec![segment_id]
        };
        let src_delta = SourceDelta { text: new_source };
        // Uses .no_freedom_analysis() so freedom_analysis: false
        let outcome = self.update_state_after_exec(outcome, false);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: new_object_ids,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    async fn add_line(
        &mut self,
        ctx: &ExecutorContext,
        sketch: ObjectId,
        ctor: LineCtor,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // Create updated KCL source from args.
        let start_ast = to_ast_point2d(&ctor.start)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let end_ast = to_ast_point2d(&ctor.end)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let mut arguments = vec![
            ast::LabeledArg {
                label: Some(ast::Identifier::new(LINE_START_PARAM)),
                arg: start_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(LINE_END_PARAM)),
                arg: end_ast,
            },
        ];
        // Add construction kwarg if construction is Some(true)
        if ctor.construction == Some(true) {
            arguments.push(ast::LabeledArg {
                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                    value: ast::LiteralValue::Bool(true),
                    raw: "true".to_string(),
                    digest: None,
                }))),
            });
        }
        let line_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(LINE_FN)),
            unlabeled: None,
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self.scene_graph.objects.get(sketch_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article(),
            ))));
        };
        // Add the line to the AST of the sketch block.
        let mut new_ast = self.program.ast.clone();
        let (sketch_block_ref, _) = self
            .mutate_ast(
                &mut new_ast,
                sketch_id,
                AstMutateCommand::AddSketchBlockExprStmt { expr: line_ast },
            )
            .map_err(KclErrorWithOutputs::no_outputs)?;
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(&new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding line: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding line".to_string(),
            )));
        };

        let line_node_ref = find_sketch_block_added_item(&new_program.ast, &sketch_block_ref).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Source range of line not found in sketch block: {sketch_block_ref:?}; {err:?}"
            )))
        })?;

        // Make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // Truncate after the sketch block for mock execution.
        let mut truncated_program = new_program;
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::Add)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute.
        let outcome = ctx
            .run_mock(
                &truncated_program,
                &MockConfig::new_sketch_mode(sketch).no_freedom_analysis(),
            )
            .await?;

        let new_object_ids = {
            let make_err =
                |msg: String| KclErrorWithOutputs::from_error_outcome(KclError::refactor(msg), outcome.clone());
            let segment_id = outcome
                .source_range_to_object
                .get(&line_node_ref.range)
                .copied()
                .ok_or_else(|| make_err(format!("Source range of line not found: {line_node_ref:?}")))?;
            let segment_object = outcome
                .scene_object_by_id(segment_id)
                .ok_or_else(|| make_err(format!("Segment not found: {segment_id:?}")))?;
            let ObjectKind::Segment { segment } = &segment_object.kind else {
                return Err(make_err(format!(
                    "Object is not a segment, it is {}",
                    segment_object.kind.human_friendly_kind_with_article()
                )));
            };
            let Segment::Line(line) = segment else {
                return Err(make_err(format!(
                    "Segment is not a line, it is {}",
                    segment.human_friendly_kind_with_article()
                )));
            };
            vec![line.start, line.end, segment_id]
        };
        let src_delta = SourceDelta { text: new_source };
        // Uses .no_freedom_analysis() so freedom_analysis: false
        let outcome = self.update_state_after_exec(outcome, false);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: new_object_ids,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    async fn add_arc(
        &mut self,
        ctx: &ExecutorContext,
        sketch: ObjectId,
        ctor: ArcCtor,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // Create updated KCL source from args.
        let start_ast = to_ast_point2d(&ctor.start)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let end_ast = to_ast_point2d(&ctor.end)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let center_ast = to_ast_point2d(&ctor.center)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let mut arguments = vec![
            ast::LabeledArg {
                label: Some(ast::Identifier::new(ARC_START_PARAM)),
                arg: start_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(ARC_END_PARAM)),
                arg: end_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(ARC_CENTER_PARAM)),
                arg: center_ast,
            },
        ];
        // Add construction kwarg if construction is Some(true)
        if ctor.construction == Some(true) {
            arguments.push(ast::LabeledArg {
                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                    value: ast::LiteralValue::Bool(true),
                    raw: "true".to_string(),
                    digest: None,
                }))),
            });
        }
        let arc_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(ARC_FN)),
            unlabeled: None,
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self.scene_graph.objects.get(sketch_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article(),
            ))));
        };
        // Add the arc to the AST of the sketch block.
        let mut new_ast = self.program.ast.clone();
        let (sketch_block_ref, _) = self
            .mutate_ast(
                &mut new_ast,
                sketch_id,
                AstMutateCommand::AddSketchBlockExprStmt { expr: arc_ast },
            )
            .map_err(KclErrorWithOutputs::no_outputs)?;
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(&new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding arc: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding arc".to_string(),
            )));
        };

        let arc_node_ref = find_sketch_block_added_item(&new_program.ast, &sketch_block_ref).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Source range of arc not found in sketch block: {sketch_block_ref:?}; {err:?}"
            )))
        })?;

        // Make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // Truncate after the sketch block for mock execution.
        let mut truncated_program = new_program;
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::Add)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute.
        let outcome = ctx
            .run_mock(
                &truncated_program,
                &MockConfig::new_sketch_mode(sketch).no_freedom_analysis(),
            )
            .await?;

        let new_object_ids = {
            let make_err =
                |msg: String| KclErrorWithOutputs::from_error_outcome(KclError::refactor(msg), outcome.clone());
            let segment_id = outcome
                .source_range_to_object
                .get(&arc_node_ref.range)
                .copied()
                .ok_or_else(|| make_err(format!("Source range of arc not found: {arc_node_ref:?}")))?;
            let segment_object = outcome
                .scene_objects
                .get(segment_id.0)
                .ok_or_else(|| make_err(format!("Segment not found: {segment_id:?}")))?;
            let ObjectKind::Segment { segment } = &segment_object.kind else {
                return Err(make_err(format!(
                    "Object is not a segment, it is {}",
                    segment_object.kind.human_friendly_kind_with_article()
                )));
            };
            let Segment::Arc(arc) = segment else {
                return Err(make_err(format!(
                    "Segment is not an arc, it is {}",
                    segment.human_friendly_kind_with_article()
                )));
            };
            vec![arc.start, arc.end, arc.center, segment_id]
        };
        let src_delta = SourceDelta { text: new_source };
        // Uses .no_freedom_analysis() so freedom_analysis: false
        let outcome = self.update_state_after_exec(outcome, false);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: new_object_ids,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    async fn add_circle(
        &mut self,
        ctx: &ExecutorContext,
        sketch: ObjectId,
        ctor: CircleCtor,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // Create updated KCL source from args.
        let start_ast = to_ast_point2d(&ctor.start)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let center_ast = to_ast_point2d(&ctor.center)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let mut arguments = vec![
            ast::LabeledArg {
                label: Some(ast::Identifier::new(CIRCLE_START_PARAM)),
                arg: start_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(CIRCLE_CENTER_PARAM)),
                arg: center_ast,
            },
        ];
        // Add construction kwarg if construction is Some(true)
        if ctor.construction == Some(true) {
            arguments.push(ast::LabeledArg {
                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                    value: ast::LiteralValue::Bool(true),
                    raw: "true".to_string(),
                    digest: None,
                }))),
            });
        }
        let circle_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(CIRCLE_FN)),
            unlabeled: None,
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self.scene_graph.objects.get(sketch_id.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article(),
            ))));
        };
        // Add the circle to the AST of the sketch block.
        let mut new_ast = self.program.ast.clone();
        let (sketch_block_ref, _) = self
            .mutate_ast(
                &mut new_ast,
                sketch_id,
                AstMutateCommand::AddSketchBlockVarDecl {
                    prefix: CIRCLE_VARIABLE.to_owned(),
                    expr: circle_ast,
                },
            )
            .map_err(KclErrorWithOutputs::no_outputs)?;
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(&new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding circle: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding circle".to_string(),
            )));
        };

        let circle_node_ref = find_sketch_block_added_item(&new_program.ast, &sketch_block_ref).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Source range of circle not found in sketch block: {sketch_block_ref:?}; {err:?}"
            )))
        })?;

        // Make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // Truncate after the sketch block for mock execution.
        let mut truncated_program = new_program;
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::Add)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute.
        let outcome = ctx
            .run_mock(
                &truncated_program,
                &MockConfig::new_sketch_mode(sketch).no_freedom_analysis(),
            )
            .await?;

        let new_object_ids = {
            let make_err =
                |msg: String| KclErrorWithOutputs::from_error_outcome(KclError::refactor(msg), outcome.clone());
            let segment_id = outcome
                .source_range_to_object
                .get(&circle_node_ref.range)
                .copied()
                .ok_or_else(|| make_err(format!("Source range of circle not found: {circle_node_ref:?}")))?;
            let segment_object = outcome
                .scene_objects
                .get(segment_id.0)
                .ok_or_else(|| make_err(format!("Segment not found: {segment_id:?}")))?;
            let ObjectKind::Segment { segment } = &segment_object.kind else {
                return Err(make_err(format!(
                    "Object is not a segment, it is {}",
                    segment_object.kind.human_friendly_kind_with_article()
                )));
            };
            let Segment::Circle(circle) = segment else {
                return Err(make_err(format!(
                    "Segment is not a circle, it is {}",
                    segment.human_friendly_kind_with_article()
                )));
            };
            vec![circle.start, circle.center, segment_id]
        };
        let src_delta = SourceDelta { text: new_source };
        // Uses .no_freedom_analysis() so freedom_analysis: false
        let outcome = self.update_state_after_exec(outcome, false);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: new_object_ids,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    async fn add_control_point_spline(
        &mut self,
        ctx: &ExecutorContext,
        sketch: ObjectId,
        ctor: ControlPointSplineCtor,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        let new_program = ensure_control_point_spline_experimental_features(&self.program)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        let points_ast = to_ast_point2d_array(&ctor.points)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        let mut arguments = vec![ast::LabeledArg {
            label: Some(ast::Identifier::new(CONTROL_POINT_SPLINE_POINTS_PARAM)),
            arg: points_ast,
        }];
        if ctor.construction == Some(true) {
            arguments.push(ast::LabeledArg {
                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                    value: ast::LiteralValue::Bool(true),
                    raw: "true".to_string(),
                    digest: None,
                }))),
            });
        }
        let spline_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(CONTROL_POINT_SPLINE_FN)),
            unlabeled: None,
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));

        let sketch_object = self.scene_graph.objects.get(sketch.0).ok_or_else(|| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!("Sketch not found: {sketch:?}")))
        })?;
        let ObjectKind::Sketch(_) = &sketch_object.kind else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Object is not a sketch, it is {}",
                sketch_object.kind.human_friendly_kind_with_article(),
            ))));
        };

        let mut new_ast = new_program.ast.clone();
        let (sketch_block_ref, _) = self
            .mutate_ast(
                &mut new_ast,
                sketch,
                AstMutateCommand::AddSketchBlockExprStmt { expr: spline_ast },
            )
            .map_err(KclErrorWithOutputs::no_outputs)?;
        let new_source = source_from_ast(&new_ast);
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding controlPointSpline: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding controlPointSpline".to_string(),
            )));
        };

        let spline_node_ref = find_sketch_block_added_item(&new_program.ast, &sketch_block_ref).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Source range of controlPointSpline not found in sketch block: {sketch_block_ref:?}; {err:?}"
            )))
        })?;

        self.program = new_program.clone();

        let mut truncated_program = new_program;
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::Add)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        let outcome = ctx
            .run_mock(
                &truncated_program,
                &MockConfig::new_sketch_mode(sketch).no_freedom_analysis(),
            )
            .await?;

        let new_object_ids = {
            let make_err =
                |msg: String| KclErrorWithOutputs::from_error_outcome(KclError::refactor(msg), outcome.clone());
            let segment_id = outcome
                .source_range_to_object
                .get(&spline_node_ref.range)
                .copied()
                .ok_or_else(|| {
                    make_err(format!(
                        "Source range of controlPointSpline not found: {spline_node_ref:?}"
                    ))
                })?;
            let segment_object = outcome
                .scene_objects
                .get(segment_id.0)
                .ok_or_else(|| make_err(format!("Segment not found: {segment_id:?}")))?;
            let ObjectKind::Segment { segment } = &segment_object.kind else {
                return Err(make_err(format!(
                    "Object is not a segment, it is {}",
                    segment_object.kind.human_friendly_kind_with_article()
                )));
            };
            let Segment::ControlPointSpline(spline) = segment else {
                return Err(make_err(format!(
                    "Segment is not a control point spline, it is {}",
                    segment.human_friendly_kind_with_article()
                )));
            };

            let mut ids = outcome
                .scene_objects
                .iter()
                .filter_map(|obj| match &obj.kind {
                    ObjectKind::Segment {
                        segment: Segment::Line(line),
                    } if line.owner == Some(segment_id) => Some(obj.id),
                    _ => None,
                })
                .collect::<Vec<_>>();
            ids.extend(spline.controls.clone());
            ids.push(segment_id);
            ids
        };
        let src_delta = SourceDelta { text: new_source };
        let outcome = self.update_state_after_exec(outcome, false);
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: new_object_ids,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    fn edit_point(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        point: ObjectId,
        ctor: PointCtor,
    ) -> Result<(), KclError> {
        // Create updated KCL source from args.
        let new_at_ast = to_ast_point2d(&ctor.position).map_err(|err| KclError::refactor(err.to_string()))?;

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch.segments.iter().find(|o| **o == point).ok_or_else(|| {
            KclError::refactor(format!("Point not found in sketch: point={point:?}, sketch={sketch:?}"))
        })?;
        // Look up existing point.
        let point_id = point;
        let point_object = self
            .scene_graph
            .objects
            .get(point_id.0)
            .ok_or_else(|| KclError::refactor(format!("Point not found in scene graph: point={point:?}")))?;
        let ObjectKind::Segment {
            segment: Segment::Point(point),
        } = &point_object.kind
        else {
            return Err(KclError::refactor(format!(
                "Object is not a point segment: {point_object:?}"
            )));
        };

        // If the point is part of a line or arc, edit the line/arc instead.
        if let Some(owner_id) = point.owner {
            let owner_object = self.scene_graph.objects.get(owner_id.0).ok_or_else(|| {
                KclError::refactor(format!(
                    "Internal: Owner of point not found in scene graph: owner={owner_id:?}",
                ))
            })?;
            let ObjectKind::Segment { segment } = &owner_object.kind else {
                return Err(KclError::refactor(format!(
                    "Internal: Owner of point is not a segment, but found {}",
                    owner_object.kind.human_friendly_kind_with_article()
                )));
            };

            // Handle Line owner
            if let Segment::Line(line) = segment {
                let SegmentCtor::Line(line_ctor) = &line.ctor else {
                    return Err(KclError::refactor(format!(
                        "Internal: Owner of point does not have line ctor, but found {}",
                        line.ctor.human_friendly_kind_with_article()
                    )));
                };
                let mut line_ctor = line_ctor.clone();
                // Which end of the line is this point?
                if line.start == point_id {
                    line_ctor.start = ctor.position;
                } else if line.end == point_id {
                    line_ctor.end = ctor.position;
                } else {
                    return Err(KclError::refactor(format!(
                        "Internal: Point is not part of owner's line segment: point={point_id:?}, line={owner_id:?}"
                    )));
                }
                return self.edit_line(new_ast, sketch_id, owner_id, line_ctor);
            }

            // Handle Arc owner
            if let Segment::Arc(arc) = segment {
                let SegmentCtor::Arc(arc_ctor) = &arc.ctor else {
                    return Err(KclError::refactor(format!(
                        "Internal: Owner of point does not have arc ctor, but found {}",
                        arc.ctor.human_friendly_kind_with_article()
                    )));
                };
                let mut arc_ctor = arc_ctor.clone();
                // Which point of the arc is this? (center, start, or end)
                if arc.center == point_id {
                    arc_ctor.center = ctor.position;
                } else if arc.start == point_id {
                    arc_ctor.start = ctor.position;
                } else if arc.end == point_id {
                    arc_ctor.end = ctor.position;
                } else {
                    return Err(KclError::refactor(format!(
                        "Internal: Point is not part of owner's arc segment: point={point_id:?}, arc={owner_id:?}"
                    )));
                }
                return self.edit_arc(new_ast, sketch_id, owner_id, arc_ctor);
            }

            // Handle Circle owner
            if let Segment::Circle(circle) = segment {
                let SegmentCtor::Circle(circle_ctor) = &circle.ctor else {
                    return Err(KclError::refactor(format!(
                        "Internal: Owner of point does not have circle ctor, but found {}",
                        circle.ctor.human_friendly_kind_with_article()
                    )));
                };
                let mut circle_ctor = circle_ctor.clone();
                if circle.center == point_id {
                    circle_ctor.center = ctor.position;
                } else if circle.start == point_id {
                    circle_ctor.start = ctor.position;
                } else {
                    return Err(KclError::refactor(format!(
                        "Internal: Point is not part of owner's circle segment: point={point_id:?}, circle={owner_id:?}"
                    )));
                }
                return self.edit_circle(new_ast, sketch_id, owner_id, circle_ctor);
            }

            if let Segment::ControlPointSpline(spline) = segment {
                let SegmentCtor::ControlPointSpline(spline_ctor) = &spline.ctor else {
                    return Err(KclError::refactor(format!(
                        "Internal: Owner of point does not have controlPointSpline ctor, but found {}",
                        spline.ctor.human_friendly_kind_with_article()
                    )));
                };
                let mut spline_ctor = spline_ctor.clone();
                let Some(control_index) = spline.controls.iter().position(|id| *id == point_id) else {
                    return Err(KclError::refactor(format!(
                        "Internal: Point is not part of owner's controlPointSpline segment: point={point_id:?}, spline={owner_id:?}"
                    )));
                };
                spline_ctor.points[control_index] = ctor.position;
                return self.edit_control_point_spline(new_ast, sketch_id, owner_id, spline_ctor);
            }

            // If owner is neither Line, Arc, nor Circle, allow editing the point directly
            // (fall through to the point editing logic below)
        }

        // Modify the point AST.
        self.mutate_ast(new_ast, point_id, AstMutateCommand::EditPoint { at: new_at_ast })?;
        Ok(())
    }

    fn edit_line(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        line: ObjectId,
        ctor: LineCtor,
    ) -> Result<(), KclError> {
        // Create updated KCL source from args.
        let new_start_ast = to_ast_point2d(&ctor.start).map_err(|err| KclError::refactor(err.to_string()))?;
        let new_end_ast = to_ast_point2d(&ctor.end).map_err(|err| KclError::refactor(err.to_string()))?;

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch
            .segments
            .iter()
            .find(|o| **o == line)
            .ok_or_else(|| KclError::refactor(format!("Line not found in sketch: line={line:?}, sketch={sketch:?}")))?;
        // Look up existing line.
        let line_id = line;
        let line_object = self
            .scene_graph
            .objects
            .get(line_id.0)
            .ok_or_else(|| KclError::refactor(format!("Line not found in scene graph: line={line:?}")))?;
        let ObjectKind::Segment { .. } = &line_object.kind else {
            let kind = line_object.kind.human_friendly_kind_with_article();
            return Err(KclError::refactor(format!(
                "This constraint only works on Segments, but you selected {kind}"
            )));
        };

        // Modify the line AST.
        self.mutate_ast(
            new_ast,
            line_id,
            AstMutateCommand::EditLine {
                start: new_start_ast,
                end: new_end_ast,
                construction: ctor.construction,
            },
        )?;
        Ok(())
    }

    fn edit_arc(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        arc: ObjectId,
        ctor: ArcCtor,
    ) -> Result<(), KclError> {
        // Create updated KCL source from args.
        let new_start_ast = to_ast_point2d(&ctor.start).map_err(|err| KclError::refactor(err.to_string()))?;
        let new_end_ast = to_ast_point2d(&ctor.end).map_err(|err| KclError::refactor(err.to_string()))?;
        let new_center_ast = to_ast_point2d(&ctor.center).map_err(|err| KclError::refactor(err.to_string()))?;

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch
            .segments
            .iter()
            .find(|o| **o == arc)
            .ok_or_else(|| KclError::refactor(format!("Arc not found in sketch: arc={arc:?}, sketch={sketch:?}")))?;
        // Look up existing arc.
        let arc_id = arc;
        let arc_object = self
            .scene_graph
            .objects
            .get(arc_id.0)
            .ok_or_else(|| KclError::refactor(format!("Arc not found in scene graph: arc={arc:?}")))?;
        let ObjectKind::Segment { .. } = &arc_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {arc_object:?}")));
        };

        // Modify the arc AST.
        self.mutate_ast(
            new_ast,
            arc_id,
            AstMutateCommand::EditArc {
                start: new_start_ast,
                end: new_end_ast,
                center: new_center_ast,
                construction: ctor.construction,
            },
        )?;
        Ok(())
    }

    fn edit_circle(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        circle: ObjectId,
        ctor: CircleCtor,
    ) -> Result<(), KclError> {
        // Create updated KCL source from args.
        let new_start_ast = to_ast_point2d(&ctor.start).map_err(|err| KclError::refactor(err.to_string()))?;
        let new_center_ast = to_ast_point2d(&ctor.center).map_err(|err| KclError::refactor(err.to_string()))?;

        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch.segments.iter().find(|o| **o == circle).ok_or_else(|| {
            KclError::refactor(format!(
                "Circle not found in sketch: circle={circle:?}, sketch={sketch:?}"
            ))
        })?;
        // Look up existing circle.
        let circle_id = circle;
        let circle_object = self
            .scene_graph
            .objects
            .get(circle_id.0)
            .ok_or_else(|| KclError::refactor(format!("Circle not found in scene graph: circle={circle:?}")))?;
        let ObjectKind::Segment { .. } = &circle_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment: {circle_object:?}"
            )));
        };

        // Modify the circle AST.
        self.mutate_ast(
            new_ast,
            circle_id,
            AstMutateCommand::EditCircle {
                start: new_start_ast,
                center: new_center_ast,
                construction: ctor.construction,
            },
        )?;
        Ok(())
    }

    fn edit_control_point_spline(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        spline: ObjectId,
        ctor: ControlPointSplineCtor,
    ) -> Result<(), KclError> {
        let points_ast = to_ast_point2d_array(&ctor.points).map_err(|err| KclError::refactor(err.to_string()))?;

        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch.segments.iter().find(|o| **o == spline).ok_or_else(|| {
            KclError::refactor(format!(
                "Control point spline not found in sketch: spline={spline:?}, sketch={sketch:?}"
            ))
        })?;

        let spline_object =
            self.scene_graph.objects.get(spline.0).ok_or_else(|| {
                KclError::refactor(format!("Control point spline not found in scene graph: {spline:?}"))
            })?;
        let ObjectKind::Segment { .. } = &spline_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment: {spline_object:?}"
            )));
        };

        self.mutate_ast(
            new_ast,
            spline,
            AstMutateCommand::EditControlPointSpline {
                points: points_ast,
                construction: ctor.construction,
            },
        )?;
        Ok(())
    }

    fn delete_segment(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        segment_id: ObjectId,
    ) -> Result<(), KclError> {
        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch.segments.iter().find(|o| **o == segment_id).ok_or_else(|| {
            KclError::refactor(format!(
                "Segment not found in sketch: segment={segment_id:?}, sketch={sketch:?}"
            ))
        })?;
        // Look up existing segment.
        let segment_object =
            self.scene_graph.objects.get(segment_id.0).ok_or_else(|| {
                KclError::refactor(format!("Segment not found in scene graph: segment={segment_id:?}"))
            })?;
        let ObjectKind::Segment { .. } = &segment_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment, it is {}",
                segment_object.kind.human_friendly_kind_with_article()
            )));
        };

        // Modify the AST to remove the segment.
        self.mutate_ast(new_ast, segment_id, AstMutateCommand::DeleteNode)?;
        Ok(())
    }

    fn delete_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        sketch: ObjectId,
        constraint_id: ObjectId,
    ) -> Result<(), KclError> {
        // Look up existing sketch.
        let sketch_id = sketch;
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        sketch
            .constraints
            .iter()
            .find(|o| **o == constraint_id)
            .ok_or_else(|| {
                KclError::refactor(format!(
                    "Constraint not found in sketch: constraint={constraint_id:?}, sketch={sketch:?}"
                ))
            })?;
        // Look up existing constraint.
        let constraint_object = self.scene_graph.objects.get(constraint_id.0).ok_or_else(|| {
            KclError::refactor(format!(
                "Constraint not found in scene graph: constraint={constraint_id:?}"
            ))
        })?;
        let ObjectKind::Constraint { .. } = &constraint_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a constraint, it is {}",
                constraint_object.kind.human_friendly_kind_with_article()
            )));
        };

        // Modify the AST to remove the constraint.
        self.mutate_ast(new_ast, constraint_id, AstMutateCommand::DeleteNode)?;
        Ok(())
    }

    fn edit_coincident_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        segments: Vec<ConstraintSegment>,
    ) -> Result<(), KclError> {
        if segments.len() < 2 {
            return Err(KclError::refactor(format!(
                "Coincident constraint must have at least 2 inputs, got {}",
                segments.len()
            )));
        }

        let segment_asts = segments
            .iter()
            .map(|segment| self.coincident_segment_to_ast(segment, new_ast))
            .collect::<Result<Vec<_>, _>>()?;

        let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
            elements: segment_asts,
            digest: None,
            non_code_meta: Default::default(),
        })));

        self.mutate_ast(
            new_ast,
            constraint_id,
            AstMutateCommand::EditCallUnlabeled { arg: array_expr },
        )?;
        Ok(())
    }

    fn edit_horizontal_points_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        points: Vec<ConstraintSegment>,
    ) -> Result<(), KclError> {
        self.edit_axis_points_constraint(new_ast, constraint_id, points, "Horizontal")
    }

    fn edit_vertical_points_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        points: Vec<ConstraintSegment>,
    ) -> Result<(), KclError> {
        self.edit_axis_points_constraint(new_ast, constraint_id, points, "Vertical")
    }

    fn edit_axis_points_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        points: Vec<ConstraintSegment>,
        constraint_name: &str,
    ) -> Result<(), KclError> {
        if points.len() < 2 {
            return Err(KclError::refactor(format!(
                "{constraint_name} points constraint must have at least 2 points, got {}",
                points.len()
            )));
        }

        let point_asts = points
            .iter()
            .map(|point| self.axis_constraint_segment_to_ast(point, new_ast))
            .collect::<Result<Vec<_>, _>>()?;

        let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
            elements: point_asts,
            digest: None,
            non_code_meta: Default::default(),
        })));

        self.mutate_ast(
            new_ast,
            constraint_id,
            AstMutateCommand::EditCallUnlabeled { arg: array_expr },
        )?;
        Ok(())
    }

    /// updates the equalLength constraint with the given lines
    fn edit_equal_length_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        lines: Vec<ObjectId>,
    ) -> Result<(), KclError> {
        if lines.len() < 2 {
            return Err(KclError::refactor(format!(
                "Lines equal length constraint must have at least 2 lines, got {}",
                lines.len()
            )));
        }

        let line_asts = lines
            .iter()
            .map(|line_id| {
                let line_object = self
                    .scene_graph
                    .objects
                    .get(line_id.0)
                    .ok_or_else(|| KclError::refactor(format!("Line not found: {line_id:?}")))?;
                let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
                    let kind = line_object.kind.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "This constraint only works on Segments, but you selected {kind}"
                    )));
                };
                let Segment::Line(_) = line_segment else {
                    let kind = line_segment.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "Only lines can be made equal length, but you selected {kind}"
                    )));
                };

                get_or_insert_ast_reference(new_ast, &line_object.source.clone(), LINE_VARIABLE, None)
            })
            .collect::<Result<Vec<_>, _>>()?;

        let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
            elements: line_asts,
            digest: None,
            non_code_meta: Default::default(),
        })));

        self.mutate_ast(
            new_ast,
            constraint_id,
            AstMutateCommand::EditCallUnlabeled { arg: array_expr },
        )?;
        Ok(())
    }

    /// Updates the parallel constraint with the given lines.
    fn edit_parallel_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        lines: Vec<ObjectId>,
    ) -> Result<(), KclError> {
        if lines.len() < 2 {
            return Err(KclError::refactor(format!(
                "Parallel constraint must have at least 2 lines, got {}",
                lines.len()
            )));
        }

        let line_asts = lines
            .iter()
            .map(|line_id| {
                let line_object = self
                    .scene_graph
                    .objects
                    .get(line_id.0)
                    .ok_or_else(|| KclError::refactor(format!("Line not found: {line_id:?}")))?;
                let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
                    let kind = line_object.kind.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "This constraint only works on Segments, but you selected {kind}"
                    )));
                };
                let Segment::Line(_) = line_segment else {
                    let kind = line_segment.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "Only lines can be made parallel, but you selected {kind}"
                    )));
                };

                get_or_insert_ast_reference(new_ast, &line_object.source.clone(), LINE_VARIABLE, None)
            })
            .collect::<Result<Vec<_>, _>>()?;

        let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
            elements: line_asts,
            digest: None,
            non_code_meta: Default::default(),
        })));

        self.mutate_ast(
            new_ast,
            constraint_id,
            AstMutateCommand::EditCallUnlabeled { arg: array_expr },
        )?;
        Ok(())
    }

    /// Updates the equalRadius constraint with the given segments.
    fn edit_equal_radius_constraint(
        &mut self,
        new_ast: &mut ast::Node<ast::Program>,
        constraint_id: ObjectId,
        input: Vec<ObjectId>,
    ) -> Result<(), KclError> {
        if input.len() < 2 {
            return Err(KclError::refactor(format!(
                "equalRadius constraint must have at least 2 segments, got {}",
                input.len()
            )));
        }

        let input_asts = input
            .iter()
            .map(|segment_id| self.equal_radius_segment_id_to_ast_reference(*segment_id, new_ast))
            .collect::<Result<Vec<_>, _>>()?;

        let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
            elements: input_asts,
            digest: None,
            non_code_meta: Default::default(),
        })));

        self.mutate_ast(
            new_ast,
            constraint_id,
            AstMutateCommand::EditCallUnlabeled { arg: array_expr },
        )?;
        Ok(())
    }

    async fn execute_after_edit(
        &mut self,
        ctx: &ExecutorContext,
        sketch: ObjectId,
        sketch_block_ref: AstNodeRef,
        new_ast: &mut ast::Node<ast::Program>,
        options: ExecuteAfterEditOptions,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        let ExecuteAfterEditOptions {
            segment_ids_edited,
            edit_kind,
            commit_solved_initial_guesses,
        } = options;

        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after editing: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after editing".to_string(),
            )));
        };

        // TODO: sketch-api: make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // Truncate after the sketch block for mock execution.
        let is_delete = edit_kind.is_delete();
        let truncated_program = {
            let mut truncated_program = new_program;
            only_sketch_block(
                &mut truncated_program.ast,
                &sketch_block_ref,
                edit_kind.to_change_kind(),
            )
            .map_err(KclErrorWithOutputs::no_outputs)?;
            truncated_program
        };

        // Execute.
        let drag_anchors = self.next_segment_drag_anchors.take().unwrap_or_default();
        let mock_config = MockConfig {
            sketch_block_id: Some(sketch),
            freedom_analysis: is_delete,
            segment_ids_edited: segment_ids_edited.clone(),
            drag_anchors,
            ..Default::default()
        };
        let outcome = ctx.run_mock(&truncated_program, &mock_config).await?;

        // Uses freedom_analysis: is_delete
        let outcome = self.update_state_after_exec(outcome, is_delete);

        let src_delta = if commit_solved_initial_guesses {
            self.commit_var_solutions_to_program(&outcome, "editing")?
        } else {
            SourceDelta { text: new_source }
        };
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: is_delete,
            new_objects: Vec::new(),
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    async fn execute_after_delete_sketch(
        &mut self,
        ctx: &ExecutorContext,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after editing: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after editing".to_string(),
            )));
        };

        // Make sure to only set this if there are no errors.
        self.program = new_program.clone();

        // We deleted the entire sketch block. It doesn't make sense to truncate
        // and execute only the sketch block. We execute the whole program with
        // a real engine.

        // Execute.
        let outcome = ctx.run_with_caching(new_program).await?;
        let freedom_analysis_ran = true;

        let outcome = self.update_state_after_exec(outcome, freedom_analysis_ran);

        let src_delta = SourceDelta { text: new_source };
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: true,
            new_objects: Vec::new(),
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    /// Map a point object id into an AST reference expression for use in
    /// constraints. If the point is owned by a segment (line or arc), we
    /// reference the appropriate property on that segment (e.g. `line1.start`,
    /// `arc1.center`). Otherwise we reference the point directly.
    fn point_id_to_ast_reference(
        &self,
        point_id: ObjectId,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        let point_object = self
            .scene_graph
            .objects
            .get(point_id.0)
            .ok_or_else(|| KclError::refactor(format!("Point not found: {point_id:?}")))?;
        let ObjectKind::Segment { segment: point_segment } = &point_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {point_object:?}")));
        };
        let Segment::Point(point) = point_segment else {
            return Err(KclError::refactor(format!(
                "Only points are currently supported: {point_object:?}"
            )));
        };

        if let Some(owner_id) = point.owner {
            let owner_object = self.scene_graph.objects.get(owner_id.0).ok_or_else(|| {
                KclError::refactor(format!(
                    "Owner of point not found in scene graph: point={point_id:?}, owner={owner_id:?}"
                ))
            })?;
            let ObjectKind::Segment { segment: owner_segment } = &owner_object.kind else {
                return Err(KclError::refactor(format!(
                    "Owner of point is not a segment, but found {}",
                    owner_object.kind.human_friendly_kind_with_article()
                )));
            };

            match owner_segment {
                Segment::Line(line) => {
                    let property = if line.start == point_id {
                        LINE_PROPERTY_START
                    } else if line.end == point_id {
                        LINE_PROPERTY_END
                    } else {
                        return Err(KclError::refactor(format!(
                            "Internal: Point is not part of owner's line segment: point={point_id:?}, line={owner_id:?}"
                        )));
                    };
                    get_or_insert_ast_reference(new_ast, &owner_object.source, LINE_VARIABLE, Some(property))
                }
                Segment::Arc(arc) => {
                    let property = if arc.start == point_id {
                        ARC_PROPERTY_START
                    } else if arc.end == point_id {
                        ARC_PROPERTY_END
                    } else if arc.center == point_id {
                        ARC_PROPERTY_CENTER
                    } else {
                        return Err(KclError::refactor(format!(
                            "Internal: Point is not part of owner's arc segment: point={point_id:?}, arc={owner_id:?}"
                        )));
                    };
                    get_or_insert_ast_reference(new_ast, &owner_object.source, ARC_VARIABLE, Some(property))
                }
                Segment::Circle(circle) => {
                    let property = if circle.start == point_id {
                        CIRCLE_PROPERTY_START
                    } else if circle.center == point_id {
                        CIRCLE_PROPERTY_CENTER
                    } else {
                        return Err(KclError::refactor(format!(
                            "Internal: Point is not part of owner's circle segment: point={point_id:?}, circle={owner_id:?}"
                        )));
                    };
                    get_or_insert_ast_reference(new_ast, &owner_object.source, CIRCLE_VARIABLE, Some(property))
                }
                Segment::ControlPointSpline(spline) => {
                    let Some(index) = spline.controls.iter().position(|id| *id == point_id) else {
                        return Err(KclError::refactor(format!(
                            "Internal: Point is not part of owner's controlPointSpline segment: point={point_id:?}, spline={owner_id:?}"
                        )));
                    };
                    let owner_expr =
                        get_or_insert_ast_reference(new_ast, &owner_object.source, CONTROL_POINT_SPLINE_FN, None)?;
                    let controls_expr = create_member_expression(owner_expr, CONTROL_POINT_SPLINE_PROPERTY_CONTROLS);
                    Ok(create_index_expression(controls_expr, index))
                }
                _ => Err(KclError::refactor(format!(
                    "Internal: Owner of point is not a supported segment type for constraints: {owner_segment:?}"
                ))),
            }
        } else {
            // Standalone point.
            get_or_insert_ast_reference(new_ast, &point_object.source, "point", None)
        }
    }

    fn line_id_to_ast_reference(
        &self,
        line_id: ObjectId,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        let line_object = self
            .scene_graph
            .objects
            .get(line_id.0)
            .ok_or_else(|| KclError::refactor(format!("Line not found: {line_id:?}")))?;
        let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {line_object:?}")));
        };
        let Segment::Line(line) = line_segment else {
            return Err(KclError::refactor(format!(
                "Only lines are currently supported: {line_object:?}"
            )));
        };

        if let Some(owner_id) = line.owner {
            let owner_object = self.scene_graph.objects.get(owner_id.0).ok_or_else(|| {
                KclError::refactor(format!(
                    "Owner of line not found in scene graph: line={line_id:?}, owner={owner_id:?}"
                ))
            })?;
            let ObjectKind::Segment { segment: owner_segment } = &owner_object.kind else {
                return Err(KclError::refactor(format!(
                    "Owner of line is not a segment, but found {}",
                    owner_object.kind.human_friendly_kind_with_article()
                )));
            };

            match owner_segment {
                Segment::ControlPointSpline(spline) => {
                    let Some(index) = spline
                        .controls
                        .windows(2)
                        .position(|window| window[0] == line.start && window[1] == line.end)
                    else {
                        return Err(KclError::refactor(format!(
                            "Internal: Line is not part of owner's controlPointSpline segment: line={line_id:?}, spline={owner_id:?}"
                        )));
                    };
                    let owner_expr =
                        get_or_insert_ast_reference(new_ast, &owner_object.source, CONTROL_POINT_SPLINE_FN, None)?;
                    let edges_expr = create_member_expression(owner_expr, CONTROL_POINT_SPLINE_PROPERTY_EDGES);
                    Ok(create_index_expression(edges_expr, index))
                }
                _ => Err(KclError::refactor(format!(
                    "Internal: Owner of line is not a supported segment type for constraints: {owner_segment:?}"
                ))),
            }
        } else {
            get_or_insert_ast_reference(new_ast, &line_object.source, "line", None)
        }
    }

    fn coincident_segment_to_ast(
        &self,
        segment: &ConstraintSegment,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        match segment {
            ConstraintSegment::Origin(_) => Ok(ast_name_expr("ORIGIN".to_owned())),
            ConstraintSegment::Segment(segment_id) => self.segment_id_to_constraint_ast_reference(*segment_id, new_ast),
        }
    }

    fn segment_id_to_constraint_ast_reference(
        &self,
        segment_id: ObjectId,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        let segment_object = self
            .scene_graph
            .objects
            .get(segment_id.0)
            .ok_or_else(|| KclError::refactor(format!("Object not found: {segment_id:?}")))?;
        let ObjectKind::Segment { segment } = &segment_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment, it is {}",
                segment_object.kind.human_friendly_kind_with_article()
            )));
        };

        match segment {
            Segment::Point(_) => self.point_id_to_ast_reference(segment_id, new_ast),
            Segment::Line(_) => self.line_id_to_ast_reference(segment_id, new_ast),
            Segment::Arc(_) => get_or_insert_ast_reference(new_ast, &segment_object.source, "arc", None),
            Segment::Circle(_) => get_or_insert_ast_reference(new_ast, &segment_object.source, CIRCLE_VARIABLE, None),
            Segment::ControlPointSpline(_) => {
                get_or_insert_ast_reference(new_ast, &segment_object.source, CONTROL_POINT_SPLINE_FN, None)
            }
        }
    }

    fn axis_constraint_segment_to_ast(
        &self,
        segment: &ConstraintSegment,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        match segment {
            ConstraintSegment::Origin(_) => Ok(ast_name_expr("ORIGIN".to_owned())),
            ConstraintSegment::Segment(point_id) => self.point_id_to_ast_reference(*point_id, new_ast),
        }
    }

    async fn add_coincident(
        &mut self,
        sketch: ObjectId,
        coincident: Coincident,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;
        for segment in &coincident.segments {
            let ConstraintSegment::Segment(segment_id) = segment else {
                continue;
            };
            let Some(segment_object) = self.scene_graph.objects.get(segment_id.0) else {
                continue;
            };
            if matches!(
                segment_object.kind,
                ObjectKind::Segment {
                    segment: Segment::ControlPointSpline(_)
                }
            ) {
                return Err(KclError::refactor(
                    "Coincident with a full controlPointSpline is not supported yet. Constrain a control point or spline edge instead."
                        .to_owned(),
                ));
            }
        }
        let segment_asts = coincident
            .segments
            .iter()
            .map(|segment| self.coincident_segment_to_ast(segment, new_ast))
            .collect::<Result<Vec<_>, _>>()?;
        if segment_asts.len() < 2 {
            return Err(KclError::refactor(format!(
                "Coincident constraint must have at least 2 inputs, got {}",
                segment_asts.len()
            )));
        }

        // Create the coincident() call using shared helper.
        let coincident_ast = create_coincident_ast(segment_asts);

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: coincident_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_distance(
        &mut self,
        sketch: ObjectId,
        distance: Distance,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;
        let [pt0_ast, pt1_ast] = match distance.points.as_slice() {
            [pt0, pt1] => [
                self.coincident_segment_to_ast(pt0, new_ast)?,
                self.coincident_segment_to_ast(pt1, new_ast)?,
            ],
            _ => {
                return Err(KclError::refactor(format!(
                    "Distance constraint must have exactly 2 points, got {}",
                    distance.points.len()
                )));
            }
        };

        let arguments = match &distance.label_position {
            Some(label_position) => vec![ast::LabeledArg {
                label: Some(ast::Identifier::new(LABEL_POSITION_PARAM)),
                arg: to_ast_point2d_number(label_position).map_err(|err| KclError::refactor(err.to_string()))?,
            }],
            None => Default::default(),
        };

        // Create the distance() call.
        let distance_call_ast = ast::BinaryPart::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(DISTANCE_FN)),
            unlabeled: Some(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
                ast::ArrayExpression {
                    elements: vec![pt0_ast, pt1_ast],
                    digest: None,
                    non_code_meta: Default::default(),
                },
            )))),
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));
        let distance_ast = ast::Expr::BinaryExpression(Box::new(ast::Node::no_src(ast::BinaryExpression {
            left: distance_call_ast,
            operator: ast::BinaryOperator::Eq,
            right: ast::BinaryPart::Literal(Box::new(ast::Node::no_src(ast::Literal {
                value: ast::LiteralValue::Number {
                    value: distance.distance.value,
                    suffix: distance.distance.units,
                },
                raw: format_number_literal(distance.distance.value, distance.distance.units, None).map_err(|_| {
                    KclError::refactor(format!(
                        "Could not format numeric suffix: {:?}",
                        distance.distance.units
                    ))
                })?,
                digest: None,
            }))),
            digest: None,
        })));

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: distance_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_angle(
        &mut self,
        sketch: ObjectId,
        angle: Angle,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let &[l0_id, l1_id] = angle.lines.as_slice() else {
            return Err(KclError::refactor(format!(
                "Angle constraint must have exactly 2 lines, got {}",
                angle.lines.len()
            )));
        };
        let sketch_id = sketch;

        // Map the runtime objects back to variable names.
        let line0_object = self
            .scene_graph
            .objects
            .get(l0_id.0)
            .ok_or_else(|| KclError::refactor(format!("Line not found: {l0_id:?}")))?;
        let ObjectKind::Segment { segment: line0_segment } = &line0_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {line0_object:?}")));
        };
        let Segment::Line(_) = line0_segment else {
            return Err(KclError::refactor(format!(
                "Only lines can be constrained to meet at an angle: {line0_object:?}",
            )));
        };
        let l0_ast = self.line_id_to_ast_reference(l0_id, new_ast)?;

        let line1_object = self
            .scene_graph
            .objects
            .get(l1_id.0)
            .ok_or_else(|| KclError::refactor(format!("Line not found: {l1_id:?}")))?;
        let ObjectKind::Segment { segment: line1_segment } = &line1_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {line1_object:?}")));
        };
        let Segment::Line(_) = line1_segment else {
            return Err(KclError::refactor(format!(
                "Only lines can be constrained to meet at an angle: {line1_object:?}",
            )));
        };
        let l1_ast = self.line_id_to_ast_reference(l1_id, new_ast)?;

        // Create the angle() call.
        let angle_call_ast = ast::BinaryPart::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(ANGLE_FN)),
            unlabeled: Some(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
                ast::ArrayExpression {
                    elements: vec![l0_ast, l1_ast],
                    digest: None,
                    non_code_meta: Default::default(),
                },
            )))),
            arguments: Default::default(),
            digest: None,
            non_code_meta: Default::default(),
        })));
        let angle_ast = ast::Expr::BinaryExpression(Box::new(ast::Node::no_src(ast::BinaryExpression {
            left: angle_call_ast,
            operator: ast::BinaryOperator::Eq,
            right: ast::BinaryPart::Literal(Box::new(ast::Node::no_src(ast::Literal {
                value: ast::LiteralValue::Number {
                    value: angle.angle.value,
                    suffix: angle.angle.units,
                },
                raw: format_number_literal(angle.angle.value, angle.angle.units, None).map_err(|_| {
                    KclError::refactor(format!("Could not format numeric suffix: {:?}", angle.angle.units))
                })?,
                digest: None,
            }))),
            digest: None,
        })));

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: angle_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_tangent(
        &mut self,
        sketch: ObjectId,
        tangent: Tangent,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let &[seg0_id, seg1_id] = tangent.input.as_slice() else {
            return Err(KclError::refactor(format!(
                "Tangent constraint must have exactly 2 segments, got {}",
                tangent.input.len()
            )));
        };
        let sketch_id = sketch;

        let seg0_object = self
            .scene_graph
            .objects
            .get(seg0_id.0)
            .ok_or_else(|| KclError::refactor(format!("Segment not found: {seg0_id:?}")))?;
        let ObjectKind::Segment { segment: seg0_segment } = &seg0_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {seg0_object:?}")));
        };
        let seg0_ast = match seg0_segment {
            Segment::Line(_) | Segment::Arc(_) | Segment::Circle(_) => {
                self.segment_id_to_constraint_ast_reference(seg0_id, new_ast)?
            }
            _ => {
                return Err(KclError::refactor(format!(
                    "Tangent supports only line/arc/circle segments for now, got: {seg0_segment:?}"
                )));
            }
        };

        let seg1_object = self
            .scene_graph
            .objects
            .get(seg1_id.0)
            .ok_or_else(|| KclError::refactor(format!("Segment not found: {seg1_id:?}")))?;
        let ObjectKind::Segment { segment: seg1_segment } = &seg1_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {seg1_object:?}")));
        };
        let seg1_ast = match seg1_segment {
            Segment::Line(_) | Segment::Arc(_) | Segment::Circle(_) => {
                self.segment_id_to_constraint_ast_reference(seg1_id, new_ast)?
            }
            _ => {
                return Err(KclError::refactor(format!(
                    "Tangent supports only line/arc/circle segments for now, got: {seg1_segment:?}"
                )));
            }
        };

        let tangent_ast = create_tangent_ast(seg0_ast, seg1_ast);
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: tangent_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_symmetric(
        &mut self,
        sketch: ObjectId,
        symmetric: Symmetric,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let &[input0_id, input1_id] = symmetric.input.as_slice() else {
            return Err(KclError::refactor(format!(
                "Symmetric constraint must have exactly 2 inputs, got {}",
                symmetric.input.len()
            )));
        };
        let sketch_id = sketch;

        let input0_ast = self.symmetric_input_id_to_ast_reference(input0_id, new_ast)?;
        let input1_ast = self.symmetric_input_id_to_ast_reference(input1_id, new_ast)?;
        let axis_ast = self.symmetric_axis_id_to_ast_reference(symmetric.axis, new_ast)?;

        let symmetric_ast = create_symmetric_ast(vec![input0_ast, input1_ast], axis_ast);
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: symmetric_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_midpoint(
        &mut self,
        sketch: ObjectId,
        midpoint: Midpoint,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;
        let point_ast = self.axis_constraint_segment_to_ast(&midpoint.point, new_ast)?;

        let segment_object = self
            .scene_graph
            .objects
            .get(midpoint.segment.0)
            .ok_or_else(|| KclError::refactor(format!("Segment not found: {:?}", midpoint.segment)))?;
        let ObjectKind::Segment {
            segment: midpoint_segment,
        } = &segment_object.kind
        else {
            return Err(KclError::refactor(format!(
                "Object must be a segment, but it was {}",
                segment_object.kind.human_friendly_kind_with_article()
            )));
        };
        let segment_ast = match midpoint_segment {
            Segment::Line(_) => self.line_id_to_ast_reference(midpoint.segment, new_ast)?,
            Segment::Arc(_) => get_or_insert_ast_reference(new_ast, &segment_object.source, "arc", None)?,
            _ => {
                return Err(KclError::refactor(format!(
                    "Midpoint target must be a line or arc segment but it was {}",
                    midpoint_segment.human_friendly_kind_with_article()
                )));
            }
        };

        let midpoint_ast = create_midpoint_ast(segment_ast, point_ast);
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: midpoint_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_equal_radius(
        &mut self,
        sketch: ObjectId,
        equal_radius: EqualRadius,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        if equal_radius.input.len() < 2 {
            return Err(KclError::refactor(format!(
                "equalRadius constraint must have at least 2 segments, got {}",
                equal_radius.input.len()
            )));
        }

        let sketch_id = sketch;
        let input_asts = equal_radius
            .input
            .iter()
            .map(|segment_id| self.equal_radius_segment_id_to_ast_reference(*segment_id, new_ast))
            .collect::<Result<Vec<_>, _>>()?;

        let equal_radius_ast = create_equal_radius_ast(input_asts);
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: equal_radius_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_radius(
        &mut self,
        sketch: ObjectId,
        radius: Radius,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let params = ArcSizeConstraintParams {
            points: vec![radius.arc],
            function_name: RADIUS_FN,
            value: radius.radius.value,
            units: radius.radius.units,
            label_position: radius.label_position,
            constraint_type_name: "Radius",
        };
        self.add_arc_size_constraint(sketch, params, new_ast).await
    }

    async fn add_diameter(
        &mut self,
        sketch: ObjectId,
        diameter: Diameter,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let params = ArcSizeConstraintParams {
            points: vec![diameter.arc],
            function_name: DIAMETER_FN,
            value: diameter.diameter.value,
            units: diameter.diameter.units,
            label_position: diameter.label_position,
            constraint_type_name: "Diameter",
        };
        self.add_arc_size_constraint(sketch, params, new_ast).await
    }

    async fn add_fixed_constraints(
        &mut self,
        sketch: ObjectId,
        points: Vec<FixedPoint>,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let mut sketch_block_ref = None;

        for fixed_point in points {
            let point_ast = self.point_id_to_ast_reference(fixed_point.point, new_ast)?;
            let fixed_ast = create_fixed_point_constraint_ast(point_ast, fixed_point.position)
                .map_err(|err| KclError::refactor(err.to_string()))?;

            let (sketch_ref, _) = self.mutate_ast(
                new_ast,
                sketch,
                AstMutateCommand::AddSketchBlockExprStmt { expr: fixed_ast },
            )?;
            sketch_block_ref = Some(sketch_ref);
        }

        sketch_block_ref.ok_or_else(|| KclError::refactor("Fixed constraint requires at least one point".to_owned()))
    }

    async fn add_arc_size_constraint(
        &mut self,
        sketch: ObjectId,
        params: ArcSizeConstraintParams,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;

        // Constraint must have exactly 1 argument (arc segment)
        if params.points.len() != 1 {
            return Err(KclError::refactor(format!(
                "{} constraint must have exactly 1 argument (an arc segment), got {}",
                params.constraint_type_name,
                params.points.len()
            )));
        }

        let arc_id = params.points[0];
        let arc_object = self
            .scene_graph
            .objects
            .get(arc_id.0)
            .ok_or_else(|| KclError::refactor(format!("Arc segment not found: {arc_id:?}")))?;
        let ObjectKind::Segment { segment: arc_segment } = &arc_object.kind else {
            return Err(KclError::refactor(format!("Object is not a segment: {arc_object:?}")));
        };
        let ref_type = match arc_segment {
            Segment::Arc(_) => ARC_VARIABLE,
            Segment::Circle(_) => CIRCLE_VARIABLE,
            _ => {
                return Err(KclError::refactor(format!(
                    "{} constraint argument must be an arc or circle segment, got: {arc_segment:?}",
                    params.constraint_type_name
                )));
            }
        };
        // Reference the arc/circle segment directly
        let arc_ast = get_or_insert_ast_reference(new_ast, &arc_object.source, ref_type, None)?;
        let arguments = match &params.label_position {
            Some(label_position) => vec![ast::LabeledArg {
                label: Some(ast::Identifier::new(LABEL_POSITION_PARAM)),
                arg: to_ast_point2d_number(label_position).map_err(|err| KclError::refactor(err.to_string()))?,
            }],
            None => Default::default(),
        };

        // Create the function call.
        let call_ast = ast::BinaryPart::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(params.function_name)),
            unlabeled: Some(arc_ast),
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));
        let constraint_ast = ast::Expr::BinaryExpression(Box::new(ast::Node::no_src(ast::BinaryExpression {
            left: call_ast,
            operator: ast::BinaryOperator::Eq,
            right: ast::BinaryPart::Literal(Box::new(ast::Node::no_src(ast::Literal {
                value: ast::LiteralValue::Number {
                    value: params.value,
                    suffix: params.units,
                },
                raw: format_number_literal(params.value, params.units, None)
                    .map_err(|_| KclError::refactor(format!("Could not format numeric suffix: {:?}", params.units)))?,
                digest: None,
            }))),
            digest: None,
        })));

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: constraint_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_horizontal_distance(
        &mut self,
        sketch: ObjectId,
        distance: Distance,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;
        let [pt0_ast, pt1_ast] = match distance.points.as_slice() {
            [pt0, pt1] => [
                self.coincident_segment_to_ast(pt0, new_ast)?,
                self.coincident_segment_to_ast(pt1, new_ast)?,
            ],
            _ => {
                return Err(KclError::refactor(format!(
                    "Horizontal distance constraint must have exactly 2 points, got {}",
                    distance.points.len()
                )));
            }
        };

        let arguments = match &distance.label_position {
            Some(label_position) => vec![ast::LabeledArg {
                label: Some(ast::Identifier::new(LABEL_POSITION_PARAM)),
                arg: to_ast_point2d_number(label_position).map_err(|err| KclError::refactor(err.to_string()))?,
            }],
            None => Default::default(),
        };

        // Create the horizontalDistance() call.
        let distance_call_ast = ast::BinaryPart::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(HORIZONTAL_DISTANCE_FN)),
            unlabeled: Some(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
                ast::ArrayExpression {
                    elements: vec![pt0_ast, pt1_ast],
                    digest: None,
                    non_code_meta: Default::default(),
                },
            )))),
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));
        let distance_ast = ast::Expr::BinaryExpression(Box::new(ast::Node::no_src(ast::BinaryExpression {
            left: distance_call_ast,
            operator: ast::BinaryOperator::Eq,
            right: ast::BinaryPart::Literal(Box::new(ast::Node::no_src(ast::Literal {
                value: ast::LiteralValue::Number {
                    value: distance.distance.value,
                    suffix: distance.distance.units,
                },
                raw: format_number_literal(distance.distance.value, distance.distance.units, None).map_err(|_| {
                    KclError::refactor(format!(
                        "Could not format numeric suffix: {:?}",
                        distance.distance.units
                    ))
                })?,
                digest: None,
            }))),
            digest: None,
        })));

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: distance_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_vertical_distance(
        &mut self,
        sketch: ObjectId,
        distance: Distance,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;
        let [pt0_ast, pt1_ast] = match distance.points.as_slice() {
            [pt0, pt1] => [
                self.coincident_segment_to_ast(pt0, new_ast)?,
                self.coincident_segment_to_ast(pt1, new_ast)?,
            ],
            _ => {
                return Err(KclError::refactor(format!(
                    "Vertical distance constraint must have exactly 2 points, got {}",
                    distance.points.len()
                )));
            }
        };

        let arguments = match &distance.label_position {
            Some(label_position) => vec![ast::LabeledArg {
                label: Some(ast::Identifier::new(LABEL_POSITION_PARAM)),
                arg: to_ast_point2d_number(label_position).map_err(|err| KclError::refactor(err.to_string()))?,
            }],
            None => Default::default(),
        };

        // Create the verticalDistance() call.
        let distance_call_ast = ast::BinaryPart::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(VERTICAL_DISTANCE_FN)),
            unlabeled: Some(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
                ast::ArrayExpression {
                    elements: vec![pt0_ast, pt1_ast],
                    digest: None,
                    non_code_meta: Default::default(),
                },
            )))),
            arguments,
            digest: None,
            non_code_meta: Default::default(),
        })));
        let distance_ast = ast::Expr::BinaryExpression(Box::new(ast::Node::no_src(ast::BinaryExpression {
            left: distance_call_ast,
            operator: ast::BinaryOperator::Eq,
            right: ast::BinaryPart::Literal(Box::new(ast::Node::no_src(ast::Literal {
                value: ast::LiteralValue::Number {
                    value: distance.distance.value,
                    suffix: distance.distance.units,
                },
                raw: format_number_literal(distance.distance.value, distance.distance.units, None).map_err(|_| {
                    KclError::refactor(format!(
                        "Could not format numeric suffix: {:?}",
                        distance.distance.units
                    ))
                })?,
                digest: None,
            }))),
            digest: None,
        })));

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: distance_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_horizontal(
        &mut self,
        sketch: ObjectId,
        horizontal: Horizontal,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;

        // Map the runtime objects back to variable names.
        let first_arg_ast = match horizontal {
            Horizontal::Line { line } => {
                let line_object = self
                    .scene_graph
                    .objects
                    .get(line.0)
                    .ok_or_else(|| KclError::refactor(format!("Line not found: {line:?}")))?;
                let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
                    let kind = line_object.kind.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "This constraint only works on Segments, but you selected {kind}"
                    )));
                };
                let Segment::Line(_) = line_segment else {
                    return Err(KclError::refactor(format!(
                        "Only lines can be made horizontal, but you selected {}",
                        line_segment.human_friendly_kind_with_article(),
                    )));
                };
                self.line_id_to_ast_reference(line, new_ast)?
            }
            Horizontal::Points { points } => {
                let point_asts = points
                    .iter()
                    .map(|point| self.axis_constraint_segment_to_ast(point, new_ast))
                    .collect::<Result<Vec<_>, _>>()?;
                ast::ArrayExpression::new(point_asts).into()
            }
        };
        // Create the horizontal() call using shared helper.
        let horizontal_ast = create_horizontal_ast(first_arg_ast);

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: horizontal_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_lines_equal_length(
        &mut self,
        sketch: ObjectId,
        lines_equal_length: LinesEqualLength,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        if lines_equal_length.lines.len() < 2 {
            return Err(KclError::refactor(format!(
                "Lines equal length constraint must have at least 2 lines, got {}",
                lines_equal_length.lines.len()
            )));
        };

        let sketch_id = sketch;

        // Map the runtime objects back to variable names.
        let line_asts = lines_equal_length
            .lines
            .iter()
            .map(|line_id| {
                let line_object = self
                    .scene_graph
                    .objects
                    .get(line_id.0)
                    .ok_or_else(|| KclError::refactor(format!("Line not found: {line_id:?}")))?;
                let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
                    let kind = line_object.kind.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "This constraint only works on Segments, but you selected {kind}"
                    )));
                };
                let Segment::Line(_) = line_segment else {
                    let kind = line_segment.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "Only lines can be made equal length, but you selected {kind}"
                    )));
                };

                self.line_id_to_ast_reference(*line_id, new_ast)
            })
            .collect::<Result<Vec<_>, _>>()?;

        // Create the equalLength() call using shared helper.
        let equal_length_ast = create_equal_length_ast(line_asts);

        // Add the constraint to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: equal_length_ast },
        )?;
        Ok(sketch_block_ref)
    }

    fn equal_radius_segment_id_to_ast_reference(
        &mut self,
        segment_id: ObjectId,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        let segment_object = self
            .scene_graph
            .objects
            .get(segment_id.0)
            .ok_or_else(|| KclError::refactor(format!("Segment not found: {segment_id:?}")))?;
        let ObjectKind::Segment { segment } = &segment_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment, it was {}",
                segment_object.kind.human_friendly_kind_with_article()
            )));
        };

        let ref_type = match segment {
            Segment::Arc(_) => ARC_VARIABLE,
            Segment::Circle(_) => CIRCLE_VARIABLE,
            _ => {
                return Err(KclError::refactor(format!(
                    "equalRadius supports only arc/circle segments, got {}",
                    segment.human_friendly_kind_with_article()
                )));
            }
        };

        get_or_insert_ast_reference(new_ast, &segment_object.source, ref_type, None)
    }

    fn symmetric_input_id_to_ast_reference(
        &mut self,
        segment_id: ObjectId,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        let segment_object = self
            .scene_graph
            .objects
            .get(segment_id.0)
            .ok_or_else(|| KclError::refactor(format!("Segment not found: {segment_id:?}")))?;
        let ObjectKind::Segment { segment } = &segment_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment, it was {}",
                segment_object.kind.human_friendly_kind_with_article()
            )));
        };

        match segment {
            Segment::Point(_) => self.point_id_to_ast_reference(segment_id, new_ast),
            Segment::Line(_) => get_or_insert_ast_reference(new_ast, &segment_object.source, LINE_VARIABLE, None),
            Segment::Arc(_) => get_or_insert_ast_reference(new_ast, &segment_object.source, ARC_VARIABLE, None),
            Segment::Circle(_) => get_or_insert_ast_reference(new_ast, &segment_object.source, CIRCLE_VARIABLE, None),
            Segment::ControlPointSpline(_) => Err(KclError::refactor(
                "Symmetric does not yet support control point splines".to_owned(),
            )),
        }
    }

    fn symmetric_axis_id_to_ast_reference(
        &mut self,
        segment_id: ObjectId,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<ast::Expr, KclError> {
        let segment_object = self
            .scene_graph
            .objects
            .get(segment_id.0)
            .ok_or_else(|| KclError::refactor(format!("Axis segment not found: {segment_id:?}")))?;
        let ObjectKind::Segment { segment } = &segment_object.kind else {
            return Err(KclError::refactor(format!(
                "Object is not a segment, it was {}",
                segment_object.kind.human_friendly_kind_with_article()
            )));
        };
        match segment {
            Segment::Line(_) => self.line_id_to_ast_reference(segment_id, new_ast),
            _ => Err(KclError::refactor(format!(
                "Symmetric axis must be a line, got {}",
                segment.human_friendly_kind_with_article()
            ))),
        }
    }

    async fn add_parallel(
        &mut self,
        sketch: ObjectId,
        parallel: Parallel,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        if parallel.lines.len() < 2 {
            return Err(KclError::refactor(format!(
                "Parallel constraint must have at least 2 lines, got {}",
                parallel.lines.len()
            )));
        };

        let sketch_id = sketch;

        let line_asts = parallel
            .lines
            .iter()
            .map(|line_id| {
                let line_object = self
                    .scene_graph
                    .objects
                    .get(line_id.0)
                    .ok_or_else(|| KclError::refactor(format!("Line not found: {line_id:?}")))?;
                let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
                    let kind = line_object.kind.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "This constraint only works on Segments, but you selected {kind}"
                    )));
                };
                let Segment::Line(_) = line_segment else {
                    let kind = line_segment.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "Only lines can be made parallel, but you selected {kind}"
                    )));
                };

                self.line_id_to_ast_reference(*line_id, new_ast)
            })
            .collect::<Result<Vec<_>, _>>()?;

        let call_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(LinesAtAngleKind::Parallel.to_function_name())),
            unlabeled: Some(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
                ast::ArrayExpression {
                    elements: line_asts,
                    digest: None,
                    non_code_meta: Default::default(),
                },
            )))),
            arguments: Default::default(),
            digest: None,
            non_code_meta: Default::default(),
        })));

        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: call_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_perpendicular(
        &mut self,
        sketch: ObjectId,
        perpendicular: Perpendicular,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        self.add_lines_at_angle_constraint(sketch, LinesAtAngleKind::Perpendicular, perpendicular.lines, new_ast)
            .await
    }

    async fn add_lines_at_angle_constraint(
        &mut self,
        sketch: ObjectId,
        angle_kind: LinesAtAngleKind,
        lines: Vec<ObjectId>,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let &[line0_id, line1_id] = lines.as_slice() else {
            return Err(KclError::refactor(format!(
                "{} constraint must have exactly 2 lines, got {}",
                angle_kind.to_function_name(),
                lines.len()
            )));
        };

        let sketch_id = sketch;

        // Map the runtime objects back to variable names.
        let line0_object = self
            .scene_graph
            .objects
            .get(line0_id.0)
            .ok_or_else(|| KclError::refactor(format!("Line not found: {line0_id:?}")))?;
        let ObjectKind::Segment { segment: line0_segment } = &line0_object.kind else {
            let kind = line0_object.kind.human_friendly_kind_with_article();
            return Err(KclError::refactor(format!(
                "This constraint only works on Segments, but you selected {kind}"
            )));
        };
        let Segment::Line(_) = line0_segment else {
            return Err(KclError::refactor(format!(
                "Only lines can be made {}, but you selected {}",
                angle_kind.to_function_name(),
                line0_segment.human_friendly_kind_with_article(),
            )));
        };
        let line0_ast = self.line_id_to_ast_reference(line0_id, new_ast)?;

        let line1_object = self
            .scene_graph
            .objects
            .get(line1_id.0)
            .ok_or_else(|| KclError::refactor(format!("Line not found: {line1_id:?}")))?;
        let ObjectKind::Segment { segment: line1_segment } = &line1_object.kind else {
            let kind = line1_object.kind.human_friendly_kind_with_article();
            return Err(KclError::refactor(format!(
                "This constraint only works on Segments, but you selected {kind}"
            )));
        };
        let Segment::Line(_) = line1_segment else {
            return Err(KclError::refactor(format!(
                "Only lines can be made {}, but you selected {}",
                angle_kind.to_function_name(),
                line1_segment.human_friendly_kind_with_article(),
            )));
        };
        let line1_ast = self.line_id_to_ast_reference(line1_id, new_ast)?;

        // Create the parallel() or perpendicular() call.
        let call_ast = ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(angle_kind.to_function_name())),
            unlabeled: Some(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
                ast::ArrayExpression {
                    elements: vec![line0_ast, line1_ast],
                    digest: None,
                    non_code_meta: Default::default(),
                },
            )))),
            arguments: Default::default(),
            digest: None,
            non_code_meta: Default::default(),
        })));

        // Add the constraint to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: call_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn add_vertical(
        &mut self,
        sketch: ObjectId,
        vertical: Vertical,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> Result<AstNodeRef, KclError> {
        let sketch_id = sketch;

        let first_arg_ast = match vertical {
            Vertical::Line { line } => {
                // Map the runtime objects back to variable names.
                let line_object = self
                    .scene_graph
                    .objects
                    .get(line.0)
                    .ok_or_else(|| KclError::refactor(format!("Line not found: {line:?}")))?;
                let ObjectKind::Segment { segment: line_segment } = &line_object.kind else {
                    let kind = line_object.kind.human_friendly_kind_with_article();
                    return Err(KclError::refactor(format!(
                        "This constraint only works on Segments, but you selected {kind}"
                    )));
                };
                let Segment::Line(_) = line_segment else {
                    return Err(KclError::refactor(format!(
                        "Only lines can be made vertical, but you selected {}",
                        line_segment.human_friendly_kind_with_article()
                    )));
                };
                self.line_id_to_ast_reference(line, new_ast)?
            }
            Vertical::Points { points } => {
                let point_asts = points
                    .iter()
                    .map(|point| self.axis_constraint_segment_to_ast(point, new_ast))
                    .collect::<Result<Vec<_>, _>>()?;
                ast::ArrayExpression::new(point_asts).into()
            }
        };
        // Create the vertical() call using shared helper.
        let vertical_ast = create_vertical_ast(first_arg_ast);

        // Add the line to the AST of the sketch block.
        let (sketch_block_ref, _) = self.mutate_ast(
            new_ast,
            sketch_id,
            AstMutateCommand::AddSketchBlockExprStmt { expr: vertical_ast },
        )?;
        Ok(sketch_block_ref)
    }

    async fn execute_after_add_constraint(
        &mut self,
        ctx: &ExecutorContext,
        sketch_id: ObjectId,
        sketch_block_ref: AstNodeRef,
        new_ast: &mut ast::Node<ast::Program>,
    ) -> ExecResult<(SourceDelta, SceneGraphDelta)> {
        // Convert to string source to create real source ranges.
        let new_source = source_from_ast(new_ast);
        // Parse the new KCL source.
        let (new_program, errors) = Program::parse(&new_source)
            .map_err(|err| KclErrorWithOutputs::no_outputs(KclError::refactor(err.to_string())))?;
        if !errors.is_empty() {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Error parsing KCL source after adding constraint: {errors:?}"
            ))));
        }
        let Some(new_program) = new_program else {
            return Err(KclErrorWithOutputs::no_outputs(KclError::refactor(
                "No AST produced after adding constraint".to_string(),
            )));
        };
        let constraint_node_ref = find_sketch_block_added_item(&new_program.ast, &sketch_block_ref).map_err(|err| {
            KclErrorWithOutputs::no_outputs(KclError::refactor(format!(
                "Source range of new constraint not found in sketch block: {sketch_block_ref:?}; {err:?}"
            )))
        })?;

        // Truncate after the sketch block for mock execution.
        // Use a clone so we don't mutate new_program yet
        let mut truncated_program = new_program.clone();
        only_sketch_block(&mut truncated_program.ast, &sketch_block_ref, ChangeKind::Add)
            .map_err(KclErrorWithOutputs::no_outputs)?;

        // Execute - if this fails, we haven't modified self yet, so state is safe
        let outcome = ctx
            .run_mock(&truncated_program, &MockConfig::new_sketch_mode(sketch_id))
            .await?;

        let new_object_ids = {
            // Extract the constraint ID from the execution outcome using source_range_to_object
            let constraint_id = outcome
                .source_range_to_object
                .get(&constraint_node_ref.range)
                .copied()
                .ok_or_else(|| {
                    KclErrorWithOutputs::from_error_outcome(
                        KclError::refactor(format!("Source range of constraint not found: {constraint_node_ref:?}")),
                        outcome.clone(),
                    )
                })?;
            vec![constraint_id]
        };

        // Only now, after all operations succeeded, update self.program.
        // This ensures state is only modified if everything succeeds.
        self.program = new_program;

        // Uses MockConfig::default() which has freedom_analysis: true
        let outcome = self.update_state_after_exec(outcome, true);

        let src_delta = self.commit_var_solutions_to_program(&outcome, "adding constraint")?;
        let scene_graph_delta = SceneGraphDelta {
            new_graph: self.scene_graph_for_ui(),
            invalidates_ids: false,
            new_objects: new_object_ids,
            exec_outcome: outcome,
        };
        Ok((src_delta, scene_graph_delta))
    }

    fn commit_var_solutions_to_program(&mut self, outcome: &ExecOutcome, operation: &str) -> ExecResult<SourceDelta> {
        let commit_failure = || {
            KclErrorWithOutputs::from_error_outcome(
                KclError::refactor(format!("Could not update KCL after {operation}.")),
                outcome.clone(),
            )
        };

        let default_length_unit = self.default_length_unit();
        let mut settled_ast = self.program.ast.clone();
        let mut committed_solver_value = false;
        for (var_range, node_path, value) in &outcome.var_solutions {
            let Some(lookup) = numeric_literal_at_node_path(&settled_ast, node_path.as_ref(), *var_range) else {
                return Err(commit_failure());
            };
            let new_value = match &lookup {
                Some(current_literal) => {
                    if !var_solution_needs_commit(current_literal, *value, default_length_unit) {
                        continue;
                    }
                    preserve_var_solution_literal_style(current_literal, *value, default_length_unit)
                }
                None => {
                    // Bare `var` with no initial literal to compare against;
                    // always commit, using the module's default length unit as
                    // an explicit suffix so the written value carries units.
                    Number {
                        value: number_value_in_default_length_units(*value, default_length_unit),
                        units: default_length_unit.into(),
                    }
                }
            };
            committed_solver_value = true;
            let source_ref = SourceRef::Simple {
                range: *var_range,
                node_path: node_path.clone(),
            };
            mutate_ast_node_by_source_ref(
                &mut settled_ast,
                &source_ref,
                AstMutateCommand::EditVarInitialValue { value: new_value },
            )
            .map_err(|_| commit_failure())?;
        }

        if !committed_solver_value {
            return Ok(SourceDelta {
                text: self.program.original_file_contents.clone(),
            });
        }

        let settled_source = source_from_ast(&settled_ast);
        let (settled_program, errors) = Program::parse(&settled_source).map_err(|_| commit_failure())?;
        if !errors.is_empty() {
            return Err(commit_failure());
        }
        let Some(settled_program) = settled_program else {
            return Err(commit_failure());
        };

        self.program = settled_program;

        Ok(SourceDelta { text: settled_source })
    }

    // Find constraints that reference the given segments.
    fn segment_will_be_deleted(&self, segment_id: ObjectId, segment_ids_set: &AhashIndexSet<ObjectId>) -> bool {
        if segment_ids_set.contains(&segment_id) {
            return true;
        }

        let Some(segment_object) = self.scene_graph.objects.get(segment_id.0) else {
            return false;
        };
        let ObjectKind::Segment { segment } = &segment_object.kind else {
            return false;
        };
        let Segment::Point(point) = segment else {
            return false;
        };

        point.owner.is_some_and(|owner_id| segment_ids_set.contains(&owner_id))
    }

    fn remaining_constraint_segments(
        &self,
        segments: &[ConstraintSegment],
        segment_ids_set: &AhashIndexSet<ObjectId>,
    ) -> Vec<ConstraintSegment> {
        segments
            .iter()
            .copied()
            .filter(|segment| match segment {
                ConstraintSegment::Origin(_) => true,
                ConstraintSegment::Segment(segment_id) => !self.segment_will_be_deleted(*segment_id, segment_ids_set),
            })
            .collect()
    }

    fn find_referenced_constraints(
        &self,
        sketch_id: ObjectId,
        segment_ids_set: &AhashIndexSet<ObjectId>,
    ) -> Result<AhashIndexSet<ObjectId>, KclError> {
        // Look up the sketch.
        let sketch_object = self
            .scene_graph
            .objects
            .get(sketch_id.0)
            .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch_id:?}")))?;
        let ObjectKind::Sketch(sketch) = &sketch_object.kind else {
            return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
        };
        let segment_or_owner_matches = |segment_id: ObjectId| {
            if segment_ids_set.contains(&segment_id) {
                return true;
            }
            let segment_object = self.scene_graph.objects.get(segment_id.0);
            if let Some(obj) = segment_object
                && let ObjectKind::Segment { segment } = &obj.kind
            {
                match segment {
                    Segment::Point(point) => point.owner.is_some_and(|owner_id| segment_ids_set.contains(&owner_id)),
                    Segment::Line(line) => line.owner.is_some_and(|owner_id| segment_ids_set.contains(&owner_id)),
                    _ => false,
                }
            } else {
                false
            }
        };
        let mut constraint_ids_set = AhashIndexSet::default();
        for constraint_id in &sketch.constraints {
            let constraint_object = self
                .scene_graph
                .objects
                .get(constraint_id.0)
                .ok_or_else(|| KclError::refactor(format!("Constraint not found: {constraint_id:?}")))?;
            let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
                return Err(KclError::refactor(format!(
                    "Object is not a constraint, it is {}",
                    constraint_object.kind.human_friendly_kind_with_article()
                )));
            };
            let depends_on_segment = match constraint {
                Constraint::Coincident(c) => c.segment_ids().any(segment_or_owner_matches),
                Constraint::Distance(d) => d.point_ids().any(segment_or_owner_matches),
                Constraint::Fixed(fixed) => fixed
                    .points
                    .iter()
                    .any(|fixed_point| self.segment_will_be_deleted(fixed_point.point, segment_ids_set)),
                Constraint::Radius(r) => segment_or_owner_matches(r.arc),
                Constraint::Diameter(d) => segment_or_owner_matches(d.arc),
                Constraint::EqualRadius(equal_radius) => {
                    equal_radius.input.iter().copied().any(segment_or_owner_matches)
                }
                Constraint::HorizontalDistance(d) => d.point_ids().any(segment_or_owner_matches),
                Constraint::VerticalDistance(d) => d.point_ids().any(segment_or_owner_matches),
                Constraint::Horizontal(h) => match h {
                    Horizontal::Line { line } => segment_or_owner_matches(*line),
                    Horizontal::Points { points } => points.iter().any(|point| match point {
                        ConstraintSegment::Segment(point) => segment_or_owner_matches(*point),
                        ConstraintSegment::Origin(_) => false,
                    }),
                },
                Constraint::Vertical(v) => match v {
                    Vertical::Line { line } => segment_or_owner_matches(*line),
                    Vertical::Points { points } => points.iter().any(|point| match point {
                        ConstraintSegment::Segment(point) => segment_or_owner_matches(*point),
                        ConstraintSegment::Origin(_) => false,
                    }),
                },
                Constraint::LinesEqualLength(lines_equal_length) => {
                    lines_equal_length.lines.iter().copied().any(segment_or_owner_matches)
                }
                Constraint::Midpoint(midpoint) => {
                    segment_or_owner_matches(midpoint.segment)
                        || matches!(
                            midpoint.point,
                            ConstraintSegment::Segment(point) if segment_or_owner_matches(point)
                        )
                }
                Constraint::Parallel(parallel) => parallel.lines.iter().copied().any(segment_or_owner_matches),
                Constraint::Perpendicular(perpendicular) => {
                    perpendicular.lines.iter().copied().any(segment_or_owner_matches)
                }
                Constraint::Angle(angle) => angle.lines.iter().copied().any(segment_or_owner_matches),
                Constraint::Symmetric(symmetric) => {
                    segment_or_owner_matches(symmetric.axis)
                        || symmetric.input.iter().copied().any(segment_or_owner_matches)
                }
                Constraint::Tangent(tangent) => tangent.input.iter().copied().any(segment_or_owner_matches),
            };
            if depends_on_segment {
                constraint_ids_set.insert(*constraint_id);
            }
        }
        Ok(constraint_ids_set)
    }

    fn update_state_after_exec(&mut self, outcome: ExecOutcome, freedom_analysis_ran: bool) -> ExecOutcome {
        let mut outcome = outcome;
        let mut new_objects = std::mem::take(&mut outcome.scene_objects);

        if freedom_analysis_ran {
            // When freedom analysis ran, replace the cache entirely with new values
            // Don't merge with old values since IDs might have changed
            self.point_freedom_cache.clear();
            for new_obj in &new_objects {
                if let ObjectKind::Segment {
                    segment: crate::front::Segment::Point(point),
                } = &new_obj.kind
                {
                    self.point_freedom_cache.insert(new_obj.id, point.freedom);
                }
            }
            add_wall_and_cap_face_objects(&mut new_objects, &outcome.artifact_graph);
            // Objects are already correct from the analysis, just use them as-is
            self.scene_graph.objects = new_objects;
        } else {
            // When freedom analysis didn't run, preserve old values and merge
            // Before replacing objects, extract and store freedom values from old objects
            for old_obj in &self.scene_graph.objects {
                if let ObjectKind::Segment {
                    segment: crate::front::Segment::Point(point),
                } = &old_obj.kind
                {
                    self.point_freedom_cache.insert(old_obj.id, point.freedom);
                }
            }

            // Update objects, preserving stored freedom values when new is Free (might be default)
            let mut updated_objects = Vec::with_capacity(new_objects.len());
            for new_obj in new_objects {
                let mut obj = new_obj;
                if let ObjectKind::Segment {
                    segment: crate::front::Segment::Point(point),
                } = &mut obj.kind
                {
                    let new_freedom = point.freedom;
                    // When freedom_analysis=false, new values are defaults (Free).
                    // Only preserve cached values when new is Free (indicating it's a default, not from analysis).
                    // If new is NOT Free, use the new value (it came from somewhere else, maybe conflict detection).
                    // Never preserve Conflict from cache - conflicts are transient and should only be set
                    // when there are actually unsatisfied constraints.
                    match new_freedom {
                        Freedom::Free => {
                            match self.point_freedom_cache.get(&obj.id).copied() {
                                Some(Freedom::Conflict) => {
                                    // Don't preserve Conflict - conflicts are transient
                                    // Keep it as Free
                                }
                                Some(Freedom::Fixed) => {
                                    // Preserve Fixed cached value
                                    point.freedom = Freedom::Fixed;
                                }
                                Some(Freedom::Free) => {
                                    // If stored is also Free, keep Free (no change needed)
                                }
                                None => {
                                    // If no cached value, keep Free (default)
                                }
                            }
                        }
                        Freedom::Fixed => {
                            // Use new value (already set)
                        }
                        Freedom::Conflict => {
                            // Use new value (already set)
                        }
                    }
                    // Store the new freedom value (even if it's Free, so we know it was set)
                    self.point_freedom_cache.insert(obj.id, point.freedom);
                }
                updated_objects.push(obj);
            }

            add_wall_and_cap_face_objects(&mut updated_objects, &outcome.artifact_graph);
            self.scene_graph.objects = updated_objects;
        }
        outcome
    }

    fn mutate_ast(
        &mut self,
        ast: &mut ast::Node<ast::Program>,
        object_id: ObjectId,
        command: AstMutateCommand,
    ) -> Result<(AstNodeRef, AstMutateCommandReturn), KclError> {
        let sketch_object = self
            .scene_graph
            .objects
            .get(object_id.0)
            .ok_or_else(|| KclError::refactor(format!("Object not found: {object_id:?}")))?;
        mutate_ast_node_by_source_ref(ast, &sketch_object.source, command)
    }
}

fn sketch_block_ref_from_id(scene_graph: &SceneGraph, sketch_id: ObjectId) -> Result<AstNodeRef, KclError> {
    // Look up existing sketch.
    let sketch_object = scene_graph
        .objects
        .get(sketch_id.0)
        .ok_or_else(|| KclError::refactor(format!("Sketch not found: {sketch_id:?}")))?;
    let ObjectKind::Sketch(_) = &sketch_object.kind else {
        return Err(KclError::refactor(format!("Object is not a sketch: {sketch_object:?}")));
    };
    expect_single_node_ref(sketch_object)
}

fn expect_single_node_ref(object: &Object) -> Result<AstNodeRef, KclError> {
    match &object.source {
        SourceRef::Simple { range, node_path } => Ok(AstNodeRef {
            range: *range,
            node_path: node_path.clone(),
        }),
        SourceRef::BackTrace { ranges } => {
            let [range] = ranges.as_slice() else {
                return Err(KclError::refactor(format!(
                    "Expected single location in SourceRef, got {}; ranges={ranges:#?}",
                    ranges.len()
                )));
            };
            Ok(AstNodeRef {
                range: range.0,
                node_path: range.1.clone(),
            })
        }
    }
}

/// This is a deprecated fall-back implementation. Prefer
/// [`only_sketch_block()`] to avoid reliance on source ranges.
fn only_sketch_block_from_range(
    ast: &mut ast::Node<ast::Program>,
    sketch_block_range: SourceRange,
    edit_kind: ChangeKind,
) -> Result<(), KclError> {
    let r1 = sketch_block_range;
    let matches_range = |r2: SourceRange| -> bool {
        // We may have added items to the sketch block, so the end may not be an
        // exact match.
        match edit_kind {
            ChangeKind::Add => r1.module_id() == r2.module_id() && r1.start() == r2.start() && r1.end() <= r2.end(),
            // For edit, we don't know whether it grew or shrank.
            ChangeKind::Edit => r1.module_id() == r2.module_id() && r1.start() == r2.start(),
            ChangeKind::Delete => r1.module_id() == r2.module_id() && r1.start() == r2.start() && r1.end() >= r2.end(),
            // No edit should be an exact match.
            ChangeKind::None => r1.module_id() == r2.module_id() && r1.start() == r2.start() && r1.end() == r2.end(),
        }
    };
    let mut found = false;
    for item in ast.body.iter_mut() {
        match item {
            ast::BodyItem::ImportStatement(_) => {}
            ast::BodyItem::ExpressionStatement(node) => {
                if matches_range(SourceRange::from(&*node))
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.expression
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
            }
            ast::BodyItem::VariableDeclaration(node) => {
                if matches_range(SourceRange::from(&node.declaration.init))
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.declaration.init
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
            }
            ast::BodyItem::TypeDeclaration(_) => {}
            ast::BodyItem::ReturnStatement(node) => {
                if matches_range(SourceRange::from(&node.argument))
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.argument
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
            }
        }
    }
    if !found {
        return Err(KclError::refactor(format!(
            "Sketch block source range not found in AST: {sketch_block_range:?}, edit_kind={edit_kind:?}"
        )));
    }

    Ok(())
}

fn only_sketch_block(
    ast: &mut ast::Node<ast::Program>,
    sketch_block_ref: &AstNodeRef,
    edit_kind: ChangeKind,
) -> Result<(), KclError> {
    let Some(target_node_path) = &sketch_block_ref.node_path else {
        #[cfg(target_arch = "wasm32")]
        web_sys::console::warn_1(
            &format!(
                "only_sketch_block: target sketch block ref doesn't have node path; sketch_block_ref={:#?}, edit_kind={edit_kind:#?}",
                &sketch_block_ref
            )
            .into(),
        );
        return only_sketch_block_from_range(ast, sketch_block_ref.range, edit_kind);
    };
    let mut found = false;
    for item in ast.body.iter_mut() {
        match item {
            ast::BodyItem::ImportStatement(_) => {}
            ast::BodyItem::ExpressionStatement(node) => {
                // Check the statement.
                if let Some(node_path) = &node.node_path
                    && node_path == target_node_path
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.expression
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
                // Check the expression.
                if let Some(node_path) = node.expression.node_path()
                    && node_path == target_node_path
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.expression
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
            }
            ast::BodyItem::VariableDeclaration(node) => {
                if let Some(node_path) = node.declaration.init.node_path()
                    && node_path == target_node_path
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.declaration.init
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
            }
            ast::BodyItem::TypeDeclaration(_) => {}
            ast::BodyItem::ReturnStatement(node) => {
                if let Some(node_path) = node.argument.node_path()
                    && node_path == target_node_path
                    && let ast::Expr::SketchBlock(sketch_block) = &mut node.argument
                {
                    sketch_block.is_being_edited = true;
                    found = true;
                    break;
                }
            }
        }
    }
    if !found {
        return Err(KclError::refactor(format!(
            "Sketch block node path not found in AST: {sketch_block_ref:?}, edit_kind={edit_kind:?}"
        )));
    }

    Ok(())
}

fn sketch_on_ast_expr(
    ast: &mut ast::Node<ast::Program>,
    scene_graph: &SceneGraph,
    on: &Plane,
) -> Result<ast::Expr, KclError> {
    match on {
        Plane::Default(name) => Ok(default_plane_ast_expr(*name)),
        Plane::Object(object_id) => {
            let on_object = scene_graph
                .objects
                .get(object_id.0)
                .ok_or_else(|| KclError::refactor(format!("Sketch plane object not found: {object_id:?}")))?;
            if let Some(face_expr) = sketch_face_of_scene_object_ast_expr(ast, on_object)? {
                return Ok(face_expr);
            }
            get_or_insert_ast_reference(ast, &on_object.source, "plane", None)
        }
    }
}

fn sketch_face_of_scene_object_ast_expr(
    ast: &mut ast::Node<ast::Program>,
    on_object: &crate::front::Object,
) -> Result<Option<ast::Expr>, KclError> {
    match &on_object.kind {
        ObjectKind::Wall(wall) => {
            let solid_ref = get_or_insert_ast_reference(
                ast,
                &source_ref_from_source_ref_range(&wall.source.solid),
                "solid",
                None,
            )?;
            let ast::Expr::Name(solid_name_expr) = solid_ref else {
                return Err(KclError::refactor(format!(
                    "Could not resolve solid reference for selected wall: artifact_id={:?}",
                    on_object.artifact_id
                )));
            };
            let solid_expr = indexed_solid_expr_for_sweep_output(
                ast_name_expr(solid_name_expr.name.name.clone()),
                wall.solid_output_index,
            );
            let sweep_ref = get_or_insert_ast_reference(
                ast,
                &source_ref_from_source_ref_range(&wall.source.sweep),
                "solid",
                None,
            )?;
            let ast::Expr::Name(sweep_name_expr) = sweep_ref else {
                return Err(KclError::refactor(format!(
                    "Could not resolve sweep reference for selected wall: artifact_id={:?}",
                    on_object.artifact_id
                )));
            };
            let sweep_name = sweep_name_expr.name.name.clone();
            let segment_ref = get_or_insert_ast_reference(
                ast,
                &source_ref_from_source_ref_range(&wall.source.segment),
                LINE_VARIABLE,
                None,
            )?;

            let face_expr = if let Some(region_name) = region_name_from_sweep_variable(ast, &sweep_name).or_else(|| {
                wall.source
                    .path
                    .as_ref()
                    .and_then(|path_source| region_name_from_path_source(ast, path_source))
            }) {
                let ast::Expr::Name(segment_name_expr) = segment_ref else {
                    return Err(KclError::refactor(format!(
                        "Could not resolve source segment reference for selected region wall: artifact_id={:?}",
                        on_object.artifact_id
                    )));
                };
                create_member_expression(
                    create_member_expression(ast_name_expr(region_name), "tags"),
                    &segment_name_expr.name.name,
                )
            } else {
                segment_ref
            };

            Ok(Some(create_face_of_ast(solid_expr, face_expr)))
        }
        ObjectKind::Cap(cap) => {
            let solid_ref =
                get_or_insert_ast_reference(ast, &source_ref_from_source_ref_range(&cap.source.solid), "solid", None)?;
            let ast::Expr::Name(solid_name_expr) = solid_ref else {
                return Err(KclError::refactor(format!(
                    "Could not resolve solid reference for selected cap: artifact_id={:?}",
                    on_object.artifact_id
                )));
            };
            let solid_expr = indexed_solid_expr_for_sweep_output(
                ast_name_expr(solid_name_expr.name.name.clone()),
                cap.solid_output_index,
            );
            // TODO: change this to explicit tag references with tagStart/tagEnd mutations
            let face_expr = match cap.kind {
                crate::frontend::api::CapKind::Start => ast_name_expr("START".to_owned()),
                crate::frontend::api::CapKind::End => ast_name_expr("END".to_owned()),
            };

            Ok(Some(create_face_of_ast(solid_expr, face_expr)))
        }
        _ => Ok(None),
    }
}

fn indexed_solid_expr_for_sweep_output(solid_expr: ast::Expr, solid_output_index: Option<usize>) -> ast::Expr {
    match solid_output_index {
        Some(output_index) => create_index_expression(solid_expr, output_index),
        None => solid_expr,
    }
}

fn source_ref_from_source_ref_range(source: &SourceRefRange) -> SourceRef {
    SourceRef::Simple {
        range: source.range,
        node_path: source.node_path.clone(),
    }
}

fn region_name_from_path_source(ast: &ast::Node<ast::Program>, path_source: &SourceRefRange) -> Option<String> {
    let source_ref = source_ref_from_source_ref_range(path_source);
    let candidate = variable_name_containing_source_ref(ast, &source_ref)?;
    let ast::Definition::Variable(region_decl) = ast.get_variable(&candidate)? else {
        return None;
    };
    let ast::Expr::CallExpressionKw(region_call) = &region_decl.init else {
        return None;
    };
    if region_call.callee.name.name != "region" {
        return None;
    }
    Some(candidate)
}

fn downstream_composite_code_ref_for_source(artifact_graph: &ArtifactGraph, source_id: ArtifactId) -> Option<&CodeRef> {
    let mut current_id = source_id;
    let mut current_composite = None;
    let mut visited = HashSet::new();

    while visited.insert(current_id) {
        let next_composite_id = downstream_composite_id_for_solid_source(artifact_graph, current_id);

        let Some(composite_id) = next_composite_id else {
            break;
        };
        let Some(Artifact::CompositeSolid(composite)) = artifact_graph.get(&composite_id) else {
            break;
        };

        current_id = composite.id;
        current_composite = Some(composite);

        if !composite.consumed {
            break;
        }
    }

    current_composite.map(|composite| &composite.code_ref)
}

fn downstream_composite_id_for_solid_source(
    artifact_graph: &ArtifactGraph,
    source_id: ArtifactId,
) -> Option<ArtifactId> {
    // Source is a path, find its solid.
    if let Some(Artifact::Path(path)) = artifact_graph.get(&source_id)
        && let Some(composite_id) = path.composite_solid_id
        && let Some(Artifact::CompositeSolid(composite)) = artifact_graph.get(&composite_id)
        && composite_contains_path_input(&composite.solid_ids, &composite.tool_ids, path.id, path.solid2d_id)
    {
        return Some(composite_id);
    }

    // Source is a sweep, find its path -> then find the solid
    for artifact in artifact_graph.values() {
        if let Artifact::Path(path) = artifact
            && path.sweep_id == Some(source_id)
            && let Some(composite_id) = path.composite_solid_id
            && let Some(Artifact::CompositeSolid(composite)) = artifact_graph.get(&composite_id)
            && composite_contains_path_input(&composite.solid_ids, &composite.tool_ids, path.id, path.solid2d_id)
        {
            return Some(composite_id);
        }
    }

    // Source is a solid, find its downstream solid.
    artifact_graph.values().find_map(|artifact| {
        let Artifact::CompositeSolid(composite) = artifact else {
            return None;
        };
        composite_contains_input(&composite.solid_ids, &composite.tool_ids, source_id).then_some(composite.id)
    })
}

fn composite_contains_path_input(
    solid_ids: &[ArtifactId],
    tool_ids: &[ArtifactId],
    path_id: ArtifactId,
    solid2d_id: Option<ArtifactId>,
) -> bool {
    composite_contains_input(solid_ids, tool_ids, path_id)
        || solid2d_id.is_some_and(|solid2d_id| composite_contains_input(solid_ids, tool_ids, solid2d_id))
}

fn composite_contains_input(solid_ids: &[ArtifactId], tool_ids: &[ArtifactId], input_id: ArtifactId) -> bool {
    solid_ids.contains(&input_id) || tool_ids.contains(&input_id)
}

fn code_ref_source_ref_range(code_ref: &CodeRef) -> SourceRefRange {
    let node_path = (!code_ref.node_path.is_empty()).then(|| code_ref.node_path.clone());
    SourceRefRange {
        range: code_ref.range,
        node_path,
    }
}

fn solid_output_index_for_sweep(
    artifact_graph: &ArtifactGraph,
    sweep_id: ArtifactId,
    sweep_code_ref: &CodeRef,
) -> Option<usize> {
    let sibling_sweeps = artifact_graph
        .values()
        .filter_map(|artifact| match artifact {
            Artifact::Sweep(sweep)
                if sweep.code_ref.range == sweep_code_ref.range
                    && sweep.code_ref.node_path == sweep_code_ref.node_path =>
            {
                Some(sweep)
            }
            _ => None,
        })
        .collect::<Vec<_>>();

    if sibling_sweeps.len() <= 1 {
        return None;
    }

    sibling_sweeps
        .iter()
        .position(|sibling_sweep| sibling_sweep.id == sweep_id)
}

fn add_wall_and_cap_face_objects(scene_objects: &mut Vec<crate::front::Object>, artifact_graph: &ArtifactGraph) {
    let mut existing_artifact_ids = scene_objects
        .iter()
        .map(|object| object.artifact_id)
        .collect::<HashSet<_>>();

    for artifact in artifact_graph.values() {
        match artifact {
            Artifact::Wall(wall) => {
                if existing_artifact_ids.contains(&wall.id) {
                    continue;
                }

                let Some(segment) = artifact_graph.get(&wall.seg_id).and_then(|artifact| match artifact {
                    Artifact::Segment(segment) => Some(segment),
                    _ => None,
                }) else {
                    continue;
                };
                let Some(sweep) = artifact_graph.get(&wall.sweep_id).and_then(|artifact| match artifact {
                    Artifact::Sweep(sweep) => Some(sweep),
                    _ => None,
                }) else {
                    continue;
                };
                let source_segment = segment
                    .original_seg_id
                    .and_then(|original_seg_id| artifact_graph.get(&original_seg_id))
                    .and_then(|artifact| match artifact {
                        Artifact::Segment(segment) => Some(segment),
                        _ => None,
                    })
                    .unwrap_or(segment);
                let solid_code_ref =
                    downstream_composite_code_ref_for_source(artifact_graph, wall.sweep_id).unwrap_or(&sweep.code_ref);
                let path_code_ref = artifact_graph
                    .get(&segment.path_id)
                    .or_else(|| artifact_graph.get(&sweep.path_id))
                    .and_then(|artifact| match artifact {
                        Artifact::Path(path) => Some(&path.code_ref),
                        _ => None,
                    });
                let source = WallSource {
                    solid: code_ref_source_ref_range(solid_code_ref),
                    sweep: code_ref_source_ref_range(&sweep.code_ref),
                    path: path_code_ref.map(code_ref_source_ref_range),
                    segment: code_ref_source_ref_range(&source_segment.code_ref),
                };
                let solid_output_index = (solid_code_ref.range == sweep.code_ref.range
                    && solid_code_ref.node_path == sweep.code_ref.node_path)
                    .then(|| solid_output_index_for_sweep(artifact_graph, sweep.id, &sweep.code_ref))
                    .flatten();
                let object_source = source_ref_from_source_ref_range(&source.solid);
                let id = ObjectId(scene_objects.len());
                scene_objects.push(crate::front::Object {
                    id,
                    kind: ObjectKind::Wall(crate::frontend::api::Wall {
                        id,
                        source,
                        solid_output_index,
                    }),
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: wall.id,
                    source: object_source,
                });
                existing_artifact_ids.insert(wall.id);
            }
            Artifact::Cap(cap) => {
                if existing_artifact_ids.contains(&cap.id) {
                    continue;
                }

                let Some(sweep) = artifact_graph.get(&cap.sweep_id).and_then(|artifact| match artifact {
                    Artifact::Sweep(sweep) => Some(sweep),
                    _ => None,
                }) else {
                    continue;
                };
                let id = ObjectId(scene_objects.len());
                let kind = match cap.sub_type {
                    CapSubType::Start => crate::frontend::api::CapKind::Start,
                    CapSubType::End => crate::frontend::api::CapKind::End,
                };
                let solid_code_ref =
                    downstream_composite_code_ref_for_source(artifact_graph, cap.sweep_id).unwrap_or(&sweep.code_ref);
                let source = CapSource {
                    solid: code_ref_source_ref_range(solid_code_ref),
                    sweep: code_ref_source_ref_range(&sweep.code_ref),
                };
                let solid_output_index = (solid_code_ref.range == sweep.code_ref.range
                    && solid_code_ref.node_path == sweep.code_ref.node_path)
                    .then(|| solid_output_index_for_sweep(artifact_graph, sweep.id, &sweep.code_ref))
                    .flatten();
                let object_source = source_ref_from_source_ref_range(&source.solid);
                scene_objects.push(crate::front::Object {
                    id,
                    kind: ObjectKind::Cap(crate::frontend::api::Cap {
                        id,
                        kind,
                        source,
                        solid_output_index,
                    }),
                    label: Default::default(),
                    comments: Default::default(),
                    artifact_id: cap.id,
                    source: object_source,
                });
                existing_artifact_ids.insert(cap.id);
            }
            _ => {}
        }
    }
}

fn default_plane_ast_expr(name: crate::engine::PlaneName) -> ast::Expr {
    use crate::engine::PlaneName;

    match name {
        PlaneName::Xy => ast_name_expr("XY".to_owned()),
        PlaneName::Xz => ast_name_expr("XZ".to_owned()),
        PlaneName::Yz => ast_name_expr("YZ".to_owned()),
        PlaneName::NegXy => negated_plane_ast_expr("XY"),
        PlaneName::NegXz => negated_plane_ast_expr("XZ"),
        PlaneName::NegYz => negated_plane_ast_expr("YZ"),
    }
}

fn negated_plane_ast_expr(name: &str) -> ast::Expr {
    ast::Expr::UnaryExpression(Box::new(ast::UnaryExpression::new(
        ast::UnaryOperator::Neg,
        ast::BinaryPart::Name(Box::new(ast_name(name.to_owned()))),
    )))
}

fn create_face_of_ast(solid_expr: ast::Expr, face_expr: ast::Expr) -> ast::Expr {
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name("faceOf")),
        unlabeled: Some(solid_expr),
        arguments: vec![ast::LabeledArg {
            label: Some(ast::Identifier::new("face")),
            arg: face_expr,
        }],
        digest: None,
        non_code_meta: Default::default(),
    })))
}

fn region_name_from_sweep_variable(ast: &ast::Node<ast::Program>, sweep_variable_name: &str) -> Option<String> {
    let ast::Definition::Variable(sweep_decl) = ast.get_variable(sweep_variable_name)? else {
        return None;
    };
    let ast::Expr::CallExpressionKw(sweep_call) = &sweep_decl.init else {
        return None;
    };
    if !matches!(
        sweep_call.callee.name.name.as_str(),
        "extrude" | "revolve" | "sweep" | "loft"
    ) {
        return None;
    }
    let ast::Expr::Name(region_name_expr) = sweep_call.unlabeled.as_ref()? else {
        return None;
    };
    let candidate = region_name_expr.name.name.clone();
    let ast::Definition::Variable(region_decl) = ast.get_variable(&candidate)? else {
        return None;
    };
    let ast::Expr::CallExpressionKw(region_call) = &region_decl.init else {
        return None;
    };
    if region_call.callee.name.name != "region" {
        return None;
    }
    Some(candidate)
}

/// Return the AST expression referencing the variable at the given source ref.
/// If no such variable exists, insert a new variable declaration with the given
/// prefix.
///
/// This may return a complex expression referencing properties of the variable
/// (e.g., `line1.start`).
fn get_or_insert_ast_reference(
    ast: &mut ast::Node<ast::Program>,
    source_ref: &SourceRef,
    prefix: &str,
    property: Option<&str>,
) -> Result<ast::Expr, KclError> {
    let command = AstMutateCommand::AddVariableDeclaration {
        prefix: prefix.to_owned(),
    };
    let ret = match mutate_ast_node_by_source_ref(ast, source_ref, command) {
        Ok((_, ret)) => ret,
        Err(err) => {
            if let Some(var_name) = variable_name_containing_source_ref(ast, source_ref) {
                AstMutateCommandReturn::Name(var_name)
            } else {
                return Err(err);
            }
        }
    };
    let AstMutateCommandReturn::Name(var_name) = ret else {
        return Err(KclError::refactor(
            "Expected variable name returned from AddVariableDeclaration".to_owned(),
        ));
    };
    let var_expr = ast::Expr::Name(Box::new(ast::Name::new(&var_name)));
    let Some(property) = property else {
        // No property; just return the variable name.
        return Ok(var_expr);
    };

    Ok(create_member_expression(var_expr, property))
}

fn variable_name_containing_source_ref(ast: &ast::Node<ast::Program>, source_ref: &SourceRef) -> Option<String> {
    let source_range = match source_ref {
        SourceRef::Simple { range, .. } => *range,
        SourceRef::BackTrace { ranges } => {
            let [range] = ranges.as_slice() else {
                return None;
            };
            range.0
        }
    };
    ast.body.iter().find_map(|item| {
        let ast::BodyItem::VariableDeclaration(var_decl) = item else {
            return None;
        };
        let init_range = SourceRange::from(&var_decl.declaration.init);
        let source_is_inside_init = init_range.module_id() == source_range.module_id()
            && init_range.start() <= source_range.start()
            && source_range.end() <= init_range.end();
        if matches!(&var_decl.declaration.init, ast::Expr::SketchBlock(_))
            && init_range != source_range
            && source_is_inside_init
        {
            return None;
        }
        source_is_inside_init.then(|| var_decl.name().to_owned())
    })
}

fn mutate_ast_node_by_source_ref(
    ast: &mut ast::Node<ast::Program>,
    source_ref: &SourceRef,
    command: AstMutateCommand,
) -> Result<(AstNodeRef, AstMutateCommandReturn), KclError> {
    let (source_range, node_path) = match source_ref {
        SourceRef::Simple { range, node_path } => (*range, node_path.clone()),
        SourceRef::BackTrace { ranges } => {
            let [range] = ranges.as_slice() else {
                return Err(KclError::refactor(format!(
                    "Expected single source ref, got {}; ranges={ranges:#?}",
                    ranges.len(),
                )));
            };
            (range.0, range.1.clone())
        }
    };
    let mut context = AstMutateContext {
        source_range,
        node_path,
        command,
        defined_names_stack: Default::default(),
    };
    let control = dfs_mut(ast, &mut context);
    match control {
        ControlFlow::Continue(_) => Err(KclError::refactor(
            "Could not find the KCL source for this edit. Try reloading the app, or update from code.".to_owned(),
        )),
        ControlFlow::Break(break_value) => break_value,
    }
}

#[derive(Debug)]
struct AstMutateContext {
    source_range: SourceRange,
    node_path: Option<ast::NodePath>,
    command: AstMutateCommand,
    defined_names_stack: Vec<HashSet<String>>,
}

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum AstMutateCommand {
    /// Add an expression statement to the sketch block.
    AddSketchBlockExprStmt {
        expr: ast::Expr,
    },
    /// Add a variable declaration to the sketch block (e.g. `line1 = line(...)`).
    AddSketchBlockVarDecl {
        prefix: String,
        expr: ast::Expr,
    },
    AddVariableDeclaration {
        prefix: String,
    },
    EditPoint {
        at: ast::Expr,
    },
    EditLine {
        start: ast::Expr,
        end: ast::Expr,
        construction: Option<bool>,
    },
    EditArc {
        start: ast::Expr,
        end: ast::Expr,
        center: ast::Expr,
        construction: Option<bool>,
    },
    EditCircle {
        start: ast::Expr,
        center: ast::Expr,
        construction: Option<bool>,
    },
    EditControlPointSpline {
        points: ast::Expr,
        construction: Option<bool>,
    },
    EditConstraintValue {
        value: ast::BinaryPart,
    },
    EditDistanceConstraintLabelPosition {
        label_position: ast::Expr,
    },
    EditCallUnlabeled {
        arg: ast::Expr,
    },
    EditVarInitialValue {
        value: Number,
    },
    DeleteNode,
}

impl AstMutateCommand {
    fn needs_defined_names_stack(&self) -> bool {
        matches!(
            self,
            AstMutateCommand::AddSketchBlockVarDecl { .. } | AstMutateCommand::AddVariableDeclaration { .. }
        )
    }
}

#[derive(Debug)]
enum AstMutateCommandReturn {
    None,
    Name(String),
}

#[derive(Debug, Clone)]
struct AstNodeRef {
    range: SourceRange,
    node_path: Option<ast::NodePath>,
}

impl<T> From<&ast::Node<T>> for AstNodeRef {
    fn from(value: &ast::Node<T>) -> Self {
        AstNodeRef {
            range: value.into(),
            node_path: value.node_path.clone(),
        }
    }
}

impl From<&ast::BodyItem> for AstNodeRef {
    fn from(value: &ast::BodyItem) -> Self {
        match value {
            ast::BodyItem::ImportStatement(node) => AstNodeRef {
                range: node.into(),
                node_path: node.node_path.clone(),
            },
            ast::BodyItem::ExpressionStatement(node) => AstNodeRef {
                range: node.into(),
                node_path: node.node_path.clone(),
            },
            ast::BodyItem::VariableDeclaration(node) => AstNodeRef {
                range: node.into(),
                node_path: node.node_path.clone(),
            },
            ast::BodyItem::TypeDeclaration(node) => AstNodeRef {
                range: node.into(),
                node_path: node.node_path.clone(),
            },
            ast::BodyItem::ReturnStatement(node) => AstNodeRef {
                range: node.into(),
                node_path: node.node_path.clone(),
            },
        }
    }
}

impl From<&ast::Expr> for AstNodeRef {
    fn from(value: &ast::Expr) -> Self {
        AstNodeRef {
            range: SourceRange::from(value),
            node_path: value.node_path().cloned(),
        }
    }
}

impl From<&AstMutateContext> for AstNodeRef {
    fn from(value: &AstMutateContext) -> Self {
        AstNodeRef {
            range: value.source_range,
            node_path: value.node_path.clone(),
        }
    }
}

impl TryFrom<&NodeMut<'_>> for AstNodeRef {
    type Error = crate::walk::AstNodeError;

    fn try_from(value: &NodeMut<'_>) -> Result<Self, Self::Error> {
        Ok(AstNodeRef {
            range: SourceRange::try_from(value)?,
            node_path: value.try_into()?,
        })
    }
}

impl From<AstNodeRef> for SourceRange {
    fn from(value: AstNodeRef) -> Self {
        value.range
    }
}

impl Visitor for AstMutateContext {
    type Break = Result<(AstNodeRef, AstMutateCommandReturn), KclError>;
    type Continue = ();

    fn visit(&mut self, node: NodeMut<'_>) -> TraversalReturn<Self::Break, Self::Continue> {
        filter_and_process(self, node)
    }

    fn finish(&mut self, node: NodeMut<'_>) {
        match &node {
            NodeMut::Program(_) | NodeMut::SketchBlock(_) => {
                self.defined_names_stack.pop();
            }
            _ => {}
        }
    }
}

fn filter_and_process(
    ctx: &mut AstMutateContext,
    node: NodeMut,
) -> TraversalReturn<Result<(AstNodeRef, AstMutateCommandReturn), KclError>> {
    let Ok(node_range) = SourceRange::try_from(&node) else {
        // Nodes that can't be converted to a range aren't interesting.
        return TraversalReturn::new_continue(());
    };
    // If we're adding a variable declaration, we need to look at variable
    // declaration expressions to see if it already has a variable, before
    // continuing. The variable declaration's source range won't match the
    // target; its init expression will.
    if let NodeMut::VariableDeclaration(var_decl) = &node {
        let expr_range = SourceRange::from(&var_decl.declaration.init);
        let expr_node_path = var_decl.declaration.init.node_path();
        if source_ref_matches(ctx, expr_range, expr_node_path) {
            if let AstMutateCommand::AddVariableDeclaration { .. } = &ctx.command {
                // We found the variable declaration expression. It doesn't need
                // to be added.
                return TraversalReturn::new_break(Ok((
                    AstNodeRef::from(&**var_decl),
                    AstMutateCommandReturn::Name(var_decl.name().to_owned()),
                )));
            }
            if let AstMutateCommand::DeleteNode = &ctx.command {
                // We found the variable declaration. Delete the variable along
                // with the segment.
                return TraversalReturn {
                    mutate_body_item: MutateBodyItem::Delete,
                    control_flow: ControlFlow::Break(Ok((AstNodeRef::from(&*ctx), AstMutateCommandReturn::None))),
                };
            }
        }
    }
    // Similar thing with expression statement. We need to look at the
    // expression inside it.
    if let NodeMut::ExpressionStatement(expr_stmt) = &node {
        let expr_range = SourceRange::from(&expr_stmt.expression);
        let expr_node_path = expr_stmt.expression.node_path();
        if source_ref_matches(ctx, expr_range, expr_node_path) {
            if let AstMutateCommand::AddVariableDeclaration { .. } = &ctx.command {
                // We found the node wrapped in an expression statement. Process
                // the statement.
                let Ok(node_ref) = AstNodeRef::try_from(&node) else {
                    return TraversalReturn::new_continue(());
                };
                return process(ctx, node).map_break(|result| result.map(|cmd_return| (node_ref, cmd_return)));
            }
            if let AstMutateCommand::DeleteNode = &ctx.command {
                // We found the node wrapped in an expression statement. Delete
                // the whole statement.
                return TraversalReturn {
                    mutate_body_item: MutateBodyItem::Delete,
                    control_flow: ControlFlow::Break(Ok((AstNodeRef::from(&*ctx), AstMutateCommandReturn::None))),
                };
            }
        }
    }

    if ctx.command.needs_defined_names_stack() {
        if let NodeMut::Program(program) = &node {
            ctx.defined_names_stack.push(find_defined_names(*program));
        } else if let NodeMut::SketchBlock(block) = &node {
            ctx.defined_names_stack.push(find_defined_names(&block.body));
        }
    }

    // Make sure the node matches the source ref.
    let node_path = <Option<ast::NodePath>>::try_from(&node).ok().flatten();
    if !source_ref_matches(ctx, node_range, node_path.as_ref()) {
        return TraversalReturn::new_continue(());
    }
    let Ok(node_ref) = AstNodeRef::try_from(&node) else {
        return TraversalReturn::new_continue(());
    };
    process(ctx, node).map_break(|result| result.map(|cmd_return| (node_ref, cmd_return)))
}

fn source_ref_matches(ctx: &AstMutateContext, node_range: SourceRange, node_path: Option<&ast::NodePath>) -> bool {
    match &ctx.node_path {
        Some(target) => Some(target) == node_path,
        None => node_range == ctx.source_range,
    }
}

fn process(ctx: &AstMutateContext, node: NodeMut) -> TraversalReturn<Result<AstMutateCommandReturn, KclError>> {
    match &ctx.command {
        AstMutateCommand::AddSketchBlockExprStmt { expr } => {
            if let NodeMut::SketchBlock(sketch_block) = node {
                sketch_block
                    .body
                    .items
                    .push(ast::BodyItem::ExpressionStatement(ast::Node {
                        inner: ast::ExpressionStatement {
                            expression: expr.clone(),
                            digest: None,
                        },
                        start: Default::default(),
                        end: Default::default(),
                        module_id: Default::default(),
                        node_path: None,
                        outer_attrs: Default::default(),
                        pre_comments: Default::default(),
                        comment_start: Default::default(),
                    }));
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::AddSketchBlockVarDecl { prefix, expr } => {
            if let NodeMut::SketchBlock(sketch_block) = node {
                let empty_defined_names = HashSet::new();
                let defined_names = ctx.defined_names_stack.last().unwrap_or(&empty_defined_names);
                let Ok(name) = next_free_name(prefix, defined_names) else {
                    return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
                };
                sketch_block
                    .body
                    .items
                    .push(ast::BodyItem::VariableDeclaration(Box::new(ast::Node::no_src(
                        ast::VariableDeclaration::new(
                            ast::VariableDeclarator::new(&name, expr.clone()),
                            ast::ItemVisibility::Default,
                            ast::VariableKind::Const,
                        ),
                    ))));
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::Name(name)));
            }
        }
        AstMutateCommand::AddVariableDeclaration { prefix } => {
            if let NodeMut::VariableDeclaration(inner) = node {
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::Name(inner.name().to_owned())));
            }
            if let NodeMut::ExpressionStatement(expr_stmt) = node {
                let empty_defined_names = HashSet::new();
                let defined_names = ctx.defined_names_stack.last().unwrap_or(&empty_defined_names);
                let Ok(name) = next_free_name(prefix, defined_names) else {
                    // TODO: Return an error instead?
                    return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
                };
                let mutate_node =
                    ast::BodyItem::VariableDeclaration(Box::new(ast::Node::no_src(ast::VariableDeclaration::new(
                        ast::VariableDeclarator::new(&name, expr_stmt.expression.clone()),
                        ast::ItemVisibility::Default,
                        ast::VariableKind::Const,
                    ))));
                return TraversalReturn {
                    mutate_body_item: MutateBodyItem::Mutate(Box::new(mutate_node)),
                    control_flow: ControlFlow::Break(Ok(AstMutateCommandReturn::Name(name))),
                };
            }
        }
        AstMutateCommand::EditPoint { at } => {
            if let NodeMut::CallExpressionKw(call) = node {
                if call.callee.name.name != POINT_FN {
                    return TraversalReturn::new_continue(());
                }
                // Update the arguments.
                for labeled_arg in &mut call.arguments {
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(POINT_AT_PARAM) {
                        labeled_arg.arg = at.clone();
                    }
                }
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditLine {
            start,
            end,
            construction,
        } => {
            if let NodeMut::CallExpressionKw(call) = node {
                if call.callee.name.name != LINE_FN {
                    return TraversalReturn::new_continue(());
                }
                // Update the arguments.
                for labeled_arg in &mut call.arguments {
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(LINE_START_PARAM) {
                        labeled_arg.arg = start.clone();
                    }
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(LINE_END_PARAM) {
                        labeled_arg.arg = end.clone();
                    }
                }
                // Handle construction kwarg
                if let Some(construction_value) = construction {
                    let construction_exists = call
                        .arguments
                        .iter()
                        .any(|arg| arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM));
                    if *construction_value {
                        // Add or update construction=true
                        if construction_exists {
                            // Update existing construction kwarg
                            for labeled_arg in &mut call.arguments {
                                if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM) {
                                    labeled_arg.arg = ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                        value: ast::LiteralValue::Bool(true),
                                        raw: "true".to_string(),
                                        digest: None,
                                    })));
                                }
                            }
                        } else {
                            // Add new construction kwarg
                            call.arguments.push(ast::LabeledArg {
                                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                    value: ast::LiteralValue::Bool(true),
                                    raw: "true".to_string(),
                                    digest: None,
                                }))),
                            });
                        }
                    } else {
                        // Remove construction kwarg if it exists
                        call.arguments
                            .retain(|arg| arg.label.as_ref().map(|id| id.name.as_str()) != Some(CONSTRUCTION_PARAM));
                    }
                }
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditArc {
            start,
            end,
            center,
            construction,
        } => {
            if let NodeMut::CallExpressionKw(call) = node {
                if call.callee.name.name != ARC_FN {
                    return TraversalReturn::new_continue(());
                }
                // Update the arguments.
                for labeled_arg in &mut call.arguments {
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(ARC_START_PARAM) {
                        labeled_arg.arg = start.clone();
                    }
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(ARC_END_PARAM) {
                        labeled_arg.arg = end.clone();
                    }
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(ARC_CENTER_PARAM) {
                        labeled_arg.arg = center.clone();
                    }
                }
                // Handle construction kwarg
                if let Some(construction_value) = construction {
                    let construction_exists = call
                        .arguments
                        .iter()
                        .any(|arg| arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM));
                    if *construction_value {
                        // Add or update construction=true
                        if construction_exists {
                            // Update existing construction kwarg
                            for labeled_arg in &mut call.arguments {
                                if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM) {
                                    labeled_arg.arg = ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                        value: ast::LiteralValue::Bool(true),
                                        raw: "true".to_string(),
                                        digest: None,
                                    })));
                                }
                            }
                        } else {
                            // Add new construction kwarg
                            call.arguments.push(ast::LabeledArg {
                                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                    value: ast::LiteralValue::Bool(true),
                                    raw: "true".to_string(),
                                    digest: None,
                                }))),
                            });
                        }
                    } else {
                        // Remove construction kwarg if it exists
                        call.arguments
                            .retain(|arg| arg.label.as_ref().map(|id| id.name.as_str()) != Some(CONSTRUCTION_PARAM));
                    }
                }
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditCircle {
            start,
            center,
            construction,
        } => {
            if let NodeMut::CallExpressionKw(call) = node {
                if call.callee.name.name != CIRCLE_FN {
                    return TraversalReturn::new_continue(());
                }
                // Update the arguments.
                for labeled_arg in &mut call.arguments {
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CIRCLE_START_PARAM) {
                        labeled_arg.arg = start.clone();
                    }
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CIRCLE_CENTER_PARAM) {
                        labeled_arg.arg = center.clone();
                    }
                }
                // Handle construction kwarg
                if let Some(construction_value) = construction {
                    let construction_exists = call
                        .arguments
                        .iter()
                        .any(|arg| arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM));
                    if *construction_value {
                        if construction_exists {
                            // Update existing construction kwarg
                            for labeled_arg in &mut call.arguments {
                                if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM) {
                                    labeled_arg.arg = ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                        value: ast::LiteralValue::Bool(true),
                                        raw: "true".to_string(),
                                        digest: None,
                                    })));
                                }
                            }
                        } else {
                            // Add new construction kwarg
                            call.arguments.push(ast::LabeledArg {
                                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                    value: ast::LiteralValue::Bool(true),
                                    raw: "true".to_string(),
                                    digest: None,
                                }))),
                            });
                        }
                    } else {
                        // Remove construction kwarg if it exists
                        call.arguments
                            .retain(|arg| arg.label.as_ref().map(|id| id.name.as_str()) != Some(CONSTRUCTION_PARAM));
                    }
                }
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditControlPointSpline { points, construction } => {
            if let NodeMut::CallExpressionKw(call) = node {
                if call.callee.name.name != CONTROL_POINT_SPLINE_FN {
                    return TraversalReturn::new_continue(());
                }
                for labeled_arg in &mut call.arguments {
                    if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONTROL_POINT_SPLINE_POINTS_PARAM)
                    {
                        labeled_arg.arg = points.clone();
                    }
                }
                // Handle construction kwarg
                if let Some(construction_value) = construction {
                    let construction_exists = call
                        .arguments
                        .iter()
                        .any(|arg| arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM));
                    if *construction_value {
                        if construction_exists {
                            for labeled_arg in &mut call.arguments {
                                if labeled_arg.label.as_ref().map(|id| id.name.as_str()) == Some(CONSTRUCTION_PARAM) {
                                    labeled_arg.arg = ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                        value: ast::LiteralValue::Bool(true),
                                        raw: "true".to_string(),
                                        digest: None,
                                    })));
                                }
                            }
                        } else {
                            call.arguments.push(ast::LabeledArg {
                                label: Some(ast::Identifier::new(CONSTRUCTION_PARAM)),
                                arg: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal {
                                    value: ast::LiteralValue::Bool(true),
                                    raw: "true".to_string(),
                                    digest: None,
                                }))),
                            });
                        }
                    } else {
                        call.arguments
                            .retain(|arg| arg.label.as_ref().map(|id| id.name.as_str()) != Some(CONSTRUCTION_PARAM));
                    }
                }
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditConstraintValue { value } => {
            if let NodeMut::BinaryExpression(binary_expr) = node {
                let left_is_constraint = matches!(
                    &binary_expr.left,
                    ast::BinaryPart::CallExpressionKw(call)
                        if matches!(
                            call.callee.name.name.as_str(),
                            DISTANCE_FN | HORIZONTAL_DISTANCE_FN | VERTICAL_DISTANCE_FN | RADIUS_FN | DIAMETER_FN | ANGLE_FN
                        )
                );
                if left_is_constraint {
                    binary_expr.right = value.clone();
                } else {
                    binary_expr.left = value.clone();
                }

                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditDistanceConstraintLabelPosition { label_position } => {
            if let NodeMut::BinaryExpression(binary_expr) = node {
                let ast::BinaryPart::CallExpressionKw(call) = &mut binary_expr.left else {
                    return TraversalReturn::new_continue(());
                };
                if !matches!(
                    call.callee.name.name.as_str(),
                    DISTANCE_FN | HORIZONTAL_DISTANCE_FN | VERTICAL_DISTANCE_FN | RADIUS_FN | DIAMETER_FN
                ) {
                    return TraversalReturn::new_continue(());
                }

                if let Some(label_arg) = call
                    .arguments
                    .iter_mut()
                    .find(|arg| arg.label.as_ref().map(|id| id.name.as_str()) == Some(LABEL_POSITION_PARAM))
                {
                    label_arg.arg = label_position.clone();
                } else {
                    call.arguments.push(ast::LabeledArg {
                        label: Some(ast::Identifier::new(LABEL_POSITION_PARAM)),
                        arg: label_position.clone(),
                    });
                }

                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditCallUnlabeled { arg } => {
            if let NodeMut::CallExpressionKw(call) = node {
                call.unlabeled = Some(arg.clone());
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::EditVarInitialValue { value } => {
            // We target the SketchVar itself (matched by NodePath) rather than
            // the inner NumericLiteral so we can also write back into vars that
            // were declared without an initial value (e.g. bare `var`).
            if let NodeMut::SketchVar(sketch_var) = node {
                let Ok(literal) = to_source_number(*value) else {
                    return TraversalReturn::new_break(Err(KclError::refactor(format!(
                        "Could not convert number to AST literal: {:?}",
                        *value
                    ))));
                };
                sketch_var.initial = Some(Box::new(ast::Node::no_src(literal)));
                return TraversalReturn::new_break(Ok(AstMutateCommandReturn::None));
            }
        }
        AstMutateCommand::DeleteNode => {
            return TraversalReturn {
                mutate_body_item: MutateBodyItem::Delete,
                control_flow: ControlFlow::Break(Ok(AstMutateCommandReturn::None)),
            };
        }
    }
    TraversalReturn::new_continue(())
}

struct FindSketchBlockSourceRange {
    /// The source range of the sketch block before mutation.
    target_before_mutation: SourceRange,
    /// The source range of the sketch block's last body item after mutation. We
    /// need to use a [Cell] since the [crate::walk::Visitor] trait requires a
    /// shared reference.
    found: Cell<Option<AstNodeRef>>,
}

impl<'a> crate::walk::Visitor<'a> for &FindSketchBlockSourceRange {
    type Error = crate::front::Error;

    fn visit_node(&self, node: crate::walk::Node<'a>) -> anyhow::Result<bool, Self::Error> {
        let Ok(node_range) = SourceRange::try_from(&node) else {
            return Ok(true);
        };

        if let crate::walk::Node::SketchBlock(sketch_block) = node {
            if node_range.module_id() == self.target_before_mutation.module_id()
                && node_range.start() == self.target_before_mutation.start()
                // End shouldn't match since we added something.
                && node_range.end() >= self.target_before_mutation.end()
            {
                self.found.set(sketch_block.body.items.last().map(|item| match item {
                    // For declarations like `circle1 = circle(...)`, use
                    // the init expression range so lookup in source_range_to_object
                    // matches the segment source range.
                    ast::BodyItem::VariableDeclaration(node) => AstNodeRef::from(&node.declaration.init),
                    _ => AstNodeRef::from(item),
                }));
                return Ok(false);
            } else {
                // We found a different sketch block. No need to descend into
                // its children since sketch blocks cannot be nested.
                return Ok(true);
            }
        }

        for child in node.children().iter() {
            if !child.visit(*self)? {
                return Ok(false);
            }
        }

        Ok(true)
    }
}

struct FindSketchBlockByNodePath {
    /// The Node Path of the sketch block before mutation.
    target_node_path: ast::NodePath,
    /// The ref of the sketch block's last body item after mutation. We need to
    /// use a [Cell] since the [crate::walk::Visitor] trait requires a shared
    /// reference.
    found: Cell<Option<AstNodeRef>>,
}

impl<'a> crate::walk::Visitor<'a> for &FindSketchBlockByNodePath {
    type Error = crate::front::Error;

    fn visit_node(&self, node: crate::walk::Node<'a>) -> anyhow::Result<bool, Self::Error> {
        let Ok(node_path) = <Option<ast::NodePath>>::try_from(&node) else {
            return Ok(true);
        };

        if let crate::walk::Node::SketchBlock(sketch_block) = node {
            if let Some(node_path) = node_path
                && node_path == self.target_node_path
            {
                self.found.set(sketch_block.body.items.last().map(|item| match item {
                    // For declarations like `circle1 = circle(...)`, use
                    // the init expression range so lookup in source_range_to_object
                    // matches the segment source range.
                    ast::BodyItem::VariableDeclaration(node) => AstNodeRef::from(&node.declaration.init),
                    _ => AstNodeRef::from(item),
                }));

                return Ok(false);
            } else {
                // We found a different sketch block. No need to descend into
                // its children since sketch blocks cannot be nested.
                return Ok(true);
            }
        }

        for child in node.children().iter() {
            if !child.visit(*self)? {
                return Ok(false);
            }
        }

        Ok(true)
    }
}

/// After adding an item to a sketch block, find the sketch block, and get the
/// source range of the added item. We assume that the added item is the last
/// item in the sketch block and that the sketch block's source range has grown,
/// but not moved from its starting offset.
///
/// TODO: Do we need to format *before* mutation in case formatting moves the
/// sketch block forward?
fn find_sketch_block_added_item(
    ast: &ast::Node<ast::Program>,
    sketch_block_before_mutation: &AstNodeRef,
) -> Result<AstNodeRef, KclError> {
    if let Some(node_path) = &sketch_block_before_mutation.node_path {
        let find = FindSketchBlockByNodePath {
            target_node_path: node_path.clone(),
            found: Cell::new(None),
        };
        let node = crate::walk::Node::from(ast);
        node.visit(&find).map_err(|err| KclError::refactor(err.msg))?;
        find.found.into_inner().ok_or_else(|| {
            KclError::refactor(format!(
                "Node ID after mutation not found for Node ID before mutation: {node_path:?}"
            ))
        })
    } else {
        // No NodePath. Fall back to legacy source range.
        let find = FindSketchBlockSourceRange {
            target_before_mutation: sketch_block_before_mutation.range,
            found: Cell::new(None),
        };
        let node = crate::walk::Node::from(ast);
        node.visit(&find).map_err(|err| KclError::refactor(err.msg))?;
        find.found.into_inner().ok_or_else(|| KclError::refactor(
            format!("Source range after mutation not found for range before mutation: {sketch_block_before_mutation:?}; Did you try formatting (i.e. call recast) before calling this?"),
        ))
    }
}

fn source_from_ast(ast: &ast::Node<ast::Program>) -> String {
    // TODO: Don't duplicate this from lib.rs Program.
    ast.recast_top(&Default::default(), 0)
}

struct FindNumericLiteral {
    target: SourceRange,
    found: Cell<Option<ast::NumericLiteral>>,
}

impl<'a> crate::walk::Visitor<'a> for &FindNumericLiteral {
    type Error = crate::front::Error;

    fn visit_node(&self, node: crate::walk::Node<'a>) -> anyhow::Result<bool, Self::Error> {
        let Ok(node_range) = SourceRange::try_from(&node) else {
            return Ok(true);
        };

        if node_range == self.target
            && let crate::walk::Node::NumericLiteral(literal) = node
        {
            self.found.set(Some(literal.inner.clone()));
            return Ok(false);
        }

        for child in node.children().iter() {
            if !child.visit(*self)? {
                return Ok(false);
            }
        }

        Ok(true)
    }
}

fn numeric_literal_at_source_range(ast: &ast::Node<ast::Program>, target: SourceRange) -> Option<ast::NumericLiteral> {
    let find = FindNumericLiteral {
        target,
        found: Cell::new(None),
    };
    let node = crate::walk::Node::from(ast);
    node.visit(&find).ok()?;
    find.found.into_inner()
}

struct FindSketchVarInitialByNodePath<'a> {
    target: &'a ast::NodePath,
    sketch_var_found: Cell<bool>,
    initial_literal: Cell<Option<ast::NumericLiteral>>,
}

impl<'a, 'b> crate::walk::Visitor<'b> for &FindSketchVarInitialByNodePath<'a> {
    type Error = crate::front::Error;

    fn visit_node(&self, node: crate::walk::Node<'b>) -> anyhow::Result<bool, Self::Error> {
        if let crate::walk::Node::SketchVar(sketch_var) = node
            && sketch_var.node_path.as_ref() == Some(self.target)
        {
            self.sketch_var_found.set(true);
            if let Some(initial) = &sketch_var.initial {
                self.initial_literal.set(Some(initial.inner.clone()));
            }
            return Ok(false);
        }

        for child in node.children().iter() {
            if !child.visit(*self)? {
                return Ok(false);
            }
        }

        Ok(true)
    }
}

/// Locate the source `var` declaration corresponding to a sketch-var solution.
///
/// The outer [`Option`] distinguishes "no matching target" (commit must fail)
/// from "target found." The inner [`Option`] is the initial numeric literal of
/// the [`SketchVar`], if any; bare `var` declarations return `Some(None)`.
///
/// When `node_path` is `None` (e.g. for older outcomes that predate the
/// node-path propagation), this falls back to source-range matching, which
/// can break under whitespace shifts elsewhere in the file.
fn numeric_literal_at_node_path(
    ast: &ast::Node<ast::Program>,
    node_path: Option<&ast::NodePath>,
    source_range: SourceRange,
) -> Option<Option<ast::NumericLiteral>> {
    let Some(node_path) = node_path else {
        let message = "numeric_literal_at_node_path: missing node_path on var solution; falling back to source-range lookup, which can fail under whitespace shifts";
        #[cfg(target_arch = "wasm32")]
        web_sys::console::warn_1(&message.into());
        #[cfg(not(target_arch = "wasm32"))]
        eprintln!("WARNING: {message}");
        return numeric_literal_at_source_range(ast, source_range).map(Some);
    };
    let find = FindSketchVarInitialByNodePath {
        target: node_path,
        sketch_var_found: Cell::new(false),
        initial_literal: Cell::new(None),
    };
    let node = crate::walk::Node::from(ast);
    node.visit(&find).ok()?;
    if !find.sketch_var_found.get() {
        return None;
    }
    Some(find.initial_literal.into_inner())
}

fn suffix_length_unit(suffix: NumericSuffix) -> Option<UnitLength> {
    match suffix {
        NumericSuffix::Mm => Some(UnitLength::Millimeters),
        NumericSuffix::Cm => Some(UnitLength::Centimeters),
        NumericSuffix::M => Some(UnitLength::Meters),
        NumericSuffix::Inch => Some(UnitLength::Inches),
        NumericSuffix::Ft => Some(UnitLength::Feet),
        NumericSuffix::Yd => Some(UnitLength::Yards),
        _ => None,
    }
}

fn number_value_in_default_length_units(number: Number, default_length_unit: UnitLength) -> f64 {
    match suffix_length_unit(number.units) {
        Some(unit) => adjust_length(unit, number.value, default_length_unit).0,
        None => number.value,
    }
}

fn literal_value_in_default_length_units(literal: &ast::NumericLiteral, default_length_unit: UnitLength) -> f64 {
    match suffix_length_unit(literal.suffix) {
        Some(unit) => adjust_length(unit, literal.value, default_length_unit).0,
        None => literal.value,
    }
}

fn var_solution_needs_commit(
    current_literal: &ast::NumericLiteral,
    solved_value: Number,
    default_length_unit: UnitLength,
) -> bool {
    let current = literal_value_in_default_length_units(current_literal, default_length_unit);
    let solved = number_value_in_default_length_units(solved_value, default_length_unit);

    (current - solved).abs() > 1e-9
}

fn preserve_var_solution_literal_style(
    current_literal: &ast::NumericLiteral,
    solved_value: Number,
    default_length_unit: UnitLength,
) -> Number {
    if current_literal.suffix == NumericSuffix::None {
        return Number {
            value: number_value_in_default_length_units(solved_value, default_length_unit),
            units: NumericSuffix::None,
        };
    }

    let Some(current_unit) = suffix_length_unit(current_literal.suffix) else {
        return solved_value;
    };

    let solved_default_value = number_value_in_default_length_units(solved_value, default_length_unit);
    Number {
        value: adjust_length(default_length_unit, solved_default_value, current_unit).0,
        units: current_literal.suffix,
    }
}

pub(crate) fn to_ast_point2d(point: &Point2d<Expr>) -> anyhow::Result<ast::Expr> {
    Ok(ast::Expr::ArrayExpression(Box::new(ast::Node {
        inner: ast::ArrayExpression {
            elements: vec![to_source_expr(&point.x)?, to_source_expr(&point.y)?],
            non_code_meta: Default::default(),
            digest: None,
        },
        start: Default::default(),
        end: Default::default(),
        module_id: Default::default(),
        node_path: None,
        outer_attrs: Default::default(),
        pre_comments: Default::default(),
        comment_start: Default::default(),
    })))
}

pub(crate) fn to_ast_point2d_array(points: &[Point2d<Expr>]) -> anyhow::Result<ast::Expr> {
    Ok(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
        ast::ArrayExpression {
            elements: points.iter().map(to_ast_point2d).collect::<anyhow::Result<Vec<_>>>()?,
            digest: None,
            non_code_meta: Default::default(),
        },
    ))))
}

fn to_ast_point2d_number(point: &Point2d<Number>) -> anyhow::Result<ast::Expr> {
    Ok(ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(
        ast::ArrayExpression {
            elements: vec![
                ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal::from(to_source_number(
                    point.x,
                )?)))),
                ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal::from(to_source_number(
                    point.y,
                )?)))),
            ],
            non_code_meta: Default::default(),
            digest: None,
        },
    ))))
}

fn to_source_expr(expr: &Expr) -> anyhow::Result<ast::Expr> {
    match expr {
        Expr::Number(number) => Ok(ast::Expr::Literal(Box::new(ast::Node {
            inner: ast::Literal::from(to_source_number(*number)?),
            start: Default::default(),
            end: Default::default(),
            module_id: Default::default(),
            node_path: None,
            outer_attrs: Default::default(),
            pre_comments: Default::default(),
            comment_start: Default::default(),
        }))),
        Expr::Var(number) => Ok(ast::Expr::SketchVar(Box::new(ast::Node {
            inner: ast::SketchVar {
                initial: Some(Box::new(ast::Node {
                    inner: to_source_number(*number)?,
                    start: Default::default(),
                    end: Default::default(),
                    module_id: Default::default(),
                    node_path: None,
                    outer_attrs: Default::default(),
                    pre_comments: Default::default(),
                    comment_start: Default::default(),
                })),
                digest: None,
            },
            start: Default::default(),
            end: Default::default(),
            module_id: Default::default(),
            node_path: None,
            outer_attrs: Default::default(),
            pre_comments: Default::default(),
            comment_start: Default::default(),
        }))),
        Expr::Variable(variable) => Ok(ast_name_expr(variable.clone())),
    }
}

fn to_source_number(number: Number) -> anyhow::Result<ast::NumericLiteral> {
    Ok(ast::NumericLiteral {
        value: number.value,
        suffix: number.units,
        raw: format_number_literal(number.value, number.units, None)?,
        digest: None,
    })
}

pub(crate) fn ast_name_expr(name: String) -> ast::Expr {
    ast::Expr::Name(Box::new(ast_name(name)))
}

fn ast_name(name: String) -> ast::Node<ast::Name> {
    ast::Node {
        inner: ast::Name {
            name: ast::Node {
                inner: ast::Identifier { name, digest: None },
                start: Default::default(),
                end: Default::default(),
                module_id: Default::default(),
                node_path: None,
                outer_attrs: Default::default(),
                pre_comments: Default::default(),
                comment_start: Default::default(),
            },
            path: Vec::new(),
            abs_path: false,
            digest: None,
        },
        start: Default::default(),
        end: Default::default(),
        module_id: Default::default(),
        node_path: None,
        outer_attrs: Default::default(),
        pre_comments: Default::default(),
        comment_start: Default::default(),
    }
}

pub(crate) fn ast_sketch2_name(name: &str) -> ast::Name {
    ast::Name {
        name: ast::Node {
            inner: ast::Identifier {
                name: name.to_owned(),
                digest: None,
            },
            start: Default::default(),
            end: Default::default(),
            module_id: Default::default(),
            node_path: None,
            outer_attrs: Default::default(),
            pre_comments: Default::default(),
            comment_start: Default::default(),
        },
        path: Default::default(),
        abs_path: false,
        digest: None,
    }
}

// Shared AST creation helpers used by both frontend and transpiler to ensure consistency.

/// Create an AST node for coincident([expr1, expr2, ...])
pub(crate) fn create_coincident_ast(exprs: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
    let elements = exprs.into_iter().collect::<Vec<_>>();
    debug_assert!(elements.len() >= 2, "Coincident AST should have at least 2 inputs");

    // Create array [expr1, expr2, ...]
    let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements,
        digest: None,
        non_code_meta: Default::default(),
    })));

    // Create coincident([...])
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(COINCIDENT_FN)),
        unlabeled: Some(array_expr),
        arguments: Default::default(),
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for line(start = [...], end = [...])
pub(crate) fn create_line_ast(start_ast: ast::Expr, end_ast: ast::Expr) -> ast::Expr {
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(LINE_FN)),
        unlabeled: None,
        arguments: vec![
            ast::LabeledArg {
                label: Some(ast::Identifier::new(LINE_START_PARAM)),
                arg: start_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(LINE_END_PARAM)),
                arg: end_ast,
            },
        ],
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for arc(start = [...], end = [...], center = [...])
pub(crate) fn create_arc_ast(start_ast: ast::Expr, end_ast: ast::Expr, center_ast: ast::Expr) -> ast::Expr {
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(ARC_FN)),
        unlabeled: None,
        arguments: vec![
            ast::LabeledArg {
                label: Some(ast::Identifier::new(ARC_START_PARAM)),
                arg: start_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(ARC_END_PARAM)),
                arg: end_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(ARC_CENTER_PARAM)),
                arg: center_ast,
            },
        ],
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for circle(start = [...], center = [...])
pub(crate) fn create_circle_ast(start_ast: ast::Expr, center_ast: ast::Expr) -> ast::Expr {
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(CIRCLE_FN)),
        unlabeled: None,
        arguments: vec![
            ast::LabeledArg {
                label: Some(ast::Identifier::new(CIRCLE_START_PARAM)),
                arg: start_ast,
            },
            ast::LabeledArg {
                label: Some(ast::Identifier::new(CIRCLE_CENTER_PARAM)),
                arg: center_ast,
            },
        ],
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for horizontal(line)
pub(crate) fn create_horizontal_ast(line_expr: ast::Expr) -> ast::Expr {
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(HORIZONTAL_FN)),
        unlabeled: Some(line_expr),
        arguments: Default::default(),
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for vertical(line)
pub(crate) fn create_vertical_ast(line_expr: ast::Expr) -> ast::Expr {
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(VERTICAL_FN)),
        unlabeled: Some(line_expr),
        arguments: Default::default(),
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create a member expression like object.property (e.g., line1.end)
pub(crate) fn create_member_expression(object_expr: ast::Expr, property: &str) -> ast::Expr {
    ast::Expr::MemberExpression(Box::new(ast::Node::no_src(ast::MemberExpression {
        object: object_expr,
        property: ast::Expr::Name(Box::new(ast::Node::no_src(ast::Name {
            name: ast::Node::no_src(ast::Identifier {
                name: property.to_string(),
                digest: None,
            }),
            path: Vec::new(),
            abs_path: false,
            digest: None,
        }))),
        computed: false,
        digest: None,
    })))
}

pub(crate) fn create_index_expression(object_expr: ast::Expr, index: usize) -> ast::Expr {
    ast::Expr::MemberExpression(Box::new(ast::Node::no_src(ast::MemberExpression {
        object: object_expr,
        property: ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal::from(ast::NumericLiteral {
            value: index as f64,
            suffix: NumericSuffix::None,
            raw: index.to_string(),
            digest: None,
        })))),
        computed: true,
        digest: None,
    })))
}

/// Create an AST node for `fixed([point, [x, y]])`.
fn create_fixed_point_constraint_ast(point_expr: ast::Expr, position: Point2d<Number>) -> anyhow::Result<ast::Expr> {
    // Create [x, y] array literal.
    let x_literal = ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal::from(to_source_number(
        position.x,
    )?))));
    let y_literal = ast::Expr::Literal(Box::new(ast::Node::no_src(ast::Literal::from(to_source_number(
        position.y,
    )?))));
    let point_array = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements: vec![x_literal, y_literal],
        digest: None,
        non_code_meta: Default::default(),
    })));

    // Create [point, [x, y]] outer array.
    let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements: vec![point_expr, point_array],
        digest: None,
        non_code_meta: Default::default(),
    })));

    // Create fixed([...])
    Ok(ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(
        ast::CallExpressionKw {
            callee: ast::Node::no_src(ast_sketch2_name(FIXED_FN)),
            unlabeled: Some(array_expr),
            arguments: Default::default(),
            digest: None,
            non_code_meta: Default::default(),
        },
    ))))
}

/// Create an AST node for equalLength([line1, line2, ...])
pub(crate) fn create_equal_length_ast(line_exprs: Vec<ast::Expr>) -> ast::Expr {
    let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements: line_exprs,
        digest: None,
        non_code_meta: Default::default(),
    })));

    // Create equalLength([...])
    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(EQUAL_LENGTH_FN)),
        unlabeled: Some(array_expr),
        arguments: Default::default(),
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for equalRadius([seg1, seg2, ...])
pub(crate) fn create_equal_radius_ast(segment_exprs: Vec<ast::Expr>) -> ast::Expr {
    let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements: segment_exprs,
        digest: None,
        non_code_meta: Default::default(),
    })));

    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(EQUAL_RADIUS_FN)),
        unlabeled: Some(array_expr),
        arguments: Default::default(),
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for tangent([seg1, seg2])
pub(crate) fn create_tangent_ast(seg1_expr: ast::Expr, seg2_expr: ast::Expr) -> ast::Expr {
    let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements: vec![seg1_expr, seg2_expr],
        digest: None,
        non_code_meta: Default::default(),
    })));

    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(TANGENT_FN)),
        unlabeled: Some(array_expr),
        arguments: Default::default(),
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for symmetric([input1, input2], axis = line)
pub(crate) fn create_symmetric_ast(input_exprs: Vec<ast::Expr>, axis_expr: ast::Expr) -> ast::Expr {
    let array_expr = ast::Expr::ArrayExpression(Box::new(ast::Node::no_src(ast::ArrayExpression {
        elements: input_exprs,
        digest: None,
        non_code_meta: Default::default(),
    })));
    let arguments = vec![ast::LabeledArg {
        label: Some(ast::Identifier::new(SYMMETRIC_AXIS_PARAM)),
        arg: axis_expr,
    }];

    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(SYMMETRIC_FN)),
        unlabeled: Some(array_expr),
        arguments,
        digest: None,
        non_code_meta: Default::default(),
    })))
}

/// Create an AST node for midpoint(segment, point = point)
pub(crate) fn create_midpoint_ast(segment_expr: ast::Expr, point_expr: ast::Expr) -> ast::Expr {
    let arguments = vec![ast::LabeledArg {
        label: Some(ast::Identifier::new(MIDPOINT_POINT_PARAM)),
        arg: point_expr,
    }];

    ast::Expr::CallExpressionKw(Box::new(ast::Node::no_src(ast::CallExpressionKw {
        callee: ast::Node::no_src(ast_sketch2_name(MIDPOINT_FN)),
        unlabeled: Some(segment_expr),
        arguments,
        digest: None,
        non_code_meta: Default::default(),
    })))
}

#[cfg(test)]
mod tests {
    use std::sync;

    use super::*;
    use crate::engine::PlaneName;
    use crate::engine::engine_manager::EngineManager;
    use crate::execution::cache::SketchModeState;
    use crate::execution::cache::clear_mem_cache;
    use crate::execution::cache::read_old_memory;
    use crate::execution::cache::write_old_memory;
    use crate::front::Distance;
    use crate::front::Fixed;
    use crate::front::FixedPoint;
    use crate::front::Midpoint;
    use crate::front::Object;
    use crate::front::Plane;
    use crate::front::Sketch;
    use crate::front::Tangent;
    use crate::frontend::sketch::Vertical;
    use crate::pretty::NumericSuffix;

    fn find_first_sketch_object(scene_graph: &SceneGraph) -> Option<&Object> {
        for object in &scene_graph.objects {
            if let ObjectKind::Sketch(_) = &object.kind {
                return Some(object);
            }
        }
        None
    }

    fn find_first_face_object(scene_graph: &SceneGraph) -> Option<&Object> {
        for object in &scene_graph.objects {
            if let ObjectKind::Face(_) = &object.kind {
                return Some(object);
            }
        }
        None
    }

    fn find_first_wall_object_id(scene_graph: &SceneGraph) -> Option<ObjectId> {
        for object in &scene_graph.objects {
            if matches!(&object.kind, ObjectKind::Wall(_)) {
                return Some(object.id);
            }
        }
        None
    }

    fn find_cap_object_id_with_solid_output_index(
        scene_graph: &SceneGraph,
        cap_kind: crate::frontend::api::CapKind,
        solid_output_index: usize,
    ) -> Option<ObjectId> {
        for object in &scene_graph.objects {
            if matches!(&object.kind, ObjectKind::Cap(cap) if cap.kind == cap_kind && cap.solid_output_index == Some(solid_output_index))
            {
                return Some(object.id);
            }
        }
        None
    }

    #[test]
    fn test_region_name_from_sweep_variable_supports_sweep_kinds() {
        let source = "\
region001 = region(point = [0.1, 0.1], sketch = s)
extrude001 = extrude(region001, length = 5)
revolve001 = revolve(region001, axis = Y)
sweep001 = sweep(region001, path = path001)
loft001 = loft(region001)
not_sweep001 = shell(extrude001, faces = [], thickness = 1)
";

        let program = Program::parse(source).unwrap().0.unwrap();

        assert_eq!(
            region_name_from_sweep_variable(&program.ast, "extrude001"),
            Some("region001".to_owned())
        );
        assert_eq!(
            region_name_from_sweep_variable(&program.ast, "revolve001"),
            Some("region001".to_owned())
        );
        assert_eq!(
            region_name_from_sweep_variable(&program.ast, "sweep001"),
            Some("region001".to_owned())
        );
        assert_eq!(
            region_name_from_sweep_variable(&program.ast, "loft001"),
            Some("region001".to_owned())
        );
        assert_eq!(region_name_from_sweep_variable(&program.ast, "not_sweep001"), None);
    }

    #[track_caller]
    fn expect_sketch(object: &Object) -> &Sketch {
        if let ObjectKind::Sketch(sketch) = &object.kind {
            sketch
        } else {
            panic!("Object is not a sketch: {:?}", object);
        }
    }

    fn point_position(scene_graph: &SceneGraph, point_id: ObjectId) -> Point2d<Number> {
        let point_object = scene_graph.objects.get(point_id.0).unwrap();
        let ObjectKind::Segment {
            segment: Segment::Point(point),
        } = &point_object.kind
        else {
            panic!("Object is not a point segment: {point_object:?}");
        };
        point.position.clone()
    }

    fn assert_point_position_close(actual: Point2d<Number>, expected: Point2d<Number>) {
        assert!((actual.x.value - expected.x.value).abs() < 1e-6);
        assert!((actual.y.value - expected.y.value).abs() < 1e-6);
    }

    /// Build a millimeter-valued point expression for concise sketch edit test
    /// setup.
    fn point_expr_mm(x: f64, y: f64) -> Point2d<Expr> {
        Point2d {
            x: Expr::Var(Number {
                value: x,
                units: NumericSuffix::Mm,
            }),
            y: Expr::Var(Number {
                value: y,
                units: NumericSuffix::Mm,
            }),
        }
    }

    /// Build a millimeter-valued numeric point for comparing solved scene graph
    /// positions.
    fn point_number_mm(x: f64, y: f64) -> Point2d<Number> {
        Point2d {
            x: Number {
                value: x,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: y,
                units: NumericSuffix::Mm,
            },
        }
    }

    fn make_line_ctor(start_x: f64, start_y: f64, end_x: f64, end_y: f64, units: NumericSuffix) -> LineCtor {
        LineCtor {
            start: Point2d {
                x: Expr::Number(Number { value: start_x, units }),
                y: Expr::Number(Number { value: start_y, units }),
            },
            end: Point2d {
                x: Expr::Number(Number { value: end_x, units }),
                y: Expr::Number(Number { value: end_y, units }),
            },
            construction: None,
        }
    }

    async fn create_sketch_with_single_line(
        frontend: &mut FrontendState,
        ctx: &ExecutorContext,
        mock_ctx: &ExecutorContext,
        version: Version,
    ) -> (ObjectId, ObjectId, SourceDelta, SceneGraphDelta) {
        frontend.program = Program::empty();

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, _scene_delta, sketch_id) = frontend
            .new_sketch(ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();

        let segment = SegmentCtor::Line(make_line_ctor(0.0, 0.0, 10.0, 10.0, NumericSuffix::Mm));
        let (source_delta, scene_graph_delta) = frontend
            .add_segment(mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        let line_id = *scene_graph_delta
            .new_objects
            .last()
            .expect("Expected line object id to be created");

        (sketch_id, line_id, source_delta, scene_graph_delta)
    }

    async fn seed_frontend_with_mock(frontend: &mut FrontendState, mock_ctx: &ExecutorContext, program: &Program) {
        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_checkpoint_round_trip_restores_state() {
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let (sketch_id, line_id, source_delta, scene_graph_delta) =
            create_sketch_with_single_line(&mut frontend, &ctx, &mock_ctx, version).await;

        let expected_source = source_delta.text.clone();
        let expected_scene_graph = frontend.scene_graph.clone();
        let expected_exec_outcome = scene_graph_delta.exec_outcome.clone();
        let expected_point_freedom_cache = frontend.point_freedom_cache.clone();

        let checkpoint_id = frontend
            .create_sketch_checkpoint(scene_graph_delta.exec_outcome.clone())
            .await
            .unwrap();

        let edited_segments = vec![ExistingSegmentCtor {
            id: line_id,
            ctor: SegmentCtor::Line(make_line_ctor(1.0, 2.0, 13.0, 14.0, NumericSuffix::Mm)),
        }];
        let (edited_source, _edited_scene) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, edited_segments)
            .await
            .unwrap();
        assert_ne!(edited_source.text, expected_source);

        let restored = frontend.restore_sketch_checkpoint(checkpoint_id).await.unwrap();

        assert_eq!(restored.source_delta.text, expected_source);
        assert_eq!(restored.scene_graph_delta.new_graph, expected_scene_graph);
        assert!(restored.scene_graph_delta.invalidates_ids);
        assert_eq!(restored.scene_graph_delta.exec_outcome, expected_exec_outcome);
        assert_eq!(frontend.scene_graph, expected_scene_graph);
        assert_eq!(frontend.point_freedom_cache, expected_point_freedom_cache);

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_checkpoints_prune_oldest_entries() {
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let (_sketch_id, _line_id, _source_delta, scene_graph_delta) =
            create_sketch_with_single_line(&mut frontend, &ctx, &mock_ctx, version).await;

        let mut checkpoint_ids = Vec::new();
        for _ in 0..(MAX_SKETCH_CHECKPOINTS + 3) {
            checkpoint_ids.push(
                frontend
                    .create_sketch_checkpoint(scene_graph_delta.exec_outcome.clone())
                    .await
                    .unwrap(),
            );
        }

        assert_eq!(frontend.sketch_checkpoints.len(), MAX_SKETCH_CHECKPOINTS);
        assert!(checkpoint_ids.windows(2).all(|ids| ids[0] < ids[1]));

        let oldest_retained = checkpoint_ids[3];
        assert_eq!(
            frontend.sketch_checkpoints.front().map(|checkpoint| checkpoint.id),
            Some(oldest_retained)
        );

        let evicted_restore = frontend.restore_sketch_checkpoint(checkpoint_ids[0]).await;
        assert!(evicted_restore.is_err());
        assert!(evicted_restore.unwrap_err().msg.contains("Sketch checkpoint not found"));

        frontend
            .restore_sketch_checkpoint(*checkpoint_ids.last().unwrap())
            .await
            .unwrap();

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_restore_sketch_checkpoint_missing_id_returns_error() {
        let mut frontend = FrontendState::new();
        let missing_checkpoint = SketchCheckpointId::new(999);

        let err = frontend
            .restore_sketch_checkpoint(missing_checkpoint)
            .await
            .expect_err("Expected restore to fail for missing checkpoint");

        assert!(err.msg.contains("Sketch checkpoint not found"));
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_clear_sketch_checkpoints_removes_all_restore_points() {
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let (_sketch_id, _line_id, _source_delta, scene_graph_delta) =
            create_sketch_with_single_line(&mut frontend, &ctx, &mock_ctx, version).await;

        let checkpoint_a = frontend
            .create_sketch_checkpoint(scene_graph_delta.exec_outcome.clone())
            .await
            .unwrap();
        let checkpoint_b = frontend
            .create_sketch_checkpoint(scene_graph_delta.exec_outcome.clone())
            .await
            .unwrap();
        assert_eq!(frontend.sketch_checkpoints.len(), 2);

        frontend.clear_sketch_checkpoints();
        assert!(frontend.sketch_checkpoints.is_empty());
        frontend.restore_sketch_checkpoint(checkpoint_a).await.unwrap_err();
        frontend.restore_sketch_checkpoint(checkpoint_b).await.unwrap_err();

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_hack_set_program_keeps_old_checkpoints_and_adds_fresh_baseline() {
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let (_sketch_id, _line_id, source_delta, scene_graph_delta) =
            create_sketch_with_single_line(&mut frontend, &ctx, &mock_ctx, version).await;
        let old_source = source_delta.text.clone();
        let old_checkpoint = frontend
            .create_sketch_checkpoint(scene_graph_delta.exec_outcome.clone())
            .await
            .unwrap();
        let initial_checkpoint_count = frontend.sketch_checkpoints.len();

        let new_program = Program::parse("sketch(on = XY) {\n  point(at = [1mm, 2mm])\n}\n")
            .unwrap()
            .0
            .unwrap();

        let result = frontend.hack_set_program(&ctx, new_program).await.unwrap();
        let SetProgramOutcome::Success {
            checkpoint_id: Some(new_checkpoint),
            ..
        } = result
        else {
            panic!("Expected Success with a fresh checkpoint baseline");
        };

        assert_eq!(frontend.sketch_checkpoints.len(), initial_checkpoint_count + 1);

        let old_restore = frontend.restore_sketch_checkpoint(old_checkpoint).await.unwrap();
        assert_eq!(old_restore.source_delta.text, old_source);

        let new_restore = frontend.restore_sketch_checkpoint(new_checkpoint).await.unwrap();
        assert!(new_restore.source_delta.text.contains("point(at = [1mm, 2mm])"));

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_hack_set_program_exec_failure_does_not_add_checkpoint() {
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let (_sketch_id, _line_id, _source_delta, scene_graph_delta) =
            create_sketch_with_single_line(&mut frontend, &ctx, &mock_ctx, version).await;
        let old_checkpoint = frontend
            .create_sketch_checkpoint(scene_graph_delta.exec_outcome.clone())
            .await
            .unwrap();
        let checkpoint_count_before = frontend.sketch_checkpoints.len();

        let failing_program = Program::parse(
            "sketch(on = XY) {\n  line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])\n}\n\nbad = missing_name\n",
        )
        .unwrap()
        .0
        .unwrap();

        let result = frontend.hack_set_program(&ctx, failing_program).await.unwrap();
        assert!(matches!(result, SetProgramOutcome::ExecFailure { .. }));
        assert_eq!(frontend.sketch_checkpoints.len(), checkpoint_count_before);
        frontend.restore_sketch_checkpoint(old_checkpoint).await.unwrap();

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_restore_sketch_checkpoint_restores_and_clears_mock_memory() {
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();

        let program = Program::parse(
            "width = 2mm\nsketch001 = sketch(on = offsetPlane(XY, offset = width)) {\n  line1 = line(start = [var 0, var 0], end = [var 1mm, var 0])\n  distance([line1.start, line1.end]) == width\n}\n",
        )
        .unwrap()
        .0
        .unwrap();
        let set_program_outcome = frontend.hack_set_program(&ctx, program).await.unwrap();
        let SetProgramOutcome::Success { exec_outcome, .. } = set_program_outcome else {
            panic!("Expected successful baseline program execution");
        };

        clear_mem_cache().await;
        assert!(read_old_memory().await.is_none());

        let checkpoint_without_mock_memory = frontend
            .create_sketch_checkpoint((*exec_outcome).clone())
            .await
            .unwrap();

        write_old_memory(SketchModeState::new_for_tests()).await;
        assert!(read_old_memory().await.is_some());

        let checkpoint_with_mock_memory = frontend
            .create_sketch_checkpoint((*exec_outcome).clone())
            .await
            .unwrap();

        clear_mem_cache().await;
        assert!(read_old_memory().await.is_none());

        frontend
            .restore_sketch_checkpoint(checkpoint_with_mock_memory)
            .await
            .unwrap();
        assert!(read_old_memory().await.is_some());

        frontend
            .restore_sketch_checkpoint(checkpoint_without_mock_memory)
            .await
            .unwrap();
        assert!(read_old_memory().await.is_none());

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_hack_set_program_exec_error_still_allows_edit_sketch() {
        let source = "\
sketch(on = XY) {
  line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
}

bad = missing_name
";
        let program = Program::parse(source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        let SetProgramOutcome::ExecFailure { .. } = frontend.hack_set_program(&ctx, program).await.unwrap() else {
            panic!("Expected ExecFailure from hack_set_program due to syntax error in program");
        };

        let sketch_id = frontend
            .scene_graph
            .objects
            .iter()
            .find_map(|obj| matches!(obj.kind, ObjectKind::Sketch(_)).then_some(obj.id))
            .expect("Expected sketch object from errored hack_set_program");

        frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch_id)
            .await
            .unwrap();

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_add_point_edit_point() {
        let program = Program::empty();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert_eq!(sketch_id, ObjectId(1));
        assert_eq!(scene_delta.new_objects, vec![ObjectId(1)]);
        let sketch_object = &scene_delta.new_graph.objects[1];
        assert_eq!(sketch_object.id, ObjectId(1));
        assert_eq!(
            sketch_object.kind,
            ObjectKind::Sketch(Sketch {
                args: SketchCtor {
                    on: Plane::Default(PlaneName::Xy)
                },
                plane: ObjectId(0),
                segments: vec![],
                constraints: vec![],
            })
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 2);

        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Number(Number {
                    value: 1.0,
                    units: NumericSuffix::Inch,
                }),
                y: Expr::Number(Number {
                    value: 2.0,
                    units: NumericSuffix::Inch,
                }),
            },
        };
        let segment = SegmentCtor::Point(point_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  point(at = [1in, 2in])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![ObjectId(2)]);
        assert_eq!(scene_delta.new_graph.objects.len(), 3);
        for (i, scene_object) in scene_delta.new_graph.objects.iter().enumerate() {
            assert_eq!(scene_object.id.0, i);
        }

        let point_id = *scene_delta.new_objects.last().unwrap();

        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Number(Number {
                    value: 3.0,
                    units: NumericSuffix::Inch,
                }),
                y: Expr::Number(Number {
                    value: 4.0,
                    units: NumericSuffix::Inch,
                }),
            },
        };
        let segments = vec![ExistingSegmentCtor {
            id: point_id,
            ctor: SegmentCtor::Point(point_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  point(at = [3in, 4in])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 3);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_add_line_edit_line() {
        let program = Program::empty();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert_eq!(sketch_id, ObjectId(1));
        assert_eq!(scene_delta.new_objects, vec![ObjectId(1)]);
        let sketch_object = &scene_delta.new_graph.objects[1];
        assert_eq!(sketch_object.id, ObjectId(1));
        assert_eq!(
            sketch_object.kind,
            ObjectKind::Sketch(Sketch {
                args: SketchCtor {
                    on: Plane::Default(PlaneName::Xy)
                },
                plane: ObjectId(0),
                segments: vec![],
                constraints: vec![],
            })
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 2);

        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Line(line_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  line(start = [0mm, 0mm], end = [10mm, 10mm])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![ObjectId(2), ObjectId(3), ObjectId(4)]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);
        for (i, scene_object) in scene_delta.new_graph.objects.iter().enumerate() {
            assert_eq!(scene_object.id.0, i);
        }

        // The new objects are the end points and then the line.
        let line = *scene_delta.new_objects.last().unwrap();

        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 1.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 2.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 13.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 14.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segments = vec![ExistingSegmentCtor {
            id: line,
            ctor: SegmentCtor::Line(line_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  line(start = [1mm, 2mm], end = [13mm, 14mm])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_add_arc_edit_arc() {
        let program = Program::empty();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert_eq!(sketch_id, ObjectId(1));
        assert_eq!(scene_delta.new_objects, vec![ObjectId(1)]);
        let sketch_object = &scene_delta.new_graph.objects[1];
        assert_eq!(sketch_object.id, ObjectId(1));
        assert_eq!(
            sketch_object.kind,
            ObjectKind::Sketch(Sketch {
                args: SketchCtor {
                    on: Plane::Default(PlaneName::Xy),
                },
                plane: ObjectId(0),
                segments: vec![],
                constraints: vec![],
            })
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 2);

        let arc_ctor = ArcCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Arc(arc_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  arc(start = [var 0mm, var 0mm], end = [var 10mm, var 10mm], center = [var 10mm, var 0mm])
}
"
        );
        assert_eq!(
            scene_delta.new_objects,
            vec![ObjectId(2), ObjectId(3), ObjectId(4), ObjectId(5)]
        );
        for (i, scene_object) in scene_delta.new_graph.objects.iter().enumerate() {
            assert_eq!(scene_object.id.0, i);
        }
        assert_eq!(scene_delta.new_graph.objects.len(), 6);

        // The new objects are the end points, the center, and then the arc.
        let arc = *scene_delta.new_objects.last().unwrap();

        let arc_ctor = ArcCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 1.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 2.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Var(Number {
                    value: 13.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 14.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 13.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 2.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segments = vec![ExistingSegmentCtor {
            id: arc,
            ctor: SegmentCtor::Arc(arc_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  arc(start = [var 1mm, var 2mm], end = [var 13mm, var 14mm], center = [var 13mm, var 2mm])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 6);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_add_circle_edit_circle() {
        let program = Program::empty();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, _scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();

        // Add a circle segment.
        let circle_ctor = CircleCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 5.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Circle(circle_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  circle1 = circle(start = [var 5mm, var 0mm], center = [var 0mm, var 0mm])
}
"
        );
        // The new objects are start, center, and then the circle segment.
        assert_eq!(scene_delta.new_objects, vec![ObjectId(2), ObjectId(3), ObjectId(4)]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        let circle = *scene_delta.new_objects.last().unwrap();

        // Edit the circle segment.
        let circle_ctor = CircleCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 3.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 4.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segments = vec![ExistingSegmentCtor {
            id: circle,
            ctor: SegmentCtor::Circle(circle_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  circle1 = circle(start = [var 10mm, var 0mm], center = [var 3mm, var 4mm])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_circle() {
        let initial_source = "sketch001 = sketch(on = XY) {
  circle(start = [var 5mm, var 0mm], center = [var 0mm, var 0mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        // The sketch should have 3 segments: start point, center point, and the circle.
        assert_eq!(sketch.segments.len(), 3);
        let circle_id = sketch.segments[2];

        // Delete the circle.
        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, vec![], vec![circle_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
}
"
        );
        let new_sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let new_sketch = expect_sketch(new_sketch_object);
        assert_eq!(new_sketch.segments.len(), 0);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_circle_via_point() {
        let initial_source = "sketch001 = sketch(on = XY) {
  circle(start = [var 5mm, var 0mm], center = [var 0mm, var 0mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        // Find the circle segment and its start point.
        let circle_id = sketch
            .segments
            .iter()
            .copied()
            .find(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::Circle(_)
                    }
                )
            })
            .expect("Expected a circle segment in sketch");
        let circle_object = &frontend.scene_graph.objects[circle_id.0];
        let ObjectKind::Segment {
            segment: Segment::Circle(circle),
        } = &circle_object.kind
        else {
            panic!("Expected circle segment, got: {:?}", circle_object.kind);
        };
        let start_point_id = circle.start;

        // Edit the start point via SegmentCtor::Point.
        let segments = vec![ExistingSegmentCtor {
            id: start_point_id,
            ctor: SegmentCtor::Point(PointCtor {
                position: Point2d {
                    x: Expr::Var(Number {
                        value: 7.0,
                        units: NumericSuffix::Mm,
                    }),
                    y: Expr::Var(Number {
                        value: 1.0,
                        units: NumericSuffix::Mm,
                    }),
                },
            }),
        }];
        let (src_delta, _scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  circle(start = [var 7mm, var 1mm], center = [var 0mm, var 0mm])
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_add_line_when_sketch_block_uses_variable() {
        let initial_source = "s = sketch(on = XY) {}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Line(line_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "s = sketch(on = XY) {
  line(start = [0mm, 0mm], end = [10mm, 10mm])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![ObjectId(2), ObjectId(3), ObjectId(4)]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_add_line_delete_sketch() {
        let program = Program::empty();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert_eq!(sketch_id, ObjectId(1));
        assert_eq!(scene_delta.new_objects, vec![ObjectId(1)]);
        let sketch_object = &scene_delta.new_graph.objects[1];
        assert_eq!(sketch_object.id, ObjectId(1));
        assert_eq!(
            sketch_object.kind,
            ObjectKind::Sketch(Sketch {
                args: SketchCtor {
                    on: Plane::Default(PlaneName::Xy)
                },
                plane: ObjectId(0),
                segments: vec![],
                constraints: vec![],
            })
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 2);

        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Line(line_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "sketch001 = sketch(on = XY) {
  line(start = [0mm, 0mm], end = [10mm, 10mm])
}
"
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        let (src_delta, scene_delta) = frontend.delete_sketch(&ctx, version, sketch_id).await.unwrap();
        assert_eq!(src_delta.text.as_str(), "");
        assert_eq!(scene_delta.new_graph.objects.len(), 0);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_sketch_when_sketch_block_uses_variable() {
        let initial_source = "s = sketch(on = XY) {}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_engine(sync::Arc::new(EngineManager::new_mock()), Default::default());
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let (src_delta, scene_delta) = frontend.delete_sketch(&ctx, version, sketch_id).await.unwrap();
        assert_eq!(src_delta.text.as_str(), "");
        assert_eq!(scene_delta.new_graph.objects.len(), 0);

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_sketch_after_comment() {
        let initial_source = "sketch001 = sketch(on = XZ) {
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_engine(sync::Arc::new(EngineManager::new_mock()), Default::default());
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let original_source = sketch_object.source.clone();

        let commented_source = "// test 1
sketch001 = sketch(on = XZ) {
}
";
        let commented_program = Program::parse(commented_source).unwrap().0.unwrap();
        frontend.engine_execute(&ctx, commented_program).await.unwrap();

        let cached_sketch_object = &frontend.scene_graph.objects[sketch_id.0];
        assert_eq!(cached_sketch_object.source, original_source);

        let (src_delta, scene_delta) = frontend.delete_sketch(&ctx, version, sketch_id).await.unwrap();
        assert!(
            !src_delta.text.contains("sketch001"),
            "sketch was not deleted: {}",
            src_delta.text
        );
        // The leading line comment must survive deletion.
        assert_eq!(src_delta.text.as_str(), "// test 1\n");
        assert_eq!(scene_delta.new_graph.objects.len(), 0);

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_sketch_preserves_pre_comment_when_followed_by_code() {
        let initial_source = "sketch001 = sketch(on = XZ) {
}
foo = 1
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_engine(sync::Arc::new(EngineManager::new_mock()), Default::default());
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let commented_source = "// keep me
sketch001 = sketch(on = XZ) {
}
foo = 1
";
        let commented_program = Program::parse(commented_source).unwrap().0.unwrap();
        frontend.engine_execute(&ctx, commented_program).await.unwrap();

        let (src_delta, _scene_delta) = frontend.delete_sketch(&ctx, version, sketch_id).await.unwrap();
        // The leading comment should remain, now attached to the following body item.
        assert_eq!(src_delta.text.as_str(), "// keep me\nfoo = 1\n");

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_segment_preserves_pre_comment() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  // describe the middle point
  point(at = [var 3, var 4])
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let middle_point_id = *sketch.segments.get(1).unwrap();

        let (src_delta, _scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![middle_point_id])
            .await
            .unwrap();
        // The line comment on the line above the deleted point must be preserved.
        // It is reattached to the next surviving body item.
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  // describe the middle point
  point(at = [var 5, var 6])
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_last_segment_preserves_pre_comment() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  // describe the trailing point
  point(at = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let last_point_id = *sketch.segments.last().unwrap();

        let (src_delta, _scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![last_point_id])
            .await
            .unwrap();
        // No following item to attach to; the comment is kept inside the sketch
        // block as trailing non-code metadata so the user does not lose it.
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  // describe the trailing point
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_segment_drops_inline_trailing_comment() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4]) // same-line note that gets dropped
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let middle_point_id = *sketch.segments.get(1).unwrap();

        let (src_delta, _scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![middle_point_id])
            .await
            .unwrap();
        // The same-line trailing comment is removed along with the deleted code.
        assert!(
            !src_delta.text.contains("same-line note"),
            "inline comment should have been removed: {}",
            src_delta.text
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_segments_preserves_block_comments_across_positions() {
        // One test exercising several `delete_body_item_preserving_pre_comments`
        // branches at once with `/* ... */` block comments:
        //   - first point: leading block comment must migrate to the next item.
        //   - first point: same-line trailing block comment must be dropped.
        //   - middle point: leading block comment must stay attached after migration.
        //   - last point: leading block comment, with no surviving next item,
        //     must be converted into a trailing NonCodeNode.
        let initial_source = "\
sketch(on = XY) {
  /* above first - moves to middle */
  point(at = [var 1, var 2]) /* same-line on first - dropped */
  /* above middle - stays */
  point(at = [var 3, var 4])
  /* above last - moves to trailing meta */
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let first_point_id = *sketch.segments.first().unwrap();
        let last_point_id = *sketch.segments.last().unwrap();

        let (src_delta, _scene_delta) = frontend
            .delete_objects(
                &mock_ctx,
                version,
                sketch_id,
                Vec::new(),
                vec![first_point_id, last_point_id],
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  /* above first - moves to middle */
  /* above middle - stays */
  point(at = [var 3, var 4])
  /* above last - moves to trailing meta */
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_line_when_editing_its_start_point() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let point_id = *sketch.segments.first().unwrap();

        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Var(Number {
                    value: 5.0,
                    units: NumericSuffix::Inch,
                }),
                y: Expr::Var(Number {
                    value: 6.0,
                    units: NumericSuffix::Inch,
                }),
            },
        };
        let segments = vec![ExistingSegmentCtor {
            id: point_id,
            ctor: SegmentCtor::Point(point_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line(start = [var 5in, var 6in], end = [var 3, var 4])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_line_when_editing_its_end_point() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.get(1).unwrap();

        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Var(Number {
                    value: 5.0,
                    units: NumericSuffix::Inch,
                }),
                y: Expr::Var(Number {
                    value: 6.0,
                    units: NumericSuffix::Inch,
                }),
            },
        };
        let segments = vec![ExistingSegmentCtor {
            id: point_id,
            ctor: SegmentCtor::Point(point_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 5in, var 6in])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_line_with_coincident_feedback() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 1, var 2])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  fixed([line1.start, [0, 0]])
  coincident([line1.end, line2.start])
  equalLength([line1, line2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line2_end_id = *sketch.segments.get(4).unwrap();

        let segments = vec![ExistingSegmentCtor {
            id: line2_end_id,
            ctor: SegmentCtor::Point(PointCtor {
                position: Point2d {
                    x: Expr::Var(Number {
                        value: 9.0,
                        units: NumericSuffix::None,
                    }),
                    y: Expr::Var(Number {
                        value: 10.0,
                        units: NumericSuffix::None,
                    }),
                },
            }),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 4.14, var 5.32])
  line2 = line(start = [var 4.14, var 5.32], end = [var 9, var 10])
  fixed([line1.start, [0, 0]])
  coincident([line1.end, line2.start])
  equalLength([line1, line2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            11,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_segments_persists_solver_feedback_for_next_mock_execute() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 1, var 2])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  fixed([line1.start, [0, 0]])
  coincident([line1.end, line2.start])
  equalLength([line1, line2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line2_end_id = *sketch.segments.get(4).unwrap();

        let segments = vec![ExistingSegmentCtor {
            id: line2_end_id,
            ctor: SegmentCtor::Point(PointCtor {
                position: Point2d {
                    x: Expr::Var(Number {
                        value: 9.0,
                        units: NumericSuffix::None,
                    }),
                    y: Expr::Var(Number {
                        value: 10.0,
                        units: NumericSuffix::None,
                    }),
                },
            }),
        }];
        let (edited_source, _) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();

        let (mock_source, _) = frontend.execute_mock(&mock_ctx, version, sketch_id).await.unwrap();
        assert_eq!(mock_source.text, edited_source.text);

        mock_ctx.close().await;
    }

    /// Preview segment edits should return solved geometry without persisting
    /// solver feedback to KCL.
    #[tokio::test(flavor = "multi_thread")]
    async fn test_preview_edit_segments_does_not_persist_solver_feedback() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 1, var 2])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  fixed([line1.start, [0, 0]])
  coincident([line1.end, line2.start])
  equalLength([line1, line2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line2_end_id = *sketch.segments.get(4).unwrap();

        let segments = vec![ExistingSegmentCtor {
            id: line2_end_id,
            ctor: SegmentCtor::Point(PointCtor {
                position: Point2d {
                    x: Expr::Var(Number {
                        value: 9.0,
                        units: NumericSuffix::None,
                    }),
                    y: Expr::Var(Number {
                        value: 10.0,
                        units: NumericSuffix::None,
                    }),
                },
            }),
        }];
        let (preview_source, preview_delta) = frontend
            .edit_segments_with_options(
                &mock_ctx,
                version,
                sketch_id,
                segments,
                EditSegmentsOptions {
                    anchor_segment_ids: Some(vec![line2_end_id]),
                    drag_anchors: Vec::new(),
                    commit_solved_initial_guesses: false,
                },
            )
            .await
            .unwrap();

        assert!(
            !preview_delta.exec_outcome.var_solutions.is_empty(),
            "preview solve should still solve and return geometry feedback"
        );
        assert!(
            preview_source
                .text
                .contains("line1 = line(start = [var 1, var 2], end = [var 1, var 2])")
        );
        assert!(
            preview_source
                .text
                .contains("line2 = line(start = [var 5, var 6], end = [var 9, var 10])")
        );

        let (mock_source, _) = frontend.execute_mock(&mock_ctx, version, sketch_id).await.unwrap();
        assert_eq!(mock_source.text, preview_source.text);

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_add_constraint_persists_solver_feedback_for_next_mock_execute() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 10, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_end_id = *sketch.segments.get(1).unwrap();

        let constraint = Constraint::Fixed(Fixed {
            points: vec![FixedPoint {
                point: line_end_id,
                position: Point2d {
                    x: Number {
                        value: 20.0,
                        units: NumericSuffix::Mm,
                    },
                    y: Number {
                        value: 0.0,
                        units: NumericSuffix::Mm,
                    },
                },
            }],
        });
        let (constraint_source, _) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();

        assert!(
            constraint_source
                .text
                .contains("line1 = line(start = [var 0, var 0], end = [var 20, var 0])"),
            "{}",
            constraint_source.text
        );
        let (mock_source, _) = frontend.execute_mock(&mock_ctx, version, sketch_id).await.unwrap();
        assert_eq!(mock_source.text, constraint_source.text);

        mock_ctx.close().await;
    }

    #[test]
    fn test_no_solver_feedback_preserves_original_source() {
        let initial_source = "\
@settings(defaultLengthUnit = in, kclVersion = 2.0)
cylinder = startSketchOn(XY)
    |> circle(center= [0, 0], radius= 22)
    |> extrude(length = 14)
";
        let mut frontend = FrontendState::new();
        frontend.program = Program::parse(initial_source).unwrap().0.unwrap();
        let outcome = ExecOutcome {
            variables: Default::default(),
            operations: Default::default(),
            artifact_graph: Default::default(),
            scene_objects: Default::default(),
            source_range_to_object: Default::default(),
            var_solutions: Default::default(),
            issues: Default::default(),
            filenames: Default::default(),
            default_planes: Default::default(),
        };

        let source_delta = frontend.commit_var_solutions_to_program(&outcome, "testing").unwrap();

        assert_eq!(source_delta.text, initial_source);
    }

    /// Explicit drag anchors should limit which edited points become temporary
    /// fixed constraints.
    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_segments_with_anchor_ids_limits_drag_fixed_constraints() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 0mm, var 0mm])
  point2 = point(at = [var 0mm, var 0mm])
  coincident([point1, point2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point1_id = sketch.segments[0];
        let point2_id = sketch.segments[1];

        let segments = vec![
            ExistingSegmentCtor {
                id: point1_id,
                ctor: SegmentCtor::Point(PointCtor {
                    position: point_expr_mm(10.0, 0.0),
                }),
            },
            ExistingSegmentCtor {
                id: point2_id,
                ctor: SegmentCtor::Point(PointCtor {
                    position: point_expr_mm(100.0, 0.0),
                }),
            },
        ];
        let (_, scene_delta) = frontend
            .edit_segments_with_options(
                &mock_ctx,
                version,
                sketch_id,
                segments,
                EditSegmentsOptions {
                    anchor_segment_ids: Some(vec![point1_id]),
                    drag_anchors: Vec::new(),
                    commit_solved_initial_guesses: true,
                },
            )
            .await
            .unwrap();

        assert_point_position_close(
            point_position(&scene_delta.new_graph, point1_id),
            point_number_mm(10.0, 0.0),
        );
        assert_point_position_close(
            point_position(&scene_delta.new_graph, point2_id),
            point_number_mm(10.0, 0.0),
        );

        mock_ctx.close().await;
    }

    /// Walks a program collecting `(literal_source_range, sketch_var_node_path)`
    /// for every SketchVar whose initial NumericLiteral has the given value.
    fn collect_sketch_var_literals_with_value(program: &Program, value: f64) -> Vec<(SourceRange, ast::NodePath)> {
        use std::cell::RefCell;
        struct Collector {
            target: f64,
            out: RefCell<Vec<(SourceRange, ast::NodePath)>>,
        }
        impl<'a> crate::walk::Visitor<'a> for &Collector {
            type Error = crate::front::Error;
            fn visit_node(&self, node: crate::walk::Node<'a>) -> anyhow::Result<bool, Self::Error> {
                if let crate::walk::Node::SketchVar(sketch_var) = node
                    && let (Some(initial), Some(node_path)) = (&sketch_var.initial, &sketch_var.node_path)
                    && (initial.value - self.target).abs() < 1e-9
                {
                    self.out
                        .borrow_mut()
                        .push((SourceRange::from(initial.as_ref()), node_path.clone()));
                }
                for child in node.children().iter() {
                    if !child.visit(*self)? {
                        return Ok(false);
                    }
                }
                Ok(true)
            }
        }
        let collector = Collector {
            target: value,
            out: Default::default(),
        };
        let _ = crate::walk::Node::from(&program.ast).visit(&collector);
        collector.out.into_inner()
    }

    /// Walk a program collecting `(sketch_var_source_range, sketch_var_node_path)`
    /// for every SketchVar (including bare `var`).
    fn collect_all_sketch_vars(program: &Program) -> Vec<(SourceRange, ast::NodePath)> {
        use std::cell::RefCell;
        struct Collector {
            out: RefCell<Vec<(SourceRange, ast::NodePath)>>,
        }
        impl<'a> crate::walk::Visitor<'a> for &Collector {
            type Error = crate::front::Error;
            fn visit_node(&self, node: crate::walk::Node<'a>) -> anyhow::Result<bool, Self::Error> {
                if let crate::walk::Node::SketchVar(sketch_var) = node
                    && let Some(node_path) = &sketch_var.node_path
                {
                    self.out
                        .borrow_mut()
                        .push((SourceRange::from(sketch_var), node_path.clone()));
                }
                for child in node.children().iter() {
                    if !child.visit(*self)? {
                        return Ok(false);
                    }
                }
                Ok(true)
            }
        }
        let collector = Collector {
            out: Default::default(),
        };
        let _ = crate::walk::Node::from(&program.ast).visit(&collector);
        collector.out.into_inner()
    }

    fn empty_exec_outcome_with_var_solutions(
        var_solutions: Vec<(SourceRange, Option<ast::NodePath>, Number)>,
    ) -> ExecOutcome {
        ExecOutcome {
            variables: Default::default(),
            operations: Default::default(),
            artifact_graph: Default::default(),
            scene_objects: Default::default(),
            source_range_to_object: Default::default(),
            var_solutions,
            issues: Default::default(),
            filenames: Default::default(),
            default_planes: Default::default(),
        }
    }

    /// Happy path: commit a var solution to a `var N` inside a sketch block
    /// using a correct NodePath. Confirms the node-path code path produces the
    /// expected source mutation.
    #[test]
    fn test_commit_var_solution_by_node_path_updates_sketch_var() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 10mm, var 0])
}
";
        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let matches = collect_sketch_var_literals_with_value(&program, 10.0);
        assert_eq!(matches.len(), 1, "expected exactly one `var 10mm`");
        let (literal_range, node_path) = matches.into_iter().next().unwrap();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let outcome = empty_exec_outcome_with_var_solutions(vec![(
            literal_range,
            Some(node_path),
            Number {
                value: 25.0,
                units: NumericSuffix::Mm,
            },
        )]);

        let source_delta = frontend.commit_var_solutions_to_program(&outcome, "testing").unwrap();

        assert_eq!(
            source_delta.text,
            "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 25mm, var 0])
}
",
        );
    }

    /// Whitespace inserted earlier in the source shifts the original SketchVar
    /// SourceRange. With NodePath propagation the commit should still target
    /// the right `var`. We simulate this by collecting node_paths against a
    /// "compact" source, then loading the frontend with a "padded" source
    /// (whose byte offsets differ), and feeding the original (now stale)
    /// source range plus the correct node_path back into the commit.
    #[test]
    fn test_commit_var_solution_survives_whitespace_shift_earlier_in_file() {
        let compact_source = "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 10mm, var 0])
}
";
        let padded_source = "\
// added comment\n// added comment\n\nsketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 10mm, var 0])
}
";
        let compact_program = Program::parse(compact_source).unwrap().0.unwrap();
        let padded_program = Program::parse(padded_source).unwrap().0.unwrap();

        let compact_match = collect_sketch_var_literals_with_value(&compact_program, 10.0)
            .into_iter()
            .next()
            .expect("expected `var 10mm` in compact source");
        let padded_match = collect_sketch_var_literals_with_value(&padded_program, 10.0)
            .into_iter()
            .next()
            .expect("expected `var 10mm` in padded source");

        assert_ne!(
            compact_match.0, padded_match.0,
            "byte offsets must differ for this test to be meaningful"
        );
        assert_eq!(
            compact_match.1, padded_match.1,
            "node paths must agree across whitespace; that's the whole point of NodePath",
        );

        let mut frontend = FrontendState::new();
        frontend.program = padded_program;

        // Stale source range from the compact source + correct node_path.
        let outcome = empty_exec_outcome_with_var_solutions(vec![(
            compact_match.0,
            Some(compact_match.1),
            Number {
                value: 30.0,
                units: NumericSuffix::Mm,
            },
        )]);

        let source_delta = frontend.commit_var_solutions_to_program(&outcome, "testing").unwrap();

        assert_eq!(
            source_delta.text,
            "\
// added comment
// added comment

sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 30mm, var 0])
}
",
        );
    }

    /// When multiple `var` declarations exist and the stale source range
    /// happens to land on a *different* var, the node_path must take
    /// precedence and the right var gets updated.
    #[test]
    fn test_commit_var_solution_node_path_wins_when_source_range_points_at_wrong_var() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 10mm, var 0mm], end = [var 20mm, var 0mm])
}
";
        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let var_10 = collect_sketch_var_literals_with_value(&program, 10.0)
            .into_iter()
            .next()
            .expect("expected `var 10mm`");
        let var_20 = collect_sketch_var_literals_with_value(&program, 20.0)
            .into_iter()
            .next()
            .expect("expected `var 20mm`");

        let mut frontend = FrontendState::new();
        frontend.program = program;

        // Use var 20mm's source range, but var 10mm's node_path. node_path wins.
        let outcome = empty_exec_outcome_with_var_solutions(vec![(
            var_20.0,
            Some(var_10.1),
            Number {
                value: 33.0,
                units: NumericSuffix::Mm,
            },
        )]);

        let source_delta = frontend.commit_var_solutions_to_program(&outcome, "testing").unwrap();

        assert_eq!(
            source_delta.text,
            "\
sketch(on = XY) {
  line1 = line(start = [var 33mm, var 0mm], end = [var 20mm, var 0mm])
}
",
        );
    }

    /// Bare `var` (no initial literal) is only locatable via node_path. With
    /// the EditVarInitialValue handler now operating on the SketchVar node, a
    /// solver solution should fill the initial value in. The
    /// `@settings(experimentalFeatures = allow)` is required because bare `var`
    /// is gated as an experimental feature; without it the re-parse of the
    /// recast source rejects bare `var` declarations.
    #[test]
    fn test_commit_var_solution_writes_back_into_bare_var() {
        let initial_source = "\
@settings(experimentalFeatures = allow, kclVersion = 2.0)
sketch(on = XY) {
  line1 = line(start = [var, var 0mm], end = [var 10mm, var 0])
}
";
        let program = Program::parse(initial_source).unwrap().0.unwrap();

        // Pick the first bare `var`; collect_all_sketch_vars returns every
        // SketchVar, including bare ones.
        let bare = collect_all_sketch_vars(&program)
            .into_iter()
            .find(|(range, _)| {
                // The bare `var` is exactly the 3 characters "var".
                range.end() - range.start() == 3
            })
            .expect("expected at least one bare `var`");

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let outcome = empty_exec_outcome_with_var_solutions(vec![(
            bare.0,
            Some(bare.1),
            Number {
                value: 7.0,
                units: NumericSuffix::Mm,
            },
        )]);

        let source_delta = frontend.commit_var_solutions_to_program(&outcome, "testing").unwrap();

        // Default length unit (mm; no `@settings(defaultLengthUnit = …)`) is
        // written as an explicit suffix so the bare var commits with units.
        // The recast adds a blank line after the `@settings` annotation.
        assert_eq!(
            source_delta.text,
            "\
@settings(experimentalFeatures = allow, kclVersion = 2.0)

sketch(on = XY) {
  line1 = line(start = [var 7mm, var 0mm], end = [var 10mm, var 0])
}
",
        );
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_point_without_var() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4])
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let point_id = *sketch.segments.get(1).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![point_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 5, var 6])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 4);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_point_with_var() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point1 = point(at = [var 3, var 4])
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let point_id = *sketch.segments.get(1).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![point_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 5, var 6])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 4);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_multiple_points() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point1 = point(at = [var 3, var 4])
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let sketch = expect_sketch(sketch_object);

        let point1_id = *sketch.segments.first().unwrap();
        let point2_id = *sketch.segments.get(1).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![point1_id, point2_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point(at = [var 5, var 6])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 3);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_coincident_constraint() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  coincident([point1, point2])
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let coincident_id = *sketch.constraints.first().unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, vec![coincident_id], Vec::new())
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  point(at = [var 5, var 6])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 5);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_cascades_to_coincident_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  coincident([line1.end, line2.start])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_id = *sketch.segments.get(5).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_cascades_to_distance_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  distance([line1.end, line2.start]) == 10mm
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_id = *sketch.segments.get(5).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_point_cascades_to_horizontal_distance_constraint() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  horizontalDistance([point1, point2]) == 10mm
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point2_id = *sketch.segments.get(1).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![point2_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            3,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_cascades_to_fixed_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  fixed([line1.start, [0, 0]])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line1_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_cascades_to_midpoint_constraint() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  line1 = line(start = [var 0, var 0], end = [var 6, var 4])
  midpoint(line1, point = point1)
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(3).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line1_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            3,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_point_preserves_multiline_coincident_constraint() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  point3 = point(at = [var 5, var 6])
  coincident([point1, point2, point3])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point3_id = *sketch.segments.get(2).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![point3_id])
            .await
            .unwrap();
        assert!(src_delta.text.contains("point1 = point("), "{}", src_delta.text);
        assert!(src_delta.text.contains("point2 = point("), "{}", src_delta.text);
        assert!(!src_delta.text.contains("point3 = point("), "{}", src_delta.text);
        assert!(
            src_delta.text.contains("coincident([point1, point2])"),
            "{}",
            src_delta.text
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.segments.len(), 2);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Coincident(coincident) = constraint else {
            panic!("Expected coincident constraint");
        };
        assert_eq!(
            coincident.segments,
            sketch
                .segments
                .iter()
                .copied()
                .map(Into::into)
                .collect::<Vec<ConstraintSegment>>()
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_preserves_multiline_equal_length_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  equalLength([line1, line2, line3])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line3_id = *sketch.segments.get(8).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line3_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  equalLength([line1, line2])
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::LinesEqualLength(lines_equal_length) = constraint else {
            panic!("Expected lines equal length constraint");
        };
        assert_eq!(lines_equal_length.lines.len(), 2);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_preserves_multiline_horizontal_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  horizontal([line1.end, line2.start, line3.start])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line1_id])
            .await
            .unwrap();
        assert!(!src_delta.text.contains("line1 = line("), "{}", src_delta.text);
        assert!(src_delta.text.contains("line2 = line("), "{}", src_delta.text);
        assert!(src_delta.text.contains("line3 = line("), "{}", src_delta.text);
        assert!(
            src_delta.text.contains("horizontal([line2.start, line3.start])"),
            "{}",
            src_delta.text
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Horizontal(Horizontal::Points { points }) = constraint else {
            panic!("Expected horizontal points constraint");
        };
        let remaining_points = vec![sketch.segments[0].into(), sketch.segments[3].into()];
        assert_eq!(*points, remaining_points);

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_preserves_multiline_vertical_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  vertical([line1.end, line2.start, line3.start])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line1_id])
            .await
            .unwrap();
        assert!(!src_delta.text.contains("line1 = line("), "{}", src_delta.text);
        assert!(src_delta.text.contains("line2 = line("), "{}", src_delta.text);
        assert!(src_delta.text.contains("line3 = line("), "{}", src_delta.text);
        assert!(
            src_delta.text.contains("vertical([line2.start, line3.start])"),
            "{}",
            src_delta.text
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Vertical(Vertical::Points { points }) = constraint else {
            panic!("Expected vertical points constraint");
        };
        let remaining_points = vec![sketch.segments[0].into(), sketch.segments[3].into()];
        assert_eq!(*points, remaining_points);

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_preserves_multiline_coincident_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  coincident([line1.end, line2.start, line3.start])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line1_id])
            .await
            .unwrap();
        assert!(!src_delta.text.contains("line1 = line("), "{}", src_delta.text);
        assert!(src_delta.text.contains("line2 = line("), "{}", src_delta.text);
        assert!(src_delta.text.contains("line3 = line("), "{}", src_delta.text);
        assert!(
            src_delta.text.contains("coincident([line2.start, line3.start])"),
            "{}",
            src_delta.text
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Coincident(coincident) = constraint else {
            panic!("Expected coincident constraint");
        };
        let remaining_segments = vec![sketch.segments[0].into(), sketch.segments[3].into()];
        assert_eq!(coincident.segments, remaining_segments);

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_lines_removes_multiline_equal_length_constraint_below_minimum() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  equalLength([line1, line2, line3])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line2_id = *sketch.segments.get(5).unwrap();
        let line3_id = *sketch.segments.get(8).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line2_id, line3_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert!(sketch.constraints.is_empty());

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_preserves_multiline_parallel_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  parallel([line1, line2, line3])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line3_id = *sketch.segments.get(8).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line3_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  parallel([line1, line2])
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Parallel(parallel) = constraint else {
            panic!("Expected parallel constraint");
        };
        assert_eq!(parallel.lines.len(), 2);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_lines_removes_multiline_parallel_constraint_below_minimum() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  parallel([line1, line2, line3])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line2_id = *sketch.segments.get(5).unwrap();
        let line3_id = *sketch.segments.get(8).unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, Vec::new(), vec![line2_id, line3_id])
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert!(sketch.constraints.is_empty());

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_delete_line_line_coincident_constraint() {
        let initial_source = "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  coincident([line1, line2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let coincident_id = *sketch.constraints.first().unwrap();

        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, vec![coincident_id], Vec::new())
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
}
"
        );
        assert_eq!(scene_delta.new_objects, vec![]);
        assert_eq!(scene_delta.new_graph.objects.len(), 8);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_two_points_coincident() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point(at = [3, 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();

        let constraint = Constraint::Coincident(Coincident {
            segments: vec![point0_id.into(), point1_id.into()],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 3, var 4])
  point2 = point(at = [3, 4])
  coincident([point1, point2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_three_points_coincident() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point(at = [var 3, var 4])
  point(at = [var 5, var 6])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let segments = sketch
            .segments
            .iter()
            .take(3)
            .copied()
            .map(Into::into)
            .collect::<Vec<ConstraintSegment>>();

        let constraint = Constraint::Coincident(Coincident {
            segments: segments.clone(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 3, var 4])
  point2 = point(at = [var 3, var 4])
  point3 = point(at = [var 3, var 4])
  coincident([point1, point2, point3])
}
"
        );

        let constraint_object = scene_delta
            .new_graph
            .objects
            .iter()
            .find(|obj| matches!(obj.kind, ObjectKind::Constraint { .. }))
            .unwrap();

        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("expected a constraint object");
        };

        assert_eq!(constraint, &Constraint::Coincident(Coincident { segments }));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_source_with_three_point_coincident_tracks_all_segments() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  point3 = point(at = [var 5, var 6])
  coincident([point1, point2, point3])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);

        let constraint_object = frontend
            .scene_graph
            .objects
            .iter()
            .find(|obj| matches!(obj.kind, ObjectKind::Constraint { .. }))
            .unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("expected a constraint object");
        };

        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let expected_segments = sketch
            .segments
            .iter()
            .take(3)
            .copied()
            .map(Into::into)
            .collect::<Vec<ConstraintSegment>>();

        assert_eq!(
            constraint,
            &Constraint::Coincident(Coincident {
                segments: expected_segments,
            })
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_point_origin_coincident_preserves_order() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
}
";

        for (origin_first, expected_source) in [
            (
                true,
                "\
sketch(on = XY) {
  point1 = point(at = [var 0, var 0])
  coincident([ORIGIN, point1])
}
",
            ),
            (
                false,
                "\
sketch(on = XY) {
  point1 = point(at = [var 0, var 0])
  coincident([point1, ORIGIN])
}
",
            ),
        ] {
            let program = Program::parse(initial_source).unwrap().0.unwrap();

            let mut frontend = FrontendState::new();

            let ctx = ExecutorContext::new_with_default_client().await.unwrap();
            let mock_ctx = ExecutorContext::new_mock(None).await;
            let version = Version(0);

            frontend.hack_set_program(&ctx, program).await.unwrap();
            let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
            let sketch_id = sketch_object.id;
            let sketch = expect_sketch(sketch_object);
            let point_id = *sketch.segments.first().unwrap();

            let segments = if origin_first {
                vec![ConstraintSegment::ORIGIN, point_id.into()]
            } else {
                vec![point_id.into(), ConstraintSegment::ORIGIN]
            };
            let constraint = Constraint::Coincident(Coincident {
                segments: segments.clone(),
            });
            let (src_delta, scene_delta) = frontend
                .add_constraint(&mock_ctx, version, sketch_id, constraint)
                .await
                .unwrap();
            assert_eq!(src_delta.text.as_str(), expected_source);

            let constraint_object = scene_delta
                .new_graph
                .objects
                .iter()
                .find(|obj| matches!(obj.kind, ObjectKind::Constraint { .. }))
                .unwrap();

            let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
                panic!("expected a constraint object");
            };

            assert_eq!(constraint, &Constraint::Coincident(Coincident { segments }));

            ctx.close().await;
            mock_ctx.close().await;
        }
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_coincident_of_line_end_points() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.get(1).unwrap();
        let point1_id = *sketch.segments.get(3).unwrap();

        let constraint = Constraint::Coincident(Coincident {
            segments: vec![point0_id.into(), point1_id.into()],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 4, var 5])
  line2 = line(start = [var 4, var 5], end = [var 7, var 8])
  coincident([line1.end, line2.start])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            9,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_coincident_of_line_point_and_circle_segment() {
        let initial_source = "\
sketch(on = XY) {
  circle1 = circle(start = [var 5mm, var 0mm], center = [var 0mm, var 0mm])
  line1 = line(start = [var 9mm, var 1mm], end = [var 10mm, var 2mm])
}
";
        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.program = program;
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).expect("Expected sketch object");
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        let circle_id = sketch
            .segments
            .iter()
            .copied()
            .find(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::Circle(_)
                    }
                )
            })
            .expect("Expected a circle segment in sketch");
        let line_id = frontend
            .scene_graph
            .objects
            .iter()
            .find_map(|obj| match &obj.kind {
                ObjectKind::Segment {
                    segment: Segment::Line(line),
                } if line.owner.is_none() => Some(obj.id),
                _ => None,
            })
            .expect("Expected a standalone line segment in scene graph");

        let line_start_point_id = match &frontend.scene_graph.objects[line_id.0].kind {
            ObjectKind::Segment {
                segment: Segment::Line(line),
            } => line.start,
            _ => panic!("Expected line segment object"),
        };

        let constraint = Constraint::Coincident(Coincident {
            segments: vec![line_start_point_id.into(), circle_id.into()],
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  circle1 = circle(start = [var 7.02mm, var 0mm], center = [var -0.01mm, var 0.22mm])
  line1 = line(start = [var 7mm, var 0.78mm], end = [var 10mm, var 2mm])
  coincident([line1.start, circle1])
}
"
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_invalid_coincident_arc_and_line_preserves_state() {
        // Test that attempting an invalid coincident constraint (arc and line)
        // doesn't corrupt the state, allowing subsequent operations to work.
        // This test verifies the transactional fix in add_constraint that prevents
        // state corruption when invalid constraints are attempted.
        // Example: coincident constraint between an arc segment and a straight line segment
        // is geometrically invalid and should fail, but state should remain intact.
        // Use the programmatic approach (new_sketch + add_segment) like test_new_sketch_add_arc_edit_arc
        let program = Program::empty();

        let mut frontend = FrontendState::new();
        frontend.program = program;

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (_src_delta, _scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();

        // Add an arc segment
        let arc_ctor = ArcCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let (_src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, SegmentCtor::Arc(arc_ctor), None)
            .await
            .unwrap();
        // The arc is the last object in new_objects (after the 3 points: start, end, center)
        let arc_id = *scene_delta.new_objects.last().unwrap();

        // Add a line segment
        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 20.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Var(Number {
                    value: 30.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let (_src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, SegmentCtor::Line(line_ctor), None)
            .await
            .unwrap();
        // The line is the last object in new_objects (after the 2 points: start, end)
        let line_id = *scene_delta.new_objects.last().unwrap();

        // Attempt to add an invalid coincident constraint between arc and line
        // This should fail during execution, but state should remain intact
        let constraint = Constraint::Coincident(Coincident {
            segments: vec![arc_id.into(), line_id.into()],
        });
        let result = frontend.add_constraint(&mock_ctx, version, sketch_id, constraint).await;

        // The constraint addition should fail (invalid constraint)
        assert!(result.is_err(), "Expected invalid coincident constraint to fail");

        // Verify state is not corrupted by checking that we can still access the scene graph
        // and that the original segments are still present with their source ranges
        let sketch_object_after =
            find_first_sketch_object(&frontend.scene_graph).expect("Sketch should still exist after failed constraint");
        let sketch_after = expect_sketch(sketch_object_after);

        // Verify both segments are still in the sketch
        assert!(
            sketch_after.segments.contains(&arc_id),
            "Arc segment should still exist after failed constraint"
        );
        assert!(
            sketch_after.segments.contains(&line_id),
            "Line segment should still exist after failed constraint"
        );

        // Verify we can still access segment objects (this would fail if source ranges were corrupted)
        let arc_obj = frontend
            .scene_graph
            .objects
            .get(arc_id.0)
            .expect("Arc object should still be accessible");
        let line_obj = frontend
            .scene_graph
            .objects
            .get(line_id.0)
            .expect("Line object should still be accessible");

        // Verify source ranges are still valid (not corrupted)
        // Just verify that the objects are still accessible and have the expected types
        match &arc_obj.kind {
            ObjectKind::Segment {
                segment: Segment::Arc(_),
            } => {}
            _ => panic!("Arc object should still be an arc segment"),
        }
        match &line_obj.kind {
            ObjectKind::Segment {
                segment: Segment::Line(_),
            } => {}
            _ => panic!("Line object should still be a line segment"),
        }

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_two_points() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![point0_id.into(), point1_id.into()],
            distance: Number {
                value: 2.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            // The lack indentation is a formatter bug.
            "\
sketch(on = XY) {
  point1 = point(at = [var 1.29, var 2.29])
  point2 = point(at = [var 2.71, var 3.71])
  distance([point1, point2]) == 2mm
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_two_points_with_label() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();

        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };
        let constraint = Constraint::Distance(Distance {
            points: vec![point0_id.into(), point1_id.into()],
            distance: Number {
                value: 2.0,
                units: NumericSuffix::Mm,
            },
            label_position: Some(label_position.clone()),
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 1.29, var 2.29])
  point2 = point(at = [var 2.71, var 3.71])
  distance([point1, point2], labelPosition = [10mm, 11mm]) == 2mm
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Distance(distance) = constraint else {
            panic!("Expected distance constraint");
        };
        assert_eq!(distance.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_distance_constraint_label_position() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![point0_id.into(), point1_id.into()],
            distance: Number {
                value: 2.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (_, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_id = sketch.constraints[0];
        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };

        let (src_delta, scene_delta) = frontend
            .edit_distance_constraint_label_position(
                &mock_ctx,
                version,
                sketch_id,
                constraint_id,
                label_position.clone(),
                vec![],
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 2])
  distance([point1, point2], labelPosition = [10mm, 11mm]) == 2mm
}
"
        );

        let constraint_object = scene_delta.new_graph.objects.get(constraint_id.0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Distance(distance) = constraint else {
            panic!("Expected distance constraint");
        };
        assert_eq!(distance.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_distance_constraint_label_position_preserves_anchor_segment_solution() {
        let initial_source = "\
sketch(on = XY) {
  point1 = point(at = [var 0mm, var 0mm])
  point2 = point(at = [var 10mm, var 0mm])
  distance([point1, point2]) == 5mm
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = sketch.segments[0];
        let point1_id = sketch.segments[1];
        let constraint_id = sketch.constraints[0];

        let edited_segments = vec![ExistingSegmentCtor {
            id: point0_id,
            ctor: SegmentCtor::Point(PointCtor {
                position: Point2d {
                    x: Expr::Var(Number {
                        value: 2.0,
                        units: NumericSuffix::Mm,
                    }),
                    y: Expr::Var(Number {
                        value: 1.0,
                        units: NumericSuffix::Mm,
                    }),
                },
            }),
        }];
        let (_, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, edited_segments)
            .await
            .unwrap();
        let point0_after_segment_edit = point_position(&scene_delta.new_graph, point0_id);
        let point1_after_segment_edit = point_position(&scene_delta.new_graph, point1_id);

        let label_position = Point2d {
            x: Number {
                value: 3.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 4.0,
                units: NumericSuffix::Mm,
            },
        };
        let (_, scene_delta) = frontend
            .edit_distance_constraint_label_position(
                &mock_ctx,
                version,
                sketch_id,
                constraint_id,
                label_position,
                vec![point0_id],
            )
            .await
            .unwrap();

        assert_point_position_close(
            point_position(&scene_delta.new_graph, point0_id),
            point0_after_segment_edit,
        );
        assert_point_position_close(
            point_position(&scene_delta.new_graph, point1_id),
            point1_after_segment_edit,
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_point_line() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 0, var 5])
  line(start = [var 0, var 0], end = [var 10, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.first().unwrap();
        let line_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Line(_)
                    })
                )
            })
            .unwrap();

        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };
        let constraint = Constraint::Distance(Distance {
            points: vec![point_id.into(), line_id.into()],
            distance: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: Some(label_position.clone()),
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 0, var 5])
  line1 = line(start = [var 0, var 0], end = [var 10, var 0])
  distance([point1, line1], labelPosition = [10mm, 11mm]) == 5mm
}
"
        );
        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Distance(distance) = constraint else {
            panic!("Expected distance constraint");
        };
        assert_eq!(distance.label_position, Some(label_position));

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_point_arc() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 0, var 8])
  arc(start = [var 5, var 0], end = [var 0, var 5], center = [var 0, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.first().unwrap();
        let arc_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![point_id.into(), arc_id.into()],
            distance: Number {
                value: 3.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 0, var 8])
  arc1 = arc(start = [var 5, var 0], end = [var 0, var 5], center = [var 0, var 0])
  distance([point1, arc1]) == 3mm
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_arc_origin() {
        let initial_source = "\
sketch001 = sketch(on = XY) {
  arc(start = [var -4.13mm, var -0.59mm], end = [var -3.47mm, var 3.38mm], center = [var -4.55mm, var 1.52mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let arc_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![arc_id.into(), ConstraintSegment::ORIGIN],
            distance: Number {
                value: 3.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch001 = sketch(on = XY) {
  arc1 = arc(start = [var -4.16mm, var -0.43mm], end = [var -3.53mm, var 3.28mm], center = [var -4.91mm, var 1.61mm])
  distance([arc1, ORIGIN]) == 3mm
}
"
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_line_origin() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 5, var 0], end = [var 5, var 10])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Line(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![ConstraintSegment::ORIGIN, line_id.into()],
            distance: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 5, var 0], end = [var 5, var 10])
  distance([ORIGIN, line1]) == 5mm
}
"
        );

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_line_circle() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var -10, var 8], end = [var 10, var 8])
  circle(start = [var 5, var 0], center = [var 0, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Line(_)
                    })
                )
            })
            .unwrap();
        let circle_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Circle(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![line_id.into(), circle_id.into()],
            distance: Number {
                value: 3.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var -10, var 8], end = [var 10, var 8])
  circle1 = circle(start = [var 5, var 0], center = [var 0, var 0])
  distance([line1, circle1]) == 3mm
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_circle_arc() {
        let initial_source = "\
sketch(on = XY) {
  circle(start = [var 5, var 0], center = [var 0, var 0])
  arc(start = [var 15, var 0], end = [var 10, var 5], center = [var 10, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let circle_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Circle(_)
                    })
                )
            })
            .unwrap();
        let arc_id = *sketch
            .segments
            .iter()
            .find(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Distance(Distance {
            points: vec![circle_id.into(), arc_id.into()],
            distance: Number {
                value: 3.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  circle1 = circle(start = [var 4.33, var 0], center = [var -0.34, var -0.09])
  arc1 = arc(start = [var 15.33, var -0.01], end = [var 10.01, var 4.33], center = [var 11.34, var 0.53])
  distance([circle1, arc1]) == 3mm
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_parallel_lines() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 0, var 0], end = [var 10, var 0])
  line(start = [var 0, var 5], end = [var 10, var 5])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_ids = sketch
            .segments
            .iter()
            .copied()
            .filter(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Line(_)
                    })
                )
            })
            .collect::<Vec<_>>();

        let constraint = Constraint::Distance(Distance {
            points: vec![line_ids[0].into(), line_ids[1].into()],
            distance: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 10, var 0])
  line2 = line(start = [var 0, var 5], end = [var 10, var 5])
  distance([line1, line2]) == 5mm
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_distance_non_parallel_lines_lowers_to_distance() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 0, var 0], end = [var 10, var 0])
  line(start = [var 0, var 0], end = [var 0, var 10])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_ids = sketch
            .segments
            .iter()
            .copied()
            .filter(|segment_id| {
                matches!(
                    frontend.scene_graph.objects.get(segment_id.0).map(|obj| &obj.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Line(_)
                    })
                )
            })
            .collect::<Vec<_>>();

        let constraint = Constraint::Distance(Distance {
            points: vec![line_ids[0].into(), line_ids[1].into()],
            distance: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 4.98, var -0.07], end = [var 4.98, var 0.14])
  line2 = line(start = [var 0.02, var 4.3], end = [var 0.03, var 5.65])
  distance([line1, line2]) == 5mm
}
"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_horizontal_distance_two_points() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();
        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };

        let constraint = Constraint::HorizontalDistance(Distance {
            points: vec![point0_id.into(), point1_id.into()],
            distance: Number {
                value: 2.0,
                units: NumericSuffix::Mm,
            },
            label_position: Some(label_position.clone()),
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            // The lack indentation is a formatter bug.
            "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  horizontalDistance([point1, point2], labelPosition = [10mm, 11mm]) == 2mm
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );
        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::HorizontalDistance(distance) = constraint else {
            panic!("Expected horizontal distance constraint");
        };
        assert_eq!(distance.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_radius_single_arc_segment() {
        let initial_source = "\
sketch(on = XY) {
  arc(start = [var 1, var 2], end = [var 3, var 4], center = [var 0, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        // Find the arc segment (not the points)
        let arc_id = sketch
            .segments
            .iter()
            .find(|&seg_id| {
                let obj = frontend.scene_graph.objects.get(seg_id.0);
                matches!(
                    obj.map(|o| &o.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Radius(Radius {
            arc: *arc_id,
            radius: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            // The lack indentation is a formatter bug.
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 1.83, var 3.62], end = [var 2.42, var 3.3], center = [var -0.25, var -0.92])
  radius(arc1) == 5mm
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            7, // Plane (0) + Sketch (1) + Start point (2) + End point (3) + Center point (4) + Arc (5) + Constraint (6)
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_radius_single_arc_segment_with_label_position() {
        let initial_source = "\
sketch(on = XY) {
  arc(start = [var 1, var 2], end = [var 3, var 4], center = [var 0, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let arc_id = sketch
            .segments
            .iter()
            .find(|&seg_id| {
                let obj = frontend.scene_graph.objects.get(seg_id.0);
                matches!(
                    obj.map(|o| &o.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };
        let constraint = Constraint::Radius(Radius {
            arc: *arc_id,
            radius: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: Some(label_position.clone()),
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 1.83, var 3.62], end = [var 2.42, var 3.3], center = [var -0.25, var -0.92])
  radius(arc1, labelPosition = [10mm, 11mm]) == 5mm
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Radius(radius) = constraint else {
            panic!("Expected radius constraint");
        };
        assert_eq!(radius.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_radius_constraint_label_position() {
        let initial_source = "\
sketch(on = XY) {
  arc1 = arc(start = [var 5mm, var 0mm], end = [var 0mm, var 5mm], center = [var 0mm, var 0mm])
  radius(arc1) == 5mm
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let constraint_id = sketch.constraints[0];
        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };

        let (src_delta, scene_delta) = frontend
            .edit_distance_constraint_label_position(
                &mock_ctx,
                version,
                sketch_id,
                constraint_id,
                label_position.clone(),
                vec![],
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 5mm, var 0mm], end = [var 0mm, var 5mm], center = [var 0mm, var 0mm])
  radius(arc1, labelPosition = [10mm, 11mm]) == 5mm
}
"
        );

        let constraint_object = scene_delta.new_graph.objects.get(constraint_id.0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Radius(radius) = constraint else {
            panic!("Expected radius constraint");
        };
        assert_eq!(radius.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_vertical_distance_two_points() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();
        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };

        let constraint = Constraint::VerticalDistance(Distance {
            points: vec![point0_id.into(), point1_id.into()],
            distance: Number {
                value: 2.0,
                units: NumericSuffix::Mm,
            },
            label_position: Some(label_position.clone()),
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            // The lack indentation is a formatter bug.
            "\
sketch(on = XY) {
  point1 = point(at = [var 1, var 2])
  point2 = point(at = [var 3, var 4])
  verticalDistance([point1, point2], labelPosition = [10mm, 11mm]) == 2mm
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );
        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::VerticalDistance(distance) = constraint else {
            panic!("Expected vertical distance constraint");
        };
        assert_eq!(distance.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_add_fixed_standalone_point() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.first().unwrap();

        let (src_delta, scene_delta) = frontend
            .add_constraint(
                &mock_ctx,
                version,
                sketch_id,
                Constraint::Fixed(Fixed {
                    points: vec![FixedPoint {
                        point: point_id,
                        position: Point2d {
                            x: Number {
                                value: 2.0,
                                units: NumericSuffix::Mm,
                            },
                            y: Number {
                                value: 3.0,
                                units: NumericSuffix::Mm,
                            },
                        },
                    }],
                }),
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 2, var 3])
  fixed([point1, [2mm, 3mm]])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            4,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_add_fixed_multiple_points() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
  point(at = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point0_id = *sketch.segments.first().unwrap();
        let point1_id = *sketch.segments.get(1).unwrap();

        let (src_delta, scene_delta) = frontend
            .add_constraint(
                &mock_ctx,
                version,
                sketch_id,
                Constraint::Fixed(Fixed {
                    points: vec![
                        FixedPoint {
                            point: point0_id,
                            position: Point2d {
                                x: Number {
                                    value: 2.0,
                                    units: NumericSuffix::Mm,
                                },
                                y: Number {
                                    value: 3.0,
                                    units: NumericSuffix::Mm,
                                },
                            },
                        },
                        FixedPoint {
                            point: point1_id,
                            position: Point2d {
                                x: Number {
                                    value: 4.0,
                                    units: NumericSuffix::Mm,
                                },
                                y: Number {
                                    value: 5.0,
                                    units: NumericSuffix::Mm,
                                },
                            },
                        },
                    ],
                }),
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 2, var 3])
  point2 = point(at = [var 4, var 5])
  fixed([point1, [2mm, 3mm]])
  fixed([point2, [4mm, 5mm]])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            6,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_add_fixed_owned_point() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_start_id = *sketch.segments.first().unwrap();

        let (src_delta, scene_delta) = frontend
            .add_constraint(
                &mock_ctx,
                version,
                sketch_id,
                Constraint::Fixed(Fixed {
                    points: vec![FixedPoint {
                        point: line_start_id,
                        position: Point2d {
                            x: Number {
                                value: 2.0,
                                units: NumericSuffix::Mm,
                            },
                            y: Number {
                                value: 3.0,
                                units: NumericSuffix::Mm,
                            },
                        },
                    }],
                }),
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 2, var 3], end = [var 3, var 4])
  fixed([line1.start, [2mm, 3mm]])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            6,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_radius_error_cases() {
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        // Test: Single point should error
        let initial_source_point = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
}
";
        let program_point = Program::parse(initial_source_point).unwrap().0.unwrap();
        let mut frontend_point = FrontendState::new();
        frontend_point.hack_set_program(&ctx, program_point).await.unwrap();
        let sketch_object_point = find_first_sketch_object(&frontend_point.scene_graph).unwrap();
        let sketch_id_point = sketch_object_point.id;
        let sketch_point = expect_sketch(sketch_object_point);
        let point_id = *sketch_point.segments.first().unwrap();

        let constraint_point = Constraint::Radius(Radius {
            arc: point_id,
            radius: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let result_point = frontend_point
            .add_constraint(&mock_ctx, version, sketch_id_point, constraint_point)
            .await;
        assert!(result_point.is_err(), "Single point should error for radius");

        // Test: Single line segment should error (only arc segments supported)
        let initial_source_line = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";
        let program_line = Program::parse(initial_source_line).unwrap().0.unwrap();
        let mut frontend_line = FrontendState::new();
        frontend_line.hack_set_program(&ctx, program_line).await.unwrap();
        let sketch_object_line = find_first_sketch_object(&frontend_line.scene_graph).unwrap();
        let sketch_id_line = sketch_object_line.id;
        let sketch_line = expect_sketch(sketch_object_line);
        let line_id = *sketch_line.segments.first().unwrap();

        let constraint_line = Constraint::Radius(Radius {
            arc: line_id,
            radius: Number {
                value: 5.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let result_line = frontend_line
            .add_constraint(&mock_ctx, version, sketch_id_line, constraint_line)
            .await;
        assert!(result_line.is_err(), "Single line segment should error for radius");

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_diameter_single_arc_segment() {
        let initial_source = "\
sketch(on = XY) {
  arc(start = [var 1, var 2], end = [var 3, var 4], center = [var 0, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        // Find the arc segment (not the points)
        let arc_id = sketch
            .segments
            .iter()
            .find(|&seg_id| {
                let obj = frontend.scene_graph.objects.get(seg_id.0);
                matches!(
                    obj.map(|o| &o.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let constraint = Constraint::Diameter(Diameter {
            arc: *arc_id,
            diameter: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            // The lack indentation is a formatter bug.
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 1.83, var 3.62], end = [var 2.42, var 3.3], center = [var -0.25, var -0.92])
  diameter(arc1) == 10mm
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            7, // Plane (0) + Sketch (1) + Start point (2) + End point (3) + Center point (4) + Arc (5) + Constraint (6)
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_diameter_single_arc_segment_with_label_position() {
        let initial_source = "\
sketch(on = XY) {
  arc(start = [var 1, var 2], end = [var 3, var 4], center = [var 0, var 0])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let arc_id = sketch
            .segments
            .iter()
            .find(|&seg_id| {
                let obj = frontend.scene_graph.objects.get(seg_id.0);
                matches!(
                    obj.map(|o| &o.kind),
                    Some(ObjectKind::Segment {
                        segment: Segment::Arc(_)
                    })
                )
            })
            .unwrap();

        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };
        let constraint = Constraint::Diameter(Diameter {
            arc: *arc_id,
            diameter: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            label_position: Some(label_position.clone()),
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 1.83, var 3.62], end = [var 2.42, var 3.3], center = [var -0.25, var -0.92])
  diameter(arc1, labelPosition = [10mm, 11mm]) == 10mm
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Diameter(diameter) = constraint else {
            panic!("Expected diameter constraint");
        };
        assert_eq!(diameter.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_diameter_constraint_label_position() {
        let initial_source = "\
sketch(on = XY) {
  arc1 = arc(start = [var 5mm, var 0mm], end = [var 0mm, var 5mm], center = [var 0mm, var 0mm])
  diameter(arc1) == 10mm
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = mock_ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let constraint_id = sketch.constraints[0];
        let label_position = Point2d {
            x: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            y: Number {
                value: 11.0,
                units: NumericSuffix::Mm,
            },
        };

        let (src_delta, scene_delta) = frontend
            .edit_distance_constraint_label_position(
                &mock_ctx,
                version,
                sketch_id,
                constraint_id,
                label_position.clone(),
                vec![],
            )
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 5mm, var 0mm], end = [var 0mm, var 5mm], center = [var 0mm, var 0mm])
  diameter(arc1, labelPosition = [10mm, 11mm]) == 10mm
}
"
        );

        let constraint_object = scene_delta.new_graph.objects.get(constraint_id.0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Diameter(diameter) = constraint else {
            panic!("Expected diameter constraint");
        };
        assert_eq!(diameter.label_position, Some(label_position));

        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_diameter_error_cases() {
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        // Test: Single point should error
        let initial_source_point = "\
sketch(on = XY) {
  point(at = [var 1, var 2])
}
";
        let program_point = Program::parse(initial_source_point).unwrap().0.unwrap();
        let mut frontend_point = FrontendState::new();
        frontend_point.hack_set_program(&ctx, program_point).await.unwrap();
        let sketch_object_point = find_first_sketch_object(&frontend_point.scene_graph).unwrap();
        let sketch_id_point = sketch_object_point.id;
        let sketch_point = expect_sketch(sketch_object_point);
        let point_id = *sketch_point.segments.first().unwrap();

        let constraint_point = Constraint::Diameter(Diameter {
            arc: point_id,
            diameter: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let result_point = frontend_point
            .add_constraint(&mock_ctx, version, sketch_id_point, constraint_point)
            .await;
        assert!(result_point.is_err(), "Single point should error for diameter");

        // Test: Single line segment should error (only arc segments supported)
        let initial_source_line = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";
        let program_line = Program::parse(initial_source_line).unwrap().0.unwrap();
        let mut frontend_line = FrontendState::new();
        frontend_line.hack_set_program(&ctx, program_line).await.unwrap();
        let sketch_object_line = find_first_sketch_object(&frontend_line.scene_graph).unwrap();
        let sketch_id_line = sketch_object_line.id;
        let sketch_line = expect_sketch(sketch_object_line);
        let line_id = *sketch_line.segments.first().unwrap();

        let constraint_line = Constraint::Diameter(Diameter {
            arc: line_id,
            diameter: Number {
                value: 10.0,
                units: NumericSuffix::Mm,
            },
            label_position: None,
            source: Default::default(),
        });
        let result_line = frontend_line
            .add_constraint(&mock_ctx, version, sketch_id_line, constraint_line)
            .await;
        assert!(result_line.is_err(), "Single line segment should error for diameter");

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_line_horizontal() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();

        let constraint = Constraint::Horizontal(Horizontal::Line { line: line1_id });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 3], end = [var 3, var 3])
  horizontal(line1)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            6,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_control_point_spline_edge_horizontal() {
        let initial_source = "\
@settings(experimentalFeatures = allow)
splineSketch = sketch(on = XY) {
  controlPointSpline1 = controlPointSpline(points = [
    [var 0mm, var 0mm],
    [var 10mm, var 20mm],
    [var 20mm, var 0mm],
  ])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let spline_id = sketch
            .segments
            .iter()
            .copied()
            .find(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::ControlPointSpline(_)
                    }
                )
            })
            .expect("Expected a control point spline segment in sketch");
        let edge_id = frontend
            .scene_graph
            .objects
            .iter()
            .find_map(|obj| match &obj.kind {
                ObjectKind::Segment {
                    segment: Segment::Line(line),
                } if line.owner == Some(spline_id) => Some(obj.id),
                _ => None,
            })
            .expect("Expected an owned control-polygon edge");

        let constraint = Constraint::Horizontal(Horizontal::Line { line: edge_id });
        let (src_delta, _) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert!(
            src_delta.text.contains("horizontal(controlPointSpline1.edges[0])"),
            "Expected horizontal constraint on spline edge, got: {}",
            src_delta.text
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_control_point_spline_edge_angle() {
        let initial_source = "\
@settings(experimentalFeatures = allow)
splineSketch = sketch(on = XY) {
  controlPointSpline1 = controlPointSpline(points = [
    [var 0mm, var 0mm],
    [var 10mm, var 20mm],
    [var 20mm, var 0mm],
  ])

  line1 = line(start = [var 40mm, var 0mm], end = [var 60mm, var 10mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let spline_id = sketch
            .segments
            .iter()
            .copied()
            .find(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::ControlPointSpline(_)
                    }
                )
            })
            .expect("Expected a control point spline segment in sketch");
        let edge_id = frontend
            .scene_graph
            .objects
            .iter()
            .find_map(|obj| match &obj.kind {
                ObjectKind::Segment {
                    segment: Segment::Line(line),
                } if line.owner == Some(spline_id) => Some(obj.id),
                _ => None,
            })
            .expect("Expected an owned control-polygon edge");
        let line1_id = frontend
            .scene_graph
            .objects
            .iter()
            .find_map(|obj| match &obj.kind {
                ObjectKind::Segment {
                    segment: Segment::Line(line),
                } if line.owner.is_none() && obj.label == "line1" => Some(obj.id),
                _ => None,
            })
            .or_else(|| {
                sketch.segments.iter().copied().find(|seg_id| {
                    matches!(
                        &frontend.scene_graph.objects[seg_id.0].kind,
                        ObjectKind::Segment {
                            segment: Segment::Line(line),
                        } if line.owner.is_none()
                    )
                })
            })
            .expect("Expected a standalone line segment in sketch");

        let constraint = Constraint::Angle(Angle {
            lines: vec![line1_id, edge_id],
            angle: Number {
                value: 30.0,
                units: NumericSuffix::Deg,
            },
            source: Default::default(),
        });
        let (src_delta, _) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert!(
            src_delta
                .text
                .contains("angle([line1, controlPointSpline1.edges[0]]) == 30deg"),
            "Expected angle constraint on spline edge, got: {}",
            src_delta.text
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_ui_scene_graph_hides_same_spline_coincident_constraints() {
        let initial_source = "\
@settings(experimentalFeatures = allow)
splineSketch = sketch(on = XY) {
  spline1 = controlPointSpline(points = [
    [var 0mm, var 0mm],
    [var 10mm, var 20mm],
    [var 20mm, var 0mm],
  ])
  line1 = line(start = [var 0mm, var 0mm], end = [var -10mm, var 0mm])
  coincident([spline1.controls[1], spline1.edges[0]])
  coincident([spline1.controls[0], line1])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;

        let ui_scene_graph = frontend.scene_graph_for_ui();
        let sketch_object = find_first_sketch_object(&ui_scene_graph).unwrap();
        let sketch = expect_sketch(sketch_object);

        assert_eq!(
            sketch.constraints.len(),
            1,
            "Expected only the external coincident constraint to remain visible in the UI scene graph"
        );

        let visible_constraints = ui_scene_graph
            .objects
            .iter()
            .filter_map(|object| match &object.kind {
                ObjectKind::Constraint {
                    constraint: Constraint::Coincident(coincident),
                } => Some(coincident.clone()),
                _ => None,
            })
            .collect::<Vec<_>>();

        assert_eq!(
            visible_constraints.len(),
            1,
            "Expected only one coincident constraint object in the UI scene graph"
        );
        assert_eq!(
            visible_constraints[0].get_segments().len(),
            2,
            "Expected the remaining visible coincident constraint to reference two segments"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_edit_control_point_spline_can_append_control_point() {
        let initial_source = "\
@settings(experimentalFeatures = allow)
splineSketch = sketch(on = XY) {
  controlPointSpline(points = [
    [var 0mm, var 0mm],
    [var 10mm, var 20mm],
    [var 20mm, var 0mm],
  ])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        seed_frontend_with_mock(&mut frontend, &mock_ctx, &program).await;
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let spline_id = sketch
            .segments
            .iter()
            .copied()
            .find(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::ControlPointSpline(_)
                    }
                )
            })
            .expect("Expected a control point spline segment in sketch");

        let ctor = ControlPointSplineCtor {
            points: vec![
                Point2d {
                    x: Expr::Var(Number {
                        value: 0.0,
                        units: NumericSuffix::Mm,
                    }),
                    y: Expr::Var(Number {
                        value: 0.0,
                        units: NumericSuffix::Mm,
                    }),
                },
                Point2d {
                    x: Expr::Var(Number {
                        value: 10.0,
                        units: NumericSuffix::Mm,
                    }),
                    y: Expr::Var(Number {
                        value: 20.0,
                        units: NumericSuffix::Mm,
                    }),
                },
                Point2d {
                    x: Expr::Var(Number {
                        value: 20.0,
                        units: NumericSuffix::Mm,
                    }),
                    y: Expr::Var(Number {
                        value: 0.0,
                        units: NumericSuffix::Mm,
                    }),
                },
                Point2d {
                    x: Expr::Var(Number {
                        value: 30.0,
                        units: NumericSuffix::Mm,
                    }),
                    y: Expr::Var(Number {
                        value: 10.0,
                        units: NumericSuffix::Mm,
                    }),
                },
            ],
            construction: None,
        };

        let segments = vec![ExistingSegmentCtor {
            id: spline_id,
            ctor: SegmentCtor::ControlPointSpline(ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();

        assert!(
            src_delta.text.contains("[var 30mm, var 10mm]"),
            "Expected appended spline control point in source, got: {}",
            src_delta.text
        );

        assert!(
            scene_delta.invalidates_ids,
            "Expected appending a spline control point to invalidate ids"
        );
        let updated_spline = scene_delta
            .new_graph
            .objects
            .iter()
            .find_map(|obj| match &obj.kind {
                ObjectKind::Segment {
                    segment: Segment::ControlPointSpline(updated_spline),
                } if updated_spline.controls.len() == 4 => Some(updated_spline),
                _ => None,
            })
            .expect("Expected edited scene graph to contain a four-point control point spline");
        assert_eq!(
            updated_spline.controls.len(),
            4,
            "Expected edited spline to expose four control points"
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_line_vertical() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();

        let constraint = Constraint::Vertical(Vertical::Line { line: line1_id });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 2, var 2], end = [var 2, var 4])
  vertical(line1)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            6,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_points_vertical() {
        let initial_source = "\
sketch001 = sketch(on = XY) {
  p0 = point(at = [var -2.23mm, var 3.1mm])
  pf = point(at = [4, 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_ids = vec![
            sketch.segments.first().unwrap().to_owned(),
            sketch.segments.get(1).unwrap().to_owned(),
        ];

        let constraint = Constraint::Vertical(Vertical::Points {
            points: point_ids.into_iter().map(ConstraintSegment::from).collect(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch001 = sketch(on = XY) {
  p0 = point(at = [var 4mm, var 3.1mm])
  pf = point(at = [4, 4])
  vertical([p0, pf])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_points_horizontal() {
        let initial_source = "\
sketch001 = sketch(on = XY) {
  p0 = point(at = [var -2.23mm, var 3.1mm])
  pf = point(at = [4, 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_ids = vec![
            sketch.segments.first().unwrap().to_owned(),
            sketch.segments.get(1).unwrap().to_owned(),
        ];

        let constraint = Constraint::Horizontal(Horizontal::Points {
            points: point_ids.into_iter().map(ConstraintSegment::from).collect(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch001 = sketch(on = XY) {
  p0 = point(at = [var -2.23mm, var 4mm])
  pf = point(at = [4, 4])
  horizontal([p0, pf])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            5,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_point_horizontal_with_origin() {
        let initial_source = "\
sketch001 = sketch(on = XY) {
  p0 = point(at = [var -2.23mm, var 3.1mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.first().unwrap();

        let constraint = Constraint::Horizontal(Horizontal::Points {
            points: vec![ConstraintSegment::from(point_id), ConstraintSegment::ORIGIN],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch001 = sketch(on = XY) {
  p0 = point(at = [var -2.23mm, var 0mm])
  horizontal([p0, ORIGIN])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            4,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_lines_equal_length() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();

        let constraint = Constraint::LinesEqualLength(LinesEqualLength {
            lines: vec![line1_id, line2_id],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  equalLength([line1, line2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            9,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_add_constraint_multi_line_equal_length() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
  line(start = [var 9, var 10], end = [var 11, var 12])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();
        let line3_id = *sketch.segments.get(8).unwrap();

        let constraint = Constraint::LinesEqualLength(LinesEqualLength {
            lines: vec![line1_id, line2_id, line3_id],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  equalLength([line1, line2, line3])
}
"
        );
        let constraints = scene_delta
            .new_graph
            .objects
            .iter()
            .filter_map(|obj| {
                let ObjectKind::Constraint { constraint } = &obj.kind else {
                    return None;
                };
                Some(constraint)
            })
            .collect::<Vec<_>>();

        assert_eq!(constraints.len(), 1, "{:#?}", frontend.scene_graph.objects);
        let Constraint::LinesEqualLength(lines_equal_length) = constraints[0] else {
            panic!("expected equal length constraint, got {:?}", constraints[0]);
        };
        assert_eq!(lines_equal_length.lines.len(), 3);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_lines_parallel() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();

        let constraint = Constraint::Parallel(Parallel {
            lines: vec![line1_id, line2_id],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  parallel([line1, line2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            9,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_lines_parallel_multiline() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
  line(start = [var 9, var 10], end = [var 11, var 12])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();
        let line3_id = *sketch.segments.get(8).unwrap();

        let constraint = Constraint::Parallel(Parallel {
            lines: vec![line1_id, line2_id, line3_id],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 1, var 2], end = [var 3, var 4])
  line2 = line(start = [var 5, var 6], end = [var 7, var 8])
  line3 = line(start = [var 9, var 10], end = [var 11, var 12])
  parallel([line1, line2, line3])
}
"
        );

        let sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let sketch = expect_sketch(sketch_object);
        assert_eq!(sketch.constraints.len(), 1);

        let constraint_object = scene_delta.new_graph.objects.get(sketch.constraints[0].0).unwrap();
        let ObjectKind::Constraint { constraint } = &constraint_object.kind else {
            panic!("Expected constraint object");
        };
        let Constraint::Parallel(parallel) = constraint else {
            panic!("Expected parallel constraint");
        };
        assert_eq!(parallel.lines.len(), 3);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_lines_perpendicular() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();

        let constraint = Constraint::Perpendicular(Perpendicular {
            lines: vec![line1_id, line2_id],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 2, var 3], end = [var 2, var 3])
  line2 = line(start = [var 6, var 7], end = [var 6, var 7])
  perpendicular([line1, line2])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            9,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_lines_angle() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  line(start = [var 5, var 6], end = [var 7, var 8])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();

        let constraint = Constraint::Angle(Angle {
            lines: vec![line1_id, line2_id],
            angle: Number {
                value: 30.0,
                units: NumericSuffix::Deg,
            },
            source: Default::default(),
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            // The lack indentation is a formatter bug.
            "\
sketch(on = XY) {
  line1 = line(start = [var 0.9, var 2.36], end = [var 3.1, var 3.64])
  line2 = line(start = [var 5.36, var 5.9], end = [var 6.64, var 8.1])
  angle([line1, line2]) == 30deg
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            9,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_segments_tangent() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 1, var 2], end = [var 3, var 4])
  arc(start = [var 5, var 2], end = [var 7, var 2], center = [var 6, var 2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let arc1_id = *sketch.segments.get(6).unwrap();

        let constraint = Constraint::Tangent(Tangent {
            input: vec![line1_id, arc1_id],
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 0.84, var 2.13], end = [var 3.82, var 3.27])
  arc1 = arc(start = [var 4.51, var 2.03], end = [var 7.05, var 2.02], center = [var 5.78, var 2.55])
  tangent([line1, arc1])
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            10,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_point_midpoint() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 1, var 1])
  line(start = [var 0, var 0], end = [var 6, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.first().unwrap();
        let line_id = *sketch.segments.get(3).unwrap();

        let constraint = Constraint::Midpoint(Midpoint {
            point: ConstraintSegment::from(point_id),
            segment: line_id,
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 2.33, var 1.67])
  line1 = line(start = [var -0.67, var -0.33], end = [var 5.33, var 3.67])
  midpoint(line1, point = point1)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            7,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_segments_symmetric() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 0, var 0], end = [var 0, var 4])
  line(start = [var 4, var 0], end = [var 4, var 4])
  line(start = [var 2, var -1], end = [var 2, var 5])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line1_id = *sketch.segments.get(2).unwrap();
        let line2_id = *sketch.segments.get(5).unwrap();
        let axis_id = *sketch.segments.get(8).unwrap();

        let constraint = Constraint::Symmetric(Symmetric {
            input: vec![line1_id, line2_id],
            axis: axis_id,
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var 0, var 0], end = [var 0, var 4])
  line2 = line(start = [var 4, var 0], end = [var 4, var 4])
  line3 = line(start = [var 2, var -1], end = [var 2, var 5])
  symmetric([line1, line2], axis = line3)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            12,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_point_arc_midpoint() {
        let initial_source = "\
sketch(on = XY) {
  point(at = [var 6, var 3])
  arc(start = [var 5, var 2], end = [var 7, var 2], center = [var 6, var 2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let point_id = *sketch.segments.first().unwrap();
        let arc_id = *sketch.segments.get(4).unwrap();

        let constraint = Constraint::Midpoint(Midpoint {
            point: ConstraintSegment::from(point_id),
            segment: arc_id,
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  point1 = point(at = [var 6, var 2.35])
  arc1 = arc(start = [var 6, var 2.35], end = [var 6, var 2.35], center = [var 6, var 1.94])
  midpoint(arc1, point = point1)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            8,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_origin_line_midpoint() {
        let initial_source = "\
sketch(on = XY) {
  line(start = [var 0, var 0], end = [var 6, var 4])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let line_id = *sketch.segments.get(2).unwrap();

        let constraint = Constraint::Midpoint(Midpoint {
            point: ConstraintSegment::ORIGIN,
            segment: line_id,
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  line1 = line(start = [var -3, var -2], end = [var 3, var 2])
  midpoint(line1, point = ORIGIN)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            6,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_origin_arc_midpoint() {
        let initial_source = "\
sketch(on = XY) {
  arc(start = [var 5, var 2], end = [var 7, var 2], center = [var 6, var 2])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let arc_id = *sketch.segments.get(3).unwrap();

        let constraint = Constraint::Midpoint(Midpoint {
            point: ConstraintSegment::ORIGIN,
            segment: arc_id,
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  arc1 = arc(start = [var 0.35, var 2.24], end = [var 1.62, var -1.58], center = [var 2.34, var 0.78])
  midpoint(arc1, point = ORIGIN)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            7,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_segments_symmetric_arcs() {
        let initial_source = "\
sketch(on = XY) {
  arc(start = [var -15, var 0], end = [var -10, var 5], center = [var -10, var 0])
  arc(start = [var 6, var 2], end = [var 12, var -4], center = [var 8, var 1])
  line(start = [var 0, var -10], end = [var 0, var 10])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.program = program.clone();
        let outcome = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
        frontend.update_state_after_exec(outcome, true);
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);
        let arc1_id = *sketch.segments.get(3).unwrap();
        let arc2_id = *sketch.segments.get(7).unwrap();
        let axis_id = *sketch.segments.get(10).unwrap();

        let constraint = Constraint::Symmetric(Symmetric {
            input: vec![arc1_id, arc2_id],
            axis: axis_id,
        });
        let (src_delta, scene_delta) = frontend
            .add_constraint(&ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch(on = XY) {
  arc1 = arc(start = [var -14.46, var 0], end = [var -10, var 4.65], center = [var -10.14, var 0.31])
  arc2 = arc(start = [var 5.49, var 2.26], end = [var 11.58, var -3.47], center = [var 9.34, var 0.25])
  line1 = line(start = [var -0.44, var -10], end = [var -0.37, var 10])
  symmetric([arc1, arc2], axis = line1)
}
"
        );
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            14,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_on_face_simple() {
        let initial_source = "\
len = 2mm
cube = startSketchOn(XY)
  |> startProfile(at = [0, 0])
  |> line(end = [len, 0], tag = $side)
  |> line(end = [0, len])
  |> line(end = [-len, 0])
  |> line(end = [0, -len])
  |> close()
  |> extrude(length = len)

face = faceOf(cube, face = side)
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let face_object = find_first_face_object(&frontend.scene_graph).unwrap();
        let face_id = face_object.id;

        let sketch_args = SketchCtor {
            on: Plane::Object(face_id),
        };
        let (_src_delta, scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert_eq!(sketch_id, ObjectId(2));
        assert_eq!(scene_delta.new_objects, vec![ObjectId(2)]);
        let sketch_object = &scene_delta.new_graph.objects[2];
        assert_eq!(sketch_object.id, ObjectId(2));
        assert_eq!(
            sketch_object.kind,
            ObjectKind::Sketch(Sketch {
                args: SketchCtor {
                    on: Plane::Object(face_id),
                },
                plane: face_id,
                segments: vec![],
                constraints: vec![],
            })
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 8);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_on_wall_artifact_from_region_extrude() {
        let initial_source = "\
s = sketch(on = YZ) {
  line1 = line(start = [0, 0], end = [0, 1])
  line2 = line(start = [0, 1], end = [1, 1])
  line3 = line(start = [1, 1], end = [0, 0])
}
region001 = region(point = [0.1, 0.1], sketch = s)
extrude001 = extrude(region001, length = 5)
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let wall_object_id = find_first_wall_object_id(&frontend.scene_graph).expect("expected a wall object");

        let sketch_args = SketchCtor {
            on: Plane::Object(wall_object_id),
        };
        let (src_delta, _scene_delta, _sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert!(src_delta.text.contains("faceOf(extrude001, face = region001.tags."));

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_on_wall_artifact_from_split_region_extrude() {
        let initial_source = "\
sketch001 = sketch(on = YZ) {
  line1 = line(start = [var 0.49, var -0.39], end = [var 6.52, var -0.39])
  line2 = line(start = [var 6.52, var -0.39], end = [var 6.52, var 4.9])
  line3 = line(start = [var 6.52, var 4.9], end = [var 0.49, var 4.9])
  line4 = line(start = [var 0.49, var 4.9], end = [var 0.49, var -0.39])
  coincident([line1.end, line2.start])
  coincident([line2.end, line3.start])
  coincident([line3.end, line4.start])
  coincident([line4.end, line1.start])
  parallel([line2, line4])
  parallel([line3, line1])
  perpendicular([line1, line2])
  horizontal(line3)
  line5 = line(start = [2.35, 6.65], end = [5.89, -2.7])
}
region001 = region(point = [3.1, 3.74], sketch = sketch001)
extrude001 = extrude(region001, length = 5)
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let wall_object_id = find_first_wall_object_id(&frontend.scene_graph).expect("expected a wall object");

        let sketch_args = SketchCtor {
            on: Plane::Object(wall_object_id),
        };
        let (src_delta, _scene_delta, _sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert!(src_delta.text.contains("faceOf(extrude001, face = region001.tags."));

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_on_multi_region_extrude_cap_indexes_selected_solid() {
        let initial_source = "\
@settings(kclVersion = 2.0)

sketch001 = sketch(on = XY) {
  circle1 = circle(start = [var -1.51mm, var 1.31mm], center = [var -1.98mm, var 1.03mm])
  circle2 = circle(start = [var 2.98mm, var 2.37mm], center = [var 2.66mm, var 1.46mm])
}
hidden001 = hide(sketch001)
region001 = region(segments = [sketch001.circle2])
region002 = region(segments = [sketch001.circle1])
extrude001 = extrude([region001, region002], length = 5)
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let version = Version(0);

        for (solid_output_index, expected_face) in [
            (0, "faceOf(extrude001[0], face = END)"),
            (1, "faceOf(extrude001[1], face = END)"),
        ] {
            let mut frontend = FrontendState::new();
            frontend.hack_set_program(&ctx, program.clone()).await.unwrap();
            let cap_object_id = find_cap_object_id_with_solid_output_index(
                &frontend.scene_graph,
                crate::frontend::api::CapKind::End,
                solid_output_index,
            )
            .unwrap_or_else(|| panic!("expected an end cap object for solid output index {solid_output_index}"));

            let sketch_args = SketchCtor {
                on: Plane::Object(cap_object_id),
            };
            let (src_delta, _scene_delta, _sketch_id) = frontend
                .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
                .await
                .unwrap();

            assert!(
                src_delta.text.contains(expected_face),
                "expected `{expected_face}` in:\n{}",
                src_delta.text
            );
            assert!(!src_delta.text.contains("faceOf(extrude001, face = END)"));
        }

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_on_multi_region_extrude_wall_indexes_selected_solid() {
        let initial_source = "\
@settings(kclVersion = 2.0)

sketch001 = sketch(on = XY) {
  rect1Line1 = line(start = [0, 0], end = [1, 0])
  rect1Line2 = line(start = [1, 0], end = [1, 1])
  rect1Line3 = line(start = [1, 1], end = [0, 1])
  rect1Line4 = line(start = [0, 1], end = [0, 0])
  rect2Line1 = line(start = [3, 0], end = [4, 0])
  rect2Line2 = line(start = [4, 0], end = [4, 1])
  rect2Line3 = line(start = [4, 1], end = [3, 1])
  rect2Line4 = line(start = [3, 1], end = [3, 0])
}
hidden001 = hide(sketch001)
region001 = region(segments = [
  sketch001.rect1Line4,
  sketch001.rect1Line1
])
region002 = region(segments = [
  sketch001.rect2Line4,
  sketch001.rect2Line1
])
extrude001 = extrude([region001, region002], length = 5)
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let region_call = "\
region(segments = [
  sketch001.rect1Line4,
  sketch001.rect1Line1
])";
        let region_call_start = initial_source.find(region_call).unwrap();
        let region_range = [region_call_start, region_call_start + region_call.len(), 0].into();
        let segment_call = "line(start = [0, 0], end = [1, 0])";
        let segment_call_start = initial_source.find(segment_call).unwrap();
        let segment_range = [segment_call_start, segment_call_start + segment_call.len(), 0].into();
        let wall_object_id = frontend
            .scene_graph
            .objects
            .iter()
            .find_map(|object| match &object.kind {
                ObjectKind::Wall(wall)
                    if wall.source.path.as_ref().is_some_and(|path| path.range == region_range)
                        && wall.source.segment.range == segment_range =>
                {
                    Some(object.id)
                }
                _ => None,
            })
            .expect("expected a wall object for region001.tags.rect1Line1");

        let sketch_args = SketchCtor {
            on: Plane::Object(wall_object_id),
        };
        let (src_delta, _scene_delta, _sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();

        let expected_face = "faceOf(extrude001[0], face = region001.tags.rect1Line1)";
        assert!(
            src_delta.text.contains(expected_face),
            "expected `{expected_face}` in:\n{}",
            src_delta.text
        );
        assert!(!src_delta.text.contains("faceOf(extrude001, face ="));

        ctx.close().await;
    }

    #[test]
    fn test_enclosing_variable_fallback_skips_nested_sketch_items() {
        let source = "\
sketch001 = sketch(on = XY) {
  line(start = [0, 0], end = [1, 0])
}
part = subtract(boxSolid, tools = [cutSolid])
  |> appearance(color = \"#8f96a3\")
";
        let ast = Program::parse(source).unwrap().0.unwrap().ast;
        let line_start = source.find("line").unwrap();
        let line_end = line_start + "line(start = [0, 0], end = [1, 0])".len();
        let line_ref = SourceRef::Simple {
            range: [line_start, line_end, 0].into(),
            node_path: None,
        };
        assert_eq!(variable_name_containing_source_ref(&ast, &line_ref), None);

        let subtract_start = source.find("subtract").unwrap();
        let subtract_end = subtract_start + "subtract(boxSolid, tools = [cutSolid])".len();
        let subtract_ref = SourceRef::Simple {
            range: [subtract_start, subtract_end, 0].into(),
            node_path: None,
        };
        assert_eq!(
            variable_name_containing_source_ref(&ast, &subtract_ref),
            Some("part".to_owned())
        );
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_on_subtracted_sweep_cap_uses_composite_solid() {
        clear_mem_cache().await;
        let source = "\
boxSolid = startSketchOn(XY)
  |> startProfile(at = [0, 0])
  |> line(end = [4, 0], tag = $bottomEdge)
  |> line(end = [0, 4])
  |> line(end = [-4, 0])
  |> close()
  |> extrude(length = 10)
cutSolid = startSketchOn(XY)
  |> startProfile(at = [1, 1])
  |> line(end = [1, 0])
  |> line(end = [0, 1])
  |> line(end = [-1, 0])
  |> close()
  |> extrude(length = 10)
part = subtract(boxSolid, tools = [cutSolid])
  |> appearance(color = \"#8f96a3\", roughness = 55, metalness = 8)
";
        let program = Program::parse(source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        match frontend.hack_set_program(&ctx, program).await.unwrap() {
            SetProgramOutcome::Success { .. } => {}
            SetProgramOutcome::ExecFailure { error } => panic!("KCL fixture failed to execute: {error:?}"),
        }

        let sweep_call_start = source.find("extrude").unwrap();
        let sweep_call_end = sweep_call_start + "extrude(length = 10)".len();
        let part_call_start = source.find("subtract").unwrap();
        let part_call_end = part_call_start + "subtract(boxSolid, tools = [cutSolid])".len();
        let sweep_range = [sweep_call_start, sweep_call_end, 0].into();
        let composite_range = [part_call_start, part_call_end, 0].into();

        let cap_object = frontend
            .scene_graph
            .objects
            .iter()
            .find(|object| {
                matches!(
                    &object.kind,
                    ObjectKind::Cap(crate::frontend::api::Cap {
                        kind: crate::frontend::api::CapKind::End,
                        source,
                        ..
                    }) if source.solid.range == composite_range && source.sweep.range == sweep_range
                )
            })
            .expect("expected end cap object to trace through subtract and original extrude");

        let mut ast = frontend.program.ast.clone();
        let cap_expr = sketch_on_ast_expr(&mut ast, &frontend.scene_graph, &Plane::Object(cap_object.id)).unwrap();
        let cap_face_decl = ast::VariableDeclaration::new(
            ast::VariableDeclarator::new("capFace", cap_expr.clone()),
            ast::ItemVisibility::Default,
            ast::VariableKind::Const,
        );
        ast.body
            .push(ast::BodyItem::VariableDeclaration(Box::new(ast::Node::no_src(
                cap_face_decl,
            ))));
        let generated_source = source_from_ast(&ast);

        assert!(generated_source.contains("capFace = faceOf(part, face = END)"));
        assert!(!generated_source.contains("faceOf(boxSolid"));
        let ast::Expr::CallExpressionKw(call) = cap_expr else {
            panic!("expected faceOf call");
        };
        assert_eq!(call.callee.name.name, "faceOf");
        let ast::Expr::Name(solid_name) = call.unlabeled.as_ref().unwrap() else {
            panic!("expected solid name");
        };
        assert_eq!(solid_name.name.name, "part");
        let ast::Expr::Name(face_name) = &call.arguments[0].arg else {
            panic!("expected face name");
        };
        assert_eq!(face_name.name.name, "END");

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_on_plane_incremental() {
        let initial_source = "\
len = 2mm
cube = startSketchOn(XY)
  |> startProfile(at = [0, 0])
  |> line(end = [len, 0], tag = $side)
  |> line(end = [0, len])
  |> line(end = [-len, 0])
  |> line(end = [0, -len])
  |> close()
  |> extrude(length = len)

plane = planeOf(cube, face = side)
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        // Find the last plane since the first plane is the XY plane.
        let plane_object = frontend
            .scene_graph
            .objects
            .iter()
            .rev()
            .find(|object| matches!(&object.kind, ObjectKind::Plane(_)))
            .unwrap();
        let plane_id = plane_object.id;

        let sketch_args = SketchCtor {
            on: Plane::Object(plane_id),
        };
        let (src_delta, scene_delta, sketch_id) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();
        assert_eq!(
            src_delta.text.as_str(),
            "\
len = 2mm
cube = startSketchOn(XY)
  |> startProfile(at = [0, 0])
  |> line(end = [len, 0], tag = $side)
  |> line(end = [0, len])
  |> line(end = [-len, 0])
  |> line(end = [0, -len])
  |> close()
  |> extrude(length = len)

plane = planeOf(cube, face = side)
sketch001 = sketch(on = plane) {
}
"
        );
        assert_eq!(sketch_id, ObjectId(2));
        assert_eq!(scene_delta.new_objects, vec![ObjectId(2)]);
        let sketch_object = &scene_delta.new_graph.objects[2];
        assert_eq!(sketch_object.id, ObjectId(2));
        assert_eq!(
            sketch_object.kind,
            ObjectKind::Sketch(Sketch {
                args: SketchCtor {
                    on: Plane::Object(plane_id),
                },
                plane: plane_id,
                segments: vec![],
                constraints: vec![],
            })
        );
        assert_eq!(scene_delta.new_graph.objects.len(), 9);

        let plane_object = scene_delta.new_graph.objects.get(plane_id.0).unwrap();
        assert_eq!(plane_object.id, plane_id);
        assert_eq!(plane_object.kind, ObjectKind::Plane(Plane::Object(plane_id)));

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_uses_unique_variable_name() {
        let initial_source = "\
sketch1 = sketch(on = XY) {
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Yz),
        };
        let (src_delta, _, _) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();

        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch1 = sketch(on = XY) {
}
sketch001 = sketch(on = YZ) {
}
"
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_new_sketch_twice_using_same_plane() {
        let initial_source = "\
sketch1 = sketch(on = XY) {
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();

        let sketch_args = SketchCtor {
            on: Plane::Default(PlaneName::Xy),
        };
        let (src_delta, _, _) = frontend
            .new_sketch(&ctx, ProjectId(0), FileId(0), version, sketch_args)
            .await
            .unwrap();

        assert_eq!(
            src_delta.text.as_str(),
            "\
sketch1 = sketch(on = XY) {
}
sketch001 = sketch(on = XY) {
}
"
        );

        ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_sketch_mode_reuses_cached_on_expression() {
        let initial_source = "\
width = 2mm
sketch(on = offsetPlane(XY, offset = width)) {
  line1 = line(start = [var 0, var 0], end = [var 1mm, var 0])
  distance([line1.start, line1.end]) == width
}
";
        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let initial_object_count = frontend.scene_graph.objects.len();
        let sketch_id = find_first_sketch_object(&frontend.scene_graph)
            .expect("Expected sketch object to exist")
            .id;

        // Entering sketch mode should reuse cached `on` expression state
        // (offsetPlane result), not fail or create extra on-surface objects.
        let scene_delta = frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch_id)
            .await
            .unwrap();
        assert_eq!(scene_delta.new_graph.objects.len(), initial_object_count);

        // A follow-up sketch-mode execution should keep the same stable object
        // graph shape as well.
        let (_src_delta, scene_delta) = frontend.execute_mock(&mock_ctx, version, sketch_id).await.unwrap();
        assert_eq!(scene_delta.new_graph.objects.len(), initial_object_count);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_multiple_sketch_blocks() {
        let initial_source = "\
// Cube that requires the engine.
width = 2
sketch001 = startSketchOn(XY)
profile001 = startProfile(sketch001, at = [0, 0])
  |> yLine(length = width, tag = $seg1)
  |> xLine(length = width)
  |> yLine(length = -width)
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
  |> close()
extrude001 = extrude(profile001, length = width)

// Get a value that requires the engine.
x = segLen(seg1)

// Triangle with side length 2*x.
sketch(on = XY) {
  line1 = line(start = [var 0.14mm, var 0.86mm], end = [var 1.283mm, var -0.781mm])
  line2 = line(start = [var 1.283mm, var -0.781mm], end = [var -0.71mm, var -0.95mm])
  coincident([line1.end, line2.start])
  line3 = line(start = [var -0.71mm, var -0.95mm], end = [var 0.14mm, var 0.86mm])
  coincident([line2.end, line3.start])
  coincident([line3.end, line1.start])
  equalLength([line3, line1])
  equalLength([line1, line2])
  distance([line1.start, line1.end]) == 2*x
}

// Line segment with length x.
sketch2 = sketch(on = XY) {
  line1 = line(start = [var 0.14mm, var 0.86mm], end = [var 1.283mm, var -0.781mm])
  distance([line1.start, line1.end]) == x
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();

        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_objects = frontend
            .scene_graph
            .objects
            .iter()
            .filter(|obj| matches!(obj.kind, ObjectKind::Sketch(_)))
            .collect::<Vec<_>>();
        let sketch1_id = sketch_objects.first().unwrap().id;
        let sketch2_id = sketch_objects.get(1).unwrap().id;
        // First point in sketch1.
        let point1_id = ObjectId(sketch1_id.0 + 1);
        // First point in sketch2.
        let point2_id = ObjectId(sketch2_id.0 + 1);

        // Edit the first sketch. Objects before the sketch block should be
        // present from execution cache so that we can sketch on prior planes,
        // for example. Objects after the first sketch block should not be
        // present since those statements are skipped in sketch mode.
        //
        // - startSketchOn(XY) Plane 1
        // - sketch on=XY Plane 1
        // - Sketch block 16
        let scene_delta = frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch1_id)
            .await
            .unwrap();
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            18,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        // Edit a point in the first sketch.
        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Var(Number {
                    value: 1.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 2.0,
                    units: NumericSuffix::Mm,
                }),
            },
        };
        let segments = vec![ExistingSegmentCtor {
            id: point1_id,
            ctor: SegmentCtor::Point(point_ctor),
        }];
        let (src_delta, _) = frontend
            .edit_segments(&mock_ctx, version, sketch1_id, segments)
            .await
            .unwrap();
        // Only the first sketch block changes.
        assert_eq!(
            src_delta.text.as_str(),
            "\
// Cube that requires the engine.
width = 2
sketch001 = startSketchOn(XY)
profile001 = startProfile(sketch001, at = [0, 0])
  |> yLine(length = width, tag = $seg1)
  |> xLine(length = width)
  |> yLine(length = -width)
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
  |> close()
extrude001 = extrude(profile001, length = width)

// Get a value that requires the engine.
x = segLen(seg1)

// Triangle with side length 2*x.
sketch(on = XY) {
  line1 = line(start = [var 1mm, var 2mm], end = [var 2.32mm, var -1.78mm])
  line2 = line(start = [var 2.32mm, var -1.78mm], end = [var -1.61mm, var -1.03mm])
  coincident([line1.end, line2.start])
  line3 = line(start = [var -1.61mm, var -1.03mm], end = [var 1mm, var 2mm])
  coincident([line2.end, line3.start])
  coincident([line3.end, line1.start])
  equalLength([line3, line1])
  equalLength([line1, line2])
  distance([line1.start, line1.end]) == 2 * x
}

// Line segment with length x.
sketch2 = sketch(on = XY) {
  line1 = line(start = [var 0.14mm, var 0.86mm], end = [var 1.283mm, var -0.781mm])
  distance([line1.start, line1.end]) == x
}
"
        );
        let edited_sketch1_source = src_delta.text.clone();

        // Execute mock to simulate drag end.
        let (src_delta, _) = frontend.execute_mock(&mock_ctx, version, sketch1_id).await.unwrap();
        assert_eq!(src_delta.text, edited_sketch1_source);
        // Exit sketch. Objects from the entire program should be present.
        //
        // - startSketchOn(XY) Plane 1
        // - sketch on=XY Plane 1
        // - Sketch block 16
        // - sketch on=XY cached
        // - Sketch block 5
        let scene = frontend.exit_sketch(&ctx, version, sketch1_id).await.unwrap();
        assert_eq!(scene.objects.len(), 30, "{:#?}", scene.objects);

        // Edit the second sketch.
        //
        // - startSketchOn(XY) Plane 1
        // - sketch on=XY Plane 1
        // - Sketch block 16
        // - sketch on=XY cached
        // - Sketch block 5
        let scene_delta = frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch2_id)
            .await
            .unwrap();
        assert_eq!(
            scene_delta.new_graph.objects.len(),
            24,
            "{:#?}",
            scene_delta.new_graph.objects
        );

        // Edit a point in the second sketch.
        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Var(Number {
                    value: 3.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 4.0,
                    units: NumericSuffix::Mm,
                }),
            },
        };
        let segments = vec![ExistingSegmentCtor {
            id: point2_id,
            ctor: SegmentCtor::Point(point_ctor),
        }];
        let (src_delta, _) = frontend
            .edit_segments(&mock_ctx, version, sketch2_id, segments)
            .await
            .unwrap();
        // Only the second sketch block changes.
        assert_eq!(
            src_delta.text.as_str(),
            "\
// Cube that requires the engine.
width = 2
sketch001 = startSketchOn(XY)
profile001 = startProfile(sketch001, at = [0, 0])
  |> yLine(length = width, tag = $seg1)
  |> xLine(length = width)
  |> yLine(length = -width)
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
  |> close()
extrude001 = extrude(profile001, length = width)

// Get a value that requires the engine.
x = segLen(seg1)

// Triangle with side length 2*x.
sketch(on = XY) {
  line1 = line(start = [var 1mm, var 2mm], end = [var 2.32mm, var -1.78mm])
  line2 = line(start = [var 2.32mm, var -1.78mm], end = [var -1.61mm, var -1.03mm])
  coincident([line1.end, line2.start])
  line3 = line(start = [var -1.61mm, var -1.03mm], end = [var 1mm, var 2mm])
  coincident([line2.end, line3.start])
  coincident([line3.end, line1.start])
  equalLength([line3, line1])
  equalLength([line1, line2])
  distance([line1.start, line1.end]) == 2 * x
}

// Line segment with length x.
sketch2 = sketch(on = XY) {
  line1 = line(start = [var 3mm, var 4mm], end = [var 2.32mm, var 2.12mm])
  distance([line1.start, line1.end]) == x
}
"
        );
        let edited_sketch2_source = src_delta.text.clone();

        // Execute mock to simulate drag end.
        let (src_delta, _) = frontend.execute_mock(&mock_ctx, version, sketch2_id).await.unwrap();
        assert_eq!(src_delta.text, edited_sketch2_source);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_exit_sketch_without_changes_allows_entering_next_sketch() {
        clear_mem_cache().await;

        let source = r#"sketch001 = sketch(on = XZ) {
  circle1 = circle(start = [var -1.96mm, var 2.77mm], center = [var -2.69mm, var 3.44mm])
}
sketch002 = sketch(on = XY) {
  line1 = line(start = [var 0mm, var 0mm], end = [var 4.68mm, var 0mm])
  line2 = line(start = [var 4.68mm, var 0mm], end = [var 4.68mm, var 2.96mm])
  line3 = line(start = [var 4.68mm, var 2.96mm], end = [var 0mm, var 2.96mm])
  line4 = line(start = [var 0mm, var 2.96mm], end = [var 0mm, var 0mm])
  coincident([line1.end, line2.start])
  coincident([line2.end, line3.start])
  coincident([line3.end, line4.start])
  coincident([line4.end, line1.start])
  parallel([line2, line4])
  parallel([line3, line1])
  perpendicular([line1, line2])
  horizontal(line3)
  coincident([line1.start, ORIGIN])
}
"#;

        let program = Program::parse(source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();
        let ctx = ExecutorContext::new_with_engine(sync::Arc::new(EngineManager::new_mock()), Default::default());
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_objects = frontend
            .scene_graph
            .objects
            .iter()
            .filter(|object| matches!(object.kind, ObjectKind::Sketch(_)))
            .collect::<Vec<_>>();
        assert_eq!(sketch_objects.len(), 2, "{:#?}", frontend.scene_graph.objects);

        let sketch1_id = sketch_objects[0].id;
        let sketch2_id = sketch_objects[1].id;

        frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch1_id)
            .await
            .unwrap();
        frontend.exit_sketch(&ctx, version, sketch1_id).await.unwrap();

        let scene_delta = frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch2_id)
            .await
            .unwrap();
        assert_eq!(scene_delta.new_graph.sketch_mode, Some(sketch2_id));

        clear_mem_cache().await;
        ctx.close().await;
        mock_ctx.close().await;
    }

    // Regression tests: operations on source code with extra whitespace/newlines.
    // These test that NodePath-based lookups work correctly when source ranges
    // are shifted by extra whitespace that wouldn't be present after formatting.

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_after_settings_edit_sketch_add_point() {
        // Extra newlines after @settings line - this shifts all source ranges.
        let initial_source = "@settings(defaultLengthUnit = mm)



sketch001 = sketch(on = XY) {
  point(at = [1in, 2in])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        // Edit sketch should succeed despite extra newlines.
        frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch_id)
            .await
            .unwrap();

        // Add a new point to the sketch.
        let point_ctor = PointCtor {
            position: Point2d {
                x: Expr::Number(Number {
                    value: 5.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 6.0,
                    units: NumericSuffix::Mm,
                }),
            },
        };
        let segment = SegmentCtor::Point(point_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        // After adding a point, the source should be reformatted with standard whitespace.
        assert!(
            src_delta.text.contains("point(at = [5mm, 6mm])"),
            "Expected new point in source, got: {}",
            src_delta.text
        );
        assert!(!scene_delta.new_objects.is_empty());

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_ensure_control_point_spline_experimental_features_adds_allow_setting() {
        let initial_program = Program::parse("s = sketch(on = XY) {}\n").unwrap().0.unwrap();

        let updated_program = ensure_control_point_spline_experimental_features(&initial_program).unwrap();
        let meta_settings = updated_program.meta_settings().unwrap().unwrap();

        assert_eq!(meta_settings.experimental_features, WarningLevel::Allow);
        assert!(
            source_from_ast(&updated_program.ast).contains("@settings(experimentalFeatures = allow)"),
            "Expected experimental settings to be added to source"
        );
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_after_settings_add_line_to_empty_sketch() {
        // Extra newlines after @settings, with an empty sketch block.
        let initial_source = "@settings(defaultLengthUnit = mm)



s = sketch(on = XY) {}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Line(line_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert!(
            src_delta.text.contains("line(start = [0mm, 0mm], end = [10mm, 10mm])"),
            "Expected line in source, got: {}",
            src_delta.text
        );
        // Line creates start point, end point, and line segment.
        assert_eq!(scene_delta.new_objects.len(), 3);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_between_operations_edit_line() {
        // Extra newlines between @settings and sketch, and inside the sketch block.
        let initial_source = "@settings(defaultLengthUnit = mm)


sketch001 = sketch(on = XY) {

  line1 = line(start = [var 0mm, var 0mm], end = [var 10mm, var 10mm])

}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        // Extract segment IDs before edit_sketch borrows frontend mutably.
        let line_id = sketch
            .segments
            .iter()
            .copied()
            .find(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::Line(_)
                    }
                )
            })
            .expect("Expected a line segment in sketch");

        // Enter sketch edit mode.
        frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch_id)
            .await
            .unwrap();

        // Edit the line.
        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 1.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 2.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Var(Number {
                    value: 13.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 14.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segments = vec![ExistingSegmentCtor {
            id: line_id,
            ctor: SegmentCtor::Line(line_ctor),
        }];
        let (src_delta, _scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert!(
            src_delta
                .text
                .contains("line(start = [var 1mm, var 2mm], end = [var 13mm, var 14mm])"),
            "Expected edited line in source, got: {}",
            src_delta.text
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_delete_segment() {
        // Extra whitespace before and after the sketch block.
        let initial_source = "@settings(defaultLengthUnit = mm)



sketch001 = sketch(on = XY) {
  circle(start = [var 5mm, var 0mm], center = [var 0mm, var 0mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        // The sketch should have 3 segments: start point, center point, and the circle.
        assert_eq!(sketch.segments.len(), 3);
        let circle_id = sketch.segments[2];

        // Delete the circle despite extra newlines in original source.
        let (src_delta, scene_delta) = frontend
            .delete_objects(&mock_ctx, version, sketch_id, vec![], vec![circle_id])
            .await
            .unwrap();
        assert!(
            src_delta.text.contains("sketch(on = XY) {"),
            "Expected sketch block in source, got: {}",
            src_delta.text
        );
        let new_sketch_object = find_first_sketch_object(&scene_delta.new_graph).unwrap();
        let new_sketch = expect_sketch(new_sketch_object);
        assert_eq!(new_sketch.segments.len(), 0);

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_unformatted_source_add_arc() {
        // Source with inconsistent whitespace - tabs, extra spaces, multiple blank lines.
        let initial_source = "@settings(defaultLengthUnit = mm)




sketch001 = sketch(on = XY) {
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let arc_ctor = ArcCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 5.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 5.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Arc(arc_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert!(
            src_delta
                .text
                .contains("arc(start = [var 5mm, var 0mm], end = [var 0mm, var 5mm], center = [var 0mm, var 0mm])"),
            "Expected arc in source, got: {}",
            src_delta.text
        );
        assert!(!scene_delta.new_objects.is_empty());

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_add_circle() {
        // Extra blank lines between settings and sketch.
        let initial_source = "@settings(defaultLengthUnit = mm)



sketch001 = sketch(on = XY) {
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        let circle_ctor = CircleCtor {
            start: Point2d {
                x: Expr::Var(Number {
                    value: 5.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            center: Point2d {
                x: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Var(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Circle(circle_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert!(
            src_delta
                .text
                .contains("circle(start = [var 5mm, var 0mm], center = [var 0mm, var 0mm])"),
            "Expected circle in source, got: {}",
            src_delta.text
        );
        assert!(!scene_delta.new_objects.is_empty());

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_add_constraint() {
        // Extra newlines with a sketch containing two lines - add a coincident constraint.
        let initial_source = "@settings(defaultLengthUnit = mm)



sketch001 = sketch(on = XY) {
  line1 = line(start = [var 0mm, var 0mm], end = [var 10mm, var 10mm])
  line2 = line(start = [var 10mm, var 10mm], end = [var 20mm, var 0mm])
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);
        let project_id = ProjectId(0);
        let file_id = FileId(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;
        let sketch = expect_sketch(sketch_object);

        // Extract segment data before edit_sketch borrows frontend mutably.
        let line_ids: Vec<ObjectId> = sketch
            .segments
            .iter()
            .copied()
            .filter(|seg_id| {
                matches!(
                    &frontend.scene_graph.objects[seg_id.0].kind,
                    ObjectKind::Segment {
                        segment: Segment::Line(_)
                    }
                )
            })
            .collect();
        assert_eq!(line_ids.len(), 2, "Expected two line segments");

        let line1 = &frontend.scene_graph.objects[line_ids[0].0];
        let ObjectKind::Segment {
            segment: Segment::Line(line1_data),
        } = &line1.kind
        else {
            panic!("Expected line");
        };
        let line2 = &frontend.scene_graph.objects[line_ids[1].0];
        let ObjectKind::Segment {
            segment: Segment::Line(line2_data),
        } = &line2.kind
        else {
            panic!("Expected line");
        };

        // Build constraint before entering sketch mode.
        let constraint = Constraint::Coincident(Coincident {
            segments: vec![line1_data.end.into(), line2_data.start.into()],
        });

        // Enter sketch edit mode.
        frontend
            .edit_sketch(&mock_ctx, project_id, file_id, version, sketch_id)
            .await
            .unwrap();
        let (src_delta, _scene_delta) = frontend
            .add_constraint(&mock_ctx, version, sketch_id, constraint)
            .await
            .unwrap();
        assert!(
            src_delta.text.contains("coincident("),
            "Expected coincident constraint in source, got: {}",
            src_delta.text
        );

        ctx.close().await;
        mock_ctx.close().await;
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn test_extra_newlines_add_line_then_edit_line() {
        // Extra newlines after @settings - add a line, then edit it.
        let initial_source = "@settings(defaultLengthUnit = mm)



sketch001 = sketch(on = XY) {
}
";

        let program = Program::parse(initial_source).unwrap().0.unwrap();
        let mut frontend = FrontendState::new();

        let ctx = ExecutorContext::new_with_default_client().await.unwrap();
        let mock_ctx = ExecutorContext::new_mock(None).await;
        let version = Version(0);

        frontend.hack_set_program(&ctx, program).await.unwrap();
        let sketch_object = find_first_sketch_object(&frontend.scene_graph).unwrap();
        let sketch_id = sketch_object.id;

        // Add a line.
        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 0.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 10.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segment = SegmentCtor::Line(line_ctor);
        let (src_delta, scene_delta) = frontend
            .add_segment(&mock_ctx, version, sketch_id, segment, None)
            .await
            .unwrap();
        assert!(
            src_delta.text.contains("line(start = [0mm, 0mm], end = [10mm, 10mm])"),
            "Expected line in source after add, got: {}",
            src_delta.text
        );
        // Line creates start point, end point, and line segment.
        let line_id = *scene_delta.new_objects.last().unwrap();

        // Edit the line.
        let line_ctor = LineCtor {
            start: Point2d {
                x: Expr::Number(Number {
                    value: 1.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 2.0,
                    units: NumericSuffix::Mm,
                }),
            },
            end: Point2d {
                x: Expr::Number(Number {
                    value: 13.0,
                    units: NumericSuffix::Mm,
                }),
                y: Expr::Number(Number {
                    value: 14.0,
                    units: NumericSuffix::Mm,
                }),
            },
            construction: None,
        };
        let segments = vec![ExistingSegmentCtor {
            id: line_id,
            ctor: SegmentCtor::Line(line_ctor),
        }];
        let (src_delta, scene_delta) = frontend
            .edit_segments(&mock_ctx, version, sketch_id, segments)
            .await
            .unwrap();
        assert!(
            src_delta.text.contains("line(start = [1mm, 2mm], end = [13mm, 14mm])"),
            "Expected edited line in source, got: {}",
            src_delta.text
        );
        assert_eq!(scene_delta.new_objects, vec![]);

        ctx.close().await;
        mock_ctx.close().await;
    }
}
