Skip to content

An image corner drag does not keep the aspect ratio #379

Description

@HMarzban

Related

No open issue asks a corner drag to keep the image ratio. Issue 151 is closed. It covers Markdown import keeping image width and height. It is not this change.

What happened

This changes a documented default in the published package @docs.plus/extension-hypermultimedia. It needs a maintainer ruling on the release and the semantic version. The target is the next release of that package.

Edward Saperia reported this on 2026-09-26. When he resizes an image, the drag does not keep the aspect ratio. He wrote: "also, when I'm resizing an image, it doesn't maintain aspect ratio. pretty rare that I want that behaviour!"

What we expect

A corner drag on an image keeps the width and height in the same ratio. The minimum and maximum limits keep that ratio too. Shift is one candidate for free resize. The side-handle rule is a separate maintainer decision. The docs and the changelog match the ruling.

Steps to reproduce

  1. Open a pad you can edit.
  2. Insert an image that is wider than it is tall.
  3. Drag a corner handle without holding Shift.
  4. Read the width and the height.

Expected by the report: the ratio stays the ratio of the image before the drag. Current result: width and height each follow the pointer.

Findings

Read from the package source on 2026-09-29. Not re-run in a browser.

computeCornerBox in gripperDrag.ts locks the ratio only while Shift is held (state.isShiftPressed && state.aspectRatio). A plain corner drag sets width from the horizontal delta and height from the vertical delta. computeSideBox changes one axis only. Its comment says one axis, and no aspect ratio.

calculateAspectRatioDimensions lets the larger pointer delta drive the other axis. If the horizontal delta is larger, height becomes width divided by the ratio. If the vertical delta is larger, width becomes height times the ratio.

clampDimensionsToConstraints uses a minimum width of 160 and a minimum height of 80. Those two floors are separate Math.max calls. The maximum width comes from the editor column. fitDimensionsToBounds scales both axes together for that maximum. updateNodeDimensions runs the same clamp again when the drag commits. A ratio-locked drag can still break the ratio at a floor. A wide image that cannot reach the minimum width at its own ratio is one example.

The same gripper is MediaResizeGripper. The kit adds a node when resizeGripper is not false and the node name is not x. The nodes are image, video, audio, youtube, vimeo, soundcloud, spotify, and loom. The pad configure call does not turn any of those off. soundcloud and spotify raise the height floor above 80.

The docs state the current rule. src/nodes/image/README.md says hold Shift to lock the aspect ratio. docs/resize-and-loading.md says the same. docs/api.md has a Shift row for a corner drag.

cypress/e2e/resize/gripper.cy.ts encodes the current rule. The bottom-right corner test expects both width and height to grow. The side tests expect one axis.

The repo package version is 2.1.0. The [Unreleased] changelog section is documentation only. RELEASE_POLICY.md is still Phase 1, so this package can ship without the other four. Phase 2 lockstep is not on. A patch is a bug fix. A minor is a backward-compatible feature. A major is a breaking change. Highlights are mandatory for a minor or a major. [Unreleased] must become a real version heading before publish. This task does not cut that release.

Reference points

  • extensions/extension-hypermultimedia/src/extensions/decoration/gripperDrag.ts
  • extensions/extension-hypermultimedia/src/extensions/decoration/utils.ts
  • extensions/extension-hypermultimedia/src/hyperMultimediaKit.ts
  • extensions/extension-hypermultimedia/src/nodes/image/README.md
  • extensions/extension-hypermultimedia/docs/resize-and-loading.md
  • extensions/extension-hypermultimedia/docs/api.md
  • extensions/extension-hypermultimedia/cypress/e2e/resize/gripper.cy.ts
  • extensions/extension-hypermultimedia/CHANGELOG.md
  • RELEASE_POLICY.md
  • Word picture size
  • Notion images
  • Google Docs crop and adjust

Word help says hold Shift while you drag a sizing handle to keep the proportions. To resize freely, turn off Lock aspect ratio. If height and width will not change on their own, clear that checkbox.

Notion help shows two guides, on the left edge and the right edge. Drag either guide to make the block bigger or smaller. That page does not mention a corner handle, Shift, or aspect ratio.

The Google Docs desktop page that was read covers crop, mask, border, and color. It does not state what a corner drag does to the ratio. Slack was not cited, because no Slack help page was read for this.

The corner default and the Shift gesture do not match across those pages. Word uses Shift to keep the ratio. The report asks for the opposite gesture. The maintainer picks one rule.

Acceptance criteria

  • The maintainer writes the corner rule and the version on this issue before the code lands.
  • Proposed corner rule: a corner drag keeps the ratio by default for the image. Shift is the documented way to resize freely. Ship the Word rule instead if the maintainer picks that.
  • The minimum and maximum limits keep the ratio.
  • The side-handle rule is a maintainer decision. Today a side handle changes one axis.
  • The ruling names whether video, audio, and the iframe embeds follow the image rule. They share computeCornerBox.
  • Committed width and height keep the ratio. The rendered DOM size keeps the ratio too. Assert the rendered size, not only the attributes.
  • Undo still restores the previous size. Collaboration sync still carries the size. Markdown and HTML round trips still keep the size.
  • cypress/e2e/resize/resize-undo.cy.ts and cypress/e2e/resize/resize-stale-pos.cy.ts still pass.
  • The image README, docs/api.md, docs/resize-and-loading.md, and the CHANGELOG match the ruling.
  • A clean-room Cypress spec asserts the ratio and fails before the change.

Blocked by

None — can start now.

Agent brief

Type: HITL for the corner rule, the side-handle rule, the node scope, and the semantic version. AFK for the drag math, the docs, and the spec after that comment.

Category: enhancement

Current behavior: See Findings. A plain corner drag changes width and height independently. Shift locks the ratio. The minimum width and the minimum height can break a locked ratio.

Desired behavior: The maintainer's written rule. The proposal is that an image corner keeps the ratio, and Shift resizes freely. Limits keep the ratio. The rendered size matches the attributes.

Where to start: computeCornerBox and computeSideBox in gripperDrag.ts. clampDimensionsToConstraints and calculateAspectRatioDimensions in utils.ts. MediaResizeGripper acceptedNodes in hyperMultimediaKit.ts. gripper.cy.ts. Search by symbol. File names are hints as of 2026-09-29.

Rules that apply: extensions/CLAUDE.md §Hypermultimedia Extension. A resize spec asserts the rendered DOM size, not node attributes alone. Read the inline style width and height, or getBoundingClientRect on img. Update the image README and the package docs in the same change. RELEASE_POLICY.md and the release-extensions skill: put the behavior change under a real version heading, with Highlights if the bump is minor or major. Do not run a release step in the fix. Phase 1 lets this package ship alone. AGENTS.md §Test Policy allows the new spec, because it pins a reported behavior change.

Verify: Run the package clean-room resize specs after bunx tsup in extensions/extension-hypermultimedia. The new ratio spec must fail on the current drag and pass after the change. resize-undo.cy.ts and resize-stale-pos.cy.ts must still pass. In a pad, drag an image corner with and without Shift, in light and dark, and check the rendered box.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions