The store settings release. Every settings page WooCommerce registers now lives inside Minn, read from its own settings registry and saved through its own pipeline, with payments, shipping zones, tax rates, webhooks and API keys managed in place rather than linked out. Novamira sites gain Agent Access, one view of every credential and every ability an AI agent may use on the site. Update everything runs as one batch and reports it like a package manager, every plugin and theme card opens its changelog, and cards link to where each plugin lives in Minn. The cycle closes with a full security pass over the release candidate: four medium findings and nineteen low ones fixed, each with a probe that failed before and a suite check that holds after.
Added
- Store settings: every WooCommerce settings page, inside Minn. Commerce gains a Store settings item (for anyone who can manage WooCommerce) with a section for each page WooCommerce registers: General, the four Products sections, Tax options, Shipping options, Accounts & Privacy, Emails, Page setup, Features, WooCommerce.com, and every extension page (Subscriptions, Point of Sale and the like) with no work on Minn's side, because the form is read from WooCommerce's own settings registry at request time. Countries pick from a searchable list, the store location is one country-and-state combobox, pages are comboboxes, the account retention rules are a number and a unit, the email logo is a media pick. Saves run through WooCommerce's own save pipeline, so its sanitizers, its page-specific rules (a terms page can never equal the checkout page) and every extension listening for a save all still apply, and a switch a save did not touch is never flipped. The Emails section lists every email WooCommerce sends with an on/off switch that saves in place and an Edit that opens the email's own fields (recipient, subject, heading, additional content, format). The few controls that are WooCommerce's own React (email preview, Blueprint) count as locked with the wp-admin link, and the Coupons empty state now opens Store settings instead of leaving for wp-admin. A Payments section lists every payment method in the order shoppers see it at checkout: drag a row to reorder, switch a method on or off in place, and open one to edit its title, instructions and the rest of its own settings (Cash on delivery's per-shipping-method rule included). Adding a provider, WooPayments onboarding and gateways with their own screens link to WooCommerce. Shipping zones live in the same page: zones in the order they are tried (drag to change it), the everywhere-else zone, and a zone editor with the name, regions picked from continents, countries and states, a postcode list, and the zone's shipping methods: add one from WooCommerce's registry (its settings open straight away), switch it on or off, drag its order, open its own cost and rules, remove it. Shipping classes add, edit and delete beside them. Local pickup for block checkout stays in WooCommerce. Tax rates get one section per tax class beside Tax options: every rate as a row (country, state, postcodes, cities, rate, name, priority, compound, shipping), add and edit through a form, drag to change the match order, delete, and Export CSV in WooCommerce's own column layout so the file imports back; adding a class in Tax options adds its section. Under Advanced, Webhooks list, add, edit and delete (name, status, topic including custom actions, delivery URL, secret, API version) and REST API keys list who each key acts as, its permissions and last use, add one (the consumer key and secret are shown once, the way WooCommerce does it, and never kept by Minn), change its description, user or permissions, and revoke it. Deliveries, logs and Blueprint stay in WooCommerce.
- Novamira: see who can drive the site through an AI agent, and what they may do. Sites running Novamira (the MCP server that lets Claude, Codex, Cursor and other agents work inside WordPress) get an Agent Access view under Tools. Its status card says whether AI abilities are on, how many connections exist, how many abilities an agent may call, and the endpoint to copy, with a switch to turn abilities on or off. The Connections list shows every credential that can reach the site (OAuth apps connected through you, admin-created client ids and Novamira application passwords) with Created, Last used and Expires, and Revoke on each, run through the plugin's own revocation. The Abilities view opens on a Context tab holding the site instructions every agent session reads, then one switch per registered ability grouped by category (Novamira's code execution, filesystem and block-editor abilities beside WooCommerce's, Yoast's and the rest), saving into Novamira's own rules. Tokens and passwords never appear. With Novamira Pro installed, the card reports its license and how many of its forty specializations (page builders, themes, field and SEO plugins) apply to this site, the license activates and verifies from Extensions → Licenses, and a Memory view lists what agents remember between sessions, with edit and delete. Connecting a client, Chat, Design and Skills stay on Novamira's screens.
- Update everything on Extensions is as fast as the command line, and shows its work. The button used to update plugins one request at a time so every card could show its progress, and each request re-checked for updates against WordPress.org and every plugin's own update server, because the previous install had changed what was installed; on a site with a couple of hundred plugins that check dominated the wait. The whole batch now runs as one request, the same path
wp plugin update --alltakes (one update check, one bulk upgrader run), and a panel opens the way a package manager reports an upgrade: every plugin on its own line with its old and new version, the download column filling in as packages arrive side by side (packages are fetched six at a time before anything installs, WordPress.org and vendor ones alike, the latter with whatever headers or cookies the plugin's own updater attaches, and each with its size), then Extracting, Installing and a green tick moving down the list, and a closing line with the totals: how many updated, how much was fetched and how long each stage took. Hide the panel and the batch keeps running; a chip in the top bar counts it down and opens the panel again, and the cards keep showing Queued, Updating and done as before. The WordPress chip in the top bar turns while a core update runs, too, instead of sitting still as if nothing were happening. If the connection drops mid-batch (some hosts recycle the PHP worker while a plugin is being replaced), Minn keeps following the batch's own progress record and then re-reads the offers, so nothing stays marked as updating, and while WordPress holds the site in maintenance mode it says so rather than giving up. That window used to blind the panel entirely: WordPress answers every request with a 503, REST included, for as long as an active plugin is being replaced, so the first run on a real site froze at Installing 0 of 26 while the batch quietly finished. The batch now also writes its record to a file, and a small reader that does not load WordPress hands it back, so the panel keeps moving through the whole install and the console stays free of 503s. Measured on a lab site with eleven pending updates: 8 seconds end to end, 0.7 of them fetching 20 MB, against 7.4 for the CLI; the notifications panel's Update everything opens the same panel. - Plugin cards link to where the plugin lives, and its views link back. An active plugin's card on Extensions now carries a row of doorways: chips for every Minn view built on it (the Forms view for Gravity Forms, Spam settings for Antispam Bee, Store settings for WooCommerce, its row on the Licenses tab), and its own screen in wp-admin for whatever Minn does not cover yet. That link is the Settings link the plugin itself declares to the Plugins screen, or failing that its admin-menu page, both read by the same hidden admin pageload the notice digest uses, so it is right for hundreds of plugins with nothing configured. The right-click menu lists every screen the plugin has. The same doorways run the other way: the provider name in the top bar of a Forms, Backups or any other plugin-backed view opens a menu with the plugin's own screens, its changelog and its card (click the name; right-click it where the name is a provider switcher), and the command palette lists every active plugin's settings screen ("Antispam Bee: Settings"). Adapter authors declare all of this with one
pluginkey on the surface descriptor. - Every plugin's and theme's changelog, one click from its card. The version number on a plugin or theme card is now a link that opens its changelog in the changelog window, whether or not an update is pending. The version rail reads the same for every vendor: one release per chip showing the bare version, its date on the line beneath, and the release you are running marked "installed"; whatever else an author put in a heading ("Version", "Date:", brackets, a "Hotfix" tag) is stripped. Notes come from the same source wp-admin's "View version details" reads, so plugins on WordPress.org and plugins with their own update servers both work, and when nothing is published there Minn reads the changelog the plugin or theme ships in its own folder (changelog.txt, or the readme's changelog section), which is what makes themes possible at all: WordPress.org publishes no release notes for themes. A card with a pending update also carries a "What's new" link (and the same entry in its right-click menu; a plugin without one gets a "Changelog" entry there) with the installed and offered versions named above the notes, the vendor's upgrade notice when there is one, and a note when the bundled copy stops at the installed release. A plugin or theme that publishes nothing anywhere says so and links to its own page. The notes are reduced to plain text markup on the server before they reach the page, and Minn's own card opens the bundled changelog.
Fixed
- A removed language no longer reappears under packs waiting to update. The update transients are rewritten by whichever check finishes last, so a scheduled check that began before a language was removed could land its packs afterwards and the Translations tab would offer updates for a language that was just deleted. The pending list now only counts packs for languages the site still has installed, which is what WordPress asks the directory for anyway.
- Clearing a list search with the × keeps the caret in the box on every list. On Customers the click moved focus onto the × itself, the reload repainted the toolbar, and the caret landed nowhere; the other lists put it back by hand. The × now never takes focus, so the shared restore covers all of them.
- Template names no longer get clipped by long descriptions. In Design → Templates, "All Archives" read "All Arc…" while the description beside it wrapped happily; the two shared one line and the browser shrank both in proportion to their natural width. The name now keeps its full text up to most of the row, the description takes what is left, and on a phone it drops to its own line under the name.
- The editor fits on a phone with the keyboard up. The formatting toolbar was wrapping to three rows and, with the keyboard covering the lower half of the screen, the two together left almost nothing to write in. It is now one row that scrolls sideways. The floating word count, which covered the last lines being written and caught the thumb on every scroll, now sits in the flow under the text instead, where it still opens the word-goal popover.
- Money the client adds up shows the store's currency. A customer's Recent total was summed in the browser and printed with a dollar sign whatever the shop sells in, so a GBP store showed a pound total in each order and a dollar total above them. The store currency now rides the app's boot data, and every place that used to guess a dollar (that total, the Orders analytics cards, a coupon's fixed amount, a booking price, and a subscription in the store's currency) prints the shop's own symbol.
- The membership page's "Grant membership" button is now "Add plan". It creates a membership on the chosen plan for that person, and the old label read like WooCommerce's tool for granting access from past purchases.
- The Plans list no longer crushes the plan names. Ten fixed-width count columns left Plan, Access and Length a few letters each on an ordinary screen. The list keeps members, active and expired beside products and rules, gives the text columns a floor, and scrolls sideways on a narrow window rather than clipping the Status column; the plan's own page still breaks members down by every status.
- Five-digit entry numbers no longer run into the Entry column. The ID column on forms lists was a fixed forty pixels, wide enough for the Users page it was copied from and not for a Gravity Forms site whose entries are numbered in the tens of thousands, where the number overlapped the name beside it. The column is now sized from the longest ID on the page.
- A Contributor can no longer switch on public preview for someone else's draft. The route that toggles Public Post Preview checked permission on a post id from the request body while acting on the id in the address, a mismatch the v0.40.0 path-parameter sweep introduced, so a Contributor with one draft of their own could enable the anonymous preview link of any unpublished post and read it back. The gate now reads the same address id its handlers use, and the last few routes still reading an id from the request body (JetEngine content types, ACF field-group verbs, OttoKit requests, media replace and regenerate, media folders, Performance Lab features, surface setup) were moved to the address as well. Found by the release audit of the shipped 0.40.0 build.
- A serialized blob nested too deep to inspect is refused, not passed through. The decoder Minn uses for other plugins' stored data stopped looking thirty-two levels down and treated whatever sat below as clean, so an object wrapped in enough arrays came back as data. Anything it will not descend into now counts as holding an object and is refused, as the helper it replaced did.
- Memberships: a refused plan save no longer leaves a half-saved plan. Access method, products, length and dates were checked only after the name, slug and status had been written, so a save the plugin then refused still renamed or published the plan. Everything is validated before the first write. In the same round, rule saves keep a rule's integration flags (a Courseware auto-enrol switched off stayed off before, and reset to on after a save from Minn), and the status picker on a membership offers, and the save accepts, only the statuses the site's own screen would offer for that membership, honoring the plugin's per-membership status filter.
- WP Freighter: a tenant sign-in is checked before its token is minted. The one-time login link was minted first and its host compared afterwards, so a refused sign-in still left a live token behind, and the comparison looked at the host only: a scheme swap, a different port, or a backslash in the address (which PHP and a browser read as different hosts) all passed. The tenant's stored address is now held to the expected scheme, host and port before anything is minted, and addresses carrying a user name, a backslash or a non-web scheme are refused outright.
- Maintenance mode now holds WooCommerce's cart and checkout calls and the registration form. WooCommerce answers its ajax calls one step before the holding page ran, so a closed shop kept adding to carts and checking out; the holding page runs first now. The login page stays open so you can sign in to a site you are staging, but its Register form is held like the multisite signup page. Scheduled tasks and WooCommerce's payment-provider webhook stay deliberately open.
- The Media card's size line no longer lets any contributor make the server walk the whole uploads folder. When the cached size expired, whoever opened the dashboard next paid for a full walk of the media library, which on a large site is seconds every time. Only an administrator's visit fills the cache now, one walk runs at a time, and a walk that outlives its budget stops and shows what it counted as a floor ("over 3 GB used").
- Gravity SMTP log search matches message bodies only for people who may open a message. Anyone who could see the log could search inside message bodies, which let a substring at a time reveal a body the site had kept them from opening, such as a password reset link. Subject, recipient and service still match for everyone.
- A duplicated Elementor template is cleaned the way Elementor's own save cleans it. For someone without unfiltered HTML rights the copy was cleaned as one encoded string, which can re-quote attributes inside the template, and the page settings were copied as they were. The template is decoded first and both it and the settings go through Elementor's own filter.
- Surface forms refuse a field key that names the prototype chain. The dotted-key setter behind every surface form gained the same guard its membership sibling already had, and the Integrations card flags a descriptor whose field key would walk into it.
- Agent Access: an AI agent can no longer switch its own disabled abilities back on for a request. Minn lifts Novamira's ability policy so its Abilities view can list the switched-off rows, and it decided which requests get that exemption by looking for its own path anywhere in the request URL, query string included. A call to the Novamira MCP server or to an ability's run route that named that path in a query argument was treated as the Abilities view and ran with every disabled ability restored. The exemption now keys on the route WordPress actually resolved, as a strict prefix, and only for someone who may manage Novamira.
- Store settings: editing one field on a payment method, email or shipping method no longer resets the rest. These forms keep their values on the object, not under a bare option name, and the reader fell through to a site option of the same name, found nothing and used the default. Changing a gateway's title posted every other field back at its default: the gateway switched off and any stored credentials blanked. The reader now uses the value it read from the object.
- Store settings: a shop manager cannot revoke or take over an administrator's REST API key. WooCommerce's own Keys screen lets someone edit or revoke a key only when they can edit its owner or it is their own; Minn's revoke checked only that the caller may manage WooCommerce, and its edit checked only the new owner. Both routes now apply the same owner rule, so a shop manager still manages their own keys and nobody else's.
- Update everything installs only what the package check accepted. Minn's own updates are verified against the sha256 in the release manifest before they install. The batch updater prefetches every package and handed its copy to the installer regardless of what that check had said, so for Minn's own package the verified download was thrown away and a refusal was overridden by the unverified copy. The batch now defers to an earlier answer, and a refused package reports as failed.
- The database browser describes credentials instead of showing them. Password hashes, pending reset keys, sign-in session tokens, application-password hashes and WooCommerce API secrets now appear as a placeholder with the byte count, in the list and in the row detail. The browser is read-only and administrators only, but a page of hashes is still an export nobody needs on screen.
- Update everything follows the site's HTTP rules when it fetches packages ahead of time. The prefetch now honors a site that blocks outbound requests except to named hosts, routes through the configured proxy, and re-checks every redirect the way WordPress does, so a package host cannot bounce a download to a private address.
- Bulk-update progress records expire. The progress file a batch writes for the maintenance-mode window is refused an hour after it was written, and its folder refuses direct web access, so a link left in a browser history or a log stops answering.
- Who sees a session's origin. The IP address, browser and sign-in time of another account's sessions are shown to administrators and to the account holder; a role that may merely edit the user, such as a shop manager over customers, sees the session count and the sign-out buttons only.
- The Modified filter answers only for people who may edit others' posts. It narrows the list to posts carrying unsaved edits across every author, so a writer limited to their own posts no longer sees the chip or the count.
- Post types and taxonomies refuse names WordPress reserves. A type named order, author, s or any other built-in query variable breaks the site's own queries the moment it exists; the create forms now refuse those names before anything is written, the way CPT UI and ACF do on their own screens.
- Adapter permissions match their plugins more exactly. Bookly's surface requires a signed-in caller. WP Activity Log's log stays closed when neither of its permission helpers is available, instead of opening to every administrator. Flamingo's restore, WPForms' trash, spam and restore, Everest Forms' restore, Redirection's status card and settings read, and Gravity Forms feed toggles all use the same capability the plugin's own screen asks for. Rewrite Rules Inspector's flush honors the plugin's opt-out filter.
- Ninja Forms entries restored from the trash come back where they were. WordPress restores a trashed item as a draft, which neither Minn's list nor Ninja's shows; a restored entry now returns to its prior status.
- A designer renaming or switching off a Custom CSS & JS snippet no longer alters its code. Saving the name or the switch ran the stored JavaScript through the HTML filter for a caller who may not author code, and the live file was rebuilt from the altered body. The body is left alone on those saves.
- Gravity SMTP resends behave like Gravity SMTP's own. A resend attaches the copy Gravity SMTP archived for that message rather than whatever now sits at the original path, and goes to the addressee only instead of every cc and bcc.
- JetEngine text fields save the way JetEngine saves them. A textarea set to Plain text or Safe HTML now applies that format and its maximum length, and a plain text field stores no tags, matching the plugin's own meta box.
- ACF relationship and user fields keep values the writer could not see. Saving a panel used to drop a related post the writer may not read, or an account they may not list, because the form sent back what it was shown. Those values now stay put; removing one the writer could see still removes it.
- License keys travel in the request body only. The activation route reads the key from the body, so a key can never ride a query string into a web server log.