Skip to content

fix(edit): preserve same-feature targets when snapping vertices - #818

Merged
giswqs merged 2 commits into
opengeos:mainfrom
yongyin-leon:codex/vertex-snap-identity
Oct 25, 2026
Merged

giswqs merged 2 commits into
opengeos:mainfrom
yongyin-leon:codex/vertex-snap-identity

Conversation

@yongyin-leon

@yongyin-leon yongyin-leon commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Dragging a vertex currently excludes its entire feature from snapping. For example, an endpoint in one MultiLineString part cannot snap to another part of the same feature. Exclude the moving source VertexId and its incident edges instead, retaining other eligible nodes and edges in that feature.

  • Resolve node identities before projection, so world-edge cuts cannot renumber the exclusion or introduce synthetic node targets. Project surviving source edges with the existing cut rules, without connecting opposite sides of the map.
  • Preserve polygon closing-point aliases and the independent endpoints of a closed LineString. Keep midpoint insertion and Point/MultiPoint moves on whole-feature exclusion.
  • Retain the existing snapping API, candidate ordering, point-before-edge priority, switches, tolerance and layer visibility/scale rules. No command parameters, data model, dependencies or project format changes.

Testing

Previously completed on the exact frozen patch; these results were reused for submission:

Platform Engine identity regressions UI gesture regressions Existing edit/snapping regression
Windows x86_64 9 passed 5 passed 1 passed
macOS ARM64 9 passed 5 passed 1 passed
Linux ARM64 9 passed 5 passed 1 passed

The existing regression is tests::modify_tools_snapping_topology_relationships_and_elevation. The new cases cover coincident but distinct nodes, parts/holes, polygon closure versus closed-line endpoints, projection cuts, switches/tolerance/priority, invalid addresses and read-only state. UI cases cover rotated dragging, read-only previews, single-step commit with undo/redo, Escape, insertion, disabled snapping and read-only editing.

Also completed on Windows: affected engine/UI libraries and integration targets passed Clippy with -D warnings; cargo check --target wasm32-unknown-unknown -p rustgis-web passed. Three headless UI screenshots were inspected for snapped preview, committed geometry and insertion preview. Rustdoc, including private helpers, built with 11 pre-existing link warnings; each warning's source line matches the upstream baseline, and the new docs introduced none.

Full workspace CI and Linux x86_64 runtime are not claimed. The Windows full-command hostile-parameter traversal was interrupted and is not counted as passed; only the named targeted regression above is counted.

Follow-up CI fix: the existing command-parameter source audit attempted to read the new src/cmd/design/ helper directory as a file. It now selects regular top-level .rs files before reading them, preserving all existing key checks and the minimum-command-count assertion. The affected audit test passed locally with an exact filter; Rust formatting and diff checks passed. The five node-snapping source/test files are unchanged, so the three-platform results above remain applicable.

Related issue

Follow-up to the Edit Vertices tool from #143 / #428; this fixes same-feature snapping within the existing workflow.

Summary by CodeRabbit

  • New Features
    • Vertex dragging can snap to other eligible vertices and edges on the same line or polygon, while excluding the dragged vertex and its incident edges.
  • Bug Fixes
    • Snapping now handles polygon closing vertices, holes, and projected map edges more reliably. Invalid vertex addresses no longer interfere with snapping.
    • Vertex edits can be cancelled with Escape, and previews remain read-only until committed. Undo and redo treat an edit as a single step.
    • Vertices remain freely draggable when snapping is disabled or the pointer is outside the snapping tolerance.

@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 27f4eb4e-5c9f-4282-beff-5cab6df8470b
Commits

Reviewing files that changed from the base of the PR and between 2931a71 and 34f056f.

Files selected for processing: 1
  • crates/engine/src/tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Walkthrough

The engine adds vertex-specific snapping for line and polygon geometry. Existing vertex dragging uses this behavior, while insertions and point or multipoint moves retain whole-feature exclusion. Engine and UI integration tests cover snapping candidates and vertex-edit interactions. The command-key scanner now checks top-level Rust files only.

Changes: Vertex-specific snapping

Layer / File(s) Summary
Snapping API and source dispatch
crates/engine/src/cmd/design.rs
snap_vertex_except validates a source vertex address and dispatches its feature to vertex-specific candidate processing. snap_except retains whole-feature exclusion.
Vertex and edge candidates
crates/engine/src/cmd/design/snap_vertex.rs, crates/engine/tests/vertex_snap_identity.rs
Candidate processing excludes the addressed vertex and its incident edges. Tests cover geometry identity, projection, candidate priority, tolerance, and invalid addresses.
Vertex-drag integration
crates/ui-egui/src/vertexedit.rs, crates/ui-egui/tests/vertex_snapping.rs
Existing vertex drags use vertex-specific exclusion; insertions retain whole-feature exclusion. UI tests cover previews, commits, cancellation, and editing state.

Command-key scan

Layer / File(s) Summary
Scanner file selection
crates/engine/src/tests.rs
The command-key test scanner skips directories and files without the .rs extension. Comments specify that the scanner checks declared parameter keys for each discovered command.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant VertexEditor
  participant DesignSnap
  participant ProjectedCache
  participant SnapVertex
  VertexEditor->>DesignSnap: request snapping with source vertex address
  DesignSnap->>ProjectedCache: iterate projected target features
  DesignSnap->>SnapVertex: consider addressed source geometry
  SnapVertex-->>DesignSnap: return eligible candidates
  DesignSnap-->>VertexEditor: return selected snap coordinate and kind
Loading

Possibly related issues

  • opengeos/RustGIS#748: The issue requests interactive editing tests, including vertex editing. This PR adds UI integration tests for vertex dragging and snapping.

Suggested reviewers: giswqs

Merge Risk: Minimal · up to 34f05

No concrete merge-blocking risk remains in the reviewed changes.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title "fix(edit): preserve same-feature targets when snapping vertices" matches the main change. The PR changes vertex-drag snapping to exclude only the moving VertexId and its incident edges, not…
Description check Passed The description follows the template. It has a Summary that states what changes and why, including the same-feature snapping problem and the exclusion rules. It has a Testing section that lists platfo…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 5 files. (1 skipped: 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "fix(edit): preserve same-feature targets when snapping vertices" matches the main change. The PR changes vertex-drag snapping to exclude only the moving VertexId and its incident edges, not the whole feature, so other nodes and edges in the same feature stay snap targets. The title uses a conventional-commit style and is specific. It does not name the engine and UI files, which is expected.

Full details: Description check

Explanation

The description follows the template. It has a Summary that states what changes and why, including the same-feature snapping problem and the exclusion rules. It has a Testing section that lists platform results, named regression tests, clippy, wasm check, screenshot inspection, and known gaps such as the unclaimed full workspace CI and Linux x86_64 runtime. It has a Related issue section that refers to #143 and #428. The description is detailed, and the Related issue wording is a follow-up reference rather than a Closes/Part of line, which is a minor deviation from the template example.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 5 files. (1 skipped: 1 too large.)

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit nudges nodes in line,
And leaves the other paths to shine.
The edges near its chosen place
Step back and let new targets grace.
One careful hop, one undo too,
Then carrots wait when edits are through.

Comment @coderabbitai help to get the list of available commands.

let closed = polygon && path.len() > 1 && path.first() == path.last();
let count = path.len().saturating_sub(usize::from(closed));
let skip = |index| {
address.part == excluded.part && address.ring == excluded.ring && excluded.index == if closed && index == count { 0 } else { index }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low-confidence edge case: skip normalises the path index count to 0, but excluded.index is compared as given. If a caller addresses a polygon's stored closing copy (index == count) and Geometry::vertex/move_vertex accept that address as an alias of node 0, then excluded.index == count never matches. Nothing is excluded and the moving node snaps to itself. Normalise excluded the same way, for example by mapping excluded.index == count to 0 for a closed polygon ring. The tests only cover index 0 for polygons, so please add a case for the closing index. Ignore this if vertex() rejects that index.

@github-actions

Copy link
Copy Markdown

Code review

The PR replaces whole-feature exclusion with exclusion of the moved node and its incident edges when a line or polygon vertex is dragged. I reviewed the diff and the changed files without building or running anything, so the new tests are unrun. I posted one inline comment.

Bugs

  • Low confidence: snap_vertex.rs (consider_path, skip) maps the path index count to 0 for a closed polygon. It does not do the same for excluded.index. If Geometry::vertex accepts a polygon's stored closing copy as an address, an excluded node at that index would never match. The moving node could then snap to itself. The tests only cover index 0 for polygons. This is the inline comment.

Security

  • Nothing found. Invalid addresses are filtered before use, the new code uses get() instead of indexing, and no unwrap or panic appears in non-test code.

Performance

  • Nothing significant. projected_in runs once and then projected_cache() is iterated. The source-geometry pass only runs for the single addressed row. Edge projection through flat.project for custom projections allocates a small Geometry::Line per edge on every drag frame. This is acceptable, but it could show up on very large single features.

Quality

  • The added doc line on snap_except ("Use [snap_vertex_except] when…") is very long, and the signature of snap_vertex_except is one long line. cargo fmt may or may not accept them, so check with cargo xtask ci.
  • snap_vertex.rs re-implements the point, edge and end visiting order of snap_excluding for the addressed row. The two paths could drift apart if snapping policy changes later. The doc comment acknowledges the intent, and the tests pin the shared ordering.
  • Point and multipoint targets inside the addressed feature are still excluded as a whole. This is documented, but it is a remaining inconsistency.

AGENTS.md

  • No unwrap, expect or panic in non-test code, and no unsafe. The tests use unwrap and expect, which is allowed.
  • The change adds no wasm-incompatible APIs, no new dependencies and no assets. There are no new user-visible strings, so tl! is not needed.
  • The PR adds regression tests for the engine and the UI, including invalid-address handling.
  • Confidence that the guidelines are met: moderate. I did not run cargo xtask ci.

Change size

Compared: the whole PR against main (merge base fa96baa to PR head 2931a71).

Metric Value
Files changed 5 (3 added, 2 modified, 0 removed, 0 renamed)
Size before 70.7 KB
Size after 95.0 KB
Total size change +24.3 KB
File Before After Change
crates/engine/tests/vertex_snap_identity.rs 0 B 11.2 KB +11.2 KB
crates/ui-egui/tests/vertex_snapping.rs 0 B 6.8 KB +6.8 KB
crates/engine/src/cmd/design/snap_vertex.rs 0 B 4.3 KB +4.3 KB
crates/engine/src/cmd/design.rs 58.0 KB 59.7 KB +1.7 KB
crates/ui-egui/src/vertexedit.rs 12.7 KB 12.9 KB +206 B

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

📖 Docs preview

Item Value
Preview https://preview.rustgis.app/pr-818/
Web app https://preview.rustgis.app/pr-818/app/
Commit 34f056f

GitHub Pages takes a minute or two to publish a new commit.

@giswqs

giswqs commented Oct 24, 2026

Copy link
Copy Markdown
Member

@yongyin-leon Can you fix the ci errir or enable the option for maintainer edit?

@github-actions

Copy link
Copy Markdown

Code review

PR #818 makes vertex-drag snapping skip only the node being moved and its incident edges. It no longer skips the whole feature. I found no blocking issues. I could only read the code, so I did not build it or run the tests.

Bugs

  • No correctness bugs found. I traced the closed-polygon aliasing (index == count maps to node 0). I also traced edge exclusion through windows(2) with skip(i) || skip(i+1), and the endpoint, vertex and edge visit order. All match the documented intent.
  • Minor, low confidence (~25%): for a one-point line, consider_path visits [0, len-1] as [0, 0], so the "end" candidate is offered twice. The result is unchanged because the distances are equal and the first candidate wins. This only costs an extra call.
  • Low confidence (~30%): snap_excluding assumes projected_cache() rows line up one-to-one with dataset.features. snap_vertex::consider looks the source feature up by the same row index. This holds for ordinary feature layers, as the tests exercise. I could not confirm it for every layer type, such as filtered or virtual layers. The .get(row) lookup fails safe and falls through to continue, so that row is dropped from the snap targets instead of causing a panic.

Security

  • None. The new code uses only get() and checked projection (forward_checked). Invalid VertexId or row addresses are filtered out up front by snap_vertex_except, and the tests cover them.

Performance

  • No concerns. Source-path processing runs only for the single addressed row, and the extent pre-check is kept. consider_edge builds a small Geometry::Line only when a custom projector has edge cuts.

Quality

  • The snap_vertex.rs split is clean, and the SnapView struct keeps the argument lists short.
  • Fixing keys_read_by_commands to skip directories and non-.rs files is needed, because design/ is now a directory. It also lets the PR meet the "keep hostile_params_never_panic and the audit tests green" rule.
  • The snap_except doc comment now carries a line about snap_vertex_except inserted into its doc block. It is slightly awkward to read, but harmless.
  • The tests are thorough: they cover polygon closing, holes, closed lines, rotation, world-edge cuts, undo, and Escape.

AGENTS.md

  • No panics in non-test code: compliant. The unwrap and expect calls appear only in tests.
  • Wasm: no std::time or filesystem use added.
  • Layering: the engine change does not pull in egui. The UI calls the new engine function, which fits the layer rules.
  • Translations and assets: no user-visible strings or assets were added.

Change size

Compared: the whole PR against main (merge base fa96baa to PR head 34f056f)

Metric Value
Files changed 6 (3 added, 3 modified, 0 removed, 0 renamed)
Size before 959.4 KB
Size after 984.0 KB
Total size change +24.6 KB
File Before After Change
crates/engine/tests/vertex_snap_identity.rs 0 B 11.2 KB +11.2 KB
crates/ui-egui/tests/vertex_snapping.rs 0 B 6.8 KB +6.8 KB
crates/engine/src/cmd/design/snap_vertex.rs 0 B 4.3 KB +4.3 KB
crates/engine/src/cmd/design.rs 58.0 KB 59.7 KB +1.7 KB
crates/engine/src/tests.rs 888.7 KB 889.0 KB +309 B
crates/ui-egui/src/vertexedit.rs 12.7 KB 12.9 KB +206 B

@giswqs
giswqs merged commit c9efee9 into opengeos:main Oct 25, 2026
20 checks passed

Copy link
Copy Markdown
Contributor Author

Thanks for flagging this! Fixed in 34f056f: the command-parameter audit was trying to read the new helper directory as a file. All CI checks are now passing. Thanks for reviewing and merging!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants