Skip to content

Conversation

@iOvergaard
Copy link
Contributor

@iOvergaard iOvergaard commented Dec 15, 2025

Summary

Closes #20451

This PR implements early validation for Move and Duplicate operations in the backoffice, providing immediate feedback to users when selecting invalid destinations.

Addressing #20451 - Different Approach

The original issue suggested that invalid destinations should not be selectable at all. We took a lazy validation approach instead, for good reasons:

Why not pre-disable all invalid items?

  • Would require fetching allowed children for every visible tree item upfront
  • Expensive API calls that scale poorly with large content trees
  • Slows down modal opening significantly

Our approach: Validate on selection

  • Items are validated when selected (lazy/on-demand)
  • Invalid selections show an error message immediately
  • After a content type is found invalid, all items of that type become disabled (progressive learning)
  • Descendants and self are disabled upfront (cheap to compute from tree data)

This gives users immediate feedback without the performance cost of pre-computing all valid destinations.

Features Added

  • Content type validation: Validates that the source item's type is allowed as a child of the destination. Shows error and disables invalid destinations.
  • Descendant prevention (Move only): Prevents moving items to their own descendants.
  • Same-parent prevention (Move only): Prevents selecting the current parent (no-op).
  • Dynamic disabling: Invalid destination types become greyed out after first validation failure.
  • Improved modal UX: Item name in headline, error messages in footer with proper styling.

Screenshots

Move Modal - Initial State

Shows item name in headline ("Select where Home should be moved to below"):

01-move-modal-initial

Move Modal - Descendants Disabled

All descendants of the source item are automatically disabled (greyed out):
02-move-descendants-disabled

Move Modal - Content Type Error

Error shown when selecting a destination that doesn't allow the source's document type:
03-move-content-type-error

Move Modal - Valid Selection

Submit button enabled when a valid destination is selected:
04-move-valid-selection

Duplicate Modal - Content Type Error

Same validation for duplicate operations:
06-duplicate-content-type-error

Design Notes

Why similar modal code is intentional

The modals for different entity types (documents, media, etc.) have similar but separate implementations. This follows Umbraco's package architecture:

  1. Independence - Each package owns its implementations, so changes to the document modal don't risk breaking media or other entity types
  2. Extensibility - Entity-specific features can be added without affecting others (e.g., documents have "relate to original" and "include descendants" options that don't apply to media)
  3. Avoiding premature abstraction - A shared base class would couple entity types that may diverge in the future

The validation logic (e.g., onTreeSelectionChange) is similar across modals but not identical, and the cost of maintaining a few similar methods is lower than the coupling a shared abstraction would introduce.

Why duplicating to descendants is allowed

Unlike move operations, duplicating a document to one of its descendants is intentionally allowed - it creates a copy without circular reference issues.

Test Plan

  • Move document: Right-click → Move to → verify descendants are disabled, invalid types show error, valid selection enables button
  • Duplicate document: Right-click → Duplicate to → verify content type validation works (descendants ARE allowed for copy)
  • Bulk move/duplicate: Select multiple items → verify "X items" in headline, validation works for all selected items
  • Media move: Same validation flow works for media items

🤖 Generated with Claude Code

iOvergaard and others added 16 commits December 15, 2025 13:57
… modal open in case the backend should fail the validation
…error in footer

- Add name property to move-to and duplicate-to modal tokens
- Display item name in modal headline using moveOrCopy_moveTo and moveOrCopy_copyTo localization keys
- Move validation error messages to modal footer (actions slot) for better visibility
- Update localization strings to use cleaner format with quotes around item name
- Pass name from document and media actions to modals
- Fix incorrect localization key in document duplicate modal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
When move/duplicate validation fails due to content type restrictions,
all other items of that same content type are now automatically disabled
in the tree picker. This prevents users from trying multiple destinations
of the same type that will all fail for the same reason.

Applied to single actions (document move, media move, document duplicate)
and bulk actions (document bulk move, document bulk duplicate, media bulk move).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add align-items: center to footer-layout actions to prevent buttons
  from stretching when error messages wrap to multiple lines
- Add padding to error messages so they don't touch the border

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add validation to move actions to prevent:
- Moving a document to one of its descendants (would create circular reference)
- Moving a document to its current parent (no-op)

For documents, the ancestors array on tree items enables descendant detection.
For media, descendant detection relies on backend validation as tree items
don't include ancestors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Show "X items" in the modal headline for bulk operations so users
know how many items they're moving/duplicating.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copilot AI review requested due to automatic review settings December 15, 2025 20:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements comprehensive early validation for Move and Duplicate operations in the Umbraco CMS backoffice, providing immediate feedback when users select invalid destinations based on content type constraints. The implementation shifts from generic "moveTo" and "duplicateTo" action kinds to custom implementations with validation logic, introduces new modal components with error handling, and adds localization support.

Key Changes:

  • Replaces generic action kinds with custom implementations that perform real-time content type validation
  • Introduces three new modal components (move-to, duplicate-to variants) with validation callbacks and dynamic tree filtering
  • Implements lazy validation that disables invalid destination types after they're selected
  • Adds descendant prevention for move operations and current-parent prevention

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
move-document.action.ts New custom implementation for single document move with content type validation and descendant/parent filtering
move-document-bulk.action.ts New bulk document move action with multi-item validation and error listing
move-media.action.ts New custom implementation for single media move with content type validation
move-media-bulk.action.ts New bulk media move action with multi-item validation
duplicate-document.action.ts Enhanced duplicate action with content type validation via callbacks
duplicate-document-bulk.action.ts New bulk duplicate action with multi-item validation
move-to-modal.element.ts New modal component with validation callbacks, dynamic filtering, and error display
duplicate-to-modal.element.ts New modal component for generic duplicate-to operations with validation support
duplicate-document-modal.element.ts Enhanced duplicate modal with validation callbacks and error handling
move-to-modal.token.ts New type definitions for move modal data, including callback interfaces
duplicate-to-modal.token.ts Enhanced type definitions for duplicate modal with validation callbacks
duplicate-document-modal.token.ts Enhanced type definitions with validation and submit callbacks
move-to.action.ts Updated generic move action to use new modal and submit callbacks
duplicate-to.action.ts Updated generic duplicate action to use callbacks and improved error handling
manifests.ts (documents/media) Changed from generic "moveTo"/"duplicateTo" kinds to "default" kind with custom API imports
footer-layout.element.ts Added flexbox alignment to center error messages with buttons
en.ts / da.ts Updated localization strings to use dynamic item names in modal headlines
commands.md Added quick verification commands for development workflow

iOvergaard and others added 3 commits December 15, 2025 22:16
The action's pickableFilter closure already manages disabled state via
#disallowedDocumentTypes. The modal now just passes the filter through
directly instead of maintaining its own _disabledItems Set.
…ions

- Add fallback to unique when variant name is missing in move-document.action.ts
- Add fallback to unique when variant name is missing in duplicate-document.action.ts
- Add comment clarifying that duplicating to descendants is intentionally allowed (unlike move)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@iOvergaard iOvergaard added area/frontend release/17.2.0 category/ux User experience category/dx Developer experience type/feature and removed category/dx Developer experience labels Dec 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

V17: UI allows moving media to other media

2 participants