From a1e6f0e76a7704f66cc473f6d39bc01951f86da9 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Wed, 22 Jul 2026 12:00:47 +0100 Subject: [PATCH 01/13] Replace connection / disconnection / management modals with dialogs from wordpress/ui --- .../update-connection-js-package-ui-pt5 | 4 + .../components/disconnect-dialog/index.tsx | 34 ++-- .../components/disconnect-dialog/style.scss | 74 ++++--- .../disconnect-dialog/test/component.tsx | 6 +- .../manage-connection-dialog/index.tsx | 166 ++++++++-------- .../manage-connection-dialog/style.scss | 23 ++- .../test/component.tsx | 2 +- .../owner-disconnect-dialog/index.tsx | 85 ++++---- .../owner-disconnect-dialog/style.scss | 23 --- .../test/component.tsx | 13 +- .../shared/connection-dialog/index.tsx | 126 ++++++++++++ .../owner-disconnect-dialog/index.jsx | 24 +-- .../owner-disconnect-dialog/style.scss | 185 +++++++++++++++++- .../update-connection-js-package-ui-pt5 | 4 + 14 files changed, 538 insertions(+), 231 deletions(-) create mode 100644 projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 create mode 100644 projects/js-packages/connection/components/shared/connection-dialog/index.tsx create mode 100644 projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 diff --git a/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 b/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 new file mode 100644 index 000000000000..88248122fa15 --- /dev/null +++ b/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Connection UI: migrate the disconnect, manage-connection and owner-disconnect modals from the @wordpress/components Modal to the @wordpress/ui Dialog. diff --git a/projects/js-packages/connection/components/disconnect-dialog/index.tsx b/projects/js-packages/connection/components/disconnect-dialog/index.tsx index a05729b8b791..a7cf9a157380 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/index.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/index.tsx @@ -1,10 +1,10 @@ import jetpackAnalytics from '@automattic/jetpack-analytics'; import restApi from '@automattic/jetpack-api'; import { jetpackConfigHas, jetpackConfigGet } from '@automattic/jetpack-config'; -import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo, useEffect, useCallback, useState } from 'react'; import useRestApiInit from '../../hooks/use-rest-api-init'; +import ConnectionDialog from '../shared/connection-dialog'; import './style.scss'; import StepDisconnect from './steps/step-disconnect'; import StepDisconnectConfirm from './steps/step-disconnect-confirm'; @@ -425,27 +425,17 @@ const DisconnectDialog = ( { }; return ( - <> - { isOpen && ( - - { getCurrentStep() } - - ) } - + + { getCurrentStep() } + ); }; diff --git a/projects/js-packages/connection/components/disconnect-dialog/style.scss b/projects/js-packages/connection/components/disconnect-dialog/style.scss index e33e3acd66af..cb0c7bd349ae 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/style.scss +++ b/projects/js-packages/connection/components/disconnect-dialog/style.scss @@ -1,15 +1,28 @@ @use "@automattic/jetpack-base-styles/style"; .jp-connection__disconnect-dialog { + --viewport-inset: 3rem; + + display: flex; + flex-direction: column; + padding: 0; + overflow-y: auto; h1 { - margin-top: 0; + --_gcd-heading-font-size: var(--font-title-small); + --_gcd-heading-font-weight: 600; + --_gcd-heading-margin: 0 0 0.67em; + + margin: 0 0 0.67em; line-height: 1.2; font-size: var(--font-title-small); font-weight: 600; } h2 { + --_gcd-heading-font-size: var(--font-title-small); + --_gcd-heading-font-weight: 400; + margin: 0; line-height: 1.2; font-size: var(--font-title-small); @@ -17,13 +30,26 @@ } p { + --_gcd-p-font-size: var(--font-body); + margin-top: 0; font-size: var(--font-body); } + // `Text`'s own stylesheet sets `margin: 0` (layered), which for a `

` + // falls back to that default anyway, but for a bare `Text` (renders as a + // ``, e.g. `OwnerDisconnectDialog`'s copy) leaves no gap before the + // next element, since a `` has no UA margin of its own to restore. + // Same story for `line-height`: wp-admin's global `common.css` sets a bare + // `p { line-height: 1.5 }` that the raw `

` steps pick up for free, but a + // `` never matches that selector, so it's restored explicitly here. &__large-text, p.jp-connection__disconnect-dialog__large-text { + --_gcd-p-font-size: 1.25rem; + + margin: 0 0 1em; font-size: 1.25rem; + line-height: 1.5; } &__link, @@ -54,25 +80,6 @@ border-radius: 4px; } - .components-modal { - - &__content { - padding: 0; - display: flex; - flex-direction: column; - flex-grow: 1; - margin: 0; - - &::before { - display: none; - } - } - - &__header { - display: none; - } - } - // The help message grows to fill the row; the button pair keeps its natural // width beside it and wraps below once the row runs out of room. The // flex-basis biases toward wrapping early, while `min-width: 0` lets the @@ -115,6 +122,23 @@ background: var(--jp-red) !important; } + // Moved here from the owner dialog's stylesheet, which no longer renders + // these buttons — `DisconnectActionFooter` supplies its own footer actions. + // The @wordpress/ui Button emits no `.components-button` class, so the + // shared reset that wp-admin would otherwise apply is restored on the + // class itself. + &__btn-dismiss, + &__btn-disconnect { + border: 0; + border-radius: 4px; + color: var(--wp-components-color-accent-inverted, #fff); + font-size: var(--font-body-small); + height: 40px; + text-decoration: none; + text-shadow: none; + white-space: nowrap; + } + &__btn-back-to-wp { background: var(--jp-black) !important; } @@ -181,8 +205,7 @@ @media (min-width: 600px) { - .jp-connection__disconnect-dialog, - .jp-connection__disconnect-dialog.components-modal__frame { + .jp-connection__disconnect-dialog { width: 100%; max-width: calc(100% - 32px); } @@ -202,8 +225,7 @@ @media (min-width: 960px) { - .jp-connection__disconnect-dialog, - .jp-connection__disconnect-dialog.components-modal__frame { + .jp-connection__disconnect-dialog { width: 1200px; height: 900px; display: flex; @@ -213,11 +235,15 @@ .jp-connection__disconnect-dialog { h1 { + --_gcd-heading-font-size: var(--font-title-large); + font-size: var(--font-title-large); } &__large-text, p.jp-connection__disconnect-dialog__large-text { + --_gcd-p-font-size: 1.5rem; + font-size: 1.5rem; } diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx b/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx index bce8066f20d8..334a39e54583 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx @@ -38,7 +38,7 @@ describe( 'DisconnectDialog', () => { } ); describe( 'Initially', () => { - it( 'renders the Modal', () => { + it( 'renders the Dialog', () => { render( ); expect( screen.getByRole( 'dialog', { name: 'Are you sure you want to disconnect?' } ) @@ -47,10 +47,12 @@ describe( 'DisconnectDialog', () => { it( 'renders the "StepDisconnect" step', () => { render( ); + // The dialog's accessible name comes from a visually hidden Dialog.Title, + // so scope the assertion to the step's own visible

. expect( within( screen.getByRole( 'dialog', { name: 'Are you sure you want to disconnect?' } ) - ).getByRole( 'heading' ) + ).getByRole( 'heading', { level: 1 } ) ).toHaveTextContent( 'Are you sure you want to disconnect?' ); } ); } ); diff --git a/projects/js-packages/connection/components/manage-connection-dialog/index.tsx b/projects/js-packages/connection/components/manage-connection-dialog/index.tsx index ee9924207e92..8a18d7489747 100644 --- a/projects/js-packages/connection/components/manage-connection-dialog/index.tsx +++ b/projects/js-packages/connection/components/manage-connection-dialog/index.tsx @@ -5,7 +5,6 @@ import jetpackAnalytics from '@automattic/jetpack-analytics'; import restApi from '@automattic/jetpack-api'; import { getRedirectUrl } from '@automattic/jetpack-components'; import { getScriptData, isWoASite } from '@automattic/jetpack-script-data'; -import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Button, Text } from '@wordpress/ui'; import { useCallback, useState, useMemo } from 'react'; @@ -16,6 +15,7 @@ import useRestApiInit from '../../hooks/use-rest-api-init'; import ConnectionErrorNotice from '../connection-error-notice'; import DisconnectDialog from '../disconnect-dialog'; import OwnerDisconnectDialog from '../owner-disconnect-dialog'; +import ConnectionDialog, { ConnectionDialogTitle } from '../shared/connection-dialog'; import SharedHelpFooter from '../shared/help-footer'; import ManageConnectionActionCard from '../shared/manage-connection-action-card'; import type { MouseEvent } from 'react'; @@ -202,95 +202,85 @@ const ManageConnectionDialog = ( { }, [ setIsOwnerDisconnectDialogOpen ] ); return ( - <> - { isOpen && ( - <> - -
-

{ title }

- - { __( - 'At least one user must be connected for your Jetpack products to work properly.', - 'jetpack-connection-js' - ) } - - { isCurrentUserAdmin && - connectedUser.currentUser?.isConnected && - connectedUser.currentUser?.isMaster && ( - - ) } - { connectedUser.currentUser?.isConnected && ( - <> - { '' !== unlinkError && } - - - ) } - { isCurrentUserAdmin && ! isWoASite() && ( - - ) } -
- - - +
+ + { title } + + + { __( + 'At least one user must be connected for your Jetpack products to work properly.', + 'jetpack-connection-js' + ) } + + { isCurrentUserAdmin && + connectedUser.currentUser?.isConnected && + connectedUser.currentUser?.isMaster && ( + - - + { '' !== unlinkError && } + - - - ) } - + + ) } + { isCurrentUserAdmin && ! isWoASite() && ( + + ) } +
+ + + + + + ); }; diff --git a/projects/js-packages/connection/components/manage-connection-dialog/style.scss b/projects/js-packages/connection/components/manage-connection-dialog/style.scss index e205b7af08e1..6e2fddf0b34a 100644 --- a/projects/js-packages/connection/components/manage-connection-dialog/style.scss +++ b/projects/js-packages/connection/components/manage-connection-dialog/style.scss @@ -3,8 +3,11 @@ .jp-connection__manage-dialog { --spacing-base: 8px; margin: auto; - width: 1200px; border-radius: 3px; + --viewport-inset: 3rem; + + padding: 0; + overflow-y: auto; &__content { background: var(--jp-white-off); @@ -16,6 +19,11 @@ align-items: center; h1 { + --_gcd-heading-font-size: var(--font-title-large); + --_gcd-heading-font-weight: 700; + --_gcd-heading-margin: 0; + --_gcd-heading-color: inherit; + margin: 0; font-size: var(--font-title-large); font-weight: 700; @@ -119,16 +127,11 @@ margin: var(--spacing-base) auto; text-align: left; } +} - .components-modal { +@media (min-width: 960px) { - &__header { - display: none; - } - - &__content { - margin: 0; - padding: 0; - } + .jp-connection__manage-dialog { + width: 1200px; } } diff --git a/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx b/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx index c76947fe90cf..2a7e46abe409 100644 --- a/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx +++ b/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx @@ -64,7 +64,7 @@ describe( 'ManageConnectionDialog', () => { expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument(); } ); - it( 'renders the labelled Modal when open', () => { + it( 'renders the labelled Dialog when open', () => { render( ); expect( screen.getByRole( 'dialog', { name: 'Manage your Jetpack connection' } ) diff --git a/projects/js-packages/connection/components/owner-disconnect-dialog/index.tsx b/projects/js-packages/connection/components/owner-disconnect-dialog/index.tsx index 0ea04a90359f..48b16ae28ef3 100644 --- a/projects/js-packages/connection/components/owner-disconnect-dialog/index.tsx +++ b/projects/js-packages/connection/components/owner-disconnect-dialog/index.tsx @@ -5,10 +5,11 @@ import jetpackAnalytics from '@automattic/jetpack-analytics'; import restApi from '@automattic/jetpack-api'; import { getRedirectUrl } from '@automattic/jetpack-components'; import { getScriptData } from '@automattic/jetpack-script-data'; -import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { Text } from '@wordpress/ui'; import { useCallback, useState } from 'react'; import useRestApiInit from '../../hooks/use-rest-api-init'; +import ConnectionDialog, { ConnectionDialogTitle } from '../shared/connection-dialog'; import DisconnectActionFooter from '../shared/disconnect-action-footer'; import ManageConnectionActionCard from '../shared/manage-connection-action-card'; import './style.scss'; @@ -94,50 +95,46 @@ const OwnerDisconnectDialog = ( { }, [ onDisconnected, onUnlinked ] ); return ( - isOpen && ( - -
-

- { __( 'Disconnect Owner Account', 'jetpack-connection-js' ) } -

-

- { __( - 'Disconnecting the owner account will remove the Jetpack connection for all users on this site. The site will remain connected.', - 'jetpack-connection-js' - ) } -

- - -
- +
+ + { __( 'Disconnect Owner Account', 'jetpack-connection-js' ) } + + + { __( + 'Disconnecting the owner account will remove the Jetpack connection for all users on this site. The site will remain connected.', + 'jetpack-connection-js' + ) } + + + - - ) +
+ + ); }; diff --git a/projects/js-packages/connection/components/owner-disconnect-dialog/style.scss b/projects/js-packages/connection/components/owner-disconnect-dialog/style.scss index 2b1ef9ef2046..fac4c1e1f644 100644 --- a/projects/js-packages/connection/components/owner-disconnect-dialog/style.scss +++ b/projects/js-packages/connection/components/owner-disconnect-dialog/style.scss @@ -1,32 +1,9 @@ .jp-connection__disconnect-dialog { - // Add !important to override WordPress styles. The @wordpress/ui Button does - // not emit a `.components-button` class, so target the class directly. - .jp-connection__disconnect-dialog__btn-dismiss { - background: var(--jp-black) !important; - } .jp-connection__disconnect-dialog__content { --spacing-base: 8px; } - .jp-connection__disconnect-dialog__btn-dismiss, - .jp-connection__disconnect-dialog__btn-disconnect { - border: 0; - border-radius: 4px; - color: var(--wp-components-color-accent-inverted, #fff); - font-size: var(--font-body-small); - height: 40px; - text-decoration: none; - text-shadow: none; - white-space: nowrap; - } - - .components-modal__content > div:not(.components-modal__header) { - display: flex; - flex-direction: column; - height: 100%; - } - // The @wordpress/ui Card renders as an anchor (the class sits on the // itself). Its own styles live in an `@layer`, which wp-admin's unlayered // global `a` rules override — so restore the prior card-headline styling here. diff --git a/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx b/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx index 4723c22288f1..72048c778ddc 100644 --- a/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx +++ b/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx @@ -46,12 +46,15 @@ describe( 'OwnerDisconnectDialog', () => { } ); describe( 'when open', () => { - it( 'renders the labelled Modal', () => { + it( 'renders the labelled dialog', () => { render( ); - // Modal aria.labelledby points at the heading; assert both resolve to the same label. + // The accessible name comes from a visually hidden Dialog.Title, so + // scope the heading assertion to the visible

. expect( screen.getByRole( 'dialog', { name: dialogName } ) ).toBeInTheDocument(); expect( - within( screen.getByRole( 'dialog', { name: dialogName } ) ).getByRole( 'heading' ) + within( screen.getByRole( 'dialog', { name: dialogName } ) ).getByRole( 'heading', { + level: 1, + } ) ).toHaveTextContent( dialogName ); } ); @@ -107,7 +110,9 @@ describe( 'OwnerDisconnectDialog', () => { await user.click( screen.getByRole( 'button', { name: 'Disconnect' } ) ); await expect( - screen.findByText( /There was a problem disconnecting your account/ ) + within( screen.getByRole( 'dialog', { name: dialogName } ) ).findByText( + /There was a problem disconnecting your account/ + ) ).resolves.toBeInTheDocument(); // Button re-enabled after failure so the user can retry. expect( screen.getByRole( 'button', { name: 'Disconnect' } ) ).toBeEnabled(); diff --git a/projects/js-packages/connection/components/shared/connection-dialog/index.tsx b/projects/js-packages/connection/components/shared/connection-dialog/index.tsx new file mode 100644 index 000000000000..fcff8ee49de8 --- /dev/null +++ b/projects/js-packages/connection/components/shared/connection-dialog/index.tsx @@ -0,0 +1,126 @@ +import { + Dialog, + VisuallyHidden, + getWpCompatOverlaySlot, + useEnableWpCompatOverlaySlot, +} from '@wordpress/ui'; +import { useCallback, useRef } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; + +type DialogOpenChange = NonNullable< ComponentProps< typeof Dialog.Root >[ 'onOpenChange' ] >; + +interface ConnectionDialogBaseProps { + /** Whether the dialog is open. */ + isOpen?: boolean; + /** Callback fired when the dialog asks to close. */ + onClose: VoidFunction; + /** Whether pressing Escape dismisses the dialog. */ + dismissOnEscape?: boolean; + /** Class name applied to the dialog popup. */ + className?: string; + /** The dialog body. */ + children?: ReactNode; +} + +/** + * The title is set exactly one way, enforced by the type: + * + * `hasOwnTitle`: the body renders its own `ConnectionDialogTitle`, which + * supplies the accessible name. `title` is then neither needed nor allowed. + * Otherwise `title` is required, and rendered for assistive technology only, + * so the dialog keeps a stable accessible name regardless of which step the + * body is showing. + */ +export type ConnectionDialogProps = ConnectionDialogBaseProps & + ( { hasOwnTitle: true; title?: never } | { hasOwnTitle?: false; title: string } ); + +/** + * Shared `@wordpress/ui` Dialog scaffolding for the connection dialogs. + * + * Centralises the dismissal policy the connection dialogs share: they are + * modal, cannot be dismissed by clicking the backdrop, and (by default) ignore + * Escape, so leaving is an explicit choice made through a footer action. + * + * @param {ConnectionDialogProps} props - The component props. + * @return {import('react').ReactNode} - The ConnectionDialog component. + */ +const ConnectionDialog = ( { + isOpen, + onClose, + title, + hasOwnTitle = false, + dismissOnEscape = false, + className, + children, +}: ConnectionDialogProps ) => { + // `Dialog` portals to `` with no z-index of its own, which leaves it + // below wp-admin chrome such as the admin menu. The compat overlay slot is + // the package's own answer to that. + useEnableWpCompatOverlaySlot(); + + // wp-admin screens are dense with links, and Base UI focuses the first + // tabbable element on open — which lands on whatever link happens to come + // first in the body. Focus the popup instead, as the old Modal did. + const popupRef = useRef< HTMLDivElement >( null ); + + const handleOpenChange = useCallback< DialogOpenChange >( + ( open, eventDetails ) => { + if ( open ) { + return; + } + + if ( ! dismissOnEscape && eventDetails.reason === 'escape-key' ) { + // Leave the dialog open, and let the keydown reach listeners + // further up the tree (the disconnect step tracks its own). + eventDetails.cancel(); + eventDetails.allowPropagation(); + return; + } + + onClose(); + }, + [ dismissOnEscape, onClose ] + ); + + return ( + + { /* `stretch` drops the preset max-width so the dialog stylesheets keep owning the sizing. */ } + } + className={ className } + size="stretch" + > + { ! hasOwnTitle && }>{ title } } + { children } + + + ); +}; + +export interface ConnectionDialogTitleProps { + /** The heading id, for consumers that reference it from other markup. */ + id?: string; + /** Class name applied to the heading. */ + className?: string; + /** The heading content. */ + children?: ReactNode; +} + +/** + * The visible dialog heading, doubling as the dialog's accessible name. + * + * Renders as an `

` to preserve the heading level the connection dialogs + * used before the `Modal` migration. + * + * @param {ConnectionDialogTitleProps} props - The component props. + * @return {import('react').ReactNode} - The ConnectionDialogTitle component. + */ +export const ConnectionDialogTitle = ( { children, ...props }: ConnectionDialogTitleProps ) => ( + } { ...props }> + { children } + +); + +export default ConnectionDialog; diff --git a/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/index.jsx b/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/index.jsx index 920b5fb0e7f5..21bb1b1c37d1 100644 --- a/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/index.jsx +++ b/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/index.jsx @@ -95,17 +95,17 @@ const OwnerDisconnectDialog = ( { isOpen, onClose, apiRoot, apiNonce, onDisconne { /* Modal content */ } -
-

+
+

{ __( 'Disconnect Owner Account', 'jetpack' ) }

-

+

{ __( 'Disconnecting the owner account will remove the Jetpack connection for all users on this site. The site will remain connected.', 'jetpack' @@ -123,7 +123,7 @@ const OwnerDisconnectDialog = ( { isOpen, onClose, apiRoot, apiNonce, onDisconne action="check-users" />

-
+
{ /* Footer content */ }
@@ -141,32 +141,32 @@ const OwnerDisconnectDialog = ( { isOpen, onClose, apiRoot, apiNonce, onDisconne href={ getRedirectUrl( 'why-the-wordpress-com-connection-is-important-for-jetpack' ) } - className="jp-connection__disconnect-dialog__link" + className="jp-owner-disconnect-dialog__link" /> ), supportLink: ( ), } ) }

-
+
{ disconnectError && ( -

{ disconnectError }

+

{ disconnectError }

) }
diff --git a/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss b/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss index 5399b41dbe6a..7cd31f1f6121 100644 --- a/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss +++ b/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss @@ -1,9 +1,192 @@ -// Self-contained styles for this plugin's OwnerDisconnectDialog action cards. +// Self-contained styles for this plugin's OwnerDisconnectDialog. // Historically this component freeloaded on the connection package's global +// `.jp-connection__disconnect-dialog*` and // `.jp-connection__manage-dialog__action-card*` classes. // @todo: refactor Jetpack plugin connection components as the duplication and // workarounds should be avoided. +.jp-owner-disconnect-dialog { + + h1 { + margin-top: 0; + line-height: 1.2; + font-size: var(--font-title-small); + font-weight: 600; + } + + p { + margin-top: 0; + font-size: var(--font-body); + } + + &__large-text, + p.jp-owner-disconnect-dialog__large-text { + font-size: 1.25rem; + } + + &__link, + .jp-owner-disconnect-dialog__link { + color: var(--jp-black); + text-decoration: underline; + height: auto; + font: inherit; + padding: 0; + + &:hover { + color: var(--jp-black); + text-decoration-thickness: var(--jp-underline-thickness); + } + + &:focus { + color: var(--jp-black); + } + } + + .components-button { + height: 40px; + font-size: var(--font-body-small); + border-radius: 4px; + } + + // Suppress the Modal's own header/close button and padding: this dialog draws + // its own heading and sticky footer edge to edge. + .components-modal { + + &__content { + padding: 0; + display: flex; + flex-direction: column; + flex-grow: 1; + margin: 0; + + &::before { + display: none; + } + } + + &__header { + display: none; + } + } + + .components-modal__content > div:not(.components-modal__header) { + display: flex; + flex-direction: column; + height: 100%; + } + + &__actions { + background: var(--jp-white); + padding: 2rem 1rem; + position: sticky; + bottom: 0; + border-top: 1px solid var(--jp-gray); + + p { + margin-bottom: 0; + } + + &::before { + content: ""; + display: block; + width: 100%; + position: absolute; + height: 80px; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--jp-white-off)); + bottom: calc(100% + 1px); + left: 0; + } + } + + &__btn-dismiss, + &__btn-dismiss.components-button { // override the components-button class + background: var(--jp-black) !important; + margin-right: 10px; + } + + &__btn-disconnect { + background: var(--jp-red) !important; + } + + &__button-wrap { + // `flex: 0 1` + `min-width: 0` lets the pair shrink below its + // two-button max-content on narrow screens, so its own `wrap` can stack + // the buttons instead of overflowing the viewport. + flex: 0 1 auto; + min-width: 0; + } + + &__error { + color: var(--jp-red); + } +} + +.jp-owner-disconnect-dialog__content { + --spacing-base: 8px; + + background: var(--jp-white-off); + margin: 0; + padding: 2rem 1rem; + border-radius: 4px; + text-align: center; + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +@media (min-width: 600px) { + + .jp-owner-disconnect-dialog, + .jp-owner-disconnect-dialog.components-modal__frame { + width: 100%; + max-width: calc(100% - 32px); + } + + .jp-owner-disconnect-dialog { + + &__content { + padding: 2rem; + } + + &__actions { + padding: 2rem; + } + } +} + +@media (min-width: 960px) { + + .jp-owner-disconnect-dialog, + .jp-owner-disconnect-dialog.components-modal__frame { + width: 1200px; + height: 900px; + display: flex; + flex-direction: column; + } + + .jp-owner-disconnect-dialog { + + h1 { + font-size: var(--font-title-large); + } + + &__large-text, + p.jp-owner-disconnect-dialog__large-text { + font-size: 1.5rem; + } + + &__content { + padding: 80px; + } + + &__actions { + padding: 2rem 3rem; + } + } +} + .jp-owner-disconnect-dialog__action-card { background-color: var(--jp-white); width: 750px; diff --git a/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 b/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 new file mode 100644 index 000000000000..1bac027a00cf --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Owner disconnect dialog: namespace its styles to the plugin so it no longer inherits the connection package's dialog stylesheet. From 74281ef9ea0bd4d7ef3f46416e658898a8abd9f2 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Wed, 22 Jul 2026 13:42:55 +0100 Subject: [PATCH 02/13] Accessibility fixes --- .../components/disconnect-dialog/index.tsx | 3 +-- .../steps/step-disconnect-confirm.tsx | 5 +++-- .../disconnect-dialog/steps/step-disconnect.tsx | 3 ++- .../disconnect-dialog/steps/step-survey.tsx | 5 ++++- .../disconnect-dialog/steps/step-thank-you.tsx | 5 ++++- .../components/disconnect-dialog/test/component.tsx | 4 ++-- .../test/step-disconnect-confirm.tsx | 12 +++++++++++- .../disconnect-dialog/test/step-disconnect.tsx | 12 +++++++++++- .../disconnect-dialog/test/step-thank-you.tsx | 12 +++++++++++- .../owner-disconnect-dialog/test/component.tsx | 4 ++-- .../components/shared/connection-dialog/index.tsx | 8 +++----- .../components/owner-disconnect-dialog/style.scss | 2 +- .../changelog/update-connection-js-package-ui-pt5 | 2 +- 13 files changed, 56 insertions(+), 21 deletions(-) diff --git a/projects/js-packages/connection/components/disconnect-dialog/index.tsx b/projects/js-packages/connection/components/disconnect-dialog/index.tsx index a7cf9a157380..98a11ffce179 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/index.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/index.tsx @@ -270,7 +270,6 @@ const DisconnectDialog = ( { const handleDisconnect = useCallback( ( e?: MouseEvent< HTMLElement > ) => { e && e.preventDefault(); - setDisconnectError( false ); setIsDisconnecting( true ); @@ -428,7 +427,7 @@ const DisconnectDialog = ( {
-

+ { createInterpolateElement( __( 'Jetpack has been
successfully disconnected.', 'jetpack-connection-js' ), { br:
, } ) } -

+ { canProvideFeedback && ( <> diff --git a/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.tsx b/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.tsx index 1bd04f605c48..5393392efbb2 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.tsx @@ -1,6 +1,7 @@ import { __ } from '@wordpress/i18n'; import { Fragment, useCallback, useEffect } from 'react'; import ConnectedPlugins from '../../connected-plugins'; +import { ConnectionDialogTitle } from '../../shared/connection-dialog'; import DisconnectActionFooter from '../../shared/disconnect-action-footer'; import type { StepDisconnectProps } from './types'; import type { MouseEvent } from 'react'; @@ -120,7 +121,7 @@ const StepDisconnect = ( { return (
-

{ title }

+ { title } { return (
-

{ __( 'Before you go, help us improve Jetpack', 'jetpack-connection-js' ) }

+ + { __( 'Before you go, help us improve Jetpack', 'jetpack-connection-js' ) } +

{ __( 'Let us know what didn‘t work for you', 'jetpack-connection-js' ) }

diff --git a/projects/js-packages/connection/components/disconnect-dialog/steps/step-thank-you.tsx b/projects/js-packages/connection/components/disconnect-dialog/steps/step-thank-you.tsx index 81b2f2e164ff..b403466615ce 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/steps/step-thank-you.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/steps/step-thank-you.tsx @@ -2,6 +2,7 @@ import { DecorativeCard } from '@automattic/jetpack-components'; import { createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Button, Stack } from '@wordpress/ui'; +import { ConnectionDialogTitle } from '../../shared/connection-dialog'; import disconnectImage from '../images/disconnect-thanks.jpg'; import type { MouseEvent } from 'react'; @@ -27,7 +28,9 @@ const StepThankYou = ( { onExit }: StepThankYouProps ) => { align="center" gap="md" > -

{ __( 'Thank you!', 'jetpack-connection-js' ) }

+ + { __( 'Thank you!', 'jetpack-connection-js' ) } +

{ createInterpolateElement( __( diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx b/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx index 334a39e54583..4fc24981e108 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/test/component.tsx @@ -47,8 +47,8 @@ describe( 'DisconnectDialog', () => { it( 'renders the "StepDisconnect" step', () => { render( ); - // The dialog's accessible name comes from a visually hidden Dialog.Title, - // so scope the assertion to the step's own visible

. + // Each step renders its heading as the dialog's `Dialog.Title`, so the + // accessible name tracks the current step's own visible

. expect( within( screen.getByRole( 'dialog', { name: 'Are you sure you want to disconnect?' } ) diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect-confirm.tsx b/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect-confirm.tsx index 3d33161bd176..ab2819914c02 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect-confirm.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect-confirm.tsx @@ -1,7 +1,17 @@ import { jest } from '@jest/globals'; -import { render, screen } from '@testing-library/react'; +import { render as rtlRender, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Dialog } from '@wordpress/ui'; import StepDisconnectConfirm from '../steps/step-disconnect-confirm'; +import type { ReactElement, ReactNode } from 'react'; + +// The step renders its heading as a `Dialog.Title`, which needs a Dialog +// context. In the app that's always `ConnectionDialog`; in isolation, supply a +// minimal one so the heading can register. +const DialogWrapper = ( { children }: { children: ReactNode } ) => ( + { children } +); +const render = ( ui: ReactElement ) => rtlRender( ui, { wrapper: DialogWrapper } ); describe( 'StepDisconnectConfirm', () => { const testProps = { diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.tsx b/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.tsx index 1a44bd6cd657..c79a8bb29411 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.tsx @@ -1,7 +1,17 @@ import { jest } from '@jest/globals'; -import { render, screen } from '@testing-library/react'; +import { render as rtlRender, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Dialog } from '@wordpress/ui'; import StepDisconnect from '../steps/step-disconnect'; +import type { ReactElement, ReactNode } from 'react'; + +// The step renders its heading as a `Dialog.Title`, which needs a Dialog +// context. In the app that's always `ConnectionDialog`; in isolation, supply a +// minimal one so the heading can register. +const DialogWrapper = ( { children }: { children: ReactNode } ) => ( + { children } +); +const render = ( ui: ReactElement ) => rtlRender( ui, { wrapper: DialogWrapper } ); describe( 'StepDisconnect', () => { const testProps = { diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/step-thank-you.tsx b/projects/js-packages/connection/components/disconnect-dialog/test/step-thank-you.tsx index cee148e2f02b..8157f730a520 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/test/step-thank-you.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/test/step-thank-you.tsx @@ -1,7 +1,17 @@ import { jest } from '@jest/globals'; -import { render, screen } from '@testing-library/react'; +import { render as rtlRender, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Dialog } from '@wordpress/ui'; import StepThankYou from '../steps/step-thank-you'; +import type { ReactElement, ReactNode } from 'react'; + +// The step renders its heading as a `Dialog.Title`, which needs a Dialog +// context. In the app that's always `ConnectionDialog`; in isolation, supply a +// minimal one so the heading can register. +const DialogWrapper = ( { children }: { children: ReactNode } ) => ( + { children } +); +const render = ( ui: ReactElement ) => rtlRender( ui, { wrapper: DialogWrapper } ); describe( 'StepThankYou', () => { const testProps = { diff --git a/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx b/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx index 72048c778ddc..a976a384472c 100644 --- a/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx +++ b/projects/js-packages/connection/components/owner-disconnect-dialog/test/component.tsx @@ -48,8 +48,8 @@ describe( 'OwnerDisconnectDialog', () => { describe( 'when open', () => { it( 'renders the labelled dialog', () => { render( ); - // The accessible name comes from a visually hidden Dialog.Title, so - // scope the heading assertion to the visible

. + // The body's `ConnectionDialogTitle` is the dialog's `Dialog.Title`, so + // its visible

doubles as the accessible name. expect( screen.getByRole( 'dialog', { name: dialogName } ) ).toBeInTheDocument(); expect( within( screen.getByRole( 'dialog', { name: dialogName } ) ).getByRole( 'heading', { diff --git a/projects/js-packages/connection/components/shared/connection-dialog/index.tsx b/projects/js-packages/connection/components/shared/connection-dialog/index.tsx index fcff8ee49de8..09e447e88883 100644 --- a/projects/js-packages/connection/components/shared/connection-dialog/index.tsx +++ b/projects/js-packages/connection/components/shared/connection-dialog/index.tsx @@ -25,11 +25,9 @@ interface ConnectionDialogBaseProps { /** * The title is set exactly one way, enforced by the type: * - * `hasOwnTitle`: the body renders its own `ConnectionDialogTitle`, which - * supplies the accessible name. `title` is then neither needed nor allowed. - * Otherwise `title` is required, and rendered for assistive technology only, - * so the dialog keeps a stable accessible name regardless of which step the - * body is showing. + * `hasOwnTitle`: the body renders its own `ConnectionDialogTitle` (a + * `Dialog.Title`), which supplies the accessible name. `title` is then neither + * needed nor allowed. */ export type ConnectionDialogProps = ConnectionDialogBaseProps & ( { hasOwnTitle: true; title?: never } | { hasOwnTitle?: false; title: string } ); diff --git a/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss b/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss index 7cd31f1f6121..0b8d9d2c7eda 100644 --- a/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss +++ b/projects/plugins/jetpack/_inc/client/components/owner-disconnect-dialog/style.scss @@ -101,7 +101,7 @@ &__btn-dismiss, &__btn-dismiss.components-button { // override the components-button class background: var(--jp-black) !important; - margin-right: 10px; + margin-inline-end: 10px; } &__btn-disconnect { diff --git a/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 b/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 index 1bac027a00cf..6d15ebf916e0 100644 --- a/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 +++ b/projects/plugins/jetpack/changelog/update-connection-js-package-ui-pt5 @@ -1,4 +1,4 @@ Significance: patch -Type: fixed +Type: other Owner disconnect dialog: namespace its styles to the plugin so it no longer inherits the connection package's dialog stylesheet. From 31bda4ed5874b56efeaadd968dae29839d460e6b Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Wed, 22 Jul 2026 17:19:45 +0100 Subject: [PATCH 03/13] Changlog tweaks and string apostrophe update --- .../connection/changelog/update-connection-js-package-ui-pt5 | 2 +- .../components/disconnect-dialog/steps/step-survey.tsx | 2 +- .../jetpack/changelog/update-connection-js-package-ui-pt5 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 b/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 index 88248122fa15..0af4b5cca6ff 100644 --- a/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 +++ b/projects/js-packages/connection/changelog/update-connection-js-package-ui-pt5 @@ -1,4 +1,4 @@ Significance: patch Type: changed -Connection UI: migrate the disconnect, manage-connection and owner-disconnect modals from the @wordpress/components Modal to the @wordpress/ui Dialog. +Connection UI: Migrate the disconnect, manage-connection and owner-disconnect modals from the @wordpress/components Modal to the @wordpress/ui Dialog. diff --git a/projects/js-packages/connection/components/disconnect-dialog/steps/step-survey.tsx b/projects/js-packages/connection/components/disconnect-dialog/steps/step-survey.tsx index c25d079063b9..a4b78b073b39 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/steps/step-survey.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/steps/step-survey.tsx @@ -26,7 +26,7 @@ const StepSurvey = ( { onExit, onFeedBackProvided, isSubmittingFeedback }: StepS { __( 'Before you go, help us improve Jetpack', 'jetpack-connection-js' ) }

- { __( 'Let us know what didn‘t work for you', 'jetpack-connection-js' ) } + { __( "Let us know what didn't work for you", 'jetpack-connection-js' ) }

Date: Thu, 23 Jul 2026 16:10:59 +0100 Subject: [PATCH 04/13] Allow Manage Connection Dialog to close on esc --- .../manage-connection-dialog/index.tsx | 2 ++ .../test/component.tsx | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/projects/js-packages/connection/components/manage-connection-dialog/index.tsx b/projects/js-packages/connection/components/manage-connection-dialog/index.tsx index 8a18d7489747..2c2d504d93d3 100644 --- a/projects/js-packages/connection/components/manage-connection-dialog/index.tsx +++ b/projects/js-packages/connection/components/manage-connection-dialog/index.tsx @@ -206,6 +206,8 @@ const ManageConnectionDialog = ( { isOpen={ isOpen } onClose={ onClose } hasOwnTitle + // Escape is allowed, except while an unlink request is in flight. + dismissOnEscape={ ! isControlsDisabled } className="jp-connection__manage-dialog" >
diff --git a/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx b/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx index 2a7e46abe409..5a686b65fc4b 100644 --- a/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx +++ b/projects/js-packages/connection/components/manage-connection-dialog/test/component.tsx @@ -78,6 +78,35 @@ describe( 'ManageConnectionDialog', () => { expect( testProps.onClose ).toHaveBeenCalledTimes( 1 ); } ); + it( 'calls onClose when Escape is pressed', async () => { + const user = userEvent.setup(); + render( ); + await user.keyboard( '{Escape}' ); + expect( testProps.onClose ).toHaveBeenCalledTimes( 1 ); + } ); + + it( 'ignores Escape while an unlink request is in flight', async () => { + const user = userEvent.setup(); + // Never resolves, so the dialog stays in its "disconnecting" state. + mockUnlinkUser.mockImplementationOnce( () => new Promise( () => {} ) ); + const nonOwner = { + currentUser: { + id: 2, + username: 'editor', + isConnected: true, + isMaster: false, + permissions: { manage_options: false }, + }, + }; + render( ); + + await user.click( screen.getByRole( 'link', { name: /Disconnect my user account/ } ) ); + await user.keyboard( '{Escape}' ); + + expect( testProps.onClose ).not.toHaveBeenCalled(); + expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument(); + } ); + describe( 'action visibility', () => { it( 'shows all three actions for an admin connection owner (not WoA)', () => { render( ); From 6afb0cc7c6a4864a7a79a5ee10163e8ef8cf4d27 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 12:45:03 +0100 Subject: [PATCH 05/13] Bundles @wordpress/theme and @wordpress/private-apis directly into jetpack-connection.js --- .../changelog/update-connection-js-package-modal-to-dialog | 4 ++++ projects/packages/connection/webpack.config.js | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog diff --git a/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog b/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..c58f84e23345 --- /dev/null +++ b/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Connection UI: Bundles wordpress/theme and @wordpress/private-apis directly into jetpack-connection.js. diff --git a/projects/packages/connection/webpack.config.js b/projects/packages/connection/webpack.config.js index 16a2433815e2..1083def8ad0c 100644 --- a/projects/packages/connection/webpack.config.js +++ b/projects/packages/connection/webpack.config.js @@ -104,6 +104,13 @@ module.exports = [ requestMap: { // We don't want to externalize this package, we rather want to bundle it. '@automattic/jetpack-connection': {}, + // @wordpress/ui's overlay components (Dialog, Popover, etc.) depend on + // these, but they aren't registered as WP core scripts on all supported + // WordPress versions (WP 6.9 lacks `wp-theme`/`wp-private-apis`), which + // silently drops this entire script from being enqueued. Bundle them + // instead of externalizing. Matches AI, Search, Boost and others. + '@wordpress/theme': { external: false }, + '@wordpress/private-apis': { external: false }, }, }, } ), From 115d1be9d1e89d355c1cc4d05d5259017af2a9bd Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 13:46:21 +0100 Subject: [PATCH 06/13] wp-private-apis/wp-theme backed by the polyfill registration instead of duplicate bundled code --- .../update-connection-js-package-modal-to-dialog | 2 +- projects/packages/connection/composer.json | 3 ++- .../packages/connection/src/class-connection-assets.php | 9 +++++++++ projects/packages/connection/webpack.config.js | 7 ------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog b/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog index c58f84e23345..a3af3fbd1ec7 100644 --- a/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog +++ b/projects/packages/connection/changelog/update-connection-js-package-modal-to-dialog @@ -1,4 +1,4 @@ Significance: minor Type: changed -Connection UI: Bundles wordpress/theme and @wordpress/private-apis directly into jetpack-connection.js. +Connection UI: Register wp-build-polyfills for wp-theme and wp-private-apis, so jetpack-connection.js's externalized dependencies resolve on WordPress versions that don't provide them. diff --git a/projects/packages/connection/composer.json b/projects/packages/connection/composer.json index 74c48c05fabf..f0ce31522f26 100644 --- a/projects/packages/connection/composer.json +++ b/projects/packages/connection/composer.json @@ -12,7 +12,8 @@ "automattic/jetpack-ip": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", - "automattic/jetpack-redirect": "@dev" + "automattic/jetpack-redirect": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev" }, "require-dev": { "automattic/jetpack-test-environment": "@dev", diff --git a/projects/packages/connection/src/class-connection-assets.php b/projects/packages/connection/src/class-connection-assets.php index 93daae1b536f..f5bef1699acd 100644 --- a/projects/packages/connection/src/class-connection-assets.php +++ b/projects/packages/connection/src/class-connection-assets.php @@ -8,6 +8,7 @@ namespace Automattic\Jetpack\Connection; use Automattic\Jetpack\Assets; +use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills; /** * Connection_Assets class. @@ -30,6 +31,14 @@ public static function configure() { * Those scripts are intended to be used in WP admin area. */ public static function register_assets() { + // jetpack-connection.js externalizes @wordpress/theme and @wordpress/private-apis + // to wp-theme / wp-private-apis. Older WP core (and Gutenberg without a + // compatible private-apis allowlist) don't provide usable versions of those, + // which silently drops the whole script from being enqueued. Register the + // polyfills before the script below declares them as dependencies. + if ( class_exists( WP_Build_Polyfills::class ) ) { + WP_Build_Polyfills::register( 'jetpack-connection', array( 'wp-private-apis', 'wp-theme' ) ); + } Assets::register_script( 'jetpack-connection', diff --git a/projects/packages/connection/webpack.config.js b/projects/packages/connection/webpack.config.js index 1083def8ad0c..16a2433815e2 100644 --- a/projects/packages/connection/webpack.config.js +++ b/projects/packages/connection/webpack.config.js @@ -104,13 +104,6 @@ module.exports = [ requestMap: { // We don't want to externalize this package, we rather want to bundle it. '@automattic/jetpack-connection': {}, - // @wordpress/ui's overlay components (Dialog, Popover, etc.) depend on - // these, but they aren't registered as WP core scripts on all supported - // WordPress versions (WP 6.9 lacks `wp-theme`/`wp-private-apis`), which - // silently drops this entire script from being enqueued. Bundle them - // instead of externalizing. Matches AI, Search, Boost and others. - '@wordpress/theme': { external: false }, - '@wordpress/private-apis': { external: false }, }, }, } ), From ee3650f8d73c8b3334ba79c4d5a6d4d24ee9f1b6 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 13:47:10 +0100 Subject: [PATCH 07/13] Add tests specific to ConnectionDialog --- .../shared/connection-dialog/test/index.tsx | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 projects/js-packages/connection/components/shared/connection-dialog/test/index.tsx diff --git a/projects/js-packages/connection/components/shared/connection-dialog/test/index.tsx b/projects/js-packages/connection/components/shared/connection-dialog/test/index.tsx new file mode 100644 index 000000000000..e5dd01df8cdb --- /dev/null +++ b/projects/js-packages/connection/components/shared/connection-dialog/test/index.tsx @@ -0,0 +1,77 @@ +import { jest } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import ConnectionDialog, { ConnectionDialogTitle } from '../index'; + +describe( 'ConnectionDialog', () => { + const onClose = jest.fn(); + + afterEach( () => { + onClose.mockClear(); + } ); + + it( 'renders nothing when closed', () => { + render( + + Body + + ); + expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument(); + } ); + + it( 'renders the dialog with the given title when open', () => { + render( + + Body + + ); + expect( screen.getByRole( 'dialog', { name: 'Test dialog' } ) ).toBeInTheDocument(); + } ); + + it( 'uses ConnectionDialogTitle as the accessible name when hasOwnTitle is set', () => { + render( + + Own title + Body + + ); + expect( screen.getByRole( 'dialog', { name: 'Own title' } ) ).toBeInTheDocument(); + expect( screen.getByRole( 'heading', { level: 1, name: 'Own title' } ) ).toBeInTheDocument(); + } ); + + it( 'ignores Escape by default, and does not call onClose', async () => { + const user = userEvent.setup(); + render( + + Body + + ); + await user.keyboard( '{Escape}' ); + expect( onClose ).not.toHaveBeenCalled(); + expect( screen.getByRole( 'dialog' ) ).toBeInTheDocument(); + } ); + + it( 'calls onClose on Escape when dismissOnEscape is true', async () => { + const user = userEvent.setup(); + render( + + Body + + ); + await user.keyboard( '{Escape}' ); + expect( onClose ).toHaveBeenCalledTimes( 1 ); + } ); + + it( 'does not call onClose when the backdrop is clicked', async () => { + const user = userEvent.setup(); + const { container } = render( + + Body + + ); + // The backdrop is the portalled sibling preceding the popup; clicking + // outside the popup content should not dismiss (disablePointerDismissal). + await user.click( container.ownerDocument.body ); + expect( onClose ).not.toHaveBeenCalled(); + } ); +} ); From 75ad660bbe1825d2d1d819bff5dd21ad16bf5911 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 14:04:22 +0100 Subject: [PATCH 08/13] Update composer.lock files --- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/agents-manager/composer.lock | 69 ++++++++++++++++++- ...date-connection-js-package-modal-to-dialog | 5 ++ .../composer.lock | 69 ++++++++++++++++++- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/backup/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/boost/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ .../classic-theme-helper-plugin/composer.lock | 69 ++++++++++++++++++- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/inspect/composer.lock | 69 ++++++++++++++++++- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/jetpack/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ .../plugins/mu-wpcom-plugin/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ .../paypal-payment-buttons/composer.lock | 69 ++++++++++++++++++- ...date-connection-js-package-modal-to-dialog | 5 ++ .../plugins/premium-analytics/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/protect/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/search/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/social/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/starter-plugin/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/videopress/composer.lock | 3 +- ...date-connection-js-package-modal-to-dialog | 5 ++ projects/plugins/wpcloud-sso/composer.lock | 69 ++++++++++++++++++- projects/plugins/wpcomsh/composer.lock | 3 +- 33 files changed, 510 insertions(+), 17 deletions(-) create mode 100644 projects/plugins/agents-manager/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/automattic-for-agencies-client/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/backup/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/boost/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/classic-theme-helper-plugin/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/inspect/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/jetpack/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/mu-wpcom-plugin/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/paypal-payment-buttons/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/premium-analytics/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/protect/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/search/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/social/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/starter-plugin/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/videopress/changelog/update-connection-js-package-modal-to-dialog create mode 100644 projects/plugins/wpcloud-sso/changelog/update-connection-js-package-modal-to-dialog diff --git a/projects/plugins/agents-manager/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/agents-manager/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/agents-manager/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/agents-manager/composer.lock b/projects/plugins/agents-manager/composer.lock index f1381dfa2ba5..000927e657e0 100644 --- a/projects/plugins/agents-manager/composer.lock +++ b/projects/plugins/agents-manager/composer.lock @@ -418,7 +418,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -429,6 +429,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { @@ -784,6 +785,72 @@ "transport-options": { "relative": true } + }, + { + "name": "automattic/jetpack-wp-build-polyfills", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/wp-build-polyfills", + "reference": "116ceba3c357f49d40854f3083f60faa372f5c25" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-test-environment": "@dev", + "automattic/phpunit-select-config": "@dev", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-wp-build-polyfills", + "textdomain": "jetpack-wp-build-polyfills", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-wp-build-polyfills/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "test-php-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"" + ], + "test-js": [ + "pnpm run build", + "pnpm run test" + ], + "test-js-coverage": [ + "pnpm run build", + "pnpm run test-coverage" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Polyfills for WordPress Core packages not available or incomplete in older WP versions", + "transport-options": { + "relative": true + } } ], "packages-dev": [], diff --git a/projects/plugins/automattic-for-agencies-client/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/automattic-for-agencies-client/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/automattic-for-agencies-client/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/automattic-for-agencies-client/composer.lock b/projects/plugins/automattic-for-agencies-client/composer.lock index 6e4128eb43c2..662b0cb5a02e 100644 --- a/projects/plugins/automattic-for-agencies-client/composer.lock +++ b/projects/plugins/automattic-for-agencies-client/composer.lock @@ -414,7 +414,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -425,6 +425,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { @@ -975,6 +976,72 @@ "transport-options": { "relative": true } + }, + { + "name": "automattic/jetpack-wp-build-polyfills", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/wp-build-polyfills", + "reference": "116ceba3c357f49d40854f3083f60faa372f5c25" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-test-environment": "@dev", + "automattic/phpunit-select-config": "@dev", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-wp-build-polyfills", + "textdomain": "jetpack-wp-build-polyfills", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-wp-build-polyfills/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "test-php-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"" + ], + "test-js": [ + "pnpm run build", + "pnpm run test" + ], + "test-js-coverage": [ + "pnpm run build", + "pnpm run test-coverage" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Polyfills for WordPress Core packages not available or incomplete in older WP versions", + "transport-options": { + "relative": true + } } ], "packages-dev": [ diff --git a/projects/plugins/backup/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/backup/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/backup/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/backup/composer.lock b/projects/plugins/backup/composer.lock index a176ee1b099a..fe61b49f8a44 100644 --- a/projects/plugins/backup/composer.lock +++ b/projects/plugins/backup/composer.lock @@ -751,7 +751,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -762,6 +762,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/boost/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/boost/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/boost/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/boost/composer.lock b/projects/plugins/boost/composer.lock index c785e809dfd4..24bc98258509 100644 --- a/projects/plugins/boost/composer.lock +++ b/projects/plugins/boost/composer.lock @@ -608,7 +608,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -619,6 +619,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/classic-theme-helper-plugin/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/classic-theme-helper-plugin/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/classic-theme-helper-plugin/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/classic-theme-helper-plugin/composer.lock b/projects/plugins/classic-theme-helper-plugin/composer.lock index c30a98fe498e..ed9b4d5f82c1 100644 --- a/projects/plugins/classic-theme-helper-plugin/composer.lock +++ b/projects/plugins/classic-theme-helper-plugin/composer.lock @@ -599,7 +599,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -610,6 +610,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { @@ -1148,6 +1149,72 @@ "transport-options": { "relative": true } + }, + { + "name": "automattic/jetpack-wp-build-polyfills", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/wp-build-polyfills", + "reference": "116ceba3c357f49d40854f3083f60faa372f5c25" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-test-environment": "@dev", + "automattic/phpunit-select-config": "@dev", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-wp-build-polyfills", + "textdomain": "jetpack-wp-build-polyfills", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-wp-build-polyfills/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "test-php-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"" + ], + "test-js": [ + "pnpm run build", + "pnpm run test" + ], + "test-js-coverage": [ + "pnpm run build", + "pnpm run test-coverage" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Polyfills for WordPress Core packages not available or incomplete in older WP versions", + "transport-options": { + "relative": true + } } ], "packages-dev": [], diff --git a/projects/plugins/inspect/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/inspect/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/inspect/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/inspect/composer.lock b/projects/plugins/inspect/composer.lock index c747243bc804..8b51ed14fd33 100644 --- a/projects/plugins/inspect/composer.lock +++ b/projects/plugins/inspect/composer.lock @@ -414,7 +414,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -425,6 +425,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { @@ -780,6 +781,72 @@ "transport-options": { "relative": true } + }, + { + "name": "automattic/jetpack-wp-build-polyfills", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/wp-build-polyfills", + "reference": "116ceba3c357f49d40854f3083f60faa372f5c25" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-test-environment": "@dev", + "automattic/phpunit-select-config": "@dev", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-wp-build-polyfills", + "textdomain": "jetpack-wp-build-polyfills", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-wp-build-polyfills/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "test-php-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"" + ], + "test-js": [ + "pnpm run build", + "pnpm run test" + ], + "test-js-coverage": [ + "pnpm run build", + "pnpm run test-coverage" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Polyfills for WordPress Core packages not available or incomplete in older WP versions", + "transport-options": { + "relative": true + } } ], "packages-dev": [ diff --git a/projects/plugins/jetpack/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/jetpack/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..1b4d872d2ab2 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Update composer.lock. + + diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index d14ce901e8d5..b3dfb77f09b9 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -1190,7 +1190,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -1201,6 +1201,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/mu-wpcom-plugin/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/mu-wpcom-plugin/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/mu-wpcom-plugin/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/mu-wpcom-plugin/composer.lock b/projects/plugins/mu-wpcom-plugin/composer.lock index 8244878e3fbd..c72adabe73fd 100644 --- a/projects/plugins/mu-wpcom-plugin/composer.lock +++ b/projects/plugins/mu-wpcom-plugin/composer.lock @@ -716,7 +716,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -727,6 +727,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/paypal-payment-buttons/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/paypal-payment-buttons/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/paypal-payment-buttons/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/paypal-payment-buttons/composer.lock b/projects/plugins/paypal-payment-buttons/composer.lock index cd20317e9419..0b6859b7c3dd 100644 --- a/projects/plugins/paypal-payment-buttons/composer.lock +++ b/projects/plugins/paypal-payment-buttons/composer.lock @@ -399,7 +399,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -410,6 +410,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { @@ -906,6 +907,72 @@ "transport-options": { "relative": true } + }, + { + "name": "automattic/jetpack-wp-build-polyfills", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/wp-build-polyfills", + "reference": "116ceba3c357f49d40854f3083f60faa372f5c25" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-test-environment": "@dev", + "automattic/phpunit-select-config": "@dev", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-wp-build-polyfills", + "textdomain": "jetpack-wp-build-polyfills", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-wp-build-polyfills/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "test-php-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"" + ], + "test-js": [ + "pnpm run build", + "pnpm run test" + ], + "test-js-coverage": [ + "pnpm run build", + "pnpm run test-coverage" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Polyfills for WordPress Core packages not available or incomplete in older WP versions", + "transport-options": { + "relative": true + } } ], "packages-dev": [ diff --git a/projects/plugins/premium-analytics/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/premium-analytics/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/premium-analytics/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/premium-analytics/composer.lock b/projects/plugins/premium-analytics/composer.lock index 19e796af8263..f0c99c6b8097 100644 --- a/projects/plugins/premium-analytics/composer.lock +++ b/projects/plugins/premium-analytics/composer.lock @@ -477,7 +477,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -488,6 +488,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/protect/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/protect/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/protect/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index f465b662d2c5..91d084840452 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -730,7 +730,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -741,6 +741,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/search/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/search/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/search/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/search/composer.lock b/projects/plugins/search/composer.lock index 0ca5ceb8fc00..dbb4b0038b78 100644 --- a/projects/plugins/search/composer.lock +++ b/projects/plugins/search/composer.lock @@ -608,7 +608,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -619,6 +619,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/social/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/social/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/social/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/social/composer.lock b/projects/plugins/social/composer.lock index 4013144a927c..ad6d171dff7b 100644 --- a/projects/plugins/social/composer.lock +++ b/projects/plugins/social/composer.lock @@ -671,7 +671,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -682,6 +682,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/starter-plugin/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/starter-plugin/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/starter-plugin/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/starter-plugin/composer.lock b/projects/plugins/starter-plugin/composer.lock index 166b3dca7785..cf4252832383 100644 --- a/projects/plugins/starter-plugin/composer.lock +++ b/projects/plugins/starter-plugin/composer.lock @@ -608,7 +608,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -619,6 +619,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/videopress/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/videopress/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/videopress/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/videopress/composer.lock b/projects/plugins/videopress/composer.lock index 7c5656cde8aa..8353f9b4099c 100644 --- a/projects/plugins/videopress/composer.lock +++ b/projects/plugins/videopress/composer.lock @@ -608,7 +608,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -619,6 +619,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { diff --git a/projects/plugins/wpcloud-sso/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/wpcloud-sso/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..47f5577b9214 --- /dev/null +++ b/projects/plugins/wpcloud-sso/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Update composer.lock. + + diff --git a/projects/plugins/wpcloud-sso/composer.lock b/projects/plugins/wpcloud-sso/composer.lock index 96b7e794d54a..a0be5c425564 100644 --- a/projects/plugins/wpcloud-sso/composer.lock +++ b/projects/plugins/wpcloud-sso/composer.lock @@ -414,7 +414,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ce362d9d8bf97228d5267f8d4a9b05379ff98aa2" + "reference": "071dc9cb46818809827318b057919a7cd42fae24" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -425,6 +425,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { @@ -907,6 +908,72 @@ "transport-options": { "relative": true } + }, + { + "name": "automattic/jetpack-wp-build-polyfills", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/wp-build-polyfills", + "reference": "116ceba3c357f49d40854f3083f60faa372f5c25" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "automattic/jetpack-test-environment": "@dev", + "automattic/phpunit-select-config": "@dev", + "yoast/phpunit-polyfills": "^4.0.0" + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "mirror-repo": "Automattic/jetpack-wp-build-polyfills", + "textdomain": "jetpack-wp-build-polyfills", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-wp-build-polyfills/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-trunk": "0.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-production": [ + "pnpm run build-production" + ], + "build-development": [ + "pnpm run build" + ], + "phpunit": [ + "phpunit-select-config phpunit.#.xml.dist --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "test-php-coverage": [ + "php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\"" + ], + "test-js": [ + "pnpm run build", + "pnpm run test" + ], + "test-js-coverage": [ + "pnpm run build", + "pnpm run test-coverage" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "Polyfills for WordPress Core packages not available or incomplete in older WP versions", + "transport-options": { + "relative": true + } } ], "packages-dev": [], diff --git a/projects/plugins/wpcomsh/composer.lock b/projects/plugins/wpcomsh/composer.lock index d216f23b20ac..40012052d8b9 100644 --- a/projects/plugins/wpcomsh/composer.lock +++ b/projects/plugins/wpcomsh/composer.lock @@ -832,7 +832,7 @@ "dist": { "type": "path", "url": "../../packages/connection", - "reference": "ac92fb106fb48e2bdfe0946da6e387b2f61585fb" + "reference": "2a21eca9ea6ba033ea8560a5d39c0f7beef81be2" }, "require": { "automattic/jetpack-a8c-mc-stats": "@dev", @@ -843,6 +843,7 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-roles": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-wp-build-polyfills": "@dev", "php": ">=7.2" }, "require-dev": { From 2496e6fb0ce968f180ed314aff7dfe123d794e79 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 14:05:37 +0100 Subject: [PATCH 09/13] changelog --- .../changelog/update-connection-js-package-modal-to-dialog | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/plugins/wpcomsh/changelog/update-connection-js-package-modal-to-dialog diff --git a/projects/plugins/wpcomsh/changelog/update-connection-js-package-modal-to-dialog b/projects/plugins/wpcomsh/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..52e61ef65c71 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Update composer.lock From 3cf90c02dff9fd07377622eb3867c25c1b0bf255 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 16:20:46 +0100 Subject: [PATCH 10/13] Update polyfills - run on every call, and update e2e builds to include wp-build-polyfills --- ...date-connection-js-package-modal-to-dialog | 4 +++ .../src/class-wp-build-polyfills.php | 18 +++++++------ .../tests/php/WP_Build_Polyfills_Test.php | 25 +++++++++++++++++++ .../plugins/protect/tests/e2e/package.json | 2 +- .../plugins/search/tests/e2e/package.json | 2 +- 5 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 projects/packages/wp-build-polyfills/changelog/update-connection-js-package-modal-to-dialog diff --git a/projects/packages/wp-build-polyfills/changelog/update-connection-js-package-modal-to-dialog b/projects/packages/wp-build-polyfills/changelog/update-connection-js-package-modal-to-dialog new file mode 100644 index 000000000000..c0d675c75458 --- /dev/null +++ b/projects/packages/wp-build-polyfills/changelog/update-connection-js-package-modal-to-dialog @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Register requested polyfills for every consumer, even those that register after wp_default_scripts has fired. Previously the first such consumer locked the singleton, so a later consumer's handles or modules (e.g. the @wordpress/boot module) were silently dropped. diff --git a/projects/packages/wp-build-polyfills/src/class-wp-build-polyfills.php b/projects/packages/wp-build-polyfills/src/class-wp-build-polyfills.php index 75c0795b4626..90d4b85e0952 100644 --- a/projects/packages/wp-build-polyfills/src/class-wp-build-polyfills.php +++ b/projects/packages/wp-build-polyfills/src/class-wp-build-polyfills.php @@ -89,11 +89,6 @@ public static function register( $consumer, $polyfills, $wp_version_threshold = self::$wp_version_threshold = $wp_version_threshold; } - if ( self::$hooked ) { - return; - } - self::$hooked = true; - $package_root = dirname( __DIR__ ); $build_dir = $package_root . '/build'; $base_file = $package_root . '/composer.json'; @@ -101,16 +96,23 @@ public static function register( $consumer, $polyfills, $wp_version_threshold = // `wp_default_scripts` fires once when the WP_Scripts singleton is // instantiated. If something has already initialized `wp_scripts()` — // common on admin requests where WP or other plugins register scripts - // before `admin_menu` priority 1 runs — adding this hook here is too - // late and the polyfills never register. Detect that case and run the - // registration synchronously so consumers can rely on the script + // before `admin_menu` priority 1 runs — adding the hook below is too + // late and the polyfills would never register. Detect that case and run + // the registration synchronously so consumers can rely on the script // handles and module IDs being available regardless of init order. + // Runs on every call once fired, not just the first. if ( did_action( 'wp_default_scripts' ) ) { self::register_scripts( wp_scripts(), $build_dir, $base_file, self::$wp_version_threshold ); self::register_modules( $build_dir, $base_file ); return; } + // `wp_default_scripts` has not fired yet: hook once. + if ( self::$hooked ) { + return; + } + self::$hooked = true; + add_action( 'wp_default_scripts', function ( $scripts ) use ( $build_dir, $base_file ) { diff --git a/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php b/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php index a0b4b99a90fc..6414aa127360 100644 --- a/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php +++ b/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php @@ -595,6 +595,31 @@ public function test_register_hooks_into_wp_default_scripts() { $this->assertArrayHasKey( 20, $wp_filter['wp_default_scripts']->callbacks ); } + /** + * Regression test: a consumer that registers after `wp_default_scripts` has + * fired must still get its handles/modules registered, even when an earlier + * consumer already triggered the synchronous registration path. + */ + public function test_register_registers_late_consumer_modules_after_wp_default_scripts() { + // Mirror an admin request where scripts are touched before our consumers run. + wp_scripts(); + $this->assertTrue( (bool) did_action( 'wp_default_scripts' ), 'Precondition: wp_default_scripts must have fired.' ); + + // First consumer requests classic scripts only (mirrors the connection package). + WP_Build_Polyfills::register( 'consumer-scripts-only', array( 'wp-theme', 'wp-private-apis' ) ); + $this->assertFalse( + $this->is_module_registered( '@wordpress/boot' ), + '@wordpress/boot should not be registered until a consumer requests it.' + ); + + // Second consumer requests the boot module (mirrors the Social dashboard). + WP_Build_Polyfills::register( 'consumer-with-modules', array( '@wordpress/boot' ) ); + $this->assertTrue( + $this->is_module_registered( '@wordpress/boot' ), + '@wordpress/boot must register for a later consumer even after an earlier synchronous run.' + ); + } + /** * Test that get_consumers returns the correct consumer map. */ diff --git a/projects/plugins/protect/tests/e2e/package.json b/projects/plugins/protect/tests/e2e/package.json index 22eae8f0d0bc..173daa107e39 100644 --- a/projects/plugins/protect/tests/e2e/package.json +++ b/projects/plugins/protect/tests/e2e/package.json @@ -3,7 +3,7 @@ "type": "module", "scripts": { "allure-report": "allure generate --clean --output ./output/allure-report ./output/allure-results && allure open ./output/allure-report", - "build": "pnpm jetpack build js-packages/social-logos packages/assets packages/connection plugins/protect plugins/jetpack -v --no-pnpm-install --production", + "build": "pnpm jetpack build js-packages/social-logos packages/assets packages/connection packages/wp-build-polyfills plugins/protect plugins/jetpack -v --no-pnpm-install --production", "clean": "rm -rf output", "config:decrypt": "openssl enc -md sha1 -aes-256-cbc -pbkdf2 -iter 100000 -d -pass env:CONFIG_KEY -in ./node_modules/@automattic/_jetpack-e2e-commons/config/encrypted.enc -out ./config/local.cjs", "distclean": "rm -rf node_modules", diff --git a/projects/plugins/search/tests/e2e/package.json b/projects/plugins/search/tests/e2e/package.json index 7d7fc9ce3677..cffc5c71c45a 100644 --- a/projects/plugins/search/tests/e2e/package.json +++ b/projects/plugins/search/tests/e2e/package.json @@ -2,7 +2,7 @@ "private": true, "type": "module", "scripts": { - "build": "pnpm jetpack build js-packages/social-logos packages/assets packages/search packages/connection plugins/jetpack -v --no-pnpm-install --production", + "build": "pnpm jetpack build js-packages/social-logos packages/assets packages/search packages/connection packages/wp-build-polyfills plugins/jetpack -v --no-pnpm-install --production", "clean": "rm -rf output", "config:decrypt": "openssl enc -md sha1 -aes-256-cbc -pbkdf2 -iter 100000 -d -pass env:CONFIG_KEY -in ./node_modules/@automattic/_jetpack-e2e-commons/config/encrypted.enc -out ./config/local.cjs", "distclean": "rm -rf node_modules", From f9c973e2f8ecf32a49a794f14eba710f4ec89a59 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 16:31:04 +0100 Subject: [PATCH 11/13] Style tweak for manage connection dialog on specific screen sizes --- .../components/manage-connection-dialog/style.scss | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/projects/js-packages/connection/components/manage-connection-dialog/style.scss b/projects/js-packages/connection/components/manage-connection-dialog/style.scss index 6e2fddf0b34a..e5fe28907943 100644 --- a/projects/js-packages/connection/components/manage-connection-dialog/style.scss +++ b/projects/js-packages/connection/components/manage-connection-dialog/style.scss @@ -9,6 +9,10 @@ padding: 0; overflow-y: auto; + &#{&} { + max-width: 1200px; + } + &__content { background: var(--jp-white-off); padding: 80px; @@ -128,10 +132,3 @@ text-align: left; } } - -@media (min-width: 960px) { - - .jp-connection__manage-dialog { - width: 1200px; - } -} From 02967e4b4180ffc3db09339de87602e8e8ff735c Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Fri, 24 Jul 2026 17:01:53 +0100 Subject: [PATCH 12/13] Fix polyfill tests - create asset file, mirror hook already firing and WP_Scripts being instantiated, reset globals. --- .../tests/php/WP_Build_Polyfills_Test.php | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php b/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php index 6414aa127360..3a1e7db3f770 100644 --- a/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php +++ b/projects/packages/wp-build-polyfills/tests/php/WP_Build_Polyfills_Test.php @@ -67,6 +67,10 @@ public function set_up() { $this->original_wp_version = $GLOBALS['wp_version']; $this->original_wp_script_modules = $GLOBALS['wp_script_modules'] ?? null; + + // Ensure each test starts with a fresh WP_Scripts singleton. + unset( $GLOBALS['wp_scripts'], $GLOBALS['wp_actions']['wp_default_scripts'] ); + remove_all_actions( 'wp_default_scripts' ); } /** @@ -601,23 +605,53 @@ public function test_register_hooks_into_wp_default_scripts() { * consumer already triggered the synchronous registration path. */ public function test_register_registers_late_consumer_modules_after_wp_default_scripts() { - // Mirror an admin request where scripts are touched before our consumers run. - wp_scripts(); + // Mirror an admin request where scripts were already touched before our + // consumers run. Pre-seed the WP_Scripts singleton ourselves. + $GLOBALS['wp_scripts'] = $this->create_clean_scripts(); + $GLOBALS['wp_script_modules'] = new \WP_Script_Modules(); + $GLOBALS['wp_actions']['wp_default_scripts'] = 1; $this->assertTrue( (bool) did_action( 'wp_default_scripts' ), 'Precondition: wp_default_scripts must have fired.' ); - // First consumer requests classic scripts only (mirrors the connection package). - WP_Build_Polyfills::register( 'consumer-scripts-only', array( 'wp-theme', 'wp-private-apis' ) ); - $this->assertFalse( - $this->is_module_registered( '@wordpress/boot' ), - '@wordpress/boot should not be registered until a consumer requests it.' - ); + $modules_dir = dirname( __DIR__, 2 ) . '/build/modules/boot'; + $asset_file = $modules_dir . '/index.asset.php'; + $original = file_exists( $asset_file ) ? file_get_contents( $asset_file ) : null; - // Second consumer requests the boot module (mirrors the Social dashboard). - WP_Build_Polyfills::register( 'consumer-with-modules', array( '@wordpress/boot' ) ); - $this->assertTrue( - $this->is_module_registered( '@wordpress/boot' ), - '@wordpress/boot must register for a later consumer even after an earlier synchronous run.' + if ( ! is_dir( $modules_dir ) ) { + mkdir( $modules_dir, 0755, true ); + } + file_put_contents( + $asset_file, + ' array(), + 'version' => '9.9.9', + 'module_dependencies' => array(), + ), + true + ) . ";\n" ); + + try { + // First consumer requests classic scripts only. + WP_Build_Polyfills::register( 'consumer-scripts-only', array( 'wp-theme', 'wp-private-apis' ) ); + $this->assertFalse( + $this->is_module_registered( '@wordpress/boot' ), + '@wordpress/boot should not be registered until a consumer requests it.' + ); + + // Second consumer requests the boot module. + WP_Build_Polyfills::register( 'consumer-with-modules', array( '@wordpress/boot' ) ); + $this->assertTrue( + $this->is_module_registered( '@wordpress/boot' ), + '@wordpress/boot must register for a later consumer even after an earlier synchronous run.' + ); + } finally { + if ( null === $original ) { + unlink( $asset_file ); + } else { + file_put_contents( $asset_file, $original ); + } + } } /** From aef8c19836436c9db5ca9c0c28eb79196fa3f013 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Mon, 27 Jul 2026 17:15:34 +0100 Subject: [PATCH 13/13] Fix test failing due to conflicting changes between recently merged PR 50776 and this one --- .../components/disconnect-dialog/test/step-survey.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/step-survey.tsx b/projects/js-packages/connection/components/disconnect-dialog/test/step-survey.tsx index e0a7fb5d87bb..63a1c88158dc 100644 --- a/projects/js-packages/connection/components/disconnect-dialog/test/step-survey.tsx +++ b/projects/js-packages/connection/components/disconnect-dialog/test/step-survey.tsx @@ -1,7 +1,16 @@ import { jest } from '@jest/globals'; -import { render, screen } from '@testing-library/react'; +import { render as rtlRender, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Dialog } from '@wordpress/ui'; import StepSurvey from '../steps/step-survey'; +import type { ReactElement, ReactNode } from 'react'; + +// The step renders its heading as a `Dialog.Title`, which needs a Dialog +// context. +const DialogWrapper = ( { children }: { children: ReactNode } ) => ( + { children } +); +const render = ( ui: ReactElement ) => rtlRender( ui, { wrapper: DialogWrapper } ); describe( 'StepSurvey', () => { const testProps = {