Skip to content

[MWPW-199159] Lingo web performance: re-apply #6210 with iconography.css fix#6316

Merged
milo-pr-merge[bot] merged 7 commits into
stagefrom
MWPW-199159
Jul 22, 2026
Merged

[MWPW-199159] Lingo web performance: re-apply #6210 with iconography.css fix#6316
milo-pr-merge[bot] merged 7 commits into
stagefrom
MWPW-199159

Conversation

@vhargrave

@vhargrave vhargrave commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Re-applies the reverted Lingo web-performance PR #6210, with the iconography regression that caused the revert fixed at the root cause.

Background

#6210 (perf: improve lingo performance — personalization, geo, lingo, XLG) was merged to stage and reverted ~3h later by #6267 because iconography.css stopped being applied — icons broke on almost all stage pages (milo-dev thread). This PR restores #6210 and fixes that regression properly.

Root cause of the icon regression

loadLink() (libs/utils/utils.js) deduped existing links by href alone, ignoring rel. #6210 added rel=preload, as=style hints for iconography.css and breakpoint-theme.css in preloadBlockResources() (for marquee/hero-marquee). Those stylesheets are applied later via loadStyle() (hero-marquee, notification, editorial-card, aside). Because a <link> with that href already existed (the preload), the later loadStyle()loadLink() found it and no-oped instead of creating the applying rel=stylesheet link — so the CSS downloaded but was never applied.

Changes

more details
  • Re-apply perf: improve lingo performance — personalization, geo, lingo, XLG #6210 in full, including the two CSS preloads — clean revert-of-the-revert onto current stage. All personalization / geo / lingo / XLG optimizations preserved unchanged.
  • fix (root cause): make loadLink() rel-aware. A rel=stylesheet request now dedups only against an existing stylesheet link, so a preload no longer shadows the loadStyle(). The block's loadStyle() creates a fresh <link rel=stylesheet> (fires load reliably in every engine — no rel-swap, no dependency on preload load semantics) that reuses the preloaded bytes via HTTP cache — the canonical preload → stylesheet pattern (MDN). Non-stylesheet rel requests keep href-only dedup, unchanged. This keeps Okan's non-render-blocking early fetch and applies the CSS — rather than discarding the optimization.
  • perf: priority-aware Lingo query-index loading. Wave 1 (primary + base) and any cross-site index flagged fetchPriority in lingo-site-mapping.json (today only upp) are fetched first; the remaining cross-site indexes wait behind a barrier and then fetch at { priority: 'low' } so they don't compete during the LCP window. lingo-site-mapping.json itself fetches at default priority so the flagged indexes are discovered promptly.

Design note (why the root-cause fix, not a rel-swap)

more details

An alternative — swap the preload link's rel to stylesheet on load — was designed and rejected after a devil's-advocate review: it depends on rel=preload as=style firing load in every engine (unverified on Safari/WebKit; our suite is Chromium-only), and if it doesn't, the awaited loadStyle callback can never fire → block decoration hangs (worse than the bug). The rel-aware-dedup fix has no such dependency: worst case is a benign double-fetch (perf, not correctness); icons always apply. Cross-repo audit: no adobecom consumer preloads a CSS URL then loadStyles it, so the shared-fn change is behavior-preserving for every non-broken caller.

Verification

  • Tests: full test/** run green (3702 passed at last full run; the sole failure is a pre-existing floating-promise flakefetchAndProcessPlainHtml with MEP, utilities.test.js:42, asserts inside a non-awaited .then(), proven non-deterministic by isolation and outside the CSS/loadLink path). New/added coverage for every risk-bearing change: the loadLink preload-dedup fix (a loadStyle for a preloaded href creates a distinct applying stylesheet, not shadowed), the fetchPriority: "yes" query-index branch, geo.js getAkamaiCode (concurrent-caller dedup, post-failure re-fetch, 5s abort→reject), and categorizeActions last-write-wins ordering under Promise.all. Lint clean.
  • Visual / DOM — da-cc /creativecloud, ?milolibs=main vs ?milolibs=MWPW-199159, confirmed on this build: the branch shows iconography.css and breakpoint-theme.css each as two links — a rel=preload as=style (not applied) plus an applied rel=stylesheet (link.sheet non-null) — the canonical preload→stylesheet pair; main has the single applied stylesheet. 0 unstyled icons on both (9 visible icons each). Confirms the preload optimization is preserved and the CSS applies.
  • Did a test on https://main--da-cc--adobecom.aem.live/fr/creativecloud to ensure the query indexes still loaded , but in a different order. Looked great.

Resolves: MWPW-199159

Test URLs:

Icon / perf comparison (consumer page):

vhargrave and others added 3 commits July 17, 2026 10:14
Restores "perf: improve lingo performance — personalization, geo, lingo,
XLG" (#6210), which was reverted in #6267 because it broke iconography.css
rendering on stage. The icon regression is fixed in the following commit;
every other optimization from #6210 is preserved unchanged.

This is a clean revert-of-the-revert applied on top of current stage, so it
also carries the intervening loadPostLCP/lenis change from #6274.

Co-authored-by: Okan Sahin <39759830+mokimo@users.noreply.github.com>
The iconography.css/breakpoint-theme.css rel=preload hints added to
preloadBlockResources() in #6210 caused icons to disappear on almost all
stage pages, forcing the #6267 revert.

Root cause: loadLink() dedupes by href alone, ignoring rel. iconography.css
and breakpoint-theme.css are applied later via loadStyle() (hero-marquee,
notification, editorial-card, aside). Once preloadBlockResources() inserted a
<link rel="preload" as="style"> for those hrefs, the subsequent
loadStyle() -> loadLink() call found an existing link by href and no-oped
(callback('noop')) instead of creating the rel=stylesheet link — so the CSS
downloaded but was never applied.

Removing just these two preloads restores icon rendering while keeping every
other optimization from #6210. Making the preload work would require
rel-aware activation in the shared loadLink() (ecosystem-wide blast radius,
fiddly onload/callback semantics) and only warms a small CSS fetch a few ms
early — not worth the risk, so the micro-optimization is dropped.
#6210 added in-flight promise memoization to getEntitlementMap() but only
cleared config.mep.entitlementMapFetch on the success path. When the XLG
tags fetch failed (fetchData returns falsy), the handle stayed set to a
resolved promise, so every later call returned the consumerEntitlements
fallback and never retried for the rest of the page session — a behavior
change from the pre-#6210 code, which re-fetched on each call.

Clear the handle on the failure branch too so a transient failure can
recover. Concurrent callers still share one in-flight fetch. Surfaced by
devil's-advocate review of the #6210 resurrection.
@aem-code-sync

aem-code-sync Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch.
In case there are problems, just click the checkbox below to rerun the respective action.

  • Re-sync branch
Commits

Reinstate the barrier that holds cross-site (Wave 2) query-index fetches
until the priority wave lands, made priority-aware:

- Wave 1 (primary + base) and any cross-site index flagged fetchPriority:"yes"
  in lingo-site-mapping.json are fetched first, at default (auto) priority.
- The barrier awaits that whole priority wave.
- Remaining cross-site indexes fetch after, at { priority: 'low' }, so they
  don't compete for bandwidth during the LCP window.
- lingo-site-mapping.json now fetches at default priority (was 'low') so the
  flagged indexes are discovered promptly rather than gated behind a
  low-priority map fetch.
…etchPriority test

- getEntitlementMap: restore #6210's failure branch (drop the in-flight-handle
  reset). It only added retry-on-transient-failure, which is not a correctness
  fix and costs a repeated fetch on deterministic failures; falling back to
  consumerEntitlements for the page is acceptable graceful degradation.
- utils.js: remove explanatory comments (keep the shared file lightweight).
- utils.test.js: cover the new fetchPriority:"yes" branch — flagged cross-site
  index is fetched before the low-priority remainder and without { priority: 'low' }.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread test/utils/utils.test.js Dismissed
Comment thread test/utils/utils.test.js Dismissed
Root-cause fix for the #6210 icon regression, replacing the earlier
approach of deleting the preloads. #6210 preloaded iconography.css /
breakpoint-theme.css (rel=preload, as=style) then applied them via
loadStyle; loadLink deduped by href alone, so the preload link shadowed
the loadStyle and the stylesheet was never created — CSS fetched, never
applied, icons broke.

Fix: a stylesheet request now dedups only against an existing stylesheet
link, so a preload no longer shadows it. The block's loadStyle creates a
fresh rel=stylesheet link (fires load reliably in every engine — no swap,
no dependency on preload load semantics) that reuses the preloaded bytes
via HTTP cache (the canonical preload -> stylesheet pattern). Non-stylesheet
rel requests keep href-only dedup unchanged. Okan's two preloads restored.

Reviewed by a devil's advocate (GO, strictly safer than swap-based
alternatives which carried a cross-engine hang risk). Cross-repo audit:
no adobecom consumer preloads a CSS URL then loadStyles it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the two coverage gaps a devil's-advocate PR review flagged (both
were correct-by-reading but had zero guarding tests):

- geo.test.js (new): getAkamaiCode success + sessionStorage cache,
  concurrent-caller dedup (single in-flight fetch), post-failure re-fetch
  (in-flight handle cleared in finally, no poisoning), non-ok rejection,
  and the 5s AbortController timeout -> reject.
- personalization.test.js: categorizeActions last-write-wins for
  config.mep.replacepage / updateframework under the same
  Promise.all(experiments.map(...)) shape applyPers uses, plus the
  default-variant early return. Pins the parallelization ordering contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vhargrave
vhargrave marked this pull request as ready for review July 17, 2026 13:56
@vhargrave
vhargrave requested a review from a team as a code owner July 17, 2026 13:56
@vhargrave
vhargrave requested review from a team, markpadbe and sukamat July 17, 2026 13:56

@DKos95 DKos95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me, found just one NIT.

Comment thread libs/utils/utils.js
Comment on lines +1177 to +1179
const selector = rel === 'stylesheet'
? `link[href="${href}"][rel="stylesheet"]`
: `link[href="${href}"]`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NIT:

Suggested change
const selector = rel === 'stylesheet'
? `link[href="${href}"][rel="stylesheet"]`
: `link[href="${href}"]`;
const selector = `link[href="${href}"]${rel === 'stylesheet' ? '[rel="stylesheet"]' : ''}`;

@github-actions

Copy link
Copy Markdown
Contributor

Reminder to set the Ready for Stage label - to queue this to get merged to stage & production.

@NadiiaSokolova NadiiaSokolova self-assigned this Jul 22, 2026

@NadiiaSokolova NadiiaSokolova left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verified. Ready for Stage.

@milo-pr-merge
milo-pr-merge Bot merged commit 4e11197 into stage Jul 22, 2026
18 checks passed
@milo-pr-merge
milo-pr-merge Bot deleted the MWPW-199159 branch July 22, 2026 20:01
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.

5 participants