diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d43e91e6031f..492de06abf1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3320,6 +3320,12 @@ importers: '@automattic/jetpack-shared-extension-utils': specifier: workspace:* version: link:../../js-packages/shared-extension-utils + '@tanstack/react-query': + specifier: 5.90.8 + version: 5.90.8(react@18.3.1) + '@wordpress/admin-ui': + specifier: 1.12.0 + version: 1.12.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/api-fetch': specifier: 7.44.0 version: 7.44.0 @@ -3338,6 +3344,9 @@ importers: '@wordpress/notices': specifier: 5.44.0 version: 5.44.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/route': + specifier: 0.10.0 + version: 0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/theme': specifier: 0.11.0 version: 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3386,7 +3395,7 @@ importers: version: 6.44.0 '@wordpress/build': specifier: 0.13.0 - version: 0.13.0(@babel/core@7.29.0)(@wordpress/theme@0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2) + version: 0.13.0(@babel/core@7.29.0)(@wordpress/route@0.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2) browserslist: specifier: ^4.24.0 version: 4.28.2 @@ -7277,19 +7286,6 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.4.0': - resolution: {integrity: sha512-QcqdVbr/+ba2/RAKJIV1PV6S02Q5+r6a4Eym8ndBw+ZbBILkkmQAyRxXCg/pArrHnkrGeU8goe26aw0h6eE8pg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@date-fns/tz': ^1.2.0 - '@types/react': ^17 || ^18 || ^19 - date-fns: ^4.0.0 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - '@base-ui/react@1.4.1': resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==} engines: {node: '>=14.0.0'} @@ -7307,16 +7303,6 @@ packages: date-fns: optional: true - '@base-ui/utils@0.2.7': - resolution: {integrity: sha512-nXYKhiL/0JafyJE8PfcflipGftOftlIwKd72rU15iZ1M5yqgg5J9P8NHU71GReDuXco5MJA/eVQqUT5WRqX9sA==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - '@base-ui/utils@0.2.8': resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==} peerDependencies: @@ -18407,7 +18393,7 @@ snapshots: '@wordpress/primitives': 4.44.0(react@18.3.1) '@wordpress/react-i18n': 4.43.0 '@wordpress/url': 4.44.0 - '@wordpress/warning': 3.44.0 + '@wordpress/warning': 3.45.0 canvas-confetti: 1.9.4 clsx: 2.1.1 colord: 2.9.3 @@ -18446,7 +18432,7 @@ snapshots: '@wordpress/api-fetch': 7.44.0 '@wordpress/data': 10.44.0(react@18.3.1) '@wordpress/data-controls': 4.44.0(react@18.3.1) - '@wordpress/deprecated': 4.44.0 + '@wordpress/deprecated': 4.45.0 '@wordpress/element': 6.44.0 '@wordpress/i18n': 6.17.0 '@wordpress/primitives': 4.44.0(react@18.3.1) @@ -19426,34 +19412,6 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.4.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@base-ui/utils': 0.2.7(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@date-fns/tz': 1.4.1 - '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@floating-ui/utils': 0.2.11 - date-fns: 4.1.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.6.0(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - optional: true - - '@base-ui/react@1.4.0(@date-fns/tz@1.4.1)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@base-ui/utils': 0.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@date-fns/tz': 1.4.1 - '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@floating-ui/utils': 0.2.11 - date-fns: 4.1.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.6.0(react@18.3.1) - optional: true - '@base-ui/react@1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.29.2 @@ -19481,28 +19439,6 @@ snapshots: '@date-fns/tz': 1.4.1 date-fns: 4.1.0 - '@base-ui/utils@0.2.7(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@floating-ui/utils': 0.2.11 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - reselect: 5.1.1 - use-sync-external-store: 1.6.0(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.28 - optional: true - - '@base-ui/utils@0.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@babel/runtime': 7.29.2 - '@floating-ui/utils': 0.2.11 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - reselect: 5.1.1 - use-sync-external-store: 1.6.0(react@18.3.1) - optional: true - '@base-ui/utils@0.2.8(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.29.2 @@ -24107,30 +24043,6 @@ snapshots: - browserslist - supports-color - '@wordpress/build@0.13.0(@babel/core@7.29.0)(@wordpress/theme@0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)': - dependencies: - '@emotion/babel-plugin': 11.13.5 - autoprefixer: 10.4.27(postcss@8.5.10) - browserslist-to-esbuild: 2.1.1(browserslist@4.28.2) - change-case: 4.1.2 - chokidar: 4.0.3 - cssnano: 7.1.4(postcss@8.5.10) - esbuild: 0.27.4 - esbuild-plugin-babel: 0.2.3(@babel/core@7.29.0) - esbuild-sass-plugin: 3.3.1(esbuild@0.27.4)(sass-embedded@1.97.3) - fast-glob: 3.3.3 - moment-timezone: 0.5.48 - postcss: 8.5.10 - postcss-modules: 6.0.1(postcss@8.5.10) - rtlcss: 4.3.0 - sass-embedded: 1.97.3 - optionalDependencies: - '@wordpress/theme': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - transitivePeerDependencies: - - '@babel/core' - - browserslist - - supports-color - '@wordpress/build@0.13.0(@babel/core@7.29.0)(browserslist@4.28.2)': dependencies: '@emotion/babel-plugin': 11.13.5 @@ -24251,7 +24163,7 @@ snapshots: '@wordpress/primitives': 4.44.0(react@18.3.1) '@wordpress/private-apis': 1.44.0 '@wordpress/rich-text': 7.44.0(react@18.3.1) - '@wordpress/warning': 3.44.0 + '@wordpress/warning': 3.45.0 change-case: 4.1.2 clsx: 2.1.1 colord: 2.9.3 @@ -24435,7 +24347,7 @@ snapshots: '@wordpress/components': 32.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/compose': 7.44.0(react@18.3.1) '@wordpress/data': 10.44.0(react@18.3.1) - '@wordpress/deprecated': 4.44.0 + '@wordpress/deprecated': 4.45.0 '@wordpress/element': 6.44.0 '@wordpress/i18n': 6.17.0 '@wordpress/icons': 12.2.0(react@18.3.1) @@ -24443,12 +24355,12 @@ snapshots: '@wordpress/primitives': 4.44.0(react@18.3.1) '@wordpress/private-apis': 1.44.0 '@wordpress/ui': 0.11.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/warning': 3.44.0 + '@wordpress/warning': 3.45.0 clsx: 2.1.1 react: 18.3.1 remove-accents: 0.5.0 optionalDependencies: - '@base-ui/react': 1.4.0(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(@types/react@18.3.28)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@emotion/cache': 11.14.0 '@emotion/css': 11.13.5 '@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1) @@ -24486,7 +24398,7 @@ snapshots: '@wordpress/components': 32.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/compose': 7.44.0(react@18.3.1) '@wordpress/data': 10.44.0(react@18.3.1) - '@wordpress/deprecated': 4.44.0 + '@wordpress/deprecated': 4.45.0 '@wordpress/element': 6.44.0 '@wordpress/i18n': 6.17.0 '@wordpress/icons': 12.2.0(react@18.3.1) @@ -24494,12 +24406,12 @@ snapshots: '@wordpress/primitives': 4.44.0(react@18.3.1) '@wordpress/private-apis': 1.44.0 '@wordpress/ui': 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/warning': 3.44.0 + '@wordpress/warning': 3.45.0 clsx: 2.1.1 react: 18.3.1 remove-accents: 0.5.0 optionalDependencies: - '@base-ui/react': 1.4.0(@date-fns/tz@1.4.1)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@base-ui/react': 1.4.1(@date-fns/tz@1.4.1)(date-fns@4.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@emotion/cache': 11.14.0 '@emotion/css': 11.13.5 '@emotion/react': 11.14.0(react@18.3.1) diff --git a/projects/packages/newsletter/_inc/subscribers/lib/query-client.ts b/projects/packages/newsletter/_inc/subscribers/lib/query-client.ts new file mode 100644 index 000000000000..8dd7c0c47ac3 --- /dev/null +++ b/projects/packages/newsletter/_inc/subscribers/lib/query-client.ts @@ -0,0 +1,10 @@ +import { QueryClient } from '@tanstack/react-query'; + +export const queryClient = new QueryClient( { + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + staleTime: 30 * 1000, + }, + }, +} ); diff --git a/projects/packages/newsletter/changelog/add-wp-build-page-shell b/projects/packages/newsletter/changelog/add-wp-build-page-shell new file mode 100644 index 000000000000..1df2de424cc2 --- /dev/null +++ b/projects/packages/newsletter/changelog/add-wp-build-page-shell @@ -0,0 +1,3 @@ +Significance: patch +Type: added +Comment: Newsletter modernization chassis (Page chrome, React Query, analytics init) behind the same feature flag; no user-visible change unless the flag is enabled. diff --git a/projects/packages/newsletter/package.json b/projects/packages/newsletter/package.json index 56ef7fa585b9..70b4b69b69b7 100644 --- a/projects/packages/newsletter/package.json +++ b/projects/packages/newsletter/package.json @@ -42,12 +42,15 @@ "@automattic/jetpack-connection": "workspace:*", "@automattic/jetpack-script-data": "workspace:*", "@automattic/jetpack-shared-extension-utils": "workspace:*", + "@tanstack/react-query": "5.90.8", + "@wordpress/admin-ui": "1.12.0", "@wordpress/api-fetch": "7.44.0", "@wordpress/components": "32.6.0", "@wordpress/dataviews": "14.1.0", "@wordpress/element": "6.44.0", "@wordpress/i18n": "6.17.0", "@wordpress/notices": "5.44.0", + "@wordpress/route": "0.10.0", "@wordpress/theme": "0.11.0", "@wordpress/ui": "0.11.0", "@wordpress/url": "4.44.0", diff --git a/projects/packages/newsletter/routes/dashboard/inspector.tsx b/projects/packages/newsletter/routes/dashboard/inspector.tsx new file mode 100644 index 000000000000..fd80ddddca3a --- /dev/null +++ b/projects/packages/newsletter/routes/dashboard/inspector.tsx @@ -0,0 +1,9 @@ +import { Page } from '@wordpress/admin-ui'; + +const Inspector = () => { + // Subscriber detail content lands in a follow-up PR; the empty Page keeps + // the inspector slot stable so the route hook can target it. + return ; +}; + +export { Inspector as inspector }; diff --git a/projects/packages/newsletter/routes/dashboard/package.json b/projects/packages/newsletter/routes/dashboard/package.json index 0bb749683cba..c625c06ebbe7 100644 --- a/projects/packages/newsletter/routes/dashboard/package.json +++ b/projects/packages/newsletter/routes/dashboard/package.json @@ -3,9 +3,14 @@ "version": "1.0.0", "private": true, "dependencies": { + "@automattic/jetpack-analytics": "workspace:*", + "@automattic/jetpack-script-data": "workspace:*", + "@tanstack/react-query": "5.90.8", "@types/react": "18.3.28", + "@wordpress/admin-ui": "1.12.0", "@wordpress/element": "6.44.0", - "@wordpress/i18n": "6.17.0" + "@wordpress/i18n": "6.17.0", + "@wordpress/route": "0.10.0" }, "route": { "path": "/", diff --git a/projects/packages/newsletter/routes/dashboard/route.tsx b/projects/packages/newsletter/routes/dashboard/route.tsx index 91499ada4b32..2df246d58d89 100644 --- a/projects/packages/newsletter/routes/dashboard/route.tsx +++ b/projects/packages/newsletter/routes/dashboard/route.tsx @@ -1 +1,19 @@ -export const route = {}; +type SubscribersSearch = { + subscriber?: string | number; + u?: string | number; +}; + +export const route = { + /** + * Show the inspector slot only when a subscriber is selected via URL params. + * Boot's router calls this on every navigation and uses the boolean to + * decide whether to render the `` export. + * + * @param ctx - Route loader context. + * @param ctx.search - URL search-param record. + * @return Whether to render the inspector slot. + */ + inspector: ( { search }: { search: SubscribersSearch } ) => { + return Boolean( search?.subscriber || search?.u ); + }, +}; diff --git a/projects/packages/newsletter/routes/dashboard/stage.tsx b/projects/packages/newsletter/routes/dashboard/stage.tsx index 62a7a36d75c7..2eebdc811419 100644 --- a/projects/packages/newsletter/routes/dashboard/stage.tsx +++ b/projects/packages/newsletter/routes/dashboard/stage.tsx @@ -1,6 +1,27 @@ +import analytics from '@automattic/jetpack-analytics'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { Page } from '@wordpress/admin-ui'; +import { useEffect } from '@wordpress/element'; +import { queryClient } from '../../_inc/subscribers/lib/query-client'; +import { getNewsletterScriptData } from '../../src/settings/script-data'; + const Stage = () => { - // "Newsletter" is a product name, do not translate. - return

Newsletter

; + // Initialize analytics once for the entire page so future tab/section + // events fire regardless of which tab a visitor lands on. Mirrors the + // initialization that lived in the legacy `NewsletterSettingsApp`. + useEffect( () => { + const tracksUserData = getNewsletterScriptData()?.tracksUserData; + if ( tracksUserData && typeof tracksUserData === 'object' ) { + analytics.initialize( tracksUserData.userid, tracksUserData.username ); + } + }, [] ); + + return ( + + { /* "Newsletter" is a product name, do not translate. */ } + + + ); }; export { Stage as stage }; diff --git a/projects/packages/wp-build-polyfills/changelog/fix-late-register b/projects/packages/wp-build-polyfills/changelog/fix-late-register new file mode 100644 index 000000000000..10a1fd91afa8 --- /dev/null +++ b/projects/packages/wp-build-polyfills/changelog/fix-late-register @@ -0,0 +1,3 @@ +Significance: patch +Type: fixed +Comment: Internal — register polyfills synchronously when wp_default_scripts has already fired, so wp-build admin pages render reliably regardless of which plugin first instantiated wp_scripts(). 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 8d0b70620db2..3c41eaeacc28 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 @@ -92,6 +92,19 @@ public static function register( $consumer, $polyfills, $wp_version_threshold = $build_dir = $package_root . '/build'; $base_file = $package_root . '/composer.json'; + // `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 + // handles and module IDs being available regardless of init order. + 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; + } + add_action( 'wp_default_scripts', function ( $scripts ) use ( $build_dir, $base_file ) {