UVE: unintercepted in-page links navigate the iframe natively — stale editor state with `mode`, blank canvas without it
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Summary
An in-page link that changes only a page-context query param is not intercepted by UVE. The browser performs a real navigation of the editor iframe, and uveStore.pageLoad() never runs — so the editor shell and the iframe contents fall out of sync.
Root cause
isSamePageNavigation (core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts:1161) compares pathname only:
return target.pathname === current.pathname;
A link to the same pathname with a different query string therefore passes as "same page". In handleInternalNav (core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts:781) this is rule 5, which returns early without calling preventDefault().
The predicate was written for hash anchors and client-side query UI — its docstring gives '/home?tab=2' -> true, "handled by the browser/client naturally". That is correct for client-side UI state and wrong for dotCMS page-context params, which must round-trip through the Page API.
Introduced in #35324 (PR #35326); the file-asset branch was added later by #35504 (PR #36925). Neither established a general contract for link following.
Two failure modes
Which one you get depends on whether the link happens to carry a mode param.
1. With mode present (customer-reported)
VelocityServlet.processPageMode (dotCMS/src/main/java/com/dotcms/rendering/velocity/servlet/VelocityServlet.java:63) short-circuits on an explicit mode param:
if (null != request.getParameter(WebKeys.PAGE_MODE_PARAMETER)){
return PageMode.get(request);
}
The server returns rendered EDIT_MODE HTML. The iframe shows the new language, but the editor shell keeps the previous language_id and toolbar label. The editing overlay — add (+) buttons, edit pencils, floating toolbar — stops rendering, because it cannot bind to content the editor did not load. Container outlines still draw, which makes it look like a partial render rather than a desync.
Customer's link: /homepage/index.html?com.dotmarketing.htmlpage.language=1&language_id=102&host_id=...&mId=edit&mode=EDIT_MODE
2. With no mode param (reproduced in-house)
Falls through to PageMode.NAVIGATE_EDIT_MODE, served by VelocityNavigateEditMode (dotCMS/src/main/java/com/dotcms/rendering/velocity/servlet/VelocityNavigateEditMode.java:30). That handler does not return page HTML at all. Its entire response is the JS_CODE template at line 36, which wraps the page JSON in an ng-event / load-edit-mode-page CustomEvent and dispatches it to window.top.document.
Nothing in core-web listens for that event. A grep of core-web/libs and core-web/apps for load-edit-mode-page returns zero hits; the only references in the repo are the Java emitter and its integration test (VelocityServletIntegrationTest.java:507).
The result is a blank editor canvas. The markup exists in the payload at page.rendered and is simply never consumed.
This is the legacy edit-mode navigation contract: the server rendered in NAVIGATE_EDIT_MODE and told the parent editor to load the page. The legacy Angular editor listened. UVE handles link-following client-side instead and never reimplemented the listener, so the server half still fires into a void.
Steps to Reproduce (failure mode 2)
- Demo starter. Create
/training/test-page-1with versions in two languages (EN id 1, ES id 2). - Add a link to the page body:
<a href="/training/test-page-1?com.dotmarketing.htmlpage.language=1">EN</a> - Open the page in UVE on the ES version.
- Click the link inside the preview.
Expected: the editor loads the EN version with the toolbar, URL and overlay all updated.
Actual: the iframe renders blank. The response body is the load-edit-mode-page bootstrap script.
Scope
The same predicate guards the headless path — dot-uve-actions-handler.service.ts:81, on NAVIGATION_UPDATE (set-url). An SDK client calling setUrl() with a changed page-context param desyncs identically. A fix in isSamePageNavigation covers both call sites.
Suggested direction
Return false from isSamePageNavigation when a dotCMS page-context param differs, so the navigation goes through pageLoad(). DotPageAssetKeys (core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api/dot-page-api.service.ts:35) is the canonical set: mode, depth, clientHost, variantName, language_id, experimentId, com.dotmarketing.persona.id, publishDate.
Note: the legacy VTL param com.dotmarketing.htmlpage.language must be treated as an alias of language_id, or the customer's exact case still slips through.
Worth deciding separately whether NAVIGATE_EDIT_MODE should remain reachable now that UVE has no consumer for its response. As it stands it turns a missed interception into a blank screen rather than a degraded one.
Workaround
The UVE toolbar language selector is unaffected — onLanguageSelected (core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts:437) calls pageLoad() directly. Editing controls survive and language_id updates. If the target language has no version of the page, the selector offers to create the translation instead of switching, which is expected behaviour.
Acceptance Criteria
-
isSamePageNavigationreturnsfalsewhen a dotCMS page-context param differs between the incoming and current URL, so the click is intercepted and routed throughuveStore.pageLoad(). Canonical set perDotPageAssetKeys:mode,depth,clientHost,variantName,language_id,experimentId,com.dotmarketing.persona.id,publishDate. - The legacy VTL param
com.dotmarketing.htmlpage.languageis treated as an alias oflanguage_id, so the customer's link shape is covered. - No regression on #35324: hash-only and client-side query navigation still short-circuit. From
/home, both'/home#faq'and'/home?tab=2'continue to returntrueand are not intercepted. - Following an in-page link that changes language inside UVE loads the target language in the editor, with the toolbar selector, the address bar
language_id, and the editing overlay (add+buttons, edit pencils, floating toolbar) all updated and functional — both with and without amodeparam on the link. - The headless path behaves identically: an SDK
setUrl()carrying a changed page-context param triggers apageLoad()rather than a silent no-op (dot-uve-actions-handler.service.ts:81). - No navigation inside UVE can leave the iframe rendering the raw
load-edit-mode-pagebootstrap script. - Unit specs cover the predicate for each case: hash-only, client-side query, language change, persona change, mode change, and the legacy language alias.
dotCMS Version
Evergreen, release 26.09.14-01 — both failure modes reproduced and the workaround verified on this release.
Severity
Medium - Some functionality impacted
Links
- Freshdesk ticket: https://dotcms.freshdesk.com/a/tickets/39465
- Split from: https://dotcms.freshdesk.com/a/tickets/35305 (original defect, fixed by #34518 in 26.07.27-01)
- Prior art: #35324 (PR #35326) introduced
isSamePageNavigation; #35504 (PR #36925) added the file-asset branch
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with isSamePageNavigation in core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts and its callers in edit-ema-editor.component.ts and dot-uve-actions-handler.service.ts. Review the existing predicate specs, then trace the Java navigation responses in VelocityServlet.java and VelocityNavigateEditMode.java. Done means page-context changes use pageLoad(), while hash-only and client-side query navigation remain unhandled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, typescript
- Domain
- backend, frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 64/100