@@ -421,6 +421,68 @@ impl ShapeState {
421421 ( point. as_handle ( ) . is_some ( ) && self . ignore_handles ) || ( point. as_anchor ( ) . is_some ( ) && self . ignore_anchors )
422422 }
423423
424+ /// Applies a dummy vector modification to the layer. In the case where a group containing some vector data is selected, this triggers the creation of a «Flatten Path» node.
425+ fn add_dummy_modification_to_trigger_graph_reorganization ( layer : LayerNodeIdentifier , start_point : PointId , _end_point : PointId , responses : & mut VecDeque < Message > ) {
426+ // Apply a zero-delta to one of the points to trigger reorganization
427+ let dummy_modification = VectorModificationType :: ApplyPointDelta {
428+ point : start_point,
429+ delta : DVec2 :: ZERO ,
430+ } ;
431+ responses. add ( GraphOperationMessage :: Vector {
432+ layer,
433+ modification_type : dummy_modification,
434+ } ) ;
435+ responses. add ( NodeGraphMessage :: RunDocumentGraph ) ;
436+ }
437+
438+ fn defer_connect_points_by_position (
439+ document : & DocumentMessageHandler ,
440+ layer1 : LayerNodeIdentifier ,
441+ start_point : PointId ,
442+ layer2 : LayerNodeIdentifier ,
443+ end_point : PointId ,
444+ target_layer : LayerNodeIdentifier ,
445+ responses : & mut VecDeque < Message > ,
446+ ) {
447+ // Get the local positions of the selected points
448+ let start_local_pos = document. network_interface . compute_modified_vector ( layer1) . and_then ( |v| v. point_domain . position_from_id ( start_point) ) ;
449+ let end_local_pos = document. network_interface . compute_modified_vector ( layer2) . and_then ( |v| v. point_domain . position_from_id ( end_point) ) ;
450+
451+ // Transform to document/world space
452+ let start_transform = document. metadata ( ) . transform_to_document ( layer1) ;
453+ let end_transform = document. metadata ( ) . transform_to_document ( layer2) ;
454+
455+ let ( Some ( start_local) , Some ( end_local) ) = ( start_local_pos, end_local_pos) else {
456+ warn ! ( "Unable to resolve point ids for joining" ) ;
457+ return ;
458+ } ;
459+ // Transform positions to document/world space
460+ // These positions are stable (won't change during reorganization)
461+ let start_pos = start_transform. transform_point2 ( start_local) ;
462+ let end_pos = end_transform. transform_point2 ( end_local) ;
463+
464+ // Defer position-based connection to run after reorganization completes
465+ // By then, PointIds will be stable with their new remapped values
466+ responses. add ( DeferMessage :: AfterGraphRun {
467+ messages : vec ! [
468+ ToolMessage :: Path ( PathToolMessage :: ConnectPointsByPosition {
469+ layer: target_layer,
470+ start_position: start_pos,
471+ end_position: end_pos,
472+ } )
473+ . into( ) ,
474+ ] ,
475+ } ) ;
476+ }
477+
478+ fn handle_grouped_transform_close_path ( document : & DocumentMessageHandler , layer : LayerNodeIdentifier , start_point : PointId , end_point : PointId , responses : & mut VecDeque < Message > ) {
479+ // This zero-delta modification triggers point domain reorganization
480+ Self :: add_dummy_modification_to_trigger_graph_reorganization ( layer, start_point, end_point, responses) ;
481+
482+ // Use the helper to defer the connection until after reorganization
483+ Self :: defer_connect_points_by_position ( document, layer, start_point, layer, end_point, layer, responses) ;
484+ }
485+
424486 pub fn close_selected_path ( & self , document : & DocumentMessageHandler , responses : & mut VecDeque < Message > ) {
425487 // First collect all selected anchor points across all layers
426488 let all_selected_points: Vec < ( LayerNodeIdentifier , PointId ) > = self
@@ -447,28 +509,34 @@ impl ShapeState {
447509 let ( layer2, end_point) = all_selected_points[ 1 ] ;
448510
449511 if layer1 == layer2 {
512+ // Same layer case
450513 if start_point == end_point {
451514 return ;
452515 }
453516
454- let segment_id = SegmentId :: generate ( ) ;
455- let modification_type = VectorModificationType :: InsertSegment {
456- id : segment_id,
457- points : [ end_point, start_point] ,
458- handles : [ None , None ] ,
459- } ;
460- responses. add ( GraphOperationMessage :: Vector { layer : layer1, modification_type } ) ;
517+ // Check if this layer has multiple children (is a merged/grouped layer created with Cmd+G)
518+ let num_children = layer1. children ( document. metadata ( ) ) . count ( ) ;
519+ let is_grouped = num_children > 1 ;
520+
521+ if is_grouped {
522+ // Grouped/merged layer: use helper function to handle reorganization
523+ Self :: handle_grouped_transform_close_path ( document, layer1, start_point, end_point, responses) ;
524+ } else {
525+ // Single segment: PointIDs are stable, use immediate insertion
526+ let segment_id = SegmentId :: generate ( ) ;
527+ let modification_type = VectorModificationType :: InsertSegment {
528+ id : segment_id,
529+ points : [ end_point, start_point] ,
530+ handles : [ None , None ] ,
531+ } ;
532+ responses. add ( GraphOperationMessage :: Vector { layer : layer1, modification_type } ) ;
533+ }
461534 } else {
462- // Merge the layers
535+ // Different layers: merge first, then create segment
463536 merge_layers ( document, layer1, layer2, responses) ;
464- // Create segment between the two points
465- let segment_id = SegmentId :: generate ( ) ;
466- let modification_type = VectorModificationType :: InsertSegment {
467- id : segment_id,
468- points : [ end_point, start_point] ,
469- handles : [ None , None ] ,
470- } ;
471- responses. add ( GraphOperationMessage :: Vector { layer : layer1, modification_type } ) ;
537+
538+ // Use the helper to defer the connection until after reorganization
539+ Self :: defer_connect_points_by_position ( document, layer1, start_point, layer2, end_point, layer1, responses) ;
472540 }
473541 return ;
474542 }
0 commit comments