dotCMS / dotCMS/core

fix(uve): editor submits empty content payload on TRADITIONAL pages (stale pageData) → save loop after net-loss guard

Open
#36,276 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Area : Frontend Team : Scout Type : Defect
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Follow-up to #36097. The backend net-loss guard (#36098) shipped and prevents the wipe, but the frontend still generates the empty payload, producing an infinite loading loop in the UVE for end users.

Root cause (frontend)

On TRADITIONAL pages, withEditor.ts builds $pageData once from store.pageAsset()?.containers; isClientReady=true blocks pageReload() from handling subsequent CLIENT_READY events, so $pageData is never refreshed. A stale/empty model is posted to POST /api/v1/page/{id}/content (full replacement) → guard now rejects with 409 → editorSave (withPageApi.ts) retries → loop.

Prod evidence

Page b923b026, 4 saves with totalContentlets=0 / containers=13, each rejected by the backend guard (31 contentlets protected). Logs tagged [FD-36897]:

WARN PageResourceHelper: [FD-36897] saveContent: pageId=b923b026-… containers=13 totalContentlets=0
WARN MultiTreeAPIImpl: Empty save payload would wipe 31 existing contentlet(s) …
WARN MultiTreeAPIImpl: Save rejected: net loss of 31 … exceeds threshold 1

Reproduction signature (template-level)

The affected pages all use a legacy "drawn" TRADITIONAL template. The smoking gun is in the template's layout: every container instance carries the legacy sentinel UUID:

{ "identifier": "…", "uuid": "LEGACY_RELATION_TYPE", "historyUUIDs": ["LEGACY_RELATION_TYPE"] }

LEGACY_RELATION_TYPE (MultiTree.LEGACY_RELATION_TYPE / ContainerUUID.UUID_LEGACY_VALUE) marks templates never migrated to numbered container instances. Combined with "drawed": true and a hand-coded body, this is exactly the "TRADITIONAL (legacy-template)" category named in #36097.

Repro signature for QA: any page whose template layout containers carry uuid: "LEGACY_RELATION_TYPE".

Backend compatibility shim to be aware of

HTMLPageAssetRenderedBuilder.transformLegacyContainerUUIDs() rewrites LEGACY_RELATION_TYPE"1" in the page-render API (so the editor sees a normal numbered instance). Its scope is only layout.body.rows[].columns[].containers[] and layout.sidebar. The UVE relies on this shim for legacy templates — worth verifying the editor correctly maps the transformed UUIDs into $pageData on TRADITIONAL pages, since this is where the model ends up empty.

Not a cause — </body> warning (ruled out)

The log line No </body> tag found in page HTML, appending UVE script at end is cosmetic and not related. HTMLPageAssetRenderedBuilder.injectUVEScript() simply appends the UVE script at the end when no </body> is present (vs. inserting before it). It does not affect the content model. Flagging so reviewers don't chase it.

Acceptance criteria

  1. $pageData reflects current pageAsset content at save time on TRADITIONAL pages (no stale/empty serialization).
  2. Adding a contentlet posts existing + new (never an empty containers array).
  3. A 409 from the content-save endpoint is handled as non-retryable: refresh page state and show an error, no infinite loop.
  4. Regression test covering the stale-session / re-entered-editor case on a TRADITIONAL page whose template layout uses LEGACY_RELATION_TYPE UUIDs.

Affected files

  • withEditor.ts$pageData computed signal, isClientReady guard that prevents pageAsset refresh
  • withPageApi.tseditorSave rxMethod that posts whatever $pageData currently holds
  • HTMLPageAssetRenderedBuilder.javatransformLegacyContainerUUIDs() (legacy-compat shim the editor depends on)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with withEditor.ts, tracing the $pageData computed signal and isClientReady guard, then inspect withPageApi.ts to follow editorSave's 409 retry behavior. Review HTMLPageAssetRenderedBuilder.java and transformLegacyContainerUUIDs() to verify the legacy UUID mapping assumed by the editor. Add regression coverage for a TRADITIONAL page and confirm saves retain existing content, 409s stop retrying, and an error is shown.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, typescript
Domain
api, backend, frontend, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.