Skip to content

Modules: rewrite legacy Backbone list-table as React + <AdminPage>#48314

Open
CGastrell wants to merge 8 commits into
trunkfrom
draft/modules-page-option-c
Open

Modules: rewrite legacy Backbone list-table as React + <AdminPage>#48314
CGastrell wants to merge 8 commits into
trunkfrom
draft/modules-page-option-c

Conversation

@CGastrell
Copy link
Copy Markdown
Contributor

@CGastrell CGastrell commented Apr 26, 2026

Proposed changes

Modules admin page (admin.php?page=jetpack_modules) — the last classic Jetpack admin screen still rendered through the Backbone list-table view — is rewritten as a React tree wrapped in <AdminPage>, dressed in the shared jetpack-admin-page-layout SCSS mixin (matching the rest of the admin-page-layout-unification series).

  • Visual goal: parity with trunk. Two-column layout (list left, filter sidebar right), preserved active-row highlight, preserved "Configure" link before each action, preserved offline-mode handling and unavailable_reason text.
  • Modernization comes from primitive swaps:
    • Activate / Deactivate buttons → <ToggleControl>.
    • Segmented filters (View / Sort by) → <ToggleGroupControl>.
    • Top-right Dashboard / Settings links → <AdminPage>'s actions slot.
  • class.jetpack-admin-page.php::render() now bypasses wrap_ui() for both page=jetpack and page=jetpack_modules so the React tree is the only chrome on the page (no double masthead, no double footer).
  • class.jetpack-settings-page.php::page_render() collapses to a single root div + the noscript / REST-disabled notices.
  • class.jetpack-modules-list-table.php drops the three Backbone wp_register_script calls and enqueues the new modules-admin bundle plus the existing jetpackModulesData localized blob.
  • Backbone sources (_inc/jetpack-modules.js, .models.js, .views.js) are left in place but no longer enqueued, so a single-commit revert restores the old experience.

Subsequent commits — feature parity restored

The three items originally cut from v1 have now landed in this PR (separate commits, easy to read in isolation):

  • c11e6eafd8 — URL state sync. search / filter / sort / tag seed from URLSearchParams on mount and reflect into the URL via history.replaceState on every change. Defaults are dropped so a clean URL stays clean.
  • 7fcd75fc12 — Module name link. Each module name is rendered as <a href={item.learn_more_button} target="_blank" rel="noreferrer noopener"> — matching trunk's Backbone js_template behavior. Modules without a learn_more_button keep the static name label.
  • 087bfcbf62 — Bulk activate / deactivate. Adds a per-row checkbox column, a master "select all" checkbox scoped to the current view (with indeterminate state when partial), and a Bulk actions select + Apply button above the list. Submits to the existing admin.php?page=jetpack&action=bulk-(activate|deactivate) handler with nonces.bulk — the server validates the nonce, runs activate/deactivate per slug, and redirects back so module state refreshes naturally.

Before / After

Trunk (before): legacy Backbone list-table.

trunk-goal-desktop-fold

This PR (after): same layout, modernized primitives, bulk-actions toolbar above the list.

iter2-desktop iter2-mobile

Updated screenshots showing the bulk-actions toolbar and the URL-sync state are available in .playwright-mcp/modules-smoke/iter4-urlsync-desktop-restored.png (filters round-tripped via URL), iter6-bulk-desktop.png (toolbar at rest), iter6-bulk-selected.png (2 rows ticked, "Activate" chosen, Apply enabled), and iter6-bulk-mobile.png.

Related product discussion/links

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

No. The existing wpa_page_view tracks event with path: 'old_settings' is preserved verbatim. No new events, no new permissions, no new endpoints. The existing nonce-protected URLs (admin.php?page=jetpack&action=activate|deactivate&module=... for single-row toggle, admin.php?page=jetpack&action=bulk-(activate|deactivate)&modules[]=... for bulk) are reused as-is.

Testing instructions

Core layout

  1. Install / activate Jetpack on a connected site and log in as an admin.
  2. Visit Modules (admin.php?page=jetpack_modules).
  3. Confirm a single Jetpack header at the top (logo + "Modules" title + Dashboard / Settings buttons on the right) — no double masthead, no double footer.
  4. Confirm the layout is two-column at desktop widths (≥960px): module list left, sidebar (search + View / Sort by / Show) right.
  5. Confirm active modules render with a light-gray row background; inactive modules with a white background.
  6. Toggle a module via its <ToggleControl> — the page should reload (server redirect) and reflect the new state.
  7. Try a configurable module (e.g. Site Verification when active): the "Configure" link should render to the left of the toggle.
  8. Try an offline-mode-only module: it should show "Offline mode" text instead of a toggle.
  9. Use the search box, View filter (All / Active / Inactive), Sort by (Alphabetical / Newest / Popular), and a tag in Show — confirm each narrows the list correctly.
  10. Resize to ≤960px: list and sidebar should stack into a single column.
  11. Resize to ≤782px (mobile): verify nothing overflows horizontally; the Jetpack header collapses appropriately.
  12. Click "Dashboard" and "Settings" in the top-right header actions — confirm they link to admin.php?page=jetpack#/dashboard and #/settings respectively.
  13. Visit admin.php?page=jetpack (the main dashboard) and confirm it still renders correctly (the wrap_ui bypass change touched both pages).

URL state sync

  1. With the page open, set search to e.g. share, View=Active, Sort by=Newest, Show=Social. Confirm the URL becomes ?page=jetpack_modules&search=share&filter=true&sort=introduced&tag=Social.
  2. Refresh: confirm the four filter inputs are restored from the URL and the visible list reflects them.
  3. Reset each control to its default. Confirm each default removes its param from the URL — at all-defaults the URL is back to ?page=jetpack_modules.

Module name link

  1. Click a module name (e.g. Forms). It should open the module's learn_more_button URL (a jetpack.com docs / product / wp-admin link) in a new tab, matching trunk.
  2. Modules without a learn_more_button should keep the static name label (no anchor).

Bulk activate / deactivate

  1. Tick the checkbox on two inactive modules — confirm the "N selected" count appears in the toolbar.
  2. Confirm the master checkbox in the toolbar shows an indeterminate state.
  3. Set "Bulk actions" to "Activate" and click "Apply". Confirm the page reloads and both modules are now active.
  4. Tick those two modules again, set "Bulk actions" to "Deactivate", and click "Apply". Confirm both go inactive again.
  5. Click the master checkbox to toggle "select all" — it should select every available + currently visible row.
  6. Apply a tag filter (e.g. Social) — selection persists across filter changes (selected modules outside the current view still submit on Apply, which matches the legacy form-serialize behavior).

The Modules admin page (`admin.php?page=jetpack_modules`) was the last
classic Jetpack admin screen still rendered through the Backbone
list-table view. This commit replaces that view with a React tree that
mounts under a single `<div id="jp-modules-admin-root">`, dressed in the
shared `jetpack-admin-page-layout` SCSS mixin and `<AdminPage>` chrome —
matching the rest of the admin-page-layout-unification series.

Visual goal was parity with trunk: two-column layout (list left, filter
sidebar right), preserved active-row highlight, preserved "Configure"
link before each action, preserved offline-mode handling and
unavailable_reason text. Modernization comes from primitive swaps:
Activate / Deactivate buttons become a <ToggleControl>, segmented
filters become a <ToggleGroupControl>, and the top-right Dashboard /
Settings links are passed through the AdminPage actions slot,
replacing the legacy masthead buttons emitted by wrap_ui().

Plumbing
--------
* class.jetpack-admin-page.php: render() now bypasses wrap_ui() for
  both page=jetpack and page=jetpack_modules, so the React tree is the
  only chrome on the page (no double masthead, no double footer).
* class.jetpack-settings-page.php: page_render() collapses to a single
  root div plus the noscript / REST-disabled notices.
* class.jetpack-modules-list-table.php: drops the three Backbone
  wp_register_script calls and enqueues the new modules-admin bundle
  plus the existing jetpackModulesData localized blob.
* tools/webpack.config.js: adds modules-admin as a sibling entry to
  network-admin.
* _inc/client/modules-admin/: new entry — index.tsx (React app) and
  style.scss (mixin scope + grid layout).

Backbone sources (_inc/jetpack-modules.js, .models.js, .views.js) are
left in place but no longer enqueued, so a single-commit revert
restores the old experience.

Deferred to follow-ups
----------------------
* Bulk activate / deactivate (dropdown + Apply, with row checkboxes).
* Thickbox "More info" per-module modal (long_description / modalinfo).
* URL state sync for search / filter / sort / tag via replaceState.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 26, 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), and enable the draft/modules-page-option-c branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack draft/modules-page-option-c

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 github-actions Bot added [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] In Progress Admin Page React-powered dashboard under the Jetpack menu labels Apr 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 26, 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: May 5, 2026
    • Code freeze: May 4, 2026

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 Apr 26, 2026
@CGastrell CGastrell added Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR 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 Apr 26, 2026
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control Bot commented Apr 26, 2026

Code Coverage Summary

Coverage changed in 3 files.

File Coverage Δ% Δ Uncovered
projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-admin-page.php 0/201 (0.00%) 0.00% -2 💚
projects/plugins/jetpack/class.jetpack-modules-list-table.php 0/189 (0.00%) 0.00% -17 💚
projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-settings-page.php 0/51 (0.00%) 0.00% -55 💚

1 file is newly checked for coverage.

File Coverage
projects/plugins/jetpack/_inc/client/modules-admin/index.tsx 0/141 (0.00%) 💔

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 and others added 3 commits April 26, 2026 18:29
* Changelog `Type: changed` is invalid for `plugins/jetpack` (the plugin's
  changelogger config only accepts major / enhancement / compat / bugfix /
  other). Switch to `enhancement`.
* `<ToggleControl>` requires a `label` prop in its TS signature even though
  we render it visually unlabeled (the row name beside it acts as the label,
  and the per-row `aria-label` carries the accessibility name). Pass an empty
  string to satisfy the type.
* `new Jetpack_Modules_List_Table()` is intentional fire-and-forget — the
  constructor enqueues the React bundle. Suppress Phan's PhanNoopNew with an
  inline annotation explaining the side effect.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per project policy "use @wordpress/ui first, fall back to @wordpress/components
only when ui doesn't ship the primitive": swap the two header-action primitives
that have ui equivalents.

* `Button` from @wordpress/components → `Button` from @wordpress/ui, with
  `nativeButton={ false }` + `render={ <a href="..." /> }` for navigational
  rendering (the same pattern Jetpack already uses elsewhere, e.g. AI MCP
  setup).
* `__experimentalHStack` from @wordpress/components → `Stack` from @wordpress/ui
  with `direction="row" gap="sm"`. Loses one experimental import in the
  process.

ui doesn't ship `ToggleControl`, `ToggleGroupControl`, or `SearchControl` yet,
so those stay imported from @wordpress/components. `Page` (the AdminPage
chrome) is already pulled from @wordpress/admin-ui transitively via
@automattic/jetpack-components' AdminPage component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The original commit's comments referenced PR-history phrasing like "Option C
draft v1", "v1 scope", and "deferred follow-ups" — context that belongs in
the PR description, not in code that future readers will see in isolation.

Rewrite the file/method header comments and inline annotations to describe
what the code does and why it exists, without referring to the rewrite
itself or the migration milestone. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CGastrell CGastrell marked this pull request as ready for review April 27, 2026 00:32
@CGastrell CGastrell self-assigned this Apr 27, 2026
CGastrell and others added 4 commits April 27, 2026 01:59
Search, view, sort, and tag filters now seed from `URLSearchParams` on
mount and reflect into the URL on every change. Defaults are dropped so
a clean URL stays clean. Refreshing or sharing the URL restores all four
filters; visual is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps each module name in an anchor pointing at item.learn_more_button
(the module's docs / product / wp-admin URL), opening in a new tab.
This matches trunk's Backbone js_template behavior. Modules without a
learn_more_button keep the static name label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a per-row checkbox column, a master "select all" checkbox scoped
to the currently filtered + available rows (with indeterminate when
partial), and a Bulk actions select + Apply button above the list.
Submitting routes to the existing
admin.php?page=jetpack&action=bulk-(activate|deactivate) handler with
nonces.bulk; the server validates the nonce, runs activate/deactivate
per slug, and redirects back so module state refreshes naturally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets the @wordpress/ui Button's `loading` prop while the bulk navigation
is in flight so the button shows a spinner and is aria-disabled, instead
of looking idle while the page reloads. Yields one frame via setTimeout
before assigning window.location.href so React paints the loading state
before the navigation starts.

Also drops the invalid `variant="secondary"` (not a valid @wordpress/ui
Button variant; was silently falling through to the default solid look).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@simison
Copy link
Copy Markdown
Member

simison commented Apr 28, 2026

Honestly, to me, it would make sense to figure out which of these features we want on the Products page, and finally ditch having two versions of the same thing. ;-)

@CGastrell
Copy link
Copy Markdown
Contributor Author

Honestly, to me, it would make sense to figure out which of these features we want on the Products page, and finally ditch having two versions of the same thing. ;-)

Point taken but, does the page work as expected?

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 Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] In Progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants