Surface core admin notices once in the desktop shell - #363
Merged
Conversation
Enumerate every WordPress Core notice that renders globally in admin (hook/priority, source, screen-gating, re-derivable state, proposed desktop surface), separating the eight truly-global notices from the self-gated and non-notice candidates. This is the checklist for detaching each per-window nag and surfacing it once in the shell. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Applies the update-nag pattern to the rest of WordPress Core's global admin notices (single-site): maintenance/failed-update, recovery mode, default-password, force-deactivated plugins, and paused plugins/themes. Each is detached inside chromeless windows (so it no longer repeats per window) and re-derived from authoritative server state into a single shell toast. - includes/core-notices.php: desktop_mode_get_core_notices() builds the descriptors, capability-gated as Core gates each notice; filterable via desktop_mode_core_notices. - includes/core/routing.php: desktop_mode_chromeless_suppress_core_notices() detaches the callbacks in chromeless requests. - src/core-notices.ts: maybeShowCoreNotices() renders each once, keyed for per-notice dismissal. - Multisite site_admin_notice is deferred (documented follow-up). - Audit doc, hooks-reference, tests (PHP + JS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the core-notice surfacing with a narrow, opt-in allowlist of shared *library* notices — starting with Action Scheduler's "N past-due actions found" warning (bundled by WooCommerce, Jetpack, and others). It prints globally with no throttle while past-due actions exist, so it repeats in every desktop window. Like the core notices, it's detached in-window and re-derived from authoritative state (Action Scheduler's own store query, mirroring ActionScheduler_AdminView::check_pastdue_actions() and its filters) into a single dismissible shell toast. Arbitrary plugin admin_notices remain untouched; the allowlist bar is "shared library + global + re-derivable". - includes/plugin-notices.php: desktop_mode_get_plugin_notices() + Action Scheduler builder + suppressor; filter desktop_mode_plugin_notices. - config.pluginNotices; client maybeShowNotices() generalized with a keyPrefix so core/plugin dismissal keys don't collide. - Audit doc, hooks-reference, tests (PHP with a fake AS store + JS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking a notice action opened the target screen with the action-button
text as the window title ("Go to Plugins", "Go to Themes"). Add a
separate `title` to each descriptor (the target screen's name —
"Plugins", "Themes", "Profile", "WordPress Updates", …) and use it for
the window title; the button keeps its own label. Falls back to the
action label when no title is given.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The close button sets `display: inline-flex`, and author styles beat the
UA `[hidden] { display: none }` rule — so the `?hidden` binding set the
attribute but the × stayed visible on every persistent toast, regardless
of `dismissible`. Add `button[ hidden ] { display: none }` so non-
dismissible notices (maintenance, recovery mode, paused plugins/themes)
show no close button, as intended. Guard with a stylesheet regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A persistent toast must never be permanent — it either auto-dismisses or carries a close icon. Since every core/plugin notice is persistent (they report conditions the user should act on), make them all dismissible. Drops the per-notice `dismissible` toggle entirely: the client always renders these as persistent + dismissible, so maintenance, recovery mode, and paused plugins/themes now get a close button too. Dismissal stays keyed per-notice in localStorage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove docs/core-notices-audit.md and the references to it; it doesn't belong in the repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0.9.5 is released, so the core/plugin notice filters, descriptors, and suppressors introduced here are @SInCE 0.9.6, not 0.9.4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prevents global WordPress admin notices (Core + a small allowlist of shared library notices like Action Scheduler) from repeating inside every desktop-mode window by suppressing them in chromeless iframes and surfacing them once at the shell level as persistent, dismissible toasts derived from server state.
Changes:
- Add server-side “notice descriptor” builders for Core global notices and allowlisted library notices, expose them on the shell config, and document new filters.
- Suppress the corresponding
admin_noticescallbacks in chromeless requests so notices don’t re-render per window. - Add shell-side toast rendering + dismissal persistence, plus PHPUnit/Vitest coverage and a small toast
[hidden]styling fix.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/phpunit/tests/pluginNotices.php | Adds PHPUnit coverage for Action Scheduler allowlisted notice + chromeless suppressor behavior. |
| tests/phpunit/tests/coreNotices.php | Adds PHPUnit coverage for Core notice builders, aggregation/filtering, and chromeless suppression. |
| src/ui/components/wpd-toast/wpd-toast.test.ts | Adds a regression test ensuring [hidden] buttons are actually hidden in toast styles. |
| src/ui/components/wpd-toast/wpd-toast.styles.ts | Ensures author styles don’t override the UA [hidden]{display:none} behavior. |
| src/types.ts | Extends DesktopConfig with coreNotices and pluginNotices descriptor shapes. |
| src/desktop.ts | Wires notice descriptors into shell boot and opens notice action URLs as windows. |
| src/core-notices.ts | Implements shell rendering of notice descriptors as persistent, dismissible toasts with local dismissal storage. |
| src/core-notices.test.ts | Adds Vitest coverage for notice rendering, action wiring, and dismissal key namespacing. |
| includes/render/assets.php | Adds coreNotices and pluginNotices into the JS boot config payload. |
| includes/plugin-notices.php | Implements allowlisted library notice descriptors (Action Scheduler) + chromeless detachment. |
| includes/core/routing.php | Adds chromeless suppression for remaining Core global notices. |
| includes/core-notices.php | Implements Core global notice descriptor builders + aggregation and filtering. |
| docs/hooks-reference.md | Documents new desktop_mode_core_notices and desktop_mode_plugin_notices filters. |
| desktop-mode.php | Loads the new Core/plugin notice PHP modules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…moval - desktop.ts: derive notice-action window ids via deriveWindowId() so URL fragments (e.g. profile.php#password) and nonces are handled consistently and ids stay slug-safe, instead of ad-hoc string splitting. - plugin-notices.php: detect Action Scheduler's actual admin_notices priority with has_action() and remove at that priority, rather than assuming the default 10. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
WordPress prints its global admin notices (update failed, recovery mode, "you're using an auto-generated password", force-deactivated plugins, paused plugins/themes) on every admin screen. Because desktop mode renders each screen as its own window, those notices repeat in every open window.
This detaches them inside windows and surfaces each once at the shell level as a dismissible toast, re-derived from WordPress's own state. It builds on the earlier "WordPress update available" work and applies the same pattern to the rest of Core's global notices.
It also adds a small, opt-in allowlist for shared library notices that behave the same way — starting with Action Scheduler's "past-due actions" warning (bundled by WooCommerce and many others). Regular plugin notices are left untouched and still render in-window.
Every notice is a persistent toast with a close (×) button; dismissing one keeps it dismissed. Each toast's action opens the relevant screen as a window.
Testing
You need a site with desktop mode enabled. Trigger a couple of notices via WP-CLI, then load the shell.
wp user meta update <your-login> default_password_nag 1wp option update wp_force_deactivated_plugins '[{"plugin_name":"Test Plugin"}]' --format=jsonwp user meta delete <your-login> default_password_nagandwp option delete wp_force_deactivated_plugins.To also check Action Scheduler, install a plugin that bundles it (e.g. WooCommerce) and create past-due scheduled actions; the "past-due actions" toast appears once and is stripped from windows.
🤖 Generated with Claude Code