Skip to content

Surface core admin notices once in the desktop shell - #363

Merged
mmtr merged 9 commits into
trunkfrom
claude/core-global-notices-audit
Jul 17, 2026
Merged

Surface core admin notices once in the desktop shell#363
mmtr merged 9 commits into
trunkfrom
claude/core-global-notices-audit

Conversation

@mmtr

@mmtr mmtr commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

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.

Screenshot 2026-07-17 at 12 13 31

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.

  1. Trigger two easy ones:
    • wp user meta update <your-login> default_password_nag 1
    • wp option update wp_force_deactivated_plugins '[{"plugin_name":"Test Plugin"}]' --format=json
  2. Open the desktop shell. Expected: each notice appears once as a toast in the top-right.
  3. Open any window (e.g. Posts, Settings). Expected: the notices do not repeat inside the window.
  4. Click a toast's × to dismiss it, then reload the shell. Expected: that toast stays gone; the others remain.
  5. Click a toast's action button (e.g. "Change password"). Expected: the correct screen opens as a window, titled by the screen (not the button).
  6. Clean up: wp user meta delete <your-login> default_password_nag and wp 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

Open WordPress Playground Preview

mmtr and others added 6 commits July 15, 2026 15:07
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>
@mmtr mmtr self-assigned this Jul 17, 2026
mmtr and others added 2 commits July 17, 2026 13:20
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>

Copilot AI 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.

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_notices callbacks 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.

Comment thread src/desktop.ts Outdated
Comment thread includes/plugin-notices.php
…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>
@mmtr
mmtr merged commit 9e3b8df into trunk Jul 17, 2026
9 checks passed
@mmtr
mmtr deleted the claude/core-global-notices-audit branch July 17, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants