Skip to content

[6.x] Fix default values and fieldtype meta after saving entries and terms - #15427

Open
duncanmcclean wants to merge 3 commits into
6.xfrom
publish-form-after-save
Open

[6.x] Fix default values and fieldtype meta after saving entries and terms#15427
duncanmcclean wants to merge 3 commits into
6.xfrom
publish-form-after-save

Conversation

@duncanmcclean

@duncanmcclean duncanmcclean commented Sep 8, 2026

Copy link
Copy Markdown
Member

This pull request fixes two issues with the publish form after saving an entry or term and continuing to edit.

Firstly, it fixes an issue where default values weren't applied to fields that had been saved empty (for example, a grid hidden behind a toggle with always_save: false), until the Stache was cleared.

This was happening because Entry::fileData() and Term::fileData() strip null and empty values before writing to disk, but the in-memory object (which is what the save response is built from, and what the Stache caches) keeps them. A hidden grid is processed to [], which isn't null, so Field::preProcess() never fell back to the field's default.

This PR fixes it by mirroring the writer's rule when extracting publish form values: empty values are dropped for root entries and default-locale terms. Localizations are left alone since explicit nulls are meaningful there.

Secondly, it fixes an issue where relationship values set in an EntrySaving or TermSaving listener rendered as broken items after "Save & continue editing", until the page was reloaded.

This was happening because the save pipeline re-applied the response's values, but nothing refreshed the fieldtype meta, so the Relationship fieldtype had no item data for the newly added IDs.

This PR fixes it by returning meta from the entry and term update endpoints and applying it in the save pipeline alongside the values.

The same missing meta was also behind #15418: a conditionally hidden nested Replicator with default: rows gets fresh row IDs on every request, so after saving, the Assets fieldtypes inside those rows couldn't find their meta and threw, leaving the Control Panel unresponsive until a hard refresh.

Fixes #11355
Fixes #11396
Fixes #15418
Replaces #11356

duncanmcclean and others added 3 commits September 8, 2026 13:04
the file writer drops empty values from root entries and default-locale terms, but the in-memory object keeps them, so `Field::preProcess()` never fell back to the default until the stache was cleared. mirror the writer's rule when extracting publish form values.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmQ7DBwRHtNLUej3bbyMQW
return `meta` from the entry and term update endpoints and apply it in the save pipeline alongside the values, so relationship items added during `EntrySaving`/`TermSaving` render correctly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmQ7DBwRHtNLUej3bbyMQW
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EmQ7DBwRHtNLUej3bbyMQW

@jasonvarga jasonvarga left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PHP half looks right to me — ExtractsFromEntryFields and ExtractsFromTermFields mirror Entry::fileData()'s isRoot() conditional and Term::fileData()'s default-locale-only stripping precisely. I also confirmed the term restructure doesn't lose in-memory TermSaving changes, since LocalizedTerm::data() and inDefaultLocale()->data() both read through to the same underlying Term.

One issue on the JS side that needs fixing before this goes in — the wholesale setMeta() clobbers client-owned slug meta. Details inline.

Non-blocking: both new PHP tests only cover the root/default-locale branch, so nothing exercises the localization side of the new isRoot() ternary.

Comment on lines +80 to +82
if (container && response.data.data?.hasOwnProperty('meta')) {
container.value.setMeta(response.data.data.meta);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This replaces the container's meta wholesale, which drops meta.slug.auto — client-owned state the server never emits.

The chain:

  • Container.vue's setMeta() is a plain replacement (meta.value = newMeta), and its watch(meta, ...) emits update:meta, which flows back to PublishForm.vue's v-model:meta.
  • PublishForm.vue then sends _auto_slug: this.meta.slug?.auto ?? false on every save — now always false.
  • The only writer of meta.slug.auto is the mounted() watcher in SlugFieldtype.vue on $refs.slugify.shouldSlugify. It's immediate: true, so it fires once on mount and then only when shouldSlugify changes. "Save & continue editing" doesn't remount the form and shouldSlugify doesn't change, so the flag never comes back.
  • Slug.php has no preload(), so the server never repopulates it.

So after any save that keeps the form open (Save & continue, autosave, inline/stack saves), EntriesController::resolveSlug() starts taking $request->slug verbatim instead of re-deriving it from the title — which is what its "An auto generated slug lags behind the values it came from" comment exists to prevent. Change the title and save before the debounced slugify settles, and the stale slug gets persisted. Entries only; terms have no _auto_slug handling.

Worth preserving client-owned meta rather than replacing it, similar to how resetValuesFromResponse already preserves revealerFields.

The distinction is narrow, to be clear: Relationship/Assets/Bard/Replicator/Grid meta is server-produced, so refreshing that is exactly the fix you want here. It's only slug's auto that's client-owned.

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

Labels

None yet

Projects

None yet

2 participants