Background
Three product-modernization umbrellas now scaffold a wp-build dashboard on top of @wordpress/admin-ui Page + Tabs from @wordpress/ui:
Each one ships near-identical glue code:
- PHP chassis helpers —
is_modernized(), is_<product>_admin_request(), load_wp_build() (require build/build.php + register WP_Build_Polyfills), alias_screen_id_for_wp_build(), plus the admin_menu priority-1 hook that swaps the menu callback. ~80 lines per product, copy-pasted with the slug/filter name swapped.
- Page-shell SCSS — sticky tab row, body-slot scroll model, header border override, content padding. Identical between
boost-page.scss and social-page.scss aside from the body-class selector and inset token.
<ProductPage> React shell — Page + Tabs.Root + JetpackFooter, with the product name, subtitles, and class prefix being the only product-specific bits.
- Route boilerplate —
routes/dashboard/route.tsx is literally export const route = {}; in every product; routes/dashboard/package.json is the same dependency set with the page slug swapped.
Proposed extractions
automattic/jetpack-wp-build-chassis (new package, PHP)
\\Automattic\\Jetpack\\WP_Build_Chassis\\Setup::register( array(
'filter' => 'rsm_jetpack_ui_modernization_social',
'menu_slug' => 'jetpack-social',
'page_slug' => 'jetpack-social-dashboard',
'handle' => 'jetpack-social',
'build_dir' => __DIR__ . '/../build',
) );
Encapsulates the priority-1 hook, filter check, polyfill registration, screen-ID alias, and exposes the menu-callback name for the consumer to branch on. Standardizes the integration so polyfill registration can't be forgotten on a future product.
Shared page-shell CSS
Two options:
- Inside
@wordpress/admin-ui — the Page component grows a tabbedShell / appShell prop that paints the scroll model directly. Right long-term home, requires upstream review.
@automattic/jetpack-base-styles — ship a SCSS partial (e.g. _product-page-shell.scss) scoped to a wrapper class the Stage applies. Lighter lift, but the dedicated upstream home is cleaner.
<JetpackProductPage> React component
Lives in @automattic/jetpack-components. Props:
type Props = {
productName: string; // \"Boost\", \"Social\", \"Newsletter\"
subtitles: Record< string, () => string >;
activeTab: string;
tabs: Array< { value: string; label: string } >;
actions?: ReactNode;
children: ReactNode;
};
Encapsulates the Page + Tabs.Root + JetpackFooter + the URL ?tab= ↔ tab-value binding. Each consumer reduces from ~90 lines to ~30.
Route-boilerplate codegen
routes/dashboard/route.tsx and routes/dashboard/package.json could be synthesized by wp-build from a single config block in the package's top-level package.json — out of our control here, but worth raising with the wp-build team.
Why not now
- The umbrellas explicitly mirror Boost and VideoPress cadence. Diverging mid-flight means re-architecting un-merged work in three places at once.
- The right moment is once 3 products have shipped through the pattern — after Social PR 5 and Newsletter completion, we'll have proven the shape and the abstraction won't be premature.
- Cross-cutting refactors right now would block five PRs (Social PRs 2–5 + Boost PR 5) on review.
Suggested trigger
Open this after all four of the following have landed in trunk:
- Boost umbrella (#48717) — including PR 5 (legacy retire).
- Newsletter umbrella (#48530) — including the final flip.
- VideoPress scaffold flow (#48494) — including any tab work that comes after.
- Social umbrella (#48824) — including PR 5.
At that point we have 4 in-tree consumers and the abstraction has clear contours.
References
- Boost PR 1 chassis PHP:
projects/plugins/boost/app/admin/class-admin.php on the update/boost-modernization-scaffold branch.
- Boost PR 1 page shell:
projects/plugins/boost/_inc/components/boost-page.{tsx,scss}.
- Social PR 1 (#48826) — equivalent files under
projects/packages/publicize/.
Background
Three product-modernization umbrellas now scaffold a wp-build dashboard on top of
@wordpress/admin-uiPage+Tabsfrom@wordpress/ui:87c6f53a65.Each one ships near-identical glue code:
is_modernized(),is_<product>_admin_request(),load_wp_build()(requirebuild/build.php+ registerWP_Build_Polyfills),alias_screen_id_for_wp_build(), plus theadmin_menupriority-1 hook that swaps the menu callback. ~80 lines per product, copy-pasted with the slug/filter name swapped.boost-page.scssandsocial-page.scssaside from the body-class selector and inset token.<ProductPage>React shell —Page+Tabs.Root+JetpackFooter, with the product name, subtitles, and class prefix being the only product-specific bits.routes/dashboard/route.tsxis literallyexport const route = {};in every product;routes/dashboard/package.jsonis the same dependency set with the page slug swapped.Proposed extractions
automattic/jetpack-wp-build-chassis(new package, PHP)Encapsulates the priority-1 hook, filter check, polyfill registration, screen-ID alias, and exposes the menu-callback name for the consumer to branch on. Standardizes the integration so polyfill registration can't be forgotten on a future product.
Shared page-shell CSS
Two options:
@wordpress/admin-ui— thePagecomponent grows atabbedShell/appShellprop that paints the scroll model directly. Right long-term home, requires upstream review.@automattic/jetpack-base-styles— ship a SCSS partial (e.g._product-page-shell.scss) scoped to a wrapper class the Stage applies. Lighter lift, but the dedicated upstream home is cleaner.<JetpackProductPage>React componentLives in
@automattic/jetpack-components. Props:Encapsulates the
Page+Tabs.Root+JetpackFooter+ the URL?tab=↔ tab-value binding. Each consumer reduces from ~90 lines to ~30.Route-boilerplate codegen
routes/dashboard/route.tsxandroutes/dashboard/package.jsoncould be synthesized bywp-buildfrom a single config block in the package's top-levelpackage.json— out of our control here, but worth raising with the wp-build team.Why not now
Suggested trigger
Open this after all four of the following have landed in
trunk:At that point we have 4 in-tree consumers and the abstraction has clear contours.
References
projects/plugins/boost/app/admin/class-admin.phpon theupdate/boost-modernization-scaffoldbranch.projects/plugins/boost/_inc/components/boost-page.{tsx,scss}.projects/packages/publicize/.