From 39848747282470d7f2fc2a7c8a0be01e0fdd376e Mon Sep 17 00:00:00 2001 From: ruchirguitar <113586074+ruchirguitar@users.noreply.github.com> Date: Sun, 8 Nov 2026 19:35:48 +0530 Subject: [PATCH] M2.1: avoid discarded path copies during deletion (#3380) --- crates/engine/src/cmd/edit.rs | 20 +++++++----- crates/engine/src/cmd/path.rs | 58 ++++++++++++++++++++++++++--------- docs/gaps.md | 6 ++-- docs/roadmap.md | 6 ++-- docs/target-app-parity.md | 6 ++-- docs/ui-parity.md | 6 ++-- 6 files changed, 64 insertions(+), 38 deletions(-) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 9dc17b7b6..c6afc9eaf 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -764,6 +764,10 @@ fn cut(s: &mut Session, p: &Value) -> Result { /// Remove the geometry copied by a direct-selection Cut, including selected guides in one edit. /// Select every surviving piece while retaining intentional compound-path structure. +/// `roots` contains the copied targets: whole-object roots are removed in full, while partial paths +/// lose only the copied geometry. An exhausted partial path is removed, along with its compound +/// parent if now empty; layers remain. Returns JSON `null` on success, replacing direct-selection +/// references with surviving object IDs. fn cut_direct_selection(s: &mut Session, roots: &[NodeId]) -> Result { let st = s.doc()?; let sel = st.selection.clone(); @@ -780,20 +784,22 @@ fn cut_direct_selection(s: &mut Session, roots: &[NodeId]) -> Result { d.remove(*id)?; continue; } - let Some(path) = node.path_data() else { + if node.path_data().is_none() { d.remove(*id)?; continue; - }; + } + let original = super::path::deletion_source(d, *id)?; let seg_refs = sel.segments.get(id); let anchor_refs = sel.anchors.get(id); let mut subpaths = Vec::new(); - for (si, sp) in path.subpaths.iter().enumerate() { - let cuts = super::clipboard::subpath_selected_segments(sp, si, seg_refs, anchor_refs); + let owned = std::mem::take(&mut super::path::path_mut(d, *id)?.subpaths); + for (si, sp) in owned.into_iter().enumerate() { + let cuts = super::clipboard::subpath_selected_segments(&sp, si, seg_refs, anchor_refs); if cuts.is_empty() { - subpaths.push(sp.clone()); + subpaths.push(sp); } else { - subpaths.extend(super::path::without_segments(sp.clone(), &cuts)); + subpaths.extend(super::path::without_segments(sp, &cuts)); } } subpaths.retain(|sp| sp.anchors.len() >= 2); @@ -806,7 +812,7 @@ fn cut_direct_selection(s: &mut Session, roots: &[NodeId]) -> Result { d.remove(p)?; } } else { - survivors.extend(super::path::replace_deleted_pieces(d, &mut pieces, *id, subpaths)?); + survivors.extend(super::path::replace_deleted_pieces(d, &mut pieces, original, subpaths)?); } } pieces.finish(d)?; diff --git a/crates/engine/src/cmd/path.rs b/crates/engine/src/cmd/path.rs index b8b8a6914..2c15f7cdd 100644 --- a/crates/engine/src/cmd/path.rs +++ b/crates/engine/src/cmd/path.rs @@ -1,6 +1,7 @@ //! Path editing commands (used by the Pen and Direct Selection tools and Object → Path). use std::collections::BTreeSet; +use std::sync::Arc; use serde_json::{Value, json}; use vectorcraft_doc::{Document, Node, NodeId, NodeKind, Scaling}; @@ -1191,6 +1192,9 @@ fn convert_anchors(s: &mut Session, p: &Value) -> Result { /// Delete direct-selected anchors or segments and whole selected objects in one edit. /// Leave surviving pieces selected, splitting ordinary contours into independent sibling objects. +/// `p.guides: true` also removes the selected ruler guides in that edit; otherwise guides remain. +/// Layers are retained, and an object removed with a selected ancestor is not removed twice. +/// Returns JSON `null` on success; selection is replaced by the surviving object IDs. pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result { // `guides: true` (Cut, #3226): the selected ruler guides go in the same undo step. let with_guides = switch_or(p, "guides", false, "path.deleteAnchors")?; @@ -1222,13 +1226,14 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result { if d.node(*id).is_none() { continue; } + let original = deletion_source(d, *id)?; let subpaths: Vec = { - let path = d.node(*id).and_then(|n| n.path_data()).ok_or(EngineError::NoNode(*id))?; + let path = path_mut(d, *id)?; + let subpaths = std::mem::take(&mut path.subpaths); if let Some(refs) = segments.get(id) { // A segment pick cuts its connection, keeping every neighboring cubic. - path.subpaths - .iter() - .cloned() + subpaths + .into_iter() .enumerate() .flat_map(|(si, sp)| { let cuts = refs.iter().filter_map(|&(s, seg)| (s == si).then_some(seg)).collect(); @@ -1239,9 +1244,8 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result { } else { // Remove every selected anchor against the original indexes, keeping surviving // stretches separate rather than joining across deleted anchors. - path.subpaths - .iter() - .cloned() + subpaths + .into_iter() .enumerate() .flat_map(|(si, sp)| { let gone = set.iter().filter_map(|&(s, ai)| (s == si).then_some(ai)).collect(); @@ -1255,7 +1259,7 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result { emptied.put(d, *id, vec![])?; } else { // Every remaining piece is selected whole, so the next Delete removes them. - survivors.extend(replace_deleted_pieces(d, &mut emptied, *id, subpaths)?); + survivors.extend(replace_deleted_pieces(d, &mut emptied, original, subpaths)?); } } emptied.finish(d)?; @@ -1265,15 +1269,39 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result { ok() } -/// Deletion splits an ordinary contour into sibling objects. Existing multi-subpath paths and -/// explicit compound members keep their compound semantics, even when a member has one contour. -/// Separate objects retain the gradient placement of the original path. -pub(super) fn replace_deleted_pieces(d: &mut Document, beside: &mut Beside, id: NodeId, subpaths: Vec) -> Result> { - let single = d.node(id).and_then(|n| n.path_data()).is_some_and(|p| p.subpaths.len() == 1); +/// Retain path `id` from `d` without copying its geometry before deletion takes its subpaths. +/// The shared snapshot supplies the original path classification and, only if needed, gradient +/// bounds after the working node's geometry has moved. Returns `NoNode` for a missing/non-path ID. +pub(super) fn deletion_source(d: &Document, id: NodeId) -> Result> { + let (parent, index, _) = d.position(id).ok_or(EngineError::NoNode(id))?; + // Verify the cached position still names this path before retaining the shared node. + d.children(parent) + .and_then(|children| children.get(index)) + .filter(|n| n.id == id && n.path_data().is_some()) + .cloned() + .ok_or(EngineError::NoNode(id)) +} + +/// Replace the deleted path in `d` with the nonempty surviving runs in `subpaths`. +/// `original` must be retained by [`deletion_source`] before taking the working node's geometry. +/// An ordinary single-contour source becomes siblings; multi-subpath paths and compound members +/// retain their node. Only actual splits pin unplaced gradients to `original`'s bounds; already +/// placed gradients and connected remainders retain their placement behavior. +/// +/// Returns the surviving IDs in `subpaths` order (the first keeps the source ID). The original +/// node's geometry is updated immediately. Replacements are handed to `beside`, which may defer +/// insertion; the caller must finish that [`Beside`] before using new IDs in `d`. +/// Missing/invalid paths return an error. +pub(super) fn replace_deleted_pieces(d: &mut Document, beside: &mut Beside, original: Arc, subpaths: Vec) -> Result> { + let id = original.id; + let single = original.path_data().is_some_and(|p| p.subpaths.len() == 1); if single && compound_or_self(d, id) == id { // Pin only actual splits: each new object's smaller bounds would otherwise refit its gradients. - if subpaths.len() > 1 { - d.node_mut(id).ok_or(EngineError::NoNode(id))?.pin_gradients(); + if subpaths.len() > 1 + && original.appearance.has_unplaced_gradient() + && let Some(bounds) = original.geometric_bounds() + { + d.node_mut(id).ok_or(EngineError::NoNode(id))?.appearance.pin_gradients(bounds); } super::draw2::replace_with_pieces(d, beside, id, subpaths.into_iter().map(PathData::single).collect()) } else { diff --git a/docs/gaps.md b/docs/gaps.md index 097cc6df7..772dc9616 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,7 +1,6 @@ # Gaps -> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged. -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -38,8 +37,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| -| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. | -| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. | +| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. | | 2026-11-05 | minor | G15: long open strokes with an undashed width profile are outlined near the view while keeping profile positions on the full path; a 200,000-segment outline took 59.95 ms before and 8.44 ms after in local median runs. Closed and dashed profile strokes remain whole; estimate unchanged. | diff --git a/docs/roadmap.md b/docs/roadmap.md index 9e7c2428c..c7dda608e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,7 +1,6 @@ # Roadmap: milestones and what's next -> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged. -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -37,8 +36,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i | Date | Change | Summary | |---|---|---| -| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. | -| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. | +| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. | | 2026-11-05 | minor | Effect dialogs: a colour swatch beside a hex colour field follows typed values, and a double-click opens the Color Picker over the dialog, whose OK fills the field (#5365); scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 8d71394fb..6fb7c44ac 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,7 +1,6 @@ # Parity with Adobe Illustrator -> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged. -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -32,8 +31,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| -| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. | -| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. | +| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | The canvas keeps selection outlines, anchors and handles between frames, so an idle frame with a large selection redraws them as one mesh; scores and estimates unchanged | | 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 46925cf11..4092c6310 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,7 +1,6 @@ # UI and interaction parity -> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged. -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence @@ -40,8 +39,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| -| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. | -| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. | +| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. | | 2026-10-30 | minor | Home screen: welcome, New / Open and Community are centred and the large Discord button uses the theme accent, part of #4684; scores and estimates unchanged (estimated) | | 2026-11-06 | minor | Snap to Grid: a straight segment dragged with Direct Selection lands its anchors on the grid, not the pressed point (#3835); scores and estimates unchanged |