feat(experiments): the page's experiments as a panel beside the canvas (#37478) - #37485
feat(experiments): the page's experiments as a panel beside the canvas (#37478)#37485oidacra wants to merge 6 commits into
Conversation
|
Claude finished @oidacra's task in 3m 1s —— View job Code ReviewReviewed the phase-1 panel plumbing (store, panel component, EMA shell integration), the constants/i18n, and the tests against One non-blocking finding: New Issues
Nothing else stood out. The Note: the review comments/threads on · branch |
db98ee0 to
07837ad
Compare
07837ad to
863f9ad
Compare
erickgonzalez
left a comment
There was a problem hiding this comment.
Just a nitpick:
D15 contradicts an already-approved spec
D15 says so itself: #37005 SC-002 promises an operator switch on this same flag, and FR-047/FR-048 rest on the opposite reframing. Two approved specs cannot both be right, and FR-048 is a hard requirement built on that reading.
Resolve before planning: amend #37005's SC-002 and get it re-approved, or drop FR-048 from this spec.
|
@erickgonzalez on D15 and #37005 — you were right that the spec said this, and the spec was wrong to say it. I went back to #37005's text and there is no contradiction to resolve, so neither of the two resolutions is needed. D15 is corrected instead. #37005 already hands the flag's removal to #37008. Two places in its own text:
Its US3 is also P2 and scoped to "someone who deliberately opts in". So on the flag's lifetime the two specs agree, and D15 was asserting a disagreement that is not in the text. SC-002 and FR-048 constrain different things. SC-002 measures how fast a flip takes effect ("under one minute, without a deployment or restart") — it says nothing about how long the flag lives or what may be built behind it. FR-048 constrains what may be built on either side. This spec keeps #37005's own uncached, fail-closed reader unchanged (FR-005): FR-048 also conflicts with nothing in #37005 — its FR-014, "the switch MUST NOT change the behavior of any capability other than the ... entry point", pushes the same way. What changed in the spec: D15's last paragraph no longer claims a contradiction, and instead records this check with the citations above. The note under US3 that said the reframing "needs reconciling with #37005's own spec" is corrected too. FR-047 and FR-048 stand as they were, and #37005 needs no amendment. On "resolve before planning" — the plan ran after this verification, and nothing in it turned on the outcome: the reader is #37005's, unchanged, and flag-off remains today's behavior exactly. The only planning artifact that moved was the note recording the check. |
…canvas Spec for #37478. Keeps #37005's premise -- the editor's Experiments gesture means "the experiments for this page" -- and changes only where they are shown: beside the page instead of instead of it. The flow: the panel always opens on the page's list, whose empty state carries New Experiment; the page is fixed context in creation and configuration and says why; a variant's Preview or Edit Content leaves for the page and returns to the same experiment's configuration rather than to the list; and results render in the panel too, reached from configuration or from the toolbar's running-experiment badge. Three phases: list, configuration, results. Experiments are scoped to a page, not to a language version of one -- DotExperiment carries pageId and no language, and the page-scoped read narrows on pageId alone. The language #37005 carries from the editor is return context, not a filter, so creation stores none, a language change does not re-scope the panel, and the round trip carries it only to land the editor back on the version they came from. Grounded against this branch, and two claims are corrected. The list's row menu does not read push-publish environments off the route, so Push Publish and Add to Bundle need no route plumbing in a panel (D9). And the editor already fetches an experiment on every page load to feed an existing toolbar badge, so a "zero experiments requests" budget was never achievable -- the budget is that the panel adds none (FR-036). That badge is a second entry point that deep-links to the legacy route and ejects, so it is brought into scope (D14), which is what pulls results into the panel (D2). The flag is a development and QA gate, removed with the legacy screens by #37008 -- not a long-lived product switch. This contradicts #37005's SC-002, which promises operators can flip it live; recorded in D15 for resolution in that spec. 60 FRs, 17 SCs, 7 user stories, 15 resolved decisions, no open clarifications. Panel width for configuration and results stays an explicit open decision (O1).
…ifetime D15 declared a contradiction with #37005 (UVE integration), reading its SC-002 as a promise that FEATURE_FLAG_EXPERIMENTS_PORTLET is a permanent operator switch. Checked against that spec's text: it says the opposite. Its Scope Note and its "Relationship to #37008" section both hand the flag's removal to #37008 (migration), together with its shipped default and its wiring, and its US3 is P2 and scoped to someone who deliberately opts in. The two specs agree on the flag's lifetime. SC-002 measures how fast a flip takes effect, not how long the flag lives or what may be built behind it. FR-005 keeps #37005's own uncached, fail-closed reader unchanged, so SC-002 goes on holding here. FR-048 conflicts with nothing in #37005 either — its FR-014, "the switch MUST NOT change the behavior of any capability other than the entry point", pushes the same way. D15's last paragraph now records that check with the citations instead of claiming the conflict, and the note under US3 no longer says the framing needs reconciling. FR-047 and FR-048 stand as they were; #37005 needs no amendment. Raised in review on #37485 (this PR).
…y point Foundation for the Experiments panel (#37478), plus the US3 tests that describe the entry point it will hang from. DotExperimentsPanelStore holds what the editor is looking at: whether the panel is open, which of the four views it shows, and which experiment. It is provided by the UVE shell rather than by the panel, because the panel component is mounted and destroyed by the shell's `@if` and the variant round trip needs the state to survive a period during which the panel is closed. Leaving for a variant only changes the editor's query params, so the shell is never re-created; a browser reload does destroy it, which is the case the spec declines to define. close() and suspendForVariant() are two named methods rather than one with a flag. Collapsing them turns the round trip into a reset and the editor loses their place on every trip. The store lives in data-access, not beside the screens it serves: edit-ema reaches the portlet lib only through a dynamic import(), so Nx marks it lazy-loaded and forbids the static import a shell-level provider needs. data-access is the non-lazy boundary the two libs already share, and this keeps the portlet lib fully lazy — which is what the panel's load budget wants. The three US3 tests asserting the flag-on entry point are intentionally RED until the nav item stops carrying an href. The flag-off assertions pass now and must go on passing: they are characterization tests, and they are the regression net the flag exists to provide. Also adds the panel's message keys, a UVE editor page object the e2e harness did not have, and the flag-off e2e suite. Two of its scenarios are recorded as fixme with reasons: the toolbar badge needs a running experiment, and the Experiments portlet is opt-in so it sits in no layout on a stock instance.
…canvas The Experiments entry point stops being a destination (#37478, FR-001, FR-002). With FEATURE_FLAG_EXPERIMENTS_PORTLET on the navigation item carries no href, which is what EditEmaNavigationBarComponent already treats as an action, so the gesture opens a drawer beside the page instead of navigating away from it. The item also stops being highlighted, since $activeHref only matches items that have an href — the cost D1 accepts, and it needed no code. The panel is a p-drawer like the Edit Content side panel, with one deliberate difference: it is not modal and paints no mask. Edit Content is a full editing surface and blocking what is behind it is right; this panel must leave the editor usable, because the editor may navigate to another page while it is open and the panel re-scopes when they do. It is created from a dynamic import() the first time it opens and destroyed on close. @defer would read better but cannot be used: the experiments portlet lib is reached only through dynamic imports, so Nx marks it lazy-loaded and rejects any static import of it — and a @defer block still needs the component in imports:, which is one. import() is what @defer compiles to anyway. The split is the point: the whole lib stays out of what the editor loads until the panel is first opened. Only the list view is wired, and it is still a placeholder. The three US3 tests that were red now pass.
Expand/collapse with a persisted preference, Escape, click-outside, the transparent mask and the width transition — the drawer chrome the Edit Content side panel already has, copied deliberately rather than reinvented. Each of the four is wrong in a way that is invisible until someone hits it, and each has already been paid for once: - Click-outside compares against this drawer's own mask, never the shared .p-drawer-mask class, so another drawer's mask cannot close this panel. - Escape goes through DotKeyboardShortcutService rather than a document listener of this component's own. Two document listeners cannot arbitrate — preventDefault does not stop the other — so the editor behind would act on the same keypress that closed the panel. - hasOverlayAbove consumes Escape rather than declining it, so the key never falls through when something is above. - The storage reads and writes are best-effort, so a browser with storage disabled does not break the panel. The class docs say all of this in place, name the file it was copied from, and ask that neither panel be improved alone. The extraction is scoped there too: the natural moment is phase 2, when this panel gains the unsaved-changes veto that Edit Content already solved, and DotSidePanelNavController is the piece that makes it more than a file move. Where it differs: the mask is transparent because the page behind must stay visible. The consequence is that the editor is not clickable while the panel is open — clicking there closes it instead — which is a product call worth seeing in the running editor before it is settled.
The previous commit used it to withdraw the Escape claim without importing it, so the app build failed while lint and Jest stayed green — neither typechecks the way the Angular compiler does.
863f9ad to
8718a32
Compare
Closes #37478
With
FEATURE_FLAG_EXPERIMENTS_PORTLETon, the Universal Visual Editor's Experiments entry point currently ejects the editor: thesciencenav item navigates to the portlet, the iframe is torn down, and the editor gets back to the page through the breadcrumb. This keeps the editor on the page — the experiments for the page in hand render in a panel beside the canvas, behind the same flag. No new switch, no second rollout.It also closes what #37005 (flagged UVE entry point) left half-done. Its FR-021c asked for the page filter to be "visible and clearable"; the filter is applied but has no affordance to clear it, and clearing it would be a dead end — the portlet is opt-in, so on a stock instance the admin menu carries no Experiments entry to return to. In a page-scoped panel there is no filter to clear: the panel is the page's scope by construction, and "clearable" becomes an explicit link out to the full portlet.
Proposed Changes
The flow, end to end
Two phases, one PR
Phase 1 ships usable on its own, and carries the shared plumbing below — phase 2 changes none of it.
The load-bearing work
Location.go(…{queryParamsHandling:'merge'})and re-hydrates on popstate. UVE writes its own params with no merge, replacing the whole query string. Two writers on one address: the panel's state gets silently dropped by UVE's next view change. In the panel the view state lives in memory, seeded frompageId/languageId. This is the largest piece of work here, and it is a store change.getAllUnfiltered()— every experiment on every site — then resolves each distinctpageIdthrough a bulkhtmlpageassetlookup and narrows client-side. The panel calls the existinggetAll(pageId)and skips the lookup, because UVE already holdspageAsset(). Three requests plus a full-set payload become one. Same swap point as Experiments Portlet — swap the List to the server-side contract #37007 (server-side list contract).UVE_FEATURE_FLAGSwould invert the default —withFlagsmapsFEATURE_FLAG_NOT_FOUNDtotrue. The dedicatedreadExperimentsPortletSwitchstays, and the shell already makes that request for the nav item, so the panel adds none.LIST_TABLE_STYLE'smin-width: 81rem(1296px across seven columns) cannot render at panel width, so the layout is what becomes mode-dependent; the state and the rules are shared. The page column drops in panel mode.Must not break
experiments/{pageId}destination, untouched — and the flag still fails closed on a missing key and on a failed read./experiments, reachable and unfiltered from the main navigation, keeping its table, its columns and its address-backed view state.Additional Info
Stacked on
issue-37005-experiments-uve-integration(#37256), which is still open — that branch is the base, notmain.The specification is the first commit here (
specs/37478-uve-experiments-panel/): 47 functional requirements, 16 success criteria, 7 user stories, 13 resolved decisions. Implementation follows in this same PR.One correction to the issue. #37478 item 5 states that the list route's
DotPushPublishEnvironmentsResolveris read by the row menu, and that a panel mounted outside the router would break Push Publish silently. It is not read —dot-experiments-list.component.tsnever touches route data, Push Publish opens throughDotPushPublishDialogServicewith only anassetIdentifier, anddot-add-to-bundlefetches its own bundles. That resolver is a prefetch nothing consumes (contrastdot-locales-list.component.ts:75, which does read it). Both actions therefore work from a panel row with no route plumbing. Recorded as decision D9; the issue has been corrected. The health-gate half of item 5 does hold and is covered.One decision deliberately left open (O1). The panel's width for phase 2. Configuration is a multi-card form laid out for a full-width column and its Variants card is a table, so it does not fit phase 1's width. Two candidates — a wider panel for that screen only (the Edit Content side panel already varies its own width, 80% to 100%), or a single-column reflow of the cards. It blocks nothing in phase 1.
Related: #37007 (server-side list contract, same swap point), #37008 (migration — this makes it simpler: the
experimentsrouted child and the legacy branch of the nav item both go, and no full-page eject survives). Epic: #36763 (Experiments: A/B Testing v2).Checklist