Skip to content

PA - VideoPress: carry former names on widget types - #52874

Merged
retrofox merged 11 commits into
trunkfrom
update/pa-extensibility-videopress-former-names
Sep 30, 2026
Merged

retrofox merged 11 commits into
trunkfrom
update/pa-extensibility-videopress-former-names

Conversation

@retrofox

@retrofox retrofox commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes WOOA7S-2200

Proposed changes

Step 2 of the VideoPress consumer stack (WOOA7S-2221), after #52866.

A layout item references its widget type by name, and the dashboard looks the type up by exact name. Renaming a type turns every persisted instance into a "Widget is no longer available" tile, and every move of a widget to its owner package is a rename (jpa/x to owner/x).

The Ads move got away with it because Ads sits outside the customer preview. Top videos sits in Traffic, so the rename needs a bridge first.

The type declares its former names

Widget_Type gains former_names. A type declares them in widget.json, in the $args['former_names'] map of register_widget_types_from_manifest() (current name to former names), or in the arguments of register_widget_type().

The registry keeps the former-to-current map. get_registered() answers for both names, resolve_name() gives the current one, and register() refuses a former name that a registered type or another type's former name already holds, and a name that is someone's former name. Unregistering releases them.

WIDGET_API_VERSION moves to 1.1.0: a consumer can rely on something new, and nothing built against 1.0 breaks.

The record and the client

GET /wpcom/v2/widget-modules publishes former_names on each record.

The dashboard stage builds the rename map from the records and hands it to useDashboardSectionLayout(), which renames a stored layout's items on the way out. There is no write-back: the stored preference keeps the old name until the section's next commit, which persists the current one. A layout with nothing to rename keeps its identity, so staged edits are untouched.

The default layouts

resolve_former_widget_types_in_default_layout() runs on jetpack_premium_analytics_dashboard_default_layout at priority 99, ahead of the unregistered-type check of #52866, so a plugin that still adds an instance under the old name keeps it under the current one.

Both callbacks read the registry through get_answering_widget_type_registry(), the guard #52866 introduced. They also treat a non-string type as an unknown type and drop it, instead of letting isset() throw a TypeError that takes the whole sections route down on PHP 8. Raised by @louwie17 on #52866: register_widget_type() returns a Widget_Type, so passing that object as the type of a default instance reads naturally.

Not here

A rename that also changes attributes needs a migration, the deprecated equivalent of blocks; nothing offers it yet. @wordpress/widget-primitives knows neither former names nor deprecations, which is the upstream ask.

The Ads package can declare jpa/wordads-* as former names in a follow-up.

Related product discussion/links

Linear: WOOA7S-2221 for the stack, WOOA7S-2200 for this step. Umbrella #52867.

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

No.

Testing instructions

Set up

jetpack test php packages/premium-analytics
jetpack test js packages/premium-analytics
jetpack build packages/premium-analytics

The build matters: the PHP side applies without one, but the rename of a stored layout happens in the dashboard's JS.

Use a connected site as an administrator, with a mu-plugin you can edit between steps.

1. Save an instance of a widget type

Register a widget type, demo/clicks, reusing the bundled Clicks modules:

add_action(
	'jetpack_premium_analytics_register_widget_types',
	static function () {
		\Automattic\Jetpack\PremiumAnalytics\register_widget_type(
			'demo/clicks',
			array(
				'render_module' => 'jetpack-premium-analytics/widgets/clicks/render',
				'widget_module' => 'jetpack-premium-analytics/widgets/clicks/widget',
				'category'      => 'stats',
				'title'         => 'Demo clicks',
			)
		);
	},
	20
);

Add the Demo clicks widget to a section and save. The instance renders.

image

2. Break it

Comment the registration out and hard refresh. The instance is now "Widget is no longer available": the stored layout names a type nobody registers.

image

3. Register the type under a new name

Register demo/the-new-clicks with the same modules and no former names yet:

add_action(
	'jetpack_premium_analytics_register_widget_types',
	static function () {
		\Automattic\Jetpack\PremiumAnalytics\register_widget_type(
			'demo/the-new-clicks',
			array(
				'render_module' => 'jetpack-premium-analytics/widgets/clicks/render',
				'widget_module' => 'jetpack-premium-analytics/widgets/clicks/widget',
				'category'      => 'stats',
				'title'         => 'Demo the new clicks',
			)
		);
	},
	20
);

The instance stays broken, and the new type shows up in the inserter. Same as trunk so far.

image

4. Declare the former name

Add 'former_names' => array( 'demo/clicks' ), to that registration and hard refresh.

add_action(
	'jetpack_premium_analytics_register_widget_types',
	static function () {
		\Automattic\Jetpack\PremiumAnalytics\register_widget_type(
			'demo/the-new-clicks',
			array(
				'render_module' => 'jetpack-premium-analytics/widgets/clicks/render',
				'widget_module' => 'jetpack-premium-analytics/widgets/clicks/widget',
				'category'      => 'stats',
				'title'         => 'Demo the new clicks',
				'former_names'  => array( 'demo/clicks' ), // <-- FORMER HERE!!!
			)
		);
	},
	20
);

The instance saved as demo/clicks renders again as "Demo the new clicks".

image

The stored preference still says demo/clicks; the stage renamed it on the way out.

Screenshot 2026-09-28 at 6 32 06 PM

Move any widget and save: from then on, the stored type is demo/the-new-clicks.

Screen.Recording.2026-09-28.at.6.34.52.PM.mov

5. The record

GET /wp-json/wpcom/v2/widget-modules lists demo/the-new-clicks with "former_names": ["demo/clicks"] and no demo/clicks record.

Screenshot 2026-09-28 at 5 46 45 PM

6. A default layout under the old name

Add demo/clicks as a Traffic default instance through jetpack_premium_analytics_dashboard_default_layout and reset the section. The default carries demo/the-new-clicks.

the default-layout policy asks the widget registry once it can answer
phan cannot see past the finally
…ity-videopress-layout-validation

# Conflicts:
#	projects/packages/premium-analytics/docs/dashboard-widgets.md
a renamed type keeps the layouts saved under its old name; contract 1.1.0
@retrofox retrofox added Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Status] In Progress labels Sep 28, 2026
@retrofox retrofox self-assigned this Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

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 update/pa-extensibility-videopress-former-names branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/pa-extensibility-videopress-former-names

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

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!

@jp-launch-control

jp-launch-control Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

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

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/src/dashboard-layout.php 50/53 (94.34%) 0.05% 1 ❤️‍🩹
projects/packages/premium-analytics/routes/dashboard/hooks/use-dashboard-section-layout/use-dashboard-section-layout.ts 23/23 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/routes/dashboard/stage.tsx 69/78 (88.46%) 0.30% 0 💚
projects/packages/premium-analytics/src/widget-modules.php 38/53 (71.70%) 0.54% 0 💚
projects/packages/premium-analytics/src/widget-types.php 135/140 (96.43%) 0.08% 0 💚

1 file is newly checked for coverage.

File Coverage
projects/packages/premium-analytics/routes/dashboard/config/widget-type-renames.ts 14/14 (100.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 .

a Widget_Type object as type dropped the whole sections route on PHP 8
Base automatically changed from update/pa-extensibility-videopress-layout-validation to trunk September 28, 2026 15:41
…ity-videopress-former-names

# Conflicts:
#	projects/packages/premium-analytics/docs/dashboard-sections.md
#	projects/packages/premium-analytics/src/dashboard-layout.php
#	projects/packages/premium-analytics/tests/php/Dashboard_Layout_Test.php
@retrofox
retrofox marked this pull request as ready for review September 28, 2026 17:40
@retrofox
retrofox requested a review from a team as a code owner September 28, 2026 17:40
build the broken instances by hand for phan; cover the non-instance branch and the sanitizer
@retrofox retrofox added [Status] Needs Review This PR is ready for review. 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] In Progress labels Sep 28, 2026

@Nikschavan Nikschavan left a comment

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.

Thank you for the work here. I left two comments inline. The first one is a crash of the whole dashboard when former_names is not a list, so I think it needs a look before this merges.

What I checked: I registered demo/the-new-clicks with former_names set to array( 'demo/clicks' ) and stored a Traffic layout with a demo/clicks instance. The dashboard rendered that instance as "Demo the new clicks", and the stored preference kept the old name.

A Traffic layout stored under demo/clicks renders as Demo the new clicks

}

$former_names = $widget_type ? $widget_type->former_names : ( $args['former_names'] ?? null );
if ( null !== $former_names && ! $this->former_names_are_free( $name, $former_names ) ) {

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.

Only the manifest path runs former_names through sanitize_widget_former_names(). A direct register_widget_type() call with array_unique( array( 'demo/clicks', 'demo/clicks', 'demo/older-clicks' ) ) passes former_names_are_free(), because it checks values only. widget-modules.php#L84 then serializes the names as {"0":"demo/clicks","2":"demo/older-clicks"}, and the for…of in buildWidgetTypeRenames() throws "object is not iterable" and takes down the whole dashboard. Normalizing the list in register() would cover both routes. Should the registry own that normalization instead of the manifest helper?
The dashboard after registering demo/the-new-clicks with a non-list former_names array

@retrofox retrofox Sep 29, 2026 •

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.

Yes, the registry owns it now (7dd1ca8): register() normalizes the declared list to distinct values with list keys before validating and stores that list on the type, so array_unique() leftovers and the direct register_widget_type() path both serialize as ["demo/clicks", "demo/older-clicks"].

sanitize_widget_former_names() is gone from the manifest helper, so both routes are strict; a non-string former name is refused with a _doing_it_wrong(), where the old check let it through silently (a second bug behind yours).

The client also stops trusting the shape: buildWidgetTypeRenames() skips a record whose former_names is not an array, so a bad payload renames nothing instead of taking the dashboard down.

}
return resetCount ? [ ...sectionDefault ] : sectionDefault;
}, [ sectionLayouts, activeSectionId, sectionDefault, resetCount ] );
const fallback = resolveLayoutTypes( sectionDefault, renames );

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.

The section default reaches the client already renamed by resolve_former_widget_types_in_default_layout(). When the registry cannot answer there, the widget-modules records are empty too, so this rename has nothing to map. With const fallback = sectionDefault; the dashboard suite still passes. Is there a path where the default arrives with an old name, or could this rename apply to the stored layout only?

@retrofox retrofox Sep 29, 2026 •

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.

Right 👍

There is no such path: boot_routes() loads widget-modules.php, and with it widget-types.php, for the sections route too, so whenever the registry can answer the default reaches the client already renamed at priority 99, and when it cannot, the records are empty as well.

Same commit: the hook renames the stored layout only, and the default passes through as the record served it.

a keyed list reached the client as an object; the client renames stored layouts only
@retrofox
retrofox added this pull request to stack #52919 September 29, 2026 11:06

@Nikschavan Nikschavan left a comment

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.

Thank you, the changes look good.

@retrofox
retrofox merged commit 77d0c47 into trunk Sep 30, 2026
80 checks passed
@retrofox
retrofox deleted the update/pa-extensibility-videopress-former-names branch September 30, 2026 06:35
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] Needs Review This PR is ready for review. labels Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR Docs Enhancement Changes to an existing feature — removing, adding, or changing parts of it [Package] Premium Analytics [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.

2 participants