Skip to content

Refactor(UVE): Real-Time Canvas#34173

Draft
fmontes wants to merge 340 commits intomainfrom
uve-experiment
Draft

Refactor(UVE): Real-Time Canvas#34173
fmontes wants to merge 340 commits intomainfrom
uve-experiment

Conversation

@fmontes
Copy link
Copy Markdown
Member

@fmontes fmontes commented Dec 26, 2025

Scope

This summary is based on the branch artifacts available locally in this workspace (not live GitHub PR metadata, which is not accessible from this environment). The changes are centered on UVE store state refactoring in edit-ema.

What Changed

The branch refactors the UVE store architecture from a duplicated state model to a single-source-of-truth model.

1. New computed-signal access layer (withPageAsset)

A new store feature was introduced to expose page asset data through computed signals:

  • $page(), $site(), $containers(), $template(), $layout()
  • $viewAs(), $vanityUrl(), $urlContentMap(), $numberContents()
  • $clientResponse()

This centralizes derived state access and reduces direct dependence on internal response structure.

2. State/API renaming for cleaner semantics

Several store properties and methods were renamed to remove GraphQL-specific wording and better describe intent:

  • graphqlRequest -> requestMetadata
  • graphqlResponse -> pageAssetResponse
  • legacyGraphqlResponse -> legacyResponseFormat
  • setGraphqlResponse() -> setPageAssetResponse()
  • setGraphqlResponseOptimistic() -> setPageAssetResponseOptimistic()
  • rollbackGraphqlResponse() -> rollbackPageAssetResponse()
  • $graphqlWithParams -> $requestWithParams
  • $customGraphqlResponse -> $clientResponse

3. Duplication removed from load/save flows

withLoad and withSave were simplified by removing repeated manual extraction/sync of pageAsset sub-properties.

Instead of repeatedly patching multiple top-level fields, flows now rely on updating pageAssetResponse once and reading via computed signals.

4. Consumer updates

Related consumers were updated to use the new API naming and signals, including:

  • Store features (withLayout, etc.)
  • Services (for example, action handling)
  • Components (for example, editor/style-editor integrations)
  • Tests across affected store/features/components

Why This Matters

Architectural improvements

  • Moves from multiple duplicated state fields to one authoritative source.
  • Removes manual synchronization points that could drift out of sync.
  • Makes API naming implementation-agnostic and easier to maintain.
  • Improves type-safety and readability via explicit computed accessors.

Engineering impact

  • Lower maintenance overhead when pageAsset shape changes.
  • Fewer fragile updates across load/save/consumer code paths.
  • Simpler mocking in tests (less setup of duplicated top-level state).

Reported Quantified Outcomes

From branch docs/changelog:

  • Manual sync points eliminated.
  • Duplicate extraction code removed from load/save paths.
  • 10 computed signals added for page-asset-derived data.
  • Store-related test suite reported as passing (with unrelated failures noted in other areas).

Backward Compatibility and Migration

Current compatibility

The branch indicates no immediate breaking changes for consumers because existing patterns are still tolerated during transition.

Recommended direction

Adopt computed-signal usage in new/updated code:

  • Prefer store.$page() over store.page()
  • Prefer store.$site() over store.site()
  • Continue migrating remaining direct property reads progressively

Future cleanup (planned)

Branch docs mention a later cleanup phase to fully remove deprecated direct-property access patterns once migration is complete.

Risk / Attention Points

  • Broad usage of legacy access patterns means migration should remain incremental.
  • Any consumer still directly tied to old names/patterns is a potential regression point if cleanup phase lands.
  • End-to-end validation of editor workflows should continue through migration phases (loading, saving, style editing, rollback flows).

Quick Takeaway

PR #34173 is primarily an internal architecture refactor that modernizes UVE store state handling: one source of truth, cleaner API names, computed signal access, and reduced duplication in critical load/save paths. It is aimed at maintainability, consistency, and safer future evolution rather than introducing large user-facing behavior changes.

Videos

video-general-walkthrough.mov
content-editing.mov
uve-bookmark.mov
new-animation.mov

fmontes and others added 20 commits January 4, 2026 18:49
- Removed unnecessary whitespace in withPageContext function for cleaner code.
- Updated withLock function to reset selectedContentlet in the editor state upon locking and unlocking, ensuring consistent state management.
- Enhanced lock loading state handling to improve user experience during page reloads.
- Changed the active tab condition in dot-uve-palette.component.html to use the TABS_MAP.LAYERS constant for improved readability and maintainability.
- Adjusted the UVE_PALETTE_TABS enum to swap the indices of STYLE_EDITOR and LAYERS, aligning with the new tab structure.
…ering and submission

- Added calls to spectator.detectChanges() to ensure the form is built and rendered correctly during tests.
- Updated input query to accommodate both form control name and ID for better test reliability.
- Improved assertions to verify emitted data upon form submission, ensuring accurate test coverage for the component's functionality.
…ection and rendering

- Added interactions to select contentlet by clicking hover bounds during tests.
- Updated assertions to verify correct rendering of bounds and actions based on contentlet area changes.
- Improved test coverage for handling undefined properties in contentlet area, ensuring robust behavior in various scenarios.
- Introduced comprehensive unit tests for the DotUveIframeComponent, covering component creation, input handling, and iframe behavior.
- Implemented tests for different page types (HEADLESS and TRADITIONAL) to ensure correct event emissions and content insertion.
- Enhanced coverage for SEO data handling and inline script management, ensuring robust functionality and state management.
- Introduced comprehensive unit tests for the DotRowReorderComponent, covering component creation, rendering of rows and columns, row selection, and drag-and-drop functionality.
- Implemented tests for row and column label rendering, ensuring correct display based on style classes and default values.
- Enhanced coverage for edit dialog interactions, verifying correct behavior for row and column editing, including form control value setting and submission logic.
- Added tests for row expansion and collapse, ensuring proper state management during user interactions.
- Added new signals for managing view state properties, including device, social media, and orientation.
- Introduced computed signals for view state and page parameters to improve test control and state management.
- Updated unit tests to reflect changes in signal handling and ensure accurate behavior during preview mode interactions.
- Refactored state management in tests to utilize new editor state structure, enhancing clarity and maintainability.
- Removed legacy unlock button tests, replacing them with assertions for the toggle lock button.
- Updated calendar interaction tests to ensure proper rendering and functionality in live mode.
- Improved overall test coverage by addressing various UI interactions and state changes.
- Added DotPageLayoutService mock to improve test coverage for layout-related functionality.
- Included calls to spectator.detectChanges() in relevant tests to ensure proper component rendering and state updates.
- Updated afterEach cleanup logic to handle activatedRoute more robustly, ensuring consistent test behavior.
Replace zoomService.$zoomLevel() with uveStore.$zoomLevel() in the
dot-ema-page-dropzone component binding. The zoom level now lives in
the UVE store as part of the withZoom feature.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ore features

## Summary
Refactors the access method for  in the  to utilize the new editor structure. Additionally, updates the store features to include new methods for handling GraphQL responses and history management.

## Changes
- Updated  access in  to use .
- Enhanced  with new methods for managing GraphQL responses and history.
- Adjusted imports in  for better organization.
- Modified  and  to align with the new structure and ensure proper functionality.

## Testing
- Verified that the form correctly retrieves and updates the active contentlet.
- Ensured that new store methods function as expected without breaking existing features.

## Related Issues
None
…ontrols components

## Summary
This commit addresses missing import statements in the test files for the dot-row-reorder and dot-uve-zoom-controls components.

## Changes
- Added the  import in .
- Included a blank line for better readability in .

## Testing
- Ensured that the test files compile successfully after the import adjustments.

## Related Issues
None
@semgrep-code-dotcms-test
Copy link
Copy Markdown

Semgrep found 9 ssc-4fd3a3fc-acff-4277-9d88-60469f5a4fa5 findings:

  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-zoom-controls/dot-uve-zoom-controls.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-palette/dot-uve-palette.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-palette/components/dot-uve-palette-list/dot-uve-palette-list.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-palette/components/dot-row-reorder/dot-row-reorder.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-iframe/dot-uve-iframe.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-contentlet-quick-edit/dot-uve-contentlet-quick-edit.component.ts
  • core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts

Risk: Affected versions of @angular/compiler and @angular/core are vulnerable to Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'). Angular's template compiler fails to classify the href and xlink:href attributes on SVG <script> elements as Resource URL contexts. This allows an attacker to bind a malicious data: URI or external script via [attr.href] or [attr.xlink:href], resulting in arbitrary JavaScript execution (XSS) in the victim's browser.

Fix: Upgrade this library to at least version 20.3.16 at core/core-web/yarn.lock:512.

Reference(s): GHSA-jrmj-c5cx-3cw6, CVE-2026-22610

If this is a critical or high severity finding, please also link this issue in the #security channel in Slack.

fmontes and others added 9 commits February 2, 2026 12:50
- Updated EditEmaEditorComponent to use activeContentlet instead of selectedContentlet for better clarity and functionality.
- Refactored related methods and store actions to align with the new naming convention.
- Removed references to selectedContentlet in tests and store state, ensuring consistency across the application.
- Adjusted tests to validate the new activeContentlet behavior.

This change enhances the codebase by improving the naming consistency and reducing confusion regarding contentlet state management.
…ActionsHandler

- Eliminated unnecessary console.log statements from the DotUvePaletteComponent and DotUveActionsHandlerService to clean up the code and improve performance.
- This change enhances code readability and reduces clutter in the console output during development.
- Changed method calls from  to  for consistency and improved readability.
- Adjusted form handling in  to clear the form before setting new values, preventing stale DOM issues.
- Updated related tests to reflect the new method access pattern, ensuring accurate assertions on active contentlet state.

This refactor enhances code clarity and maintains consistent access patterns across the UVE editor components.
- Updated the right sidebar to include a tab view for managing contentlet quick edits and style editing.
- Integrated NgRx signalState for managing the active tab state, improving the user experience.
- Removed the style editor tab from the DotUvePalette component, consolidating tab management within the EditEmaEditor.
- Adjusted image paths for toolbar icons to ensure correct asset loading.

This refactor improves the organization and usability of the editing interface, allowing for a more streamlined editing process.
zJaaal and others added 12 commits March 31, 2026 16:10
- Changed the method from  to  in the DotUveToolbarComponent to improve clarity and functionality.
- The  parameter is now passed to the updated method for better state management.
…, and add utils tests

- Rewrote reportIframeHeight to use MutationObserver + ResizeObserver
  with 50ms debounce and double-RAF; uses html.offsetHeight to correctly
  handle DOM removal shrinking; skips height=0 to prevent flicker;
  returns destroyHeightReporter() cleanup function
- Added full test suite (utils.spec.ts) covering reportIframeHeight,
  scrollHandler, addClassToEmptyContentlets, setClientIsReady,
  listenBlockEditorInlineEvent, and injectEmptyStateStyles
- Removed unused togglePalette() method and palette toggle button from toolbar
- Updated Tailwind class syntax in toolbar template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dary tests

Converts SEO_LIMITS from enum to as const, fixes null-safety in all SEO
rule length comparisons, updates SCSS selectors for PrimeNG 21 host-binding
changes, and adds test coverage for description/title length boundary warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 2, 2026

Rollback Safety Analysis

Result: Safe To Rollback

I reviewed the diff between commits 39d3c7e...6a823baa against all rollback-unsafe categories.

Scope of changes: ~200 files changed — the overwhelming majority are frontend-only (core-web/ Angular/TypeScript: components, store features, SCSS, tests). Only 6 backend Java files changed plus openapi.yaml.

Backend changes: Additive additions of a nullable metadata: Map<String, Object> field to TemplateLayoutRow, TemplateLayoutColumn, and their REST DTO counterparts (TemplateLayoutRowView, TemplateLayoutColumnView), plus a new DotPageMetadata GraphQL type and metadata fields on LayoutRow/LayoutColumn in PageAPIGraphQLTypesProvider.

All rollback-unsafe categories checked — clear:

  • C-1: No runonce task, no structural storage change
  • C-2: No ESMappingAPIImpl / ESMappingConstants changes, no reindex task
  • C-3: No CURRENT_MODEL_VERSION bump, no ImmutableContentlet change
  • C-4: No DROP TABLE or DROP COLUMN
  • H-1 through H-7: No data migration, rename, PK change, new field type, storage change, procedure drop, or NOT NULL column added
  • M-1 through M-4: No column type change, bundle format change, breaking API contract, or OSGi interface breakage. GraphQL/REST changes are additive only — new metadata field added, nothing removed or renamed.

Rollback scenario: If N writes a template with metadata fields then rolls back to N-1 — Jackson's default FAIL_ON_UNKNOWN_PROPERTIES=false means N-1 silently ignores the unknown metadata field during template JSON deserialization. No crash, no data corruption.

View job run

zJaaal and others added 14 commits April 2, 2026 13:58
…-toolbar spec

- Spy on pageReload instead of pageLoad (fetchPageOnDate calls pageReload)
- Remove mode from expected call args (component only passes { publishDate })
- Replace if-guard clauses with expect().toBeTruthy() assertions
- Test $MIN_DATE signal directly instead of unreliable ng-reflect-* attributes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tate for quick edit

- Add withContentTypeCache store feature to cache content types by variable name
- Replace loading spinner with dot-spinner in dot-uve-contentlet-quick-edit
- Add $isEmptyContainer computed and TEMP_EMPTY_CONTENTLET_TYPE sentinel to handle empty container state without fetching
- Reset editorActiveContentlet on pageLoad in withPageApi to avoid stale state on navigation
- Remove contentTypeCache from root dot-uve.store state (moved to withContentTypeCache)
- Add DotContentTypeService mock provider to integration spec
- Add uve.quick-edit.empty.empty-container.* i18n keys to Language.properties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… isSameContentlet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Filter switchSite$ emissions to compare the incoming site identifier
against the current page's site, avoiding spurious redirects caused
by SSE reconnects replaying SWITCH_SITE events for the same site.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary

This change updates how iframe height is handled for UVE zoom.

Before this work, zoom depended on iframe height being reported to the
parent shell through `postMessage`. That worked for cross-origin
iframes, but it was also being used in cases where the editor can
already access the iframe directly.

Now the behavior is split by iframe access mode:

- `LOCAL`: the editor can access the iframe document directly, so it
measures height locally
- `CROSS_ORIGIN`: the editor cannot access the iframe document, so the
child keeps reporting height through `postMessage`

This keeps the existing cross-origin behavior, but removes the
unnecessary message-based dependency for same-origin/local iframes.

## High-level flow

```text
                    +----------------------+
                    |   UVE Editor Shell   |
                    +----------------------+
                               |
                               v
                    determine iframeAccessMode
                               |
                 +-------------+-------------+
                 |                           |
                 v                           v
        +------------------+        +--------------------+
        |      LOCAL       |        |   CROSS_ORIGIN     |
        +------------------+        +--------------------+
        | parent can read  |        | parent cannot read |
        | iframe document  |        | iframe document    |
        +------------------+        +--------------------+
                 |                           |
                 v                           v
   observe iframe document height      child reports height
   directly from the editor            with postMessage
                 |                           |
                 +-------------+-------------+
                               |
                               v
                    update iframe height used
                    by editor zoom behavior
```

## Architecture

```text
                    +----------------------------------+
                    |   shared document height logic   |
                    |    observeDocumentHeight(...)    |
                    +----------------------------------+
                             /                \
                            /                  \
                           v                    v
          +--------------------------------+   +----------------------------------+
          | editor local iframe tracking   |   | sdk reportIframeHeight()         |
          | same-origin / accessible iframe|   | cross-origin fallback            |
          +--------------------------------+   +----------------------------------+
```

## Main idea

`PageType` and iframe accessibility are not the same thing.

This PR separates those concerns:

- `PageType` still describes how the page is rendered
- `IframeAccessMode` describes whether the editor can access the iframe
document

That allows the editor to choose the correct height sync strategy
without coupling it to page type.

## Result

- Same-origin/local iframes use direct height observation
- Cross-origin iframes keep using `postMessage`
- Both paths reuse the same shared document-height observer logic
- Zoom still works, but the local case is simpler and avoids unnecessary
messaging
…ite switches

The Struts PortalRequestProcessor reads host_id from the request and sets
CMS_SELECTED_HOST_ID on the session before any action runs. Without it,
the edit contentlet action could resolve a different site from the contentlet
data, triggering SwitchSiteListener to emit SWITCH_SITE and redirect to /pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use jest.mock() before imports for @dotcms/uve/internal to fix
  'Cannot redefine property: observeDocumentHeight' in iframe spec
- Fix cross-origin postMessage test to set contentWindow on the actual
  DOM iframe element and mock event.source so the new
  #isMessageFromUvePreviewFrame filter passes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s specs

- document-height-observer: update schedules-again test to mock new height
  before re-triggering so the lastHeight dedup guard does not suppress notification
- utils: add jest.restoreAllMocks() to injectEmptyStateStyles afterEach to
  prevent Storage spy bleed into subsequent tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 6, 2026

Rollback Safety Analysis

Result: Safe To Rollback

I reviewed the diff between da62968b...670cc540 against all rollback-unsafe categories.

Scope of changes: ~130 files — overwhelmingly frontend-only (Angular/TypeScript/SCSS). Only 6 backend Java files changed; no SQL migrations, no runonce tasks.

Backend changes (all additive):

  • Nullable metadata: Map<String, Object> added to TemplateLayoutRow, TemplateLayoutColumn and their REST DTO counterparts
  • New DotPageMetadata GraphQL type added; metadata field added to LayoutRow/LayoutColumn in PageAPIGraphQLTypesProvider

All rollback-unsafe categories — clear:

  • C-1: No runonce task, no structural storage change
  • C-2: No ESMappingAPIImpl/ESMappingConstants changes, no reindex
  • C-3: No CURRENT_MODEL_VERSION bump, no ImmutableContentlet change
  • C-4: No DROP TABLE or DROP COLUMN
  • H-1 thru H-7: No data migration, rename, PK change, new field type, storage change, procedure drop, or NOT NULL column added
  • M-1 thru M-4: No column type change, bundle format change, breaking API contract, or OSGi interface breakage

Rollback scenario: If N writes templates with metadata fields, N-1 rollback is safe — Jackson's @JsonCreator deserialization silently ignores the unknown metadata property. No crash, no data corruption.

Applied label: AI: Safe To Rollback

View job run

zJaaal and others added 2 commits April 6, 2026 15:04
The mock path '../../../utils/mocks' never existed. Replaced with
inline minimal mocks to avoid the circular dependency
utils -> edit-content -> utils.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ponent

refresh-button testid and lastUpdated signal were referenced in specs
but never added to DotUsageShellComponent or its template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code Area : Documentation PR changes documentation files Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries Changelog: Breaking Change

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

UVE: Real-Time Canvas

6 participants