Skip to content

feat(admin): core update notice in the admin dashboard (WordPress-style version banner)#1939

Open
swissky wants to merge 3 commits into
emdash-cms:mainfrom
swissky:feat/core-update-notice
Open

feat(admin): core update notice in the admin dashboard (WordPress-style version banner)#1939
swissky wants to merge 3 commits into
emdash-cms:mainfrom
swissky:feat/core-update-notice

Conversation

@swissky

@swissky swissky commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Implements the core update notice proposed (and maintainer-+1'd) in Discussion #1889: a WordPress-style "a new version is available" banner in the admin dashboard.

Coming from WordPress, the update notice is the single biggest "am I safe?" signal. EmDash already surfaces updates for marketplace plugins, but for the core there is nothing — a site owner who isn't the developer has no way to know they're running 0.22 while 0.24 is out with security fixes.

How it works:

  • The server knows its own version (VERSION). The latest published version comes from a plain GET to https://registry.npmjs.org/emdash/latest — checked at most once per day, always deferred via after() so it never blocks a request, result cached in the options table. The request carries no site data; sites without outbound internet simply never show the banner.
  • A new admin-only endpoint GET /_emdash/api/admin/core-update (new updates:read permission, min role admin) serves the cached status — always a fast local read.
  • The dashboard shows a Kumo banner ("EmDash X.Y is available (you're running A.B)") with a link to the release notes, dismissible per version: dismissing 0.24 re-arms when 0.25 ships (localStorage).
  • Opt-out: updateCheck: false in the EmDash config disables the check entirely (documented in the configuration reference).

Deliberately not in scope (as discussed): an "update now" button. An EmDash update is an npm bump + rebuild + redeploy, which the admin cannot and should not trigger. The value is the awareness.

Discussion: #1889

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Cursor + Fable 5

Screenshots / test output

✓ tests/integration/api/update-check.test.ts (9 tests) — isNewerVersion comparisons,
  cache serving, malformed-cache handling, opt-out, registry refresh success/failure
Test Files  1 passed (1)
     Tests  9 passed (9)

WordPress-style "a new version is available" awareness (Discussion
emdash-cms#1889). The server caches the latest published version from the npm
registry in the options table — checked at most once per day, always
deferred via after() so no request blocks on the network — and a new
admin-only endpoint (GET /_emdash/api/admin/core-update) serves it to
the dashboard, which shows a dismissible banner (per version) linking
to the release notes. Opt out with updateCheck: false in config.
@changeset-bot

changeset-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dbe9979

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
emdash Minor
@emdash-cms/admin Minor
@emdash-cms/auth Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 515 lines across 13 files. Large PRs are harder to review and more likely to be closed without review.
This PR spans 4 different areas (area/core, area/admin, area/docs, area/auth). Consider breaking it into smaller, focused PRs.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@pkg-pr-new

pkg-pr-new Bot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1939

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1939

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1939

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1939

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1939

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1939

emdash

npm i https://pkg.pr.new/emdash@1939

create-emdash

npm i https://pkg.pr.new/create-emdash@1939

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1939

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1939

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1939

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1939

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1939

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1939

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1939

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1939

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1939

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1939

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1939

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1939

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1939

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1939

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1939

commit: 79abc28

@emdashbot emdashbot Bot 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.

The approach is sensible and matches the approved Discussion: a lightweight, deferred registry check with a purely informational admin banner is the right scope for core update awareness. The implementation generally follows EmDash conventions — admin-only route with requirePerm, after() for the background fetch, options-table caching, Lingui-wrapped strings, and RTL-safe layout classes.

I found two real issues that should be fixed before merge:

  1. updateCheck: false does nothing at runtime. The option is defined on EmDashConfig and documented, but it is not added to serializableConfig, which is what becomes virtual:emdash/config and is exposed as locals.emdash.config. The route therefore reads emdash.config.updateCheck as undefined, making the documented opt-out silently ignored.

  2. A malformed checkedAt string prevents the cache from ever refreshing. parseCache only verifies that checkedAt is a string, not that it parses to a valid date. If the stored value is garbage, the stale check becomes NaN >= CHECK_INTERVAL_MS (false), so no deferred refresh is scheduled and stale/corrupt data can persist indefinitely.

The unit tests cover the handler and the fetch logic well, but they exercise handleCoreUpdateStatus directly (with VERSION = "dev", so the deferred branch is never hit) and do not cover the integration-level serialization of updateCheck. Add a serialization-level check or route-level test for the opt-out once the config is wired through.


Findings

  • [needs fixing] packages/core/src/astro/integration/index.ts:353

    updateCheck is added to the EmDashConfig type and documented, but it is missing from the serializableConfig object that is baked into virtual:emdash/config and later attached as locals.emdash.config. Because the route reads emdash.config.updateCheck !== false, the value is always undefined at runtime and the check is always treated as enabled — the documented opt-out is silently ignored.

    		toolbar: resolvedConfig.toolbar,
    		updateCheck: resolvedConfig.updateCheck,
    	};
    

…able checkedAt

Two bot findings: (1) updateCheck was on EmDashConfig but missing from
serializableConfig, so locals.emdash.config.updateCheck was always
undefined and the documented opt-out was silently ignored — extracted
buildSerializableConfig() (unit-testable) and added the key. (2) a
garbage checkedAt made the staleness math NaN >= interval (never
stale), freezing a corrupt cache forever — parseCache now treats an
unparsable date as no cache so the next request schedules a refresh.
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/needs-review No maintainer or bot review yet labels Jul 11, 2026
@swissky

swissky commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Both findings addressed in 79abc28:

  1. updateCheck now reaches the runtime. Extracted the inline object into buildSerializableConfig() (exported @internal for testing, same pattern as buildImageRemotePatterns) and added updateCheck to it. Added a unit test asserting the option survives serialization, so the next "on the type but not in the virtual config" slip fails a test instead of silently no-oping.

  2. Unparsable checkedAt no longer freezes the cache. parseCache now rejects a checkedAt that doesn't parse as a date (Number.isNaN(Date.parse(...))), so a corrupt row is treated as "no cache" — the next request schedules a deferred refresh that overwrites it. Covered by a new test.

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants