Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [6.x] Only preload meta for the selected link type by duncanmcclean · Pull Request #15113 · statamic/cms · GitHub
Skip to content

[6.x] Only preload meta for the selected link type - #15113

Merged
jasonvarga merged 2 commits into
6.xfrom
14664
Aug 5, 2026
Merged

[6.x] Only preload meta for the selected link type#15113
jasonvarga merged 2 commits into
6.xfrom
14664

Conversation

@duncanmcclean

Copy link
Copy Markdown
Member

This pull request fixes an issue where entry edit pages containing lots of Link fields inside nested Replicators would take a long time to load, and often end in a blank page or a timeout.

This was happening because Link::preload() built the full nested relationship meta for every registered link type, on every instance of the field, whether or not that type was actually selected. That meta is derived entirely from the field's config, so a page with hundreds of link fields shipped hundreds of byte-identical copies of it. In the reported case, 1,008 link fields produced 1,008 identical copies of the entries meta, accounting for 97% of an 11mb data-page payload.

This PR fixes it by only preloading meta for the type matching the field's initial option. The rest send null, and the Control Panel fetches them from the existing fields/field-meta endpoint the first time the user picks that type. On a test entry with 480 cells (960 link fields), the payload drops from 9.5mb to 1.9mb.

Fixes#14664

`Link::preload()` built the full nested relationship meta for every registered
link type on every instance of the field. In a nested Replicator with hundreds
of link fields, that's hundreds of byte-identical copies of the same config
derived payload. The rest are now fetched from `fields/field-meta` the first
time the user picks that type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Requires changes — found a regression while reviewing this PR, reproduced live in a sandbox.

Issue

LinkFieldtype.vue now gates rendering the nested fieldtype component on matchedType.meta being truthy:

<componentv-else-if="matchedType && matchedType.meta" .../><Iconv-else-if="matchedType" name="loading" class="size-4 self-center" />

And Link::preload() (src/Fieldtypes/Link.php) only preloads meta for the initial option now; every other type gets meta: null.

The problem: null is also exactly what a legitimately-loaded meta looks like for any fieldtype that doesn't override Fieldtype::preload() — the base implementation just returns null (src/Fields/Fieldtype.php:374-377). So any custom Link::extend() type built on a plain fieldtype (e.g. text, toggle, integer) will have meta === null forever — both from the initial preload and from every subsequent fields/field-meta fetch, since that endpoint just calls preload() again and gets the same null back.

Since the component only renders once meta is truthy, the field gets stuck on the loading spinner permanently and becomes completely unusable, with no error shown to the user. This isn't hypothetical — it's exactly the shape of this repo's own TestBasicLinkType fixture in tests/Fieldtypes/LinkTest.php (fieldtype() => ['type' => 'text']). I reproduced it live: registered a custom email Link type backed by the text fieldtype, added it to a blueprint, selected it in the CP — permanent spinner, field never renders.

Suggested fix

Track fetch status explicitly instead of inferring it from the value — add a metaLoaded boolean alongside meta:

src/Fieldtypes/Link.php:

$types[$handle] = [
...
'meta' => $handle === $initialOption ? $nestedFieldtype->preload() : null,
'metaLoaded' => $handle === $initialOption,
'selected' => $selected ? [$selected] : [],
];

LinkFieldtype.vue:

<componentv-else-if="matchedType && matchedType.metaLoaded" .../><Iconv-else-if="matchedType" name="loading" .../>
loadTypeMeta(handle){consttype=this.meta.types[handle];if(!type||type.metaLoaded)returnPromise.resolve();
...
},updateTypeMeta(handle,typeMeta){this.updateMeta({
...this.meta,types: { ...this.meta.types,[handle]: { ...this.meta.types[handle],meta: typeMeta,metaLoaded: true}},});},

Everything else here looks solid — the built-in Entry/Asset path (the actual reported performance issue) is correctly fixed and well tested. This is scoped to custom Link types built on non-preloadable fieldtypes.

(Comment posted via Claude)

`null` is also what a legitimately loaded meta looks like for any fieldtype
that doesn't override `Fieldtype::preload()`, so custom link types built on
plain fieldtypes (like `text`) were stuck on the loading spinner forever.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@jasonvargajasonvarga 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.

Confirmed fixed in 168b130 — the metaLoaded flag correctly separates "not fetched yet" from "legitimately null," so custom Link types on non-preloadable fieldtypes (e.g. text) no longer get stuck loading. Verified:

  • Read the diff line-by-line, matches the suggested fix.
  • it_marks_meta_as_loaded_for_a_type_whose_fieldtype_preloads_nothing (PHP) and does not request meta again for a type whose fieldtype has no meta to preload (JS) directly cover the scenario I reproduced.
  • ./vendor/bin/phpunit tests/Fieldtypes/LinkTest.php passes locally (35/35).
  • CI green.

Approving.

(Comment posted via Claude)

@jasonvarga
jasonvarga merged commit 433aafc into 6.xAug 5, 2026
22 checks passed
@jasonvarga
jasonvarga deleted the 14664 branch August 5, 2026 14:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large CP entry edit payload with nested Replicator + Link fields

2 participants

@duncanmcclean@jasonvarga