Skip to content

v0.21.0

Choose a tag to compare

@austinginder austinginder released this 24 Jul 13:27
· 270 commits to main since this release

Minn Admin v0.21.0 — July 24, 2026

The trust release. This cycle is for everyone who is not the person who built the app: the interface gains real translation plumbing so it can meet users in their own language, the app chrome passes a genuine accessibility audit with a suite that keeps it honest, and a plain-words user guide ships inside the plugin, one click from help. The same spirit runs underneath: updates verify themselves against a published checksum before they install, a security policy opens a private door for researchers, a tab left open overnight recovers on its own, and boot rides one request instead of nine so shared hosting stops watching panels trickle in.

Fixed

  • Pending comments stay in the moderation queue: the Overview's Recent activity feed included comments awaiting moderation (author names and all) for every user who could see the dashboard, even though the Comments view, the notification panel and the pending row's own click-through are all reserved for moderators. The feed now applies the same rule everywhere: users who can moderate see pending rows, everyone else sees approved comments only.
  • Test scaffolding no longer ships in the notice pipeline: the two dev-fixture ajax handlers used by Minn's own browser suites lived in the shipped whitelist, reachable (though harmless) on any site. The notice button mapping is now filter-open like the whitelist itself, and the fixture handlers moved to the dev site's fixtures plugin where they belong. Production installs carry no fixture code paths.
  • A tab left open overnight recovers on its own: REST nonces expire after a day, and an expired one used to dead-end the app (every request failed with a raw error toast until a manual reload). Minn now notices the expired nonce, mints a fresh one in the background through WordPress core's own nonce endpoint, and retries the request; a whole page of parallel requests shares one refresh. If the login session itself is gone, Minn says so and reloads into the login flow instead of leaving dead buttons.
  • Typing a license key can no longer be interrupted: a background loader resolving late could rebuild the Licenses tab out from under an open key form, discarding the form and the key being typed before the activation request even returned. A stray rebuild now leaves an open paste form alone; only the renders that legitimately dismiss or replace it (the post-action refresh, Cancel, the inactive-components toggle) still swap it out.
  • Three low-severity hardening fixes from a full security audit: a line-by-line pass over the REST surface, adapters and app shell turned up no serious issues and three small gaps worth closing. A public read of a post no longer reveals the name of whoever happens to have it open in the editor (that "someone is editing" signal is for the dashboard, not the world). The Custom CSS & JS reader now refuses to reconstruct PHP objects from stored data, matching the safe pattern the rest of the integrations already use. And the small block of startup data the app inlines is encoded so a site name or display name can never break out of it. None of these were exploitable in normal use; all three are now closed.

Added

  • A guide for the people who actually use Minn: docs/user-guide.md is the new site-owner manual, covering getting around (the sidebar groups, the command palette, and the fact that right-click works nearly everywhere), writing and the editor's safety model in plain words, daily site care, hiding what you don't use, every keyboard shortcut, and an honest safety section: what happens when you deactivate (nothing), how updates are verified, and who can open the app. It ships inside the plugin so the copy you read always matches the version you run, and it is now part of the release checklist so it stays current. The guide is one click away in the app itself: a User guide button on the About dialog and an "Open the user guide" palette command render the bundled copy in a reader modal, with the shortcuts table and all, no internet required.
  • The app chrome passes a real accessibility audit: every main view now audits clean with axe (zero violations, both themes), and a dedicated browser suite keeps it that way. Screen readers hear each navigation announced and see the active nav item marked as the current page; keyboard focus is rescued when a view swap would have dropped it; every page has a proper top-level heading; the sidebar is a labeled navigation landmark; icon-only buttons and list checkboxes carry real names ("Select Hello world", not silence); sort buttons speak their direction; and users who prefer reduced motion get an interface that stops animating. Muted text and status-chip colors were nudged to meet WCAG AA contrast in both themes: same hues, slightly more present, with pills and dots keeping their original tones.
  • Minn is ready to speak your language: the app now has real internationalization plumbing. Interface strings translate through standard WordPress translation files (the same .po/.pot format every translator already knows), each user sees the interface in their own profile language, and English remains the built-in default so nothing changes until a translation exists. The app shell (navigation, page titles, toolbar) is translated first; the rest of the interface converts view by view. Plugin authors' own labels stay theirs to translate.
  • Boot rides one request instead of nine: the startup burst (notifications, plugin and update caches, core status, the pending-comment badge, post types and the order summary) now arrives in a single consolidated request. Each section is produced by the same route the standalone fetch used, so nothing changes shape, and a section the server cannot provide falls back to its old standalone fetch automatically. On shared hosting, where a handful of PHP workers had to serialize nine parallel requests, the app's panels stop trickling in one by one.
  • Updates are checksum-verified before they install: the release manifest now publishes the sha256 of each release zip, and the self-updater downloads the package, checks it against that hash, and refuses to install on any mismatch. The manifest travels from the GitHub repository while the zip comes from the release CDN; pinning the hash ties the two together, so a tampered or truncated download can never reach your plugins directory.
  • A private channel for security reports: the repository now carries a security policy with GitHub private vulnerability reporting enabled, so a researcher can disclose quietly and expect an acknowledgment within 48 hours. It also includes design notes for reviewers, the properties worth knowing before an audit: the capability gate and per-route permission checks, the descriptor-only integration boundary (third-party PHP never runs in Minn's render paths), the no-unserialize and prefix-scoped-SQL shim rules, and the checksum-verified updater.

Improved

  • The help dialog helps first: the About dialog now leads with what a person clicking "?" actually needs: how to get around, the fact that right-click works nearly everywhere, and how to hide what you don't use, followed by the keyboard shortcuts. The philosophy is still there, condensed at the end, and the User guide button remains one tap away.
  • The plugin and its website finally point at each other: the plugin's listed website is minnadmin.com now (it was the GitHub repository), and the About dialog links to the site and its new shareable docs pages at minnadmin.com/docs. The bundled in-app user guide is unchanged; the web pages are the linkable copies.
  • One listing, not two: the wp.org-style readme.txt is gone. GitHub is the distribution channel, so readme.md and minnadmin.com are the listing surfaces; the FAQ, security notes and user guide now live where people actually read them instead of drifting in a second copy.
  • List pages are ready to search the moment they open: navigating to Extensions (plugins or themes), Content, Media, Users, Orders, Terms and every other list view drops the caret straight into the view's filter box, so typing filters immediately with no click first. It happens once per navigation and politely: a re-render never yanks focus back, an already-focused text field (the palette, a modal input) is never robbed, and touch devices skip it so the software keyboard stays down.