Skip to content

Dock: keep off-site admin menus out, and put the wp-admin originals back - #645

Merged
mmtr merged 5 commits into
trunkfrom
claude/openstation-wpcom-admin-menu-af82c2
Aug 21, 2026
Merged

Dock: keep off-site admin menus out, and put the wp-admin originals back#645
mmtr merged 5 commits into
trunkfrom
claude/openstation-wpcom-admin-menu-af82c2

Conversation

@mmtr

@mmtr mmtr commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Proposed changes

  • Admin menu entries pointing at another host no longer reach the dock. A tile that can only open a browser tab breaks the shell's navigation, so the entry is dropped instead.
  • The exception is a child of a menu a plugin registered, when the parent menu itself stays on-site. A docs or account link under a plugin's own menu keeps its row, flagged offSite and marked with an outbound glyph in the constellation flyout. It never becomes a tab in the window's tab strip, because the iframe can't load it.
  • When a menu's own slug points off-site and a surviving child stands in for it, the menu's identity follows: isCore, multi, placement, pluginFile and the slug passed to openstation_dock_item all come from the adopted child.
  • Menu rows carrying the hide-if-js class are dropped. They are out of the classic sidebar, so they were only ever duplicates in the dock.
  • A hidden row is restored, in the dropped row's place, when the off-site row that displaced it is removed. That is what puts Appearance > Themes, Plugins > Add Plugin and Users > All Users back on the wp-admin screens Core registered.
  • A menu with no page of its own and no child left to open is dropped. add_menu_page() accepts a null callback, and admin.php answers such a slug with "Cannot load ."
  • A menu that registered a self-link keeps its own URL, wherever in the list that link sits. The fallthrough to the first submenu stays for menus with no page of their own, which is what WooCommerce needs.
  • Menu icons are sampled across admin_menu and recorded write-once, so art moved into a stylesheet mid-hook still reaches the dock. The live value still wins whenever there is one.

New filter: openstation_menu_item_is_external( bool $external, string $url ).

The submenu flag is offSite, not external, because the tab strip already uses data-kind="external" for plugin-opened sub-iframes, which are on-site.

Note the scope: a plugin that registers its top-level menu as a link to its own hosted service loses its dock tile on any site, not just on WordPress.com. Before this it opened a browser tab. Children are the exception, top-level entries are not, because a tile that can never open a window lies about what a dock click does. Covered in the testing steps below.

Why are these changes being made?

On WordPress.com, a large part of the admin menu points at wordpress.com. My Home, Theme Showcase, Hosting and Upgrades are wordpress.com URLs, and Jetpack repoints Themes, Add Plugin and All Users at their Calypso versions. The shell was sending all of them to a new browser tab, one click at a time.

Jetpack does the repointing by keeping the wp-admin row and marking it hide-if-js, then appending a duplicate beside it. That is also why some submenus showed twice: the dock was rendering both copies. Reading the class gives us both fixes, and reading it in the right order gives us the wp-admin URL for free.

Jetpack's override_svg_icons() moves every SVG-data-URI icon into an inline stylesheet and blanks the menu array, which is why Jetpack and MailPoet were wearing generic cogs.

None of this is host-specific. The rules read the menu arrays, not the host: URL host against admin_url() and home_url(), pluginFile for the plugin exception, the hide-if-js class, and has_action() on the page hook.

Testing instructions

Needs a WordPress.com site (Simple or Atomic) with the admin interface set to the default WordPress.com style, and OpenStation enabled for your user. Jetpack, and ideally MailPoet, active.

The dock

  1. Open the desktop shell.
  2. Make sure My Home, Theme Showcase, Hosting and Upgrades are gone from the dock.
  3. Make sure Jetpack and MailPoet show their own artwork, not a grey cog.
  4. Click every remaining tile. Make sure each one opens in a window, and that no click opens a new browser tab.
  5. Make sure Plugins opens plugins.php, not the installer and not wordpress.com.

The restored entries

  1. Hover Appearance. Make sure the flyout lists Themes once, not twice, and that clicking it opens themes.php in a window.
  2. Same for Plugins > Add Plugin, which should open plugin-install.php.
  3. Same for Users > All Users, which should open users.php.

The plugin exception

  1. On any site, activate a plugin whose menu has an off-site child. MailPoet's Help entry works, or add one:
    add_action( 'admin_menu', function () {
        add_menu_page( 'Demo', 'Demo', 'manage_options', 'demo-plugin', function () { echo 'hi'; } );
        add_submenu_page( 'demo-plugin', 'Docs', 'Docs', 'manage_options', 'https://example.org/docs' );
    } );
  2. Hover the Demo tile. Make sure the Docs row shows a small outbound arrow, and that its tooltip reads "Docs (opens in a new tab)".
  3. Click it. Make sure it opens in a new browser tab, and that no window opens.
  4. Click the Demo tile itself. Make sure the window's tab strip has no Docs tab.

An off-site top-level plugin menu

  1. On any site, add a plugin menu whose top level points off-site:
    add_action( 'admin_menu', function () {
        add_menu_page( 'Portal', 'Portal', 'manage_options', 'https://example.org/portal', null, 'dashicons-cloud' );
    } );
  2. Make sure it appears in the classic wp-admin sidebar.
  3. Open the desktop shell. Make sure no Portal tile appears in the dock. This is the deliberate behaviour change: it used to appear and open a browser tab.

No regressions off WordPress.com

  1. On a self-hosted site, open the desktop shell.
  2. Make sure the dock is unchanged: same tiles, same order, same icons, same submenus.
  3. Open a WooCommerce window if you have one. Make sure the tile still lands on ?page=wc-admin rather than the broken top-level slug.
Open WordPress Playground Preview

A host can extend the admin menu with links to its own control panel,
and can repoint Core entries at its own versions of those screens.
Nothing on another host loads in a window, so those tiles could only
ever escape to a browser tab.

The builder now drops off-site entries, honours the hide-if-js class,
and restores a hidden wp-admin row when the off-site row that displaced
it is dropped. A menu with no page of its own and no child left to open
is dropped too. Icons are sampled across admin_menu so art moved into a
stylesheet mid-hook still reaches the dock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mmtr mmtr self-assigned this Aug 21, 2026
mmtr and others added 4 commits August 21, 2026 14:49
Adopting a surviving child's URL moved only the URL. Everything keyed
off the menu's slug still read the off-site one, so a rescued Plugins
tile reported isCore false, sorted into the plugin cohort at the end of
the dock, and picked up whoever registered the replacement as its owning
plugin. The openstation_dock_item filter saw the off-site slug too,
which would have made openstation_inject_appearance_tabs() skip a
rescued Appearance menu. Identity now travels with the URL.

Renames the submenu row flag from external to offSite. The window's tab
strip already uses data-kind="external" for plugin-opened sub-iframes,
which are on-site, and the two meanings sat a few lines apart.

The constellation's "opens in a new tab" note moves from an aria-label
on the row to a visually hidden span, so it extends the accessible name
instead of replacing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y label or fallback

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mmtr
mmtr enabled auto-merge (squash) August 21, 2026 13:08
@mmtr
mmtr merged commit 2d12a61 into trunk Aug 21, 2026
5 checks passed
@mmtr
mmtr deleted the claude/openstation-wpcom-admin-menu-af82c2 branch August 21, 2026 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant