Skip to content

Fix move/resize/undo geometry flattening (#1090, #1086) + dependency updates - #1091

Merged
jfhenon merged 7 commits into
masterfrom
fix/issue-1090-move-resize-undo
Jul 11, 2026
Merged

Fix move/resize/undo geometry flattening (#1090, #1086) + dependency updates#1091
jfhenon merged 7 commits into
masterfrom
fix/issue-1090-move-resize-undo

Conversation

@jfhenon

@jfhenon jfhenon commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Root cause (#1090 / #1086)

Both issues stem from the same defect in packages/svgcanvas/core/event.js's mouseup handler:

  1. The dummy transform inserted at the start of a move-drag was an identity matrix, but the code that replaces it during dragging only recognized dummies of type translate — leaving a stray extra transform behind after every move and routing simple moves into a "consolidate transforms" path instead of flattening them into real geometry.
  2. That consolidate path (shared by move/resize/rotate) always treated a transform list with more than one item as a "pre-existing multi-transform" case to collapse into one matrix — but a resize always builds an intentional translate/scale/translate list of exactly that shape. So recalculateDimensions() (which already has a dedicated branch for this exact shape) was never actually called for a resize, and the "old transform" recorded for undo was read from a map that's only populated during move drags, so undoing a resize always recorded '' as the prior state instead of the move's real transform.

Fix: the drag-start dummy is now a real translate(0,0) so it's correctly recognized and replaced; and non-group elements now always try recalculateDimensions() first (falling back to raw matrix consolidation only for groups or shapes it doesn't recognize), using the transform captured at mousedown as the true "before" state for undo regardless of interaction mode.

Test plan

  • npm run lint
  • npx vitest run tests/unit — 562 tests pass, including new regression test tests/unit/move-resize-undo.test.js covering move→resize→undo and resize-only flattening for rect and ellipse
  • npm run build — production build succeeds

🤖 Generated with Claude Code

Summary by Sourcery

Fix move/resize/undo handling so drag transforms are correctly flattened into element geometry, ensuring accurate undo behavior and geometry attributes, and update key workspace dependencies.

Bug Fixes:

  • Ensure move followed by resize preserves the moved position when undoing the resize instead of reverting to the original creation-time geometry.
  • Ensure resize operations update elements’ real geometry attributes and do not leave scale transforms that visually distort stroke width.
  • Ensure group elements consolidate drag transforms into a single matrix while preserving intended behavior for non-group elements.

Enhancements:

  • Refine transform handling to prioritize geometry flattening via recalculateDimensions for non-group elements, with a matrix consolidation fallback.
  • Improve jsdom-based test reliability by shimming missing SVG APIs used during mouse interactions.

Build:

  • Update core build and tooling dependencies (vite, jsdom, nyc, open-cli, vite-plugin-istanbul, rollup optional binary) to current versions.

Tests:

  • Add unit regression tests covering move→resize→undo and resize-only flows for rect, ellipse, path, and group elements.
  • Adjust Playwright layer panel e2e expectations to align with the latest @playwright/test behavior.

jfhenon and others added 3 commits July 11, 2026 12:58
Undoing a resize that followed a move reverted the shape all the way
back to its creation-time position instead of just the resize, for
ellipse/star/path (and, per code inspection, any shape driven through
this path).

Two bugs compounded: the drag-start dummy transform was an identity
matrix instead of a translate, so the type check in
updateTransformList never recognized it and left a stray extra
transform behind after every move; and the mouseup handler's "old
transform" for undo was read from dragStartTransforms, which is only
populated for move drags and is null during resize/rotate, so undoing
a resize always recorded '' as the prior state instead of the move's
actual transform.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resizing a shape never called recalculateDimensions(): the mouseup
handler treated any transform list with more than one item as a
"pre-existing multi-transform" case to consolidate into a single
matrix, but a resize always builds a translate/scale/translate list
of exactly that shape. As a result every resize left the change as a
transform matrix instead of updating the shape's x/y/width/height/
rx/ry/points/d attributes - so the coordinate and size panel inputs
never reflected a resize, and the shape appeared scaled (distorting
stroke width) rather than recalculated.

recalculateDimensions() already has a dedicated branch for exactly
this translate/scale/translate shape (and for plain moves), so now
non-group elements always try it first; the raw matrix consolidation
is only used as a fallback when recalculateDimensions can't handle
the transform shape (or for groups, which must keep their transform
attribute).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bumps devDependencies and production deps to their latest available
versions, including majors: vite 7->8, jsdom 27->29, i18next 25->26,
react/react-dom 19.1->19.2, nyc 17->18, open-cli 8->9,
vite-plugin-istanbul 7->9, @babel/preset-react 7->8, plus minor/patch
bumps across the rest. Verified lint, the full unit test suite, and
the production build all still pass.

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

sourcery-ai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes geometry flattening for move/resize interactions and undo behavior by changing how drag dummy transforms are created and consolidated, preferring geometry recalculation for elements and matrix consolidation for groups/fallbacks, plus updating dependencies and tests to match new behavior and tooling versions.

Sequence diagram for move→resize→undo geometry handling

sequenceDiagram
  actor User
  participant SvgCanvas as SvgCanvas
  participant Element as SVGElement
  participant UndoMgr as UndoManager

  User->>SvgCanvas: mousedown
  SvgCanvas->>SvgCanvas: setStartTransform(Element)

  User->>SvgCanvas: drag move (select mode)
  SvgCanvas->>Element: updateTransformList(svgRoot, Element, dx, dy)
  SvgCanvas->>SvgCanvas: dragStartTransforms.set(Element, transform)

  User->>SvgCanvas: mouseup (after move)
  SvgCanvas->>SvgCanvas: mouseUpEvent(evt)
  SvgCanvas->>SvgCanvas: recalculateDimensions(Element)
  SvgCanvas-->>UndoMgr: ChangeElementCommand(...)

  User->>SvgCanvas: mousedown
  SvgCanvas->>SvgCanvas: setStartTransform(Element)

  User->>SvgCanvas: drag resize
  SvgCanvas->>Element: updateTransformList(svgRoot, Element, dx, dy)

  User->>SvgCanvas: mouseup (after resize)
  SvgCanvas->>SvgCanvas: mouseUpEvent(evt)
  SvgCanvas->>SvgCanvas: getStartTransform()
  SvgCanvas-->>SvgCanvas: oldTransform = dragStartTransforms.has(Element)
  SvgCanvas-->>SvgCanvas: else oldTransform = getStartTransform()
  SvgCanvas->>SvgCanvas: recalculateDimensions(Element)
  SvgCanvas-->>UndoMgr: ChangeElementCommand(...)

  User->>UndoMgr: undo
  UndoMgr->>Element: apply oldTransform / geometry
Loading

Flow diagram for mouseUpEvent transform consolidation and geometry flattening

flowchart TD
  A[mouseUpEvent for dragged element] --> B[Get transform list tlist]
  B --> C[Determine isGroup and hasDragTranslate]
  C --> D{isGroup and hasDragTranslate?}
  D -->|yes| E[consolidateTransform svgRoot elem tlist oldTransform]
  E --> Z[Add ChangeElementCommand to batchCmd]
  D -->|no| F[cmd = recalculateDimensions elem]
  F --> G{cmd exists?}
  G -->|yes| H[Add cmd to batchCmd]
  H --> Y[End]
  G -->|no| I{tlist.numberOfItems > 1 and hasDragTranslate?}
  I -->|yes| J[consolidateTransform svgRoot elem tlist oldTransform]
  J --> Z
  I -->|no| K[Read newTransform from elem]
  K --> L{newTransform !== oldTransform?}
  L -->|yes| M[Add ChangeElementCommand elem transform oldTransform]
  M --> Y
  L -->|no| Y[End]
Loading

File-Level Changes

Change Details Files
Correct drag/transform handling so move and resize operations flatten into element geometry and undo uses the true pre-interaction transform.
  • Change translate-type checks to use SVGTransform.SVG_TRANSFORM_TRANSLATE instead of magic number 2.
  • Initialize drag-start dummy transform as an explicit translate(0,0) and insert/append that to the transform list.
  • Capture pre-interaction transforms using dragStartTransforms when present, otherwise fall back to getStartTransform for resize/rotate.
  • Introduce consolidateTransform helper to collapse an element’s transform list into a single matrix and return an undo ChangeElementCommand.
  • Update mouseup logic to treat groups as always consolidated into a single matrix and non-groups as preferring recalculateDimensions, with matrix consolidation only as fallback when needed.
packages/svgcanvas/core/event.js
Add regression coverage for move→resize→undo and resize-only interactions, including group transform behavior, using jsdom shims for missing SVG APIs.
  • Create a jsdom-based unit test harness that stubs getScreenCTM and SVGTransformList#replaceItem.
  • Simulate mouse-driven move and resize interactions against rect, ellipse, and path elements and assert geometry attribute updates and transform clearing.
  • Verify undo behavior for move+resize and resize-only so geometry is restored correctly and transforms are not left behind.
  • Add a group move test that inspects the SVGTransformList directly and asserts consolidation into a single matrix transform.
tests/unit/move-resize-undo.test.js
Adjust Playwright-based layers panel e2e tests to use the current expect.poll API semantics.
  • Remove deprecated .resolves chaining from expect.poll assertions.
  • Update layer name presence/absence checks to use expect.poll(...).toContain and .not.toContain.
tests/e2e/layers-panel.spec.js
Update runtime and dev dependencies, including React-related packages, Vite/Vitest/jsdom, coverage, Playwright, nyc, open-cli, and Rollup binary versions.
  • Bump i18next and jspdf versions in main package.json dependencies.
  • Upgrade devDependencies such as @playwright/test, @vitest/coverage-v8, jsdom, nyc, open-cli, rimraf, vite, vite-plugin-istanbul, vitest, and the optional @rollup/rollup-linux-x64-gnu binary.
  • Update react and react-dom versions, along with @babel/preset-react, in the React test package.
package.json
packages/react-test/package.json
package-lock.json

Assessment against linked issues

Issue Objective Addressed Explanation
#1086 Ensure that moving shapes updates their underlying geometry (e.g., x/y) so the coordinate inputs reflect the new position.
#1086 Ensure that resizing shapes updates their underlying geometry (e.g., width/height and other size attributes) so the size inputs reflect the new dimensions.
#1086 Ensure that resizing shapes recalculates their geometry instead of applying scale transforms, so shapes are not visually scaled (no stroke distortion) and remain proper SVG geometry.
#1090 Fix undo behavior so that when a shape is moved and then resized, undoing the resize reverts only the size while preserving the moved position.
#1090 Ensure the corrected move/resize/undo behavior applies to non-rectangular shapes such as ellipses and stars (and generally other shape types).

Possibly linked issues


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

@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 found 2 security issues, 2 other issues, and left some high level feedback:

Security issues:

  • BlueOak-1.0.0: Open-source license could not be identified (link)
  • BlueOak-1.0.0: Open-source license could not be identified (link)

Fixed security issues:

General comments:

  • In the jsdom SVGTransformList.replaceItem stub, relying on the non-standard _items array makes the test brittle; consider using existing list methods (e.g., removeItem/insertItem) or a lightweight mock implementation instead of mutating internal state.
  • This PR mixes a fairly intricate interaction/undo fix with multiple dependency upgrades; splitting behavioral changes and tooling/dependency bumps into separate PRs would make future bisects and reviews easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the jsdom SVGTransformList.replaceItem stub, relying on the non-standard _items array makes the test brittle; consider using existing list methods (e.g., removeItem/insertItem) or a lightweight mock implementation instead of mutating internal state.
- This PR mixes a fairly intricate interaction/undo fix with multiple dependency upgrades; splitting behavioral changes and tooling/dependency bumps into separate PRs would make future bisects and reviews easier.

## Individual Comments

### Comment 1
<location path="tests/unit/move-resize-undo.test.js" line_range="134-117" />
<code_context>
+  function testResizeOnly (tag, attrs) {
</code_context>
<issue_to_address>
**suggestion (testing):** Add an undo assertion for the resize-only scenario to ensure undo restores the original geometry and transform state.

Because move, resize, and rotate share code paths, it’s important that this helper also cover undo behavior. After computing `afterResize`, call `svgCanvas.undoMgr.undo()`, capture `afterUndo`, and assert `deepEqual(afterUndo, attrs)` plus any expectations on the `transform` attribute (likely empty). This will help catch regressions where resize-only undo behaves differently from move+resize undo.

Suggested implementation:

```javascript
  function testResizeOnly (tag, attrs) {
    it(`resize-only (no prior move) updates geometry and supports undo for <${tag}>`, function () {
      const attrStr = Object.entries(attrs).map(([k, v]) => `${k}="${v}"`).join(' ')
      svgCanvas.setSvgString(
        `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480"><${tag} id="shape1" ${attrStr}/></svg>`
      )
      const elem = document.getElementById('shape1')
      svgCanvas.clearSelection()
      svgCanvas.addToSelection([elem])

      const selector = svgCanvas.selectorManager.requestSelector(elem)

```

To fully implement the undo assertion, you will also need to:
1. Locate where `testResizeOnly` currently computes the geometry after performing the resize, something like:
   ```js
   const afterResize = getGeometry(elem)
   ```
   or equivalent logic that reads `x`/`y`/`width`/`height` (or `cx`/`cy`/`rx`/`ry`) and the `transform` attribute.

2. Immediately after computing `afterResize`, insert:
   ```js
   svgCanvas.undoMgr.undo()
   const afterUndo = getGeometry(elem)

   assert.deepEqual(
     afterUndo,
     attrs,
     `${tag}: undo(resize-only) should restore original geometry`
   )
   assert.isFalse(
     elem.hasAttribute('transform') && elem.getAttribute('transform'),
     `${tag}: undo(resize-only) should leave transform empty`
   )
   ```
   - If your helper `getGeometry` already returns a combined object with attributes and transform, adapt the `deepEqual` to compare only the geometry portion and assert separately on the transform.
   - If `transform` is represented differently (e.g. `null`, `''`, or omitted), adjust the transform assertion accordingly, e.g.:
     ```js
     assert.isFalse(elem.hasAttribute('transform'))
     ```
     or
     ```js
     assert.strictEqual(elem.getAttribute('transform'), '')
     ```

3. Ensure that any shared helpers used by the move+resize tests (for reading geometry and transform) are reused here so that resize-only undo is tested along the same code path as move+resize undo.
</issue_to_address>

### Comment 2
<location path="tests/unit/move-resize-undo.test.js" line_range="131-140" />
<code_context>
+  testTag('rect', { x: 10, y: 10, width: 40, height: 30 })
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding coverage for other element types and interactions (e.g., paths/groups and rotate) that share this transform-flattening path.

These tests cover rects and ellipses for move/resize/undo and resize-only, but the updated mouseup logic now applies to all non-group elements and has a separate branch for groups. To better protect against regressions, please add at least one case for another shape type (e.g., `path` or `polygon`) to confirm `recalculateDimensions`/consolidate fallback behavior, and one for a `g` element to verify group transform consolidation. A simple rotate-then-undo test would also exercise the same consolidation vs. `recalculateDimensions` logic from a different interaction mode.

Suggested implementation:

```javascript
  testTag('rect', { x: 10, y: 10, width: 40, height: 30 })
  testTag('ellipse', { cx: 30, cy: 30, rx: 20, ry: 15 })
  // Non-rect/ellipse shape to cover recalculateDimensions/consolidate fallback
  testTag('polygon', { points: '10,10 50,10 50,40 10,40' })

  function testResizeOnly (tag, attrs) {

```

Based on the test comment and the broader change to the mouseup logic, you should also:
1. Add a `testTag('path', ...)` or a separate test case using a `<path>` element if your current `testTag` helper supports paths, to ensure transform flattening is exercised for another non-rect/ellipse type.
2. Introduce a new test (or tests) for a `<g>` element that:
   - Creates a group (`<g id="group1">`) with at least one child (e.g., a `<rect>`).
   - Applies a rotate interaction through the same utilities used in this file (e.g., whatever helper currently performs rotate via the selector/mouse interaction).
   - Triggers undo and asserts that group transforms are properly consolidated (e.g., transform attribute restored/flattened as expected).
3. If this file already contains helpers (e.g., `performRotate`, `simulateRotate`, or similar), use them in the new `g` test to keep things consistent; otherwise, follow the existing pattern used in the move/resize tests to simulate mouse events for rotate + undo.

You’ll need to integrate these new tests where appropriate in the file’s existing `describe`/`context` structure, mirroring how the current move/resize/undo tests are organized.
</issue_to_address>

### Comment 3
<location path="package-lock.json" line_range="7419-7427" />
<code_context>

</code_context>
<issue_to_address>
**security (license/minipass):** BlueOak-1.0.0: Open-source license could not be identified

The obligations of the `BlueOak-1.0.0` license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

*Source: trivy*
</issue_to_address>

### Comment 4
<location path="package-lock.json" line_range="9545-9559" />
<code_context>

</code_context>
<issue_to_address>
**security (license/spawn-wrap):** BlueOak-1.0.0: Open-source license could not be identified

The obligations of the `BlueOak-1.0.0` license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

*Source: trivy*
</issue_to_address>

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.

// The resize must flatten into the shape's real geometry attributes
// (issue #1086), not just leave a scale matrix on the element.
assert.notDeepEqual(afterResize, afterMove, `${tag}: resize should update geometry attrs, not just leave a transform`)
assert.ok(!elem.getAttribute('transform'), `${tag}: resize should not leave a leftover transform attribute`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Add an undo assertion for the resize-only scenario to ensure undo restores the original geometry and transform state.

Because move, resize, and rotate share code paths, it’s important that this helper also cover undo behavior. After computing afterResize, call svgCanvas.undoMgr.undo(), capture afterUndo, and assert deepEqual(afterUndo, attrs) plus any expectations on the transform attribute (likely empty). This will help catch regressions where resize-only undo behaves differently from move+resize undo.

Suggested implementation:

  function testResizeOnly (tag, attrs) {
    it(`resize-only (no prior move) updates geometry and supports undo for <${tag}>`, function () {
      const attrStr = Object.entries(attrs).map(([k, v]) => `${k}="${v}"`).join(' ')
      svgCanvas.setSvgString(
        `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480"><${tag} id="shape1" ${attrStr}/></svg>`
      )
      const elem = document.getElementById('shape1')
      svgCanvas.clearSelection()
      svgCanvas.addToSelection([elem])

      const selector = svgCanvas.selectorManager.requestSelector(elem)

To fully implement the undo assertion, you will also need to:

  1. Locate where testResizeOnly currently computes the geometry after performing the resize, something like:

    const afterResize = getGeometry(elem)

    or equivalent logic that reads x/y/width/height (or cx/cy/rx/ry) and the transform attribute.

  2. Immediately after computing afterResize, insert:

    svgCanvas.undoMgr.undo()
    const afterUndo = getGeometry(elem)
    
    assert.deepEqual(
      afterUndo,
      attrs,
      `${tag}: undo(resize-only) should restore original geometry`
    )
    assert.isFalse(
      elem.hasAttribute('transform') && elem.getAttribute('transform'),
      `${tag}: undo(resize-only) should leave transform empty`
    )
    • If your helper getGeometry already returns a combined object with attributes and transform, adapt the deepEqual to compare only the geometry portion and assert separately on the transform.
    • If transform is represented differently (e.g. null, '', or omitted), adjust the transform assertion accordingly, e.g.:
      assert.isFalse(elem.hasAttribute('transform'))
      or
      assert.strictEqual(elem.getAttribute('transform'), '')
  3. Ensure that any shared helpers used by the move+resize tests (for reading geometry and transform) are reused here so that resize-only undo is tested along the same code path as move+resize undo.

Comment on lines +131 to +140
testTag('rect', { x: 10, y: 10, width: 40, height: 30 })
testTag('ellipse', { cx: 30, cy: 30, rx: 20, ry: 15 })

function testResizeOnly (tag, attrs) {
it(`resize-only (no prior move) updates geometry for <${tag}>`, function () {
const attrStr = Object.entries(attrs).map(([k, v]) => `${k}="${v}"`).join(' ')
svgCanvas.setSvgString(
`<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480"><${tag} id="shape1" ${attrStr}/></svg>`
)
const elem = document.getElementById('shape1')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding coverage for other element types and interactions (e.g., paths/groups and rotate) that share this transform-flattening path.

These tests cover rects and ellipses for move/resize/undo and resize-only, but the updated mouseup logic now applies to all non-group elements and has a separate branch for groups. To better protect against regressions, please add at least one case for another shape type (e.g., path or polygon) to confirm recalculateDimensions/consolidate fallback behavior, and one for a g element to verify group transform consolidation. A simple rotate-then-undo test would also exercise the same consolidation vs. recalculateDimensions logic from a different interaction mode.

Suggested implementation:

  testTag('rect', { x: 10, y: 10, width: 40, height: 30 })
  testTag('ellipse', { cx: 30, cy: 30, rx: 20, ry: 15 })
  // Non-rect/ellipse shape to cover recalculateDimensions/consolidate fallback
  testTag('polygon', { points: '10,10 50,10 50,40 10,40' })

  function testResizeOnly (tag, attrs) {

Based on the test comment and the broader change to the mouseup logic, you should also:

  1. Add a testTag('path', ...) or a separate test case using a <path> element if your current testTag helper supports paths, to ensure transform flattening is exercised for another non-rect/ellipse type.
  2. Introduce a new test (or tests) for a <g> element that:
    • Creates a group (<g id="group1">) with at least one child (e.g., a <rect>).
    • Applies a rotate interaction through the same utilities used in this file (e.g., whatever helper currently performs rotate via the selector/mouse interaction).
    • Triggers undo and asserts that group transforms are properly consolidated (e.g., transform attribute restored/flattened as expected).
  3. If this file already contains helpers (e.g., performRotate, simulateRotate, or similar), use them in the new g test to keep things consistent; otherwise, follow the existing pattern used in the move/resize tests to simulate mouse events for rotate + undo.

You’ll need to integrate these new tests where appropriate in the file’s existing describe/context structure, mirroring how the current move/resize/undo tests are organized.

Comment thread package-lock.json
Comment on lines 7419 to 7427
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (license/minipass): BlueOak-1.0.0: Open-source license could not be identified

The obligations of the BlueOak-1.0.0 license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

Source: trivy

Comment thread package-lock.json
Comment on lines 9545 to 9559
"node_modules/spawn-wrap": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
"integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz",
"integrity": "sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"dependencies": {
"cross-spawn": "^7.0.6",
"foreground-child": "^2.0.0",
"is-windows": "^1.0.2",
"make-dir": "^3.0.0",
"rimraf": "^3.0.0",
"rimraf": "^6.1.3",
"signal-exit": "^3.0.2",
"which": "^2.0.1"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (license/spawn-wrap): BlueOak-1.0.0: Open-source license could not be identified

The obligations of the BlueOak-1.0.0 license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

Source: trivy

jfhenon and others added 3 commits July 11, 2026 13:19
expect.poll() no longer supports chaining a .resolves/.rejects matcher
in the updated @playwright/test version - its callback result is
already awaited directly. Drop the redundant .resolves calls so the
e2e suite passes again after the dependency bump.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Reimplement the jsdom SVGTransformList#replaceItem stub in terms of
  the list's public removeItem/insertItemBefore API instead of
  mutating its internal _items array directly.
- Add an undo assertion to the resize-only case so it also verifies
  undo restores the original geometry and leaves no transform.
- Add path element coverage alongside rect/ellipse for the
  move-then-resize-then-undo and resize-only flows.
- Add a group move test covering the isGroup consolidation branch,
  which recalculateDimensions() intentionally skips.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolves all 14 flagged vulnerabilities (1 low, 5 moderate, 7 high, 1
critical) in transitive dev-tooling dependencies - eslint plugin
chains, remark/markdown-it, nyc/istanbul, npm-run-all - by updating
package-lock.json resolutions. No direct dependency versions changed;
npm audit reports 0 vulnerabilities afterward.

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

jfhenon commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the Sourcery review feedback:

  • jsdom replaceItem stub: reimplemented in terms of the list's own removeItem/insertItemBefore public API instead of mutating the internal _items array directly.
  • Resize-only undo coverage: added an undo assertion to the resize-only test case verifying it restores the original geometry with no leftover transform.
  • Broader element/interaction coverage: added path alongside rect/ellipse for the move→resize→undo and resize-only flows, plus a new test covering the isGroup consolidation branch (which recalculateDimensions() intentionally skips for groups).
  • npm audit: ran npm audit fix, resolving all 14 flagged vulnerabilities in transitive dev-tooling deps (eslint/remark/nyc chains) - 0 vulnerabilities remain, no direct dependency versions changed.
  • BlueOak-1.0.0 license flags (minipass, spawn-wrap): checked - this is their genuine, current license at every supported version (not stale metadata), a legitimate permissive OSS license that some scanners simply don't have indexed yet. Downgrading to reintroduce a "recognized" license would mean reintroducing the vulnerabilities just fixed above, so left as-is.
  • Splitting into separate PRs: per explicit request this round combines the Undo command on resizing shapes #1090/*breaking* shape x y coordinates, width / height inputs do not update and shape resizing does not work in accordance with SVG #1086 fix with the dependency updates; keeping as one PR.

All changes verified with npm run lint, npm test (unit + e2e), and npm run build.

@jfhenon

jfhenon commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator Author

@sourcery-ai review

@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 found 2 security issues, and left some high level feedback:

Security issues:

  • BlueOak-1.0.0: Open-source license could not be identified (link)
  • BlueOak-1.0.0: Open-source license could not be identified (link)

Fixed security issues:

General comments:

  • The mouseup transform logic still relies on magic numeric SVGTransform type values (e.g., 2 for translate, 1 for matrix); consider centralizing these as named constants or using the corresponding DOM constants to improve readability and reduce the risk of incorrect values.
  • The new consolidate helper inside mouseUpEvent closes over local variables; extracting it to a small standalone function that takes elem, tlist, and oldTransform as arguments would make the control flow clearer and avoid subtle capture issues if this block evolves.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The mouseup transform logic still relies on magic numeric SVGTransform `type` values (e.g., `2` for translate, `1` for matrix); consider centralizing these as named constants or using the corresponding DOM constants to improve readability and reduce the risk of incorrect values.
- The new `consolidate` helper inside `mouseUpEvent` closes over local variables; extracting it to a small standalone function that takes `elem`, `tlist`, and `oldTransform` as arguments would make the control flow clearer and avoid subtle capture issues if this block evolves.

## Individual Comments

### Comment 1
<location path="package-lock.json" line_range="7370-7378" />
<code_context>

</code_context>
<issue_to_address>
**security (license/minipass):** BlueOak-1.0.0: Open-source license could not be identified

The obligations of the `BlueOak-1.0.0` license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

*Source: trivy*
</issue_to_address>

### Comment 2
<location path="package-lock.json" line_range="9485-9499" />
<code_context>

</code_context>
<issue_to_address>
**security (license/spawn-wrap):** BlueOak-1.0.0: Open-source license could not be identified

The obligations of the `BlueOak-1.0.0` license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

*Source: trivy*
</issue_to_address>

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.

Comment thread package-lock.json
Comment on lines 7370 to 7378
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (license/minipass): BlueOak-1.0.0: Open-source license could not be identified

The obligations of the BlueOak-1.0.0 license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

Source: trivy

Comment thread package-lock.json
Comment on lines 9485 to 9499
"node_modules/spawn-wrap": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
"integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz",
"integrity": "sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"dependencies": {
"cross-spawn": "^7.0.6",
"foreground-child": "^2.0.0",
"is-windows": "^1.0.2",
"make-dir": "^3.0.0",
"rimraf": "^3.0.0",
"rimraf": "^6.1.3",
"signal-exit": "^3.0.2",
"which": "^2.0.1"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (license/spawn-wrap): BlueOak-1.0.0: Open-source license could not be identified

The obligations of the BlueOak-1.0.0 license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

Source: trivy

- Replace magic SVGTransform.type numbers (2, 1) with the named
  SVG_TRANSFORM_TRANSLATE/SVG_TRANSFORM_MATRIX constants, matching the
  convention already used in math.js and recalculate.js.
- Extract the inline consolidate() closure into a standalone
  consolidateTransform(svgRoot, elem, tlist, oldTransform) function
  that takes its dependencies as explicit parameters instead of
  capturing them from the enclosing mouseUpEvent scope.

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

jfhenon commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator Author

@sourcery-ai review

@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 found 2 security issues

Security issues:

  • BlueOak-1.0.0: Open-source license could not be identified (link)
  • BlueOak-1.0.0: Open-source license could not be identified (link)

Fixed security issues:

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="package-lock.json" line_range="7370-7378" />
<code_context>

</code_context>
<issue_to_address>
**security (license/minipass):** BlueOak-1.0.0: Open-source license could not be identified

The obligations of the `BlueOak-1.0.0` license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

*Source: trivy*
</issue_to_address>

### Comment 2
<location path="package-lock.json" line_range="9485-9499" />
<code_context>

</code_context>
<issue_to_address>
**security (license/spawn-wrap):** BlueOak-1.0.0: Open-source license could not be identified

The obligations of the `BlueOak-1.0.0` license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

*Source: trivy*
</issue_to_address>

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.

Comment thread package-lock.json
Comment on lines 7370 to 7378
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (license/minipass): BlueOak-1.0.0: Open-source license could not be identified

The obligations of the BlueOak-1.0.0 license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

Source: trivy

Comment thread package-lock.json
Comment on lines 9485 to 9499
"node_modules/spawn-wrap": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
"integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz",
"integrity": "sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==",
"dev": true,
"license": "ISC",
"license": "BlueOak-1.0.0",
"dependencies": {
"cross-spawn": "^7.0.6",
"foreground-child": "^2.0.0",
"is-windows": "^1.0.2",
"make-dir": "^3.0.0",
"rimraf": "^3.0.0",
"rimraf": "^6.1.3",
"signal-exit": "^3.0.2",
"which": "^2.0.1"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (license/spawn-wrap): BlueOak-1.0.0: Open-source license could not be identified

The obligations of the BlueOak-1.0.0 license for this code could not be determined automatically. Unknown licenses may carry obligations or restrictions and should be reviewed manually to ensure compliance

Source: trivy

@jfhenon
jfhenon merged commit 6f33e9b into master Jul 11, 2026
8 of 9 checks passed
@jfhenon
jfhenon deleted the fix/issue-1090-move-resize-undo branch July 11, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant