Skip to content

fix(editor): Preserve closed-path Z command when moving a path (#1093) - #1100

Merged
jfhenon merged 2 commits into
masterfrom
fix/issue-1093-path-move-close-command
Jul 29, 2026
Merged

fix(editor): Preserve closed-path Z command when moving a path (#1093)#1100
jfhenon merged 2 commits into
masterfrom
fix/issue-1093-path-move-close-command

Conversation

@jfhenon

@jfhenon jfhenon commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes Path tool issues #1093: moving a closed shape drawn with the path tool caused it to snap back to its original position (Firefox specifically), with a console error like Cannot destructure property 'type' of undefined.
  • Root cause: remapElement()'s native getPathData() branch in packages/svgcanvas/core/coords.js maps each path segment's letter (M, L, Z, ...) to a numeric type via pathMap.indexOf(t). pathMap only lists the lowercase 'z' for the close-path command, but paths serialize the close command as uppercase 'Z'. The lookup returned -1, leaving a hole in the segment array; a later loop destructured type off that hole and threw, aborting the move before the new geometry was committed. This only reproduces in browsers with native getPathData/setPathData support (e.g. Firefox) — Chrome falls back to the legacy pathSegList API, which uses numeric constants instead of letters and isn't affected, matching what's reported in the issue thread.
  • Also fixed a related latent bug in the same function: the newPathData builder had no case for the close-path segment type, so even after the crash is fixed, calling setPathData() would silently drop the Z and reopen the shape.

Test plan

  • Added a regression test in tests/unit/coords.test.js that stubs getPathData/setPathData on a closed path (simulating Firefox) and verifies the move no longer throws and the closed shape (and its Z command) is preserved.
  • Verified the new test reproduces the exact reported error when the fix is reverted.
  • npx vitest run tests/unit — 569 tests pass.
  • npx standard — no lint errors on changed files.

🤖 Generated with Claude Code

Summary by Sourcery

Preserve closed SVG paths when remapping elements so translated shapes remain closed across browsers.

Bug Fixes:

  • Ensure remapElement correctly handles uppercase close-path commands from native getPathData/setPathData so closed shapes no longer revert or throw errors when moved.
  • Include close-path segments when rebuilding path data so moving a closed path does not drop its closing command and reopen the shape.

Tests:

  • Add regression tests covering remapElement translation of closed paths, including a simulated native getPathData/setPathData environment, to verify the close-path command is preserved and no errors are thrown.

remapElement's native getPathData() branch matched close-path segments
against a pathMap that only listed lowercase 'z', but paths serialize
the close command as uppercase 'Z'. The indexOf lookup returned -1,
leaving a hole in the segment array that later crashed with
"Cannot destructure property 'type' of undefined" when the path was
moved, aborting the update and making the shape snap back in place
(Firefox only, since Chrome falls back to the legacy pathSegList API).
Also fixed newPathData missing a case for the close-path segment,
which would have silently dropped it once the crash was fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes remapElement’s handling of closed SVG paths so that uppercase 'Z' close commands are correctly mapped and preserved when using native getPathData/setPathData, and adds regression tests to cover both attribute-based and native-path-data flows.

File-Level Changes

Change Details Files
Normalize and correctly serialize close-path segments when remapping SVG paths with native getPathData/setPathData.
  • Normalize uppercase 'Z' close-path segment types to the lowercase 'z' entry in pathMap before indexing, preventing holes in the segment array.
  • Extend the newPathData builder to handle the close-path segment type by pushing a corresponding 'Z'/'z' segment with empty values.
  • Ensure the reconstructed path data string includes the close-path command and remains consistent with newPathData.
packages/svgcanvas/core/coords.js
Add regression coverage for translating closed paths, including native getPathData/setPathData behavior.
  • Add a unit test that translates a closed path defined via the 'd' attribute and asserts that the close command is preserved and coordinates are translated.
  • Add a unit test that stubs native getPathData/setPathData for a closed path with an uppercase 'Z' segment, asserts remapElement no longer throws, and verifies both the resulting 'd' attribute and the data passed to setPathData preserve the close-path segment.
tests/unit/coords.test.js

Assessment against linked issues

Issue Objective Addressed Explanation
#1093 Allow closed shapes created with the path tool to be moved without snapping back to their original position (fix the remap/move bug, including in Firefox with native getPathData/setPathData).
#1093 Ensure that when moving a closed path, the closing Z command is preserved so the shape remains closed after the move.

Possibly linked issues

  • Path tool issues #1093: PR fixes remapElement’s handling of uppercase Z close-path so moved closed shapes no longer snap back.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@jfhenon jfhenon mentioned this pull request Jul 18, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In remapElement, the switch (type) branch uses the magic number case 1 to represent closepath; consider deriving this from pathMap.indexOf('z') or using a named constant to avoid coupling the behavior to the current pathMap ordering.
  • The parsedPathData object in the getPathData test stub is reused across calls and may be mutated by remapElement; consider returning a fresh clone from getPathData() to keep the test isolated from internal mutations.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `remapElement`, the `switch (type)` branch uses the magic number `case 1` to represent closepath; consider deriving this from `pathMap.indexOf('z')` or using a named constant to avoid coupling the behavior to the current `pathMap` ordering.
- The `parsedPathData` object in the `getPathData` test stub is reused across calls and may be mutated by `remapElement`; consider returning a fresh clone from `getPathData()` to keep the test isolated from internal mutations.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Replace the magic number 1 with a CLOSEPATH_TYPE constant derived from
pathMap.indexOf('z') so the switch branch doesn't silently break if
pathMap's ordering ever changes. Also make the getPathData test stub
return a fresh clone on each call so remapElement can't leak mutations
back into the fixture data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jfhenon
jfhenon merged commit 244a26c into master Jul 29, 2026
9 checks passed
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.

Path tool issues

1 participant