[UVE] Page Editor hangs indefinitely with no error when a container references a content type missing from the environment
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Summary
When the Universal Visual Editor (UVE) saves a page, it sends the full page payload to POST /api/v1/page/{pageId}/content. If any container on the page is configured to allow a content type that does not exist in the current environment (e.g., a type exists in QA but was never published to production), the backend throws a 400 Bad Request. However, the frontend silently swallows this error and leaves the editor in an infinite loading/spinner state with no message shown to the user.
Root Cause (confirmed via source code)
There are two compounding issues — one in the service layer and one that silently swallows errors from it:
1. DotPageApiService.save() swallows HTTP errors (dot-page-api.service.ts:94)
// core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api/dot-page-api.service.ts
save({ pageContainers, pageId, params }: SavePagePayload): Observable<unknown> {
const variantName = params.variantName ?? DEFAULT_VARIANT_ID;
return this.http
.post(`/api/v1/page/${pageId}/content?variantName=${variantName}`, pageContainers)
.pipe(catchError(() => EMPTY)); // ← swallows ALL HTTP errors, returns EMPTY
}
EMPTY is an Observable that completes without emitting. It is not an error — it is a silent completion.
2. Callers depend on an emission to proceed (withSave.ts:50, withPageApi.ts:354)
Both withSave.ts and withPageApi.ts follow the same pattern:
tap(() => patchState(store, { status: UVE_STATUS.LOADING })), // ← spinner starts
switchMap(() =>
dotPageApiService.save(payload).pipe(
switchMap(() => { /* reload page, set LOADED */ }), // ← never runs: EMPTY emits nothing
catchError((e) => {
patchState(store, { status: UVE_STATUS.ERROR }); // ← never runs: EMPTY doesn't error
return EMPTY;
})
)
)
Because dotPageApiService.save() returns EMPTY on error:
- The inner
switchMap(which setsLOADED) never executes — no value to map over. - The
catchErrornever executes —EMPTYis not an error. - The store remains permanently in
UVE_STATUS.LOADING. - The spinner spins forever. No error toast, no message, no rollback.
3. Backend validation (PageResource.java:879, ContainerAPIImpl.java:604)
validateContainerEntries() calls getContainerContentTypes() → getContentTypesInContainer(). That method silently skips content types it cannot find (if (type == null) { continue; }), producing an empty or incomplete allowed-types set. When a page save includes containers whose allowed types don't exist in the environment, the subsequent check:
if (!contentTypeSet.contains(contentlet.getContentType().variable())) {
throw new BadRequestException("The content type: " + ... + " is not valid for the container");
}
...throws a BadRequestException (HTTP 400). The backend has a meaningful error message; the frontend never surfaces it.
Steps to Reproduce
- Set up two environments (e.g., QA and production) that share pages via push publishing.
- In QA, create a container that allows a new content type (e.g.,
ReactApplicationImport). Do not publish the content type to production. - In QA, add a page that uses that container. Push-publish the page to production (using Only Selected Item).
- Open the page in the production UVE/Page Editor.
- Attempt to drag, reorder, or move any content item on the page (not just items of the missing type).
Expected: An error message is displayed explaining that a content type referenced by the page is missing. The editor remains functional for unaffected containers.
Actual: The editor enters an infinite loading/spinner state. No error is shown. After refreshing, all changes are lost.
Affected Files
| File | Issue |
|---|---|
core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api/dot-page-api.service.ts |
save() uses catchError(() => EMPTY) — swallows all HTTP errors silently |
core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/save/withSave.ts |
Store stuck in LOADING because EMPTY never emits |
core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/page-api/withPageApi.ts |
Same pattern as withSave.ts — editorSave also affected |
dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java |
validateContainerEntries() (L879) blocks the save with a 400 when a referenced content type is missing |
Proposed Fix
Minimal (frontend): Remove or replace the catchError(() => EMPTY) in DotPageApiService.save(). Both callers already have their own catchError that correctly patches state to UVE_STATUS.ERROR — they just never fire because the error is swallowed upstream. Letting the error propagate is sufficient to display the error state.
Better UX: Surface the backend's error message as a toast/notification so the user knows why the save failed, rather than just showing a generic error state.
Ideal (backend): validateContainerEntries() should tolerate missing content types rather than failing the entire page save. If a container references a type not present in the environment, it should either skip that container's validation or return a warning — it should not block reordering of completely unrelated content.
Environment
- dotCMS Version: dotEvergreen (Current Release)
- Environment: Production (Cloud)
- Freshdesk Ticket: https://dotcms.freshdesk.com/a/tickets/38036
- Reproduction confirmed: Yes (customer confirmed fix after publishing missing content type to prod)
Acceptance Criteria
- Moving/reordering content in the Page Editor does not leave the editor in an infinite loading state when a non-fatal backend error occurs
- HTTP errors from
POST /api/v1/page/{pageId}/contentpropagate to the store's error handler so the spinner is cleared - A user-visible error message is shown when a page save fails, with enough context to understand the cause
- Optionally: the backend does not block a full page save due to a container referencing a content type that is absent from the environment
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 dot-page-api.service.ts and trace save() into withSave.ts and withPageApi.ts, then inspect the POST /api/v1/page/{pageId}/content flow. Reproduce the missing-content-type scenario and verify the editor exits LOADING, reaches its error state, and shows a useful message instead of hanging.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, typescript
- Domain
- api, backend, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100