Opened 5 weeks ago
Last modified 3 weeks ago
#65191 reviewing defect (bug)
deletePluginSuccess does not update counts for custom plugin_status groups
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 7.1 | Priority: | normal |
| Severity: | minor | Version: | |
| Component: | Plugins | Keywords: | has-patch |
| Focuses: | Cc: |
Description
#60495 added the plugins_list_status_text filter, so plugins can register custom group tabs on plugins.php by adding entries to the array passed through plugins_list and supplying a label through the new filter.
The server side works. The count badge is right on initial load, and ?plugin_status=<custom> filters the list as expected.
The AJAX delete flow does not keep up. wp.updates.deletePluginSuccess in wp-admin/js/updates.js only decrements counts for the hardcoded buckets (upgrade, inactive, active, recently_activated). Anything added through plugins_list is skipped, so the badge on the custom tab stays stale until the page reloads.
Steps to reproduce
- Drop the snippet below into a must-use plugin to register a sample group.
<?php add_filter( 'plugins_list', function( $plugins ) { if ( empty( $plugins['all'] ) ) { return $plugins; } $plugins['demo_group'] = array_filter( $plugins['all'], static function ( $data, $file ) { return str_starts_with( $file, 'a' ); }, ARRAY_FILTER_USE_BOTH ); return $plugins; } ); add_filter( 'plugins_list_status_text', function( $text, $count, $type ) { return $type === 'demo_group' ? 'Demo Group' : $text; }, 10, 3 );
- Install two plugins whose folder starts with
a, plus one that does not. - Visit
wp-admin/plugins.php. The "Demo Group (2)" tab shows the right count. - Click the tab. Delete one of the listed plugins from the row actions.
- The row fades out, but the tab still reads "(2)".
- Reload the page. The tab now reads "(1)", matching what the server returns.
The hardcoded tabs (Active, Inactive) decrement correctly in the same flow.
Cause
In wp-admin/js/updates.js, deletePluginSuccess checks four buckets and stops:
if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) { ... } if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) { ... } if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) { ... } if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) { ... }
Other keys present in plugins are not iterated.
Suggested approach
After the existing blocks, walk any remaining keys and apply the same pattern. Hardcoded keys keep their explicit path, so existing sites see no behavior change.
var knownKeys = [ 'all', 'search', 'upgrade', 'active', 'inactive', 'recently_activated', 'mustuse', 'dropins', 'paused', 'auto-update-enabled', 'auto-update-disabled' ]; _.each( _.keys( plugins ), function( key ) { if ( -1 !== _.indexOf( knownKeys, key ) ) { return; } if ( ! _.isArray( plugins[ key ] ) ) { return; } if ( -1 === _.indexOf( plugins[ key ], response.plugin ) ) { return; } plugins[ key ] = _.without( plugins[ key ], response.plugin ); var $tab = $views.find( '.' + key ); if ( plugins[ key ].length ) { $tab.find( '.count' ).text( '(' + plugins[ key ].length + ')' ); } else { $tab.remove(); } } );
The activate, deactivate, and update success callbacks likely have the same gap. Happy to roll those into one patch if reviewers want a single sweep, otherwise this ticket can stay scoped to delete.
Notes
wp-plugin-delete-success is already triggered as a jQuery event, so individual plugins can shim this on the client. With #60495 turning custom groups into a documented extension point, asking every consumer to ship the same workaround does not feel right.
Test plan
- Hardcoded tabs: delete still decrements
Active,Inactive, andUpdate availableexactly as before. - Custom group registered via
plugins_list: the count drops on delete, and the tab disappears when it reaches zero. - Sites with no custom group: no change, no extra DOM lookups.
Attachments (1)
Change History (6)
This ticket was mentioned in PR #11752 on WordPress/wordpress-develop by andreas-pa.
5 weeks ago
#1
- Keywords has-patch added; needs-patch removed
This ticket was mentioned in PR #11777 on WordPress/wordpress-develop by @alessioarzentondev.
5 weeks ago
#2
Improvement to PR: https://github.com/WordPress/wordpress-develop/pull/11752.
This PR refactors the rendering of the | separator between filter links in WP_List_Table::views() (the .subsubsub navigation — e.g. "All | Active | Inactive" on the Plugins screen).
Before: the separator was hardcoded in the PHP output via implode( " |</li>\n", $views ) in wp-admin/includes/class-wp-list-table.php.
After: PHP emits clean </li> markup, and the separator is drawn in CSS via a ::after pseudo-element.
Trac ticket: https://core.trac.wordpress.org/ticket/65191
## Use of AI Tools
#3
@
5 weeks ago
Worked on this during WordPress Contributor Day, Torino 2026.
While testing the previous PR, we noticed that when a plugin is deleted and the corresponding <li> is removed from the WP_List_Table filters via Ajax, the | separator on the preceding <li> was left behind, because it was injected via PHP and therefore not aware of subsequent DOM changes.
Since it's purely a visual element, we think it belongs in CSS instead.
Moving it to a stylesheet keeps structure and presentation separated, makes the separator easier to restyle or remove, and handles the last item automatically without any special PHP logic. Nothing changes visually for the user, and the markup ends up a bit cleaner — including for screen readers and copy/paste, since the | is no longer part of the text content.
Props andrew.p, alessioarzentondev, gabrieleburgarella, enzo2018, lopo, realloc, f94leonardo.
#4
@
4 weeks ago
- Milestone changed from Awaiting Review to 7.1
- Owner set to SergeyBiryukov
- Status changed from new to reviewing
#5
@
3 weeks ago
- Keywords needs-testing removed
Test Report
Patch tested: https://github.com/WordPress/wordpress-develop/pull/11777
Environment
- WordPress: 7.1-alpha-62161-src
- Subdirectory: No
- PHP: 8.2.29
- Server: nginx/1.29.4
- Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
- Browser: Chrome 148.0.0.0
- OS: macOS
- Theme: Twenty Twenty-Five 1.5
- MU Plugins: None activated
- Plugins:
- Code Snippets 3.9.6
- Test Reports 1.3.0
Steps taken
- Add the snippet provided in the ticket description
- Install 2 plugins start with letter a
- Observe the Demo Group count increase to 2
- Delete one of them and observe that the count stays the same (as 2)
- Apply patch and repeat steps
- Observe that Demo Group count decreases to 1 as expected
- ✅ Patch is solving the problem
Expected result
- After patch, we expect the dynamic tab's count update correctly same as the hardcoded tabs.
Screenshots/Screencast with results
Before: https://files.catbox.moe/qbq8df.mp4
After: https://files.catbox.moe/177r7i.mp4
Trac ticket: https://core.trac.wordpress.org/ticket/65191
Decrements counts for groups registered via the
plugins_listfilter when a plugin is deleted via AJAX, so tabs whose label is supplied throughplugins_list_status_text(introduced in #60495) stay in sync without requiring a page reload. The four hardcoded buckets (upgrade,inactive,active,recently_activated) plusauto-update-enabled/auto-update-disabledkeep their explicit fast paths; the new loop only runs for keys outside that set.## Reproduction
See the steps in Trac #65191. TL;DR: register a group via
plugins_listwith a label supplied throughplugins_list_status_text, delete a plugin in that group via the row action, and observe the count badge stay stale until reload.## Test plan
Active,Inactive,Update available) still decrement on delete.plugins_list: count drops on delete; tab disappears at zero.