Skip to content

[8723] Handle errors when trying to paste an item in an invalid location for its content type#4890

Open
jvega190 wants to merge 5 commits into
craftercms:developfrom
jvega190:bugfix/8723
Open

[8723] Handle errors when trying to paste an item in an invalid location for its content type#4890
jvega190 wants to merge 5 commits into
craftercms:developfrom
jvega190:bugfix/8723

Conversation

@jvega190

@jvega190 jvega190 commented Jun 12, 2026

Copy link
Copy Markdown
Member

The error handler was correct, but the block/unblock UI actions were added to the new dialogs system, so the showing/removal wasn't working as expected in this scenario (and probably others).

Added error handling for this scenario, for the error message to be clear to the user.

This PR craftercms/studio#3956 is needed for the error code handling to work properly.

craftercms/craftercms#8723

Summary by CodeRabbit

Summary by CodeRabbit

  • Style

    • Improved how error messages wrap by adjusting text-breaking behavior for longer or unbroken content.
  • Bug Fixes

    • Enhanced clipboard error handling by displaying a more specific, destination-clarifying message for a particular error code, with an improved fallback for other errors.
  • Refactor

    • Removed the UI-blocking dialog flow and its related dialog-mapping/state handling to streamline dialog management.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR removes the dedicated UI blocker dialog infrastructure from the dialog system and enhances error handling for paste operations. The blockUI/unblockUI actions and their corresponding dialog mapping are eliminated, error message styling is refined for better text wrapping, and paste operation errors are now caught with special handling for specific error codes.

Changes

UI Error Handling and Dialog Management

Layer / File(s) Summary
Remove UIBlocker dialog infrastructure
ui/app/src/state/epics/mappedDialogs.ts
The blockUI and unblockUI imports are removed, the blockUI.type entry is deleted from dialogsMap, and the entire // region UIBlocker epic logic that handled opening/closing UI blocker dialogs on those actions is eliminated.
Error state styling and paste error handling
ui/app/src/components/ErrorState/ErrorState.tsx, ui/app/src/state/epics/content.ts
Error message text styling changes from wordBreak: 'break-all' to wordBreak: 'break-word' for improved text wrapping, and the pasteItem epic now detects AJAX error code 56001 to display a tailored error message about disallowed content type transfers, while preserving default error behavior for other codes.

Possibly related PRs

  • craftercms/studio-ui#4349: Introduced the dialog-system mappedDialogs epic structure with UIBlocker handling that this PR removes.

Suggested reviewers

  • rart

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR description lacks detailed explanation of changes and uses external issue references instead of describing what was changed in the codebase. Expand description to explain: (1) what changes were made to ErrorState.tsx, content.ts, and mappedDialogs.ts; (2) why these changes fix the issue; (3) how the wordBreak style change and error handling improvements work together.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: handling errors when pasting items in invalid locations for their content type, directly addressing issue #8723.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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

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

@jvega190

Copy link
Copy Markdown
Member Author

@coderabbitai can you check if everything looks good? Please give it a final review before I hand over for human review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the full PR diff
gh pr diff 4890 --repo craftercms/studio-ui

Length of output: 2375


🏁 Script executed:

#!/bin/bash
# Read the full content of the three changed files
echo "=== ErrorState.tsx ==="
cat ui/app/src/components/ErrorState/ErrorState.tsx

echo ""
echo "=== content.ts (epics) ==="
cat ui/app/src/state/epics/content.ts

echo ""
echo "=== mappedDialogs.ts ==="
cat ui/app/src/state/epics/mappedDialogs.ts

Length of output: 33895


🏁 Script executed:

#!/bin/bash
# Check where extractErrorPayload is used in content.ts
rg -n "extractErrorPayload" ui/app/src/state/epics/content.ts

# Check how blockUI is used elsewhere in the codebase
echo "=== blockUI usages ==="
rg -n "blockUI\|unblockUI" --type ts -C2 ui/app/src/

# Check if there's another handler for blockUI
echo "=== blockUI action/slice ==="
rg -rn "blockUI" --type ts ui/app/src/state/

Length of output: 2869



@jvega190 Overall the changes look solid and accomplish the stated goal. I found two minor issues to clean up before handing off:


✅ What looks good

  1. ErrorState.tsxwordBreak: 'break-word' is the correct fix. break-all aggressively splits anywhere (including mid-word), while break-word only breaks when necessary, which is much better for error messages containing long paths or URLs.

  2. pasteItem error handling — The epic now properly routes Ajax errors to pushErrorDialog, which is the core fix for issue #8723.

  3. mappedDialogs.ts UIBlocker cleanup — Removing the dialog-stack-based UIBlocker in favour of the direct state-driven reducer (uiBlocker.ts) is a cleaner architecture.


⚠️ Issues to address

1. Unused import: extractErrorPayload in content.ts

extractErrorPayload is imported but never called anywhere in the file. This is likely a leftover from development iterations.

// Line 45 — remove the unused import
import { catchAjaxError } from '../../utils/ajax';

2. Dead imports: blockUI and unblockUI in mappedDialogs.ts

After removing the UIBlocker epic handlers and the dialogsMap entry, both blockUI and unblockUI are still imported at the top of the file but are no longer referenced anywhere in it. These should be removed:

// Remove this entire line from mappedDialogs.ts
import { blockUI, unblockUI } from '../actions/system';

Summary

File Status
ErrorState.tsx ✅ Good to go
content.ts ⚠️ Remove unused extractErrorPayload import
mappedDialogs.ts ⚠️ Remove unused blockUI / unblockUI imports

Two small cleanups and this is ready for human review. 🐇

@jvega190

Copy link
Copy Markdown
Member Author

@coderabbitai can you check if everything looks good? Please give it a final review before I hand over for human review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh pr diff 4890 --repo craftercms/studio-ui

Length of output: 2267


🏁 Script executed:

#!/bin/bash
# Check current state of content.ts - look for extractErrorPayload and pasteItem error handling
rg -n "extractErrorPayload\|pasteItem\|pushErrorDialog\|catchAjaxError" ui/app/src/state/epics/content.ts

Length of output: 456


🏁 Script executed:

#!/bin/bash
# Find the correct path for content.ts epics file
fd "content.ts" ui/app/src/state/epics/

echo "=== Check imports section ==="
head -60 ui/app/src/state/epics/content.ts

echo "=== Check pasteItem area ==="
rg -n "paste" ui/app/src/state/epics/content.ts | head -30

Length of output: 2709


🏁 Script executed:

#!/bin/bash
# Read the pasteItem epic section
sed -n '410,520p' ui/app/src/state/epics/content.ts

Length of output: 3535



@jvega190 Everything looks good now — all the issues from my previous review have been addressed. Here's the final state:

File Status Notes
ErrorState.tsx ✅ Clean wordBreak: 'break-word' fix in place
content.ts ✅ Clean Unused extractErrorPayload import removed; pasteItem error handling with pushErrorDialog is correctly wired
mappedDialogs.ts ✅ Clean blockUI/unblockUI imports removed, UIBlocker dialog-stack logic removed

The diff is minimal and focused. Ready to hand off for human review. 🐇

@jvega190 jvega190 marked this pull request as ready for review June 12, 2026 22:13
@jvega190 jvega190 requested a review from rart as a code owner June 12, 2026 22:13
Comment on lines -278 to -299
// region UIBlocker
(action$, state$) =>
action$.pipe(
ofType(blockUI.type),
withLatestFrom(state$),
map(([{ payload, type }]) => {
return pushDialog({
id: blockUI.type,
component: dialogsMap[type],
props: payload
});
})
),
(action$, state$) =>
action$.pipe(
ofType(unblockUI.type),
withLatestFrom(state$),
map(() => {
return popDialog({ id: blockUI.type });
})
)
// end region

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's this got to do with paste?
Why is this being removed?
Where is it handled if not here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The actions were added to the dialogs system, but we left some existing dialogs in the GlobalDialogManager (ItemMegaMenu, AuthMonitor, Laucher, etc) including the UI Blocker. So it was duplicated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Haven't gotten an answer to where the UI blocker via dispatch is handled

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The UIBlocker is still in the GlobalDialogManager. When dispatching the action there's a uiBlocker reducer that updates the state (opens the blocker), and the component in the GlobalDialogManager gets the updated state and shows/hides according to the action dispatched

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ui/app/src/state/epics/content.ts`:
- Around line 443-444: The code unconditionally accesses error.response.code at
line 443 without first verifying that error.response exists, which will throw an
exception for network/offline/CORS failures where the response property is
absent, breaking the error handling flow. Add a guard clause to check if
error.response is defined before attempting to access its code property. This
pattern also needs to be fixed at the second location mentioned around line 470.
Use optional chaining or an explicit null/undefined check to safely access the
code property only when the response object exists.
- Around line 457-464: The sourceContentType variable passed to the
formatMessage interpolation in the remedialAction block can be undefined when
the clipboard source item is not found in itemsByPath, causing the message to
display "Content type "undefined"...". Add a fallback value for
sourceContentType before passing it to the formatMessage call using a logical OR
operator or ternary expression to provide a default string like "Unknown" when
sourceContentType is undefined.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 87af99c7-91f7-44d4-8d03-2d22de7c19b8

📥 Commits

Reviewing files that changed from the base of the PR and between d6927d2 and 8e67f4e.

📒 Files selected for processing (2)
  • ui/app/src/state/epics/content.ts
  • ui/app/src/state/epics/mappedDialogs.ts
💤 Files with no reviewable changes (1)
  • ui/app/src/state/epics/mappedDialogs.ts

Comment thread ui/app/src/state/epics/content.ts Outdated
Comment thread ui/app/src/state/epics/content.ts Outdated
@jvega190

Copy link
Copy Markdown
Member Author

@coderabbitai can you check if everything looks good? Please give it a final review before I hand over for human review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current PR status and latest commits
gh pr view 4890 --repo craftercms/studio-ui --json commits,headRefName,updatedAt | jq '.commits[-3:], .updatedAt'

# Get current diff to verify state
gh pr diff 4890 --repo craftercms/studio-ui

Length of output: 5875


🏁 Script executed:

#!/bin/bash
echo "=== AjaxError import check in content.ts ==="
head -55 ui/app/src/state/epics/content.ts | grep -n "AjaxError\|ajax\|rxjs"

echo ""
echo "=== remedialAction usage across codebase ==="
rg -n "remedialAction" --type ts --type tsx -C3

echo ""
echo "=== ErrorDialog / pushErrorDialog props type ==="
rg -n "remedialAction\|ErrorDialogProps\|ErrorDialogStateProps" --type ts -C2 ui/app/src/

echo ""
echo "=== Check ErrorState component for remedialAction ==="
cat ui/app/src/components/ErrorState/ErrorState.tsx

Length of output: 4138


🏁 Script executed:

#!/bin/bash
echo "=== Full imports section of content.ts ==="
sed -n '1,60p' ui/app/src/state/epics/content.ts

echo ""
echo "=== remedialAction in all files ==="
rg -rn "remedialAction" ui/app/src/ -C3

echo ""
echo "=== ErrorDialog component and its props ==="
fd "ErrorDialog" ui/app/src/ --extension ts --extension tsx

echo ""
echo "=== pushErrorDialog action definition ==="
rg -rn "pushErrorDialog" ui/app/src/state/actions/ -C3

echo ""
echo "=== Error type/interface used in dialogs ==="
rg -rn "interface.*Error\b|type.*Error\b" ui/app/src/models/ -C2

Length of output: 10996


🏁 Script executed:

#!/bin/bash
echo "=== ErrorDialog component full content ==="
cat ui/app/src/components/ErrorDialog/ErrorDialog.tsx

echo ""
echo "=== ApiResponse model ==="
cat ui/app/src/models/ApiResponse.ts

echo ""
echo "=== AjaxError import check — more of content.ts imports ==="
sed -n '55,80p' ui/app/src/state/epics/content.ts

echo ""
echo "=== extractErrorPayload function signature ==="
rg -n "extractErrorPayload" ui/app/src/utils/ajax.ts -A10

Length of output: 5006


🏁 Script executed:

#!/bin/bash
echo "=== Search for AjaxError import in content.ts ==="
rg -n "AjaxError" ui/app/src/state/epics/content.ts

echo ""
echo "=== ApiResponseErrorState component — how it uses remedialAction ==="
cat ui/app/src/components/ApiResponseErrorState/ApiResponseErrorState.tsx

Length of output: 3783



@jvega190 Just verified the two new commits — everything checks out. Here's the final state:

File Status Notes
ErrorState.tsx ✅ Clean wordBreak: 'break-word' in place
content.ts ✅ Clean AjaxError properly imported (line 85); extractErrorPayload imported and used in the else branch; remedialAction is a valid field on ApiResponse and correctly rendered by ApiResponseErrorState; optional chaining on error.response?.code is a nice defensive improvement
mappedDialogs.ts ✅ Clean blockUI/unblockUI imports removed; UIBlocker dialog-stack logic removed

The two new commits refined the error handling nicely — the optional chaining and the use of extractErrorPayload in the fallback path are both solid improvements. Ready to hand off for human review. 🐇

@jvega190 jvega190 requested a review from rart June 18, 2026 21:54
textAlign: 'center',
marginBottom: (theme) => theme.spacing(1),
wordBreak: 'break-all',
wordBreak: 'break-word',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This style might have been in place for super long exceptions. Why change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

break-all causes it to break at any point of the text, causing it to look like this:
Screenshot from 2026-06-25 12-06-49
by setting it to break-word, it won't split words, like this:
Screenshot from 2026-06-25 12-05-59

Comment on lines -278 to -299
// region UIBlocker
(action$, state$) =>
action$.pipe(
ofType(blockUI.type),
withLatestFrom(state$),
map(([{ payload, type }]) => {
return pushDialog({
id: blockUI.type,
component: dialogsMap[type],
props: payload
});
})
),
(action$, state$) =>
action$.pipe(
ofType(unblockUI.type),
withLatestFrom(state$),
map(() => {
return popDialog({ id: blockUI.type });
})
)
// end region

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Haven't gotten an answer to where the UI blocker via dispatch is handled

@jvega190 jvega190 requested a review from rart June 25, 2026 18:09
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.

2 participants