| 1 |
# Dashboard widget types |
| 2 |
|
| 3 |
How a widget type of the Premium Analytics dashboard is registered, filtered, served to the client, and imported. A widget type is a namespaced name plus the script modules and the metadata the client needs to offer it in the picker and render it in a section's layout. |
| 4 |
|
| 5 |
Widget types are registered on the server by whoever owns them: the package for the widgets in its build, another plugin for its own. One registry holds them all, and the client offers whatever the server publishes. |
| 6 |
|
| 7 |
This page covers the registration path. Sections, which place widget instances in default layouts, have their own page, [](dashboard-sections.mdDashboard sections](dashboard-sections.md](dashboard-sections.md). |
| 8 |
|
| 9 |
## Vocabulary |
| 10 |
|
| 11 |
| Term | Meaning | Example | |
| 12 |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | |
| 13 |
| Widget type name | Namespaced identifier, `<namespace>/<name>`, lowercase. The namespace names the owner. | `jpa/clicks`, `wordads/highlights` | |
| 14 |
| Render module, widget module | The script-module ids of the widget's render entry and metadata entry, which the client `import()`s through the page import map. | `jetpack-premium-analytics/widgets/clicks/render` | |
| 15 |
| Manifest | The widgets wp-build discovered under `widgets/`, generated into `build/widgets.php` and read through `jpa_get_registered_widget_modules()`. | see `Analytics::widget_manifest_path()` | |
| 16 |
| Candidate | A manifest entry before the registry-time filter. A dropped candidate never registers. | `jetpack_premium_analytics_registrable_widget_types` | |
| 17 |
| Metadata | `presentation`, `category`, `title`, `description`, `help`, `icon`, `actions`, `keywords`: what the picker shows, translated and sanitized on the way into the registry. | see `Widget_Type` | |
| 18 |
| Catalog location | `textdomain` and `i18n_manifest`: the text domain the widget's bundles are stamped with, and the i18n manifest of the build that serves them. | see `Widget_Type` | |
| 19 |
| SDK | `@automattic/jetpack-premium-analytics-sdk`: the one name a plugin's widgets import the dashboard through. | `projects/js-packages/premium-analytics-sdk` | |
| 20 |
|
| 21 |
## Files |
| 22 |
|
| 23 |
| File | Role | |
| 24 |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 25 |
| `src/class-widget-type.php` | The widget type model: the name, the two module ids, the metadata fields, the catalog location, `is_renderable()`. | |
| 26 |
| `src/class-widget-type-registry.php` | The registry: `register()` with its validations, the reads, and the lazy hydration that fires the registration action. | |
| 27 |
| `src/widget-types.php` | The widget type API: `register_widget_types_from_manifest()`, `register_widget_type()`, `WIDGET_API_VERSION`, the package's own registrant, the metadata translation and sanitizers, the two availability filters, `get_available_widget_types()`. | |
| 28 |
| `src/widget-availability.php` | The package's policy on manifest candidates: environment, plugins present, capabilities. | |
| 29 |
| `src/widget-type-support.php` | The package's policy on default layouts: which types a site cannot serve. | |
| 30 |
| `src/widget-modules.php` | The two readers: `ensure_widget_registry_ready()`, the `/wpcom/v2/widget-modules` route, and the import-map entries on the page boot dependencies. | |
| 31 |
| `src/sdk-module.php` | Registers the facade module built from `packages/sdk` a second time, as `@automattic/jetpack-premium-analytics-sdk`. | |
| 32 |
| `packages/sdk/` | The SDK facade: re-exports of the toolkit, data, fields, dates and shared primitives a widget imports, built as the `@jetpack-premium-analytics/sdk` module. | |
| 33 |
| `build/widgets.php` | Generated by wp-build: the manifest, and `jpa_register_widget_modules()`, which registers the package's script modules. | |
| 34 |
| `routes/use-widget-modules.ts` | The client's read of the records, as a core-data entity. | |
| 35 |
| `routes/widget-module-i18n.ts` | The client resolver: loads a widget bundle's translation catalog from the domain and manifest its record declares, then imports the module. | |
| 36 |
|
| 37 |
## When the widget type files load |
| 38 |
|
| 39 |
`ensure_widget_registry_ready()` runs ahead of the two reads, and only then. On Simple the REST registration runs on every public-api request, and most never read the registry. |
| 40 |
|
| 41 |
It requires `widget-types.php` and `widget-availability.php`, then the manifest. When the registry hydrates, the package's registrant finds `jpa_get_registered_widget_modules()` and the registry-time filter is hooked. |
| 42 |
|
| 43 |
The two reads are `get_widget_modules_response()`, the REST route the client fetches, and `add_widget_modules_to_boot_deps()`, the callback on `jetpack-premium-analytics-wp-admin_boot_dependencies` that puts each module id in the page import map. Both happen after `init`. On WordPress.com Simple the route runs from public-api, through `Dashboard_Support_Routes::register()`. |
| 44 |
|
| 45 |
That is why the registration moment is not `init`: the registry hydrates on its first read, whichever reader gets there first, and fires one action then. |
| 46 |
|
| 47 |
A read before `init` is a `_doing_it_wrong()`. It answers only what was registered directly and does not latch, so the registrants hooked later still run on the first read after `init`. |
| 48 |
|
| 49 |
## Registering a widget type |
| 50 |
|
| 51 |
 |
| 52 |
|
| 53 |
### The package's own widgets |
| 54 |
|
| 55 |
`register_widget_types()` in `src/widget-types.php` registers them from a callback on the action at priority 10, into the registry the action hands over. |
| 56 |
|
| 57 |
Each manifest candidate that survives `jetpack_premium_analytics_registrable_widget_types` is translated by `translate_widget_metadata()`, sanitized (`help`, `icon`, `actions`) and registered. A name already registered is skipped. |
| 58 |
|
| 59 |
### A plugin's widgets |
| 60 |
|
| 61 |
A plugin with a `widgets/` folder of its own, built with wp-build, registers the whole manifest its build generates from a callback on `jetpack_premium_analytics_register_widget_types`. This is the shape of the VideoPress registrant, `Analytics_Dashboard::register_widget_types()` in `projects/packages/videopress/src/class-analytics-dashboard.php`: |
| 62 |
|
| 63 |
```php |
| 64 |
use function Automattic\Jetpack\PremiumAnalytics\register_widget_types_from_manifest; |
| 65 |
|
| 66 |
add_action( |
| 67 |
'jetpack_premium_analytics_register_widget_types', |
| 68 |
static function ( $registry ) { |
| 69 |
if ( ! defined( 'Automattic\\Jetpack\\PremiumAnalytics\\WIDGET_API_VERSION' ) ) { |
| 70 |
return; |
| 71 |
} |
| 72 |
$version = \Automattic\Jetpack\PremiumAnalytics\WIDGET_API_VERSION; |
| 73 |
if ( version_compare( $version, '1.3.0', '<' ) || version_compare( $version, '2', '>=' ) ) { |
| 74 |
return; |
| 75 |
} |
| 76 |
|
| 77 |
require_once __DIR__ . '/build/build.php'; |
| 78 |
|
| 79 |
register_widget_types_from_manifest( |
| 80 |
jetpack_videopress_get_registered_widget_modules(), |
| 81 |
array( |
| 82 |
'textdomain' => 'jetpack-videopress-pkg', |
| 83 |
'i18n_manifest' => plugins_url( 'i18n-manifest.json', __DIR__ . '/build/build.php' ), |
| 84 |
'former_names' => array( 'videopress/top-videos' => array( 'jpa/videopress' ) ), |
| 85 |
), |
| 86 |
$registry |
| 87 |
); |
| 88 |
}, |
| 89 |
20 |
| 90 |
); |
| 91 |
``` |
| 92 |
|
| 93 |
The version range is the consumer's own: the lowest contract its widgets import from, below the next major. An undefined version is a request that never loaded `widget-types.php`, such as the sections REST route, and the registrant waits for one that does. |
| 94 |
|
| 95 |
The `require_once` of the generated `build/build.php` is what registers the plugin's widget script modules. The generated `build/widgets.php` hooks that on `init` by itself, or runs at once when `init` has fired, so a plugin that pays the registration only on sites that qualify requires it from inside the callback. |
| 96 |
|
| 97 |
`jetpack_videopress_get_registered_widget_modules()` is the manifest accessor wp-build generates from `wpPlugin.name`. Guard it with `function_exists()` where the build can be absent, and hand the helper an empty manifest then: it registers nothing, and a test can still feed candidates through `jetpack_premium_analytics_registrable_widget_types`. A plugin needs no manifest hook of its own. |
| 98 |
|
| 99 |
`i18n_manifest` is the URL of the build's `i18n-manifest.json`, which `stamp-textdomains` writes next to the bundles. The real code appends the package version to it as a cache buster. |
| 100 |
|
| 101 |
`former_names` maps a current name to the names it registered under before (see [](#renaming-a-widget-typeRenaming a widget type](#renaming-a-widget-type](#renaming-a-widget-type)). |
| 102 |
|
| 103 |
A single type written by hand goes through `register_widget_type( $name, $args )`, the primitive the helper is built on. |
| 104 |
|
| 105 |
### The contract |
| 106 |
|
| 107 |
The action hands over the `Widget_Type_Registry` being hydrated. `register_widget_types_from_manifest()` and `register_widget_type()` are what a plugin calls. The helper takes `$registry` as its third argument and defaults to the main instance, which is that same registry in production; `register_widget_type()` always writes to the main instance. |
| 108 |
|
| 109 |
`$registry` also serves lookups, `is_registered()` and `get_all_registered()`. The package's own registrant and the Ads one pass it through the helper, so a test can hydrate a fresh instance. |
| 110 |
|
| 111 |
### What the manifest helper does |
| 112 |
|
| 113 |
It runs the candidates through `jetpack_premium_analytics_registrable_widget_types`, gives each one without a `textdomain` or an `i18n_manifest` the ones passed in `$args`, translates the metadata with `translate_widget_metadata()`, sanitizes `help`, `icon` and `actions`, and skips a name already registered. |
| 114 |
|
| 115 |
The package's own `register_widget_types()` is its first caller, with its text domain and no manifest: the page's boot init module loads the package's catalogs. |
| 116 |
|
| 117 |
### Loading |
| 118 |
|
| 119 |
The files are required by `ensure_widget_registry_ready()`, not autoloaded. The action fires from the registry those files load, so a callback on it runs only once the API is there and needs no `function_exists()` guard. |
| 120 |
|
| 121 |
### Validation in `register()` |
| 122 |
|
| 123 |
The name must be a lowercase `<namespace>/<name>` string, and must not be registered. Each failure is a `_doing_it_wrong()` and a `false` return. |
| 124 |
|
| 125 |
### Arguments |
| 126 |
|
| 127 |
Any public property of `Widget_Type`: `render_module`, `widget_module`, `presentation` (`framed`, `content-bleed` or `full-bleed`), `category`, `title`, `description`, `help` (`content` plus optional `links`), `icon` (`collection/name`), `actions`, `keywords`, `textdomain`, `i18n_manifest`, `former_names`. `set_props()` copies every key onto the instance. |
| 128 |
|
| 129 |
Through `register_widget_type()` the strings arrive translated and `help`, `icon` and `actions` in shape. The manifest helper translates and sanitizes them itself. |
| 130 |
|
| 131 |
### Version |
| 132 |
|
| 133 |
`WIDGET_API_VERSION` names the contract a widget is built against (see [](#versioning-the-contractVersioning the contract](#versioning-the-contract](#versioning-the-contract)). A consumer compares it in the callback: it skips registration when the major differs, and waits while the minor is below the one its imports need. |
| 134 |
|
| 135 |
### Hydration and order |
| 136 |
|
| 137 |
`Widget_Type_Registry` fires the action from `ensure_hydrated()`, which `get_registered()` and `get_all_registered()` call on their first read after `init`. The latch is set before the action fires, so a callback that reads the registry does not re-enter it. |
| 138 |
|
| 139 |
`is_registered()` does not hydrate: `register()` relies on it, and a registrant may run before the action. |
| 140 |
|
| 141 |
The package's own widget types register at priority 10. A plugin that wants to see them registered first hooks later. |
| 142 |
|
| 143 |
### Renaming a widget type |
| 144 |
|
| 145 |
A type that changes its name declares the old one in `former_names`: 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()`. Moving a widget to its owner's package is such a rename, `jpa/x` to `owner/x`. Declaring former names needs `WIDGET_API_VERSION` 1.1.0. |
| 146 |
|
| 147 |
The registry maps each former name to the current one. `get_registered()` answers for both, `resolve_name()` gives the current name, and `register()` refuses a former name a registered type or another type's former name holds, and a name that is someone's former name. |
| 148 |
|
| 149 |
The REST record publishes `former_names`. The dashboard stage maps a stored layout's types through them before rendering (`buildWidgetTypeRenames()`, `useDashboardSectionLayout()`), with no write-back: the current name persists with the section's next commit. On the server, `resolve_former_widget_types_in_default_layout()` renames default-layout instances at priority 99, ahead of the unregistered-type check, so a plugin that still adds an instance under the old name keeps it. |
| 150 |
|
| 151 |
A rename keeps the attributes as they are. One that changes them needs a migration, which nothing offers yet. |
| 152 |
|
| 153 |
### Script modules |
| 154 |
|
| 155 |
The module ids are what the client hands to `import()`. The package's are registered by the generated `jpa_register_widget_modules()`; a plugin's by the same generated file of its own build. |
| 156 |
|
| 157 |
### The SDK a plugin imports |
| 158 |
|
| 159 |
A plugin's widgets import the dashboard through `@automattic/jetpack-premium-analytics-sdk` (`projects/js-packages/premium-analytics-sdk`), a types-only package that declares itself a script module (`wpScriptModuleExports`). |
| 160 |
|
| 161 |
wp-build finds it installed under that name, leaves the import external (`wpPlugin.externalNamespaces` lists the `automattic` scope) and records it as a module dependency of the widget. |
| 162 |
|
| 163 |
`src/sdk-module.php` registers the facade built from `packages/sdk` under that same name on `wp_default_scripts`. The page import map resolves the SDK to the facade and the facade to the dashboard's own modules: one React, one toolkit, one query client for the dashboard and every widget on the page. |
| 164 |
|
| 165 |
### Translations on the client |
| 166 |
|
| 167 |
Every record says where its bundles' catalogs live. `routes/widget-module-i18n.ts` derives the bundle path from the module id, `{handle-prefix}/widgets/{dir}/{render,widget}` to `build/widgets/{dir}/{render,widget}.js`, whatever the prefix. |
| 168 |
|
| 169 |
`createWidgetModuleResolver()` caches the record's manifest by URL once (`loadI18nManifest()` in wp-build-polyfills) and loads the bundle's catalog under the record's `textdomain` before importing. The metadata bundles are preloaded the same way. A record without a text domain is treated as the package's own. |
| 170 |
|
| 171 |
What the catalog load hashes is the bundle path relative to the plugin, the way WordPress names JS translation files. A package vendored inside a plugin therefore needs its text domain aliased in the plugin's `i18n-map.php`, as this package's is. |
| 172 |
|
| 173 |
## From the registry to the client |
| 174 |
|
| 175 |
### One policy for both readers |
| 176 |
|
| 177 |
On the server, `get_available_widget_types()` runs the registered map through `jetpack_premium_analytics_widget_types`, the runtime filter. Both readers use it, so the REST list and the import map share one policy. |
| 178 |
|
| 179 |
### The REST record |
| 180 |
|
| 181 |
`GET /wpcom/v2/widget-modules` returns one record per available type: `name`, `render_module`, `widget_module`, the metadata fields, `textdomain`, `i18n_manifest` and `former_names`. |
| 182 |
|
| 183 |
It is gated on `Capabilities::current_user_can_view_analytics()`, the dashboard's own gate. The `wpcom/v2` namespace is what lets WordPress.com expose it through public-api for Simple sites. |
| 184 |
|
| 185 |
### The import map |
| 186 |
|
| 187 |
`add_widget_modules_to_boot_deps()` adds each `render_module` and `widget_module` as a dynamic dependency of the page, which the generated page loader turns into import-map entries. |
| 188 |
|
| 189 |
A type whose module id no script module claims imports nothing, and an instance of it renders as "Widget is no longer available". |
| 190 |
|
| 191 |
### The client |
| 192 |
|
| 193 |
`useWidgetModules()` reads the records as a core-data entity, and `useWidgetTypesWithI18n()` resolves the ones the active layout renders. |
| 194 |
|
| 195 |
The widget dashboard offers the types in the picker and imports an instance's render module through the resolver `useWidgetModuleResolver()` builds from the records. |
| 196 |
|
| 197 |
## Availability |
| 198 |
|
| 199 |
Two filters, both problem-agnostic, plus a policy on default layouts. |
| 200 |
|
| 201 |
### Registry-time filter |
| 202 |
|
| 203 |
`jetpack_premium_analytics_registrable_widget_types` runs over the manifest candidates in `register_widget_types()`. A dropped candidate never registers: gone from the REST list, the import map and every registry reader. For hard availability. |
| 204 |
|
| 205 |
The package's own policy hooks it, in `src/widget-availability.php`: developer-only widgets off production, the store and bookings categories without WooCommerce or Bookings, the store report categories without the capability. |
| 206 |
|
| 207 |
A plugin's manifest goes through the same filter when it registers through `register_widget_types_from_manifest()`. A type registered one by one with `register_widget_type()` does not. Either way, a plugin decides in its callback whether to register at all, as the section owners do. |
| 208 |
|
| 209 |
### Runtime filter |
| 210 |
|
| 211 |
`jetpack_premium_analytics_widget_types` runs over the registered map on every read of `get_available_widget_types()`. The type stays registered. For request-dependent or soft state, e.g. a type shown locked. |
| 212 |
|
| 213 |
### Default layouts |
| 214 |
|
| 215 |
A third policy acts on default layouts rather than on the registry: `remove_unsupported_default_layout_items()` in `src/dashboard-layout.php`, over the type lists of `src/widget-type-support.php`, drops from a section's default the instances whose type the site cannot serve. |
| 216 |
|
| 217 |
It reads the package's fixed list and, once the registry can answer, the registry itself: an instance whose type is not registered is dropped too (see [](dashboard-sections.md#default-layoutsDefault layouts](dashboard-sections.md#default-layouts](dashboard-sections.md#default-layouts)). |
| 218 |
|
| 219 |
## Versioning the contract |
| 220 |
|
| 221 |
`WIDGET_API_VERSION` names the contract a widget is built against: the `@automattic/jetpack-premium-analytics-sdk` module and the exports it declares, the dashboard modules the facade re-exports from (`@jetpack-premium-analytics/widgets-toolkit`, `data`, `fields`, `datetime`, `externals`), and the `Widget_Type` fields the client reads. |
| 222 |
|
| 223 |
The major changes when a widget built against the previous contract stops working; the minor when a consumer can rely on something new. So far, 1.1.0 added `former_names` and 1.2.0 `Leaderboard`, `describeError()` and `useStatsVideoPlays`, and 1.3.0 `ExporterCsvDownloadButton`, which takes the linked report by id. |
| 224 |
|
| 225 |
Inside `plugins/jetpack` the package and a consumer module ship together, so the check is a formality. With the standalone `plugins/premium-analytics` next to another plugin, each brings its own copy, and the check is what keeps a widget built against 1.x from registering on a 2.x package. |
| 226 |
|
| 227 |
## Two real consumers |
| 228 |
|
| 229 |
The Ads package owns a section of its own with three widgets. The VideoPress package owns one widget inside a section this package bundles. Between them they answer most of the questions a third consumer will have. |
| 230 |
|
| 231 |
### Where the widgets live |
| 232 |
|
| 233 |
The three Ads widgets live in `projects/packages/ads`, a widgets-only wp-build project. `wpPlugin.name` is `jetpack_ads` and the handle prefix `jetpack-ads`, so the module ids are `jetpack-ads/widgets/<dir>/render` and `…/widget`. |
| 234 |
|
| 235 |
The Top videos widget lives in `projects/packages/videopress/widgets/top-videos/`, next to the routes that package already built with wp-build. The same `wpPlugin.name`, `jetpack_videopress`, gives the module ids `jetpack-videopress/widgets/top-videos/render` and `…/widget`. A package that already builds with wp-build adds a `widgets/` folder and nothing else to its build. |
| 236 |
|
| 237 |
Neither package points at this one with a `../` path or a workspace alias. Ads requires this package with Composer, since it registers against its API. VideoPress lists it as a `require-dev` only: the registration action fires when the dashboard is loaded and never otherwise, and two core packages depend on VideoPress. |
| 238 |
|
| 239 |
### One import: the SDK |
| 240 |
|
| 241 |
The widgets import the dashboard by one name, `@automattic/jetpack-premium-analytics-sdk`. Each package depends on it with `workspace:*` and lists the `automattic` scope in `wpPlugin.externalNamespaces`. |
| 242 |
|
| 243 |
wp-build keeps a specifier external only when it finds that package installed under the specifier and declaring `wpScriptModuleExports`. The SDK package does, so the import stays external, the way `@wordpress/*` does. |
| 244 |
|
| 245 |
The SDK package (`projects/js-packages/premium-analytics-sdk`) holds the contract only, the types of what a widget can import. This package provides the implementation: `packages/sdk`, a facade over the toolkit, data, fields, dates and shared primitives, built as `@jetpack-premium-analytics/sdk` and registered a second time under the SDK's name by `src/sdk-module.php`. A widget therefore runs on the module instances the dashboard renders with. |
| 246 |
|
| 247 |
The SDK exposes widget kinds and host capabilities, never the parts a kind is built from, and no dashboard policy value. Top videos renders one component, `Leaderboard`, hands it rows and a request status, and reads its data through `useStatsVideoPlays`, a provisional entry that leaves the SDK when the package owns its data. How many rows it asks for is its own constant; how many the dashboard shows is `Leaderboard`'s. |
| 248 |
|
| 249 |
### What each package registers |
| 250 |
|
| 251 |
`Analytics_Dashboard::init()` hooks the registrants at priority 20 in both packages. |
| 252 |
|
| 253 |
Ads: `register_section()` registers `wordads/ads` with its layout of `wordads/chart-tabs`, `wordads/highlights` and `wordads/earnings-history`. It skips when the `ads` slug is taken, or when `WIDGET_API_VERSION` moved to a major the package was not built against. An undefined version is not a mismatch: the sections REST route hydrates the section registry before the dashboard loads `widget-types.php`. `register_widget_types()` waits for that version, requires the generated `build/build.php` from inside the callback, and hands the manifest to `register_widget_types_from_manifest()` with the text domain `jetpack-ads-pkg` and the URL of its `i18n-manifest.json`. |
| 254 |
|
| 255 |
VideoPress: `register_widget_types()` registers `videopress/top-videos` the same way, with `jpa/videopress` as its former name, and waits for 1.3.0, the contract the widget imports from. `add_default_layout_instance()` seeds the widget into the Traffic section's default layout at priority 10 on `jetpack_premium_analytics_dashboard_default_layout`, where this package used to seed it: order 8, one column by two rows, the same instance uuid. It leaves a layout alone that already holds the instance, by uuid or by type, current or former. It does not wait for the contract version: the dashboard's own policy drops the instance on a site where the type never registers (see [](dashboard-sections.md#default-layoutsDefault layouts](dashboard-sections.md#default-layouts](dashboard-sections.md#default-layouts)). |
| 256 |
|
| 257 |
### Who calls it |
| 258 |
|
| 259 |
Ads is the section's story, in [](dashboard-sections.md#a-real-consumer-the-ads-sectionDashboard sections](dashboard-sections.md#a-real-consumer-the-ads-section](dashboard-sections.md#a-real-consumer-the-ads-section): the WordAds module outside the WordPress.com platform, `jetpack-mu-wpcom` on Simple and Atomic where the plan includes WordAds and the site has it on. |
| 260 |
|
| 261 |
VideoPress follows `Status::is_active()`: `Initializer::active_initialization()` calls `init()` where the VideoPress module of the Jetpack plugin or the standalone plugin is active, and `init()` returns early on the WordPress.com platform. `jetpack-mu-wpcom` calls the two registrants from `src/features/premium-analytics/videopress-widgets.php` on Simple and Atomic where `wpcom_site_has_feature( 'videopress' )` holds. Both run against the copy the Jetpack plugin bundles, so Simple, which runs no Jetpack module, serves the bundles from it too. |
| 262 |
|
| 263 |
### Layouts saved before the move |
| 264 |
|
| 265 |
The Ads types were `jpa/wordads-*` while they lived here, and the package declares no former names: a layout persisted with those names renders its tiles as unavailable until it is reset. |
| 266 |
|
| 267 |
Top videos moved with `jpa/videopress` declared as a former name. A stored layout that still carries it renders Top videos, and a default layout seeded under the old name is renamed on the server before the unregistered-type check runs. |
| 268 |
|
| 269 |
### What leaves this package |
| 270 |
|
| 271 |
The widget folder, its default instance in Traffic, its entry in `VIDEOPRESS_WIDGET_TYPES`, and the row helpers only it used. `src/videopress-availability.php` stays: the Videos report and the video detail route still gate on it, and whether the widget appears is the VideoPress package's call now. |
| 272 |
|
| 273 |
## Where the tests are |
| 274 |
|
| 275 |
| Behaviour | Test | |
| 276 |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | |
| 277 |
| Hydration and the action, `register_widget_type()`, `register_widget_types_from_manifest()`, `WIDGET_API_VERSION`, a plugin's type reaching both readers | `tests/php/Widget_Type_Registry_Test.php` | |
| 278 |
| Metadata translation and sanitizing, the REST record | `tests/php/Widget_Metadata_Test.php` | |
| 279 |
| The route's namespace and gate, hydration from the route | `tests/php/Widget_Modules_Test.php`, `tests/php/Analytics_Test.php` | |
| 280 |
| The package's candidate policy and the runtime filter | `tests/php/Widget_Availability_Test.php` | |
| 281 |
| The client's records read | `routes/use-widget-modules.test.ts` | |
| 282 |
| The client's catalog loads per record, the resolver, the metadata preload | `tests/js/widget-module-i18n.test.tsx`, `wp-build-polyfills/tests/js/load-i18n-catalogs.test.js` | |
| 283 |
| The SDK registration: the facade's id, bundle, dependencies and version | `tests/php/Sdk_Module_Test.php` | |
| 284 |
| The Ads package's registrants, with and without the widget contract loaded, and the module and mu-wpcom callers | `packages/ads/tests/php/Analytics_Dashboard_Test.php`, `packages/ads/tests/php/Analytics_Dashboard_Without_Widget_Types_Test.php`, `plugins/jetpack/tests/php/modules/wordads/WordAds_Premium_Analytics_Test.php`, `packages/jetpack-mu-wpcom/tests/php/features/premium-analytics/Wordads_Section_Test.php` | |
| 285 |
| The VideoPress package's registrant and layout seed, with and without the widget contract loaded, and the mu-wpcom caller; the fixture manifest enters through `jetpack_premium_analytics_registrable_widget_types` | `packages/videopress/tests/php/Analytics_Dashboard_Test.php`, `packages/videopress/tests/php/Analytics_Dashboard_Without_Widget_Types_Test.php`, `packages/jetpack-mu-wpcom/tests/php/features/premium-analytics/Videopress_Widgets_Test.php` | |
| 286 |
| A consumer widget's render, against a stand-in for the SDK module: the rows, the status, the error copy and the footer it hands the dashboard's components | `packages/videopress/widgets/top-videos/test/render.test.tsx` | |
| 287 |
|
| 288 |
## Not covered here |
| 289 |
|
| 290 |
- Stories for a plugin's widgets, and a jest harness that renders the real SDK: the Ads and VideoPress widgets left this package's Storybook with their move. The SDK module has no implementation outside the dashboard, so a consumer's suite stands in for it and asserts on what the widget hands over, as the Top videos suite does. |
| 291 |
- Precise types for the SDK: `projects/js-packages/premium-analytics-sdk` declares its exports loosely until a build step emits them from the facade. |
| 292 |
- A migration for a rename that also changes attributes, the `deprecated` equivalent of blocks, and the upstream ask: `@wordpress/widget-primitives` knows neither former names nor deprecations. |
| 293 |
- The metadata strings of `widget.json`, which reach no catalog in any package until the strings stub ships. |
| 294 |
|