Skip to content

MWPW-192450: Fix - Lingo placeholders in Studio card preview#760

Merged
afmicka merged 11 commits intomainfrom
MWPW-192450
Apr 21, 2026
Merged

MWPW-192450: Fix - Lingo placeholders in Studio card preview#760
afmicka merged 11 commits intomainfrom
MWPW-192450

Conversation

@seanchoi-dev
Copy link
Copy Markdown
Contributor

Resolves https://jira.corp.adobe.com/browse/MWPW-192450
QA Checklist: https://wiki.corp.adobe.com/display/adobedotcom/M@S+Engineering+QA+Use+Cases

Please do the steps below before submitting your PR for a code review or QA

  • C1. Cover code with Unit Tests
  • C2. Add a Nala test (double check with #fishbags if nala test is needed)
  • C3. Verify all Checks are green (unit tests, nala tests)
  • C4. PR description contains working Test Page link where the feature can be tested
  • C5: you are ready to do a demo from Test Page in PR (bonus: write a working demo script that you'll use on Thursday, you can eventually put in your PR)
  • C.6 read your Jira one more time to validate that you've addressed all AC's and nothing is missing

🧪 Nala E2E Tests

Nala tests run automatically when you open this PR.

To run Nala tests again:

  1. Add the run nala label to this PR (in the right sidebar)
  2. Tests will run automatically on the current commit
  3. Any future commits will also trigger tests as long as the label remains

To stop automatic Nala tests:

  • Remove the run nala label

Note: Tests only run on commits if the run nala label is present. Add the label whenever you need tests to run on new changes.

Test URLs:

More Test URL:

@aem-code-sync
Copy link
Copy Markdown

aem-code-sync Bot commented Apr 13, 2026

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

@seanchoi-dev seanchoi-dev requested review from Axelcureno, npeltier and yesil and removed request for yesil April 13, 2026 15:14
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 13, 2026

Codecov Report

❌ Patch coverage is 85.85859% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.33%. Comparing base (4b1efaa) to head (009e7f8).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
studio/src/mas-repository.js 86.44% 8 Missing ⚠️
studio/src/mas-fragment-editor.js 78.26% 5 Missing ⚠️
studio/src/editor-panel.js 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #760      +/-   ##
==========================================
- Coverage   87.41%   87.33%   -0.09%     
==========================================
  Files         210      210              
  Lines       62825    62870      +45     
==========================================
- Hits        54920    54907      -13     
- Misses       7905     7963      +58     
Files with missing lines Coverage Δ
studio/src/reactivity/preview-fragment-store.js 72.51% <100.00%> (-11.26%) ⬇️
studio/src/store.js 92.13% <100.00%> (+0.23%) ⬆️
studio/src/editor-panel.js 58.84% <0.00%> (ø)
studio/src/mas-fragment-editor.js 88.89% <78.26%> (-0.07%) ⬇️
studio/src/mas-repository.js 64.62% <86.44%> (+0.07%) ⬆️

... and 5 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4b1efaa...009e7f8. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seanchoi-dev seanchoi-dev marked this pull request as ready for review April 13, 2026 15:33
Copy link
Copy Markdown
Member

@Axelcureno Axelcureno left a comment

Choose a reason for hiding this comment

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

Review Summary

The architectural approach is sound — switching loadPreviewPlaceholders from this.filters.value.locale to Store.localeOrRegion() fixes the root cause (dictionary fetched/cached with the wrong locale for variations), and the "set region before load" reordering eliminates the unnecessary double-fetch. Both code paths (#initializeFromCachedStore and #initializeFromRepository) now handle variation locale detection consistently.

What works well

  • Fix is at the correct layer (repository/source, not consumer)
  • Cache key now uses the effective locale — this was the actual root cause
  • Region override is set before loadPreviewPlaceholders(), not after — cleaner and faster
  • Remaining this.filters.value.locale uses in mas-repository.js are correctly left unchanged (DAM paths, fragment search, placeholder creation)
  • All 11 CI checks green

Requesting changes: Test coverage gaps

The PR's core behavioral change — switching the locale source in loadPreviewPlaceholders — has insufficient test coverage. See inline comments for specifics.

Three tests needed:

  1. Repository-level unit test for loadPreviewPlaceholders verifying it uses Store.localeOrRegion() for cache key and fetch context (the foundational change in this PR has zero direct unit tests — all existing repo tests stub the method entirely)

  2. Cached store path test for #initializeFromCachedStore when variation locale differs (the 4 new lines at mas-fragment-editor.js:703-708 — this is what Codecov flagged)

  3. Same-locale no-op test verifying that when fragmentLocale === Store.localeOrRegion(), no extra placeholder reload occurs (regression guard)

Comment thread studio/src/mas-repository.js Outdated
if (!this.search.value.path) return;

const cacheKey = `${this.filters.value.locale}_${this.search.value.path}`;
const cacheKey = `${Store.localeOrRegion()}_${this.search.value.path}`;
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.

Core change untested at the repository level

This is the foundational fix — switching from this.filters.value.locale to Store.localeOrRegion(). But every existing test in mas-repository.test.js stubs loadPreviewPlaceholders entirely, so this change (and the matching ones at lines 674, 677, 710) has zero direct unit test coverage.

A focused test could verify:

it('uses Store.localeOrRegion() for dictionary cache key and fetch', async () => {
    // Set region override to simulate variation navigation
    Store.search.set((prev) => ({ ...prev, region: 'fr_FR' }));
    Store.filters.value = { locale: 'en_US' };
    // ... setup mocks for getDictionary ...
    await repository.loadPreviewPlaceholders();
    // Verify getDictionary was called with locale: 'fr_FR' (not 'en_US')
    // Verify cache key is 'fr_FR_sandbox' (not 'en_US_sandbox')
});

Without this, if someone reverts one of the 4 call sites but not the others, no test catches the inconsistency.

await this.#attachParentToCachedVariation(existingStore, fragmentPath);
const fragmentLocale = extractLocaleFromPath(fragmentPath);
if (fragmentLocale && fragmentLocale !== Store.localeOrRegion()) {
Store.search.set((prev) => ({ ...prev, region: fragmentLocale }));
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.

New cached-store locale reload path has no test coverage

These 6 new lines add locale-specific placeholder loading to #initializeFromCachedStore — this is the path Codecov flagged (4 missing lines, 71.42% patch coverage on this file).

The existing test at mas-fragment-editor.test.js:358 only exercises #initializeFromRepository (non-cached). A test for this path would look like:

it('reloads locale placeholders for cached variations when locale differs', async () => {
    const fragmentData = createFragmentData({ id: 'cached-var', locale: 'fr_FR', slug: 'var' });
    const fragment = new Fragment(fragmentData);
    const sourceStore = generateFragmentStore(fragment);
    Store.fragments.list.data.set([sourceStore]);
    Store.filters.value = { locale: 'en_US' };

    el.editorContextStore.isVariation.returns(true);
    sandbox.stub(el, 'resolveVariationParentFragment').resolves(null);
    Store.fragmentEditor.fragmentId.value = 'cached-var';

    await el.initFragment();

    expect(mockRepo.loadPreviewPlaceholders.calledOnce).to.be.true;
    expect(Store.search.get().region).to.equal('fr_FR');
});

Comment thread studio/test/mas-fragment-editor.test.js Outdated

expect(mockRepo.loadPreviewPlaceholders.callCount).to.equal(2);
expect(resolvePreviewSpy.calledOnce).to.be.true;
expect(mockRepo.loadPreviewPlaceholders.calledOnce).to.be.true;
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.

Weakened assertion — preview resolution no longer verified

The old test verified both loadPreviewPlaceholders call count AND resolvePreviewFragment being called. The new test only checks calledOnce and region.

In the new flow, preview resolution happens implicitly via PreviewFragmentStore's constructor subscription to Store.placeholders.preview. This works, but is no longer tested for the variation-locale-differs path.

Consider adding a lightweight assertion that Store.placeholders.preview was set (which confirms the dictionary loaded), even if you don't spy on resolvePreviewFragment directly.

Comment thread studio/src/mas-repository.js Outdated
url: 'https://odinpreview.corp.adobe.com/adobe/sites/cf/fragments',
},
locale: this.filters.value.locale,
locale: Store.localeOrRegion(),
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.

Suggestion: Capture locale once to avoid mid-execution drift

Store.localeOrRegion() is called 3 times during a single loadPreviewPlaceholders execution (lines 649, 674, 677) plus once in fetchDictionary (line 710). If an async operation modifies Store.search.region between these calls, the cache key check at line 674 could mismatch.

Not a blocker (in practice these are sequential), but a defensive improvement:

async loadPreviewPlaceholders() {
    if (!this.search.value.path) return;
    const locale = Store.localeOrRegion();
    const cacheKey = `${locale}_${this.search.value.path}`;
    // ... use `locale` throughout instead of re-calling Store.localeOrRegion()
}

Copy link
Copy Markdown
Contributor

@yesil yesil left a comment

Choose a reason for hiding this comment

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

Sequential placeholder loading introduces a latency cost on every fragment open

The sequential load introduced in this PR (get fragment → set region → load placeholders) solves the wrong-locale bug but adds ~200ms to every fragment open, not just cross-locale variations.

Root cause of the constraint: you need fragment.path before you know which locale to use for the variation, so the placeholder fetch cannot start until getById resolves. That dependency is real but only applies to the variation locale — the surface locale is already known before getById is called.

Proposed approach: locale-keyed dictionary map

Instead of a single Store.placeholders.preview slot, maintain a map of dictionaries keyed by locale. The load side writes into the correct slot when a fetch completes. The render side reads whichever slot matches the current active locale, reactively — no coordination, no abort, no explicit resolvePreviewFragment call needed.

Loading: fire for the surface locale immediately on fragment open. After getById resolves and the variation locale is known, fire for that locale too. Both calls are fire-and-forget. If a locale was already cached from a previous visit, it resolves instantly.

Rendering: the preview consumer reads previewByLocale[currentLocale]. When the correct locale slot populates, the reactive system triggers the re-render automatically.

No abort needed: if the user navigates away before a fetch completes, the result lands in its slot harmlessly. The cache makes the next visit to that locale instant. You trade a small amount of unnecessary network completion for zero coordination code.

Timeline

getById()               ░░░░░░░░░░░░░░░░░░░░▓
placeholders[en_US]     ░░░░░░░░░░░░▓              → preview renders immediately (surface locale)
placeholders[fr_FR]                     ░░░░░░░░░░▓  → preview re-renders with correct locale

Compare to the current PR:

getById()               ░░░░░░░░░░░░░░░░░░░░▓
                                             │
placeholders[fr_FR]                          ░░░░░░░░░░▓  → first render, correct but delayed

And the regression vs main for non-variation fragments (the common case):

// main branch
getById()               ░░░░░░░░░░░░░░░░░░░░▓
placeholders[en_US]     ░░░░░░░░░░░░▓              → already done by the time getById lands

// this PR
getById()               ░░░░░░░░░░░░░░░░░░░░▓
placeholders[en_US]                          ░░░░░░░░░░▓  → ~200ms added on every open

# Conflicts:
#	studio/src/mas-fragment-editor.js
# Conflicts:
#	studio/src/mas-repository.js
@afmicka afmicka merged commit 916b5e4 into main Apr 21, 2026
12 of 13 checks passed
@afmicka afmicka deleted the MWPW-192450 branch April 21, 2026 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants