Skip to content

Surface WordPress update notices once in the desktop shell (with release artwork) - #340

Merged
mmtr merged 21 commits into
trunkfrom
claude/global-notices-desktop-mode-96ab50
Jul 15, 2026
Merged

Surface WordPress update notices once in the desktop shell (with release artwork)#340
mmtr merged 21 commits into
trunkfrom
claude/global-notices-desktop-mode-96ab50

Conversation

@mmtr

@mmtr mmtr commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Why

WordPress core shows the "a new version is available, please update" nag on every admin screen. In desktop mode each screen is its own window, so that one nag ends up repeated in every open window — cluttered and noisy. This makes the update surface once, in the shell, and turns it into something that fits the desktop.

What changes (from a user's point of view)

Screenshot 2026-07-14 at 17 55 16
Screen.Recording.2026-07-14.at.17.54.46.mov
Screen.Recording.2026-07-14.at.17.53.34.mov
  • No more repeats. The core update nag is hidden inside the windows.
  • One notification, in the shell. When an update is available you get a single notification in the top-right.
  • Two styles, chosen automatically:
    • Regular update → a clean notification: "WordPress X.Y.Z is available" with an Update now button.
    • A release that has cover art → an album-sleeve moment: the release's own artwork as a record sleeve with a vinyl sliding out, tinted to match the artwork. The message names the release (e.g. WordPress 7.0 "Armstrong") when you're moving up to a new major version, or shows the exact version for a point release.
  • Artwork is live, not bundled. The release art is fetched from WordPress.org, so it works for past and future releases without shipping any images. The notification waits for the art to load, so it appears once — no placeholder flicker.
  • Update now opens the Updates screen.
  • Dismissible. A close button fades it away. Once dismissed it won't nag you again for that version — but a newer version will notify you again.
  • Respects "reduced motion" (no animation) and only shows to users who can actually update.

How to test

WordPress can't be behind by default, so fake an available update.

  1. Turn on desktop mode.

  2. Add this as a must-use plugin (wp-content/mu-plugins/fake-update.php), then reload the desktop:

    <?php
    add_filter( 'pre_site_transient_update_core', function () {
        $item = (object) array(
            'response' => 'upgrade',
            'current'  => '7.0.9', // the version to advertise (see step 4)
            'locale'   => 'en_US',
            'url'      => 'https://wordpress.org/download/',
            'packages' => (object) array( 'full' => '' ),
        );
        return (object) array(
            'updates'         => array( $item ),
            'version_checked' => get_bloginfo( 'version' ),
            'last_checked'    => time(),
        );
    } );
  3. Check the windows: open a few admin windows (Dashboard, Posts, Tools). The "update available" nag should not appear inside any of them.

  4. Check the notification (top-right of the shell):

    • Set current to a higher patch of a real release line — e.g. 7.0.9 if a 7.0 exists (or 6.5.9 on/against a 6.5 line). You should see the album-sleeve / vinyl notification with that release's artwork.
    • Set current to a made-up version with no release — e.g. 9.9 — to see the plain notification fallback.
    • The first appearance may take a moment while the artwork loads; after that it's instant.
  5. Update now: click it — the Updates screen opens in a window.

  6. Dismiss: click the × — the notification fades out. Reload: it stays gone. Now change current to a newer version (e.g. 7.0.10) and reload — it notifies you again.

  7. Remove the mu-plugin when you're done.

Tip: to force a specific release's artwork regardless of your installed version, set current to that release's version (e.g. 6.5.1). Codenames only appear when the update moves you up to a new major line.

🤖 Generated with Claude Code

Open WordPress Playground Preview

mmtr and others added 2 commits July 13, 2026 16:54
WordPress core prints "WordPress X is available!" on every admin
screen, so in desktop mode the same nag repeated in every open window.
Now the plugin detaches core's per-window update/maintenance nags
inside chromeless windows and re-surfaces the update once, as a single
persistent (non-dismissible) toast in the shell.

- includes/core/routing.php: chromeless-gated remove_action() for
  update_nag / maintenance_nag, plus a .update-nag CSS net.
- includes/update-notice.php: desktop_mode_get_core_update() computes a
  { version, url } descriptor from authoritative update state
  (update_core-gated; desktop_mode_core_update_notice filter to
  suppress) and ships it as config.coreUpdate.
- src/update-notice.ts: shows one persistent toast; "Update now" opens
  update-core.php. Clears via the action or once the update installs.
- src/toast.ts: new `persistent` option (never auto-dismiss).
- wpd-toast: light border + inset highlight so the toast reads as a
  distinct surface over the widget cards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the core-update notification: a routine (minor) release still
shows the plain persistent toast, but a major release becomes the
release's own music-themed art — the album sleeve with a CSS-drawn
vinyl that slides out and spins.

The flat release PNG is used only as the square sleeve; the record is
drawn entirely in CSS (grooves, sheen, tinted label), so it's a real
separate object that can emerge and turn — no image surgery.

- includes/update-notice.php: desktop_mode_is_major_update() (X.Y bump
  vs installed) + desktop_mode_core_update_release() registry (bundled
  Armstrong 7.0 / Gene 6.9 art, desktop_mode_core_update_release filter
  to extend/override). Descriptor gains `major` + `release`.
- assets/releases/{7.0,6.9}.jpg: square sleeve crops of the release art.
- src/ui/components/wpd-release-card: the vinyl web component; static
  under prefers-reduced-motion.
- src/release-card.ts + src/update-notice.ts: picker — vinyl for a major
  with art, plain toast otherwise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmtr mmtr changed the title Surface the WordPress update nag as a single desktop toast Surface the WordPress update nag as a single desktop notification (toast + release vinyl) Jul 14, 2026
@mmtr
mmtr marked this pull request as draft July 14, 2026 09:18
mmtr and others added 14 commits July 14, 2026 12:59
The vinyl release moment now pulls each release's album art from the
wordpress.org/news announcement (the post's featured image) at runtime,
so past and future releases work without a plugin update — no bundled
art, no hardcoded registry.

- includes/update-notice.php: desktop_mode_core_update_release() reads a
  cached descriptor; a cold cache schedules a background fetch
  (wp_schedule_single_event) so the admin render never blocks on an
  external request. The fetcher searches the news REST API, matches the
  major-announcement title (WordPress X.Y "Codename"), and takes the
  post's featured image (medium_large). Hits cache a week, misses 6h.
  The desktop_mode_core_update_release filter still overrides everything.
- assets/releases/*: removed (no longer bundled).
- wpd-release-card: the sleeve art is cropped left (object-position),
  which extracts the square sleeve from the landscape release image for
  any release; classic cream label + WordPress-blue button by default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ved accent

Reworks the core-update notification behavior:

- Show the vinyl for ANY update in a branch that has release art, not
  just a fresh major — a minor (7.0 -> 7.0.1) reuses its major's album
  art. Falls back to the toast only when no art is available.
- Branch-aware wording, computed server-side: crossing into a new major
  (6.9 -> 7.0 or 7.0.1) reads "WordPress 7.0 'Armstrong' is available"
  (branch version + codename); a same-branch minor (7.0 -> 7.0.1) reads
  "WordPress 7.0.1 is available" (exact version, no codename).
- Accent color is sampled from the sleeve art at runtime (its dominant
  vivid color) and tints the record label + Update button; the CDN
  allows CORS so this is done client-side, with a filter override and a
  WordPress-blue default. Verified: Armstrong -> coral, Gene -> blue.

Descriptor is now { version, name, branch, url, release:{artUrl} };
`major` is gone (the shell keys off release art). The fallback sprintf
gained positional-token (%1$s) support.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eeve

Some release covers (e.g. 6.8 Cecil) ship a white frame around the
artwork in their featured image, unlike 7.0/6.9 which bleed to the edge.
Cropping the left square then showed that white padding.

The sleeve is now painted via canvas: trim any uniform near-white border
first, then draw the left square (the sleeve). Full-bleed art has
non-white edges so nothing is trimmed; padded art (Cecil) crops cleanly.
Accent extraction runs on the same painted sleeve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-wrap

- .cover border-radius 10px -> 2px
- remove the .spine gradient element
- .disc-wrap: top 2px, 148x148

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- notification width 240px -> 268px
- replace the "W" + version text on the record label with the WordPress
  logo on the accent-colored label (accent-ink glyph); drop the spindle
  hole. The label color follows the art-derived accent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#15161b -> #34373f — the near-black disc was blending into the near-black card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the outer drop shadow to the non-rotating .disc-wrap (made round);
.disc keeps only its radially-symmetric inner shadows, so the shadow
stays fixed while the record spins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… shell)

A backtick inside the css`` tagged template terminated the literal, so
`.disc-wrap` in a comment parsed as JS (`.disc` minus `wrap`) →
"wrap is not defined" at boot, blanking the shell. Plain-text the comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…con animation

Add a close button. Dismissing plays a two-stage animation: the record
slides back into the sleeve, then the whole card collapses into the
admin-bar Updates icon (with a small bounce on the icon it lands in;
falls back to the admin bar / top-right if the node isn't present).
Respects prefers-reduced-motion (instant removal). Pending animation
timers are cancelled on disconnect.

Dismissal persists per release branch (localStorage), and the picker
skips a release the user already dismissed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PHP now only reports that a core update is pending ({version, branch,
url, crossing}); it no longer fetches the news feed (removed the cron,
transient, wp_remote_get parse and the desktop_mode_core_update_release
filter). The shell resolves the album art + codename client-side:

- src/release-art.ts: resolveReleaseArt(branch) fetches the news REST API
  (CORS-enabled, via trackedFetch), matches the announcement title, takes
  the featured image, caches in localStorage (hit forever, miss 6h);
  preloadImage() waits for the image to decode.
- src/update-notice.ts: maybeShowUpdate() is async — resolve art, wait for
  it to load, THEN show the vinyl once (no placeholder flash). Falls back
  to the toast only when art can't be resolved.

Net effect: the notification appears a beat later but already as the
vinyl, instead of flashing a toast and swapping to the vinyl on reload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The release card is an internal, single-use, feature-specific surface, so
it no longer needs to be a custom element. Convert `<wpd-release-card>` to
a plain module in src/release-card.ts: it builds its DOM directly and
injects one scoped stylesheet (everything under `.dm-release-card`),
removing the custom-element + shadow-DOM lifecycle that the imperative
canvas/animation code had to work around (no more queueMicrotask/rAF
dance; render is synchronous). Behavior + animation are unchanged.

Also: `.label svg` sizing 60% -> 59%.

- Remove src/ui/components/wpd-release-card/ + its barrel/tags entries.
- release-card.ts owns build + paint + accent + dismiss animation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the two-stage dismiss (record-back-to-sleeve + collapse-into-the-
Updates-icon). The close button now just fades the card out (~200ms) and
persists the dismissal; reduced-motion removes it instantly. Removes the
collapseToTarget logic and the data-collapsing CSS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A relevance-ranked news search for an older branch (e.g. 6.5) buries the
major announcement below newer 6.5.x maintenance posts + betas, past the
first 20 results — so it fell back to the toast. Fetch up to 100 (the
REST max) and version the localStorage cache key so stale misses from the
old logic are re-fetched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dismissing 6.5.1 also hid 6.5.2 because the dismissal was keyed by
branch. A newer point release is a new notice (and core itself re-nags
per version), so key the dismissal on the exact available version. PHP
now ships `available` (the exact version) alongside the display
`version`; the client keys "dismissed" on it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmtr mmtr self-assigned this Jul 14, 2026
@mmtr
mmtr marked this pull request as ready for review July 14, 2026 15:58
@mmtr mmtr changed the title Surface the WordPress update nag as a single desktop notification (toast + release vinyl) Surface WordPress update notices once in the desktop shell (with release artwork) Jul 14, 2026
@mmtr
mmtr requested a review from Copilot July 14, 2026 15:59

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 centralizes WordPress core update notifications so they appear once in the Desktop Mode shell (instead of repeating in every chromeless admin window), with an enhanced “release artwork” presentation when art can be resolved from WordPress.org.

Changes:

  • Adds a server-side core update descriptor (coreUpdate) to the shell config and suppresses core’s per-window update/maintenance nags in chromeless requests.
  • Introduces a shell-side update notice picker that resolves/caches release art client-side and shows either a vinyl-style release card or a persistent toast.
  • Extends toast behavior to support persistent toasts and documents the new surfaces/hooks.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/phpunit/tests/updateNotice.php Adds PHPUnit coverage for the core update descriptor and chromeless nag suppression.
src/update-notice.ts Implements the shell-side update notification decision flow (release card vs persistent toast).
src/update-notice.test.ts Adds Vitest coverage for update notice selection, dismissal keys, and “Update now” behavior.
src/ui/components/wpd-toast/wpd-toast.styles.ts Tweaks toast styling (border/shadow) for better surface separation.
src/types.ts Extends DesktopConfig with optional coreUpdate descriptor shape.
src/toast.ts Adds persistent toast support (no auto-dismiss timer) and documents behavior.
src/release-card.ts Implements the vinyl-style release card UI and accent extraction from cover art.
src/release-card.test.ts Adds Vitest coverage for release-card DOM, update action, and dismissal persistence.
src/release-art.ts Adds client-side resolver for release art + codename via WordPress.org news feed with caching.
src/release-art.test.ts Adds Vitest coverage for parsing and caching behavior of release-art resolver.
src/i18n.ts Extends sprintf fallback to support positional tokens (e.g. %1$s).
src/desktop.ts Boots the update notice picker from shell init using config.coreUpdate.
includes/update-notice.php Adds server-side core update descriptor computation and desktop_mode_core_update_notice filter.
includes/render/assets.php Ships coreUpdate in the shell config payload.
includes/core/routing.php Suppresses core update/maintenance nags inside chromeless admin requests.
docs/javascript-reference.md Documents persistent toasts and the core-update notice behavior.
docs/hooks-reference.md Documents desktop_mode_core_update_notice filter and core-update notice behavior.
desktop-mode.php Loads the new includes/update-notice.php module.
assets/css/chromeless.css Hides any update nags that slip past server-side suppression in chromeless windows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread includes/update-notice.php Outdated
Comment thread includes/update-notice.php Outdated
Comment thread src/update-notice.ts Outdated
Comment thread src/toast.ts Outdated
Comment thread docs/hooks-reference.md Outdated
Comment thread docs/hooks-reference.md Outdated
Comment thread docs/javascript-reference.md Outdated
Comment thread src/release-card.ts Outdated
Comment thread src/release-card.ts
Comment thread src/release-card.ts Outdated
mmtr and others added 3 commits July 15, 2026 09:14
- release-card: always draw the sleeve; make white-frame trim + accent
  best-effort (no blank sleeve if pixel reads are disallowed).
- release-card: render the message as one translated string (passed in
  by the picker via updateMessage) instead of English fragments;
  localize the close button's aria-label.
- toast: correct a stale comment (persistent toasts have no × button).
- docs/PHPDoc/types: add the `available` field to the coreUpdate
  descriptor everywhere it was omitted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trim the verbose docblocks across the core-update-notice feature and
rename the `desktop_mode_core_update_notice` descriptor filter to
`desktop_mode_show_core_update_notice` — a plain boolean (default true)
that turns the notification off. Bump the feature's @SInCE to 0.9.4 and
adapt the docs accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the over-explained comments across the core-update-notice
feature and reset the release-art localStorage cache prefix from `v2`
to `v1` (the feature never shipped, so there was no v1 to supersede).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@epeicher epeicher left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @mmtr, this looks great! I have tested it, and it works as expected, I see the new upgrade toast, and it looks nice. The fallback toast also looks good, but I miss a dismissal icon there. Could it be added?

Upgrade toast Default toast
Image Image

I have also added a _fields query param as part of f6a2f72 to reduce the payload when getting the releaseArt artifact.

epeicher and others added 2 commits July 15, 2026 13:31
_fields=title,_links,_embedded + _embed=wp:featuredmedia cuts the
per-branch response from ~1.3 MB (full rendered content of 100 posts)
to ~115 KB, with _links kept in _fields because the REST API only
embeds relations whose links survive the field filter. Parse results
are unchanged, so no cache version bump.
Review feedback: the plain fallback toast (shown when no release art
resolves) had no way to be dismissed. Add a `dismissible` option to
`<wpd-toast>` / `showToast()` that renders a close (×) button emitting
`wpd-toast-dismiss`, plus an `onDismiss` callback. The core-update
fallback toast now uses it and persists the dismissal on the same
exact-version key as the vinyl card, so closing it keeps it closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmtr

mmtr commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

The fallback toast also looks good, but I miss a dismissal icon there. Could it be added?

Nice suggestion! Done:

Screenshot 2026-07-15 at 13 54 46

@mmtr
mmtr merged commit d586c9b into trunk Jul 15, 2026
5 checks passed
@mmtr
mmtr deleted the claude/global-notices-desktop-mode-96ab50 branch July 15, 2026 11:56
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.

3 participants