diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bd08d9459f7..041a666d724 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3616,9 +3616,9 @@ importers:
projects/packages/podcast:
dependencies:
- '@wordpress/components':
- specifier: 32.6.0
- version: 32.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@automattic/jetpack-components':
+ specifier: workspace:*
+ version: link:../../js-packages/components
'@wordpress/element':
specifier: 6.44.0
version: 6.44.0
@@ -3629,6 +3629,9 @@ importers:
specifier: 0.11.0
version: 0.11.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
devDependencies:
+ '@automattic/jetpack-base-styles':
+ specifier: workspace:*
+ version: link:../../js-packages/base-styles
'@automattic/jetpack-wp-build-polyfills':
specifier: workspace:*
version: link:../wp-build-polyfills
@@ -3641,12 +3644,18 @@ importers:
'@types/react':
specifier: 18.3.28
version: 18.3.28
+ '@wordpress/base-styles':
+ specifier: 6.20.0
+ version: 6.20.0
'@wordpress/browserslist-config':
specifier: 6.44.0
version: 6.44.0
'@wordpress/build':
specifier: 0.13.0
- version: 0.13.0(@babel/core@7.29.0)(browserslist@4.28.2)
+ 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)
+ '@wordpress/theme':
+ specifier: 0.11.0
+ version: 0.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
browserslist:
specifier: ^4.24.0
version: 4.28.2
@@ -24143,6 +24152,30 @@ 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
diff --git a/projects/packages/jetpack-mu-wpcom/changelog/arcangelini-podcast-make-it-better b/projects/packages/jetpack-mu-wpcom/changelog/arcangelini-podcast-make-it-better
new file mode 100644
index 00000000000..c8022d7ce2c
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/changelog/arcangelini-podcast-make-it-better
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Internal: rename the new Podcast slug to 'podcast' in the Jetpack submenu reorder list; legacy 'podcasting' entry untouched.
+
+
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-menu/wpcom-admin-menu.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-menu/wpcom-admin-menu.php
index 38aad8eab7d..3d710a04c0f 100644
--- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-menu/wpcom-admin-menu.php
+++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-menu/wpcom-admin-menu.php
@@ -466,7 +466,7 @@ function () {
'search',
'subscribers',
'newsletter',
- 'jetpack-podcast',
+ 'podcast',
'podcasting',
'traffic',
'jetpack#/settings',
diff --git a/projects/packages/podcast/changelog/arcangelini-podcast-make-it-better b/projects/packages/podcast/changelog/arcangelini-podcast-make-it-better
new file mode 100644
index 00000000000..d23e3f2f3e5
--- /dev/null
+++ b/projects/packages/podcast/changelog/arcangelini-podcast-make-it-better
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Slim down wp-build wiring to the Backup pattern: drop bridge_wp_build_enqueue and fix_boot_import_map_ordering, alias $screen->id via current_screen instead.
diff --git a/projects/packages/podcast/package.json b/projects/packages/podcast/package.json
index f3eeedc12ad..1beb9bfdac8 100644
--- a/projects/packages/podcast/package.json
+++ b/projects/packages/podcast/package.json
@@ -27,18 +27,21 @@
"extends @wordpress/browserslist-config"
],
"dependencies": {
- "@wordpress/components": "32.6.0",
+ "@automattic/jetpack-components": "workspace:*",
"@wordpress/element": "6.44.0",
"@wordpress/i18n": "6.17.0",
"@wordpress/ui": "0.11.0"
},
"devDependencies": {
+ "@automattic/jetpack-base-styles": "workspace:*",
"@automattic/jetpack-wp-build-polyfills": "workspace:*",
"@babel/core": "7.29.0",
"@babel/runtime": "7.29.2",
"@types/react": "18.3.28",
+ "@wordpress/base-styles": "6.20.0",
"@wordpress/browserslist-config": "6.44.0",
"@wordpress/build": "0.13.0",
+ "@wordpress/theme": "0.11.0",
"browserslist": "^4.24.0"
},
"optionalDependencies": {
diff --git a/projects/packages/podcast/routes/dashboard/package.json b/projects/packages/podcast/routes/dashboard/package.json
index f130ca6154a..54f202cc4e7 100644
--- a/projects/packages/podcast/routes/dashboard/package.json
+++ b/projects/packages/podcast/routes/dashboard/package.json
@@ -3,8 +3,8 @@
"version": "1.0.0",
"private": true,
"dependencies": {
+ "@automattic/jetpack-components": "workspace:*",
"@types/react": "18.3.28",
- "@wordpress/components": "32.6.0",
"@wordpress/element": "6.44.0",
"@wordpress/i18n": "6.17.0",
"@wordpress/ui": "0.11.0"
diff --git a/projects/packages/podcast/routes/dashboard/route.scss b/projects/packages/podcast/routes/dashboard/route.scss
new file mode 100644
index 00000000000..668d953f4d6
--- /dev/null
+++ b/projects/packages/podcast/routes/dashboard/route.scss
@@ -0,0 +1,6 @@
+@use "@automattic/jetpack-base-styles/admin-page-layout" as *;
+
+body.jetpack_page_jetpack-podcast {
+
+ @include jetpack-admin-page-layout;
+}
diff --git a/projects/packages/podcast/routes/dashboard/stage.tsx b/projects/packages/podcast/routes/dashboard/stage.tsx
index 92f5ceebfb9..f87f7df247a 100644
--- a/projects/packages/podcast/routes/dashboard/stage.tsx
+++ b/projects/packages/podcast/routes/dashboard/stage.tsx
@@ -1,14 +1,8 @@
-/**
- * Podcast dashboard stage: page chrome + tab navigation.
- *
- * Placeholder scaffolding only — each tab panel renders a stub. PR 4 in the
- * untangle train wires the full AdminPage + jetpack-components integration
- * along with the real tab contents.
- */
-
+import AdminPage from '@automattic/jetpack-components/admin-page';
import { useState, useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Tabs } from '@wordpress/ui';
+import './route.scss';
const TAB_VALUES = [ 'settings', 'episodes', 'distribution', 'stats' ] as const;
type TabName = ( typeof TAB_VALUES )[ number ];
@@ -26,19 +20,25 @@ const Stage = () => {
}, [] );
return (
-
-
Podcast
-
- { __( 'Publish a podcast and reach your fans, anywhere they listen.', 'jetpack-podcast' ) }
-
-
-
-
- { __( 'Settings', 'jetpack-podcast' ) }
- { __( 'Episodes', 'jetpack-podcast' ) }
- { __( 'Distribution', 'jetpack-podcast' ) }
- { __( 'Stats', 'jetpack-podcast' ) }
-
+
+
+
+ { __( 'Settings', 'jetpack-podcast' ) }
+ { __( 'Episodes', 'jetpack-podcast' ) }
+ { __( 'Distribution', 'jetpack-podcast' ) }
+ { __( 'Stats', 'jetpack-podcast' ) }
+
+
+ }
+ >
{ __( 'Settings — placeholder.', 'jetpack-podcast' ) }
@@ -51,8 +51,8 @@ const Stage = () => {
{ __( 'Stats — placeholder.', 'jetpack-podcast' ) }
-
-
+
+
);
};
diff --git a/projects/packages/podcast/src/class-admin-page.php b/projects/packages/podcast/src/class-admin-page.php
index 4aa92d26859..d924b2f8f19 100644
--- a/projects/packages/podcast/src/class-admin-page.php
+++ b/projects/packages/podcast/src/class-admin-page.php
@@ -14,53 +14,27 @@
* `jetpack_podcast_untangle` filter is enabled. Until that filter flips, every
* entry point here is a no-op so the legacy podcasting experience keeps
* running unchanged.
- *
- * Menu registration is owned by `wpcom-admin-menu.php` (in the
- * `jetpack-mu-wpcom` package), which calls `add_wp_admin_submenu()` at
- * `admin_menu` priority 999999 — late enough that the Jetpack parent menu
- * already exists. wpcom-admin-menu runs on both Simple and Atomic, so a single
- * registration path covers both. Standalone Jetpack is excluded by the host
- * gate in `Podcast::init()`.
- *
- * The wp-build chassis is loaded inline from `init()` and routed onto our
- * user-facing slug via `bridge_wp_build_enqueue()` — mirroring
- * `Automattic\Jetpack\Scan_Page\Jetpack_Scan`.
*/
class Admin_Page {
- /**
- * URL-facing menu slug.
- *
- * @var string
- */
const ADMIN_PAGE_SLUG = 'jetpack-podcast';
/**
- * Internal slug emitted by `@wordpress/build` (`wpPlugin.pages[0]`
- * plus the `-wp-admin` suffix the build template appends). Used to
- * find the auto-generated render / enqueue functions.
- *
- * @var string
+ * Slug emitted by `@wordpress/build`. wp-build's auto-generated enqueue
+ * callback only fires when `$screen->id` matches this value, so we alias
+ * the screen id via `current_screen` without changing the user-facing URL.
*/
- const WP_BUILD_SLUG = 'jetpack-podcast-dashboard-wp-admin';
+ const WP_BUILD_SLUG = 'jetpack-podcast-dashboard';
/**
- * Whether the class has already wired its admin hooks.
+ * Whether `init()` has already wired its hooks.
*
* @var bool
*/
private static $initialized = false;
/**
- * Wire the admin hooks. Called from `Podcast::init()` once the
- * `jetpack_podcast_untangle` filter and host gates have been satisfied.
- *
- * Menu registration itself is handled by `wpcom-admin-menu.php` calling
- * `add_wp_admin_submenu()` at `admin_menu` priority 999999. Here we set
- * up the wp-build chassis at plugins_loaded time so:
- * - `WP_Build_Polyfills::register()` registers BEFORE `wp_default_scripts`
- * fires (otherwise `@wordpress/boot` never lands in the import map).
- * - The wp-build render function is defined before the menu callback runs.
+ * Wire admin hooks. Idempotent.
*/
public static function init() {
if ( self::$initialized ) {
@@ -68,23 +42,25 @@ public static function init() {
}
self::$initialized = true;
- self::load_wp_build();
- self::bridge_wp_build_enqueue();
- self::fix_boot_import_map_ordering();
+ add_action( 'admin_menu', array( __CLASS__, 'maybe_load_wp_build' ), 1 );
}
/**
* Register the Podcast submenu under Jetpack on Simple and Atomic.
*
* Called from `wpcom-admin-menu.php` at priority 999999 once the Jetpack
- * parent menu exists. Bails when the untangle filter is off so the legacy
- * "Podcasting" Calypso link in `wpcom-admin-menu.php` keeps rendering.
+ * parent menu exists.
*/
public static function add_wp_admin_submenu() {
if ( ! self::is_enabled() ) {
return;
}
+ $wp_build_render = 'jetpack_podcast_jetpack_podcast_dashboard_wp_admin_render_page';
+ $callback = function_exists( $wp_build_render )
+ ? $wp_build_render
+ : array( __CLASS__, 'render' );
+
$page_suffix = add_submenu_page(
'jetpack',
/** "Podcast" is a product name, do not translate. */
@@ -92,7 +68,7 @@ public static function add_wp_admin_submenu() {
'Podcast',
'manage_options',
self::ADMIN_PAGE_SLUG,
- self::get_render_callback()
+ $callback
);
if ( $page_suffix ) {
@@ -102,134 +78,60 @@ public static function add_wp_admin_submenu() {
/**
* Wire admin-init actions once we know the Podcast page is loading.
- *
- * Subsequent PRs in the untangle train layer script-data + Tracks here.
- * The wp-build dashboard manages its own enqueue pipeline (bridged via
- * `bridge_wp_build_enqueue()`).
*/
public static function admin_init() {
// Intentionally empty for now.
}
/**
- * Bridge wp-build's auto-generated enqueue function — which checks for
- * `?page=jetpack-podcast-dashboard-wp-admin` — to our user-facing slug
- * `?page=jetpack-podcast`. Hooked at priority 9 so the wp-build copy
- * (registered at priority 10) sees the original `$_GET['page']` and skips
- * its own enqueue.
- *
- * Mirrors `Automattic\Jetpack\Scan_Page\Jetpack_Scan::bridge_wp_build_enqueue`.
+ * Hooked at admin_menu priority 1 so polyfills register before
+ * `wp_default_scripts` fires and the wp-build render function is defined
+ * before `add_wp_admin_submenu()` runs at priority 999999.
*/
- private static function bridge_wp_build_enqueue() {
- add_action(
- 'admin_enqueue_scripts',
- static function ( $hook_suffix ) {
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- if ( ! isset( $_GET['page'] ) || self::ADMIN_PAGE_SLUG !== $_GET['page'] ) {
- return;
- }
-
- $enqueue_fn = 'jetpack_podcast_jetpack_podcast_dashboard_wp_admin_enqueue_scripts';
- if ( ! function_exists( $enqueue_fn ) ) {
- return;
- }
+ public static function maybe_load_wp_build() {
+ if ( ! self::is_enabled() || ! self::is_podcast_admin_request() ) {
+ return;
+ }
- // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
- $original = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : null;
- $_GET['page'] = self::WP_BUILD_SLUG;
- // @phan-suppress-next-line PhanUndeclaredFunctionInCallable -- Function is generated by @wordpress/build into build/pages/jetpack-podcast-dashboard/page-wp-admin.php, which is outside Phan's analysis scope. The function_exists() guard above protects the call at runtime.
- call_user_func( $enqueue_fn, $hook_suffix );
- if ( null === $original ) {
- unset( $_GET['page'] );
- } else {
- $_GET['page'] = $original;
- }
- // phpcs:enable WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
- },
- 9
- );
+ self::load_wp_build();
+ add_action( 'current_screen', array( __CLASS__, 'alias_screen_id_for_wp_build' ) );
}
/**
- * Fix import map ordering for the wp-build boot script.
- *
- * In wp-admin, `_wp_footer_scripts` (classic scripts) and
- * `print_import_map` both hook into `admin_print_footer_scripts` at
- * priority 10, but `_wp_footer_scripts` is registered first. This causes
- * the inline `import("@wordpress/boot")` to execute before the import
- * map exists.
- *
- * This fix moves the `import()` call from the classic inline script to a
- * `