Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
edb4791
MWPW-189894: fix: Lingo placeholders in Studio card preview (dictiona…
seanchoi-dev Apr 6, 2026
9ef385d
Revert "MWPW-189894: fix: Lingo placeholders in Studio card preview (…
seanchoi-dev Apr 6, 2026
4c1e9ff
MWPW-189894: Fix - Lingo placeholders in Studio card preview
seanchoi-dev Apr 7, 2026
fe6a34d
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 8, 2026
11c062f
MWPW-189894: fix - align Lingo placeholders with Akamai country and r…
seanchoi-dev Apr 8, 2026
7e76079
Revert "MWPW-189894: Fix - Lingo placeholders in Studio card preview"
seanchoi-dev Apr 8, 2026
d489b47
MWPW-189894: Fix - Lingo placeholders in Studio card preview
seanchoi-dev Apr 9, 2026
78f560a
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 9, 2026
38e9894
MWPW-189894: Applying Nicolas's comment.
seanchoi-dev Apr 9, 2026
2f67cd8
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 9, 2026
67e81a1
MWPW-189894: nit - format fix
seanchoi-dev Apr 9, 2026
ada1bea
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 10, 2026
afaf786
MWPW-189894: Applying Nicolas's comment 2.
seanchoi-dev Apr 10, 2026
bebe16c
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 13, 2026
5f890a1
Revert "MWPW-189894: Fix - Lingo placeholders in Studio card preview"
seanchoi-dev Apr 13, 2026
9b6408f
MWPW-189894: Applying Nicolas's comment 3.
seanchoi-dev Apr 13, 2026
31ea9b5
MWPW-189894: Nit: more named variable.
seanchoi-dev Apr 13, 2026
396dadb
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 14, 2026
f9cd222
MWPW-189894: Removing Akamai/geo merge
seanchoi-dev Apr 15, 2026
82eef2b
MWPW-189894: Reverting the cache.js country-aware key and checking fo…
seanchoi-dev Apr 15, 2026
92445f8
MWPW-189894: unit test update, fetchResult update
seanchoi-dev Apr 15, 2026
f0380ee
MWPW-189894: Applied npeltier’s suggestion on getRequestInfos
seanchoi-dev Apr 15, 2026
de3a795
MWPW-189894: For npeltier's comment
seanchoi-dev Apr 15, 2026
c87863a
MWPW-189894: nit: remove re-export
seanchoi-dev Apr 15, 2026
1cf2108
MWPW-189894: polishing logic including remove double loading and remo…
seanchoi-dev Apr 15, 2026
1132aee
MWPW-189894: nit: update some comments.
seanchoi-dev Apr 15, 2026
70e6927
MWPW-189894: Splitting fetchFragment into two pipeline transformers.
seanchoi-dev Apr 15, 2026
7d45741
MWPW-189894: relocate some of functions.
seanchoi-dev Apr 15, 2026
9ea4692
MWPW-189894: Using body.path instead hard-coded path for the fallback.
seanchoi-dev Apr 15, 2026
0cb7389
MWPW-189894: Unit tests updates.
seanchoi-dev Apr 15, 2026
cbeccca
Merge branch 'main' into MWPW-189894
npeltier Apr 15, 2026
5820296
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 15, 2026
702dcee
MWPW-189894: Rename defaultLanguageVariation -> defaultLanguage
seanchoi-dev Apr 15, 2026
b6552a9
MWPW-189894: Handling double computing
seanchoi-dev Apr 15, 2026
bf61073
Merge branch 'main' into MWPW-189894
Axelcureno Apr 15, 2026
b37a6ab
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 16, 2026
9edc248
Merge branch 'main' into MWPW-189894
afmicka Apr 16, 2026
2fe294a
Merge branch 'main' into MWPW-189894
seanchoi-dev Apr 16, 2026
5e3f989
MWPW-189894: unit test updates to reach out 100% code coverage
seanchoi-dev Apr 16, 2026
0d78468
Merge branch 'main' into MWPW-189894
afmicka Apr 20, 2026
c52ea30
Merge branch 'main' into MWPW-189894
afmicka Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions io/www/src/fragment/pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ async function main(params) {
}

async function mainProcess(context) {
context.requestLocale = context.locale;
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.

see above

const cachedMetadata = await getRequestMetadata(context);
const metadataContext = extractContextFromMetadata(cachedMetadata);
context = { ...context, ...metadataContext };
Expand Down
88 changes: 11 additions & 77 deletions io/www/src/fragment/transformers/customize.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { odinReferences, odinUrl } from '../utils/paths.js';
import { fetch, getFragmentId, getRequestInfos } from '../utils/common.js';
import { getRequestInfos } from '../utils/common.js';
import { log, logDebug } from '../utils/log.js';
import { getDefaultLocaleCode, getLocaleCode, getRegionLocales, parseLocaleCode } from '../locales.js';
import { computeRegionLocale, getDefaultLanguageVariation } from './fetchFragment.js';

const PZN_FOLDER = '/pzn/';

Expand All @@ -13,53 +12,16 @@ function skimFragmentFromReferences(fragment) {
return skimmedFragment;
}

/**
* get fragment associated to default language, just returning the body
* @param {*} context
* - 'locale' comes from request parameter, so can be optional
* - 'parsedLocale' is the actual location of the fetched fragment
* @returns null if something wrong, [] if not found, body if found
*/
async function getDefaultLanguageVariation(context) {
let { body } = context;
const { surface, locale, fragmentPath, preview, parsedLocale } = context;
// if no 'locale' request parameter, serve fragment as is
if (!locale) {
context.defaultLocale = parsedLocale;
return { body, parsedLocale, status: 200 };
}
const defaultLocale = getDefaultLocaleCode(surface, locale);
if (!defaultLocale) {
return { status: 400, message: `Default locale not found for requested locale '${locale}'` };
}
if (defaultLocale !== parsedLocale) {
logDebug(() => `Looking for fragment id for ${surface}/${defaultLocale}/${fragmentPath}`, context);
const defaultLocaleIdUrl = odinUrl(surface, { locale: defaultLocale, fragmentPath, preview });
const { id: defaultLocaleId, status, message } = await getFragmentId(context, defaultLocaleIdUrl, 'default-locale-id');
if (status != 200) {
return { status, message };
}
const defaultLocaleUrl = odinReferences(defaultLocaleId, true, preview);
const response = await fetch(defaultLocaleUrl, context, 'default-locale-fragment');
if (response.status != 200 || !response.body) {
/* c8 ignore next */
const message = response.message || 'Error fetching default locale fragment';
/* c8 ignore next */
return { status: response.status || 503, message };
}
({ body } = response);
}
context.defaultLocale = defaultLocale;
return { body, defaultLocale, status: 200 };
}

async function init(context) {
if (context?.promises?.fetchFragment) {
return await context.promises.fetchFragment;
}
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.

why is that i don't understand?
Init are here to trigger asap if possible in parallel network dependencies. From what i see, you don't need init in customize anymore, as all has been triggered from fetch fragment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought this is better for maintainability and clearer boundary between transformers but I guess I made you confused.

I’ll update them to remove init() from customize.js.

const { body, surface, fragmentPath, parsedLocale } = await getRequestInfos(context);
context = { ...context, surface, fragmentPath, parsedLocale, body };
const merged = { ...context, surface, fragmentPath, parsedLocale, body };
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.

i would not use "merge" here as it has special meaning in that transformer's context, and tbh at this stage you should already all all of those informations in the context (or if not please make fetchFragment process add it)

if (!surface || !fragmentPath) {
return { status: 400, message: 'Missing surface or fragmentPath' };
}
return await getDefaultLanguageVariation(context);
return await getDefaultLanguageVariation(merged);
}

function deepMerge(...objects) {
Expand Down Expand Up @@ -285,37 +247,6 @@ function customizeTree(root, referencesTree = [], customizeContext) {
return { fragment: customizedRoot, references: customizeContext.references, referencesTree: adaptedTree };
}

/**
* Returns the locale used for regional paths and personalization.
* If the request uses the default locale code but country differs from that locale's default country and maps to a
* known region for that language on the surface, returns that regional code (e.g. fr_FR + CA → fr_CA).
* If the requested locale is already a regional code, it is preserved when no country override applies.
* @param {*} context
* @returns {string}
*/
export function computeRegionLocale(context) {
const { locale, defaultLocale: defaultLocaleCode, surface } = context;
const country = context.country?.toUpperCase();
const [, defaultCountry] = parseLocaleCode(defaultLocaleCode);
const defaultCountryUpper = defaultCountry?.toUpperCase();
const effectiveCountry = country && defaultCountryUpper != null && country !== defaultCountryUpper ? country : null;

let regionLocale = locale;
if (locale !== defaultLocaleCode || effectiveCountry != null) {
const regionObjects = getRegionLocales(surface, defaultLocaleCode, true);
const regionLocaleObject =
effectiveCountry != null ? regionObjects.find((r) => r.country?.toUpperCase() === effectiveCountry) : null;
const mapped = regionLocaleObject ? getLocaleCode(regionLocaleObject) : null;
regionLocale = mapped || locale;
}
logDebug(
() =>
`Computed region locale '${regionLocale}' for requested locale '${locale}' with country '${country}' on surface '${surface}'`,
context,
);
return regionLocale;
}

async function customize(context) {
const { surface } = await getRequestInfos(context);
const { body, defaultLocale, status, message } = await context.promises?.customize;
Expand All @@ -326,7 +257,7 @@ async function customize(context) {
}
const baseFragment = skimFragmentFromReferences(body);
const { references, referencesTree } = body;
const regionLocale = computeRegionLocale({ ...context, defaultLocale, surface });
const regionLocale = context.regionLocale ?? computeRegionLocale({ ...context, defaultLocale, surface });
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.

why would context.regionLocale be unset? we should always have it here, and no mention of computeRegionLocale at all

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I removed computeRegionLocale from customize() and only use context.regionLocale when the pipeline has already run fetchFragment process (so region is always on context there).

The ?? regionLocaleFromInit is for our unit tests: they call customize.process() with a stub context and no promises.fetchFragment (see customize.test.js — the small process() helper only sets context.promises = {} and runs customize.process). In that path we never run fetchFragment process, so context.regionLocale is never set. resolveFragmentInit then uses the fallback (getRequestInfos + getDefaultLanguageVariation) and now returns regionLocale the same way fetchFragment init would, and customize reads it from that result.

Example: a test builds { locale: 'fr_FR', country: 'CA', body, surface, … } and expects the Canadian variation path — regionLocale has to come from the init-shaped result, not from context, because there is no prior fetchFragment step in the test harness.

const isRegionLocale = regionLocale !== defaultLocale;
const customizeContext = {
...context,
Expand All @@ -347,6 +278,8 @@ async function customize(context) {
...context,
status: 200,
body: customizedFragment,
locale: regionLocale,
defaultLocale,
Comment thread
seanchoi-dev marked this conversation as resolved.
};
}

Expand All @@ -356,3 +289,4 @@ export const transformer = {
init,
};
export { deepMerge };
export { computeRegionLocale, getDefaultLanguageVariation } from './fetchFragment.js';
110 changes: 99 additions & 11 deletions io/www/src/fragment/transformers/fetchFragment.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,83 @@
import { fetch } from '../utils/common.js';
import { getErrorContext } from '../utils/log.js';
import { PATH_TOKENS, odinReferences } from '../utils/paths.js';
import { fetch, getFragmentId } from '../utils/common.js';
import { getErrorContext, logDebug } from '../utils/log.js';
import { PATH_TOKENS, odinReferences, odinUrl } from '../utils/paths.js';
import { getDefaultLocaleCode, getLocaleCode, getRegionLocales, parseLocaleCode } from '../locales.js';

/**
* get fragment associated to default language, just returning the body
* @param {*} context
* - 'locale' comes from request parameter, so can be optional
* - 'parsedLocale' is the actual location of the fetched fragment
* @returns null if something wrong, [] if not found, body if found
*/
export async function getDefaultLanguageVariation(context) {
let { body } = context;
const { surface, locale, fragmentPath, preview, parsedLocale } = context;
if (!locale) {
context.defaultLocale = parsedLocale;
return { body, parsedLocale, status: 200 };
}
const defaultLocale = getDefaultLocaleCode(surface, locale);
if (!defaultLocale) {
return { status: 400, message: `Default locale not found for requested locale '${locale}'` };
}
if (defaultLocale !== parsedLocale) {
logDebug(() => `Looking for fragment id for ${surface}/${defaultLocale}/${fragmentPath}`, context);
const defaultLocaleIdUrl = odinUrl(surface, { locale: defaultLocale, fragmentPath, preview });
const { id: defaultLocaleId, status, message } = await getFragmentId(context, defaultLocaleIdUrl, 'default-locale-id');
if (status != 200) {
return { status, message };
}
const defaultLocaleUrl = odinReferences(defaultLocaleId, true, preview);
const response = await fetch(defaultLocaleUrl, context, 'default-locale-fragment');
if (response.status != 200 || !response.body) {
/* c8 ignore next */
const message = response.message || 'Error fetching default locale fragment';
/* c8 ignore next */
return { status: response.status || 503, message };
}
({ body } = response);
}
context.defaultLocale = defaultLocale;
return { body, defaultLocale, status: 200 };
}

/**
* Returns the locale used for regional paths and personalization.
* If the request uses the default locale code but country differs from that locale's default country and maps to a
* known region for that language on the surface, returns that regional code (e.g. fr_FR + CA → fr_CA).
* If the requested locale is already a regional code, it is preserved when no country override applies.
* @param {*} context
* @returns {string}
*/
export function computeRegionLocale(context) {
const { locale, defaultLocale: defaultLocaleCode, surface } = context;
const country = context.country?.toUpperCase();
const [, defaultCountry] = parseLocaleCode(defaultLocaleCode);
const defaultCountryUpper = defaultCountry?.toUpperCase();
const effectiveCountry = country && defaultCountryUpper != null && country !== defaultCountryUpper ? country : null;

let regionLocale = locale;
if (locale !== defaultLocaleCode || effectiveCountry != null) {
const regionObjects = getRegionLocales(surface, defaultLocaleCode, true);
const regionLocaleObject =
effectiveCountry != null ? regionObjects.find((r) => r.country?.toUpperCase() === effectiveCountry) : null;
const mapped = regionLocaleObject ? getLocaleCode(regionLocaleObject) : null;
regionLocale = mapped || locale;
}
logDebug(
() =>
`Computed region locale '${regionLocale}' for requested locale '${locale}' with country '${country}' on surface '${surface}'`,
context,
);
return regionLocale;
}

const TRANSFORMER_NAME = 'fetchFragment';

async function init(initContext) {
const { id, locale, fragmentsIds, preview } = initContext;
if (id && locale) {
//either we have a default locale id that we can fetch directly or we use the id
const toFetchId = fragmentsIds?.['default-locale-id'] || id;
const path = odinReferences(toFetchId, true, preview);
const response = await fetch(path, initContext, 'fragment');
Expand All @@ -22,20 +93,31 @@ async function init(initContext) {
}

const { parsedLocale, surface, fragmentPath } = match.groups;
let context = { ...initContext, body: response.body, parsedLocale, surface, fragmentPath };
const variationResult = await getDefaultLanguageVariation(context);
/* c8 ignore next 3 — default-locale fetch errors are covered via customize / pipeline */
if (variationResult.status != 200) {
return variationResult;
}
context = { ...context, body: variationResult.body };
const defaultLocale = context.defaultLocale;
const regionLocale = computeRegionLocale({ ...context, defaultLocale });
return {
...initContext,
status: 200,
body: response.body,
body: variationResult.body,
parsedLocale,
surface,
fragmentPath,
};
} else {
return {
status: 400,
message: 'requested parameters id & locale are not present',
defaultLocale,
locale: regionLocale,
regionLocale,
};
}
return {
status: 400,
message: 'requested parameters id & locale are not present',
};
}

async function fetchFragment(context) {
Expand All @@ -45,7 +127,13 @@ async function fetchFragment(context) {
}
return {
...context,
body: response?.body,
body: response.body,
parsedLocale: response.parsedLocale,
surface: response.surface,
fragmentPath: response.fragmentPath,
defaultLocale: response.defaultLocale,
locale: response.locale,
regionLocale: response.regionLocale,
};
}

Expand Down
9 changes: 3 additions & 6 deletions io/www/src/fragment/transformers/replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,9 @@ function replaceValues(input, dictionary, calls) {
}

async function init(context) {
// we fetch dictionary at this stage only if id has already been cached
// because we can't know surface of fragment *before* first fetch
// if dictionaryId is present in cache - early load dictionary
// if nothing in cache - dictionaryId and dictionary itself will be loaded later,
// during process
return await getDictionary(context);
const fetchResult = await context?.promises?.fetchFragment;
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.

in the end you still wait for both, no?
i know that it's not easy...my suggestion is:
context.promises.fetchFragment is as before, with encapsulated another promise for customize

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You’re right that replace.init still waits for the full fragment result; it needs merged locale / surface for the dictionary cache key, so it can’t stop at requestInfos alone. The gain is elsewhere: getRequestInfos uses requestInfos first, so dictionary id / settings can start after the first fetch without waiting on default-locale work.

I updated some code according to your suggestion, promises.customize is now the same promise as promises.fetchFragment (set inside fetchFragment.init), so customize is explicitly tied to that encapsulated result rather than a separate parallel init.

Here is an AI report on the performance changes in this branch:

Yes, but it’s narrow — not a blanket “faster pipeline.”

Where it can be faster
getRequestInfos → requestInfos: Anything that only needs surface / path / body from the first fragment response can proceed without waiting on the default-locale fetch and the rest of fetchFragment.
So inits that call getRequestInfos (e.g. settings, dictionary id resolution that only needs early metadata) can overlap more work with that second phase instead of sitting behind it.

Where it’s not faster (or similar)
replace.init still awaits the full fetchFragment / customize result before getDictionary, because the dictionary cache key needs the merged regional locale. So replace is not “freed” by requestInfos; that part is correctness, not latency win.
End-to-end for a cold request: you still do the same fragment + default-locale work as before; you mainly reorder what can run in parallel after the first fetch.

Summary
Meaningful win: less serial waiting on the full fetchFragment promise for code paths that only need early request metadata.
Not a big global speedup: total network work is largely the same; replace still waits for the full fragment outcome.

const merged = fetchResult?.status === 200 ? { ...context, ...fetchResult } : context;
return await getDictionary(merged);
}

async function replace(context) {
Expand Down
10 changes: 9 additions & 1 deletion io/www/src/fragment/utils/cache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { getJsonFromState, mark, measureTiming } from './common.js';
import { log } from './log.js';
const getRequestMetadataKey = (context) => `req-${context.id}-${context.locale}`;
function getRequestMetadataKey(context) {
const id = context.id;
const locale = (context.requestLocale ?? context.locale) || 'no-locale';
const raw = context.country;
if (raw != null && String(raw).trim() !== '') {
return `req-${id}-${locale}-${String(raw).trim().toUpperCase()}`;
Comment thread
seanchoi-dev marked this conversation as resolved.
Outdated
}
return `req-${id}-${locale}`;
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.

while agreeing it's a needed fix, i would make cache key format here, that is
always req-${id}-${reqLocale}-${reqCountry}
reqCountry being context.country || 'n/a' or any other string that is not a country code :)

}

async function getRequestMetadata(context) {
const requestKey = getRequestMetadataKey(context);
Expand Down
1 change: 1 addition & 0 deletions io/www/test/fragment/customize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ describe('computeRegionLocale', function () {
expect(computeRegionLocale({ locale: 'fr_FR', country: 'FR', ...CTX })).to.equal('fr_FR');
expect(computeRegionLocale({ locale: 'fr_FR', country: 'BE', ...CTX })).to.equal('fr_BE');
expect(computeRegionLocale({ locale: 'fr_FR', country: 'ca', ...CTX })).to.equal('fr_CA');
expect(computeRegionLocale({ locale: 'fr_FR', country: 'CH', ...CTX })).to.equal('fr_CH');
expect(computeRegionLocale({ locale: 'fr_FR', country: 'IN', ...CTX })).to.equal('fr_FR');
expect(computeRegionLocale({ locale: 'fr_BE', country: undefined, ...CTX })).to.equal('fr_BE');
expect(computeRegionLocale({ locale: 'fr_BE', country: 'FR', ...CTX })).to.equal('fr_BE');
Expand Down
4 changes: 2 additions & 2 deletions io/www/test/fragment/pipeline.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,8 @@ describe('pipeline full use case', () => {
expect(result.headers).to.have.property('Last-Modified');
expect(result.headers).to.have.property('ETag');
expect(Object.keys(state.store).length).to.equal(1);
expect(state.store).to.have.property('req-some-en-us-fragment-fr_FR');
const json = JSON.parse(state.store['req-some-en-us-fragment-fr_FR']);
expect(state.store).to.have.property('req-some-en-us-fragment-fr_FR-CA');
const json = JSON.parse(state.store['req-some-en-us-fragment-fr_FR-CA']);
expect(json.fragmentsIds['dictionary-id']).to.not.equal('sandbox_fr_FR_dictionary');
expect(json.fragmentsIds['default-locale-id']).to.equal('some-fr-fr-fragment');
});
Expand Down
2 changes: 1 addition & 1 deletion io/www/test/fragment/replace.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe('replace', () => {

it('returns 200 & no placeholders', async () => {
const response = await getResponse('foo', 'Buy now');
const expected = expectedResponse('foo');
const { fragmentsIds: _omit, ...expected } = expectedResponse('foo');
expect(response).to.deep.include(expected);
});
it('returns 200 & replaced entries keys with text', async () => {
Expand Down
28 changes: 14 additions & 14 deletions studio/src/mas-fragment-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,12 @@ export default class MasFragmentEditor extends LitElement {

if (isVariationAfterContext && !skipVariation) {
await this.#attachParentToCachedVariation(existingStore, fragmentPath);
const fragmentLocale = extractLocaleFromPath(fragmentPath);
if (fragmentLocale && fragmentLocale !== Store.localeOrRegion()) {
Store.search.set((prev) => ({ ...prev, region: fragmentLocale }));
await this.repository.loadPreviewPlaceholders();
existingStore.resolvePreviewFragment();
}
} else {
this.localeDefaultFragment = existingStore.parentFragment;
}
Expand Down Expand Up @@ -751,8 +757,6 @@ export default class MasFragmentEditor extends LitElement {
// Initializes editor state for fragments that are not yet present in list store cache.
async #initializeFromRepository(fragmentId) {
try {
// Start loading placeholders early
const placeholdersPromise = this.repository.loadPreviewPlaceholders().catch(() => null);
const fragmentData = await this.repository.aem.sites.cf.fragments.getById(fragmentId);
const fragment = new Fragment(fragmentData);

Expand All @@ -763,8 +767,14 @@ export default class MasFragmentEditor extends LitElement {
const parentFragment = await this.#resolveParentForFetchedVariation(fragmentId, fragment, isVariationAfterContext);
const isVariationForStore = isVariationAfterContext || !!parentFragment;

// Wait for placeholders before creating stores (needed for preview resolution)
await placeholdersPromise;
if (isVariationForStore) {
const fragmentLocale = extractLocaleFromPath(fragment.path);
if (fragmentLocale && fragmentLocale !== Store.localeOrRegion()) {
Store.search.set((prev) => ({ ...prev, region: fragmentLocale }));
}
}

await this.repository.loadPreviewPlaceholders();

const fragmentStore = generateFragmentStore(fragment, parentFragment);
// Only add to main list if not a variation (variations appear under parent's variations panel)
Expand All @@ -775,16 +785,6 @@ export default class MasFragmentEditor extends LitElement {
this.#activateEditorStore(fragmentStore);
this.dispatchFragmentLoaded();

// Handle locale-specific placeholder reload for variations
if (isVariationForStore) {
const fragmentLocale = extractLocaleFromPath(fragment.path);
if (fragmentLocale && fragmentLocale !== Store.localeOrRegion()) {
Store.search.set((prev) => ({ ...prev, region: fragmentLocale }));
await this.repository.loadPreviewPlaceholders();
fragmentStore.resolvePreviewFragment();
}
}

Store.editor.resetChanges();
this.updateTranslatedLocalesStore(isVariationForStore, fragment.path); // no need to await
this.#markInitReady();
Expand Down
Loading
Loading