feat: load simulation panel UI in loadLazy (AEM Sidekick)#62
Conversation
v2 moved the experiment simulation UI to a hosted MFE opened by the AEM
Sidekick extension, but nothing actually loaded it: loadLazy() was an empty
placeholder, so clicking the Sidekick button was a silent no-op.
loadLazy() now wires the Sidekick toolbar button to the panel, lazy-loads the
MFE on first use, honors the ?experiment=<id>/<variant> deep-link, and restores
the panel after a variant-switch reload. It is gated to preview/dev only and
never runs in production. setupCommunicationLayer is now idempotent so eager and
lazy phases can both ensure the postMessage handshake is available.
A new `simulationUI` option ('auto' | 'sidekick' | 'universal-editor' | false)
distinguishes the EDS + Sidekick surface from the Universal Editor, where the
panel is delivered as a UE extension instead.
This supersedes the manual copy-paste listener script approach; docs updated to
just register the Sidekick plugin and wire runExperimentationLazy() into the
project's loadLazy().
Refs SITES-48456
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the simulation/preview code out of the core engine so it never ships or parses on the eager/critical path in production. The panel controller, Sidekick button wiring, deep-link/reload handling, and the postMessage handshake now live in a dedicated src/simulation.js that loadLazy pulls in via dynamic import() only in preview environments. index.js (the engine) shrinks by ~200 lines and no longer carries any UI code; production pages never fetch simulation.js. The handshake (setupCommunicationLayer) moves from the eager phase to the lazy module alongside the panel it serves. It was already preview-gated, and the panel that uses it is itself lazy/user-triggered, so this is behavior-preserving in practice; the simulation panel continues to require the loadLazy wiring. No public API change: loadLazy, runExperimentationLazy, and the simulationUI option are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Updated (a8606da) to address a performance concern: the simulation/preview UI no longer lives in the core engine ( Why this matters: previously the panel/handshake code (~200 lines) shipped inside the eager-loaded engine module and was parsed on every production experiment page, despite only ever running in preview. Now:
The No public API change ( |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Multi-perspective reviewReviewed via the diff plus targeted raw-file fetches at head ( SummaryMakes the plugin own the Sidekick simulation-panel wiring in Strengths
BlockersNone. Preview/dev-only authoring tooling that never runs in production. Should Fix1. A failed MFE script load permanently disables the button — script.onerror = (e) => {
loadPromise = null; // allow retry on next toggle
reject(e);
};Perspective: SRE / Reliability 2. Moving the handshake out of the eager phase is a consumer behavior change that isn't flagged as a migration — Nice to Have1. 2. Bounded container wait resolves even on timeout with no feedback — 3. Pre-existing: config handshake replies to Recommendations
VerdictReady to merge: with fixes — design is sound and production-safe, but the failed-load lockout (#1) and the undocumented eager→lazy handshake migration (#2) are behavior-affecting for consumers and worth addressing first. Neither is hard. |
|
Solid fix — right shape: plugin owns the wiring, lazy-loaded, prod-gated, and One item worth folding in, plus a couple of small notes. Handshake timing — the eager+lazy split the description promises would also close a real race. The PR text says The gap is the AEM Sidekick bookmarklet, which injects Minimal fix that gets both: keep the small Minor:
The one thing I can't check from here is the live Sidekick panel (button dispatch + |
…ager Address review findings on PR #62 (both the multi-perspective review and @FentPams' approval notes): - Keep the tiny, UI-less postMessage handshake (setupCommunicationLayer) in index.js and register it eagerly from loadEager under setDebugMode, so a Sidekick bookmarklet that injects the MFE early in page load doesn't race past the listener (one-shot config request -> blank/stale panel). The idempotent guard makes loadLazy's re-call a no-op. Only the handler returns to eager; the heavy MFE client.js still loads lazily on click, so production pages download/parse no simulation code. This also restores v2 behavior for projects that bump the plugin without adding runExperimentationLazy. - Reset loadPromise on script.onerror so a transient MFE load failure retries on the next toggle instead of permanently disabling the button. - Make setupSimulationUI idempotent so a re-invoked loadLazy doesn't stack duplicate button listeners; bind each sidekick element once via a dataset marker and add a bounded re-query so a missed sidekick-ready isn't silent. - Debug-log the waitForContainer timeout so a missing MFE container is distinguishable from an unwired button. - Bump VERSION/package.json to 1.2.0; README documents the eager-handshake / lazy-UI split (no consumer migration needed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ager Address review findings on PR #62 (multi-perspective review + @FentPams' approval notes): - Keep the tiny, UI-less postMessage handshake (setupCommunicationLayer) in index.js and register it eagerly from loadEager under setDebugMode, so a Sidekick bookmarklet that injects the MFE early in page load doesn't race past the listener (one-shot config request -> blank/stale panel). The idempotent guard makes loadLazy's re-call a no-op. Only the handler is eager; the heavy MFE client.js still loads lazily on click, so production pages download/parse no simulation code. Restores v2 behavior for projects that bump the plugin without adding runExperimentationLazy. - Reset loadPromise on script.onerror so a transient MFE load failure retries on the next toggle instead of permanently disabling the button. - Make setupSimulationUI idempotent; bind each sidekick element once via a dataset marker and add a bounded re-query so a missed sidekick-ready event isn't silent. - Debug-log the waitForContainer timeout so a missing MFE container is distinguishable from an unwired button. - Bump the exported VERSION const to 1.2.0; README documents the eager-handshake / lazy-UI split (no consumer migration needed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f65603e to
133c22b
Compare
|
Re-reviewed at Only remaining nit, non-blocking: the new handshake/simulation code is uncovered (codecov ~16% patch). A couple of unit tests around One process note, not code: the live Sidekick panel (real extension dispatching the button event + |
Add tests/simulation.test.js exercising src/simulation.js, which Codecov flagged as low-coverage after the extraction. Covers the Sidekick button toggle, the sidekick-ready fallback, the ?experiment deep-link, the get-config and last-modified postMessage handshakes, the reload + panel-reopen flow, and the production / simulationUI=false / universal-editor gates. The hosted MFE script is stubbed via page.route (injecting the #aemExperimentation container), and loadLazy is exposed on window through a string-content module script so the runner's Babel transform doesn't rewrite the dynamic import. Brings simulation.js to ~88% statement coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Added
The hosted MFE is stubbed via Coverage now: |
Summary
In v2 the experiment/audience/campaign simulation UI was moved out of the plugin's own injected overlay and into a hosted MFE that the AEM Sidekick extension opens. But nothing on the page ever loaded it:
loadLazy()insrc/index.jswas an empty placeholder, so the Sidekick "Experimentation" button dispatched its custom event to nobody — a silent no-op with no console error. This is the issue reported in SITES-48456.This makes the plugin own that wiring in its lazy phase, so a consuming project no longer copies a listener script into
tools/sidekick/or adds anything tohead.html.Changes
loadLazy(document, options)now:custom:aem-experimentation-sidekick) to the panel, falling back to thesidekick-readyevent since the extension injects its element asynchronously;client.json first use and toggles the panel thereafter;?experiment=<id>/<variant>simulation deep-link;setDebugMode) and never runs in production.simulationUIoption —'auto'(default) |'sidekick'|'universal-editor'|false— provides a clean way to distinguish the EDS + Sidekick surface from the Universal Editor, where the panel is delivered as a UE extension instead.'auto'only activates on the Sidekick path, so a UE context naturally gets nothing.setupCommunicationLayeris now idempotent, so the eager and lazy phases can both ensure thepostMessagehandshake exists (the panel now works even on preview pages with no experiment configured yet). Its reload handler also preserves the panel's open state across the reload.runExperimentationLazyin the loader, theloadLazy()wiring, thesimulationUIoption, and an "Enabling the simulation panel (AEM Sidekick)" section. That section keeps the still-necessary Sidekick plugin registration (config.json) and the helix5 persisted-config / PUT-vs-POST gotcha, and drops the obsolete copy-script /head.htmlsteps.documentation/experiments.md's Simulation section is corrected (v1 overlay → v2 panel,audience→experimentquery param). Addsdocumentation/sidekick/config.jsonas a reference.Relationship to #61
This supersedes the fragile half of #61: the ~167-line copy-paste
aem-experimentation.jslistener is no longer needed because the plugin owns it (versioned, no CSP-nonce trap, no silent no-op). The genuinely project-side parts of #61 — the Sidekickconfig.jsonregistration and the helix5 admin-config gotcha — are preserved here in the README. Thanks to @keepthebyte for working out the reference implementation and documenting the gotchas first-hand.Test plan
npm run lint:jscleannpx playwright test— 88 passed🤖 Generated with Claude Code