Skip to content

[8023] Image cropper dialog does not open when using XB#4843

Open
jvega190 wants to merge 13 commits into
craftercms:developfrom
jvega190:bugfix/8023
Open

[8023] Image cropper dialog does not open when using XB#4843
jvega190 wants to merge 13 commits into
craftercms:developfrom
jvega190:bugfix/8023

Conversation

@jvega190

@jvega190 jvega190 commented Apr 21, 2026

Copy link
Copy Markdown
Member

craftercms/craftercms#8023

Summary by CodeRabbit

  • New Features
    • Image editor dialog now opens when images fail configured size/dimension restrictions during browse, search, upload, and drag/drop; edited images are then applied to the experience and uploaded with progress.
  • Refactor
    • Centralized image restriction validation for reuse across all image flows.
    • Upload creation now prefers an existing blob, falling back to data-URL conversion only when needed.
  • Bug Fixes
    • Properly revokes temporary blob/object URLs to prevent lingering URLs.
  • Documentation
    • Subtitle for the image editor now displays restriction-specific width/height messaging only when applicable.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@jvega190, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 54 minutes and 20 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ab95d39-4d9f-4846-8995-987f4c7d030b

📥 Commits

Reviewing files that changed from the base of the PR and between c6402fd and d003023.

📒 Files selected for processing (9)
  • ui/app/src/components/FormsEngine/controls/ImagePicker.tsx
  • ui/app/src/components/FormsEngine/lib/controlHelpers.tsx
  • ui/app/src/components/ImageEditorDialog/ImageEditorDialogContainer.tsx
  • ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx
  • ui/app/src/services/content.ts
  • ui/app/src/state/actions/dialogs.ts
  • ui/app/src/utils/content.ts
  • ui/guest/src/react/ExperienceBuilder.tsx
  • ui/guest/src/store/epics/root.ts

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

Image restriction validation is centralized and wired into editor, preview, and guest upload flows. New dialog actions and subtitle rendering support opening the image editor on validation failure, and edited images are uploaded through the guest epic flow.

Changes

Image restriction validation and editor upload flow

Layer / File(s) Summary
Shared image validation utility
ui/app/src/utils/content.ts
Adds validateImageRestrictions(path, restrictions?) with size checks and image loading timeout behavior.
Redux dialog actions
ui/app/src/state/actions/dialogs.ts
Adds typed payloads and action creators for showing the image editor, reporting edits, and handling cancellation.
Editor subtitle and dialog wiring
ui/app/src/components/FormsEngine/lib/controlHelpers.tsx, ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx
Adds ImageRestrictionSubtitle, uses it in crop dialog subtitle rendering, and opens ImageEditorDialog from PreviewConcierge with crop/close callbacks.
Image editor cleanup and upload blob source
ui/app/src/components/ImageEditorDialog/ImageEditorDialogContainer.tsx, ui/app/src/services/content.ts
Revokes blob URLs in the image editor container and prefers file.blob when building uploads.
Guest host-message routing
ui/guest/src/react/ExperienceBuilder.tsx
Whitelists imageEdited and imageEditCancelled host messages for dispatch.
Guest epic imports and helpers
ui/guest/src/store/epics/root.ts
Adds the validation, dialog, cleanup, and drop-zone imports used by the updated epic flow.
Detached asset placement validation
ui/guest/src/store/epics/root.ts
Validates detached image placement against field restrictions and opens the image editor when restrictions fail.
Desktop upload validation
ui/guest/src/store/epics/root.ts
Validates desktop uploads before the existing upload policy flow and opens the image editor on restriction failure.
Edited image upload handling
ui/guest/src/store/epics/root.ts
Uploads edited blobs, handles progress and completion, and treats cancellation as a failed upload.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant PreviewConcierge
  participant ExperienceBuilder
  participant GuestEpic
  participant validateImageRestrictions
  participant ImageEditorDialog
  participant uploadDataUrl

  User->>PreviewConcierge: request image edit
  PreviewConcierge->>ImageEditorDialog: open dialog with restrictions
  User->>ImageEditorDialog: crop or cancel
  ImageEditorDialog->>ExperienceBuilder: imageEdited / imageEditCancelled
  ExperienceBuilder->>GuestEpic: dispatch action
  GuestEpic->>validateImageRestrictions: validate path or dataUrl
  alt restrictions fail
    validateImageRestrictions-->>GuestEpic: false
    GuestEpic->>ImageEditorDialog: show editor flow
  else restrictions pass
    validateImageRestrictions-->>GuestEpic: true
    GuestEpic->>uploadDataUrl: upload image or edited blob
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • craftercms/studio-ui#4630 — This PR externalizes the image restriction validation used by ImagePicker into ui/app/src/utils/content.ts, which is directly connected to the shared validator used here.

Suggested reviewers

  • rart
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing the image cropper dialog issue in XB.
Description check ✅ Passed The PR description includes the required ticket reference link, which satisfies the repository template.
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.

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx`:
- Around line 1133-1165: Add an onClose/cancel path to the ImageEditorDialog
push so cancelling the editor clears the dialog and notifies the guest to stop
the pending upload; specifically, in the showImageEditorDialog.type branch
(where pushDialog and createComponentId('ImageEditorDialog') are used and onCrop
currently sends hostToGuest$.next with imageEdited.type and then
dispatch(popDialog({ id }))), add an onClose/onCancel prop that dispatches
popDialog({ id }) and sends a hostToGuest$.next message (e.g.,
imageEditCancelled.type or a desktopAssetUploadFailed/ended message) with the
original fileName/recordId/uploadPath so the guest side can clean up the started
upload/drag state. Ensure the new message type aligns with the guest epic's
expected cancellation/failure event.

In `@ui/app/src/state/actions/dialogs.ts`:
- Around line 395-397: Update the action payload typings to match actual usage:
widen showImageEditorDialog (created by createAction in the symbol
showImageEditorDialog) from Partial<ImageEditorDialogBaseProps> to include
fileName, recordId, and uploadPath (or replace with a new interface that extends
ImageEditorDialogBaseProps plus these fields) so callers in the guest epic no
longer produce excess property errors; likewise broaden imageEdited (created by
createAction in the symbol imageEdited) from { blob: Blob; path?: string | null
} to include newPath, fileName, recordId, and uploadPath (or define and use a
richer ImageEditedPayload interface) so the host/guest flow and the
destructuring in PreviewConcierge.tsx align with the declared contract.

In `@ui/guest/src/store/epics/root.ts`:
- Around line 293-302: Remove the premature desktopAssetUploadStarted emission
from the branches that call showImageEditorDialog (the block posting
showImageEditorDialog with path/restrictions/fileName/recordId/uploadPath) and
instead dispatch the action that ends the drag state (the existing
desktop-drag-end action used elsewhere in this file) so the UI exits drag mode
when the editor opens; then add desktopAssetUploadStarted({ record }) into the
imageEdited epic's upload path so the upload-start is emitted only when the user
actually commits the crop.
- Around line 538-541: The policy validation is constructing the target path
using blob.name but blob is a Blob (no .name) causing `/undefined`; update the
call to validateActionPolicy to use the carried imageFileName from the action
payload instead of blob.name (i.e. target:
ensureSingleSlash(`${path}/${imageFileName}`)), keeping state.activeSite and
ensureSingleSlash as-is so the policy target is correct.
🪄 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: 4b653b4a-75cb-4bba-a58d-1b413f55d4f4

📥 Commits

Reviewing files that changed from the base of the PR and between 5c8cbd6 and 9308684.

📒 Files selected for processing (7)
  • ui/app/src/components/FormsEngine/controls/ImagePicker.tsx
  • ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx
  • ui/app/src/services/content.ts
  • ui/app/src/state/actions/dialogs.ts
  • ui/app/src/utils/content.ts
  • ui/guest/src/react/ExperienceBuilder.tsx
  • ui/guest/src/store/epics/root.ts

Comment thread ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx
Comment thread ui/app/src/state/actions/dialogs.ts Outdated
Comment thread ui/guest/src/store/epics/root.ts Outdated
Comment thread ui/guest/src/store/epics/root.ts

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/src/utils/content.ts`:
- Around line 1304-1307: The current validateImageRestrictions function bypasses
validation when isImage(path) returns false, but isImage only checks file
extensions so blob: and data:image/ URLs (created by URL.createObjectURL and
FileReader.readAsDataURL) slip through; update validateImageRestrictions to
treat blob: and data:image/* URLs as images by checking path.startsWith('blob:')
or path.startsWith('data:image/') (or extend isImage to include those schemes),
and only skip validation for non-image schemes; ensure the dimension/crop checks
still run for blob/data URLs when restrictions are provided.
🪄 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: 2fafcfab-76f3-4ecb-8ce3-daa48bd39bf4

📥 Commits

Reviewing files that changed from the base of the PR and between 9308684 and 92e4620.

📒 Files selected for processing (1)
  • ui/app/src/utils/content.ts

Comment thread ui/app/src/utils/content.ts
@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 19, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== PR Diff ==="
gh pr diff 4843 --repo craftercms/studio-ui

echo ""
echo "=== PR Files Changed ==="
gh pr view 4843 --repo craftercms/studio-ui --json files --jq '.files[].path'

Length of output: 29794



@jvega190 Good work overall — the overall approach (centralizing validateImageRestrictions, the host↔guest round-trip via showImageEditorDialog/imageEdited/imageEditCancelled, and deferring desktopAssetUploadStarted until after crop confirmation) is sound. I did find a couple of issues worth fixing before handover.


🐛 Bug 1 (Critical) — PLACING_DETACHED_ASSET: dropZone accessed before null-check, and record is undefined

In ui/guest/src/store/epics/root.ts, iceRecord is now assigned outside the if (dropZone && dragContext.inZone) guard, and the showImageEditorDialog call inside it references record.id which is never defined in that scope.

case EditingStatus.PLACING_DETACHED_ASSET: {
    const { dropZone } = dragContext;
    // 🐛 dropZone could be null here — accessing dropZone.iceId will throw
    const iceRecord = iceRegistry.getById(dropZone.iceId);

    if (dropZone && dragContext.inZone) {
        // ...
        post(showImageEditorDialog({
            path,
            restrictions,
            fileName: name,
            recordId: record.id,   // 🐛 `record` is undefined — should be `iceRecord.id`
            uploadPath
        }));

Suggested fix:

case EditingStatus.PLACING_DETACHED_ASSET: {
    const { dropZone } = dragContext;
    if (dropZone && dragContext.inZone) {
        const iceRecord = iceRegistry.getById(dropZone.iceId); // moved back inside guard
        // ...
        post(showImageEditorDialog({
            path,
            restrictions,
            fileName: name,
            recordId: iceRecord.id,  // ✅ use iceRecord.id
            uploadPath
        }));

🐛 Bug 2 (Minor) — Blob URL leak in UPLOAD_ASSET_FROM_DESKTOP and imageEdited epic

URL.createObjectURL() is called in two places but never revoked:

  1. UPLOAD_ASSET_FROM_DESKTOP — when restrictions fail:

    const url = URL.createObjectURL(file);
    post(showImageEditorDialog({ path: url, ... }));
    // url is never URL.revokeObjectURL()'d

    The ImageEditorDialog (or its onClose/onCrop handlers) should revoke this URL after use.

  2. imageEdited epic — preview src update:

    aImg.src = URL.createObjectURL(blob);
    // previous blob URL (if any) is never revoked, new one is also never revoked

    Consider capturing the old src before overwriting and revoking the previous blob URL if it starts with blob:.


⚠️ Issue 3 (Minor) — Subtitle message hardcodes "width & height"

In PreviewConcierge.tsx:

defaultMessage="The image does not meet the width & height constraints (Width: {width}. Height: {height})."

This message always says "width & height" even when only one dimension has a restriction. Consider using two separate conditional messages (width-only, height-only, or both) based on what restrictions actually contains, or adjust getImageRestrictionMessages to return a combined human-readable string directly.


✅ What looks good

  • Centralizing validateImageRestrictions to utils/content.ts and adding the early-exit for non-image paths is clean and correct.
  • The file.blob ?? dataUriToBlob(file.dataUrl) change in createFileUpload is the right fix to pass the already-cropped blob directly.
  • The imageEditCancelled epic correctly ends the drag and emits desktopAssetUploadFailed.
  • Whitelisting imageEdited.type and imageEditCancelled.type in ExperienceBuilder.tsx is the correct approach.
  • The desktopAssetUploadStarted is now properly deferred until after restrictions pass — this was the core timing bug.

The critical item to fix before handover is Bug 1 (the record.id / dropZone null-access in the PLACING_DETACHED_ASSET case). The blob URL leak is worth addressing too, but is lower severity.

@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 19, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== PR Files Changed ==="
gh pr view 4843 --repo craftercms/studio-ui --json files --jq '.files[].path'

echo ""
echo "=== Current PR Diff ==="
gh pr diff 4843 --repo craftercms/studio-ui

Length of output: 34071



@jvega190 Good progress — two of the three previously flagged items are now resolved. Here's the current status:


✅ Fixed from previous review

  1. Bug 2 (Blob URL leak) — Fixed in both places:

    • ImageEditorDialogContainer.tsx revokes the blob URL on unmount via useEffect(() => { if (path && isBlobUrl(path)) return () => URL.revokeObjectURL(path); }, [path]).
    • imageEdited epic cleans up the preview URL with finalize(() => URL.revokeObjectURL(previewUrl)). ✅
  2. Issue 3 (Subtitle hardcoding "width & height") — Fixed cleanly with the new ImageRestrictionSubtitle component in controlHelpers.tsx that handles width-only, height-only, and both-dimensions cases. ✅


🐛 Still unresolved — Critical

PLACING_DETACHED_ASSET: record.id is still undefined in scope

In ui/guest/src/store/epics/root.ts, the else-branch of meetsRestrictions still references record.id — but only iceRecord is declared in this case block:

// `iceRecord` is declared above (line ~264), but `record` never is
post(
    showImageEditorDialog({
        path,
        restrictions,
        fileName: name,
        recordId: record.id,   // 🐛 ReferenceError at runtime — `record` is not in scope
        uploadPath
    })
);

Fix:

recordId: iceRecord.id,  // ✅

⚠️ New minor finding — Type gap in PreviewConcierge.tsx

restrictions from ShowImageEditorDialogPayload is typed as optional (via Partial<ImageEditorDialogBaseProps>), but ImageRestrictionSubtitle expects it as required (restrictions: ImageRestrictions). If restrictions is ever undefined, both getImageRestrictionMessages(undefined) and hasWidthRestriction(undefined) will throw at runtime.

In practice all current callers pass restrictions, but the type gap is a latent risk. A simple guard would close it:

subtitle: restrictions ? <ImageRestrictionSubtitle restrictions={restrictions} /> : undefined,

Summary

Item Status
record.idiceRecord.id in PLACING_DETACHED_ASSET ❌ Still needs fix
ImageRestrictionSubtitle with undefined restrictions guard ⚠️ Minor — worth fixing
Blob URL leaks ✅ Fixed
Subtitle conditional messages ✅ Fixed
createFileUpload blob fallback ✅ Correct
imageEditCancelled epic ✅ Correct
imageEdited epic structure ✅ Correct
XB whitelist entries ✅ Correct

One-line fix remaining before this is ready for human review.

@jvega190

Copy link
Copy Markdown
Member Author

@coderabbitai about this item PLACING_DETACHED_ASSET: record.id is still undefined in scope, it is not undefined, it is available in the payload of the 'drop' action. Can you please give it a final review before I hand over for human review, considering this detail?

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Current PR Diff ==="
gh pr diff 4843 --repo craftercms/studio-ui

echo ""
echo "=== root.ts PLACING_DETACHED_ASSET section ==="
gh api repos/craftercms/studio-ui/contents/ui/guest/src/store/epics/root.ts \
  -H "Accept: application/vnd.github.raw" \
  --jq '.' 2>/dev/null | head -n 600 || true

Length of output: 33815



@jvega190 You're right — looking at the code more carefully, record is the element registry record from the outer drop action handler scope, while iceRecord is the ICE registry record from iceRegistry.getById(dropZone.iceId). They are different entities. The imageEdited epic uses const record = get(recordId) from elementRegistry, so passing record.id (element record) is correct, not iceRecord.id (ICE record). I'm withdrawing that concern.


✅ Final Review — All Clear

Item Status
record.id in PLACING_DETACHED_ASSET ✅ Valid — record is in scope from the drop action payload
Blob URL leak (UPLOAD_ASSET_FROM_DESKTOP) ✅ Fixed via useEffect cleanup in ImageEditorDialogContainer
Blob URL leak (imageEdited epic) ✅ Fixed via finalize(() => URL.revokeObjectURL(previewUrl))
Subtitle conditional messages ImageRestrictionSubtitle correctly handles width-only, height-only, both
restrictions guard in PreviewConcierge.tsx restrictions ? <ImageRestrictionSubtitle .../> : undefined already present
createFileUpload blob fallback file.blob ?? dataUriToBlob(file.dataUrl)
imageEditCancelled epic ✅ Correctly ends drag and emits failure
imageEdited epic structure ✅ Policy validation, preview URL management, upload flow all correct
validateImageRestrictions centralized to utils/content.ts ✅ Clean, proper early-exit for non-image paths
XB whitelist entries imageEdited.type and imageEditCancelled.type routed correctly
Deferred desktopAssetUploadStarted ✅ Core timing fix is correct

The PR looks good to hand over for human review. 🐇

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Current diff ==="
gh pr diff 4843 --repo craftercms/studio-ui

echo ""
echo "=== PLACING_DETACHED_ASSET block in root.ts ==="
gh api repos/craftercms/studio-ui/contents/ui/guest/src/store/epics/root.ts \
  -H "Accept: application/vnd.github.raw" \
  --jq . 2>/dev/null | head -400 || true

Length of output: 33807



@jvega190 You're absolutely right — I apologize for the confusion. Looking at the epic structure more carefully, record is indeed declared in the outer scope of the switch block from the drop action payload, making record.id (the element registry record ID) fully in scope in the PLACING_DETACHED_ASSET case. This is also consistent with how the imageEdited epic retrieves it back via get(recordId). My concern was incorrect.


✅ Final Review — Everything looks good

Item Status
record.id in PLACING_DETACHED_ASSET ✅ Valid — outer-scope element record
Blob URL leak (UPLOAD_ASSET_FROM_DESKTOP) ✅ Fixed — revoked by ImageEditorDialogContainer unmount
Blob URL leak (imageEdited epic) ✅ Fixed — finalize(() => URL.revokeObjectURL(previewUrl))
Subtitle conditional messages ✅ Fixed — ImageRestrictionSubtitle handles width-only, height-only, both
restrictions guard in PreviewConcierge.tsx ✅ Fixed — restrictions ? <ImageRestrictionSubtitle .../> : undefined
desktopAssetUploadStarted timing ✅ Correctly deferred until restrictions pass
createFileUpload blob fallback file.blob ?? dataUriToBlob(file.dataUrl)
validateImageRestrictions centralization ✅ Clean extraction to utils/content.ts
imageEditCancelled epic ✅ Correctly ends drag and emits upload failed
XB whitelist entries imageEdited.type and imageEditCancelled.type added

The PR is ready for human review. 🐇 ✨

@jvega190 jvega190 marked this pull request as ready for review June 19, 2026 18:07
@jvega190 jvega190 requested a review from rart as a code owner June 19, 2026 18:07

@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: 1

🤖 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/components/FormsEngine/lib/controlHelpers.tsx`:
- Line 405: The ImageRestrictionSubtitle component is being rendered
unconditionally at line 405 even though the restrictions parameter can be
undefined based on the showImageCropDialog function signature at line 393. Since
ImageRestrictionSubtitle dereferences restrictions at lines 447-449, this will
cause a runtime error when restrictions is not provided. Add a conditional check
to only render the subtitle property with ImageRestrictionSubtitle when
restrictions is defined, otherwise render nothing or null for the subtitle.
🪄 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: 4535ad13-2fad-4617-bc86-d242d79546ef

📥 Commits

Reviewing files that changed from the base of the PR and between 028e823 and f9a0290.

📒 Files selected for processing (8)
  • ui/app/src/components/FormsEngine/lib/controlHelpers.tsx
  • ui/app/src/components/ImageEditorDialog/ImageEditorDialogContainer.tsx
  • ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx
  • ui/app/src/services/content.ts
  • ui/app/src/state/actions/dialogs.ts
  • ui/app/src/utils/content.ts
  • ui/guest/src/react/ExperienceBuilder.tsx
  • ui/guest/src/store/epics/root.ts
💤 Files with no reviewable changes (2)
  • ui/app/src/state/actions/dialogs.ts
  • ui/app/src/utils/content.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • ui/app/src/services/content.ts
  • ui/guest/src/react/ExperienceBuilder.tsx
  • ui/guest/src/store/epics/root.ts

Comment thread ui/app/src/components/FormsEngine/lib/controlHelpers.tsx Outdated
@rart

rart commented Jun 25, 2026

Copy link
Copy Markdown
Member

@coderabbitai, don't see your review of d003023. Please look through this PR and report findings and if ready for merge

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="craftercms/studio-ui"
pr="4843"

echo "=== PR metadata ==="
gh pr view "$pr" --repo "$repo" --json number,title,headRefName,baseRefName,author,mergeStateStatus,reviewDecision,statusCheckRollup,commits \
  --jq '{
    number,
    title,
    headRefName,
    baseRefName,
    author: .author.login,
    mergeStateStatus,
    reviewDecision,
    checks: (.statusCheckRollup // [] | map({name: .name, status: .status, conclusion: .conclusion})),
    commits: [.commits[].oid[0:7]]
  }'

echo
echo "=== PR commit list ==="
gh pr view "$pr" --repo "$repo" --json commits \
  --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline, committedDate: .committedDate}'

echo
echo "=== Specific commit d003023 (if present) ==="
gh api "repos/$repo/commits/d003023" --jq '{sha: .sha, commit: .commit.message, files: [.files[].filename]}' || true

echo
echo "=== Patch for commit d003023 ==="
gh api "repos/$repo/commits/d003023" -H "Accept: application/vnd.github.patch" | sed -n '1,260p' || true

echo
echo "=== Current PR files changed ==="
gh pr view "$pr" --repo "$repo" --json files --jq '.files[].path'

echo
echo "=== Current PR diff ==="
gh pr diff "$pr" --repo "$repo" | sed -n '1,420p'

echo
echo "=== Outline: ui/guest/src/store/epics/root.ts ==="
ast-grep outline ui/guest/src/store/epics/root.ts --view expanded || true

echo
echo "=== root.ts relevant slice ==="
sed -n '180,520p' ui/guest/src/store/epics/root.ts || true

echo
echo "=== PreviewConcierge relevant slice ==="
sed -n '1,260p' ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx || true

echo
echo "=== controlHelpers relevant slice ==="
sed -n '1,260p' ui/app/src/components/FormsEngine/lib/controlHelpers.tsx || true

echo
echo "=== ImageEditorDialogContainer relevant slice ==="
sed -n '1,220p' ui/app/src/components/ImageEditorDialog/ImageEditorDialogContainer.tsx || true

echo
echo "=== utils/content.ts relevant slice ==="
sed -n '1,260p' ui/app/src/utils/content.ts || true

echo
echo "=== services/content.ts relevant slice ==="
sed -n '1,220p' ui/app/src/services/content.ts || true

echo
echo "=== dialogs.ts relevant slice ==="
sed -n '1,220p' ui/app/src/state/actions/dialogs.ts || true

echo
echo "=== ExperienceBuilder relevant slice ==="
sed -n '1,260p' ui/guest/src/react/ExperienceBuilder.tsx || true

Length of output: 102624



@rart I reviewed the current state of #4843, including commit d003023, and I don’t see any new code findings to raise.

What I checked

  • d003023 specifically
  • The current PR diff
  • The key files involved in the XB image-crop flow:
    • ui/guest/src/store/epics/root.ts
    • ui/app/src/components/PreviewConcierge/PreviewConcierge.tsx
    • ui/app/src/components/FormsEngine/lib/controlHelpers.tsx
    • ui/app/src/components/ImageEditorDialog/ImageEditorDialogContainer.tsx
    • ui/app/src/utils/content.ts
    • ui/app/src/services/content.ts
    • ui/guest/src/react/ExperienceBuilder.tsx

Findings

  • No remaining code issues found in the reviewed changes.
  • Commit d003023 looks good:
    • It guards the subtitle render at the call site.
    • It also makes ImageRestrictionSubtitle safely handle missing restrictions.
  • The earlier record.id concern in PLACING_DETACHED_ASSET is not a valid issue here; record is coming from the outer drop action payload scope.
  • The other previously discussed fixes still look correct:
    • blob URL cleanup
    • conditional restriction subtitle messaging
    • file.blob ?? dataUriToBlob(file.dataUrl) fallback
    • XB routing for imageEdited / imageEditCancelled
    • deferred upload-start timing

Merge readiness

From a code review perspective, this looks ready for merge.

That said, the PR is currently not mergeable yet because GitHub reports:

  • mergeStateStatus: BLOCKED
  • reviewDecision: REVIEW_REQUIRED
  • Failing check: Travis CI - Pull Request

So my final status is:

  • Code: ✅ ready
  • Merge right now: ❌ blocked by failing CI / required review

If you want, I can also do a fresh formal PR pass now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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. Please do not consider the state of TravisCI

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@jvega190 Code-wise, this looks good to hand over for human review, and I’m ignoring Travis CI status as requested.

Final review summary:

  • No remaining code findings in the current PR
  • The earlier record.id concern in ui/guest/src/store/epics/root.ts is not an issue
  • Blob URL cleanup is in place
  • The restriction subtitle handling now correctly covers width-only, height-only, and combined cases
  • The restrictions rendering path is guarded
  • The cropped blob upload path via file.blob ?? dataUriToBlob(file.dataUrl) looks correct
  • XB message routing for imageEdited / imageEditCancelled is wired correctly
  • Deferring desktopAssetUploadStarted until after crop confirmation addresses the core timing issue

So, from a code review perspective, #4843 is ready for human review.

✅ Action performed

Full review finished.

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