Skip to content

fix(drop-zone): do not intercept non-file drops - #6168

Merged
barmac merged 3 commits into
mainfrom
claude/issue-6166-dnd-menu-update-fix
Sep 4, 2026
Merged

fix(drop-zone): do not intercept non-file drops#6168
barmac merged 3 commits into
mainfrom
claude/issue-6166-dnd-menu-update-fix

Conversation

@barmac

@barmac barmac commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

🤖 This pull request was opened autonomously by an AI agent (Claude), based on human-reviewed analysis. Please review it with that in mind.

Proposed Changes

The app-wide drop zone (used to open a diagram by dragging a file onto the window, client/src/app/drop-zone/) wraps the whole editor area, so every native drop event happening inside it — including inside a nested dmn-js decision table — reaches it first.

DropHandler#handleDragOver and #handleDragLeave already skip non-file drags by checking isDragAllowed(event) before touching the event. #handleDrop was missing that same guard and always called event.stopPropagation(), regardless of what was being dragged. That silently killed dmn-js's own document-level drop listener for any internal (non-file) drag, so dragging a decision table row or column to reorder it never actually applied the move — even though the drag-over highlight looked correct the whole time, which made the bug easy to miss.

The fix mirrors the existing guard from handleDragOver/handleDragLeave onto handleDrop.

Closes #6166

Why this surfaced now, between 5.44 and 5.45

The defect itself is old — introduced in 2f320ff8 ("chore: move drop handling to a separate component", Sep 2024, first released in v5.29.0) — but it was harmless until 7b284466c ("deps: update to react@18", first released in v5.45.0, matching the reporter's bisect exactly).

That commit switched client/src/index.js from React 16's ReactDOM.render(<App/>, rootElement) to React 18's createRoot(rootElement).render(<App/>):

  • React 16 (through 5.44.0) attaches its own delegated native event listener to document — the same node table-js binds its decision-table drag-and-drop drop listener to. Multiple listeners on the same node all fire regardless of order, so DropHandler's buggy unconditional stopPropagation() never actually blocked table-js's sibling listener from also running. Dragging worked despite the latent bug.
  • React 18 with createRoot (from 5.45.0) moves that listener to the app's own root container, below document. Now the drop event hits React's listener — and DropHandler's stopPropagation() — before it can reach document, so table-js's listener never fires and the reorder command never applies.

The fix in this PR is unaffected by any of this — handleDrop should have had the same guard as its siblings all along, and fixing that is correct and sufficient regardless of which React root API is in use.

Full analysis history (including an earlier, since-corrected hypothesis) is on the issue: comment 1, correction, timeline.

Steps to try out

  1. Open or create a DMN diagram with a decision table containing at least two rules.
  2. Drag a rule's row-index handle (or a column header) to reorder it.
  3. Before this fix: the drag-over highlight shows, but releasing the mouse does not reorder the row/column. After this fix: the reorder is applied.

The included e2e test (test/e2e/specs/dmn-modeling.spec.js) automates this exact repro with a real (non-synthetic) Playwright-driven drag.

Checklist

Ensure you provide everything we need to review your contribution:

  • Contribution meets our definition of done
  • Pull request establishes context
    • Link to related issue(s), i.e. Closes {LINK_TO_ISSUE} or Related to {LINK_TO_ISSUE}
    • Brief textual description of the changes
    • Screenshots or short videos showing UI/UX changes (behavioral fix only, no UI change — covered instead by the new e2e test)
    • Steps to try out

Copilot AI lite review requested due to automatic review settings September 3, 2026 13:45
@bpmn-io-tasks bpmn-io-tasks Bot added the needs review Review pending label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

This Pull Request targets develop branch, but contains fix commits.

Consider targeting main instead.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The newly added tests have correctness/robustness issues (mocked event propagation and potentially false-positive assertions) that should be addressed before relying on them as regressions.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes an editor-wide drop zone regression where non-file drop events were being intercepted and stopped before they could reach nested editors (e.g. dmn-js decision table), preventing internal drag-and-drop operations like rule reordering. It also adds unit/e2e coverage to lock in the expected behavior.

Changes:

  • Add an isDragAllowed(event) guard to DropHandler#handleDrop so non-file drops are not prevented/stopped.
  • Add a DropZone unit test ensuring non-file drops bubble past the drop zone.
  • Add a Playwright e2e scenario (plus DMN fixture + page helpers) to verify decision table rule reordering after editing a cell.
File summaries
File Description
client/src/app/drop-zone/DropHandler.js Skip handling drop for non-file drags so internal DnD isn’t blocked.
client/src/app/drop-zone/tests/DropZoneSpec.js Add regression test asserting non-file drops bubble to outer listeners.
test/e2e/pages/DmnEditorPage.js Add DMN decision table helpers to edit a cell and drag-reorder a rule.
test/e2e/specs/dmn-modeling.spec.js Add e2e repro/assertion for rule reordering after cell edit + save verification.
test/e2e/fixtures/decision-table-rows.dmn Add minimal DMN fixture with two rules for reorder test.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread client/src/app/drop-zone/__tests__/DropZoneSpec.js
Comment thread test/e2e/pages/DmnEditorPage.js
Comment thread test/e2e/specs/dmn-modeling.spec.js Outdated
@barmac
barmac changed the base branch from develop to main September 3, 2026 13:55
@barmac

barmac commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 (human-reviewed) Addressed the Copilot review comments — see individual replies on each thread; fixed in f645c38.

Also retargeted this PR to main per the CI bot's suggestion, since it's a fix: commit and main is this repo's patch-release branch.

The commit that introduced the regression: 2f320ff8"chore: move drop handling to a separate component" (2024-09-27).

Before that commit, DropZone#handleDrop guarded itself with if (!this.state.draggingOver) { return; } — and draggingOver was only ever set to true inside handleDragOver after it had already checked isDragAllowed(event). So a non-file (internal) drag never set draggingOver, and the old handleDrop correctly skipped preventDefault/stopPropagation for it.

When drop handling was extracted into the new DropHandler class in that commit, this state-derived guard was dropped and never replaced with an equivalent explicit check — the new handleDrop started calling preventDefault/stopPropagation unconditionally, while handleDragOver kept (and handleDragLeave later gained, in a subsequent commit) the real isDragAllowed(event) check. That's the moment handleDrop diverged from its siblings and started swallowing every internal drag's drop event, including nested dmn-js decision table row/column reordering.

It sat unnoticed for a long time because it only breaks a drop inside something like a dmn-js decision table that itself listens on document — not something exercised by this component's own tests, which never drop anything other than files.

@barmac

barmac commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 (human-reviewed) Found the commit that actually made this regression user-visible at the reported version boundary.

The defect itself (DropHandler#handleDrop missing the isDragAllowed guard that its siblings have) was introduced back in 2f320ff8 (Sep 2024, first released in v5.29.0) — but it was harmless at the time, which is why nobody noticed it for a year and a half.

Under React 16 (ReactDOM.render, used through 5.44.0), React attaches its own delegated native listeners directly to document — the same node table-js binds its row/col-drag drop listener to. Multiple listeners bound to the same node for the same event/phase all fire regardless of order; stopPropagation() only blocks propagation to further ancestors, not sibling listeners on the node where it's called. So even though DropHandler#handleDrop was calling stopPropagation() unconditionally, it didn't matter — table-js's own document-level listener fired anyway, and the row/column reorder went through.

7b284466c — "deps: update to react@18" (first released in v5.45.0, matching @barmac's bisect exactly) switched client/src/index.js from ReactDOM.render(<App/>, rootElement) to createRoot(rootElement).render(<App/>). This is React's well-documented change (introduced in React 17, hit this app going straight from 16 to 18): the root API moves the delegated native listener off document and onto the app's own root container element. From that point on, a drop event bubbling up from inside a nested dmn-js decision table hits React's delegated listener before it reaches documentDropHandler#handleDrop's stopPropagation() now genuinely blocks the event from ever reaching table-js's document-level listener, and the reorder command never fires.

So: the code defect is 2f320ff8; the commit that made it user-visible, matching the exact 5.44 → 5.45 boundary, is 7b284466c. The fix in this PR is unaffected either way — handleDrop should have had the same guard as handleDragOver/handleDragLeave all along, and fixing that is correct and sufficient regardless of which React root API is in use.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is minimal and targeted, and it is backed by both a focused unit regression test and an end-to-end test covering the reported DMN drag-and-drop failure.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

The app-wide drop zone (used to open a diagram by dragging a file onto
the window) wraps the whole editor area, so every native `drop` event
happening inside it - including inside a nested dmn-js decision table -
reaches it first. `handleDragOver`/`handleDragLeave` already skip
non-file drags via `isDragAllowed()`, but `handleDrop` was missing that
guard and always called `stopPropagation()`. This silently broke
dmn-js's own document-level `drop` listener, so dragging a decision
table row/column never actually reordered it, even though the
drag-over highlight looked correct throughout the gesture.

The defect itself dates back to 2f320ff8
("chore: move drop handling to a separate component"), but stayed
harmless until 7b284466c ("deps: update to
react@18") switched the app from React 16's `ReactDOM.render` to React
18's `createRoot`, moving React's own delegated event listener off
`document` and onto the app's root container - which is what let
`stopPropagation()` here actually block dmn-js's `document`-level
listener from ever seeing the event.

Closes #6166

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

barmac commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 (human-reviewed) Opened a draft upstream PR in table-js addressing the same fragility at the source (scoping its drag-and-drop listeners to the table container instead of document, so no embedder can accidentally break it via stopPropagation()): bpmn-io/table-js#106. This PR here is independent and still needed regardless of that one's outcome.

@AlekseyManetov AlekseyManetov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Works great!

Screen.Recording.2026-09-04.at.11.17.14.mov

@barmac
barmac merged commit 70c7600 into main Sep 4, 2026
17 of 18 checks passed
@bpmn-io-tasks bpmn-io-tasks Bot removed the needs review Review pending label Sep 4, 2026
@barmac
barmac deleted the claude/issue-6166-dnd-menu-update-fix branch September 4, 2026 09:53
@github-actions github-actions Bot added this to the M106 milestone Sep 4, 2026
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.

DMN rules can not be reordered

3 participants