Skip to content

fix(richtext-lexical): preserve block data on remount - #17957

Open
paulpopus wants to merge 7 commits into
mainfrom
codex/poss-157-lexical-block-remount
Open

paulpopus wants to merge 7 commits into
mainfrom
codex/poss-157-lexical-block-remount

Conversation

@paulpopus

Copy link
Copy Markdown
Member

Summary

  • prevent regular and inline Lexical blocks from installing stale cached form state after their node data changes
  • reuse cached form state only when its reduced values still match current node data
  • fall back to the existing schema-aware getFormState pipeline for changed arrays, nested blocks, objects, null transitions, and metadata

Root cause

The remount initializer tried to merge nested node data into a flat cached FormState by matching top-level keys. Dotted array-row fields were never refreshed or created, and the array parent received the full array where the form-state contract expects a row count. The truncated state could then be written back to the Lexical node and autosaved.

The fix avoids reconstructing schema-derived state from runtime value shapes. When cached values differ from the node, both block component variants rebuild through Payload's authoritative form-state builder.

Verification

  • focused regression suite — 7 passed
  • pnpm test:unit packages/richtext-lexical/src — 9 files, 89 tests passed
  • pnpm build:richtext-lexical — 4/4 tasks successful
  • changed-file Prettier and ESLint — passed
  • git diff --check — passed

Fixes #17894

Linear: https://linear.app/figma/issue/POSS-157

@github-actions

github-actions Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 215.24 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.86 MB ✅ No change
packages/payload/meta_shared.json esbuild/exports/shared.js 553.18 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 286.75 KB ⚠️ +1.35 KB (+0.5%)
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 36.54 KB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.95 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▋ }}}$ 98.8%, 210.74 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 718 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.2%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.2%, 520 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.1%, 315 B
dist/utilities/initReq.js ${{\color{Goldenrod}{ }}}$ 0.1%, 306 B
dist/utilities/selectiveCache.js ${{\color{Goldenrod}{ }}}$ 0.1%, 263 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████ }}}$ 72.0%, 1.33 MB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 54.49 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 46.15 KB
dist/auth/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.87 KB
dist/globals/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.10 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.02 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 14.31 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 13.65 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.88 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 10.82 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 10.50 KB
dist/cli/commands ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 9.92 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 8.76 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 8.38 KB
dist/database/migrations ${{\color{Goldenrod}{ }}}$ 0.4%, 8.12 KB
dist/index.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.79 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ }}}$ 0.4%, 7.66 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 7.36 KB
dist/utilities/entityInputSchema ${{\color{Goldenrod}{ }}}$ 0.4%, 7.34 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.15 KB
(other) ${{\color{Goldenrod}{ ███████ }}}$ 28.0%, 517.25 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████████▏ }}}$ 88.8%, 486.64 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 10.79 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 5.83 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 4.34 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.52 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.42 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.79 KB
dist/config/client.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.56 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.42 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ }}}$ 0.2%, 1.04 KB
dist/globals/config ${{\color{Goldenrod}{ }}}$ 0.2%, 939 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.2%, 850 B
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.1%, 794 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.1%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.1%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.1%, 651 B
dist/utilities/fieldPath.js ${{\color{Goldenrod}{ }}}$ 0.1%, 639 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.1%, 632 B
(other) ${{\color{Goldenrod}{ ██▊ }}}$ 11.2%, 61.67 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▍ }}}$ 13.6%, 38.55 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▉ }}}$ 11.7%, 33.21 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.6%, 33.01 KB
dist/features/table ${{\color{Goldenrod}{ ██▍ }}}$ 9.6%, 27.18 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.6%, 18.82 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 17.45 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 14.23 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.9%, 11.08 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▉ }}}$ 3.5%, 10.02 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.3%, 9.43 KB
dist/features/converters ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 8.40 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.14 KB
dist/features/lists ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.67 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.28 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.23 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 2.89 KB
dist/lexical/nodes ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.66 KB
(other) ${{\color{Goldenrod}{ █████████████████████▌ }}}$ 86.4%, 245.00 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/exports/client ${{\color{Goldenrod}{ █████████████████████████ }}}$ 100.0%, 26.90 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.5%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▌ }}}$ 14.5%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██ }}}$ 8.3%, 1.51 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.2%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 419 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/layouts/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 230 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 158 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
(other) ${{\color{Goldenrod}{ █████████████████▍ }}}$ 69.5%, 12.68 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@paulpopus
paulpopus force-pushed the codex/poss-157-lexical-block-remount branch from eee628b to 6c4331a Compare August 27, 2026 17:12
@alphanull

Copy link
Copy Markdown

FYI: have this problem as well. A fix would be greatly appreciated

@ClementNumericite

Copy link
Copy Markdown

Hello there, same problem for me.

ClementNumericite added a commit to Numericite/ditnd-unification-site that referenced this pull request Sep 23, 2026
Editing an accordion item in a WYSIWYG page, switching admin tab and coming
back dropped everything added since the page load, before even saving.

On remount, BlockComponent rebuilds its form state from the server-rendered
snapshot and only refreshes top-level keys (`fieldName in formData`). The
accordion block holds an array (`items`), whose sub-fields live under dotted
paths (`items.0.content`): they are never refreshed, so the stale snapshot
wins and is then written back to the lexical node.

Backport payloadcms/payload#17957 to 3.89.0 via patch-package. The new
getCachedFormStateIfDataMatches resolves dotted paths against the current
node data, skips array/blocks parents whose value is a row list, and only
falls back to a server rebuild when the row topology itself changed.

Upstream issue payloadcms/payload#17894. Drop this patch once #17957 ships.

Co-Authored-By: Claude <noreply@anthropic.com>
ClementNumericite added a commit to Numericite/ditnd-unification-site that referenced this pull request Sep 23, 2026
Editing an accordion item in a WYSIWYG page, switching admin tab and coming
back dropped everything added since the page load, before even saving.

On remount, BlockComponent rebuilds its form state from the server-rendered
snapshot and only refreshes top-level keys (`fieldName in formData`). The
accordion block holds an array (`items`), whose sub-fields live under dotted
paths (`items.0.content`): they are never refreshed, so the stale snapshot
wins and is then written back to the lexical node.

Backport payloadcms/payload#17957 to 3.89.0 via patch-package. The new
getCachedFormStateIfDataMatches resolves dotted paths against the current
node data, skips array/blocks parents whose value is a row list, and only
falls back to a server rebuild when the row topology itself changed.

The admin does not load dist/features/**, it loads the prebuilt
dist/exports/client bundles, which carry their own copy of the component.
So the same fix is applied there too, inlined as __pl17957 in
exports/client/index.js (BlockComponent) and exports/client/chunk-XFA5M7FY.js
(InlineBlockComponent). Patching only the unbundled sources has no effect.

Upstream issue payloadcms/payload#17894. Drop this patch once #17957 ships.

Co-Authored-By: Claude <noreply@anthropic.com>
@paulpopus
paulpopus marked this pull request as ready for review September 24, 2026 11:33

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lexical block silently loses array-field rows on remount (data loss)

3 participants