Skip to content

Normalize admin page headers with unified AdminHeader wrapping @wordpress/admin-ui#47313

Merged
CGastrell merged 42 commits intotrunkfrom
update/normalize-admin-page-headers
Mar 2, 2026
Merged

Normalize admin page headers with unified AdminHeader wrapping @wordpress/admin-ui#47313
CGastrell merged 42 commits intotrunkfrom
update/normalize-admin-page-headers

Conversation

@CGastrell
Copy link
Copy Markdown
Contributor

@CGastrell CGastrell commented Feb 25, 2026

Part of JETPACK-1353

Somewhat related to JETPACK-1360 as this centralizes the use of Page through AdminHeader Jetpack component.

Summary

Introduces a unified AdminHeader component in @automattic/jetpack-components that wraps @wordpress/admin-ui's Page component, providing a consistent header across all Jetpack admin pages. Migrates publicize/Social as the first proof of concept.

What this PR does

  • Adds AdminHeader component to jetpack-components — a thin wrapper around @wordpress/admin-ui Page that composes a logo + title into the header. Defaults to the Jetpack bolt icon when no custom logo is provided.
  • Extends AdminPage props with title, subTitle, logo, actions, tabs, breadcrumbs, badges. When title is present, renders the new AdminHeader; otherwise falls back to the legacy header prop path. Fully backward compatible.
  • Migrates publicize admin page to use title="Social" instead of the custom AdminPageHeader component. Deletes the now-unused page-header/ directory (inline SVG logo, styles, component, test).

Social

Before After
image image
image image
image image

VideoPress

Before After
image image
image image

Backup

Before After
image image

Search

Before After
image image
image image

Long-term plan

This is a step in the p1HpG7-xCB-p2 effort (i1 designs by @keoshi and @sanjagrbic). Figma: fBCpFRsWEkIP0rx9AXNIJt-fi-4580_27730

Architecture:

AdminPage (jetpack-components)
  └── AdminHeader (jetpack-components) — thin wrapper, adds logo default
        └── Page (@wordpress/admin-ui) — upstream Gutenberg component

AdminHeader adds only two things that @wordpress/admin-ui Page doesn't have:

  1. A default JetpackLogo bolt icon when no logo prop is provided
  2. Logo composed alongside the title text in an HStack

Migration pattern for each remaining product:

  1. Pass title="ProductName" to AdminPage (optionally subTitle, actions, tabs)
  2. Delete the old custom header component
  3. Add a changelog entry

Products to migrate next (~13 remaining):

Product Current approach Migration complexity
backup Custom <Header> with logo SVG Simple — swap to title prop
videopress header={<JetpackVideoPressLogo/>} Simple
search (upsell) Custom <Header> with logo Simple
search (dashboard) Inline header Simple
newsletter Custom <Header> with logo + title Simple
my-jetpack Default <JetpackLogo/> fallback Simple
protect header={<JetpackProtectLogo/>} Simple
classic-theme-helper Default fallback Simple
starter-plugin Default fallback Simple
boost Fully custom Header Medium — custom layout
jetpack (main plugin) Custom Masthead class component Medium — class component
crm PHP-rendered header Medium — PHP/JS boundary
forms Local admin-ui copy Special — already uses admin-ui pattern, needs to switch to shared component

Future convergence: Once all products use AdminHeader, the wrapper itself can eventually be removed in favor of using @wordpress/admin-ui Page directly — the props already map 1:1.

Does this pull request change what data or activity we track or use?

No

Testing instructions:

  • jetpack build packages/publicize passes
  • jetpack test js packages/publicize passes
  • jetpack test php packages/publicize passes
  • Visual check: Social admin page renders header with Jetpack bolt icon + "Social" title
  • Verify no regressions on other admin pages (AdminPage backward compatibility)

Changelog

  • Generate changelog entries for this PR (using AI).

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings February 25, 2026 05:23
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 25, 2026

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the update/normalize-admin-page-headers branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/normalize-admin-page-headers
bin/jetpack-downloader test jetpack-mu-wpcom-plugin update/normalize-admin-page-headers

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 25, 2026

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

The Jetpack plugin has different release cadences depending on the platform:

  • WordPress.com Simple releases happen as soon as you deploy your changes after merging this PR (PCYsg-Jjm-p2).
  • WoA releases happen weekly.
  • Releases to self-hosted sites happen monthly:
    • Scheduled release: March 3, 2026
    • Code freeze: March 3, 2026

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Backup plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Boost plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Search plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Social plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Starter Plugin plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Protect plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Videopress plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Mu Wpcom plugin:

  • Next scheduled release: WordPress.com Simple releases happen semi-continuously (PCYsg-Jjm-p2)

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Inspect plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Wpcomsh plugin:

  • Next scheduled release: Atomic deploys happen twice daily on weekdays (p9o2xV-2EN-p2)

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Automattic For agencies client plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Classic Theme helper plugin plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Paypal Payment buttons plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Wpcloud Sso plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@github-actions github-actions bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Feb 25, 2026
@CGastrell CGastrell added the Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR label Feb 25, 2026
@CGastrell CGastrell self-assigned this Feb 25, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a unified AdminHeader component in @automattic/jetpack-components that standardizes admin page headers across Jetpack products by wrapping the @wordpress/admin-ui Page component. It migrates the publicize/Social admin page as a proof of concept, demonstrating the new architecture while maintaining full backward compatibility.

Changes:

  • Adds new AdminHeader component that provides a consistent header with logo (defaulting to Jetpack bolt) and title composition
  • Extends AdminPage with new props (title, subTitle, logo, actions, tabs) for the unified header pattern while preserving the legacy header prop for backward compatibility
  • Migrates publicize admin page to use the new pattern, removing the old custom AdminPageHeader component and associated files

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
projects/js-packages/components/components/admin-header/index.tsx New AdminHeader component that wraps @wordpress/admin-ui Page with Jetpack-specific defaults
projects/js-packages/components/components/admin-header/types.ts TypeScript types for AdminHeader props
projects/js-packages/components/index.ts Exports the new AdminHeader component
projects/js-packages/components/components/admin-page/types.ts Adds new props for unified header support, deprecates legacy header prop
projects/js-packages/components/components/admin-page/index.tsx Implements conditional rendering logic for unified vs legacy header
projects/js-packages/components/package.json Adds @wordpress/admin-ui@1.8.0 dependency
projects/js-packages/components/changelog/update-normalize-admin-page-headers Documents minor addition of AdminHeader component
projects/packages/publicize/_inc/components/admin-page/index.tsx Migrates to use title prop and actions for license text
projects/packages/publicize/_inc/components/admin-page/page-header/index.jsx Deleted - old custom header component
projects/packages/publicize/_inc/components/admin-page/page-header/logo.js Deleted - inline SVG logo
projects/packages/publicize/_inc/components/admin-page/page-header/styles.module.scss Deleted - old header styles
projects/packages/publicize/_inc/components/admin-page/test/page-header.test.jsx Deleted - tests for old header component
projects/packages/publicize/changelog/update-normalize-admin-page-headers Documents patch-level change for header migration
pnpm-lock.yaml Updates lock file with new @wordpress/admin-ui dependency
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +101
// should we add a subTitle? Page seems already crowded
// subTitle={ __( 'Share your posts with your social media network.', 'jetpack-publicize-pkg' ) }
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented-out code suggests uncertainty about whether to include a subtitle. If this is intentional for future consideration, consider converting it to a TODO comment with context. If it's not needed, remove it entirely to reduce code clutter.

Suggested change
// should we add a subTitle? Page seems already crowded
// subTitle={ __( 'Share your posts with your social media network.', 'jetpack-publicize-pkg' ) }
// TODO: Consider adding a subtitle here if the page layout is simplified in the future.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +94
const licenseAction =
! hasSocialPaidFeatures() && isJetpackSite
? createInterpolateElement(
__(
'Already have an existing plan or license key? <a>Click here to get started</a>',
'jetpack-publicize-pkg'
),
{
a: <a href={ getMyJetpackUrl( '#/add-license' ) } />,
}
)
: null;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleted test file page-header.test.jsx contained tests for the license text functionality (showing/hiding based on paid features and site type). These tests should be migrated to ensure the license action behavior is still covered. Consider adding test cases to social-admin-page.test.jsx to verify that the license action appears in the header's actions prop when appropriate.

Copilot uses AI. Check for mistakes.
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control bot commented Feb 25, 2026

Code Coverage Summary

Coverage changed in 7 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/backup/src/js/components/Admin/index.js 0/124 (0.00%) 0.00% 27 💔
projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php 0/203 (0.00%) 0.00% 10 💔
projects/js-packages/components/components/admin-page/index.tsx 1/15 (6.67%) -2.42% 4 💔
projects/packages/search/src/dashboard/components/pages/upsell-page/index.jsx 0/48 (0.00%) 0.00% 3 ❤️‍🩹
projects/plugins/jetpack/_inc/client/components/masthead/index.jsx 9/15 (60.00%) 5.45% 1 ❤️‍🩹

Full summary · PHP report · JS report

Coverage check overridden by Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR .

@CGastrell CGastrell added [Status] In Progress and removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. labels Feb 25, 2026
Copilot AI review requested due to automatic review settings February 25, 2026 15:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +58
const AdminHeader: FC< AdminHeaderProps > = ( {
logo,
title,
subTitle,
actions,
tabs = null,
className,
breadcrumbs = null,
badges = null,
} ) => {
const classes = className;

// While admin-ui Page has a title prop, it fails to render both the logo and
// text. Internally it tries to accommodate both inside Heading.
// Composing here with Heading as it is on admin-ui Page.
const composedTitle = title ? (
<HStack spacing={ 2 } justify="left">
{ logo || <JetpackLogo showText={ false } height={ 20 } /> }
<Heading as="h2" level={ 3 } weight={ 500 } truncate>
{ title }
</Heading>
</HStack>
) : undefined;

return (
<Page
className={ classes }
title={ composedTitle }
subTitle={ subTitle }
actions={ actions }
breadcrumbs={ breadcrumbs }
badges={ badges }
showSidebarToggle={ false }
>
{ tabs }
</Page>
);
};

export default AdminHeader;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new AdminHeader component is introduced without any test coverage. While the old AdminPageHeader test was deleted (which is appropriate since that component was removed), the new component should have tests to verify its prop handling, logo defaulting behavior, and integration with the @wordpress/admin-ui Page component. Consider adding tests for AdminHeader similar to the test coverage that existed for the old AdminPageHeader.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +94
const licenseAction =
! hasSocialPaidFeatures() && isJetpackSite
? createInterpolateElement(
__(
'Already have an existing plan or license key? <a>Click here to get started</a>',
'jetpack-publicize-pkg'
),
{
a: <a href={ getMyJetpackUrl( '#/add-license' ) } />,
}
)
: null;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleted page-header.test.jsx file contained tests verifying that the license action text ("Already have an existing plan or license key?") appears conditionally based on whether the site has paid features and whether it's a Jetpack site. This behavior is now implemented in the licenseAction variable (lines 83-94) and passed as the actions prop, but there's no test coverage for this functionality in the remaining test files. Consider adding tests to social-admin-page.test.jsx to verify the license action appears/disappears under the correct conditions.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +55
return (
<Page
className={ classes }
title={ composedTitle }
subTitle={ subTitle }
actions={ actions }
breadcrumbs={ breadcrumbs }
badges={ badges }
showSidebarToggle={ false }
>
{ tabs }
</Page>
);
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @wordpress/admin-ui Page component is designed to wrap the entire page content (header + body), as seen in its usage throughout the forms package. However, AdminHeader uses Page only for the header portion, with only tabs passed as children (line 53), while the actual page content is rendered separately in AdminPage (line 102). This creates an unusual architecture where Page doesn't contain the page content. Consider either: (1) refactoring to have Page wrap all content, or (2) using a more direct approach to render just the header elements without wrapping them in the full Page component. The current approach may cause issues if @wordpress/admin-ui expects Page to manage layout for its entire content area.

Copilot uses AI. Check for mistakes.
// text. Internally it tries to accommodate both inside Heading.
// Composing here with Heading as it is on admin-ui Page.
const composedTitle = title ? (
<HStack spacing={ 2 } justify="left">
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HStack uses justify="left", but the codebase more commonly uses justify="start" for left alignment (see examples in forms/routes/responses/integrations-modal.tsx:184, forms/src/blocks/contact-form/components/jetpack-integrations-modal/helpers/akismet.tsx:76, and many others). While both work, using justify="start" would be more consistent with the rest of the codebase.

Suggested change
<HStack spacing={ 2 } justify="left">
<HStack spacing={ 2 } justify="start">

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alignment="center" might also be needed.

Comment on lines +3 to +6
* Importing the CSS via @import inside a .css file avoids the
* @wordpress/dependency-extraction-webpack-plugin externalization that
* breaks direct JS `import '@wordpress/admin-ui/build-style/style.css'`.
* css-loader resolves @import independently of the externals plugin.
Copy link
Copy Markdown
Member

@simison simison Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should handle any externalization/bundling issues instead in the Webpack config, which is much more traceable in future when we want to make changes to this. CSS file is otherwise a bit of a hack and mystery change that will go unnoticed in future.

There are existing examples in the repo you can see for dataviews, ui and admin-ui packages.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that forms solves this with resolve.alias + DependencyExtractionPlugin { requestMap: { external: false } }. Could we do this once at @automattic/jetpack-webpack-config and have it for free for each consumer? Not sure if possible, but otherwise we have to do this for every consumer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responding myself. The resolve.alias for the CSS file can't be centralized since the path to the style.css is different for each package—can't hardcode a path that works everywhere.

So the realistic "fix it once" approach would be:

  • Add @wordpress/admin-ui: { external: false } to jetpack-webpack-config's defaultRequestMap
  • Keep the CSS import in jetpack-components's AdminHeader (or AdminPage)
  • Add a sideEffects: true rule for admin-ui CSS in jetpack-webpack-config's default rules

Not sure about going this route right now. I'll prototype and see if it doesn't get out of hand, and otherwise we'll go with the way it's done in Forms.

Copy link
Copy Markdown
Member

@simison simison Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do this once at @automattic/jetpack-webpack-config

Yep, totally!

The new newsletter dashboard had this configured as well, I think.

Just note that dependency extraction plugin should already bundle admin-ui. I'm just a bit vague if that applies only to JS, and we still separately need to instruct scss pipeline to bundle the CSS.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a long fight to get it working. I'd like to get it through some webpack config, but when I tried it failed to deliver. Let me know @vianasw if you get somewhere with this.

While I don't like it, it works great as a transitional component.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the issue, and did you check with the Triforce team? Let's not settle on poor workarounds.

</Col>
</Container>
{ showHeader && title ? (
<AdminHeader
Copy link
Copy Markdown
Member

@simison simison Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I'd componentize what was already a component — seems more complicated than need, and instead just use it directly:

		<Page
			className={ classes }
			title={ composedTitle }
			subTitle={ subTitle }
			actions={ actions }
			breadcrumbs={ breadcrumbs }
			badges={ badges }
			showSidebarToggle={ false }
		>
			{ tabs }
		</Page>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale for a separate AdminHeader component was to use it independently by products with legacy layouts that can't wrap everything in AdminPage. But in practice, when I tried to use it in the Settings page, the AdminHeader standalone created layout conflicts because it wraps admin-ui's Page component, which is a full-page layout (nested layout systems fighting each other). I'll give it some more thought and see if we can't easily use AdminHeader as a standalone component.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, if you wanna proceed without full Page component as a first step:

  • See if the Header can be exported from the admin-ui in upstream outside the Page. (Minding the etiquette pb6Nl-dyI-p2)
  • Copy the Header component from Gutenberg locally (just temporarily), which lets you update headers first, and gives you time to work through changes needed for a more complete <Page> migration.

There's actually old temporary copy for forms still here, but it might be worth taking a fresh one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an exception on one product the full copy Header dance might be good, but not if we have to standardize it for multiple ones. The thin wrapper is meant to cope with that. While it wraps the full Page component, we only use the header support in it while also exporting a standalone version of the AdminHeader. There are some cases where the Page component can easily wrap the legacy (PR) content and we'll use it. I don't see the need to depend on GB exporting admin-ui/header, it would be good though, but I'm unaware of the side effects and requirements. Meanwhile the thin wrapper can deal with it and maintain a flexible back/future compatibility.

Pushing the upstream export should be done, but attaching this work to depend on that is double effort from my PoV.

Copy link
Copy Markdown
Member

@simison simison Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CGastrell I'm not sure I understand what you are suggesting on how we proceed. Can you elaborate?

To be clear, I said above that none of this is a blocker, but getting to a good place in follow-ups is an absolute must so that we don't leave things in a weird state.

const composedTitle = title ? (
<HStack spacing={ 2 } justify="left">
{ logo || <JetpackLogo showText={ false } height={ 20 } /> }
<Heading as="h2" level={ 3 } weight={ 500 } truncate>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this end up double-wrapping the heading, and locking to pre-defined styles? I'd just expect a plain string and let the actual Header component deal with styles.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarifying that I expect HStack+logo but extra h2 with styles was surprising.

Just noting that we're sorting out a small bug with non-string title but I don't think it's a blocker here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I'll see how to fix this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarifying that I expect HStack+logo but extra h2 with styles was surprising.

Just noting that we're sorting out a small bug with non-string title but I don't think it's a blocker here.

got it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this end up double-wrapping the heading

Yes, but no harm in it. Meanwhile this is the exact structure used by admin-ui/header. The issue here was that passing the logo as part of title wouldn't guarantee vertical alignment, so structuring the logo and the text (heading) on a HStack seemed like the cleanest way.

When I tried it as simply injecting the title side by side with the logo, alignment would have required custom CSS. This approach nests a heading but achieves the goal with less customizations/hacks.

Copy link
Copy Markdown
Member

@simison simison Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CGastrell you're misunderstanding. HStack is fine, my question was if you end up with <h2><h2>Title</h2></h2>.

showSidebarToggle={ false }
>
{ tabs }
</Page>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be fine as a stopgap, but now this new Header component locks us too much into the <Page><Header/><Tabs/></Page> format, while right next up, we should wrap the entire page contents with Page instead. How would it look if we used Page for the full page contents already now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it "properly" now is a bigger lift per product. The stopgap approach (current AdminHeader) lets products adopt the new header with minimal changes (like the one-line My Jetpack PR), while the full-Page approach requires rethinking each product's layout container hierarchy. Unless we went with the full-page wrapping where we already can (Search, Social, Protect, etc), and do a custom header-only solution for the legacy layouts—just a styled div with the right typography and spacing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still don't have a use for this, we could remove it. But I'd like to see how the Settings page is handling its tabs. This was mostly copied from what we have in forms, that I used as the most refined version.

Removing it should be fine if we don't see a use case for it.

Copy link
Copy Markdown
Member

@simison simison left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left more detailed comments, but overall as a summary:

  • I like how it allows conditionally opting into the new layout page by page.
  • I didn’t like the CSS-import hack instead of just using the webpack config as we do elsewhere in the repo.
  • <Page><Header/><Tabs/></Page> structure, which leaves out the actual page contents, is questionable, but fine as a stop-gap to keep moving if we right away follow up with more appropriate use.

Copilot AI review requested due to automatic review settings February 26, 2026 16:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +79
{ showHeader && title ? (
<AdminHeader
logo={ logo }
title={ title }
subTitle={ subTitle }
actions={ actions }
tabs={ tabs }
/>
) : (
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When title is provided, AdminPage renders the new AdminHeader path, but the sandboxedDomain badge/testConnection affordance is only rendered in the legacy header branch. As a result, sandboxedDomain becomes a no-op for pages adopting the unified header. Consider rendering the sandbox badge in both branches (e.g., as part of actions, or an additional element in AdminHeader) so existing diagnostics keep working after migrations.

Copilot uses AI. Check for mistakes.
subTitle,
logo,
actions,
tabs,
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdminHeader supports breadcrumbs/badges, but AdminPage doesn’t currently accept/forward them in the unified-header path. If AdminPage is intended to be the single entrypoint for the new header API, consider adding breadcrumbs and badges to the props destructuring here and passing them through to .

Suggested change
tabs,
tabs,
breadcrumbs,
badges,

Copilot uses AI. Check for mistakes.
Comment on lines +53 to 56
for await ( const filePath of fs.glob( '**/*.{scss,css}', {
cwd: packageRoot,
exclude: Array.from( IGNORED_DIRS, dir => `**/${ dir }/**` ),
} ) ) {
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script now copies both .scss and .css files into build output, but the script name and npm script key are still copy-scss. Consider renaming to something like copy-styles (or adding a brief comment near the script definition) to avoid confusion for future maintainers.

Copilot uses AI. Check for mistakes.
*/
tabs?: ReactNode;

/**
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdminPageProps exposes the new unified-header props, but it’s missing breadcrumbs and badges even though AdminHeader (and the PR description) supports them. This prevents consumers from passing these through via AdminPage and makes the public API incomplete. Consider adding breadcrumbs?: ReactNode and badges?: ReactNode to AdminPageProps (and wiring them through in AdminPage) or updating the PR description/props to match the actual supported surface.

Suggested change
/**
/**
* Breadcrumb navigation displayed in the unified header.
*/
breadcrumbs?: ReactNode;
/**
* Badge elements displayed alongside the title in the unified header.
*/
badges?: ReactNode;
/**

Copilot uses AI. Check for mistakes.
Add flex column layout with 8px gap to match admin-ui Page header's
spacing between title and subtitle. Also add padding-block-end: 8px
on subtitle to match the admin-ui-page__header-subtitle style. This
brings the total header height to 96px, matching Social, Backup, etc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 81 out of 82 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

>
{ notice && <Notice floating={ true } dismissable={ true } { ...notice } /> }
<Container horizontalSpacing={ 0 }>
title={ __( 'Protect', 'jetpack-protect' ) }
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The product name "Protect" is passed to title inconsistently: protect/src/js/routes/setup/index.jsx uses the raw string literal 'Protect' with a "do not translate" comment (consistent with the pattern used by "Newsletter" and "Boost"), while protect/src/js/components/admin-page/index.jsx wraps it with __( 'Protect', 'jetpack-protect' ). Product names that are not to be translated should consistently use raw string literals without __() to avoid them being flagged for translation.

Copilot uses AI. Check for mistakes.
<Logo />
<JetpackLogo showText={ false } height={ 20 } />
</div>
<h2 className={ clsx( styles.title ) }>
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Boost" is a product name that should not be translated according to the pattern established by "Newsletter", "Protect" (setup page), and "VideoPress" in this PR, which use raw string literals with "do not translate" comments. Wrapping "Boost" with __() makes it appear in translation tools as a translatable string. It should be { 'Boost' /** "Boost" is a product name, do not translate. */ } instead.

Copilot uses AI. Check for mistakes.
vianasw and others added 3 commits March 2, 2026 16:40
The #jb-admin-settings React mount point had a transparent background,
so the area behind the floated #dolly element showed the gray wp-admin
background instead of white. Give the mount point a white background
to match how AdminPage-based products handle this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The existing #dolly style had a border-bottom that created a visible
horizontal line between the Hello Dolly text and the Jetpack header.
Remove it so the dolly area blends seamlessly with the header below.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous commit fixed border-bottom in _main.scss (.jetpack-pagestyles
#dolly), but admin-static.scss has a duplicate .wp-admin #dolly rule that
loads later in the CSS bundle, overriding the fix via cascade order.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
showFooter
moduleName={ __( 'VaultPress Backup', 'jetpack-backup-pkg' ) }
header={ <Header /> }
title={ __( 'Backup', 'jetpack-backup-pkg' ) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

<AdminPage
moduleName={ moduleName }
showHeader={ false }
title={ __( 'Social', 'jetpack-publicize-pkg' ) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

</div>
<AdminPage
moduleName={ __( 'Jetpack Search', 'jetpack-search-pkg' ) }
title={ __( 'Search', 'jetpack-search-pkg' ) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

<Col lg={ 12 } md={ 12 } sm={ 12 }>
<ConnectionError />
<AdminPage
title={ __( 'Search', 'jetpack-search-pkg' ) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

<AdminPage
moduleName={ __( 'Jetpack VideoPress', 'jetpack-videopress-pkg' ) }
header={ <JetpackVideoPressLogo /> }
title={ __( 'VideoPress', 'jetpack-videopress-pkg' ) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

} elseif ( 'jetpack_about' === $page ) {
esc_html_e( 'About', 'jetpack' );
} else {
esc_html_e( 'Jetpack', 'jetpack' );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

>
{ notice && <Notice floating={ true } dismissable={ true } { ...notice } /> }
<Container horizontalSpacing={ 0 }>
title={ __( 'Protect', 'jetpack-protect' ) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Product name

Product names (Boost, Protect, Search, Social, Backup, VideoPress, Jetpack,
etc.) are brand names and should not be wrapped in translation functions.

- Remove moduleName props from AdminPage/JetpackFooter callers so the
  default "Jetpack" kicks in for footers
- Change translated title props to plain strings with do-not-translate
  comments
- Fix default moduleName in AdminPage and JetpackFooter components to
  use plain strings instead of __()
- Fix translated product names in Boost image-guide PHP and Jetpack
  Stats banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 2, 2026 16:07
- Newsletter: remove moduleName prop and MODULE_NAME constant so the
  default "Jetpack" footer kicks in
- Jetpack admin page PHP: use plain echo for "Jetpack" product name
  instead of esc_html_e()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 87 out of 88 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Admin Page React-powered dashboard under the Jetpack menu [Boost Feature] Image Guide Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR E2E Tests [Feature] Contact Form [Feature] Publicize Now Jetpack Social, auto-sharing [JS Package] Components [JS Package] Webpack Config [Package] Backup [Package] Forms [Package] My Jetpack [Package] Newsletter [Package] Publicize [Package] Search Contains core Search functionality for Jetpack and Search plugins [Package] VideoPress [Plugin] Automattic For Agencies Client [Plugin] Backup A plugin that allows users to save every change and get back online quickly with one-click restores. [Plugin] Boost A feature to speed up the site and improve performance. [Plugin] Classic Theme Helper Plugin [Plugin] CRM Issues about the Jetpack CRM plugin [Plugin] Inspect [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Plugin] mu wpcom jetpack-mu-wpcom plugin [Plugin] Paypal Payment Buttons [Plugin] Protect A plugin with features to protect a site: brute force protection, security scanning, and a WAF. [Plugin] Search A plugin to add an instant search modal to your site to help visitors find content faster. [Plugin] Social Issues about the Jetpack Social plugin [Plugin] Starter Plugin [Plugin] VideoPress A standalone plugin to add high-quality VideoPress videos to your site. [Plugin] Wpcloud Sso [Plugin] Wpcomsh RNA [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants