Skip to content

Debug Helper: Add a Package Provenance module - #50995

Merged
retrofox merged 13 commits into
trunkfrom
update/debug-helper-package-provenance
Aug 6, 2026
Merged

retrofox merged 13 commits into
trunkfrom
update/debug-helper-package-provenance

Conversation

@retrofox

@retrofox retrofox commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Proposed changes

  • New Debug Helper module, Package Provenance: an admin-bar badge showing the WordPress core and Gutenberg plugin versions, plus a floating, filterable panel listing every registered wp-* classic script handle and @-prefixed script module on the current screen.
  • Each row shows the package name, type (classic script or script module), the runtime that serves it (WordPress core, the Gutenberg plugin, Jetpack's wp-build polyfills, or another plugin), and its registered version. Dimmed rows are registered but not loaded on that screen.
  • For classic scripts the data is what WordPress itself resolved. The panel prints late on admin_print_footer_scripts, so wp_scripts()->done is complete and a script_loader_src filter has already recorded the URL each handle was served from. That is what the provider badge classifies, so a CDN or cache-busting rewrite shows up instead of staying hidden, and handles core folds into load-scripts.php are still reported as loaded even though they print no per-handle tag. Script modules come from the browser's live import map.
  • Packages compiled inline into an app bundle are invisible at runtime by design; the panel states this limitation instead of guessing.
  • A ? help panel documents how the provider is decided: registration order, the polyfills' force-replace rules, the path-to-badge table, and what a dimmed row or a missing version means. That logic previously only existed in the code.

Related product discussion/links

  • Born while manually testing Update Bundled @wordpress/* monorepo #50509, where which runtime provides wp-theme / wp-private-apis / wp-rich-text / @wordpress/widget-primitives on each WordPress + Gutenberg combination decides whether the dashboards work. This module replaces a console snippet with a one-click view.

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

No. The module renders locally on the admin screen and sends nothing anywhere.

Testing instructions

  • Activate the Jetpack Debug Tools plugin (wp plugin activate debug-helper in the Docker environment).
  • Go to Jetpack Debug in the admin menu and enable the Package Provenance module, or run wp option update jetpack_debug_helper_active_modules --format=json '["package-provenance"]'.
  • Visit any wp-admin screen. A 📦 WP <version> · GB <version|off> badge appears on the right side of the admin bar.
  • Click the badge: the floating panel opens with the packages table. Try the filter box (e.g. type theme or polyfill).
  • For the most interesting output, open a screen that boots a wp-build dashboard (Jetpack → Forms, or Premium Analytics when enabled): the contested handles (wp-theme, wp-private-apis, wp-rich-text) and the script modules should appear with a POLYFILL or CORE provider depending on your WordPress version, and with GUTENBERG when the Gutenberg plugin is active.
  • Verify rows not printed on the screen render dimmed, and that the panel works with the Gutenberg plugin active and inactive.
  • Open the ? help panel and confirm it fills the panel and scrolls on its own.
  • Script concatenation needs a site where SCRIPT_DEBUG is false, so the Docker environment does not cover it (tools/docker/bin/run.sh sets it to true). On a stock install or a Jurassic Ninja site, filter for wp-hooks or wp-dom-ready: core folds them into load-scripts.php with no per-handle tag, and they must render normally rather than dimmed. On the same site, handles core registers with false as the version (wp-util, wp-backbone, wp-api-request, wp-theme-plugin-editor) must show the WordPress version rather than an empty cell.

admin-bar badge + floating panel showing which runtime serves each WordPress package (core, Gutenberg plugin, wp-build polyfills, app)
@retrofox retrofox added [Status] Needs Review This PR is ready for review. [Plugin] Debug Helper Debug Tools plugin labels Aug 3, 2026
@retrofox retrofox self-assigned this Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

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!


Debug Helper plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

dim rows by text color instead of opacity so provider badges stay legible; brighten the badge palette one step
@retrofox
retrofox requested review from a team August 3, 2026 13:00
paint after window load and recompute on open (footer scripts print after admin_footer, so early DOM checks under-reported loaded state); classify /wp-admin/ and mu-plugins sources; list all registered module ids, not only @-prefixed; escape names and versions; admin-only badge; precise dimmed semantics per type
each ver opens the exact URL the runtime serves in a new tab; rows without a version get a glyph link

@dhasilva dhasilva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice module — self-contained, opt-in, and it follows the existing debug-helper conventions closely (same hook pair and priorities as WPCOM_API_Request_Tracker_Module, @phan-constructor-used-for-side-effects, kebab-case file/class naming, logical CSS properties throughout). phpcs, parallel-lint, and phan on projects/plugins/debug-helper are all clean for the new file, and .phan/baseline.php is untouched. Changelog is present and valid.

No blockers. Three accuracy findings, all sharing one root cause: the panel re-derives what WordPress already resolved at print time, rather than reusing the resolved value.


1. [suggestion] Version column is blank exactly where WP substitutes the WP version

(string) $dependency->ver collapses false and null to '', but core treats them oppositely (wp-includes/class-wp-scripts.php:299-303):

if ( null === $obj->ver ) { $ver = ''; }                        // no ?ver= at all
else { $ver = $obj->ver ? $obj->ver : $this->default_version; } // false → WP version

Core registers a fair number of wp-* handles with false — wp-util, wp-backbone, wp-sanitize, wp-lists, wp-pointer, wp-api-request, wp-color-picker (script-loader.php:784, 841, 853, 875, 1062, 1073, 1496). Those rows render an empty version cell and a link with no ver param, while the served file is …?ver=<wp_version>.

Script modules have the identical rule in WP_Script_Modules::get_src() (class-wp-script-modules.php:790-794), so

'ver' => (string) ( $module['version'] ?? '' ),
is affected the same way.

'ver' => null === $dependency->ver
	? ''
	: (string) ( $dependency->ver ? $dependency->ver : wp_scripts()->default_version ),

2. [suggestion] The version link bypasses WP_Scripts::$base_url and the script_loader_src filter

const url = info.src
? info.src + ( info.ver ? ( info.src.includes( '?' ) ? '&' : '?' ) + 'ver=' + encodeURIComponent( info.ver ) : '' )
: '';

Core registers packages with root-relative srcs (/wp-includes/js/dist/…) and WP_Scripts::do_item() prepends $this->base_url (= site_url()) at print time (class-wp-scripts.php:411-413), then runs the result through script_loader_src. The panel links to the unresolved path, so on a subdirectory install the link 404s, and on any site using script_loader_src (CDN rewrites, cache busting) both the link and the provider() classification describe a URL the browser never fetched.

Cheapest fix reuses work compute() already does — when the printed tag exists, read its real URL:

const el = document.getElementById( handle + '-js' );
const url = el ? el.src : ( info.src ? info.src + … : '' ); // reconstruct only as fallback

3. [suggestion] Concatenated core scripts render dimmed even though they loaded

loaded: !! document.getElementById( handle + '-js' ) || info.printed,

In wp-admin without SCRIPT_DEBUG, $concatenate_scripts defaults to true, and any handle under /wp-includes/js/ or /wp-admin/js/ with no textdomain, no inline before/after, and no delayed strategy is folded into a single load-scripts.php?load[]=… tag with no per-handle id (class-wp-scripts.php:365-390). Core packages that don't depend on wp-i18n (wp-hooks, wp-dom-ready, wp-priority-queue, …) take that path — and since core registers packages in the footer, the info.printed fallback is also false at admin_footer time. Net effect: loaded packages show dimmed.

tools/docker/bin/run.sh:45 sets SCRIPT_DEBUG true, which masks this in the monorepo Docker env; a stock install or a JN site will show it.

Either fix works:

  • Move render() to admin_print_footer_scripts at a late priority (after _wp_footer_scripts, wp-admin/includes/admin-filters.php:61). wp_scripts()->done is then authoritative and the import map is already in the DOM, which demotes the client-side probing to a pure fallback — and it also gives you finding 2 for free if you additionally record real URLs via a late script_loader_src filter.
  • Or parse load[]= handles out of any load-scripts.php tag inside compute().

Minor notes (non-blocking)

  • The Fixes # line at the top of the description is left dangling with no issue number — worth filling or dropping.
  • On fullscreen block-editor screens the admin bar is visually hidden, so the badge exists in the DOM but can't be clicked and the panel is unreachable — on the screen with the richest wp-* surface. Not a regression, just a gap worth knowing about.
  • role="dialog" with no focus management, Escape-to-close, or close button. Fine for a dev tool; flagging only for completeness.

Checked: PR description, changelog, conventions, bugs, security (JSON island escaping and the esc/escAttr helpers look correct), error handling, backward compat, cross-package version skew (none — debug-helper has "require": {} and the module references no \Automattic\Jetpack\* symbols), phan suppressions, feature gating, a11y/RTL, PHP/WP compat.

Generated by Claude.

@retrofox

retrofox commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

All three fixed. Thanks for the detail, especially the concatenation one. I would not have caught it in Docker.

1. Blank versions. New resolve_version() follows core's rule: null prints no version, any other falsy value substitutes the WordPress one. Applied to classic scripts and modules. For modules I used array_key_exists(), because ?? '' collapsed the missing key with the false that needs telling apart.

2. Unresolved URLs. A script_loader_src filter at PHP_INT_MAX records the final URL of every printed handle, with absolute_src() as fallback for handles registered but never printed. The panel now classifies by that URL instead of the registered src, so a CDN rewrite shows up rather than staying hidden.

3. Dimmed concatenated rows. render() moved from admin_footer to admin_print_footer_scripts at PHP_INT_MAX, after _wp_footer_scripts. wp_scripts()->done is complete by then, and handles core folds into load-scripts.php do land there even though they print no tag. The DOM probe is now a fallback.

Verified on a Jurassic Ninja site with SCRIPT_DEBUG false, the exact condition that triggers it. wp-theme-plugin-editor shows 6.9 instead of blank, since core registers it with false, and concatenated packages stopped rendering dimmed.

I also added a help screen to the panel: registration order, the force-replace rules, and the path-to-badge table. That logic only lived in the code.

Dropped the dangling Fixes #. The hidden admin bar on fullscreen editor screens is real, noting it for a follow-up.

@chihsuan chihsuan 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.

Tested this locally and it works exactly as described!

Lovely little plugin addition. LGTM 🚀

@retrofox
retrofox merged commit 1a0bbe3 into trunk Aug 6, 2026
70 checks passed
@retrofox
retrofox deleted the update/debug-helper-package-provenance branch August 6, 2026 09:05
@github-actions github-actions Bot removed the [Status] Needs Review This PR is ready for review. label Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Plugin] Debug Helper Debug Tools plugin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants