Skip to content

fix: center dropped image and svg insertions#700

Open
k08200 wants to merge 5 commits intogridaco:mainfrom
k08200:fix/366-drop-insertion-position
Open

fix: center dropped image and svg insertions#700
k08200 wants to merge 5 commits intogridaco:mainfrom
k08200:fix/366-drop-insertion-position

Conversation

@k08200
Copy link
Copy Markdown
Contributor

@k08200 k08200 commented Apr 30, 2026

Summary

  • convert drop coordinates to canvas space before subtracting known asset half-size
  • center external image and SVG insertions at the drop point
  • apply the same center-placement logic to inline image data-transfer drops

Verification

  • pnpm --dir editor exec vitest run grida-canvas-react/tests/data-transfer-position.test.ts
  • pnpm exec oxfmt --check editor/grida-canvas-react/data-transfer-position.ts editor/grida-canvas-react/tests/data-transfer-position.test.ts editor/grida-canvas-react/use-data-transfer.ts
  • pnpm exec oxlint editor/grida-canvas-react/data-transfer-position.ts editor/grida-canvas-react/tests/data-transfer-position.test.ts editor/grida-canvas-react/use-data-transfer.ts

Note: pnpm --dir editor typecheck currently fails on unrelated missing static image modules under app/(www), app/(site), and components/stripe.

Closes #366

Summary by CodeRabbit

  • New Features

    • Compute centered canvas insertion points for dropped/inserted items
    • Drag-and-drop and programmatic insertion now center images and SVGs at the pointer
    • Optional "preserve placement" mode so inserted content can retain absolute coordinates
  • Bug Fixes

    • More reliable resolution of image/SVG dimensions during insertion
    • Centering ignores missing or non-measurable dimensions
  • Tests

    • Added tests validating centered insertion and preservation of absolute layout

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 30, 2026

@k08200 is attempting to deploy a commit to the Grida Team on Vercel.

A member of the Team first needs to authorize it.

@vercel vercel Bot temporarily deployed to Preview – viewer April 30, 2026 08:22 Inactive
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
backgrounds Skipped Skipped Apr 30, 2026 3:29pm
blog Skipped Skipped Apr 30, 2026 3:29pm
viewer Skipped Skipped Apr 30, 2026 3:29pm

Request Review

@vercel vercel Bot temporarily deployed to Preview – backgrounds April 30, 2026 08:22 Inactive
@vercel vercel Bot temporarily deployed to Preview – blog April 30, 2026 08:22 Inactive
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a client->canvas coordinate utility and tests, extends the data-transfer hook to compute canvas drop points and center inserted images/SVGs using resolved dimensions, and makes insertion reducer placement optional via a new action field.

Changes

Cohort / File(s) Summary
Coordinate Utility
editor/grida-canvas-react/data-transfer-position.ts
New exports: Vector2, Size, and getCenteredCanvasInsertionPoint which converts client->canvas and subtracts half of measurable dimensions (ignores non-finite or zero sizes).
Utility Tests
editor/grida-canvas-react/__tests__/data-transfer-position.test.ts
Adds Vitest tests validating client->canvas conversion and centering behavior, including handling of unmeasurable size values (width 0, height NaN).
Data Transfer & Insertion Flow
editor/grida-canvas-react/use-data-transfer.ts
Refactors to use InsertionPosition, computes canvas insertion via transformer, centralizes image-rectangle creation (insertImageRectangle), resolves image dimensions before inserting, updates ondrop to compute canvas drop position, and adds instance to hook deps.
Insert Action Type
editor/grida-canvas/action.ts
Adds optional `placement?: "auto"
Insert Reducer
editor/grida-canvas/reducers/document.reducer.ts
insert reducer now skips dynamic placement and child offset application when action.placement === "none"; otherwise previous placement logic remains.
Insert Dispatch Test
editor/grida-canvas/__tests__/headless/node-properties.test.ts
New test asserting that inserting a prototype with layout_positioning: "absolute" preserves explicit layout_inset_left/top in resulting node.

Sequence Diagram(s)

sequenceDiagram
    participant User as Client
    participant Hook as use-data-transfer
    participant Xform as clientPointToCanvasPoint
    participant Loader as Image/Metadata Resolver
    participant Doc as Document/Canvas
    User->>Hook: drop at clientPosition (file/metadata)
    Hook->>Xform: clientPointToCanvasPoint(clientPosition)
    Xform-->>Hook: canvasPosition
    Hook->>Loader: resolve width/height (if needed)
    Loader-->>Hook: size {width,height}
    Hook->>Hook: compute half-offsets (ignore invalid)
    Hook->>Doc: insertImageRectangle(position = canvasPosition - halfOffsets, size)
    Doc-->>Hook: nodeCreated/selected
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #357 — Modifies image drop/insertion flow to measure or resolve image dimensions before insertion (closely related to sizing on drop).
  • #358 — Updates provider/image insertion to load natural image dimensions before insertion (related to resolving size prior to placing).
  • #482 — Changes insertion/paste code paths and insertion placement APIs (related to placement and reducer behavior).

Suggested labels

codex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: center dropped image and svg insertions' clearly describes the main objective of fixing centering behavior for dropped images and SVGs.
Linked Issues check ✅ Passed The PR successfully addresses all objectives from issue #366: converts drop coordinates to canvas space, centers assets when size is known, and ensures consistent placement across image/SVG drops.
Out of Scope Changes check ✅ Passed All changes are scoped to coordinate handling, centering logic, and insertion placement behavior directly related to issue #366 requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
editor/grida-canvas-react/__tests__/data-transfer-position.test.ts (1)

4-24: ⚡ Quick win

Add a regression that exercises the real drop flow.

These tests prove the helper math, but they will not catch wiring mistakes in editor/grida-canvas-react/use-data-transfer.ts such as using the wrong event coordinates or choosing the wrong dimensions for inline image drops. Please add either a hook-level regression or the matching ../test/ manual case for image/SVG drop under zoom/pan. Based on learnings: When checking UX bugs or changing interaction behavior in grida-canvas*, check ../test/ for related manual test cases and add new ones when appropriate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas-react/__tests__/data-transfer-position.test.ts` around
lines 4 - 24, Add a hook-level regression (or a new manual case in ../test/)
that exercises the real drop flow through use-data-transfer.ts rather than only
unit-testing getCenteredCanvasInsertionPoint: simulate an actual drop of an
inline image/SVG under non-identity zoom/pan, dispatch real DragEvent/DropEvent
objects carrying a file/dataTransfer and set clientX/clientY (and pageX/pageY if
needed), ensure the component/hook receives the event and converts coordinates
via clientPointToCanvasPoint, and assert the created element uses the converted
canvas position and the measured (or fallback) dimensions; this will catch
wiring mistakes like using wrong event coords or wrong dimension sources in
use-data-transfer.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@editor/grida-canvas-react/use-data-transfer.ts`:
- Around line 604-620: The width/height fallbacks currently use nullish
coalescing so invalid numeric values like 0, NaN or Infinity from the payload
can become the node size; when computing imageWidth/imageHeight before calling
getCenteredCanvasInsertionPoint and creating the node, validate the payload
values with a positive-finite guard (e.g., Number.isFinite(value) && value > 0)
and only use payload width/height if they pass, otherwise fall back to
imageRef.width/imageRef.height; keep the rest of the flow
(getCenteredCanvasInsertionPoint, instance.commands.createRectangleNode, and
setting layout_* properties) unchanged.

---

Nitpick comments:
In `@editor/grida-canvas-react/__tests__/data-transfer-position.test.ts`:
- Around line 4-24: Add a hook-level regression (or a new manual case in
../test/) that exercises the real drop flow through use-data-transfer.ts rather
than only unit-testing getCenteredCanvasInsertionPoint: simulate an actual drop
of an inline image/SVG under non-identity zoom/pan, dispatch real
DragEvent/DropEvent objects carrying a file/dataTransfer and set clientX/clientY
(and pageX/pageY if needed), ensure the component/hook receives the event and
converts coordinates via clientPointToCanvasPoint, and assert the created
element uses the converted canvas position and the measured (or fallback)
dimensions; this will catch wiring mistakes like using wrong event coords or
wrong dimension sources in use-data-transfer.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb7ff988-5e6f-4c4a-85f2-7659e2e846c3

📥 Commits

Reviewing files that changed from the base of the PR and between 5e63162 and a373563.

📒 Files selected for processing (3)
  • editor/grida-canvas-react/__tests__/data-transfer-position.test.ts
  • editor/grida-canvas-react/data-transfer-position.ts
  • editor/grida-canvas-react/use-data-transfer.ts

Comment thread editor/grida-canvas-react/use-data-transfer.ts Outdated
Copy link
Copy Markdown
Contributor Author

k08200 commented Apr 30, 2026

Behavior verification update: I ran a temporary browser harness against the actual useInsertFile() / useDataTransferEventTarget() hook paths. With a 2x client-to-canvas transform, external image, SVG, and inline image data-transfer drops all landed at the expected centered canvas positions after converting client coordinates first.

@vercel vercel Bot temporarily deployed to Preview – backgrounds April 30, 2026 14:56 Inactive
@vercel vercel Bot temporarily deployed to Preview – viewer April 30, 2026 14:56 Inactive
@vercel vercel Bot temporarily deployed to Preview – blog April 30, 2026 14:56 Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
editor/grida-canvas-react/use-data-transfer.ts (1)

650-651: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate dropped image dimensions before applying them to node size.

Line 650 and Line 651 still allow 0, NaN, or Infinity from the transfer payload via ??, which can create invalid/invisible nodes at Line 665 and Line 666. Use a positive-finite guard before reusing payload dimensions.

Proposed fix
-              const imageWidth = width ?? imageRef.width;
-              const imageHeight = height ?? imageRef.height;
+              const imageWidth =
+                Number.isFinite(width) && width > 0 ? width : imageRef.width;
+              const imageHeight =
+                Number.isFinite(height) && height > 0
+                  ? height
+                  : imageRef.height;

Also applies to: 665-666

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas-react/use-data-transfer.ts` around lines 650 - 651, The
image dimension assignment (imageWidth = width ?? imageRef.width; imageHeight =
height ?? imageRef.height) can accept 0/NaN/Infinity from the transfer payload;
change these to guard for positive finite numbers before using payload values
(e.g. use Number.isFinite(width) && width > 0 ? width : imageRef.width, and
similarly for height) and apply the same positive-finite guard where node size
is set later (the code around setting node width/height at the lines
referenced). This ensures imageWidth/imageHeight and subsequent node size
assignments only use valid positive finite values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@editor/grida-canvas-react/use-data-transfer.ts`:
- Around line 650-651: The image dimension assignment (imageWidth = width ??
imageRef.width; imageHeight = height ?? imageRef.height) can accept
0/NaN/Infinity from the transfer payload; change these to guard for positive
finite numbers before using payload values (e.g. use Number.isFinite(width) &&
width > 0 ? width : imageRef.width, and similarly for height) and apply the same
positive-finite guard where node size is set later (the code around setting node
width/height at the lines referenced). This ensures imageWidth/imageHeight and
subsequent node size assignments only use valid positive finite values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3ab3dd3-481a-47b3-bc51-b571d8484f7e

📥 Commits

Reviewing files that changed from the base of the PR and between a373563 and 71607ee.

📒 Files selected for processing (1)
  • editor/grida-canvas-react/use-data-transfer.ts

@vercel vercel Bot temporarily deployed to Preview – blog April 30, 2026 15:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – viewer April 30, 2026 15:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – backgrounds April 30, 2026 15:10 Inactive
@vercel vercel Bot temporarily deployed to Preview – backgrounds April 30, 2026 15:16 Inactive
@vercel vercel Bot temporarily deployed to Preview – blog April 30, 2026 15:16 Inactive
@vercel vercel Bot temporarily deployed to Preview – viewer April 30, 2026 15:16 Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
editor/grida-canvas-react/use-data-transfer.ts (1)

659-664: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Await insertSVG and reject non-OK fetches in the SVG drop task.

At Line 659–Line 664, the promise chain resolves even if insertSVG later fails, so toast.promise can report success prematurely. Also, non-2xx responses are not treated as errors.

Proposed fix
-            const task = fetch(src, {
-              cache: "no-store",
-            }).then((res) =>
-              res.text().then((text) => {
-                insertSVG(name, text, dropPosition);
-              })
-            );
+            const task = fetch(src, { cache: "no-store" }).then(async (res) => {
+              if (!res.ok) {
+                throw new Error(`Failed to fetch SVG (${res.status})`);
+              }
+              const text = await res.text();
+              await insertSVG(name, text, dropPosition);
+            });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas-react/use-data-transfer.ts` around lines 659 - 664, The
task promise currently treats any fetch response as success and doesn't await
insertSVG, causing toast.promise to resolve prematurely; update the fetch chain
used to create the task (the const task = fetch(src, { cache: "no-store" }) ...
) to check res.ok and throw on non-2xx responses, await res.text(), then await
insertSVG(name, text, dropPosition) and propagate any thrown errors so the task
rejects on fetch or insert failures.
♻️ Duplicate comments (1)
editor/grida-canvas-react/use-data-transfer.ts (1)

677-678: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate payload dimensions before using them as inserted image size.

At Line 677 and Line 678, nullish fallback still accepts 0, NaN, and Infinity, which can produce invalid rectangle dimensions (even though centering logic guards half-size math).

Proposed fix
-              const imageWidth = width ?? imageRef.width;
-              const imageHeight = height ?? imageRef.height;
+              const imageWidth =
+                Number.isFinite(width) && width > 0 ? width : imageRef.width;
+              const imageHeight =
+                Number.isFinite(height) && height > 0
+                  ? height
+                  : imageRef.height;

Also applies to: 679-690

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas-react/use-data-transfer.ts` around lines 677 - 678, The
code currently assigns imageWidth = width ?? imageRef.width and imageHeight =
height ?? imageRef.height which still allows 0, NaN, or Infinity to be used;
update the insertion-dimension logic (the variables imageWidth/imageHeight and
the same logic used in the block handling payload dimensions around lines
679-690) to validate and sanitize the incoming width/height: accept the payload
value only if Number.isFinite(value) and value > 0, otherwise fall back to
imageRef.width/imageRef.height (or a safe positive default), and ensure the
centering/rect math uses these sanitized values to avoid invalid rectangle
sizes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@editor/grida-canvas-react/use-data-transfer.ts`:
- Around line 659-664: The task promise currently treats any fetch response as
success and doesn't await insertSVG, causing toast.promise to resolve
prematurely; update the fetch chain used to create the task (the const task =
fetch(src, { cache: "no-store" }) ... ) to check res.ok and throw on non-2xx
responses, await res.text(), then await insertSVG(name, text, dropPosition) and
propagate any thrown errors so the task rejects on fetch or insert failures.

---

Duplicate comments:
In `@editor/grida-canvas-react/use-data-transfer.ts`:
- Around line 677-678: The code currently assigns imageWidth = width ??
imageRef.width and imageHeight = height ?? imageRef.height which still allows 0,
NaN, or Infinity to be used; update the insertion-dimension logic (the variables
imageWidth/imageHeight and the same logic used in the block handling payload
dimensions around lines 679-690) to validate and sanitize the incoming
width/height: accept the payload value only if Number.isFinite(value) and value
> 0, otherwise fall back to imageRef.width/imageRef.height (or a safe positive
default), and ensure the centering/rect math uses these sanitized values to
avoid invalid rectangle sizes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b103a02-f7d4-4357-8e2f-a258a888a26e

📥 Commits

Reviewing files that changed from the base of the PR and between b5e8fad and 8e521f3.

📒 Files selected for processing (1)
  • editor/grida-canvas-react/use-data-transfer.ts

@vercel vercel Bot temporarily deployed to Preview – backgrounds April 30, 2026 15:29 Inactive
@vercel vercel Bot temporarily deployed to Preview – viewer April 30, 2026 15:29 Inactive
@vercel vercel Bot temporarily deployed to Preview – blog April 30, 2026 15:29 Inactive
@k08200
Copy link
Copy Markdown
Contributor Author

k08200 commented Apr 30, 2026

2026-05-01.12.30.51.mov

GOOD!!!!!!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
editor/grida-canvas-react/use-data-transfer.ts (1)

665-681: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate payload dimensions before reusing them.

width ?? imageRef.width and height ?? imageRef.height still allow 0, NaN, or Infinity to flow through, so a malformed drop payload can create a zero-sized image and center it incorrectly.

Suggested fix
-              const imageWidth = width ?? imageRef.width;
-              const imageHeight = height ?? imageRef.height;
+              const imageWidth =
+                Number.isFinite(width) && width > 0 ? width : imageRef.width;
+              const imageHeight =
+                Number.isFinite(height) && height > 0
+                  ? height
+                  : imageRef.height;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@editor/grida-canvas-react/use-data-transfer.ts` around lines 665 - 681, The
width/height fallbacks allow 0, NaN, or Infinity through; before computing
imageWidth/imageHeight in the async task, validate the incoming data.width and
data.height and the imageRef.width/imageRef.height using finite and positive
checks (e.g., Number.isFinite(...) && > 0) so that imageWidth =
valid(data.width) ? data.width : valid(imageRef.width) ? imageRef.width : a sane
default, and likewise for imageHeight; then pass those validated values into
centerCanvasPoint and insertImageRectangle (referencing createImageAsync,
centerCanvasPoint, and insertImageRectangle to locate the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@editor/grida-canvas-react/use-data-transfer.ts`:
- Around line 665-681: The width/height fallbacks allow 0, NaN, or Infinity
through; before computing imageWidth/imageHeight in the async task, validate the
incoming data.width and data.height and the imageRef.width/imageRef.height using
finite and positive checks (e.g., Number.isFinite(...) && > 0) so that
imageWidth = valid(data.width) ? data.width : valid(imageRef.width) ?
imageRef.width : a sane default, and likewise for imageHeight; then pass those
validated values into centerCanvasPoint and insertImageRectangle (referencing
createImageAsync, centerCanvasPoint, and insertImageRectangle to locate the
code).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a3141ad-d0a0-4957-a353-d02531a95114

📥 Commits

Reviewing files that changed from the base of the PR and between 8e521f3 and 231f326.

📒 Files selected for processing (4)
  • editor/grida-canvas-react/use-data-transfer.ts
  • editor/grida-canvas/__tests__/headless/node-properties.test.ts
  • editor/grida-canvas/action.ts
  • editor/grida-canvas/reducers/document.reducer.ts

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.

[Grida Canvas] Dropping (from external) insertion position does not handle the current transform accurately

1 participant