fix(uve): editor submits empty content payload on TRADITIONAL pages (stale pageData) → save loop after net-loss guard
Nobody has claimed this yet.
- 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
$pageDatareflects currentpageAssetcontent at save time on TRADITIONAL pages (no stale/empty serialization).- Adding a contentlet posts existing + new (never an empty
containersarray). - A 409 from the content-save endpoint is handled as non-retryable: refresh page state and show an error, no infinite loop.
- Regression test covering the stale-session / re-entered-editor case on a TRADITIONAL page whose template layout uses
LEGACY_RELATION_TYPEUUIDs.
Affected files
withEditor.ts—$pageDatacomputed signal,isClientReadyguard that preventspageAssetrefreshwithPageApi.ts—editorSaverxMethod that posts whatever$pageDatacurrently holdsHTMLPageAssetRenderedBuilder.java—transformLegacyContainerUUIDs()(legacy-compat shim the editor depends on)
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 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