Jetpack Beta: modernize the admin UI with React, the WordPress design system, and the Abilities API#49326
Jetpack Beta: modernize the admin UI with React, the WordPress design system, and the Abilities API#49326enejb wants to merge 53 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Activity Log Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds package.json, webpack.config.js, tsconfig.json, and babel.config.js modeled on projects/plugins/protect. Adds automattic/jetpack-wp-abilities to composer.json. Updates .gitignore to cover build/, node_modules/, .cache/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngs) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y metadata Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…and wire init - Add jetpack-beta/activate-branch and jetpack-beta/update-settings write abilities - Refactor get_plugin() → build_plugin_view() and get_settings() → build_settings() for DRY reuse - Extract plugin_view_schema() helper so the large schema literal lives in one place - Wire Beta_Abilities::init() into plugins_loaded at priority 20 in jetpack-beta.php - Require wp-admin includes inside activate_branch() for REST-context safety Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…clude Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…client
- Add `JPBETA__PLUGIN_FILE` constant to `jetpack-beta.php` for use with `Assets::register_script`.
- Add `automattic/jetpack-assets` to `composer.json` require (alphabetical order) and update `composer.lock`.
- Add `use Automattic\Jetpack\Assets` to `class-admin.php`; replace legacy `wp_enqueue_style`/`wp_enqueue_script`/`wp_localize_script` with `Assets::register_script`/`Assets::enqueue_script` + `wp_add_inline_script` bootstrapping `window.JetpackBeta`.
- Replace PHP template `require_once` calls in `render()` with `<div id="jetpack-beta-root"></div>`; preserve `PluginDataException` guard and network-admin `RuntimeException` path.
- Create `src/js/api/types.ts`: full TypeScript types for all ability payloads plus `window.JetpackBeta` global declaration.
- Create `src/js/api/abilities.ts`: typed `apiFetch` client for all five Beta abilities endpoints.
- Create `src/js/index.tsx`: entry point — bootstraps `apiFetch` middleware then mounts `<App />` via `createRoot`.
- Create `src/js/app.tsx`: `AdminPage` shell routing between `<PluginList />` and `<PluginManage slug={plugin} />` based on `window.JetpackBeta.plugin`.
- Create `src/js/screens/plugin-list.tsx` and `plugin-manage.tsx`: minimal type-correct stubs (replaced in Tasks 6–7).
- Create `src/js/style.scss`: minimal root layout rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the PluginManage screen (branch picker) and supporting components: markdown-panel, branch-card, branch-section. The manage screen owns its own AdminPage wrapper so it can inject a plugin-name breadcrumb after the async getPlugin() resolves. app.tsx routes list vs manage accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d mu-plugin notice Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d CSS The React app (Tasks 5–7) fully replaces the old PHP-template/vanilla-JS UI. Delete the six dead templates, admin.js, updates.js, and admin.css. Strip the now-dead GET-action handlers (activate-branch, toggle-autoupdates, toggle-email-notifications) and the helper methods they used (is_toggle_action, show_toggle, show_toggle_autoupdates, show_toggle_emails) from class-admin.php, while keeping the multisite network-admin access-control redirect, render(), render_banner(), to_test_content(), admin_enqueue_scripts(), and plugin_action_links() intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pe, admin includes) - Read abilities are called over GET with an `input` query envelope; writes POST with an `input` body envelope, matching the wp-abilities/v1 run controller. - Add a default empty-object input to the zero-arg read abilities so GET requests (which cannot encode an empty object) pass input validation. - Load wp-admin/includes/file.php + plugin.php in build_plugin_view() since the REST run endpoint executes outside wp-admin (WP_Filesystem/get_plugin_data). - Add @wordpress/url dependency for addQueryArgs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…semantics Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…settings heading - Wrap the plugin list in a Container/Col column (matching the My Jetpack module layout). - Replace the per-row Manage button with a chevron; the whole card is now a keyboard-accessible button that navigates to the plugin's manage screen. - Add a Settings heading above the global toggles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tainer as the list Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…markup Use the same @wordpress/ui primitives, '/' separator and h1 current item as @wordpress/admin-ui's Breadcrumbs, relabeled 'Beta Tester'. Links via a real anchor since this admin page has no TanStack router for the component's RouterLink. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the redundant uppercase heading for the fixed branches (Latest Stable, Release Candidate, Bleeding Edge); the card now carries the section name as its label with the version as a sub-line only when it differs. Searchable sections (Feature Branches, Released Versions) keep their heading and search. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… a plain card - Replace the shared JetpackFooter (which hard-codes Products/Help links) with a minimal footer: Jetpack mark + Automattic byline only. AdminPage showFooter=false. - Render the To Test / What changed panels as a regular Card with a heading instead of a CollapsibleCard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a shimmering Skeleton primitive and a card-row skeleton; the plugin list, manage screen, and settings toggles now show layout-matching skeletons while loading instead of a spinner. Honors prefers-reduced-motion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…name immediately - Apply jetpack-admin-page-layout-wp-build to the page body so the header pins to the top, the middle scrolls, and the footer stays fixed at the bottom (and breadcrumb links drop the wp-admin underline). Adds a stable 'jetpack-beta-page' body class and renders AdminPage 'unwrapped' so the footer is a direct page child the mixin can pin. - Pass the plugin display name in the bootstrap so the manage-screen breadcrumb renders immediately instead of waiting for the get-plugin ability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preload the list-plugins payload into the page bootstrap (cached data) on the overview screen so it paints instantly, and remember it in localStorage. The list rarely changes once loaded, so a preloaded/cached list is not re-fetched; the list-plugins ability is only called when neither is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cons - Wrap each screen's constrained Container in a full-width scroll container so the scrollbar sits at the page edge rather than inset beside the content. - Show each plugin's wordpress.org icon (ps.w.org/<slug>/assets/icon.svg) on the list rows, hiding it gracefully when a plugin has no wporg assets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The autoupdate and email-notification settings are site-wide Beta plugin options, not per-plugin, so the toggles no longer appear on each plugin's manage screen — only on the overview where they apply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plugins without wordpress.org assets now render a generic plugin icon in a neutral box instead of hiding the image, so every list row stays aligned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- activate_branch(): guard the post-activation plugin refresh — catch PluginDataException, handle a null Plugin, and propagate a WP_Error from build_plugin_view() instead of fataling on a type error. - get_plugin view: sanitize to_test_html/what_changed_html with wp_kses_post() before returning, since the React UI injects them via dangerouslySetInnerHTML. - update_settings(): normalize email_notifications to bool before persisting, matching the autoupdates path. - get_abilities() docblock: correct the count (seven abilities: four read, three write). - global-toggles: include inFlight in the applySetting useCallback deps and drop the eslint-disable, removing the stale-closure re-entrancy hazard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PHP (abilities): - activate-branch: add an enum to the `source` schema and wrap install_and_activate() to catch InvalidArgumentException, so a bad source returns a clean WP_Error instead of an uncaught 500. - update-plugin: restrict the target to the Beta-managed update set (Utils::plugins_needing_update) so the ability can't drive arbitrary updates. PHP (admin): - Re-add show_toggle_autoupdates/show_toggle_emails/show_toggle as deprecated shims (_deprecated_function) instead of removing them outright, avoiding fatals for any external caller. JS/TS: - plugin-list: render the plugin card as a real <a> link (proper link semantics + keyboard/middle-click) instead of role="button"; revalidate the remembered plugin list in the background (stale-while-revalidate) so a stale localStorage cache reconciles. - plugin-manage: guard the post-update refresh against setState-after-unmount; rebuild the mu-plugin notice with createInterpolateElement (one translatable sentence) and the "Currently Running" title with sprintf; render section/card titles as real <h2> headings; add "(opens in a new tab)" announcements. - global-toggles / markdown-panel / branch-section: section titles render as <h2> for a proper heading hierarchy. - footer: announce the external Automattic link opens in a new tab. - style.scss: unify the --wpds-dimension-padding-2xl fallback to 24px. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The card is a real <a> for link semantics, but it was inheriting the default link color/underline. Inherit the admin text color and drop the underline, and suppress the rectangular UA focus outline (it squared off the card's rounded corners on focus) in favor of the existing blue-border + shadow focus indicator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
I created a JN site and set the beta tester to this branch but I see WSOD on the beta tester page, with |
|
Looks great, @enejb! A couple of thoughts: Plugin overview
Manage a plugin
Updates available
|
| * Mirrors `@automattic/jetpack-components`' JetpackFooter (Jetpack logo + the | ||
| * Automattic byline) but deliberately omits its hard-coded "Products" and | ||
| * "Help" menu links, which aren't relevant to the Beta Tester screen. | ||
| * |
There was a problem hiding this comment.
It would be pretty simple to just add a feature that doesn't include those links, so you wouldn't need to duplicate the component, which is just more effort to maintain consistency and sort of against the whole idea of having central components like that. ;-)
There was a problem hiding this comment.
Good call — done in 8bcfc7c. Added a showDefaultLinks prop (default true, so every existing consumer is unchanged) to the shared JetpackFooter and used <JetpackFooter showDefaultLinks={ false } /> here, dropping the duplicate footer.tsx and its now-redundant CSS.
| * Mirrors the markup of `@wordpress/admin-ui`'s `Breadcrumbs` (the component the | ||
| * My Jetpack screens use) — same `@wordpress/ui` primitives, `/` separator, and | ||
| * an `h1` current item — but links with a real anchor instead of the TanStack | ||
| * router `Link` that component depends on, since this admin page has no router. |
There was a problem hiding this comment.
Worth adding issue link here:
There was a problem hiding this comment.
Done in 8bcfc7c — linked WordPress/gutenberg#77039 in the renderBreadcrumbs docblock.
| <Stack direction="row" align="center" justify="space-between"> | ||
| <Stack direction="column" gap="xs"> | ||
| <Card.Title> | ||
| <Text variant="body-md" render={ <h2 /> }> |
There was a problem hiding this comment.
Note that Card.Title already sets styles so you don't need the <Text> here.
If you do need it, the variant should prolly be header-* instead since you're rendering it as h2?
There was a problem hiding this comment.
Done in 8bcfc7c — collapsed to <Card.Title render={ <h2 /> }>…</Card.Title> so Card.Title does the styling and we keep the heading semantics; removed the redundant nested <Text>.
| <a | ||
| href={ view.bug_report_url } | ||
| target="_blank" | ||
| rel="external noopener noreferrer" | ||
| aria-label={ __( | ||
| 'Found a bug? Report it! (opens in a new tab)', | ||
| 'jetpack-beta' | ||
| ) } | ||
| /> | ||
| } | ||
| > | ||
| { __( 'Found a bug? Report it!', 'jetpack-beta' ) } | ||
| </Button> |
There was a problem hiding this comment.
Would one of these options work?
onClicksetsdocument.location.href?- You'd use
Linkcomponent instead ofButton?
Noting that we'll need the Link button component:
There was a problem hiding this comment.
This is intentionally a link styled as a button (it navigates to the bug-report URL), so a real <a> is the correct element — onClick+document.location would drop middle/Cmd-click and copy-link, and a plain Link reads as text rather than a button. Added a comment referencing WordPress/gutenberg#77098 in 8bcfc7c so we can switch to a first-class link-button when it lands.
| @@ -0,0 +1,56 @@ | |||
| const path = require( 'path' ); | |||
| const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpack' ); | |||
There was a problem hiding this comment.
Might be a good point to add wp-build instead here. ;-)
There was a problem hiding this comment.
Great suggestion. This turns out to be a sizeable refactor — the JS would need restructuring into a wp-build sub-package and class-admin.php rewired from Assets::register_script() + index.asset.php to the generated build/build.php handle, and beta would be the first projects/plugins/* to adopt @wordpress/build (all current adopters are packages). To keep this PR focused on the UI modernization, I am splitting the wp-build migration into a dedicated follow-up PR — it also makes a nice before/after of @automattic/jetpack-webpack-config vs @wordpress/build.
- Footer: add a `showDefaultLinks` prop (default true) to the shared
`@automattic/jetpack-components` JetpackFooter and use it with
`showDefaultLinks={ false }` instead of duplicating the component. Drops
beta's footer.tsx and its now-redundant CSS (the shared component brings its
own styles). Changelog added to js-packages/components.
- Breadcrumb: link the upstream Gutenberg issue (#77039) explaining why we
reimplement Breadcrumbs without the router.
- Currently Running card: let Card.Title render the <h2> directly instead of a
redundant nested <Text variant="body-md">.
- Bug-report button: add a comment referencing the pending link-button issue
(#77098) for the Button-rendered-as-anchor pattern.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
You say this like it's a bad thing.
I'm not sure that replacing it with 1331 lines of TypeScript and some heavyweight external dependencies, resulting in replacing the 8.2K admin.js with 125K of bundled JS, is really a win for a developer-facing tool. This seems to add a lot more moving parts that could break when someone is trying to debug plugins on their site. |
|
I haven't looked at the code, but have similar concerns to Brad on the amount of added code (and this is before implementing with I tried to spin up a JN site, but it gave a blank screen (404 on
|
| * @package automattic/jetpack-beta | ||
| */ | ||
|
|
||
| // @phan-file-suppress PhanUndeclaredFunction, PhanUndeclaredClassMethod @phan-suppress-current-line UnusedSuppression -- Abilities API added in WP 6.9; suppressions needed for older-WP compatibility runs. |
There was a problem hiding this comment.
The minimum version we test against is 6.9 since #49021, this suppression shouldn't be needed anymore.
There was a problem hiding this comment.
Good catch — removed the suppression in 6901a1e. Phan passes without it now that the minimum tested WordPress is 6.9 (Abilities API symbols resolve).
| ); | ||
| } | ||
|
|
||
| $view = self::build_plugin_view( $refreshed ); |
There was a problem hiding this comment.
This doesn't do the right thing if Jetpack Beta Tester itself is the plugin that was activated. The fancy React UI doesn't reload.
There was a problem hiding this comment.
Fixed in 6901a1e. The activate-branch (and update-plugin) abilities now return a reload flag — true when the affected plugin is Jetpack Beta Tester itself — and the React UI does a full window.location.reload() instead of a soft view refresh in that case, since the running app's own PHP/JS was just swapped.
The bug there is that the PR doesn't add a line in
The settings are global, but IIRC were kept on the manage plugin page back in #20168 to make them more straightforward to access. |
Replace the stack of separate plugin cards with one bordered @wordpress/ui Card whose rows are link items separated by hairline dividers — tighter and matching the Social-style list. (@wordpress/ui has no list/item primitive, and @wordpress/components' ItemGroup/Item is an experimental API blocked by @wordpress/no-unsafe-wp-apis, so the list is composed from Card + rows.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface the GitHub PR number on feature-branch cards (new `pr` field from branch_to_section + schema + type), and extend the Feature Branches search to accept a pasted pull-request URL (…/pull/12345) or a bare/`#`-prefixed PR number in addition to branch text — making branches easier to find. Updated the search placeholder to advertise it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The focus indicator was a square `outline`, which cut straight across the card's rounded top/bottom corners on the first/last rows. Switch to an inset box-shadow (follows border-radius) and round the first/last rows to the card's `--wpds-border-radius-lg`, so the focus ring follows the rounded corners. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the updates Notice from intent="warning" (orange) to intent="info"; it stays non-dismissable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalize the compact-list styles (jetpack-beta-list / -list-row) and reuse them for the branch picker: the simple sections (Existing, Latest Stable, Release Candidate, Bleeding Edge) collapse into one bordered card of divider-separated rows, and the searchable Feature Branches / Released Versions sections render their results in the same compact card under their search box. Branch cards become rows (no per-branch Card/gap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eleton - Plugin row links now show the same inset focus ring for :focus as for :focus-visible, overriding the wp-admin default that rendered only a blue bottom border on click. - Loading skeleton is now a single compact list card of divider-separated skeleton rows (ListSkeleton), matching the loaded list instead of a stack of separate cards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the "Welcome to Jetpack Beta Tester" intro (lost when the legacy notice template was removed) as a React card at the top of the overview, reusing the original copy with <em> emphasis via createInterpolateElement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ssion - Reload the page after activating or updating Jetpack Beta Tester itself: the activate-branch / update-plugin abilities now return a `reload` flag (true when the affected plugin is Beta itself), and the React UI does a full window.location.reload() instead of a soft view refresh, since the running app's own code was just swapped. - Remove the @phan-file-suppress for the Abilities API symbols; the minimum tested WordPress is 6.9 (which ships the Abilities API), so it's unused. Phan passes without it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndidate rows
Their pretty version is just the label ("Bleeding Edge" / "Release Candidate"),
so the secondary version line was suppressed. Fall back to the raw `version`
string so each branch row shows which build it points at (the version you'd run).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For plugins running a dev branch the list showed only the channel label
("Bleeding Edge" / "Release Candidate"). Surface the underlying version via a
new `active_version_detail` field on the list item (from dev_info()) and render
it as a secondary line, matching the manage screen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The server bootstrap (window.JetpackBeta.plugins) is localized on every page load and already provides the instant-paint preload, so the localStorage layer was redundant (and a prior staleness hazard). Keep the stale-while-revalidate shape with the bootstrap as the cache: paint from boot.plugins, then revalidate against the cache-bypassing list-plugins ability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- is_self/update-plugin reload now match both the stable (jetpack-beta/) and dev (jetpack-beta-dev/) file forms via a shared is_self_file() helper. Previously is_self() compared the running file (which is the -dev path when a dev build of Beta is active) against Plugin::plugin_file() (always the stable path), so activating/updating Beta while running its own dev build skipped the reload. - branch-section: anchor the GitHub PR-URL regex to a host boundary so lookalike hosts (evilgithub.com) don't match. - plugin-manage: guard handleActivated's setView with the mounted ref, matching handleUpdated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Fixes # N/A
Modernizes the Jetpack Beta Tester admin UI. The plugin was the last major Jetpack plugin with no JS build step: a set of server-rendered PHP templates, 268 lines of vanilla
admin.js, and a hand-rolled 1299-lineadmin.cssreimplementing old Calypso "dops-" styling. This replaces all of that with a React app built on the WordPress design system (@wordpress/ui+@automattic/jetpack-componentsAdminPage), backed by the WordPress Abilities API for all reads and actions.Screenshots
Proposed changes
src/abilities/class-beta-abilities.php): registers seven abilities — four reads (jetpack-beta/list-plugins,get-plugin,get-settings,list-updates) and three writes (activate-branch,update-settings,update-plugin). Adds theautomattic/jetpack-wp-abilitiesdependency. All callbacks gate onupdate_pluginsand preserve the multisite/network-admin access control from the oldadmin_page_load().meta.mcp.public = false(installing PR code shouldn't be agent-callable). Registers without the globaljetpack_wp_abilities_enabledrollout filter since the UI fully depends on these abilities.@wordpress/ui(src/js/): a new webpack build (mirroring Protect) renders both screens — the plugin overview and the per-plugin branch picker. UsesAdminPagefrom@automattic/jetpack-componentsfor the standard Jetpack header/footer + breadcrumb (the same chrome the Activity Log uses). The overview leads with a welcome/intro card and a compact, single-card plugin list (divider-separated link rows). Branch activation, search/filter, settings toggles, the pending-updates panel, and the "To Test"/"What changed" panels are all React.@wordpress/uiCard with divider-separated rows (sharedjetpack-beta-liststyles) rather than a stack of separate cards — tighter and easier to scan.info)@wordpress/uiNotice with an in-place Update action, on both the overview and the per-plugin screen.admin.js,updates.js, andadmin.css(kept the first-run banner + exception templates). The threeAdmin::show_toggle*helpers are kept as_deprecated_function()shims rather than deleted.window.JetpackBeta.plugins) for an instant first paint, then revalidated against the cache-bypassinglist-pluginsability (stale-while-revalidate, no client-side storage); compact-list skeleton loaders instead of a spinner; each list row is a real link (keyboard + middle/Cmd-click) with wordpress.org icons (generic fallback for unpublished plugins); pinned header/footer with the content scrolling; the footer reuses the sharedJetpackFooter(newshowDefaultLinksprop) without the Products/Help links; mobile horizontal-scroll fixed.Hardening & review follow-ups
activate-branchvalidatessourcevia a schemaenumand catchesInvalidArgumentException(cleanWP_Errorinstead of a 500);update-pluginrestricts the target to the Beta-managed update set so the ability can't drive arbitrary updates; server-rendered "To Test"/"What changed" HTML is run throughwp_kses_post()before delivery (it is injected viadangerouslySetInnerHTML).<h2>headings under the breadcrumb<h1>; external links announce "(opens in a new tab)".createInterpolateElement/sprintfinstead of concatenated fragments.reloadflag, and the UI does a full page reload rather than a soft view refresh, since the running app's own PHP/JS was just swapped.@phan-file-suppressfor the Abilities API symbols — they resolve at the minimum tested WordPress (6.9), so the suppression was unused.Related product discussion/links
Does this pull request change what data or activity we track or use?
This removes the legacy
data-jptracks-*events that were attached to the old action links (Manage / Activate / toggle). It does not add any new analytics or collect new data. It does add a new REST surface underwp-abilities/v1for the abilities listed above — all capability-gated (update_plugins) and not exposed to MCP agents (meta.mcp.public = false).Testing instructions
A live Jurassic Ninja test site is running this branch (Jetpack connected):
Steps:
Note: the Beta plugin has no PHPUnit harness today, so this PR adds no automated tests — a
Beta_Abilitiesregistration/permission test is a sensible follow-up. Build/typecheck/eslint/stylelint/phpcs/phan all pass.