PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.2
1.4.2 1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 36 releases
← All changes | includes/modules/Score/ScoreModule.php +49 -19 1.3.3 → 1.4.2 View file →
@@ -60,10 +60,11 @@
60 60 // Provider-neutral: the panel runs whichever provider the site has
61 61 // configured (PageSpeed Insights by default, no API key needed).
62 62 // The Hub-run test has its own copy and is gated behind
63 63 // hub_speed_test_enabled(), so this line must not promise it.
64 - 'description' => __( 'Run a PageSpeed Insights or GTmetrix audit from the dashboard and keep the history next to your TTFB benchmark.', 'xspeed' ),
64 + 'description' => __( 'Run a PageSpeed Insights or GTmetrix test and keep a history of the scores.', 'xspeed' ),
65 65 'custom_panel' => 'ScorePanel',
66 + 'group' => 'insights',
66 67 );
67 68 }
68 69
69 70 public function settings_schema(): array {
@@ -79,9 +80,9 @@
79 80 // run turns this on rather than refusing (#425). What it
80 81 // still guards is everything that is NOT a Test press — an
81 82 // optimize run measuring its own effect, for instance.
82 83 // Switch it off (REST/CLI) and nothing contacts a provider.
83 - 'description' => __( 'Turns on automatically the first time you run a speed test — the button press is the consent. Switch it off to stop every feature, including optimize runs, from contacting a score provider.', 'xspeed' ),
84 + 'description' => __( 'Turns on the first time you run a speed test. Switch it off and no feature, not even an optimize run, contacts a test provider.', 'xspeed' ),
84 85 // No dashboard control: the Test press manages it, and a
85 86 // visible switch that gates a button elsewhere was the
86 87 // confusion #425 removed. Hidden fields are skipped by the
87 88 // panel renderer and by settings search.
@@ -94,19 +95,20 @@
94 95 'option_labels' => array(
95 96 'psi' => 'PageSpeed Insights',
96 97 'gtmetrix' => 'GTmetrix',
97 98 ),
98 - 'label' => __( 'Provider', 'xspeed' ),
99 - 'description' => __( 'PageSpeed Insights works without an API key. GTmetrix requires one.', 'xspeed' ),
99 + 'label' => __( 'Test provider', 'xspeed' ),
100 + 'description' => __( 'PageSpeed Insights works without an API key. GTmetrix needs one.', 'xspeed' ),
100 101 ),
101 102 'psi_api_key' => array(
102 103 'type' => 'secret',
103 104 'default' => '',
104 105 'label' => __( 'PageSpeed API key (optional)', 'xspeed' ),
105 - 'description' => __( 'Only needed if you hit Google\'s anonymous rate limit. Free from cloud.google.com.', 'xspeed' ),
106 + 'description' => __( 'Only needed if Google starts refusing tests without a key. You can get a free key at cloud.google.com.', 'xspeed' ),
106 107 // Rendered as a trailing "Check the documentation" link —
107 108 // descriptions themselves are plain text (#111).
108 109 'doc_url' => 'https://xspeedcache.com/docs/pagespeed-insights-integration/',
110 + 'advanced' => true,
109 111 'dependsOn' => array(
110 112 'field' => 'provider',
111 113 'value' => 'psi',
112 114 ),
@@ -114,9 +116,9 @@
114 116 'gtmetrix_api_key' => array(
115 117 'type' => 'secret',
116 118 'default' => '',
117 119 'label' => __( 'GTmetrix API key', 'xspeed' ),
118 - 'description' => __( 'Required — GTmetrix has no anonymous mode. Found in your GTmetrix account settings.', 'xspeed' ),
120 + 'description' => __( 'GTmetrix does not run tests without a key. Find it in your GTmetrix account settings.', 'xspeed' ),
119 121 'dependsOn' => array(
120 122 'field' => 'provider',
121 123 'value' => 'gtmetrix',
122 124 ),
@@ -130,10 +132,10 @@
130 132 'default_strategy' => array(
131 133 'type' => 'enum',
132 134 'default' => 'mobile',
133 135 'options' => array( 'mobile', 'desktop' ),
134 - 'label' => __( 'Strategy', 'xspeed' ),
135 - 'description' => __( 'PageSpeed Insights only. Mobile is what Google ranks on.', 'xspeed' ),
136 + 'label' => __( 'Device', 'xspeed' ),
137 + 'description' => __( 'Test as a phone or a desktop visitor. Google ranks sites on the mobile result.', 'xspeed' ),
136 138 'dependsOn' => array(
137 139 'field' => 'provider',
138 140 'value' => 'psi',
139 141 ),
@@ -252,9 +254,12 @@
252 254 }
253 255
254 256 $started = Scan::start(
255 257 (string) ( $request->get_param( 'url' ) ?? '' ),
256 - (bool) $request->get_param( 'fresh' )
258 + (bool) $request->get_param( 'fresh' ),
259 + // `both` (default), `desktop` or `mobile` — which Lighthouse runs
260 + // the engine spends and therefore which device is the headline.
261 + (string) ( $request->get_param( 'strategy' ) ?? 'both' )
257 262 );
258 263 if ( is_wp_error( $started ) ) {
259 264 return $started;
260 265 }
@@ -291,8 +296,9 @@
291 296 'status' => 'running',
292 297 'scan_id' => $polled['scan_id'],
293 298 'step' => $polled['step'],
294 299 'latest' => Scan::latest(),
300 + 'stored' => Scan::stored_state(),
295 301 'visibility' => Scan::visibility(),
296 302 'private_supported' => Scan::private_supported(),
297 303 )
298 304 );
@@ -300,8 +306,9 @@
300 306 return rest_ensure_response(
301 307 array(
302 308 'status' => 'complete',
303 309 'latest' => $polled,
310 + 'stored' => 'ok',
304 311 'visibility' => Scan::visibility(),
305 312 'private_supported' => Scan::private_supported(),
306 313 )
307 314 );
@@ -310,8 +317,9 @@
310 317 return rest_ensure_response(
311 318 array(
312 319 'status' => 'idle',
313 320 'latest' => Scan::latest(),
321 + 'stored' => Scan::stored_state(),
314 322 'visibility' => Scan::visibility(),
315 323 'private_supported' => Scan::private_supported(),
316 324 )
317 325 );
@@ -809,8 +817,15 @@
809 817 'description' => 'Force a new scan instead of reusing a recent cached report.',
810 818 'optional' => true,
811 819 ),
812 820 array(
821 + 'type' => 'assoc',
822 + 'name' => 'strategy',
823 + 'description' => 'Which Lighthouse runs to spend: both (default; desktop graded as the headline, mobile graded alongside), desktop, or mobile. One device costs half the engine quota and becomes the headline.',
824 + 'optional' => true,
825 + 'options' => array( 'both', 'desktop', 'mobile' ),
826 + ),
827 + array(
813 828 'type' => 'flag',
814 829 'name' => 'wait',
815 830 'description' => 'Poll until the scan finishes instead of returning immediately.',
816 831 'optional' => true,
@@ -861,9 +876,10 @@
861 876 }
862 877
863 878 $started = Scan::start(
864 879 (string) ( $assoc_args['target'] ?? '' ),
865 - ! empty( $assoc_args['fresh'] )
880 + ! empty( $assoc_args['fresh'] ),
881 + (string) ( $assoc_args['strategy'] ?? 'both' )
866 882 );
867 883 if ( is_wp_error( $started ) ) {
868 884 \WP_CLI::error( $started->get_error_message() );
869 885 return;
@@ -912,8 +928,12 @@
912 928 return;
913 929 }
914 930
915 931 $latest = Scan::latest();
932 + if ( null === $latest && 'unreadable' === Scan::stored_state() ) {
933 + \WP_CLI::log( 'The last scan report could not be read back from the database and was discarded. Run `wp xspeed scan run --wait` again.' );
934 + return;
935 + }
916 936 if ( null === $latest ) {
917 937 \WP_CLI::log( 'No scan yet. Run `wp xspeed scan run --wait`.' );
918 938 return;
919 939 }
@@ -933,20 +953,30 @@
933 953 );
934 954 foreach ( (array) ( $r['dimensions'] ?? array() ) as $key => $d ) {
935 955 \WP_CLI::log( sprintf( ' %-9s %3s/100 (%s of %s pts)', $key, $d['score'] ?? '-', $d['earned'] ?? '-', $d['weight'] ?? '-' ) );
936 956 }
937 - $lh = $r['measured']['lighthouse'] ?? null;
957 + // Read the PER-DEVICE fields, never `measured.lighthouse`: that one is
958 + // the graded run's score, which is desktop on a default scan, so
959 + // printing it as "mobile" reported desktop under a mobile label.
960 + $lhm = $r['measured']['lighthouse_mobile'] ?? null;
938 961 $lhd = $r['measured']['lighthouse_desktop'] ?? null;
939 - if ( null !== $lh || null !== $lhd ) {
940 - // Both strategies: the engine measures both and they diverge
941 - // widely, so reporting only mobile states the harsher number as
942 - // though it were the whole picture. Labelled, and never as "the
943 - // score": different scale.
962 + if ( null !== $lhm || null !== $lhd ) {
963 + // Name only the devices actually measured. A desktop-only scan
964 + // never ran mobile, and a dash there reads as "scored zero"
965 + // rather than "not run".
966 + $parts = array();
967 + if ( null !== $lhd ) {
968 + $parts[] = 'desktop ' . $lhd . '/100';
969 + }
970 + if ( null !== $lhm ) {
971 + $parts[] = 'mobile ' . $lhm . '/100';
972 + }
973 + $graded = (string) ( $r['device'] ?? '' );
944 974 \WP_CLI::log(
945 975 sprintf(
946 - 'Lighthouse: mobile %s, desktop %s - a different scale, one check inside the score above.',
947 - null === $lh ? '-' : $lh . '/100',
948 - null === $lhd ? '-' : $lhd . '/100'
976 + 'Lighthouse: %s - a different scale, one check inside the score above.%s',
977 + implode( ', ', $parts ),
978 + '' !== $graded ? ' Graded on ' . $graded . '.' : ''
949 979 )
950 980 );
951 981 }
952 982 \WP_CLI::log( 'Report: ' . ( $r['report_url'] ?? '' ) );