Changelog

Releases since the 1.0 commercial launch, newest first; some point releases are folded into the card for the release that followed them. The full list is in the plugin’s readme. This page is written from the plugin’s own readme, so it says what actually shipped.

6.0.0

25 September 2026 current

  • The plugin is now Checknaut, and the add-on is Checknaut Pro. New name, new plugin folder, new logo. What it does, your licence and the connector address your assistant uses stay the same.
  • Moving to Checknaut: install Checknaut, activate it, then delete the old plugin. Settings, the connector address and key, history, snapshots and reports carry over. Deleting the old copy keeps your data: Checknaut switches off its cleanup, and Checknaut Pro does the same even before Checknaut is activated. Pro stays off beside the old plugin and offers an Install Checknaut button. The FAQs have the steps.
  • The download is now checknaut.zip. The Download free buttons on this site already point at it.
  • Verified: 121 harness runs green, and the rig harnesses green on the plain, Divi 5.13.1 and Elementor 4.3 test sites (WordPress 7.1), including a new 78-check move test run against the real 5.0.2 download. The full self-test with an owner seeded first changed 0 of their rows: Divi 3,297 of 3,297 with Pro and 2,402 of 2,402 free; block editor 2,203 of 2,203 with Pro; Elementor 2,388 of 2,388 with Pro.

5.0.2

25 September 2026

  • The self-test no longer deletes or changes anything of yours. On 5.0.0 a full self-test (scope all) on a Divi site could delete a published style guide page, remove type and spacing scale tokens and the accent colour-role binding, and prune restore points. It now leaves every row that existed before the run byte-identical, with nothing left behind, checked with an owner seeded first on Divi 5.13.1, Elementor 4.3 and block-editor test sites (WordPress 7.1).
  • On Divi the skip link now moves keyboard focus, not just the view.
  • Pro: station_get_woo_context on Elementor now names the store tools that work there (product and shop templates, product loop grids); station_build_woo_page’s plan describes the page it would replace truthfully.
  • 5.0.1 was prepared but not released: its final checks on a fresh WordPress found the self-test’s menu check left a theme settings row behind. 5.0.2 puts it back, and includes everything from 5.0.1.

5.0.0

  • Renamed for the WordPress.org plugin directory, which does not allow WP in a plugin name. An existing site installs the renamed plugin, then deactivates and deletes the old 4.x plugin. Nothing moves: settings, the connector address and key, OAuth clients, history, snapshots and reports carry over, so no assistant needs reconnecting. The FAQs have the migration steps.
  • Free finds and fixes; Pro builds. The one-page limit is gone. Every audit and every one-click fix works on every page, site-level fixes and image compression included, with no licence, quota or trial. Page building, fixing a cause on every page at once, scheduled audits and client reports moved into Checknaut Pro. The site-wide broken-link scan is now free.
  • A new admin design with a tiered Site Score. The score reads as a launch track (Grounded, Liftoff at 60, In orbit at 75, Mission ready at 90), shows the points each audit is costing, counts up after every scan, and records a milestone in History when a full scan crosses a tier. Two themes remain, Light and Deep Space; Terminal is retired.
  • Design-system adoption and legal page drafts are free. Adopt the system your pages already use in one click, override any role, undo any change. Missing legal pages arrive as drafts, never published.
  • Deleting the plugin now removes its data. From 4.0.0 to 4.6.5 it did not; a new test that deletes the shipped 4.6.5 zip found it.
  • Divi 5 canvases, interactions and module locks. Off-canvas menus, popups and mega menus are audited, broken interaction triggers are reported, and a module locked in the Divi builder is never edited.
  • New free tools for your assistant. Check which plugin loads which files, describe images and write alt text, name icon links and buttons, check interactions, and list Elementor V4 classes and components. 53 free tools, 175 with Pro.
  • Site Health checks certificate and domain expiry, and audits page-builder performance settings and Divi and Elementor forms.
  • Checknaut Pro 5.0.0 for agencies. Tested updates: each plugin, theme or WordPress update is checked on key pages before and after, and a plugin or theme update that breaks a page is rolled back automatically. Screenshots through a screenshot service you choose, with a page compared against a mock-up. A fleet hub with every client site’s score, pending updates, security, uptime, certificate and domain expiry, alert emails and library push. Client approvals and a monthly care report, asset cleanup rules and client lockdown.
  • Checknaut Pro on Divi 5 and Elementor. Divi 5 interactions and HTML attributes, canvases, option-group presets and variables, loop filters and pagination, breakpoints, fonts and Divi JSON import and export. Elementor V4 global classes, variables, kit settings and default styles, and Elementor Pro theme templates, display conditions, popups, loops, dynamic tags and forms.
  • Checknaut Pro fixes. Rollback sessions now record, so station_restore_session has something to restore (the list was always empty from 4.1.0). A backslash in a page title is kept. With the licence not active, only the Checknaut Pro screen asks for it. Pro’s uninstall now runs. On a site still running the 4.x plugin, Pro offers an Install Checknaut button, which works while the wordpress.org listing is pending: it installs the official download only after checking its published checksum.
  • Also in the free plugin. Seven free tools now answer on Elementor sites in Elementor’s own vocabulary. Contrast checks and the design system resolve Divi 5 relative and nested colours. The Checknaut Pro screen, with locked previews of what Pro adds. A self-test that pauses between chunks keeps its start time and never takes a real fix with it. Plugin Check and directory clean-up, and suggested privacy-policy text for the site history.
  • Verified: 118 harness runs green. The 8 that need a real WordPress ran green on the plain, Divi and Elementor rigs (WordPress 7.1). In-plugin self-test at scope=all on the Divi rig: the free plugin alone 2,386 of 2,386 (admin-ajax) and 2,493 of 2,493 (MCP over REST); with Checknaut Pro active 3,279 of 3,279 and 3,447 of 3,447. Plugin Check 2.1.0 on the wordpress.org zip: 0 errors, 0 warnings.

4.6.5

  • Native AI clients connect with OAuth. Claude Code, Cursor, Codex and VS Code register loopback and app-scheme redirect URIs (RFC 8252). The connector routes are no-store and report a stale host name instead of failing silently.
  • Writes cannot overwrite a newer edit. Reads return a content hash; a page changed since it was read is refused, and so is one open in the WordPress editor. Search-and-replace and session restores apply only the plan that was previewed.
  • A compact profile for clients with a tool limit. Three tools (search, describe, run) instead of the full list, with every check still running on the real tool. The owner can switch off any tool, and the Connection tab tests the connection step by step.
  • Divi Theme Builder, Theme Options and site code (Pro). Template edit and resolve, Theme Options with undo, one labelled snippet per Integration slot behind “Lock site code”, batch module edits, module search and token usage.
  • Site operations (Pro). Redirects, a dead-link scan, users, ACF and Meta Box fields, media metadata, form notifications, term SEO and a plugin manager that backs up, probes and rolls back on its own.
  • What places a Divi box, explained. station_get_node explain=“alignment” names the setting that positions a node, measured from Divi 5.13.1’s source. Deleting a container asks for the module count first. HTML Before/After needs unfiltered_html, and a shared header or footer asks how many templates it reaches.
  • Site Health reads the files. Core checksums from WordPress.org, unknown PHP in core folders, PHP and code-enabling rules in uploads, OPcache, memory and database size. station_check_performance live=true measures how the page is actually served.
  • One-click pause, prompts, and take-it-with-you. An admin-bar switch refuses every AI tool call until you resume. Seven ready-made MCP prompts. The skill downloads as a SKILL.md, and settings export and import.
  • Stock photos and brand kits (Pro). Pexels and Pixabay with your own keys, credited in the caption. Six colour-and-type kits, every text pairing WCAG AA, undone with one snapshot. 128 tools: 51 free, 77 Pro.
  • The free plugin now gets update notices on sites that run Pro (4.6.2, fixed in 4.6.3). Pro reads the manifest published beside the free download and puts the update in the Plugins screen, checked against the published checksum before WordPress unpacks it. 4.6.3 starts the notice before Pro waits for the free plugin, the one case 4.6.2 missed. Checked on this site: Pro installed ahead of the free plugin, and the free update was offered and installed from the Plugins screen.
  • 4.6.4: self-test and pattern fixes from a client site. Three licensing checks failed on installs Pro reached through Freemius, whose processor re-prints the file they read; they now ignore layout and were run against the build Freemius serves. The shipped site-header pattern no longer uses the accent colour for its mobile panel or border.
  • 4.6.5: the free update shows up as soon as it is released. A Pro that updated first could wait up to 12 hours for the free plugin it needs, because the notice read a stored copy of the release; WordPress's update check now reads the release directly. Checked on this site: the Pro build Freemius serves went in ahead of the free plugin, and one click on Check again offered free 4.6.5, which installed from the Plugins screen.
  • 4.6.1: a self-test fix. One check still looked for the contact-form label advice 4.6.0 replaced with Divi 5.13’s Show Labels switch.
  • Verified: 78 harnesses green (the 5 that need WordPress on the rig); 288 recorded mutations each turn a harness red. Live on this site with the Pro build Freemius serves, on Divi 5.13.1 and WordPress 7.1.1: 1,004 of 1,004 read-side checks, 0 failures, 5 named as not runnable at scope=read; Patterns 74 of 74 with writes.

4.5.0

  • Divi 5 sites are built once, not once per device. A live header was found carrying a desktop section hidden on tablet and phone beside a mobile section hidden on desktop, six group modules of which two were empty, and a navigation hand-built from six text modules: 23 modules where Divi 5.13 needs six. Every attribute path was valid and every check passed, because nothing looked at the shape. Validate, create and update now report per-device copies, empty and pass-through groups, modules hidden everywhere and, on a header, a hand-built nav. A Theme Builder header or footer that is two copies of one thing, one per device, is refused by name before anything is written; two sections that hold different things per device are warned about and allowed.
  • Two shipped patterns give every site the same starting shape. site-header is section, row, column and one Menu module: logo slot, the WordPress menu, an inline-centred-logo desktop bar and Divi’s own hamburger and mobile panel. site-footer is one row that stacks on tablet. Colour roles bind to the site’s palette at insert, and the skill teaches the same recipe path by path, including the attribute that centres the bar.
  • Uppercase is native, and the map now says so. Measured on Divi 5.13.1: capitalization “uppercase” emits text-transform; the CSS-named textTransform stores and emits nothing. The native-attribute audit names the right key when a code module writes it as CSS.
  • The MCP endpoint serves resources, content blocks and tool filters. resources/list and resources/read under the wpbs:// scheme; protocol-era-gated content blocks with resource links; four filters that let an operator narrow what a connection sees, failing closed. Idempotency replay had read the wrong key for four releases, so replays never matched. Fixed and pinned on the wire.
  • 87 validators accepted a trailing newline. A $ anchor matches before a final newline in PHP. Every one is now \z, and a harness refuses any new one across 115 files, including the eleven ways of writing one that a grep misses.
  • Menu writes: classes were stored as the word “Array”, and append landed first. Both fixed. The check that said “append adds to the end” had pinned the count alone for four releases and now reads the position back.
  • Verified: 56 harnesses green with 0 failures; 22 mutations on the structure checks, 51 on the MCP work and 16 on the needle sweep each turn their harness red. Live on this site on Divi 5.13.1 and WordPress 7.1.1: 1,004 of 1,004 read-side checks, 0 failures, 5 named as not runnable at scope=read. The rebuilt live header and footer were checked at 1440, 820 and 390 pixels.

4.4.11 & 4.4.12

  • The Connection screen was still printing the token, twice, after 4.4.9 masked it. 4.4.9 masked the connector URL and the matter was treated as closed. The same token was rendered twice more on the same screen, a few inches below the URL that had just been masked: once as an Authorization header, once inside the cache-buster URL. Both sat in plain blocks with no copy button, which is why neither had been touched. A screenshot of that screen still carried a working credential, which is the exact thing 4.4.9 was written to stop. It was found by screenshotting the tab to check an unrelated panel and reading the token out of the image.
  • Both render masked now, and both gained a Copy button they never had. Four leading and five trailing characters, Copy carrying the real value, Reveal off on load and re-masking after thirty seconds. One masking function serves all three places the token appears, so a fourth cannot drift from the rest.
  • The Reveal control would have shipped dead and said nothing about it. Its script found the row it belongs to by the URL bar’s own class name, written for the one place that masked a secret and then hardcoded to it. The two new rows render the button and the listener would never have attached. It matches a contract now rather than a class name.
  • The check guarding that screen was matching the markup instead of asking the question. It looked for one exact class name on one exact span. That made it fail on a correct change in 4.4.11, and it is the reason it stayed green through three releases while the token sat in plain sight twice on the screen it was guarding. It now asks whether the token can reach a screen unmasked at all, which does not depend on what anything is called.
  • Verified: 49 harnesses green with 0 failures, the full self-test at 1,887 of 1,887 at scope=all on a clean WordPress with 52 named as not runnable there, and live on this site on Divi 5.13.1 and WordPress 7.1.1: 1,004 of 1,004 read-side checks, 0 failures, 5 named. Four mutations were tried against the widened check, including restoring each unmasked print. Each one turns it red, and the rewritten in-plugin check was mutation-tested against the real defect rather than a renamed class: restoring the unmasked header takes the rig to 1,886 of 1,887 with that row red, where the old check did not move at all.

4.4.10

  • A check the self-test could not run is no longer counted as one that passed. Fifty places in the suite needed a fixture the site itself has to supply: a plain colour in the palette to plant in markup, a preset to collide attributes against, a compiled CSS file on disk, a loopback request the server will answer. Where it was missing, the row read “Skipped” and counted toward the total. That reads as a skip to a person and as green to the arithmetic, and the arithmetic is what gets published. Those rows are now named in full, each with its reason, and kept out of the count.
  • The number went down, and that is the fix. On the same clean WordPress, 4.4.9 reported 1,929 of 1,929. 4.4.10 reports 1,887 of 1,887 with 52 checks named as not runnable there. Live on this site: 1,004 of 1,004 with 5 named. Nothing got worse. Some forty rows stopped claiming to be something they were not.
  • What that hid, found the day it shipped. The advice for a colour literal inside a module’s raw content was widened and reworded in 4.4.9. The check that holds it honest needs a plain six-digit colour in the palette, and the test rig has none, so it never ran there while its row counted toward the green. The first real Divi site the build reached turned it red in one pass. It now matches the sentence that explains why, which is the part a rewording has to keep.
  • The admin note that names skipped groups was one rename away from going silent. It found them by matching the word “Skipped” in a row’s name. Renaming the rows would have emptied the note while every test around it stayed green. It reads the row’s status now.
  • Self-update has a switch you can reach. The tool that installs a published build over the running one shipped behind an operator switch that was off by default, which is right, and that nothing in either plugin could turn on, which is not. Its own refusal named a screen with no such control. It was unreachable for four releases and the message read as the owner’s oversight. The Connection tab has the switch now.
  • Verified: 49 harnesses green with 0 failures, the full self-test at 1,887 of 1,887 at scope=all on a clean WordPress with 52 named as not runnable there, and live on this site on Divi 5.13.1 and WordPress 7.1.1: 1,004 of 1,004 read-side checks, 0 failures, 5 named. Nine mutations were tried against the two new gates. Restoring a single skip-as-pass moves the rig to 1,888 of 1,888 with 51 unrun, which is the fake pass reappearing in the total, and the gate turns red on it.

4.4.8 & 4.4.9

  • The connector key is no longer printed on the screen that shows the connector. That URL is the whole login. Anyone holding it drives the site through the MCP endpoint, deletes included. It was rendered whole in a large monospace block. People screenshot that screen: a support ticket, a tutorial, a client handover. Every one leaked a working credential, and nothing about the leak was visible at the time. The key now shows four leading and five trailing characters with bullets between, which is enough to tell two keys apart in a support thread and not enough to use. Copy still carries the real URL, and Reveal is off on load and re-masks itself after thirty seconds.
  • A mismatch between the free plugin and the Pro add-on is now named. The two release together and nothing checked at runtime. One of them sat a release behind the other for a day here, after an upload that reported success and did not take, and the live self-test ran 1,006 of 1,006 straight through it, because every check it runs is satisfied by either version. Preflight now shows both versions and warns when they differ.
  • A colour literal inside a module’s content gets advice that works there. The token form resolves only in module attribute values; inside raw content it renders as literal text. The audit knew that about code modules alone, so a text module with inline styles was told to use a token that would have broken the page. The check now asks where the literal sits rather than what the module is called, and recommends the CSS custom property instead.
  • The plugin leads with what the audit checks rather than how many tools it ships. The short description is now “Seven audits over every page, on any theme. Fix what it finds, undo anything, and see what it could not check. No overlay, no script on your site.” The WordPress.org readme opens the same way, and now carries the disclaimer it was missing: audit output is informational, not a certification.
  • Verified: 47 harnesses green with 0 failures, and the full self-test at 1,929 of 1,929 at scope=all on a clean WordPress. Nine mutations were tried against the new and rewritten checks — the raw key printed again, the mask widened, Reveal made permanent, a version mismatch reported as a pass, the two trees drifted apart, the old sign-in trap re-created, and Copy broken while the mask stayed. Each one turns the right check red.

4.4.6 & 4.4.7

  • The WordPress.org review pass, and the free plugin now reports zero errors. WordPress’s own plugin checker, run on a clean WordPress 7.1 and PHP 8.4, found 49 errors in the free build. It now finds none. The last recorded figure before tonight was 873 findings against 3.3.4 — and most of that gap is not work: 540 of those lived inside the licensing SDK, which the free plugin stopped carrying at 4.0.0, so the question every plugin sold this way gets asked went away with the folder it lived in.
  • WordPress’s own helpers, where they are the right call. Fifteen file, URL and text operations now go through WordPress rather than PHP directly. Nothing about the behaviour changed, and the tests covering those paths are green unchanged.
  • One real defect, found by the checker rather than by us. The Site Score was written into the admin dial without escaping. It holds a number today and that is not the point; it is escaped now.
  • Everything else is explained rather than silenced. Each remaining exception carries the reason beside it — why the audit deliberately reads pages a translation plugin would hide, why a fixture URL points at a reserved example host, why the fuzz seed is fixed on purpose, and why the sign-in redirect cannot use the “safe” variant without refusing every legitimate sign-in.
  • Two changes went the other way, because a test said so. The recovery code is deliberately WordPress-free — it is what runs when WordPress cannot — so it keeps the plain PHP function, with that reason recorded. The taste engine does run inside WordPress, so it kept the WordPress function and its tests gained a faithful copy instead.
  • Verified: 45 tests green with 0 failures, the full self-test at 1,928 of 1,928 on a clean WordPress in continuous integration, and live on this site, on Divi 5.13.1 and WordPress 7.1.1: self-test 1,006 of 1,006 read-side checks, 0 failures. No behaviour changed in either release.

4.4.5

  • The readme said 100 tools. There are 105. 51 free, 54 Pro. It had been wrong since 4.0.0 split the free and Pro builds, because nothing ever compared that line to the plugin it describes. A new gate reads the tool manifest and the free-tool list and holds the readme to them, so the number cannot drift again without turning the build red.
  • A figure nothing produced has been removed. The readme advertised a “2,400+ check self-test”. That number dates from 2.3.8 and no run since has produced it. What replaced it names its own run: 1,928 checks on a clean WordPress in CI, and 1,006 read-only ones on this site. The same gate now refuses any check-count claim that does not say where the number came from — the rule this project already applies to changelogs and to this website, finally applied to the readme as well.
  • The description reads better and claims exactly the same things. A 78-word sentence carrying a nested aside became three; a list of unsupported page builders moved off its dashes and into parentheses. Both readmes now score 5 of 5 on an AI-copy linter. Nothing in the changelog history was touched: a past entry is allowed to record a past count.
  • Verified: the gate was mutation-tested five ways before it was believed — wrong total, wrong free count, the old unattributed figure restored, a miscounted language claim, and a deleted tool entry. Each one turns it red. 45 offline harnesses green on PHP 8.0, 8.4 and 8.5, plus the five that need a real WordPress, including the full self-test at 1,928 of 1,928. Live on this site, Divi 5.13.1 and WordPress 7.1.1: self-test 1,006 of 1,006 read-side checks, 0 failures. No product code changed in this release.

4.4.4

  • The primary button is flat. It was a vertical gradient under a glow; it is now one solid colour — the same blue this site uses on its own download button. That also settles a question the gradient could not answer: a gradient gives a label a different background at the top of the letters than at the bottom, so “is this label readable on this button” had no single answer. It does now, and the hover state, which nothing had ever measured, is checked too.
  • The score no longer runs under the ring in the Terminal theme. Every character of that theme’s pixel face is exactly one em wide, where the normal face uses about half that — so at the same size the number was half as wide again as the dial it sits in, measured at 138px inside a 99px ring. The denominator now sits under the number instead of beside it, which keeps the score the biggest thing in the dial rather than shrinking it to fit.
  • Light and Deep Space are one typeface plus code. A single token was doing two jobs: the face for code — arguments, JSON, tokens, a licence key, a URL, where a zero must not read as an O — and the face for small labels, where it was only texture. That left 42 elements rendering in the system monospace next to Plus Jakarta Sans, which reads as a second typeface nobody chose. The two are separate now: code keeps a real monospace everywhere, labels follow the theme. Those two themes render ten monospace elements now, all of them genuinely code.
  • Verified: 49 harnesses green with nothing skipped; the full self-test at 1,928 of 1,928 on a clean WordPress 7.1. Each change has a check that a deliberate reversal turns red — including one caught passing vacuously and rewritten to read the button’s own rule. Live on this site, on Divi 5.13.1 and WordPress 7.1.1: self-test 1,006 of 1,006 read-side checks, 0 failures.

4.4.3

  • A Free owner’s first self-test could bind their one free page to a probe and then delete it. One check wrote through the tool surface, and on a site with nothing bound yet the page gate binds on the first write — to that check’s own throwaway fixture, which it then removed. The run the tool calls “safe on production” left the binding pointing at nothing until the owner released it in wp-admin. Fixed, with a row that pins the binding is exactly as it was found.
  • robots.txt could advertise a sitemap that is not there. Without pretty permalinks WordPress serves its sitemap index at ?sitemap=index, not /wp-sitemap.xml, and the hardening switch reported the pretty path either way. That string is also quoted back to you in the “robots.txt does not name a sitemap” finding, as the line to paste — so the fix named a 404. It follows the site’s permalink structure now.
  • The whole self-test now runs in CI, on a clean WordPress, before every tag — 1,928 checks in five seconds, the write path included. 4.4.0 and 4.4.1 each went 1,004 of 1,005 live because a check that reads the stylesheet had drifted from the CSS the same commit changed, and those checks only ever ran live, after the tag. Mutation-tested three ways: break the header-mark needle, end a group early, or put the sitemap bug back, and it goes red on exactly that row.
  • Running it found nineteen checks that had never once run. Every self-test in this plugin’s history ran on a licensed site, so the unlicensed paths were never exercised — and the suite knew one way for a paid tool to be unavailable (the free build, code absent) but not the other (the paid build with no licence, which is what every site runs between installing and paying). Nine read the paywall’s own refusal as a broken feature; two were wrong about the product. They assert the real behaviour now.
  • Verified: 44 harnesses green; CI green on PHP 8.0, 8.4 and 8.5, with the full self-test at 1,928 of 1,928 on a clean WordPress 7.1; both zips reproducible to the byte and the download verified as served. Live on this site, on Divi 5.13.1 and WordPress 7.1.1: self-test 1,006 of 1,006 read-side checks, 0 failures.

4.4.2

  • The self-test’s read scope now runs in CI, on a clean WordPress, before every tag. 4.4.0 and 4.4.1 each went 1,004 of 1,005 live for the same reason: a check that reads the stylesheet had drifted from the CSS the same commit changed, and those checks only ever ran live, after the tag. A new rig harness boots the CI WordPress, runs the suite’s read scope and fails on any failing row or PHP warning. Mutation-tested: breaking the header-mark needle in the CSS turns it red on exactly the 4.4.0 row. The Divi-only groups skip there and are reported, not counted — the live number below is still the verdict.
  • Running it found a real drift in the self-test itself. The Full audit group drove real audits, then restored every archive with a plain option write and never rebuilt the summary the admin rail reads — so on a site whose Legal audit had never run, the rail showed the probe’s three legal failures against an archive holding none. The restore now rebuilds the summary.
  • Three smaller self-test fixes: a regex that interpolated an undefined variable (a PHP warning on every run) and matched by luck; a pin that met the Free page gate before the value check on an unlicensed install; a group that aborted on a refused probe write instead of reporting it. Three publish-date checks that reached 4.4.0 ahead of the parameter they call are out until that parameter ships.
  • Verified: 44 harnesses green; CI green on PHP 8.0, 8.4 and 8.5 with the rig harnesses, the new one at 680 of 680 read-scope rows on a clean WordPress 7.1 (24 groups run, 19 skipped for no Divi); both zips reproducible to the byte and the download verified as served. Live on this site, on Divi 5.13.1 and WordPress 7.1.1: self-test 1,005 of 1,005 read-side checks, 0 failures.

4.4.1

  • 4.4.0’s live self-test was 1,004 of 1,005. The one failure was a stale check, not a stale product: the pin that the header carries the current mark still looked for the logo’s file name inside the rule, and 4.4.0 had moved that behind a token so each theme could hand the header its own cut. The pin reads the token now. Same shape as 4.3.2’s stale pin, so the full read-side suite was run on the rig before this tag as well.
  • Contrast, measured on the live page across all three themes — every ancestor’s background and opacity composited — on the Overview, Audit, Design and Diagnostics screens. Four things were under 4.5:1 and are fixed: a secondary button dimmed by opacity (4.40:1 Light, 4.25:1 Deep Space) now quiets by colour; links took BS Blue and measured 4.44:1 on the raised surfaces they sit on, so links are text now and take the text accent with an underline on hover; the PASS chip’s green on its green fill was 4.39–4.45:1 and has its own text token (6.44:1 in Light), pinned per theme like the FAIL chip; Terminal’s muted green was 4.37:1 on the tinted chips it sits on and is a step brighter (6.74:1 on the panel, 5.17:1 on the chips).
  • After these changes the same probe reports zero text failures on all four screens in Light, Deep Space and Terminal. Dial fills measured live against their tracks: 3.23:1 Light, 8.24:1 Deep Space, 11.62:1 Terminal.
  • Verified: 45 harnesses green; CI green on PHP 8.0, 8.4 and 8.5 with the four rig harnesses; both zips reproducible to the byte and the download verified as served. Live on this site, on Divi 5.13.1 and WordPress 7.1.1: self-test 1,005 of 1,005 read-side checks, 0 failures.

4.4.0

  • Three themes: Light, Deep Space, Terminal. A segmented switch in the header beside Simple/Advanced, saved per user, printed on the page server-side so the chosen theme paints with no flash and with JavaScript off. Light is the default and is unchanged. This reverses 4.3.2’s “one skin”, in the open: what 4.3.2 removed was an unrepainted second palette bolted onto fifteen undeclared tokens. The token system 4.3.2 and 4.3.3 built is what makes a theme one token block now instead of a fork — a theme redefines the variables and no component rule branches on which theme is on. That last part is a test, not a promise.
  • Deep Space is the brand on a dark ground — ink and navy surfaces, signal-blue accents, off-white text — with a starfield drifting slowly behind the panels on every screen: one composited CSS layer, no script per frame; still under reduced motion, absent under reduced transparency. Contrast computed from the values that ship: text 16.06:1 (primary), 13.71 (body), 8.45 (secondary), 6.45 (muted) on the panel; the dial’s three fills 5.19, 8.60 and 8.24:1 on its dark track.
  • Terminal is one green phosphor on near-black: Press Start 2P for headings and the large figures (a 4.7 KB pixel face, OFL licence shipped, fetched only when this theme is on), scanlines, the dial and the mark rendered pixelated, square corners, no shadows. Monochrome has no red, so a failing chip inverts — dark on green, 14.86:1 — and the score names its band in words beside the figure. Every text, status and dial token within 8 degrees of the phosphor’s hue, and a test says so.
  • The dial follows the theme. The sweep used to leave its landed colour on the element; it re-reads the band tokens on the switch now. “Increase contrast” no longer forces a white ground — the high-contrast override carried Light’s values unconditionally.
  • Pins: the four 4.3.2 checks that held the absence of a theme system are replaced by presence checks, plus a live check that an unknown stored value renders Light without being rewritten. The brand harness runs its contrast table once per theme, requires every Light token to be redefined by each alternate, and refuses a theme selector outside the themes region, a second hue in Terminal, or pixel rendering that could reach wp-admin’s chrome. Eleven mutations run, all red, on record — including the first cut of one pin that passed with the attribute missing, rewritten and re-run.
  • Verified: 45 harnesses green; admin render harness 51/51 on the Gutenberg rig with the Overview rendered under all three themes and an unknown fourth; CI green on PHP 8.0, 8.4 and 8.5. Live on this site: self-test 1,004 of 1,005 — the one failure is the stale pin 4.4.1 fixes.

4.3.3

  • The coloured stripes are gone. Result banners, audit and self-test accordions, callout steps, the prerequisite box and the fix plan each drew a 3px bar down their left edge in the state colour. They read as decoration and made every card look like a different component. Every one is removed. State lives in the PASS/WARN/FAIL chip and an 8px dot beside a banner’s heading; a failing banner or accordion keeps a red 1px hairline all the way round, because a failure has to look different at a glance.
  • No amber or brown surface anywhere. The warn text token was a brown on white, and the status bar, hero and warn banners took a beige or green tint from it. Warn text is BS Navy at 10.19:1, warn surfaces take the blue tint, and the status bar and hero use one neutral blue gradient for every state. The “To fix” figure is ink. The only amber left is the dial’s band colour and the small state dots.
  • Hover has bite. One shadow scale, palette-only. Cards, capability tiles, next-step cards, accordions and swatches lift 2px with a shadow and accent border on hover; buttons lift 1px and press back on click; nav pills lift; score cells and table rows tint. One blue focus ring. Reduced motion drops every transform and transition.
  • The Design tab opens on “This site’s system”, with Scan and Adopt together in one action row at the top of the screen. Adopt was the last element on the page — the one button the screen exists for, and the one nobody scrolled to. It renders only while a proposal is waiting, once.
  • Two new brand pins — the warn text token must be a blue, and no amber or brown value may appear outside the dial — both proven red by mutation before they went in. The accordion pin inverted from “pass carries a green edge” to “fail carries a red hairline and nothing carries a stripe”.
  • Verified: 44 harnesses green on the build machine; admin-render harness 38/38 on the Gutenberg rig with this tree. CI green on PHP 8.0, 8.4 and 8.5, the four rig harnesses included; both zips reproducible to the byte and the public download verified as served. Live on this site, on Divi 5.13.1 and WordPress 7.1.1: self-test 1,005 of 1,005 read-side checks, 0 failures.

4.3.2

  • One skin. Dark mode is gone — the toggle, the per-user setting, the endpoint behind it and the second palette. The admin is light, always, and the light values are now simply the palette rather than an override on top of a dark one. The self-test checks that used to hold the switch together now hold its absence together.
  • The Site Score is a dial. A full circle: 0 at the top, 50 at the bottom, 100 back at the top, filling clockwise with the satellite riding the leading end. The arc takes the band’s colour — red under 50, amber to 74, green from 75 — and blends through the shades between as it sweeps. Number, dial, satellite and the panel’s status dot key on one method, so they cannot disagree. The fills are held to 3:1 against the track as UI graphics, computed from what ships; the middle band is amber because no yellow reaches 3:1 on a track pale enough to read as empty. Everything the sweep ends on is printed in the markup, so with JavaScript off or motion reduced the dial is simply already finished — and it waits for the tab to be visible, because a hidden tab gets no animation frames and a sweep started there froze mid-way.
  • The menu icon is the mark in one ink. It sits in the WordPress sidebar like every other item, grey at rest and white on the active row. WordPress repaints these icons with a rule that turns every fill into one colour and never touches a stroke, so an open ring came back as a solid lump and a stroked arc as a speech bubble. The icon is now filled shapes only, with the check knocked out of the disc as a hole in the same path, and the rules WordPress imposes are pinned rather than the drawing.
  • Fifteen colour names were used and never declared. Rules written against an older scheme referenced tokens no version of the stylesheet had ever defined. Fourteen fell back to a generic grey — a good part of why the interface read as muddy — and one fell back to nothing, so the amber “To fix” figure rendered in plain text colour. They resolve to the real tokens now, and an undeclared variable fails the build.
  • Plus Jakarta Sans only, five weights, self-hosted. This site pairs it with Figtree for body copy; the plugin interface does not, by decision — one face with five weights is lighter and reads better at 13px than two families — and the reason is recorded beside the declaration so it is not mistaken later for the accident 4.3.0 made.
  • Spacing is an 8pt grid. Component padding 24, panels 32, section gaps 24, one control box for every button and pill where before there were four. The large numbers beside the score are cells with hairlines between them instead of figures floating in a row, and two-up on a phone. Twenty-five distinct off-grid values were measured on the live Overview before the pass; zero after. The tab strip on a phone scrolls sideways instead of wrapping to four rows.
  • Two contrast pairs the token maths could not see, found by compositing the live page: the score’s “/100” at half opacity was 2.15:1 and link blue on the grey chips was 4.28:1. They are 5.92:1 and 8.62:1 now, and pinned as the rule that fixed them.
  • Verified: 44 harnesses green, 4 skipped for want of a WordPress rig on the build machine, which the runner names rather than counting as passes. Every check written for this release was mutation-tested. CI green on PHP 8.0, 8.4 and 8.5, the four rig harnesses included; both zips reproducible to the byte and the public download verified as served. Live on this site, on Divi 5.13 and WordPress 7.1.1: self-test 1,005 of 1,005 read-side checks, 0 failures.

4.3.1

  • Fixes the product's own name rendering invisible in 4.3.0. On the light skin the admin header painted “Checknaut” in white on a near-white background. A comment added in 4.3.0 contained, in the middle of itself, the two characters that end a CSS comment. CSS closes a comment at the first terminator, so the rest of that comment became instructions, the real terminator became stray, and the browser's error recovery discarded everything up to the next semicolon — which happened to be the one setting the heading's colour. Exactly one line lost. The line two below it survived, which is why the tagline looked right and the heading did not.
  • The check that should have caught it was reading the file instead of the result. The stylesheet still contained the correct colour as text, so every assertion about it passed while the browser painted something else. Those checks now parse the stylesheet the way a browser does — closing each comment at its first terminator and keeping only the declarations that survive — so a value that is present in the file but discarded when the page loads fails the build. Proved by putting the fault back: two checks name it.
  • 4.3.0 is left where it is rather than replaced, because it was published and installed. This is the fix on top.
  • Verified: 42 harnesses green, 4 skipped for want of a WordPress rig on the build machine, which the runner names rather than counting as passes. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13 and WordPress 7.1.1: self-test 999 of 999 read-side checks, 0 failures, and both skins measured in the browser — every colour variable resolves to the value the stylesheet states, in light and in dark.

4.3.0

  • The admin now looks like the product it belongs to. The dark skin already used this site's own colours exactly. The light skin — the one most people actually see — matched nothing: a teal it had carried since 0.48, on a grey gradient, while the brand's light palette is blue. Light now speaks that palette, and both skins move to Outfit, the typeface this site sets for headings and body alike. The admin had been speaking a different typeface from the product's own marketing.
    Correction, 4.3.2: two claims in that paragraph were not true. The dark skin did not use this site's colours — it used the previous brand's steel-and-cyan ramp, still sitting in the design system under old ids. And this site has never loaded Outfit; that is also the previous brand's face. The admin actually loads Plus Jakarta Sans since 4.3.2, and the dark skin is gone. Both were checked this time by opening the page in a browser and reading what it computed, rather than by reading a token's name.
  • Three brand colours are deliberately not used at their published value, and the stylesheet says why beside each one. The brand's muted grey is 4.48:1 on the page background, under the accessibility floor for body text. The brand blue is 4.47:1 on the brand's own pale blue chip, which is the chip pattern this screen uses everywhere — so chip text takes the darker navy at 9.11:1 and blue keeps the button fills, where white on it is 5.06:1. The brand green is 2.28:1 as text: a dot and a fill, never a word. A brand is a palette, not a licence to ship unreadable text, and this plugin audits other people's sites for exactly this.
  • The contrast is computed on every build, from the stylesheet itself. A new check reads each skin's real variables and works out every text-on-surface pair by the WCAG formula, including chip text composited over its own translucent fill. 39 checks. The first version of that check was wrong in an instructive way: it computed the ratios from values typed into the test, so replacing a shipped colour with a failing one left it green — it was measuring the intended palette rather than the shipped one. Caught by deliberately breaking it; it now reads what actually ships, and the same mutation fails at 4.48:1.
  • 188 KB of fonts left the download. Sora and Inter are no longer used, and Montserrat had been shipping unreferenced since well before this release. A new check fails the build on any font file no rule asks for, and on any @font-face whose file is missing — so a skin can never quietly fall back to Arial, and dead weight cannot return.
  • Verified: 42 harnesses green, 4 skipped for want of a WordPress rig on the build machine, which the runner names rather than counting as passes. Eight mutations across the new checks, each turning it red. No translatable strings changed. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte.

4.2.1

  • Diagnostics reported six failing checks about an audit engine that was working. Every one was in the Full audit group and every message blamed another request for holding the lock. Neither was true. The self-test drove the WHOLE site-wide plan whenever writes were on and the site had 50 or fewer published pages, on the theory that a small site is a test rig. Size is not cost: this audit is priced per module, and twenty Divi pages spent all 120 of the drive's calls to finish ONE group of seven. The other five failures were consequences of that one. It now plans the two site-level groups, which is what those checks are actually about, and the drive stands down BY NAME when a plan is still running at the end of its budget — exactly as it already did when another request held the lock. A site with more to audit than the self-test budgets for is not a defect in the build.
  • Why it took a live site to find. Offline the suite runs read-only and takes the cheap branch, which passes. Only a real install with writes enabled takes the other one, and that branch had no test rig to run on at all — so it could only ever fire where it could not afford to. Through the connector this same group reported 15 of 15 green while wp-admin showed six red: same build, same site, two paths, and the honest screen was the one showing red.
  • The WordPress Abilities API surface can now see itself. 4.2.0 registered this plugin's tools with WordPress and gave nobody a way to check. WordPress does not fail loudly on a bad registration — it notes the mistake and returns nothing, silent on a production site — and these abilities are deliberately kept off the public REST listing, which was the only other window. A tool WordPress refused would simply not exist while this plugin went on naming it in its own manifest. The Connection tab now asks WordPress what it actually holds and prints the answer: all of them registered, or how many did and the names of the ones WordPress would not take. Named rather than counted, because “two are missing” is not something an owner can act on and the two names are.
  • Verified: the registry read-back carries 8 checks, proved three ways by breaking it on purpose — counting a shortfall instead of naming it, reporting what was asked for instead of what WordPress holds, and flattening “this WordPress has no Abilities API” into “none registered” each turn it red. Full suite: 41 harnesses green, 4 skipped for want of a WordPress rig on the build machine, which the runner names rather than counting as passes. Live on this site, on Divi 5.13 and WordPress 7.1.1: the group that reported six failures now runs 63 of 63, including the write path.
  • Still open, and written down rather than quietly fixed: a seven-group audit of a twenty-page site needing more than 120 passes is a question about what the audit costs, not about the test that surfaced it. That is not addressed here.

4.2.0

  • Checknaut's tools are now WordPress abilities. The Abilities API is core from WordPress 6.9, and it is how WordPress expects a plugin to tell an agent what it can do. Until now the only way in was your connector URL, so an assistant on the site that does not speak MCP, and holds none of your tokens, could not use any of these 105 tools. It can now, and it meets exactly the same gates: an ability's handler calls the one internal dispatcher every connector call already goes through, where tool availability, arguments, licence tier, the Free tier's page binding and the write guard all run. Nothing about the paywall or the safety switches is re-implemented for the new door, because a second copy of a rule is a rule that drifts.
  • Off until you turn it on, and only you can. Registering abilities hands the same tools to anything else on the site, acting as the logged-in user rather than under a token you issued and can revoke. That may well be what you want; it is not a decision to make on your behalf during an update. The switch is on the Connection tab, behind the administrator capability and a nonce, and it is deliberately not one of the safety switches an assistant can change: those can only be tightened from a conversation, and turning this one ON would be loosening.
  • A second door had to prove the gates live at the funnel, not at the door. Every existing gate test called the gate directly, which proves each gate decides correctly and proves nothing about whether the path every caller takes still runs them. A new test drives every Pro tool through the real dispatcher and requires the gate's own refusal back. Proved by putting the fault in: removing the tier check names the tools that leak.
  • Three builders are written, and every other builder is named rather than guessed at. Divi 5, Elementor and the block editor are built and edited natively. Bricks, Beaver Builder, Breakdance, Oxygen, WPBakery and the rest are detected by name, audited from the page they actually render, and refused for writes. Stated on the plugin page now because it was only ever implicit — and the same pass fixed a summary that had been claiming less than the product does, having said “Divi 5 and the block editor” for four minor versions after whole-page Elementor authoring shipped.
  • Verified: 33 checks on the new surface, proved by mutation eleven ways, and 13 checks on the builder boundary proved five ways. Three of those tests first reported nothing when the fault was inserted — two read an unassigned variable, one matched past the end of the method it was about — and all three were fixed rather than trimmed, with any PHP notice now failing the run. Full suite: 41 harnesses green, 4 skipped for want of a WordPress rig on the build machine, which the runner names rather than counting as passes. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13: self-test 951 of 951 read-side checks, 0 failures.

4.1.1

  • Eleven new engines, built after reading a competitor's plugin end to end. Respira for WordPress 9.0.1 is GPLv2. Its source was reviewed across six surfaces to find capabilities we did not have. Architecture was borrowed and reimplemented; no code was copied.
  • Untrusted text is stripped before it reaches a model. Invisible and bidirectional characters go first, because they are what hides a forged turn marker from every later pattern: a zero-width space inside the word renders as a clean role label and defeats a naive scan. Role labels at the start of a line are rewritten rather than deleted, so real prose survives and only the impersonation dies. This does not make injected text harmless. It makes it visible.
  • A dropped response can no longer become a second write. When a host discards the reply to a write rather than refusing the request, the write has already happened, the client sees a timeout, and the client retries. With new-pages-as-drafts on, that retry used to mint a second draft nobody asked for. Calls are now keyed and replayed, and the key is derived from the connection, the tool and the arguments when the client sends none, so the protection works without the client doing anything at all.
  • The audit log is tamper evident, and every audit result now states what it could not see. Each row seals over the one before it, so editing a row, deleting one or reordering two all break the chain, and the report names where it stops rather than returning a bare failure. Separately, an assistant reading 0 findings will tell the owner the page is clean, so the list of checks that actually ran is now derived from each engine's real check list, with a test comparing the two so they cannot drift apart.
  • This release exists because 4.1.0 broke wp-admin on this site. The clone-detection routine added in 4.1.0 called two of its own properties by the wrong name, which PHP resolves to nothing and then tries to call. The front end, the REST connection and all site data were unaffected; only wp-admin was down, and nothing was lost. It got out because php -l reports an undefined property as valid and only fails when the line actually runs: the routine was added by hand after its test file was written, no test ever called it, and the whole suite stayed green. The test now calls every public method on that class, and a new check fails the build on any property the class reads but does not declare. Verified by putting the bug back, which makes the harness exit with a fatal. 4.1.0 was never released to customers.
  • Verified: 711 checks across eleven new harnesses, 30 mutation checks, each proving its gate can still fail. Full suite of 42 harnesses: 38 green, 4 skipped for want of a WordPress rig on the build machine, which the runner names rather than counting as passes. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13: self-test 999 of 999, 0 failures.

4.0.8

  • The free plugin now has one download link that never changes. It used to be served under a versioned filename, so every release meant re-uploading the archive and editing the links by hand — and a missed edit left this site offering the previous version from a link that looked current. Builds now publish to a public repository that holds release artifacts and nothing else, under a constant name. The build gates verify it rather than assume it: after publishing, CI downloads the archive back from that link and fails the release unless what it serves hashes to the md5 the packager printed.
  • New in Pro: station_self_update installs a published build over the running one. Updating a site meant uploading a zip through wp-admin and clicking through two screens. This does it in one call, through WordPress's own plugin installer. Four gates run before a byte is written, and the caller sets none of them: an operator switch that is off by default, a download source compiled into the plugin, the archive's md5 stated up front — so a redirected or truncated download fails closed instead of installing something nobody named — and a check that the archive is the plugin you asked to update. Dry by default: without confirm it downloads, verifies and reports, which is worth having on its own.
  • The check that guards the uninstall sweep can now see the Pro plugin. The sweep is a hand-kept list rather than a wildcard, because a wildcard would delete another plugin's rows. The harness that catches a missing entry was only reading the free plugin's code; two Pro option names were outside its view. Both were in fact swept, so nothing leaked — but the gate could not have said so either way.
  • Verified: 27 offline harnesses green, 4 skipped for want of a rig and named rather than counted as passes. 44 checks on the new tool, driven through every refusal rather than grepped for, and mutation-tested three ways. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13: self-test 999 of 999, 0 failures.

4.0.7

  • The MCP now names the native Divi attribute instead of accepting a code module. CSS in a code module whose selectors reach Divi's own generated classes is reported on every write with the attribute it should have been. This is the failure mode where you change a setting in the builder and nothing happens: Divi rewrites a module's CSS whenever a setting changes and knows nothing about your code module, so the two drift apart and an !important there wins forever. Found on a real Theme Builder header carrying eleven layout properties pinned that way, every one against a setting that already existed on the module.
  • The property map is measured, not derived. 42 CSS properties, each one written as an attribute on Divi 5.13 and read back out of the CSS Divi emitted. The three absences carry the most information: gap has no native shorthand and must be split into rowGap and columnGap, display is already set by Divi core on its own flex wrappers, and display:contents has no equivalent at all.
  • Two values Divi stores, validates, and then ignores are now named before the page ships. position.mode outside default/relative/absolute/fixed — there is no static — and disabledOn written with desktop/tablet/phone instead of desktopAbove/tabletOnly/phone. Both read out of Elegant Themes' own published types rather than inferred. Nothing downstream could catch these: both groups emit no CSS by design, so a render probe reporting silence is not evidence either way.
  • Uninstalling now removes dvc_policy_modes. The guardrail modes were written by the plugin and left behind in the options table when it was deleted. The self-test caught it — but only on a live install, after 4.0.6 was already tagged. That same scan now runs offline during the release prepare, so the next missing option goes red before a tag exists.
  • Verified: 26 offline harnesses green, 4 skipped for want of a rig and named rather than counted as passes. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13: self-test 999 of 999, 0 failures.

4.0.5

  • The free plugin now passes the wordpress.org review sniffs with zero errors. It had 83. The readiness audit behind that number was written against 3.3.4, when the free build still carried the Freemius SDK — the 4.0.0 split removed it, which deleted two of the four hard blockers outright and 540 of the original 873 findings. None of that had been re-measured until now.
  • 38 superglobal reads were sanitised, and the ones that must not be were left alone on purpose. A value that is stored, compared loosely or displayed now goes through a real sanitiser. A value that is hashed, compared with hash_equals(), parsed by wp_parse_url(), or saved and restored verbatim gets wp_unslash() and a comment naming the reason — because sanitising those changes the thing being compared. The lazy way would have turned a correct API key into a 403 nobody could explain.
  • One real SQL rewrite. The autoloaded-options query built its IN list by escaping values into the string; it now generates %s placeholders and passes every value to prepare(). The remaining interpolations are table names, which prepare() cannot parameterise — each carries a comment saying why that line is safe rather than a blanket suppression.
  • Also fixed: 14 missing translator comments (which is what makes the eight locales translatable), 8 global save-and-restore pairs, 5 printed <link>/<script> sites and one deprecated get_page_by_title() call replaced with WP_Query. The 229 remaining warnings are listed with a reason each.
  • Verified: 0 errors under the review sniff set, down from 83. 22 offline harnesses green; CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13: self-test 951 of 951, 0 failures.

4.0.4

  • The trademark notice reached the one screen it had missed. 4.0.3 wrote it once and rendered it from that one place on every admin screen and in the Pro report — and left both plugins’ Description: headers behind, so the Plugins list still named Elegant Themes and nobody else. That is the most-read screen in WordPress, and the same string is what wordpress.org renders on a listing page. Both headers now carry the three vendors, the three denials and the respective-owners clause.
  • The same header still advertised “pages on Divi 5 and the block editor”. Elementor authoring shipped in 4.0.2; the product’s most-read description of itself had not been told. It now says so.
  • The harness that holds the notice to the builders shipped now reads the headers too. It could not have caught this: all 33 of its checks read a string PHP renders, and a file header is read by WordPress rather than rendered by us — so an audit that greps its own output misses it by construction. It now reads every file carrying a Plugin Name: header in both plugins, and drives the expected owners off the adapters on disk, so a new builder fails it by existing. 33 checks to 50.
  • Verified: 22 offline harnesses green; CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site, on Divi 5.13: self-test 999 of 999, 0 failures, build reported as pro. On a second live site running Divi 5.11.0: 989 of 989.

4.0.3

superseded by 4.0.4

  • Turning read-only OFF did not always turn writes back on. WordPress’s update_option() reads the old value, sees boolean false, decides the option must be new and calls add_option() — which refuses, silently, when the row already exists. So writing “writes enabled” over a flag the same request had just disabled did nothing at all, and the site stayed read-only until the next request. One writer now, delete-then-add, which always lands. Found by the live self-test on a real WordPress; no offline harness can see it, because it is a question about what get_option() hands back.
  • A self-test chunk that DIES could leave a site read-only — and that was reported three times over three releases as product failures before anyone understood it. Two groups tighten read-only deliberately and restore it in their own cleanup, and that cleanup does not run when a gateway timeout kills the request. The switches are now captured as the run found them, carried in the saved state, and reapplied before each resumed chunk, at the end, and when an abandoned run is reclaimed.
  • An upgrade that adds tools was invisible for an hour, and it was this plugin’s own doing. tools/list answered with a one-hour freshness hint, so a site going from 39 tools to 60 kept serving 39 to its connected client and an explicit refresh changed nothing — the client was obeying us. The hint is now five minutes, and zero while a fingerprint of the tool surface differs from the one this install last served.
  • The Elementor build was dogfooded on a live site, and the write path found six things no offline harness could. station_run_self_test scope=all had never been run on an Elementor site. It came back 1,924 of 1,995; it now returns 2,025 of 2,025.
  • The trademark notice now names the builders this plugin actually supports. It is written once, rendered from that one place on every admin screen and in the Pro report, and it names WordPress, Divi and Elementor with their owners, every other product this plugin reads or works alongside, and states those names identify software rather than imply endorsement. The admin footer carries a 35-word version; the brand list is the only thing the short one drops.
  • Verified: on the live Elementor site, self-test 676/676 read-only and 2,025/2,025 including the write path. Offline, 21 harnesses green on a machine with no rig and 23 of 23 with the rigs attached. Nineteen mutations recorded (M210–M228), each watched turning named checks red. CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte. Live on this site with both plugins active, on Divi 5.13: self-test 999 of 999, 0 failures, build reported as pro, edge cache purged on the write.

4.0.2

elementor authoring

  • Checknaut can now BUILD Elementor pages, not only read and repair them. station_create_page, station_update_page and the four element tools — add, edit, move, delete — were withheld on every Elementor site since 3.0.0 and are now offered and answered. They take Elementor’s own element tree, the same shape station_get_node returns, because a translation layer between an assistant and a builder is a thing that goes stale silently and blames the builder.
  • It was proved before it was written. A rig — WordPress 7.1, Elementor 4.2.4, PHP 8.4 — builds a page from nothing with no browser and no editor, then fetches the rendered front end over HTTP and asserts it: 76 assertions, cold build to green, on both the classic widgets and the 4.x atomic ones. Three traps are on record because each one produced a silent wrong answer first: update_post_meta() unslashes JSON, so the tree needs wp_slash( wp_json_encode() ); _elementor_edit_mode must be builder or Elementor stands down with HTTP 200 and no warning; and _elementor_element_cache serves the OLD html after every update, which is the likeliest cause of any “the fix did not take” you have seen on an Elementor site.
  • Every write is undoable, through the path Elementor forces. Elementor keeps the page in post meta and WordPress revisions do not record post meta — a revision of an Elementor page contains none of the thing being replaced. So the raw meta rows are stashed and hash-checked at both ends, reported as method: meta with no revision id, exactly as the surgical fixes have done since 3.2.0. One mechanism, not two.
  • A write that loses a setting now says so. Elementor’s own save keeps every element and does not keep every setting: atomic e-* widgets silently drop any prop their schema does not declare, because the parser iterates the schema rather than your props. A full recursive diff after the save names every dropped or altered key. Where the site’s user lacks unfiltered_html and WordPress rewrites the content itself, the warning says so and names the capability rather than blaming Elementor.
  • A failed create leaves nothing behind. A malformed atomic value throws after the post is already published, which used to leave a live blank page. Every failure path now deletes the post it created and refuses by name. An update never rolls back: Elementor validates before it writes, so the previous page is untouched.
  • Verified: 18 offline harnesses green, of which the Elementor authoring harness is 115 checks with six recorded mutations, each turning named checks red; the rig probes are 76/76 for authoring and 91/91 for the settings diff; 57 new strings translated into all eight locales; both zips reproducible. Not covered, and named rather than implied: Elementor Pro was never installed on the rig, so the Pro-widget refusal is reasoning rather than measurement, and no data-level check can see a value that stores correctly and renders empty.

4.0.1

superseded by 4.0.3install this, not 4.0.0

  • 4.0.0’s add-on refused to load on every correctly configured site. WordPress sorts active_plugins, and in ASCII - sorts before / — so wp-basestation-pro/ loads before wp-basestation/, always. 4.0.0 checked for the free plugin at file scope, found nothing, and refused by name: an operator who installed both correctly saw “Checknaut is not active on this site” while it plainly was. Every check that needs the free plugin now runs on plugins_loaded, the first moment every plugin file has been included, so the answer is about the site rather than about the alphabet. Found by installing 4.0.0 on a real site.
  • The dependency header is not a substitute, and never was. Requires Plugins stops an operator activating Pro without the free plugin; it says nothing about the order the two are then loaded in. Both are needed, and both are now pinned.
  • What stays at file scope, deliberately: the Pro plugin’s text domain and its uninstall hook. An add-on that refuses to load must still release its licence seat and sweep its own options when deleted.
  • The harness that should have caught this tested a configuration that cannot occur. It wrote active_plugins in the order it chose rather than the order WordPress produces. It now sorts exactly as activate_plugin() does and asserts the add-on really is first; reverting the fix turns five of its checks red.
  • The free plugin itself is unchanged: git diff v4.0.0 v4.0.1 -- wp-basestation/includes is empty. The version moves in lockstep because the two ship as a pair.
  • Verified: two launch sweeps on the 4.0.1 tree, 28 rows each. Free plugin with Pro beside it — Divi 5.11.0 on the rig 3,037 of 3,038 with eight other plugins and 2,933 of 2,934 alone, six other themes 2,038/2,038 and 1,922/1,922. Free plugin alone — Divi 2,492 of 2,493 and 2,390 of 2,391, other themes 1,827/1,827 and 1,699/1,699, with 44 to 54 checks named “Not in this build” and kept out of those totals rather than counted as passes. The one failure on each Divi row is Serving over HTTPS, which a plain-HTTP rig cannot satisfy. 0 PHP diagnostics from either plugin at E_ALL across all 56 rows; the admin screens rendered green on all 28. 20 offline harnesses green; CI green on PHP 8.0, 8.4 and 8.5; both zips reproducible to the byte on three machines. Live on this site with both plugins active, on Divi 5.13: self-test 999 of 999, 0 failures, build reported as pro, and the edge cache purged on the write.

4.0.0

two pluginssuperseded by 4.0.1

  • Free and Pro became two plugins, installed side by side. Until 3.5.x this was one plugin in two builds and a licence swapped one for the other in place. The WordPress plugin directory does not allow that: guideline 8 forbids a plugin the directory distributes from installing or updating a paid version of itself, and the free plugin belongs in the directory. Pro is now its own plugin — Checknaut Pro — installed beside the free one, declaring Requires Plugins: wp-basestation and registering on ten hooks the free plugin declares. It is the shape Yoast SEO Premium and Elementor Pro use. Nothing about what either half does has changed, and an existing install keeps its settings, pages, audit history and journal: nothing is replaced.
  • The free plugin now carries no licensing code whatsoever. The provider SDK (201 files), the licensing bridge and every Pro class are gone from the tree, not stripped from a build. The packager refuses a release whose free artifact names a class the Pro plugin declares, carries a Pro tool’s handler, or contains a file under a provider SDK — proven on every build by mutating each of those three shapes in and watching the gate go red.
  • WordPress 6.5 is the new floor, up from 6.4: Requires Plugins is a 6.5 header, and it is what stops the Pro plugin being activated without the free one. Pro also checks at run time, because that header cannot express a minimum version, and it refuses visibly and loads nothing when the free plugin is absent or too old, naming both versions.
  • Licence key entry moved to the Pro plugin along with the licence. The free plugin’s Licensing screen now says what Pro is and where it comes from. Uninstalling either plugin sweeps only its own state: deleting the free one no longer touches a licence seat it does not own.
  • Do not install 4.0.0. It shipped with a load-order defect that made the Pro plugin refuse on every correctly configured site. 4.0.1 fixes it and is the release to use; the card above says what went wrong.
  • Verified: two launch sweeps on the 4.0.0 tree, 28 rows each. Free + Pro — Divi 5.11.0 on the rig 3,037 of 3,038 with eight other plugins and 2,933 of 2,934 alone, six other themes 2,038/2,038 and 1,922/1,922; from wp-admin 2,038/2,038 and 1,922/1,922. Free alone — Divi 2,492 of 2,493 and 2,390 of 2,391, other themes 1,827/1,827 and 1,699/1,699, with 44 to 54 checks reported by name as “Not in this build” and kept out of those totals rather than silently absent. 0 fatals and 0 PHP diagnostics from either plugin at E_ALL on all 56 rows; the admin screens rendered green on all 28. 18 offline harnesses green; thirteen mutations on record, each watched turning a named check red; 14 new strings and 3 new Pro strings translated into all eight locales, with both catalogues now checked by the same harness; both zips reproducible.

3.5.2

deployment

  • Second attempt at the same deployment problem, with what 3.5.1 taught. 3.5.1 added @fs_ignore /freemius/ and the licensing provider still refused the upload with “Too many files to parse”. Two things were wrong with it. The directive listed only the vendored SDK, where the zip also carries 94 asset files and 17 translation files that no preprocessor needs to read; and four comments elsewhere in the tree wrote the provider’s own directive token in prose, which a preprocessor scanning for that token can read as a directive with a nonsense path.
  • Both are fixed: one directive line naming every directory that need not be parsed, and no bare directive token anywhere it is not a directive. The package gate now checks the set of ignored directories rather than the spelling of the line, so adding one is a one-line change and losing the line is a refusal.
  • No change to any code path: git diff v3.5.0 v3.5.2 -- wp-basestation/includes is empty. The 3.5.0 entry below describes what this release does.

3.5.1

deployment

  • One header line, because the licensing provider had never parsed this plugin before. 3.5.0 is the first release the provider generates a free version from, and generating means parsing every PHP file in the zip to strip the paid code. It ran out of memory on 420 files and refused the upload by name.
  • The main file now carries @fs_ignore /freemius/: the vendored SDK is 201 of those files, carries no paid-code markers, and still ships — the directive governs processing, not packaging. The package gate pins the line, because nothing at run time reads it, and a header line that nothing reads is one that disappears in an edit and is missed until a release fails.
  • No change to any code path: git diff v3.5.0 v3.5.1 -- wp-basestation/includes is empty.

3.5.0

in-place upgradereplaced in 4.0.0

  • The Pro build installs itself. It could not before, and the reason was one word. 3.4.0 shipped two builds but left both declaring the same slug to the licensing provider, and on that configuration two things are true that nobody can see from the outside: the provider will not generate a free version at all, and the SDK’s own “install the Pro build” action answers Premium version already active on a site holding no Pro code, because with one slug a free install’s premium path is its own. The path 3.4.0’s dvc_pro_build_missing refusal pointed licensed operators at did not exist. It does now: the Pro build ships in wp-basestation-premium/, the free build stays in wp-basestation/, and activating a key offers the Pro build and installs it in place.
  • Two directories is a state the plugin had to be taught to survive. For one request during the switch both are active: WordPress includes the newly activated main file while the already-loaded copy holds every class name in this plugin, and the SDK only deactivates the loser afterwards. The main file stands the second copy down and says which build is running and which is standing by — in wp-admin, in eight languages, naming builds rather than directories. A new harness drives both orders on a real WordPress with both directories installed under their shipping names.
  • The defect this release nearly shipped with. The SDK caches where its main file lives and only recalculates when that path stops existing — which, after a purchase leaves the deactivated free directory in place, it never does. The running Pro build was resolving to the free copy’s basename, which is the value that decides which copy gets deactivated and which one the updater writes to. A repair runs before every init, writes only when something differs, and touches nothing but location fields. The harness seeds the stale state deliberately, because an ordinary run never reaches it.
  • Verified: two launch sweeps on the 3.5.0 tree, the Pro build swept from wp-basestation-premium/. Pro — Divi 5.11.0 on the rig 3,034 of 3,035 with eight plugins and 2,930 of 2,931 alone, six other themes 2,027/2,027 and 1,911/1,911; from wp-admin 2,930 of 2,931, 2,027/2,027 and 1,911/1,911. Free, from the packaged free zip — Divi 2,513 of 2,514 and 2,411 of 2,412, with 53 and 52 checks named “Not in this build” and kept out of those totals; other themes 1,840/1,840 and 1,712/1,712, 43 not in this build. 0 fatals and 0 PHP notices at E_ALL on both builds; 18 offline harnesses green; seven mutations on record, of which three changed the product rather than confirming it; both zips reproducible on three machines.
  • This model did not last. A plugin the WordPress directory distributes may not install a paid version of itself from another server, and the free plugin belongs in the directory. 4.0.0 replaced the in-place upgrade with two plugins installed side by side.

3.4.0

two builds

  • Two builds from one tree: the free download no longer contains the Pro code. Until 3.3.4 every install carried the whole plugin and the licence decided at run time what could run. Reversed: the Free zip is the Pro zip minus includes/pro/ — the 49 Pro tools’ handlers, the Pro halves of the shared engines (Theme Builder writers, Loop Builder, patterns, transfer, project memory, WooCommerce builder, preset and design-token writers, the report, scheduled audits, legal drafts, design-system adoption), the Pro admin bodies and the Pro self-test groups. What you download is what runs, and you can read all of it. Same product, same plans, same key.
  • What did not change. The free tier: 51 tools, site-wide audits, fixes on one bound page, the self-test and the rollback path never paywalled. The Pro build still gates every Pro tool on the licence — having the build is not entitlement. Every Pro tool is still listed to a connected assistant by name on the Free build and refuses with the reason, never “unknown tool”.
  • A valid licence on the Free build says so, everywhere. A new refusal, dvc_pro_build_missing, answers a Pro tool call on a licensed Free install with the install link; the Licensing panel gained a BUILD line; every Pro screen and the fix-all button stay locked on the Free build whatever the licence says. The self-test reports each Pro group as “Not in this build” on the Free build — kept out of N/N, never counted as a pass — and the packager refuses a Free zip in which any free file names a Pro class or carries a Pro tool’s handler (five mutations on record).
  • Verified: two launch sweeps on the 3.4.0 tree. Pro build — Divi 5.11.0 on the rig 3,033 of 3,034 with eight plugins and 2,929 of 2,930 alone (the one row is Serving over HTTPS, which a plain-HTTP rig cannot satisfy), six other themes 2,027/2,027 and 1,911/1,911; from wp-admin 2,929 of 2,930, 2,028/2,028, 1,912/1,912; every self-test group equal to 3.3.4 to the check except the Guardian group, whose 40 loop checks now run as their own Pro group, and one new Legal pin. Free build, from the packaged free zip — Divi 2,512 of 2,513 with eight plugins and 2,410 of 2,411 alone, six other themes 1,838/1,838 and 1,710/1,710; from wp-admin 2,410 of 2,411, 1,839/1,839, 1,711/1,711; 52 rows on Divi and 43 elsewhere named “Not in this build” and kept out of those totals; 0 fatals, admin render harness green with every Pro screen locked. 0 PHP notices from this plugin at E_ALL on both builds; 17 offline harnesses green; CI green on PHP 8.0, 8.4 and 8.5, both zips reproducible to the byte on three machines. Live on this site after upgrading, on Divi 5.13: all 984 read-side checks passing.

3.3.4

licensing

  • A licence key in wp-config.php is now verified, not trusted. Until 3.3.3, DVC_LICENSE_KEY wrote a lifetime Pro record for any non-empty string without contacting the licensing provider — a paywall one documented line long. The constant is now only a place to put the key: it is sent to Freemius from the first wp-admin request by an administrator, the verdict is remembered per key with a six-hour retry, and the Licensing panel shows whether the key is pending, verified, refused (with the provider’s reason and a Check-it-now button) or unverifiable. A record the old path wrote is removed on the first request after upgrade and refused by the tier resolver regardless. No SDK means no verification, which means Free.
  • One enforcement lift in the self-test was not in a finally. The no-Divi report check switched tier gating off around one tool call and back on afterwards; a fatal or a gateway kill inside that call would have left a Free site with its paywall off until something else wrote the option. It is in a try/finally now, and a new check walks the suite’s own source and fails if any lift is ever bare again.
  • Verified: launch sweep on the 3.3.4 tree, fourteen runs — Divi 5.11.0 on the rig 3,032 of 3,033 with eight plugins and 2,928 of 2,929 alone (the one row each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 2,026/2,026 with eight plugins active and 1,910/1,910 bare; 2,928 of 2,929, 2,027/2,027 and 1,911/1,911 from wp-admin; 1,911/1,911 with the admin context defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. The licensing block drives a stubbed provider through refuse, accept and absent and pins that the wp-config path never writes entitlement; seven mutations, M154–M160, each turning a named check red. Live on this site after upgrading, on Divi 5.13: all 983 read-side checks passing, this site’s own Freemius licence intact.

3.3.3

  • “1,167 issues” was mostly one warning, 919 times. The counter beside the Site Score added up every occurrence of every open finding — on a 506-post site that read 1,167, of which 19 were failures and 919 were a single SEO warning repeated on every post. Nothing there was wrong, and it looked like a site in ruins. The headline now counts causes, split by what they are: Critical (failing causes — a visitor, a liability or the install is affected) and To fix (warning causes — work), with the occurrence count on the line under them, because that is the number the score is computed from and the two must be seen together. Waivers apply by cause key, so accepting a finding removes exactly one cause; the self-test plants a waiver on a live cause and checks that. The index of causes rides on the summary each audit already writes, so the panel stays a cheap read; a summary written before 3.3.3 is rebuilt from the archive once.
  • The assistant gets the same split. station_get_score now returns a counters block — critical and to-fix causes, per-audit cause counts, occurrences, fixed, ignored — with a note telling it to report causes to the owner, never the occurrence total; station_audit_site group=“all” carries the same block on finish, and the skill says so.
  • Verified: launch sweep on the 3.3.3 tree, fourteen runs — Divi 5.11.0 on the rig 3,020 of 3,021 with eight plugins and 2,916 of 2,917 alone (the one row each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 2,026/2,026 with eight plugins active — 2,027/2,027 on OceanWP — and 1,910/1,910 bare; 2,916 of 2,917, 2,027/2,027 and 1,911/1,911 from wp-admin; 1,911/1,911 with the admin context defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Six mutations, M148–M153, each turning a named check red; the first sweep caught the reconciliation pin reading a per-request memo instead of the archive, and the tree was fixed and swept again. Live on this site after upgrading, on Divi 5.13: all 971 read-side checks passing, and the panel reading 0 critical · 7 to fix · 14 occurrences · 10 resolved.

3.3.2

Divi 5.13

  • The number beside the score was mostly the self-test’s own homework. Every fix the suite applies to its own fixture pages was journaled as one of yours and counted toward “fixed” — on this site, “135 fixes standing” was about 130 self-test rows on pages that no longer existed, and the journal’s hundred-entry window had pushed every real fix past its undo button. A fix the self-test writes now carries a flag, never moves the durable counter, and is dropped by the suite’s own cleanup. On upgrade the counter is reseeded once from what can be shown: fixes standing in the journal now, not undone and on a page that still exists. It goes down on any site that has ever run the self-test — here, 135 became 5 — and that is the honest number.
  • A Divi upgrade quietly switched off part of the accessibility audit. The map of which modules contribute headings is probed on one Divi version; the next version’s first read-only audit rebuilt it without probing, and every page then carried “the module map has not been completed” until somebody happened to run the accessibility audit from wp-admin. On this site that was 27 rows across every page, the morning after Divi 5.13. Preflight now carries a Module map row that names the version the map was built for against the one running; when they differ it rebuilds the map once, from the admin request it is allowed to write in, before you have clicked anything.
  • A busy full audit now says who is busy. The self-test’s Full audit group went red eight times on 5.13 with “another request is advancing the full audit” and nothing said which request. The lock now records its caller and age — “the wp-admin Audit tab, 12s ago” — and the self-test stands down by name when it meets one instead of reporting someone else’s run as eight defects of this build.
  • Divi 5.13 itself. Checked the morning it landed, with 3.3.1 watching: 944 of 944 read-side checks, the full audit on all seven groups through the connector, the landmark repair applied and verified, the module index unchanged at 114. The slider’s automatic-animation attribute the Design Guardian fix flips is unchanged; sliders gained swipe controls, the Contact Form gained a Show Labels option (the unlabelled-field advice now names it), and the Theme Builder wrapper still takes role=“main”.
  • Verified: launch sweep on the 3.3.2 tree, fourteen runs — Divi 5.11.0 on the rig 3,013 of 3,014 with eight plugins and 2,909 of 2,910 alone (the one row each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 2,019/2,019 with eight plugins active and 1,903/1,903 bare; 2,909 of 2,910, 2,020/2,020 and 1,904/1,904 from wp-admin; 1,904/1,904 with the admin context defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Thirteen mutations, M135–M147, each turning a named check red. Live on this site after upgrading, on Divi 5.13: all 964 read-side checks passing, the Preflight row reading “Module map: complete for Divi 5.13”, and the score panel’s fixed counter reseeded from 135 to 5.

3.3.1

  • Every Divi 5 Theme Builder site was serving a document with no main landmark, and the audit could only tell you to edit the theme. Divi prints its header layout as <header>, its footer as <footer>, and the page’s own content as a plain <div> — so a screen-reader user pressing the “jump to main content” key gets nothing, on every page of every such site, and the finding stood on this plugin’s own marketing site since the check shipped. The Fix button now answers it: Divi 5.6 added a filter on exactly that wrapper’s attributes, and the repair puts role=“main” there from inside the request — one attribute on an element that already exists, nothing wrapped, moved or re-rendered, once per page and on the outermost content layout. Like the pinch-zoom and skip-link repairs before it, it then re-reads the page this server serves and refuses by name — the switch turned back off — if the attribute did not land or if the page would end up with two main landmarks. A landmark repair that doubles a landmark has made the document worse while silencing the check, so it is not applied.
  • The other half of that finding is honest about what a plugin cannot do. The same check covers <nav>, and no code can know which links are the menu. The finding’s own state already told the halves apart — a warning with no main landmark, information when only the menu is unmarked — so the Fix control is offered for the first and replaced by the sentence for the second: Divi’s Menu module renders <nav> on its own; links hand-written in a Code module need wrapping in <nav aria-label=“Primary”>. Asked to fix it anyway, the fixer writes nothing and gives the same sentence.
  • The font audit reads the served page before it accuses. “A family this plugin does not enqueue” was reported six times on this site for families the theme itself was loading — the check knew what this plugin served and assumed nothing else did. It now reads the Google Fonts links and @font-face rules in the home document as well; a family something else serves is covered, and when the home page cannot be fetched the finding says the claim was not checked rather than asserting it.
  • Uninstall removes eleven more things it wrote — one of them an API key. 3.3.0 added five names a review had found. This release stops enumerating by hand: the self-test now scans the source for every option name the plugin passes to the options API, resolves the constants, and diffs the result against the uninstall list on every run. First run found a PageSpeed credential from a feature removed releases ago, still in the options table of any site that once set it, along with the three document-repair switches, the hardening switches, the full-audit plan, the seventh audit group’s run archive, the fixed-findings counter, the site’s structured data, the per-page H1 render cache and a test fixture. All swept now, and the list cannot drift again without a named check going red.
  • Verified: launch sweep on the 3.3.1 tree, fourteen runs — Divi 5.11.0 on the rig 2,993 of 2,994 with eight plugins and 2,889 of 2,890 alone (the one row each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 2,001/2,001 with eight plugins active — 2,002/2,002 on OceanWP — and 1,885/1,885 bare; 2,889 of 2,890, 2,002/2,002 and 1,886/1,886 from wp-admin; 1,886/1,886 with the admin context defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. The landmark repair’s apply, verify and undo ran for real against a Divi 5 page served as the rig’s front page, and refused by name on the rig’s classic Divi index. Nineteen mutations, M116–M134, each turning a named check red. Live on this site after upgrading, on Divi 5.13: all 944 read-side checks passing, and the Fix applied to this site’s own long-standing landmark warning — the served page came back with exactly one main landmark, on every page, and the accessibility audit now reports 0 failures and 0 warnings across 30 pages.

3.3.0

security review

  • A search-and-replace could empty a page and report that it had worked. The text you search for becomes a regular expression three times over — once plainly, and twice in the escaped forms block attributes are stored in, where one character can become six. Past about 5,400 characters the second form exceeded what the regular-expression engine will compile. It answers a failure with nothing at all, the code read nothing as empty text, and on a page whose content is not block markup — a classic page, or any builder that keeps its layout in post meta — no guard downstream caught it. The page was written empty and the result reported one successful replacement. Every pass is now checked rather than cast, a failure is reported against that page and nothing is written for it, and a result that would empty a non-empty page is refused on every builder.
  • One big module could hide a broken one on the same page. The scan that refuses a write whose block attributes will not parse — the thing standing between an assistant and a module silently stripped of its background, preset and content — gave up on a single valid module whose styling passed roughly 25 KB, and the code read giving up as “nothing wrong here”. Six write paths trust that answer. The scan is now a linear walk that does not back-track: 190 KB of attributes reads in four milliseconds, and a scan that fails refuses the write instead of blessing it. Equivalence with the old one was established by fuzzing the two against each other over 60,000 generated inputs.
  • The connector key can now be bound to a user of your choosing. It resolved to the first administrator on the site, always. Bind it to an Editor and the connector is an Editor. Nothing changes until you set it, and a stale binding falls back rather than locking your own connector out. Header credentials are also read before the query-string form now, so a client able to send a header no longer leaves the key in your web server’s access log.
  • Three things that should never have left the building. The client report — the one written to a public folder and handed to someone as a link — is now scrubbed of this site’s own credentials before it is written. Uninstall removes five more things it wrote, including the permanent record of every assistant that ever connected. And the self-test no longer edits your audit history: it drives real audits to test them, and used to put back only half of what it borrowed.
  • Also: the cross-site page pull is bounded before the response is in memory; the OAuth token endpoint verifies the client and redirect the code was issued against; credential redaction in the activity log catches spellings it was missing; and a post-write verification that cannot re-read the page now rolls back instead of passing silently.
  • Verified: launch sweep on the 3.3.0 tree, fourteen runs — Divi 5.11.0 on the rig 2,959 of 2,960 with eight plugins and 2,855 of 2,856 alone (the one row each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,967/1,967 with eight plugins active and 1,851/1,851 bare; 1,968/1,968 and 1,852/1,852 from wp-admin; 1,852/1,852 with the admin context defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL; 17 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Eighteen mutations, M98–M115, each turning a named check red. Live on this site after upgrading: all 915 read-side checks passing, and the full-audit group that had been showing eleven red checks now reports 59 of 59.

3.2.14

  • Replacing a price silently wrote the wrong text, and reported success. station_search_replace puts the replacement through preg_replace, where $1, ${1} and \1 are backreferences rather than characters. Every needle is escaped before it becomes a pattern, so the pattern has no capture group at all and each of those expanded to nothing: “$3.32” wrote “.32”, “$199” wrote “9”, and “$0” wrote the matched phrase back over itself. The replacement count came from preg_replace, so the result said it had worked. Changing a price is the most ordinary thing this tool is asked to do. Found on this plugin’s own pricing page — two applies in a row reported a replacement and the live page never changed. Replacements are now literal text on all three passes (plain and both JSON-escaped forms) and in the title path, pinned with a price fixture end to end.
  • Verified: launch sweep on the 3.2.14 tree, fourteen runs — Divi 5.11.0 on the rig 2,819 of 2,820 alone and 2,923 of 2,924 with eight plugins (the one row on each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,931/1,931 with eight plugins active and 1,815/1,815 bare; 1,932/1,932 and 1,816/1,816 from wp-admin; 1,816/1,816 with WP_ADMIN defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL. Two mutations: M96 removes the escaping and M97 leaves it in place but unwired at the call site — the defect was a call site, and the second mutation is the one that proves the pin would have caught it. 16 offline harnesses green, i18n 54/54 across eight locales.

3.2.13

  • Every Divi 5 page was being reported as carrying a broken block. The “unregistered block” check means the plugin that provided a block is gone and the editor shows an error where content used to be. divi/placeholder is the opposite of that: it is Divi 5’s own structural page root, Divi never registers it as a block type, and this plugin’s own writers add it when it is missing. So the check fired on every Divi 5 page on every Divi 5 site, and its advice was to reinstall the theme the owner was already running. On this plugin’s own marketing site it was 15 of the 20 open warnings. The active builder now declares its own internal blocks and only those are exempt, so a block whose plugin really has gone is still reported by name on the very same page.
  • The focus-outline finding now checks the thing it says it checks. It has always read “removes the keyboard focus outline site-wide … without replacing it”, and nothing ever looked for the replacement. A site that did exactly what the fix text prescribes — leave the theme’s reset alone, restore the ring on :focus-visible — was reported anyway. Found here, one remedy later. A replacement now has to clear the same breadth bar as the removal, so a ring on a single component still answers nothing.
  • A write that reported success and changed nothing. station_edit_module wrote any attribute into a block’s attribute JSON. That is always right for a Divi module, whose JSON IS its storage, but a core block declares some attributes with source html or rich-text, meaning the value lives in the block’s own markup and the JSON copy is never read. Adding links to a paragraph on a Divi site therefore parked the text where nothing reads it and returned success. It is now a refusal that names the attribute and the tool that does work; the check is driven by the block type’s own declaration, so it is correct for third-party blocks and inert for Divi modules by construction.
  • Verified: launch sweep on the 3.2.13 tree, fourteen runs — Divi 5.11.0 on the rig 2,817 of 2,818 alone and 2,921 of 2,922 with eight plugins (the one row on each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,929/1,929 with eight plugins active and 1,813/1,813 bare; 1,930/1,930 and 1,814/1,814 from wp-admin; 1,814/1,814 with WP_ADMIN defined; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL. Seven mutations, M89-M95, each turning a named check red — including both directions of the new block exemption (remove it and the wrapper is reported again; abuse it and a real orphan is hidden). 16 offline harnesses green, i18n 54/54 across eight locales.

3.2.12

  • A page is now told it has no H1 only if the page it serves has no H1. The accessibility audit has verified since 3.2.9 that a page rendering an H1 from its template AND one from its content serves two. This release verifies the other half. On this plugin’s own site the heading fixer correctly demoted the duplicate H1 on the privacy policy — the template already supplies the title as H1 — and the very next audit reported that same page as having no H1 at all. Both halves now rest on one render of the page, cached on its modified time: an H1 in the served document that the content does not store removes the finding, a render with none keeps it and says the document was checked, and a render that could not be fetched leaves the finding exactly as it was. Nothing is inferred from what the post type’s default template happens to do.
  • The fixer reads the same answer, so it cannot undo its own work. One function answers the question for both, which is what stops the heading fixer offering to promote a heading back into the duplicate it had just removed. A self-test check fails if the audit and the fixer ever disagree about the same page in either direction.
  • And a red check that was the suite’s own fault. Running the self-test from the Diagnostics screen reported one failure on a healthy site: a skip-link check asserted front-end behaviour without allowing for the fact that the route it tests deliberately stands down in wp-admin — which is exactly where that screen runs it. Two sibling checks had the opposite fault and passed there on the outer guard rather than their own claim. All three now decide by context, and where a claim cannot be told apart from its guard the screen says so by name instead of counting a pass. Nothing a visitor sees changed.
  • The verification rig gained the context that hid it. Not one of its thirteen runs had ever had is_admin() true — the axes it varied were theme, plugin stack and invocation path, and none of them was that one. There is a fourteenth run now, and reverting this release’s change turns that run, and only that run, red.
  • Verified: launch sweep on the 3.2.12 tree — Divi 5.11.0 on the rig 2,805 of 2,806 alone and 2,909 of 2,910 with eight plugins (the one row each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,923/1,923 with eight plugins active and 1,807/1,807 bare; 1,924/1,924 and 1,808/1,808 from wp-admin, and 1,808/1,808 on the new run; admin render harness green on all fourteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Eight mutations, M81–M88, each turning a named check red. Live on this site after upgrading: all 2,670 checks passing from wp-admin with both skips named, 0 open failures across all seven audits, and the false missing-H1 report gone — one finding fixed, none introduced.

3.2.11

not released on Freemius

  • “Page has no H1.” is now answered by the page this site actually serves. 3.2.10’s own fix opened this one. On this plugin’s own site the headings fixer correctly demoted the duplicate H1 on /privacy-policy/ — the template already renders the title as the page’s H1 — and the very next audit reported that same page as having no H1 at all. Both halves of the claim now rest on one render of the page, cached on its modified time: an H1 in the served document that the content does not store removes the finding; a render with none keeps it and says the served document was checked; a render that could not be fetched leaves the finding exactly as it was, with the note that verification failed. Nothing is inferred from what the post type’s default template does.
  • The fixer reads the same answer, so it cannot undo its own work. DVC_A11y::template_supplies_h1() answers the absence question too, which is what stops the headings fixer offering to promote a heading back into the duplicate it had just removed. A self-test pin fails if the audit and the fixer ever disagree on the same page in either direction, and two of this release’s mutations are exactly those two disagreements.
  • Verified: launch sweep on the 3.2.11 tree — Divi 5.11.0 on the rig 2,805 of 2,806 alone and 2,909 of 2,910 with eight plugins (the one row on each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,923/1,923 with eight plugins active and 1,807/1,807 bare; 1,924/1,924 and 1,808/1,808 from wp-admin; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL. Seven mutations, M81-M87, each turning a named check red — including the two arms of the new comparison, the per-page verification being skipped, and the fixer reverting to the 3.2.10 answer. 16 offline harnesses green, i18n 54/54 across eight locales.

3.2.10

not released on Freemius

  • The finding and its Fix button now read the same question. 3.2.9 taught the audit to verify per page whether something outside a page’s content renders an H1 for it, and left the headings fixer reading the old per-post-type verdict. So on this plugin’s own site the new finding appeared on /privacy-policy/ — correctly, two H1s — and its Fix button answered “the heading outline on this page is already correct”. A reported defect whose own fix refuses it is a dead click, and this project has treated that as worse than no button since 1.4.0. Both now call DVC_A11y::template_supplies_h1(), which answers per post type where that is enough and per page where it is not, and a self-test pin fails if the two ever disagree on the same page again.
  • 3.2.9 was never released: it was tagged, built and installed here, where the dead click showed up in the first click. Its pending deployment is deleted.
  • Verified: launch sweep on the 3.2.10 tree — Divi 5.11.0 on the rig 2,797 of 2,798 alone and 2,901 of 2,902 with eight plugins (the one row on each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,913/1,913 with eight plugins active and 1,797/1,797 bare; 1,914/1,914 and 1,798/1,798 from wp-admin; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL. Nine mutations, M72-M80, each turning a named check red — the last two being the audit and the fixer each reverted to the verdict the other had outgrown. 16 offline harnesses green, i18n 54/54 across eight locales. Live on this site after upgrade: the finding on /privacy-policy/ and a Fix that actually demotes.

3.2.9

tagged, never released

  • A page that renders two H1s is now reported on the evidence of its own render, not on a verdict about a different page. Since 0.73.0 this audit has verified one rendered fact per post type — whether the theme’s template outputs the title as the page’s H1 — by fetching the newest post whose stored content has no H1, and it is that verdict the “renders with two H1s” finding hangs off. On a site that composes pages from more than one template, which is every Theme Builder site and any site using page templates, that verdict is evidence about a document the page being audited may not share. Found on this plugin’s own site: /privacy-policy/ serves “Privacy Policy” from its template AND “Who we are” from its content — two H1s, WCAG 1.3.1 — and the audit reported nothing at all, because the verdict for pages here is not “true”. Under-reporting, which is the worse direction: an over-report gets investigated, an under-report gets believed.
  • So where it matters, the page is rendered and counted for itself. Only where it matters: the content has to carry an H1 of its own (a minority of pages), and the answer has to be one the per-type verdict cannot already give. Bounded three ways — four renders per request, so one audit batch cannot run out of time; a cache keyed on each page’s own modified time, so a page is verified once and re-verified when it is edited; and an unreachable page is cached briefly rather than retried on every run. The finding quotes the H1s the visitor actually gets and says which of them the content stores.
  • The blank-template rule is now scoped to what the document serves. 2.0.0 taught the engine that Divi’s blank page template renders no title, so a landing page on it is not carrying a second H1. That was a claim about a template, and on a site whose theme has no such template at all it was simply wrong. It now yields to the render: where the page really does serve two H1s, that is what is reported, and the template-based wording never appears on a page that was verified directly.
  • Verified: launch sweep on the 3.2.9 tree — Divi 5.11.0 on the rig 2,796 of 2,797 alone and 2,900 of 2,901 with eight plugins (the one row on each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,912/1,912 with eight plugins active and 1,796/1,796 bare; 1,913/1,913 and 1,797/1,797 from wp-admin; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL. Seven new checks, including the seven-way comparison table over fixtures, the cache key, the render budget and the end-to-end case on a fixture page whose template adds an H1; seven mutations, M72-M78, each turning a named check red — one of which exposed a pin that was passing because an earlier run of itself had left a cached answer behind. 16 offline harnesses green, i18n 54/54 across eight locales.

3.2.8

  • Keyboard users can skip the header now, and the Fix button does both halves. “No skip to content link” has been a warning in the Accessibility group since 2.0.0, and its advice cannot be followed on Divi: the rendered page has no content wrapper with an id, so there is nothing for a hand-written link to point at. A keyboard user tabs the whole menu on every page. The fix prints the link as the first thing inside <body> AND puts its target at the top of the content, with one small style block so the link shows while it has focus and is invisible otherwise. Nothing about the layout changes. One reversible switch, journaled and undoable like every other fix.
  • It works in eight languages by construction. The link carries a class as well as its visible words, and the accessibility check matches either — so a translated “Zum Inhalt springen” still clears the finding. A fix that cleared it in English and left it standing in German would be worse than none, and a self-test pin holds exactly that case.
  • And it measures what it claims: Tab presses saved. Three facts have to be true of the page the site serves afterwards, and none can be known before the write — the link is printed, the target is printed, and at least three focusable elements sit between them. On this site that number is seven: six menu links and a call to action, on every page. Where any of the three is false the fix turns its own switch back off and names which one, with the remedy for that one. A skip link pointing at an id that does not exist is worse than none: it silences the audit and goes nowhere.
  • 3.2.6 and 3.2.7 were built, tagged and never released, and that is the feature working. Installed here, each applied, re-read the page, disagreed with itself and rolled back — first because the target landed above the header on a Theme Builder page, then because the floor was counting characters of text instead of keyboard stops, and this site’s entire navigation is 56 characters. A verification that only ever confirms is decoration. No customer saw either one.
  • Verified: launch sweep on the 3.2.8 tree — Divi 5.11.0 on the rig 2,790/2,791 alone and 2,894/2,895 with eight plugins (the one row each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,906/1,906 with eight plugins active and 1,790/1,790 bare; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Twenty mutations, each turning a named check red. Live on this site: 0 open failures across all seven audits, and the skip link verified against the page this server serves — seven Tab presses saved on every page.

3.2.7

skip link

  • The skip link now anchors where it actually skips the header, on a Theme Builder site too. 3.2.6 wrote its target at loop_start, which is inside the content area on a blog index, an archive or a search page — and on a singular Theme Builder page is not, because the main loop there starts before the template prints the header. The target landed above the header and the link skipped nothing. loop_start is now the listing route only; on a singular request the target goes into the_content, which runs inside the content area wherever the builder puts it.
  • 3.2.6 was never released, and the reason it was not is the feature working. Installed on this plugin’s own site, the fix applied, re-read the page, measured 56 characters of text between the link and its target, turned its own switch back off and said so. A verification that only ever confirms is decoration; this one refused the release. The floor that caught it (120 characters of real text) and the rollback behind it are unchanged — only the anchor point moved, and a self-test pin now holds loop_start to standing down on a singular request.
  • Verified: launch sweep on the 3.2.7 tree — Divi 5.11.0 on the rig 2,787 of 2,788 alone and 2,891 of 2,892 with eight plugins (the one row on each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,903/1,903 with eight plugins active and 1,787/1,787 bare; 1,904/1,904 and 1,788/1,788 from wp-admin; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL. Skip-link repair 31/31 on the Divi rig, end to end against the real served page: apply, verify (205 characters of header skipped on a blog-index front page), undo brings the finding back. Sixteen mutations, M52–M67, each turning a named check red. 16 offline harnesses green, i18n 54/54 across eight locales.

3.2.6

skip linknever released

  • Never shipped: its own verification refused it. This entry is here because the changelog is written from the plugin’s readme and says what happened, not only what reached customers. The work below is real and went out in 3.2.7 with the anchor point corrected.
  • A keyboard user can now skip past the header on a Divi site, from the finding. “No skip to content link” had been a warning in the Accessibility group since 2.0.0, and its advice — add the link as the first thing inside <body> and give the content wrapper id=“main” — cannot be followed on Divi: the rendered page goes body, page container, boc, the header layout, the body layout, and the body layout carries no id, so there is nothing for a hand-written link to point at. A keyboard user therefore tabs the whole header and menu on every page of every Divi site. station_fix_finding on a11y-doc-skip-link (and the Fix button on the finding) now writes both halves: the link on wp_body_open, and its target at the top of the main query’s content. One reversible switch, journaled and undoable.
  • It works in eight languages, which is a property of the markup and not of the translation. The link carries class=“dvc-skip-link” as well as its visible words. The accessibility check recognises either, so a translated “Zum Inhalt springen” still clears the finding — a fix that cleared it in English and left it standing in German would have been worse than none, and the self-test pins exactly that case.
  • And it measures whether the link actually skips anything before it believes itself. Three facts have to hold in the document the site serves afterwards, and none can be known before the write: the link is printed (a theme that never calls wp_body_open swallows it), the target is printed (a page template that renders neither the main loop nor the_content swallows it), and enough header text sits between the two to be worth jumping. A skip link pointing at an id that does not exist is worse than no skip link — it silences the audit and goes nowhere. So the fix re-fetches the home page, and on any of those three it turns the switch back off and says which one, with the remedy for that one. Nothing is journaled for a fix that did not take.
  • Found by running it: the first version worked on a page and refused itself on a blog. On the rig, whose front page is the posts index, the link printed and the target did not — there is no singular content to filter there. The target now has two routes, loop_start and the_content, first one wins, which covers a page, a post, an archive, a blog-index front page and a Theme Builder body layout. The refusal that caught it was the feature working; the narrow fix behind it was the bug.
  • Verified: launch sweep on the 3.2.6 tree — Divi 5.11.0 on the rig 2,786 of 2,787 alone and 2,890 of 2,891 with eight plugins, both invocation paths; the six other themes 1,902/1,902 with eight plugins active and 1,786/1,786 bare; 1,903/1,903 and 1,787/1,787 from wp-admin; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL on every configuration. The new Skip-link repair group is 30/30 on the Divi rig; fifteen mutations, M52–M66, each turning a named check red, one of which survived the first pass because the line it attacked was already covered by WordPress’s own tag stripper. 16 offline harnesses green, i18n 54/54 across eight locales.

3.2.5

  • A viewport that blocks pinch-zoom now has a Fix button. It is a WCAG 1.4.4 failure, this audit has reported it since 2.0.0, and the advice it shipped with — “remove user-scalable=no from the viewport meta in the theme header” — was true and unactionable: on Divi that tag comes out of the theme’s own functions.php, so following it means editing a theme that updates over itself. Every Divi site fails this check for that reason, and so did this one, for four releases, while the plugin reported it. Fix it from the finding now: one reversible switch, and while it is on the plugin rewrites the viewport meta the site serves so visitors can zoom. Desktop rendering is identical. Journaled and undoable like every other fix.
  • It only ever removes a restriction. user-scalable=no and a maximum-scale below 2 are dropped; width, initial-scale, viewport-fit and anything else the theme wrote pass through verbatim and in order, with the tag’s own quoting and attribute order intact. What it will not do is add a viewport meta to a page that has none — that makes every phone re-render the site at device width, which on a theme without media queries changes the layout for every visitor. That half of the check says so where the button would have been, instead of offering one.
  • And it re-reads the page the site serves before it calls itself applied. The rewrite reaches what wp_head prints, which is where Divi and every plugin put that tag — but not a theme that prints it directly in header.php, and not a full-page cache still serving the old HTML. So the fix applies the switch, fetches the home page again, and if the document still blocks zoom it turns the switch back off and says which of those two it is. Nothing is journaled for a fix that did not take. “Applied” over an unchanged failure is the one answer a fixer must never give.
  • One check, two causes, one sentence. Where a fixable check has a cause no fixer can answer, the refusal the engine gives and the explanation the screen shows now come from the same place. That rule has existed since 1.4.2 for broken design tokens, as a hand-written condition in the admin with its own copy of the wording — two texts nothing held together.
  • Verified: launch sweep on the 3.2.5 tree — Divi 5.11.0 on the rig 2,755/2,756 alone and 2,859/2,860 with eight plugins (the one row each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,873/1,873 with eight plugins active and 1,757/1,757 bare; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Sixteen mutations, each turning a named check red, two of which survived the first pass and named a real gap in the new checks themselves. Live on this site after upgrade: 848/848 read-side, the fix applied and verified against the page this server serves, and the accessibility audit re-run — 0 failures, the first time this site has had none.

3.2.4

  • The accessibility conformance check now catches what it is for, and still leaves honest statements alone. 3.2.3 rewrote it as an assertion test plus a qualifier test over the whole document, and an adversarial review found that cure worse than the disease. One qualifying phrase anywhere defused the entire page — and the Accessibility Statement this plugin’s own installer writes says “We work toward the Web Content Accessibility Guidelines (WCAG) 2.1 Level AA”, so on every site that used the installer the check was switched off for good. An owner who later published “This website conforms to WCAG 2.1 Level AA” over forty open failures would have got no finding at all. The assertion test also missed the most ordinary phrasings there are: “WCAG 2.2 AA conformant”, “complies with WCAG 2.1 AA”, “meets all Level AA success criteria”, “We are ADA compliant”.
  • One root cause, and it is worth stating: a conformance claim is a property of a SENTENCE, not of a document. A statement that says in one paragraph that it has not arrived has not stopped claiming conformance in another. The unit of judgement is now the sentence; the assertion must be anchored to a named standard (WCAG, Level AA, Section 508, EN 301 549, ADA), so “this privacy policy is compliant with the GDPR” is no longer read as an accessibility claim; and aspiration in the same sentence still defuses it. Twenty-seven wordings are pinned as one table, and the finding now quotes the sentence it objects to.
  • A skipped self-test group can no longer read as a clean pass. 3.2.3’s stand-down carried its reason in a passing assertion, and assertions discard their detail when they pass — by design, so failure prose never prints beside a green check. The reason therefore reached nobody, and the screen said “all checks passing” with twenty-nine checks not run. It now records a skipped row. And the note that explains a short run stopped inventing the reason: it threw every skipped group’s own explanation away and printed one hardcoded sentence about Divi’s builder framework, so an owner whose WooCommerce group was skipped because WooCommerce is not installed was told it was a wp-admin limitation.
  • The stand-down now sees the audits most likely to be running. 3.2.3 asked only about the full-audit plan, and so missed the first source of contention its own release notes named: the scheduled digest does not use the full-audit engine. It drives one group at a time, as do the per-section buttons and the individual check tools, and none of them touches the plan — so during the weekly digest the plan looked idle and the self-test reset the group the digest was halfway through. The question now covers the plan, any group’s batch lock, and any group’s run between batches.
  • An abandoned audit no longer promises to continue. One plan could be written off by the engine and still offered as “Continue full audit” on screen, while Start over silently refused because a plan nobody had driven since Friday still said “running”. The button, the auto-resume and the engine now use one rule.
  • Four of 3.2.3’s own new checks could be defeated by a one-line change, and every one is rewritten. Fixtures now set “started” and “last advanced” to different values (3.2.3 set them equal, so reverting the entire point of the change left six checks green); the abandonment window is pinned to a justified range rather than only to fixtures derived from itself; the stand-down’s early return and its skipped row are asserted, not just its position in the file; and the timestamp check reads the stored plan rather than the value the call happened to return.
  • Verified: launch sweep on the 3.2.4 tree — Divi 5.11.0 on the rig 2,729/2,730 alone and 2,833/2,834 with eight plugins (the one row each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,849/1,849 with eight plugins active and 1,733/1,733 bare; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green, i18n 54/54 across eight locales, CI green on PHP 8.0, 8.4 and 8.5. Forty-six new checks; fourteen mutations, each turning a named check red, including four that escaped 3.2.3 entirely. Live on this site after upgrade: 2,598/2,598 from wp-admin with one group named as skipped — and with an audit deliberately left half-finished, 2,556/2,556 passing, the half-finished audit still there afterwards, and the screen naming both skipped groups with their own reasons.

3.2.3

tagged, never released

  • The self-test stops fighting your own audit. The Full audit checks drive real audits and clear the stored plan to do it. That plan has no expiry and stays “running” BETWEEN batches — which is most of the time, because the design is one batch per call with the browser, the schedule or an assistant coming back for the next one. So on a live site where something else was mid-run, the self-test threw that progress away and then reported the contention it had caused as failures. Found on this site: 3 red checks on one run, 6 on the next, and all of them green again once the leftover plan was driven to finish. It now stands down, by name, and leaves the run alone.
  • And “in flight” now ages out. “Running” and “still being driven” are not the same thing, so without this one closed tab would have disabled those checks for good. Every batch stamps when it moved; a plan nobody has advanced for ten minutes counts as abandoned, and a plan written by an older version ages out on its start time rather than inheriting a permanent stand-down.
  • A mutation escaped, and the fix was to move the seam. With the staleness rule inside the function the new gate calls, its own checks sat BEHIND that gate — so deleting the rule made the gate fire, the gate silenced the checks, and the suite reported 1 of 1 passing on a build where the thing was simply broken. A gate that can switch off its own tests is not testable. The rule now lives in a pure function judged from fixtures, in front of the gate, and the same mutation turns two named checks red.
  • The accessibility conformance check stops crying wolf. It exists to catch a published WCAG conformance claim on a site whose own accessibility audit is failing — the one check that needs both the document and the audit. But it fired on any statement containing the word “WCAG”, including the hedged statement its own advice asks you to write. This site’s own statement says “We are actively working toward WCAG 2.1, Level AA. We do not claim to have arrived”, and the check called that a misstatement. It is now two-sided, in W3C’s own vocabulary: an ASSERTION of conformance, unless the document also qualifies it — and it catches two real claims the old test missed.
  • Verified: launch sweep on the 3.2.3 tree — Divi 5.11.0 on the rig 2,681/2,682 alone and 2,785/2,786 with eight plugins (the one row each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,801/1,801 with eight plugins active and 1,685/1,685 bare; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL; 16 offline harnesses green and CI green on PHP 8.0, 8.4 and 8.5. Nineteen new checks, six mutations each turning a named one red. Live on this site after upgrade: 778/778 read-side, and the thing the release is about — with an audit deliberately left half-finished, the self-test came back 2,527/2,527 passing and the half-finished plan was still there afterwards; idle, 2,550/2,550. Its own score went 90 to 92 as the false failure cleared.

3.2.2

  • An alt-text fix on Elementor is reversible again. The undo transaction keeps the first state it was given, which is right for the page and wrong for the images a batch reaches along the way: the fixer opens the transaction before it knows which images it will touch, so each image’s previous alt text arrived afterwards and was discarded. Undo then restored the page tree — which that fix never changes — and reported full success while the overwritten alt text stayed overwritten. The prior text is now added to the open transaction, first-wins per image, and the write refuses outright if it cannot be recorded.
  • Fixing an image’s alt no longer re-saves the whole page. The alt lives in the media library, so that fix should not touch the document at all — but it was running the entire tree through Elementor’s save pipeline for a byte-identical result, and checking your permission on the image only after that write. The arm is chosen before anything is written, and the media-library path now performs zero document saves.
  • Losing an element is an error, not a warning. A save that came back missing elements returned a success result carrying a warning, and every fixer checks only for errors — so a fix that made Elementor drop a section reported itself as written. It now fails, names the elements and points at the undo slot in the same sentence.
  • Undo no longer writes over work done since. Restoring wrote the stored rows straight onto the live page with no check that the page was still the one the slot describes and no step back of its own. It now refuses while the page is open in an editor, keeps what it replaced (so restoring twice toggles), and says plainly when the page had moved on.
  • Found by an adversarial review, not by the tests that shipped 3.2.0. Six content-loss defects in one pass over a release that had 94/94 in its own group, nine recorded mutations and green CI on three PHP versions. Every one now has a pin and a recorded mutation — and two of those pins had to be rewritten after the mutation pass showed them passing for the wrong reason, including one that could not see a needless page save at all, because Elementor re-serialises to the same bytes.
  • Verified: launch sweep on the 3.2.2 tree — Divi 5.11.0 on the rig 2,662/2,663 alone and 2,766/2,767 with eight plugins (the one row each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,782/1,782 with eight plugins active and 1,666/1,666 bare; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL on every configuration; the Elementor group 103/103 with six new regression checks, each mutation-tested; 16 offline harnesses green. Live after upgrade: 760/760 on this site, 749/749 on a second Divi install, 446/446 on an Elementor install.

3.2.1

  • Checknaut now fixes Elementor pages, not just reads them. Alt text, heading level, link text and any element setting write through Elementor’s own document save — from the audit finding straight to the fix, in the plugin or over the connector. Whole-page building on Elementor is still refused and says so by name; that is the next release, not a silent gap.
  • The undo is the point. Elementor keeps the page in a post meta row, and WordPress revisions do not record post meta — so the usual “take a revision, then write” would have produced an undo that restores cleanly, reports success and leaves the edit in place. Writes to a builder like that stash the raw rows instead and restore them byte-for-byte with a hash check at both ends.
  • Image alt text is a media-library write, and now says so. Elementor renders an image’s alt from the attachment, so fixing one page changes that image everywhere it appears. The fix checks your permission on the attachment rather than the page, and lists every other page carrying the same image. A fix with an invisible blast radius is not a fix anyone agreed to.
  • SEO title, meta description and the social sharing image now work on every builder. They were gated behind “can this builder replace a page” while writing nothing a builder owns — so on Elementor the og:image finding was reported and its only fixer withheld.
  • 3.2.1 followed the same hour. Opening the writes left five shipped sentences telling Elementor owners their pages could not be fixed — true that morning, false by the time it tagged. All five corrected, and a self-test pin now reads real tool output and fails if any sentence about the active builder still carries a cannot-write claim.
  • Verified: launch sweep on the 3.2.1 tree — Divi 5.11.0 on the rig 2,662/2,663 alone and 2,757/2,758 with eight plugins (the one row each is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden); the six other themes 1,773/1,773 with eight plugins active; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL; the Elementor write suite 94/94 with every check mutation-tested — nine mutations, each turning a named pin red, one of which proved a capability test was passing for the wrong reason. Live after upgrade: 760/760 on this site, 749/749 on a second Divi install, 446/446 on an Elementor install.

3.2.0

elementor writes

  • Checknaut now fixes Elementor pages, not just reads them. Alt text, heading level, link text and any element setting write through Elementor’s own document save, from the audit finding straight to the fix — station_fix_headings, station_fix_alt_text, station_fix_finding and the wp-admin Fix buttons all work on an Elementor site now. Whole-page authoring (create, replace, add or move elements) is still refused and says so by name; that is the next release, not a silent gap.
  • The undo is the point. Elementor keeps the page in a post meta row, and WordPress revisions do not record post meta — so wp_save_post_revision() on an Elementor page returns a real revision id for a revision that contains none of the thing being replaced. It would restore cleanly, report success and leave the edit in place. Every write to a builder like that now stashes the raw rows instead, reports method: meta with no revision id, and restores them byte-for-byte with a hash check at both ends.
  • Image alt text is a media-library write, and now says so. Elementor renders an image’s alt from the attachment, not from the widget, so fixing one page changes that image everywhere it appears. The fix checks your permission on the attachment rather than on the page, and the result lists every other page carrying the same image under also_affects. A fix with an invisible blast radius is not a fix anyone agreed to.
  • SEO title, meta description and the social sharing image work on every builder. station_set_post_fields sat behind a “can this builder replace a page” capability while writing nothing a builder owns. On an Elementor site that meant the og:image finding was reported and its only fixer withheld — the one thing the audit told you to do could not be done, and the refusal blamed the builder for a post-meta write. Page deletion stays gated; that removes content a builder owns.
  • A finding with no automatic fix now says which it is. While nothing on Elementor could be written, every unfixable finding inherited one page-level “nothing can be written here” message. Once the surgical fixes worked, that blanket stopped applying, and an untitled iframe was left marked unfixable with no reason at all — which reads as a defect rather than a boundary.
  • Two defects found by building this, both now pinned: the undo slot was stored unslashed (update_post_meta() unslashes what it is handed, and the Elementor tree is JSON full of escapes, so the stash held a subtly different page — the hash check refused to restore it, which is how it was found); and a write released an undo transaction it had not opened, so the second and third fix in one call each took a fresh step back. The same slash defect was latent in the 2.0.0 content path and is fixed there too.
  • Verified: launch sweep on the 3.2.0 tree — Divi 5.11.0 on the rig 2,662/2,663 alone and 2,756/2,757 with eight plugins (the one row on each is Serving over HTTPS, which a rig on plain HTTP cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,772/1,772 with eight plugins active (Elementor among them, so the new write group runs) and 1,666/1,666 bare on the tool path, 1,667/1,667 bare from wp-admin — the one-row difference is the journal pin that only a non-nested call can assert, by design; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL on every configuration; the Elementor group 93/93 including the new write checks, every one of them mutation-tested (8 mutations, each turning a named pin red) — one of which proved the first capability pin was passing for the wrong reason and had to be rewritten; 16 offline harnesses green.

3.1.6

  • The social image now sticks on Yoast sites. Setting a page’s social sharing image wrote the attachment id to Yoast’s field as an integer; Yoast keeps that field only as a string, so the URL landed and the id was dropped and the image did not appear. It is written as a string now — the type Yoast’s own settings screen saves — and reads back. This page’s og:image was set through the plugin as the proof.
  • Verified: launch sweep on the 3.1.6 tree — Divi 5.11.0 on the rig 2,662/2,663 alone and 2,728/2,729 with eight plugins (the one row is “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and is named, not hidden), both invocation paths; the six other themes 1,744/1,744 with eight plugins and 1,667/1,667 bare; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL. On this site after the upgrade: 760/760 read-side, station_clear_css_cache returned purged, HTTP 200, and og:image now serves on the homepage.

3.1.5

  • The admin says what the marketing says: the AI is optional. First flight opened as if a connected AI assistant were required; it never was. The seven audits, every fixer, the score, the report, scheduling and the self-test are plain PHP and run from wp-admin with no assistant. The checklist now opens with the audit and the connector step is labelled optional.
  • Bracket text inside code is not a shortcode. Site QA read [i], [n] and a quoted [gravityform] out of a code module’s script, CSS and <code> as unregistered shortcodes — findings a visitor could never see. The check now scrubs script, style, code and pre before scanning; a real dead shortcode in prose is still reported.
  • Find-and-replace sees text that spans a tag on Divi pages. It matched a plain-JSON encoding that escapes / and leaves < alone, so a needle like <b>Zero failures</b> reported zero hits on a page that plainly held it. It now matches the exact bytes Divi stores.
  • A stray debug key is gone. Adding a module returned an internal _debug block in every result since 2.0.0; removed.
  • The og:image finding has a fix. The SEO audit could report a page with no social sharing image but nothing could set one. Now it can — into Yoast, Rank Math or SEOPress’s own field, or, with no SEO plugin, the plugin prints og:image itself. Off-site URLs are refused because the audit could never measure them.
  • Verified: launch sweep on the 3.1.5 tree — Divi 5.11.0 on the rig 2,661/2,662 alone and 2,727/2,728 with eight plugins; the six other themes 1,743/1,743 with eight plugins and 1,666/1,666 bare; admin render harness green on all thirteen runs; 14 new checks, each with a recorded mutation.

3.1.4

  • Divi is on the test rig now. Every release’s sweep runs on Divi 5.11.0 as well as the six other themes, alone and with eight plugins active, and the admin screens are rendered there too. The first run found two things, both fixed here.
  • Loop fields are known inside wp-admin. With Divi’s builder framework loaded, the classic dynamic-content registry answers 24 fields and no loop_* at all, so binding loop_post_title from wp-admin was refused as “not a field on this site” while the Visual Builder resolved it. When the first registry carries no loop field on a Divi 5 install, the D5 REST registry is merged in. Over the connector this was never wrong — that path already read the REST registry.
  • A self-test check assumed Divi and Elementor are never installed together. With both active, a Divi-vocabulary tool is stamped adapter divi|elementor; the check expected divi and reported the correct stamp as a failure. It reads the condition now.
  • Verified: the launch sweep on the 3.1.4 tree — on the rig with Divi 5.11.0, 2,646/2,647 alone and 2,712/2,713 with eight plugins, both invocation paths, the one row on each being “Serving over HTTPS”, which a rig on http://127.0.0.1 cannot satisfy and which is named, not hidden; 1,728/1,728 on the six other themes with eight plugins active and 1,652/1,652 bare, both paths; admin render harness green on all thirteen runs; 0 PHP notices from this plugin at E_ALL on every configuration. On this site after the upgrade: station_clear_css_cache returned purged, HTTP 200; 745/745 read-side.

3.1.3

the 3.1 series: 3.1.0 – 3.1.3, same day

  • The score now says how much work it implies. Beside the Checknaut Site Score: Issues — every failure and warning still open across the audits that have run, net of what you waived — and Resolved — fixes this plugin applied plus findings you set aside, shown as “N fixed · M ignored”. Both derive from the same per-audit attribution the score prints under it, so the three numbers cannot disagree; the fixed figure is a durable total that survives the journal’s 100-entry cap; both refresh in place after a fix or an undo. Resolved opens a new History › Fixes & waivers panel: every fix with its undo, every waiver with its restore.
  • One click runs every audit. The Audit tab’s Run full audit button runs every audit the site can be assessed on, in sequence, behind one progress bar naming the audit in flight; audits the site cannot assess are listed as skipped with the reason, never counted as clean; close the tab and it offers Continue; at the end, one line with each audit’s failures and warnings and the score. Over the connector: station_audit_site group “all” (or an array of groups), resumable by calling again until status is finished.
  • A stale run no longer inflates the score. A stored run of an audit the active builder can no longer be assessed on — Design Guardian after a site moved to Elementor — scored 100 and the coverage line read “from all 6 audits” while one audit had never run. Outside coverage now means outside the number.
  • The admin screens are tested. A new harness renders the Overview, Audit, History and Connection screens on every test configuration and asserts what must and must not appear for what the builder can do — the class of defect 3.0.2 fixed by hand. 3.1.1–3.1.3 are what that process found on the way: the Divi skill text had not gained the whole-site sentence (a self-test pin on a live Divi site caught it), the self-test now records the safety switches it found at run start, and the Fixes & waivers header says standing · listed · undone instead of one number beside a list of a different length.
  • Verified: self-test 1,700/1,700 from both invocation paths on a test install running Elementor 4.2.4 Free and 1,651/1,651 with Elementor deactivated, 0 PHP notices at E_ALL; 44 new checks with a recorded mutation each; the launch sweep on the 3.1.3 tree 1,727/1,727 on six themes with eight plugins active and 1,651/1,651 bare, both paths, admin render harness green on all ten. On this site after the upgrade: station_clear_css_cache returned purged, HTTP 200; 745/745 read-side. On this product’s Elementor Pro site: group “all” finished in 6 calls, Design Guardian skipped by name, and the same run from the button. On a live Divi 5.11 site upgraded from 2.3.11: wp-admin self-test 2,476/2,476; a Site Health fix moved the fixed counter 10 → 11 in place and its undo moved it back; a waiver moved Issues 27 → 26 and Resolved 10 → 11.

3.1.2

skill text

  • The Divi skill’s workflow section now carries the whole-site audit. 3.1.1 added the sentence to the text served on other builders; the Divi text is a separate body, and the live self-test on a Divi 5.11 site still reported the pin red (2,475/2,476 from wp-admin). Divi’s workflow section now has a WHOLE-SITE AUDIT paragraph, and the pin accepts the group=“all” spelling that text uses.
  • Verified: self-test 1,700/1,700 from both invocation paths on a test install running Elementor 4.2.4 Free and 1,651/1,651 with Elementor deactivated, 0 PHP notices at E_ALL; 16 offline harnesses green.

3.1.1

skill text

  • The Divi skill teaches the whole-site audit too. 3.1.0’s self-test pin — the tool description and the skill both teach group all — failed on a live Divi 5.11 site (2,559/2,560): the skill text served on Divi is assembled separately from the one served elsewhere and had not gained the sentence. It has now.
  • The self-test records the safety switches it found. Twice — this site after 3.0.0, and another after 3.1.0 — the first scope=all run after an upgrade on a Divi site failed the two cache-clear checks with “READ-ONLY mode” and passed on every re-run, and neither run recorded which switch was on. The Runtime context and Edge cache groups now print the raw switch state as they start, so the next occurrence names its cause instead of being recorded as not understood.
  • The Elementor adapter no longer promises “Writes arrive in 3.0.1” in its limits line; it says page writes are not yet available on this builder and that every finding carries the element id.
  • Verified: self-test 1,700/1,700 from both invocation paths on a test install running Elementor 4.2.4 Free and 1,651/1,651 with Elementor deactivated, 0 PHP notices at E_ALL; 16 offline harnesses green.

3.1.0

score counters

  • The score now says how much work it implies. Beside the Site Score, two counters: Issues — every failure and warning still open across the audits that have run, net of what you waived — and Resolved — fixes this plugin applied plus findings you set aside, shown as “N fixed · M ignored”. Both derive from the same per-audit attribution the score prints under it, so the three numbers cannot disagree; the fixed figure is a durable total that survives the journal’s 100-entry cap; both refresh in place after a fix or an undo. Before any audit has run they say so instead of printing 0. Resolved opens a new History › Fixes & waivers panel: every fix on the site with its undo, every waiver with its restore.
  • One click runs every audit. The Audit tab has a Run full audit button: every audit this site can be assessed on, in sequence, behind one progress bar naming the audit in flight; audits the site cannot assess are listed as skipped with the reason, never counted as clean; close the tab and it offers Continue; when it finishes, one line with each audit’s failures and warnings and the score. The same over the connector: station_audit_site group all (or an array of groups), resumable by calling again until status is finished.
  • A stale run no longer inflates the score. A stored run of an audit the active builder can no longer be assessed on — Design Guardian after a site moved to Elementor — was still counted: it scored 100 and the coverage line read “from all 6 audits” while one audit had never run. Outside coverage now means outside the number.
  • The admin screens are tested. A new harness renders the Overview, Audit, History and Connection screens on every rig configuration and asserts what must and must not appear for what the builder can do — the class of defect 3.0.2 fixed by hand.
  • Verified: self-test 1,699/1,699 from both invocation paths on a test install running Elementor 4.2.4 Free and 1,650/1,650 with Elementor deactivated, 0 PHP notices at E_ALL; 44 new checks across the two new groups, each with a recorded mutation that turns it red; the admin render harness green on both configurations; 16 offline harnesses green.

3.0.2

  • The admin screens stop describing a read-only builder as a writable one. On a site whose pages are read but not yet written (Elementor, until its writers land), the Detected builder card and preflight row printed the version twice (“Elementor 4.2.4 + Pro 4.2.2 4.2.4”), preflight warned that the site’s own builder was a second builder active alongside itself, and the fix text said everything authored with it “is read and written directly”. The version prints once, the adapter is never its own second builder, and the sentence says what is true: read from storage, writers in a later release. Found on this product’s own Elementor Pro site; 3.0.1 was never released to customers.
  • The Overview tiles and the onboarding checklist offer only what the site can complete. On a read-only builder the Fix tile reads “Fixes name the element” and says one-click page fixes arrive in the next release while site-level fixes (SEO meta, legal pages) work now; the Patterns & style guide tile is withheld, since its tools are; Beyond pages lists blog posts only; and the checklist never asks such a site to pick a free page or fix a cause everywhere — the surviving step is taking a finding to its element. Divi sites are unchanged. Every new string ships in all eight catalogues.
  • Verified: self-test 1,651/1,651 from both invocation paths on the Elementor 4.2.4 Free test install and 1,602/1,602 with Elementor deactivated, 0 PHP notices at E_ALL; the Overview rendered on that install contains none of the old wording; 15 offline harnesses green on macOS and Linux and in CI on PHP 8.0, 8.4 and 8.5; release zip byte-identical on every machine that built it. On this site after the upgrade: station_clear_css_cache returned purged, HTTP 200; 700/700 read-side. On this product’s Elementor Pro site after the upgrade: the Overview shows none of the old wording, 388/388 read-side, and the Elementor group 64/64 with writes.

3.0.1

  • On a site with Elementor Pro, every Pro widget was reported as a promotion stub and read as opaque. 3.0.0’s stub detector had a third rule — “has get_help_url() and show_in_panel() and sits in the pro-elements category” — that every Elementor widget satisfies, so the first Pro install’s 39 real Pro widgets came back pro-missing with their content unread. Found the day 3.0.0 shipped, on this product’s own Elementor Pro site; 3.0.0 was never released to customers. A stub is a promotion class, or a pro-elements widget on a site without Pro — nothing else, and that rule is pinned at source.
  • Translucent widget fills are composited over the section behind them before contrast is rated. A ghost button — white text on rgba(255,255,255,.06) inside a #0C0D0E section — rated 1.00:1, a hard fail on a pair that renders at 17:1; the same maths the Divi walker has used since 0.45.0 now runs for Elementor pairs, and a translucent fill over a background image is reported undecidable rather than rated against nothing. Widgets whose colours live in their own markup (html, code) or that print no text no longer draw an “unrated” row each.
  • Two self-test checks that failed only on a chunked live run (found on this site with writes enabled): the generic-adapter group aborted when the shared QA draft had been deleted by an earlier group — a missing fixture is a visible skip now; and the site-history cleanup check counted the whole audit table, so a real event landing mid-run blamed the cleanup — it counts its own marker rows.
  • Verified: self-test 1,650/1,650 from both invocation paths on the Elementor 4.2.4 Free test install, 0 PHP notices at E_ALL; offline Elementor harness 64 checks; 15 offline harnesses green on macOS and Linux and in CI on PHP 8.0, 8.4 and 8.5; release zip byte-identical on every machine that built it. On this site after the upgrade: station_clear_css_cache returned purged, HTTP 200; 700/700 read-side; the generic-adapter group 42/42 with writes. On this product’s Elementor Pro site after the upgrade: the Elementor group 63/64 with writes (the one miss is a check that demands an opaque element on a page that, with Pro, has none — fixed for the next release), the home page’s 21 text/background pairs all rated, 0 failures, 0 unresolved, and the whole-site accessibility audit reporting every finding by element id.

3.0.0

  • Elementor sites are read from storage, element by element. A new Elementor adapter reads the stored element tree — sections, columns, containers, every classic widget and the atomic V4 elements — so station_get_page, station_get_tree and station_get_node return the document with element ids, every accessibility, SEO and performance finding names the element it belongs to, and the design system is the active kit’s global colours and fonts with paste-ready globals/colors?id= tokens. Same tool names as Divi, Elementor’s vocabulary. Until now an Elementor page was audited from its rendered HTML only.
  • Every element is accounted for; nothing is called clean that was not seen. Each element is modelled or listed as opaque with a reason (a Pro widget on Free, a run-time widget, an unregistered third-party widget, a malformed one); a page with anything opaque withholds “none found” unless the rendered page resolves it; a document that does not decode reads as unread and falls back to the render; an empty Elementor document reads the classic content Elementor actually shows. Colour contrast is rated from storage — kit globals and V4 design variables resolved, text on a background image reported as undecidable. Reading a page writes nothing: Elementor’s CSS-file and element-cache writes are held off for the duration of an audit.
  • Writes to Elementor pages are not in 3.0.0. Every fixer refuses by name on an Elementor page and points at the element id; fixes (3.0.1), building and design-system writes (3.0.2) and Pro coverage (3.0.3) follow. On Elementor Free, 82 of the 149 registered widgets are Pro promotion stubs and are reported as such.
  • From a bug report on a live Divi 5.12 site, six items. Presets refuse innerContent at any path (dvc_preset_content) — a preset carrying an icon glyph rendered no glyph at all, silently; a column sized by its preset is no longer warned about or overwritten with an inline 8_24; station_render_probe answers status: unprobeable with the reason instead of calling every node on a draft silent; a pre-escaped &amp; in a heading is warned about with its node id; the preset docs no longer read as “no presets for divi/group”; the Connection tab says to pick “No sign-in” in the connector dialog.
  • Verified: self-test 1,650/1,650 from both invocation paths on a test install running Elementor 4.2.4 Free (1,601/1,601 with Elementor deactivated), 0 PHP notices at E_ALL; the launch sweep on the 3.0.0 tree: 1,677/1,677 on six themes with eight plugins active and 1,601/1,601 bare, from both paths, 0 Checknaut diagnostics on all ten configurations; 15 offline harnesses green on macOS and Linux and in CI on PHP 8.0, 8.4 and 8.5; 22 of 22 reverse mutations caught; release zip byte-identical on every machine that built it. On this site after the upgrade: station_clear_css_cache returned purged, HTTP 200, and the full 2,593-check suite ran with writes — 2,591 pass; the two failures are in the suite’s own fixture handling on a chunked live run (a deleted draft read by a later group, a whole-table row count) and are fixed in 3.0.1.

2.3.11

  • station_clear_css_cache now reports the Hostinger edge purge it actually made. The tool purges the edge synchronously; in 2.3.10 the generic write hook then overwrote its result with queued and scheduled a second purge at shutdown. The hook now leaves a result that already carries edge_cache alone. Found on this site the day 2.3.10 shipped — that tool is Divi-only, so the Gutenberg test rig could not reach it. After upgrading, the same call on this site returned purged, HTTP 200.
  • New self-test check in the Edge cache group asserts the guard from source; removing the guard fails the check (mutation-tested). Edge cache group 28/28, 0 diagnostics.
  • Verified: 14 offline harnesses green on macOS and Linux and in CI on PHP 8.0, 8.4 and 8.5; the release zip is byte-identical on every machine that built it; 692/692 read-side on this site after the upgrade.

2.3.10

  • Hostinger’s edge cache is purged after your AI assistant writes. Hostinger’s CDN sits in front of WordPress and no plugin hook reaches it — every cache this plugin already flushed was clean while visitors kept getting the old page. On this site, every edit made for 2.3.8 was invisible to the public for a whole release cycle. Now, on a Hostinger site with an API token pasted on the Connection tab (or DVC_HOSTINGER_TOKEN in wp-config.php), any request that writes purges the edge cache exactly once, at the end of the request, after the assistant’s response has gone out. Forty pages fixed in one call is one purge; reads never purge; nothing is queued or retried. Verified from outside on this site: the edge served a fresh page about eight seconds after a write through the connector.
  • It says what it did, not what it hoped. A write result carries edge_cache: queued, unconfigured (with the settings link), or not_applicable — never “purged” for a purge that has not happened. The outcome, with Hostinger’s own message on failure, lands on the request’s journal rows and on a new Diagnostics row that turns red rather than green when the host refuses. The very first live attempt did exactly that: from inside their own server Hostinger’s API answered too slowly and the row said so. The plugin now hands the response to the client first and waits up to fifteen seconds.
  • station_clear_css_cache purges the edge cache synchronously on Divi sites and reports the real outcome. The token is a credential: password field, last four characters shown after saving, never exported, never in a tool result, journal row, report or self-test line. Twenty-seven new self-test checks in an adapter-neutral group use a recording transport and make no network calls; eight of ten reverse mutations were caught and the two that were not are documented as non-semantic.
  • First feature built through the project’s Spec Kit workflow — constitution, spec, plan, tasks, analysis and the live verification record are in the repository under specs/001-hostinger-cdn-purge/.
  • Verified: self-test 1,596/1,596 from both invocation paths on a Gutenberg test install (was 1,569 — the 27 new checks), 0 PHP notices at E_ALL on PHP 8.4; 14 offline harnesses green locally on 8.4 and 8.5 and in CI on 8.0, 8.4 and 8.5; 692/692 read-side on this site after the upgrade.

2.3.9

  • A missing key no longer sends your AI assistant hunting for an OAuth server that does not exist. In key mode the connector answered an unauthenticated request with a bare 401 and no WWW-Authenticate header. MCP clients read that as “this server wants OAuth”, fetch the discovery documents key mode deliberately does not publish, and report “connection stopped working — reconnect”, forever. The real message — add ?key= to the URL — never reached anyone. Key mode now refuses with 403, which is not an auth challenge. OAuth mode is unchanged. Verified over HTTP on a test install: no key 403, wrong key 403, right key 200; switch to OAuth, 401 with the header and the metadata document at 200; switch back, 404.
  • The connector URL has one home: the Connection tab. Overview showed the keyless address next to a button that copied the keyed one, and a reader who typed what they saw sent the exact request above. Both Overview renderings are gone, the keyed URL is no longer computed on that screen, and the pre-connect hero links to the Connection tab, where what is shown and what is copied are the same string.
  • “Adopt your design system” is step two of First flight on Pro, flagged Affects every build. Every colour, type size and spacing the assistant resolves goes to the adopted system first; without one it works from whatever the page it is looking at uses. The step lands right after connecting, before the audit, and turns green only when a system is actually adopted. Free does not see a step it cannot finish.
  • The connector-auth security checks now run on every site. The self-test’s pins on the static-key guard, the auth throttle and the 403 rule above lived in a group that only runs on Divi; they had never executed on a Gutenberg install. Moved to an adapter-neutral group. Found because the new pin was checked for having run, not for the suite being green.
  • The release itself is now a reproducible artifact. The same tree hashes identically on a laptop, in a container and on GitHub Actions; a version bump touches eleven files through one script; the release script refuses a dirty tree, a missing changelog entry or a re-used tag; and CI uploads the zip it built to Freemius as pending on the tag. Nothing reaches customers until a human releases it. The first tagged run caught a gate that compared file timestamps a checkout does not preserve — green on two PHP versions, red on the third, same bytes — and that gate now compares catalogue contents instead.
  • Verified: self-test 1,569/1,569 from wp-admin on a Gutenberg test install; 692/692 read-side on this site after the upgrade; 0 PHP notices at E_ALL on PHP 8.4; 13 offline harnesses green on PHP 8.4 and 8.5 locally and on 8.0, 8.4 and 8.5 in CI.

2.3.8

  • Every fix in 2.3.7 was applied to the instance in front of me rather than to the class, and the review of that release found all three. 2.3.7 removed a false red from a check; the same flaw was still sitting in the other half of the same assertion. It fixed a warning missing from one branch of the skill; the warning was missing from a second branch too. It added a harness to pin the work; the harness pinned the labels and not the conditions. Turning the same adversarial review on the release that had just been called clean is what found them, and that is now how a release ends rather than how it begins.
  • The skill told the assistant that a Pro tool could not be refused. 2.3.7 added a line saying station_create_gutenberg_post is not refused on a site whose pages another builder owns. Both block page-writing tools are Pro and the tier gate refuses them on every Free site. And the skill preamble is what the connector returns as its instructions, so the false sentence was in the handshake — the first text the model reads. An agent told a refusal cannot happen has no way to interpret the refusal it gets. It now claims nothing about acceptance, and it names post_type, because the tool defaults to a post and a sentence about pages invites the wrong call.
  • The Classic Editor warning still never reached a Divi site. The skill returns early for Divi, above both places the warning was emitted, so on Divi with Classic Editor active the skill said nothing and the check asserting otherwise went red on a correct build — the mirror image of the false red 2.3.7 removed, in the same assertion, left standing. One helper now carries it into every branch that can lead to block content, Divi included.
  • A pin that could not fail on the install it ran on. The journal is a 200-row ring that slices back to 200, so once full a new row leaves the count unchanged. The read-side check compared counts, and 2.3.7 moved it onto the wp-admin path — the long-lived install where the ring is always full. It passed on a build that journaled every read. Both pins now compare the ring head. Neither branch knew whether journaling was switched on at all, either: with logging off, one went green on a build that double-logs and the other went red on a correct one.
  • Setting a featured image left every cache holding the old page. It wrote post meta, verified the re-read, reported written and verified, and flushed nothing — the one write in the plugin that did not. On a site with a page cache the visitor kept getting the old image and the old social card while the tool result and the SEO audit both read the new value out of the database and agreed with each other. Two readers of the database agreeing is not evidence about what is being served. Found by setting this product's own social card and watching the live page not change.
  • And the half of that this plugin does not control is now stated. Yoast and All in One SEO cache a page's og:image and refresh it when the post is saved, not when the featured image changes. So after a write the database is right, this plugin's audit reads the database and reports the new card, and the SEO plugin keeps emitting the old one until something saves the post. The result now says so, on those two plugins only.
  • Fixed in the release tooling, same class: the harness runner could report all green about a tree it was not testing — a shared scratch directory with no lock, and a copy that normalised timestamps and silently disarmed the pin that a compiled translation is not older than its source. It would also have deleted the plugin tree outright if pointed at its own parent.
  • Corrections to this site, found by pointing the same review at the marketing copy. A claim that a seventh theme had been dropped because it would not stay active was a fabrication — the run record shows it active and passing; it had simply not been carried into the current matrix. A claim of four SEO plugins was three. Claims that each configuration was a clean install described one long-lived install reconfigured between runs. Two diagnostic counts were modes reported as results. Two tool counts came from a superseded run and have been re-measured. Three assertions about what a screenshot showed had no record behind them and are gone.

2.3.7

  • 1,185 checks per configuration had never been executed, and three of them were wrong. The verification for 2.3.6 ran the self-test on eight configurations and reported 382 of 382 green. scope=all — the write path — quietly downgrades to the read-only pass on a Free-tier site, the test rig was Free tier, and nobody noticed that the number being celebrated was a quarter of the suite. Forcing Pro on the rig turned 382 checks into 1,567 and produced three failures on a build that had been called ready. The lesson is not any one of them: a green suite is evidence only about the checks that ran, and a suite that can silently run a subset while reporting a total will eventually lie to you.
  • A check reported red where the feature was working — and would have reported green on the build that broke it. The journal's contract is that the outermost tool call owns the log row and a nested call adds none; that is what stops one request becoming five rows. The check asserting that a write is journaled ran inside another tool call on every connector run, so no row appeared and it failed on a healthy install. Worse the other way: on a build that had lost that rule and logged every hop, the row would have appeared and the check would have passed. It now reads how it was invoked and asserts the matching half. Second time a check has assumed the suite runs from wp-admin; first time one would have passed because the product was broken.
  • A check counted two tool descriptions on a site that correctly offers one. With Classic Editor beside Elementor — an ordinary pairing — one of the two block page-writing tools is withheld for want of the write capability, exactly as availability gating should. The check read that as a defect. It now asserts that every such tool the site actually offers carries the warning, and names any that does not.
  • And the real one, which the count above was hiding. The Classic Editor warning — the owner will see block delimiter comments, and re-saving from the Visual tab strips them — reached the tool descriptions and the skill's page-building section. That section is written only where the block editor owns the site. On a site whose pages Elementor owns, the skill says page building belongs to Elementor, which is true of its pages and not of a new one: station_create_gutenberg_post survives the gate there and was verified creating real block content, delimiters and all. So the one tool that could still produce them did so while the document the assistant reads first said nothing. A warning that lives only where you look second is not a warning. Each of these was fixed in the branch in front of me rather than in the class — see 2.3.8.

2.3.6

  • Verification release. No behaviour change from 2.3.5 — what changed is how much of it had been run. Eight configurations built from a clean WordPress install and driven end to end: six themes (Twenty Twenty-One, Twenty Twenty-Three, Twenty Twenty-Four, Twenty Twenty-Five, Hello Elementor, OceanWP), each with eight current plugins live at the same time — Elementor, Beaver Builder Lite, SiteOrigin Page Builder, Gravity Forms, Contact Form 7, Akismet, Classic Editor, Performance Lab — and two more with no page builder at all.
  • Corrected in 2.3.8: a seventh theme was in an earlier run, not dropped from this one. Neve ran against an earlier build and passed; it was simply not carried into this release matrix, which runs six. This entry originally said Neve had been dropped because it would not stay active on the rig. That was written from a stale working note rather than from the run record, and the run record says the opposite. A fabricated negative result is worse than an overstated positive one, because it is offered as evidence of rigour — and it sat in the part of this site whose whole purpose is honesty about limits. Found by turning this project own adversarial review on its own marketing copy.
  • An Elementor adapter that writes to Elementor pages was built for this release and deliberately held back. An adversarial review found defects serious enough to fail the standard the rest of this plugin is held to — most of all that a page built from widgets it did not recognise would have audited as clean rather than as unread. An auditor that reports a clean page it could not read is worse than one that admits it cannot write, so it waits.

2.3.5

  • A site-level SEO check reported “nothing found” on databases that could not run it. A database returns an empty result for a query that FAILED exactly as it does for one that matched nothing, and both site-wide checks ended by treating empty as clean. The keyword-cannibalisation query uses GROUP_CONCAT … SEPARATOR and SUBSTRING_INDEX, which SQLite does not have — so on any SQLite-backed WordPress that check had been silently dead for its whole life while reporting a clean result. Found by running the suite against this project's own test rig.
  • Not SQLite-specific: a permission, a collation, or another plugin filtering the query took the same silent path. Both checks now distinguish a refused query from an empty one and report it as unchecked, naming what the database said. The rule this product is built on is that an audit must never report clean about something it could not see, and it was being broken by the most ordinary line of code in the file.

2.3.4

  • The SEO audit now checks the social sharing card. It did not, and the omission was found the only way it could be: by reading the head of this very marketing site, which the audit had just reported as zero failures and zero warnings. The page declared twitter:card: summary_large_image and emitted no og:image at all, so every time anyone pasted the link into Slack, LinkedIn, X or iMessage the preview was an empty box.
  • A social card is the first impression of every shared link and has no fallback the way a title or description does, so silence about it was the largest blind spot left in the engine. The check resolves the image the way the SEO plugins themselves do — the page's own social image, then its featured image, then the site-wide default — so it never reports a defect the visitor does not have. It warns when there is none and when the image is below the 200px platforms accept, notes when the shape is far from 1.91:1 (the crop is centred, so a logo near an edge is what gets cut), and reports an off-site image as UNMEASURED rather than as passing, because this engine does not fetch remote files mid-audit and “could not look” must never render as a green tick.

2.3.3

  • The assistant roster could lose a client entirely, and the client most likely to be lost was the one you had just connected. 2.3.2 kept the whole roster in one record and argued the read-modify-write was safe because an undercount of one is invisible, and because “this client exists” would survive since both writers assert it. Both writers only assert a client they already know about. Two assistants calling in the same instant each read the same prior state, and the second write replaces the first whole — so the row that disappears is the one nobody has seen before, which is the only row anyone is looking for. Each client now has its own record.
  • Fixed a self-test check that failed because the feature worked. 2.3.2 added a check asserting that outside an MCP request no assistant is named — the rule that stops a site owner's own clicks in wp-admin being filed under an assistant's name. It was written assuming the self-test runs from wp-admin. It also runs over the connector, which is how it is invoked most of the time — so on its first live run a real client had called, the name was correctly present, and the check reported red. It now tests both directions and picks the right one from how the request actually arrived. A red result that means “working” is worse than no result: it teaches people to skim past red, which is the one thing a test suite cannot afford.

2.3.2

  • An assistant that only READS now leaves a trace. Every log in the plugin recorded writes and nothing else — on purpose, because a row per read buries the timeline — so a newly connected assistant reading its way around a site produced no server-side evidence at all, and the owner's only proof the connector worked was the assistant saying so. Activity now opens with a roster of every assistant that has called in: when it was first and last seen, how many calls it made, how many were reads, how many were writes, how many were refused, and which protocol era it speaks. station_get_history returns the same roster, and station_get_site_context carries a short form of it, so an assistant can also see whether it is alone on the site.
  • The activity log names WHICH assistant made each call. It recorded the WordPress account the call authenticated as, which on an MCP call is whoever owns the token — so every row read as though the site owner had done it personally. A row with no client is a human: wp-admin, WP-CLI or the self-test, never a borrowed name.
  • Corrects 2.3.0. That release said the activity log names the client behind each write. It named it only for clients that send clientInfo on every request, which is the 2026-07-28 era alone. A handshake-era client — which today is most of them — states its name once, in initialize, and that statement was read and thrown away; every such connection was logged under a generic label for its entire life. The name now comes from the request when it is there, from the OAuth client's own registration when it is not, and otherwise from the handshake, remembered against the credential that gave it. The feature whose whole purpose is telling two assistants apart could not name either.
  • The tool count explains itself instead of looking like a fault. station_get_site_context reported how many tools a site exposes beside a note saying any disagreement with your client's list meant a stale client. Adapter gating has made that false since 2.0.0: a Divi site with no WooCommerce correctly serves 98 of the 100 tools the plugin defines. Reported alone that number reads as either a stale client or a short install, and on most sites it is neither — so it now ships with tools_defined, and tools_withheld naming each absent tool and why. The three always add up.
  • The Activity screen stopped telling owners nothing had happened. Its empty state promised that the first request an assistant made would land there. Reads do not land there, so a site a second assistant had called dozens of times said “No tool calls yet”.
  • Fixed in the test suite rather than the plugin, and worth recording: a stub for WordPress's tag stripper used PHP's linear strip_tags() where the real function is quadratic. That removed the exact phenomenon two denial-of-service pins exist to measure — reintroducing the bug they guard timed at 0.000s against the stub and the suite reported all green. Against the real implementation the same bug costs 22 seconds. A stub may be smaller than the real thing; it must not be faster in the dimension under test.

2.3.1

security

  • Fixed a stored cross-site-scripting hole in structured data. The JSON-LD printer encoded with a flag that stopped </script> being escaped, so a value written through station_set_structured_data could close the script element and run in every visitor's browser on every page view. Reachable by any connector that can edit one post.
  • Site-wide design-system writes now require the edit_theme_options capability. Ten tools could repaint every page, or delete a design token live pages reference, for any connector authorised by an Editor — a role that cannot change site appearance anywhere else in WordPress. “Protect the design system” asked whether the brand may be edited; it never asked whether that user may edit it.
  • Changing the safety switches now requires manage_options. The tighten-only rule stopped the assistant loosening them; it did not stop an editor-level connector locking the owner out of their own automation.
  • The link checker no longer follows links to private addresses. station_check_links fetches every link on a page using HTTP functions that skipped WordPress's URL validation — a working scanner for cloud metadata endpoints and internal hosts, reachable even by a read-only connector.
  • Restoring a design-system snapshot now takes a snapshot first. This was the only write in the plugin with no way back: restoring an old snapshot destroyed everything built since, and nothing held the state it replaced. The restore now returns the id of the snapshot that undoes it.
  • A page write is refused when its step back could not be saved. The undo slot reported success whether or not the write landed — on exactly the sites it exists for, those with WordPress revisions switched off.
  • Replacing a menu writes the new items before removing the old ones, and trashes them rather than deleting permanently. A failure part-way used to leave the site with a partial menu and the original permanently gone.
  • Draft-only is honoured by station_duplicate_page and station_import_page. Both accepted status: “publish” directly, so one call could put a live page on a site whose owner had switched publishing off.
  • Deleting a Theme Builder template now trashes it. It carries the display conditions — which pages the chrome applies to — and force-deleting it destroyed the one part nobody can reconstruct from memory.
  • station_search_replace preserves a page whose title it rewrites. A post matching only in its title was rewritten with no step back, under a note claiming a revision held it.

2.3.0

  • Works with any MCP client, not just one. The connector now answers both protocol eras on the same URL: clients that open with an initialize handshake (MCP 2025-06-18), and clients that skip it and declare their version on every request (MCP 2026-07-28). A modern client against a handshake-only server fails outright with no way to recover, so this is what makes ChatGPT and other current clients able to reach the site at all.
  • Added server/discover, which the 2026-07-28 spec requires, so a client can learn the versions, capabilities and identity this install serves in one call.
  • An unsupported protocol version is refused with error -32022 naming the versions the install does serve, so the client can pick one and retry instead of giving up.
  • The activity log names the client behind each write — read from the clientInfo modern clients send. Self-reported and used for display only, never for an access decision, and stripped of markup before it is rendered. Corrected in 2.3.2: this worked only for clients that send clientInfo on every request, so handshake-era clients were logged under a generic label.
  • The interface no longer names one assistant: 83 strings across all nine languages, plus the safety-switch labels on Settings. The Connection tab still names the actual clients, because that is where naming them is the instruction.
  • Fixed: the OAuth dynamic-registration fallback recorded every client that omitted a name as one particular vendor.

2.2.2

  • ROLLBACK: one tool call is now one step back, everywhere. Three fixers write one item at a time — link text, alt text, heading levels — and every per-item write took its own undo point, each overwriting the last. Fixing four missing alts on a site with WordPress revisions disabled left the undo slot holding the page as it stood after image THREE: "undo" restored a page that had never existed. On a site with WP_POST_REVISIONS capped at 1 it was worse — the first write created the revision the tool then named in its result, and the later writes pruned it away, so the operator was handed a revision id that station_restore_revision answers with "is not a revision." All three now take the step back once and report it truthfully; the notes no longer promise "a revision records the change" on a site that has no revisions.
  • ROLLBACK: the site-wide image reference rewrite leaves a step back on every post it touches. Compressing an image on one page rewrites every other post that uses the same file — pages the operator never named — and not one of them got a revision or an undo slot. The only way back was the fix journal, which rotates.
  • ROLLBACK: restoring a snapshot can no longer report a store it did not write. The variables verdict defaulted to "restored" before either write branch ran, so every path that wrote nothing still read as a clean rollback — including the common case of a snapshot taken on a Divi build whose variables option was empty. It now proves itself by re-read like every other store, or says plainly that nothing was written. Legacy snapshots also restore their default colour slots, which they had been silently dropping.
  • SAFETY: the wp-admin Snapshots screen goes through the active builder adapter, like the tool surface always has. On a block-theme site with no Divi, "Take snapshot" captured nothing and reported success, and "Restore" then wrote nothing and reported a clean rollback — a rollback button that lies is worse than no rollback button.
  • SAFETY: station_build_style_guide will not overwrite a page it did not write. "Refresh in place" matched on title equality alone, with the overwrite confirmation hardcoded to true, so a title argument was enough to replace a live page's entire body past the guard the owner had switched on. It now marks the pages it owns; adopting any other page requires confirm=true and names the id first.
  • SAFETY: content fixers no longer dissolve a Divi Library reference. Seven read paths used the bare WordPress block parser, which dereferences divi/global-layout on Divi 5.11.0 — the fix landed and the global section quietly stopped being global, invisibly to the strip detector because both sides of the comparison expanded the same way.
  • SAFETY: station_import_page names the design system it already created when the page write then fails. apply_design() runs first by necessity, so an unconfirmed import onto a published page left new colours, variables and presets on the site while the refusal said "Nothing was changed."
  • HONESTY: the Free tier's refusal message stopped selling what Free already includes. It told the agent the site-wide audit, the score and the client report were Pro; station_audit_site and station_get_score are free on purpose, and only fixing past the bound page is paid.
  • HONESTY: four Pro tool descriptions printed the "[Checknaut Pro feature.]" notice twice, because they hardcoded the suffix the manifest already appends.
  • HONESTY: readme.txt said 44 free tools; there are 51. The plugin header claimed exactly one REST namespace; 2.2.0 deliberately kept the pre-rename one alive and the header now says so. Upgrade Notices existed only up to 1.9.5, so the breaking 2.2.0 rename surfaced no warning in the update UI at all.
  • FIXES: closing comments counts the pages that actually saved, not the candidates; undoing a trash-content fix whose items were permanently deleted reports the failure instead of marking itself done; a block-editor write reports zero changes when nothing changed, instead of always at least one.
  • DESIGN: the admin skin's print stylesheet turned the ground white and left every text token at its light-on-dark value — 1.12:1, an effectively blank page. The FAIL status dot was brand cyan at 2.2:1, under the non-text contrast floor and the wrong colour for the meaning. The step numerals were drawn in a gradient whose lower stop hit 1.6:1 on the card, and were invisible entirely in the light theme. Six more pairs across the admin and the client report were brought over AA.
  • DESIGN: the last of the pre-rebrand violet is gone. It survived inside a prefers-contrast branch and a no-backdrop-filter fallback, where it painted every panel in the app purple for anyone whose browser or OS took either path.
  • I18N: the translation template was a release behind — 227 of 907 strings were missing from it and untranslatable in all eight locales. Regenerated from the source. The German catalogue shipped the old product name in the main navigation's screen-reader label and in an audit-log entry; a botched rename left one msgid matching no source string in every locale. readme.txt now states the real translation coverage rather than implying it is complete.
  • FIXED IN 2.2.2 AFTER FIRST LIVE RUN: three of the defects above were introduced by 2.2.2 itself and only appeared on a real install, because the standalone harnesses cannot run the self-test groups that need WordPress loaded. A new pin called a private method and took its whole group down with a fatal; emptying the scheduled-digest default list to force it through a derivation told four other pins the weekly digest covered no groups at all; and the tagline pin compared the tool's reported previous value against a raw option read, which are not the same string on a site whose tagline contains an ampersand — WordPress escapes blogname and blogdescription inside sanitize_option(). station_update_site_setup now reports and accepts those two options as the plain text a human typed, in both directions, so writing back the value it just handed you is a true undo.
  • A new harness resolves every Class::method() call in the plugin against that method's declared visibility, so a cross-class call to a private method is caught from the source instead of on a customer's site. It found the fatal above; the first version of it reported ALL GREEN while parsing nothing, because "{$var}" string interpolation desynchronised its brace depth, so it now fails loudly if it cannot parse a plausible number of declarations.
  • BRANDING, CORRECTED AFTER SEEING IT ON A REAL ADMIN SCREEN: the menu icon drew the plate and knocked the tower OUT of it, which in one ink inverts — WordPress rendered a solid pale square with a dark slot down the middle, in a sidebar where every other item is a thin grey glyph. It draws the tower now and no plate at all. The dashboard header carried the mark on its Deep Space plate, which is very nearly the colour of the panel behind it, so the mark was a faint scratch rather than an object; it uses the Signal Cyan tile there, which is also what the browser tab shows, so the two carry one identity.
  • Two self-test pins failed on their own documentation: the check for "no pre-rebrand violet paints anything" matched the comments written to explain the fix. It strips CSS comments before looking now, because the question is what PAINTS, and a comment paints nothing. Its needles are also spelled out one per assertion rather than looped, so the static needle harness can see them.
  • The packager refuses to build if the translation catalogues are not stamped with the release version. The .pot was regenerated during 2.2.2 and stamped before the version bump, and a customer install is a silly place to discover a version string.
  • The self-test gained a pin for every defect above.

2.2.1

  • ROLLBACK: restoring a revision now restores everything the revision holds — content, title and excerpt — the way WordPress's own revision restore does. It restored content alone, so renaming a page and rolling back returned the old body under the NEW title, reported as "Restored and verified" because the only thing verified was the module count. A title-only change was answered with "byte-identical to the current content. Nothing to restore." — a refusal to undo a change this plugin had just made. The dry run now names the title change explicitly. The undo slot still holds content only, so restoring from revision_id 0 deliberately leaves the title alone.
  • ROLLBACK: rolling back is never paywalled, in the one case where it still was. station_list_revisions and station_restore_revision were free but page-scoped, so a trialist or a lapsed customer who had written to forty pages could roll back exactly one of them and got a price for any other. A revision restore is a pure rollback: it can only return state the site already had, and nobody would buy a licence for it. 1.3.8 applied that test to snapshots; this applies it to revisions. Restoring is free everywhere. Creating the mess still is not.
  • ROLLBACK: station_set_post_fields now reports the excerpt and featured image it replaced, under "before", with the call that reverses them — and preserves the outgoing excerpt first, which it had never done on a site with WordPress revisions disabled. The featured image is post meta, so no revision and no undo slot has ever held it; "before" is the only step back it has, and the tool now says so rather than closing with "Written and verified by re-read."
  • ROLLBACK: station_set_color_roles was the last design-system write with no restore point. It now takes a snapshot and returns the binding it replaced. Snapshots now capture colour role bindings too — they did not, so a restore put back every part of the design system except the one this tool writes.
  • ROLLBACK: the preset moduleName repair now takes a snapshot before it writes. It runs unattended on upgrade with its return value discarded, so it was the write most in need of one and the only preset write without it; a failed verification is now logged with the snapshot id instead of vanishing.
  • HSTS is no longer enabled without saying it cannot be undone. It is the one hardening switch with no step back — max-age is cached by every visitor's browser, which then refuses plain http to the site for up to 180 days whatever the server later sends — and the note on that write promised the opposite ("Undo by setting the switch back to false"). Turning it on now requires accept_hsts_is_one_way=true.
  • The security-headers finding can now be cleared by its own fix. The check asked for five headers; the switch that fixes it sends four, and the fifth (HSTS) is a separate switch that answers no check. So on every HTTPS site the fix reported success, the re-check still failed, and a second attempt blamed the host: "something outside this plugin re-enables the behaviour." That message was wrong every time it was shown. The check and the switch are now pinned equal by the self-test, and HSTS has its own finding with no Fix button, on purpose.
  • Structured data: the fabricated-review guard now walks the whole graph. It only checked the top level of each node, so a rating or review claim nested one level down was published live. Fabricated review markup is a straightforward FTC problem and this is the guard that exists to stop it.
  • The design audit no longer reports "clean" on a site it cannot see. Its checks read Divi 5 block attributes, so on a Gutenberg or third-party-builder site it found nothing and said so as a pass. A clean result that is an artefact of not looking is worse than no result; it now declines the group and says why.
  • Exported SEO reports no longer state that this plugin never writes meta titles and descriptions. It does, when you run a fix or set them explicitly — never silently as part of an audit, which is what the sentence was trying to say and did not.
  • BRAND: the plugin wears the Station Plate mark — a rounded plate carrying an uplink chevron over two ticks, drawn on a 32-unit grid so it stays crisp at the 20px the WordPress admin menu renders it at. The old spire-and-orbit mark was hairline-thin and dissolved at small sizes.
  • BRAND: every purple is gone. The exported client report was built on a purple-tinted neutral ramp (ink, borders, page ground) with a purple default badge, and the dashboard hero gradient ran through a purple midpoint; all of them are now blue-tinted at the same lightness, and the admin stylesheet's colour tokens are named for what they are (--signal-*) rather than --violet-*, which they had not been since the palette moved to cyan. The example markup the skill hands to the model no longer teaches a purple fill either.
  • station_search_replace now reports what it actually replaced. It reported the count LEFT AFTER the write, which is wrong for the ordinary "append to this string" shape: the replacement puts the search string back at every site, so a fully successful write reported "0 replaced, 11 remaining". The honest reading of that is "it did not work, run it again" — and a second pass appends a second copy. Found by running this tool on our own site, which is how one link ended up with three stacked colour declarations. The count is now the number of substitutions performed, and when the needle legitimately survives the write the result says so in a sentence.
  • station_restore_revision's own dry-run preview is still free and still writes nothing, and the rescue plan no longer names a tool ("autopilot/station_audit_site") that does not exist.

2.2.0

  • Renamed from Divi Autopilot. 2.1.0 made every audit work on any theme with any builder, and the old name still said one builder. The plugin folder, main file, text domain, admin page slug and REST namespace all move to wp-basestation. Nothing you already have breaks.
  • All 100 tools are renamed station_*, and the pre-2.2.0 divi_* names are still accepted silently, so a saved skill, a site memory or a model context holding the old names keeps working and is journaled under the new name. The manifest lists the canonical 100 only.
  • The pre-2.2.0 REST namespace still serves every route, byte-identically, so a connector added before this release keeps working without being re-added. Undocumented and unadvertised.
  • Settings, tokens, snapshots, history and the licence all carry over. The storage keys do not move, and the licence record is migrated to the new product slug the first time 2.2.0 loads, so no key needs re-entering.
  • Upgrading from 2.1.x: install and activate the renamed plugin while the old one is still active. It stands by with a notice rather than loading twice; deactivate the old plugin and it takes over on the next request. Leave the old folder installed, or delete it knowing that its uninstall routine removes the shared settings.
  • New mark, new admin skin. The spire-and-orbit mark, a steel and cyan palette across the admin, and a WordPress-wide tagline. The dedicated auth header follows the product (X-Checknaut-Key); the two older names are still read.
  • Verified end to end before release: 2,441 checks green through a connector still addressing the pre-rename namespace and tool names, 1,432 green on a Divi-less install, 2,435 green on this site.

2.1.0

  • Site Health — the seventh audit, and the first that reads the install instead of its pages. station_audit_site group="site" (and station_check_site_health, free) works on any theme with any builder: WordPress, PHP and database versions against their support windows; pending updates; inactive and abandoned plugins; the security posture (guessable admin logins, open registration, XML-RPC, user enumeration, login protection, backups, salts, security headers, directory listing, mixed content); database bloat; content hygiene (sample content, soft 404s, robots.txt, empty menu locations, comments); SPF, DMARC, MX and PHP mail(); and WordPress core's own Site Health tests folded in. Every network probe is cached, time-boxed and reported as unchecked by name when it cannot run, and every run lists what each self-fetch actually received.
  • Hardening switches. Seven reversible switches applied on every request — XML-RPC off (endpoint answers 403, method table emptied), hidden version strings, no user enumeration, security headers, HSTS, file editors off, sitemap in robots.txt — through station_set_hardening or station_fix_finding on the matching health finding, each journaled for station_undo_fix. Two content fixes with their own undo: trash the sample post and page, close comments.
  • Document-level accessibility, any theme. From the rendered home page: a missing <html lang>, no skip link, no landmarks, a viewport that blocks pinch-zoom, duplicate ids, positive tabindex, autoplaying sound, and a site-wide focus-outline removal. On pages read from their render (every Elementor, Beaver, Bricks page) the form-label, button-name and table-header checks are now decided, not deferred.
  • Vendor-built pages are identified per page. A Beaver Builder page on an Elementor-led site was labelled Elementor and audited as an empty, clean page. Each page's builder now comes from its own data — labels, edit links and the "fix there" note all name it — and a page whose builder plugin is deactivated is a failure (visitors see nothing), not "rendered at runtime". Block-editor pages on a vendor-led site are read from storage and stay writable; the site context and the skill say which.
  • Every publicly queryable post type is audited by the per-page groups — portfolio, team, events, courses — not only pages and posts. A group the active builder cannot assess is refused instead of reported clean.
  • Found dogfooding on a live staging site and fixed: the self-fetch parsed one response header (so every header check failed silently); the probe cache did not clear behind a persistent object cache; undoing one hardening fix undid all of them; a physical robots.txt was treated as WordPress's virtual one.
  • Self-test: new "Site Health" and "Hardening" groups; suites green in four configurations — Divi 5.12.0, blocks only, blocks with Elementor, blocks with Beaver Builder. 2,437 checks on Divi 5.12.0. 100 tools: 51 free, 49 Pro.

2.0.0

  • Measured against Divi 5.12.0. Every Divi-specific verdict was re-taken on a real 5.12.0 install — a 55-module matrix page, one of every content module with one styling write each, read back from a real browser. 55 of 55 emit their background and 54 of 54 emit their text colour on the documented path; the one exception (divi/breadcrumbs, whose trail font path is dead in Divi's own defaults) is now documented with the paths that do render.
  • The render probe read the wrong classes. Divi 5.12 numbers a page's instances from the page's own module count — a second render pass that never resets — so the probe predicted _0 and called every styled block "silent" against a stylesheet that styled all of them. It now reads the classes Divi actually rendered and aligns them by ordinal; modules with no Divi 4 shortcode (group, tooltip, breadcrumbs, payment button) are probed by their real class; a block with no rendered instance is listed as unprobed, never silent.
  • The WordPress side of a build. Nine new tools that end "ask the owner to do it in wp-admin": site setup (title, tagline, front page, posts page), menus with locations, form discovery (Gravity, CF7, WPForms, Ninja, Fluent, Formidable), media search, a broken-link check, site-wide search & replace (dry-run by default, structure-verified), structured data, and SEO title / meta description per page — into Yoast, Rank Math or SEOPress, or printed by Checknaut itself. They work on any WordPress site, Divi or not. 98 tools: 50 free, 48 Pro.
  • Seven of eight sections landed outside the wrapper. A cold-start build anchored an insert "after node 0" — the page wrapper — and the tool obeyed, leaving sections the Visual Builder could not edit. Before/after on the wrapper now means inside it, every write folds stray sections back in, and station_add_module returns the node ids of what it inserted.
  • A column with no flexType renders full width on 5.12 — the front end never reads the row structure. The write now fills an equal split when the count divides 24, as the Visual Builder would, and warns.
  • Gradient backgrounds render on 5.12, literal stops and gradient tokens alike (they did not over REST on 5.11). The "htmlAttributes does not render" warning was measured false and removed; both id/class forms are documented and pinned on a real render.
  • Divi 4 icon strings ("&#xe0e5;||divi||400") draw nothing on Divi 5; the write rewrites them into the icon object Divi 5 reads. Warning node ids now match station_get_tree on every audit.
  • A preset store can carry a record under Divi's "default" sentinel — never applied, undeletable, sorted first. It is listed as corrupt with the repair named, and station_delete_preset removes it. Divi's Dynamic Assets module cache is cleared on every write so a slider or tooltip added over REST loads its script.
  • Accessibility: the blank template renders no title, so a landing page on it is no longer told the template supplies an H1; widget semantics re-measured on 5.12.0 (still absent); charts announce as images with their title.
  • Builder adapters. One core, N builders: a Divi adapter that is a thin delegate to the existing code, and a rendered-HTML adapter that audits any other site from what it serves and refuses every write by name. Tools the active builder cannot serve are dropped from the tool list rather than failing.
  • Skill: a new "dynamic" chapter (loop, dynamic-content and display-condition storage shapes copied from real markup), ids and classes, icon objects, gradients on 5.12, browser verification rules, self-test etiquette.
  • Self-test: a new "Site plumbing" group and ~40 new pins across the findings above. 2,359 checks, all green on Divi 5.12.0 with the write path exercised; 66 groups.

1.9.5

  • The validator passed a shape that fataled the page. A divi/contact-field given an object where its innerContent value is a string validated clean, saved, and returned HTTP 500 to every visitor. From one agent's cold-start build of a 17-section landing page over the MCP — the full field report is on the blog. Values are now type-checked against the module schema (a scalar field given an object is a hard error, dvc_bad_attr_type, naming the node) and every page write runs an in-process render probe and reports render_ok or render_error.
  • A bare string on a blurb title, button or image innerContent stored fine and rendered nothing — the renderer reads ['text']. It is coerced to {"text":…} on write with a warning; the schema carries inner_content_shapes.
  • divi/blurb advertised imageIcon.advanced.width…image, which Divi stores and never reads; width renders from imageIcon.decoration.sizing…width. The real path is in known_paths, the dead one under renders_from, and writing it warns. Every module schema now returns roots — where sizing, spacing, shadow and border actually live.
  • A font variable created a font nothing enqueued. type:font "Saira" resolved and rendered as the OS serif — no @font-face, no Google Fonts link — while the design system called it active. Google families are enqueued on creation, the response says font.enqueued and how, and the performance audit warns about any font variable no stylesheet backs.
  • Media URLs are pinned to the request scheme (a site whose siteurl is still http returned mixed http/https); SVG sources are rasterized to PNG; template:"blank" on page create; license.activate_url and a draft preview_url; a row with maxWidth and no width warns.
  • Accessibility: a link that sets its own colour inline is rated on that colour; when the audit has to assume the global link colour the finding says so and carries assumed:true.
  • Self-test: a new "Field report 1.9.5" group pins every item above. 1,898 checks, all green on the store we dogfood against.

1.9.4

  • Guardian runs no longer overwrite each other. All six audit groups stored their run in one option; two groups continuing in the same minute each read it, appended their pages and wrote it back whole, so the slower one erased the faster one's progress. On an 800-page store this was an audit "stalled" at 48% for an hour. Each group now has its own option and a 90-second lock.
  • Guardian roll-ups are complete under the cap. Causes were built from stored findings, so once the 600-finding cap dropped notes the counts were wrong and the diff compared two truncated lists. Every finding is tallied into per-cause counters as it is admitted or dropped; the diff uses tallies (basis:"causes") when either run dropped findings and says so.
  • Guardian audits published and private content only — a store with 35 legacy drafts scored on pages no visitor could reach. A running batch is a progress report: causes with counts, only failures inline, page ids with the finished run (was 120 KB a call, twenty calls a run). Blog posts in the block editor are an info, not 709 warnings.
  • Site QA called Divi's own writes invalid: themeBuilderArea, the Custom CSS group (css), divider hand-off colours and attrName-declared groups like a video's thumbnail. Builder-written keys are accepted; every attrName head counts as a group.
  • Legal: a store requires a Refund and Returns Policy alongside Privacy and Terms — detected from WooCommerce or a Divi payment button — and the installer can write one. Document resolution prefers the designated page, then a published page, and only then a draft.
  • Contrast, a11y, SEO and performance declare divi/woocommerce-* modules and form shortcodes ([gravityform], [contact-form-7], [wpforms], [ninja_forms]…) as content they cannot read — station_check_contrast returned "no pairs found" on a checkout page and on a page that IS a form, and both read as passes.
  • station_run_self_test never said it had paused: a paused run came back as 242/242 "All checks pass" with no status field — 1/7 of the suite read as a verdict. It now reports status, progress and a PARTIAL note until the last chunk.
  • station_update_page wrapped block-editor content in Divi's placeholder block, so a page with no Divi block read as a Divi 5 page without builder meta and Site QA failed it. Only Divi content is wrapped; the builder meta follows the content. A product with no description is a warning naming the field, not a "0 words" failure.

1.9.3

  • The Guardian findings cap dropped failures on large sites. A run stores up to 600 findings; beyond that, findings were counted and discarded first-come. The audit queue runs in post-ID order, so on a big site the NEWEST pages were audited last and theirs were the findings that vanished — including failures, while six hundred bookkeeping warnings from ten-year-old pages kept their seats. On an 809-item store the plugin's own self-test caught it: a Divi 4 shortcode page created for the test was classified correctly and then dropped from the stored run, so the run said nothing about it and the re-run diff reported it as never fixed. The cap is now severity-aware — notes are dropped first, then warnings; a failure is never dropped — and the truncation note says so.
  • Self-test: the four Guardian classification pins read the probe page directly when a run reports drops, and a new pin asserts that a failure on the last page audited survives the cap on this very site.
  • Self-test: the two "zero H1s" Design Guardian pins now honour the engine's own template awareness — on a site whose page template renders the title as the H1, a page with no stored H1 is correct and the pins expect NO warning. A new pin asserts a text-module <h2> is never counted toward multiple-h1.
  • Self-test: the 'CSS cache + validator' group (nine 1.9.0 pins) was emitted but never registered, so it could not be run by name or seen in the group list; registered, and a CI harness now diffs emitted labels against the registry both ways.

1.9.2

  • The one write that could break a store now plans first and refuses the breaking case. station_build_woo_page replaces WooCommerce's assigned shop, cart, checkout or account page whole, by role. On a real client store the cart page was literally "[woocommerce_cart]" — 18 bytes that ARE the cart — and the tool offered to replace it with no plan step, while its enum listed "myaccount" though no Divi 5 registers a My Account module, so the only reachable outcome there was destruction. It is now a PLAN unless apply=true, showing what the page holds and what would replace it; a layout that carries none of the role's functional modules (divi/shop, the cart modules, the checkout modules) is REFUSED unless allow_nonfunctional=true is passed deliberately; and myaccount is refused outright while the live registry has no account module — checked against the install, not a list, so a future Divi that ships one starts working without a release here.
  • Rescue and Site QA stop telling owners to rebuild their cart. Both classified WooCommerce's assigned pages as "non-Divi content" and the Rescue fix plan said to rebuild them as Divi 5 — a documented workflow that ends in a broken store. Those pages now get their own bucket, annotated with their role, and Site QA reports them as an info ("this is the store's cart page; its content is correct") instead of a warning.
  • The operator restrictions summary now names station_build_woo_page alongside station_update_page as a write that replaces a published page, so an owner reading their own safety settings can see every path that reaches the checkout.
  • A throwing write left a stray product in the admin's cart. Checkout modules run WooCommerce code while the page saves and fatal on an empty cart, so the plugin boots a temporary cart with one sample product for the write and restores the cart afterwards. On the first real store this suite ever ran against, the restore did not restore: the sample product stayed in the session after a failed write. The old restore emptied the whole cart and re-added a snapshot — a wide operation whose own failure was swallowed with the original exception. It now removes exactly the item it added, by cart key, touches nothing else, and records what it did so a restore that fails is visible. The self-test also clears the stray product the old code left behind — it sat in the connector user's persistent cart, which no browser session of yours could empty — so the restore check exercises the real path on the next run.
  • Ten new self-test checks run the real thing against the store's real cart page: a cart-less layout must plan, must be refused on apply, and must leave the cart untouched; myaccount must be refused even with allow_nonfunctional; Site QA must not file the cart as non-Divi; the summary must name the tool.

1.9.1

  • The truncation detector shipped looking in the wrong directory. 1.9.0 measured the compiled CSS under wp-content/uploads/et-cache; Divi writes wp-content/et-cache/<post_id>/. So on every real install the probe answered "no et-cache directory yet" — the one check that exists to catch a silently truncated rebuild could never fire, and a caller reading it saw nothing wrong forever. Found within the hour by pointing 1.9.0 at a live WooCommerce client store. The correct path was already written down in this plugin's own cache-flush comment.
  • The markup validator called Divi's own styling invented. It checked attribute groups against the module's builder-panel map, which the schema tool's own documentation warns is not a storage map. divi/woocommerce-cart-notice writes error.decoration.font.* — present in both its harvested paths and Divi's own style defaults — but has no "error" panel, so validating a live WooCommerce checkout reported that Divi's styling "renders nothing". An agent trusting that would have deleted working checkout styling. Groups are now accepted if they appear anywhere Divi actually writes, and an invented group still warns.
  • Four new self-test checks: the CSS probe must search where Divi writes and must find a real file wherever et-cache exists, a group Divi ships in its own defaults must not be called invented, and an invented group must still warn.

1.9.0

  • An agent can now unbreak an unstyled site. Divi concatenates module CSS and Theme Options Custom CSS into one static file per page and rebuilds it on the first front-end request after any save. On a client site that rebuild came back truncated mid-declaration — 12,820 bytes against a healthy 27,700 — and every page rendered as an unstyled list while the builder looked perfect. The flush that fixes it had existed inside the plugin for releases; nothing exposed it, so the only cure was a person clicking a button in Theme Options. station_clear_css_cache exposes it, warms one request in series so the rebuild cannot race itself, and reports the compiled CSS size before and after — saying so outright when the rebuild comes back short. It is a FREE tool: an install that can see its site broken and not fix it is a worse advert than the revenue.
  • Theme Builder writes now report the compiled CSS size, so a truncated rebuild shows up in the tool result instead of on the client's screen.
  • Memories can no longer go dark in silence. Only what fits the injection cap reaches a session, and the overflow was dropped without a word — found in the field at 2,486 characters against a 2,000 cap, seven memories stored, every save accepted cheerfully, roughly the last fifth never loading while the owner believed all seven applied. The cap is raised to 3,500 (it was below the per-entry limit times three, so a site inside every documented limit still lost rules), a save that would silence an existing memory is now REFUSED and names which ones, every save reports the budget back, and station_list_memory leads with the verdict in words instead of two unlabelled numbers at the bottom.
  • station_list_memory also flags stale memories — ones naming a colour token or pattern this site no longer has. A stale memory is worse than a missing one: it sends every future session after something that was deleted months ago.
  • Twelve new self-test checks, including one that fills the store past the cap to prove the refusal fires and names the casualties.

1.8.4

  • The connector had two sets of instructions. Overview and the Connection tab each carried their own folded "how to connect" — two step lists that had already drifted apart (one said four steps and mentioned Claude Code, the other said five and did not), on the one screen a new customer cannot afford to be confused by. The Connection tab owns the instructions now; Overview keeps the URL and a copy button, because a credential you cannot find is a support ticket, and links across for the rest.
  • The Connection tab reads as what it is: copy this URL, paste it in Claude. The four paragraphs that used to sit under the steps — plan prerequisites, the desktop-app connector cache, stale tool lists after an update — are kept in full behind one "Not connecting?" disclosure, so a working connection stops looking like a problem. The summary names the plan prerequisite outright, because that is the one failure a person cannot diagnose from the steps.
  • Four self-test checks hold the line: the Overview renderer may contain no steps of its own, the troubleshooting stays folded rather than deleted, and the plan prerequisite stays inside the fold rather than orphaned above it.

1.8.3

 

 

  • The cross-site pull handoff never worked — and now does. The single-use ticket a target site redeems anonymously hit a per-user read check that anonymous requests fail on every post, so the first real pull answered 403 on a published page and burned the ticket. The ticket is now the read authorization it was always meant to be, and four new self-test checks run the serve leg exactly as a pulling site does.
  • Client reports no longer cry wolf: the since-last-run delta counted informational notes — audit blind-spot disclosures whose own body says there is nothing to fix — as new issues. Only failures and warnings move the fixed / new numbers now.
  • station_delete_token’s refusal now names the posts that reference the token, instead of telling you to retarget references it never locates.
  • All three found dogfooding 1.8.2 on our own sites: the first real site-to-site pull, the first real client report, the first real token cleanup.

1.8.2

 

 

  • Theme Builder templates now actually assign. Short conditions like singular:post were stored verbatim and Divi resolved none of them — the template sat Unassigned and never matched on the front end. Conditions are now translated to the ids Divi resolves, and an id the install cannot resolve is refused with the real list instead of stored as a dud.
  • Body-only templates no longer lose the site chrome: omitted header and footer slots now inherit the global layouts instead of falling back to the theme’s stock header, and deleting a template can no longer trash a layout another template still references.
  • Loop Builder fields (loop_post_title, loop_post_link and friends) now pass the token audit instead of warning that a perfectly working binding resolves to nothing — found building this site’s own blog index.
  • Self-test hardening: ten new checks pin the fixes, and fixture reclaim now covers design tokens — an aborted run could leak its test color and false-fail every later tokenize run.

1.8.1

 

 

  • Audit accuracy fix, found dogfooding this site’s own new blog: Gutenberg and classic-editor content was invisible to the audit collector, so posts measured zero headings and zero internal links — and the SEO audit warned that link-filled posts linked to nothing.
  • The collector now runs non-Divi blocks and freeform (classic) content through the same HTML scanner Divi text modules use — headings, links, images, iframes and inputs in posts now count in every audit and in client reports.
  • Five new self-test checks pin the fix end to end, through the exact internal-link counter the SEO audit uses.

1.8.0

 

 

  • A landed fix re-checks its page. The customer panel’s top frustration, verbatim: “I fixed a finding and the tab badge still said ×5.” Every fix door — one-click fixes, the review screens, Fix All, and undo — now re-audits the touched page inside the stored run: findings swapped, counts corrected, and the header badge updated on the spot. The fix message tells you what the re-check found instead of sending you off to re-run a forty-page audit.
  • Fix All re-checks inside a time budget. A bulk fix across many pages re-verifies as many as fit in the budget and says plainly when the rest wait for the next run — one sentence never covers two different truths. Score history still only records at finished audits, so the trend line stays a record of real measurements.
  • Branded client reports. Settings → Client reports takes your business name and a logo from your Media Library; every exported report then carries your brand in place of our mark (a short trademark notice still names Checknaut). The logo is embedded into the report file itself, so the document keeps working offline and in email. Available on every paid plan — the plans differ by sites, not features, and that stays true.
  • Eleven new self-test checks pin both features, including that a re-audit never writes score history and that a branded report drops our mark without dropping the Divi trademark notice its content still requires.

1.7.3

 

 

  • Licence seats are released on uninstall. Deleting the plugin now tells Freemius, so a single-site licence frees itself for the next site. This closes the oldest documented bug in the codebase: WordPress runs a plugin’s uninstall.php file INSTEAD of the registered uninstall hook, so shipping that file silently stopped Freemius ever hearing about the uninstall — and the seat stayed burned.
  • The cleanup sweep moved to the after_uninstall hook. Same sweep, credentials first and multisite-aware — it now runs after Freemius releases the seat, with a fallback registration when the SDK is absent. Subsites on a network additionally get their cached transients cleared, which the old file only did on the main site.
  • The build refuses uninstall.php forever. A resurrected file would silently re-break seat release with no error anywhere, so the packager now fails the build — the same check Freemius’ deployment pipeline runs, moved to where the mistake would be made.
  • Three new self-test checks pin that the file stays deleted, that both registration paths exist, and that the sweep only runs during a real uninstall.

1.7.2

 

  • Second dogfood find, same client site: The plugin was auditing its own test fixtures as the customer’s content: probe pages stranded by runs the 1.7.1 lock bug killed showed up as SEO and QA failures on pages the customer never made. Both audits went clean the moment the debris was removed.
  • The fix: The probes now carry the swept fixture prefix, the old name is legacy-swept so pages leaked by earlier versions are reclaimed automatically on the next run, and two new self-test checks pin both.

1.7.1

 

  • Found dogfooding, fixed same day: We ran the plugin on a real client site on WP Engine and its own self-test could not finish: chunked runs pinned at the same cursor forever. Diagnosed, fixed, and verified on that same site within the hour.
  • The cause: On hosts with a persistent object cache, a transient with a TTL lives only in the cache — never the database — so eviction erased the run lock mid-pause and the resume path reaped the paused run as abandoned.
  • The fix: The lock is now a database-backed option with its own expiry, and every read and write of the lock and the paused state flushes the object-cache key. The host cache can neither evict a run nor serve it stale.
  • Honest fixtures: Two self-test checks assumed a fresh site whose typography the fixtures could control. On a lived-in site whose own headings join the vote, they now pin what is actually guaranteed — and say why they skipped the rest.

1.7.0

 

  • The connection pill: Every screen of the app now says, at a glance, when the site last heard from Claude — green while requests are fresh, amber after a silent week, grey when no client has ever called in. The pill links to the Connection tab.
  • Honest by design: A stateless connector holds no socket open, so there is no true “connected right now” to show. The pill shows the one thing the server actually knows — the time of the last authenticated call — and never pretends otherwise.
  • Durable: The timestamp is a latch recorded on every authenticated request, so it survives the activity log aging out or being switched off. The Connection tab reads the same latch and no longer claims a bare “connected” with no time on long-idle sites.
  • Pinned: Uninstall removes the latch, and new self-test checks pin the recorder, the reader, the pill states and the cleanup.

1.6.1

 

  • Agent sweep, round two: Four independent adversarial review agents against the whole surface. Twelve confirmed findings, all fixed same-day and pinned in a new self-test group.
  • Paywall hardened: A crafted request could run a Pro site-level fix on Free by riding a page id past the gate — closed on both doors, wp-admin and the connector.
  • Tokenize verify fixed: A page that also displays one of its colours (a code sample, a style guide) no longer rolls back a clean tokenize with an error.
  • Honest contrast math: rgb() channels above 255 now clamp to what the browser paints, so an overdriven value can no longer slip past the AA gate.
  • Specimens everywhere: Type-scale findings sourced from your site’s own mined convention now render their specimens too.
  • Controls survive errors: A network blip or expired session no longer removes Fix buttons until reload; review panels load once; one audit loop per screen.
  • Fix All: An apply must present the plan token of the plan it displayed, and plan and apply now agree on their arithmetic.

1.6.0

 

  • Search previews: SEO title and description findings now show the page as a Google result — the part search engines keep bright, the part they cut struck through, missing fields flagged in red.
  • Heading outline: Heading findings open the page’s real outline: every heading indented by level, skipped levels and extra H1s flagged amber. Opens automatically.
  • Image evidence: Performance image findings list the actual offending images — thumbnail, file size, and width badges — with honest skips for images the plugin cannot read.
  • Type specimens: Font-size drift findings render both sizes as real type, so you can see the drift instead of comparing numbers.
  • Color dots: Every hex color named in any finding now carries a swatch dot showing the color itself.
  • Fix: Escaped apostrophes are no longer mistaken for three-digit hex colors.

1.5.0

 

 

  • Fix alt text where you see it. Missing-alt findings arrive with the review already open — the image thumbnail, the best suggestion pre-filled, and the input ready. Describe the picture and Apply, right in the row; the builder link stays for anyone who wants the full page around the image.
  • Every audit pill says when it last ran. A compact age on each of the six pills — 35m, 3h, 6d — with the full time in the tooltip. A run older than a week, or an audit never run, shows amber: the point at which the counts stop describing the site.

1.4.3

 

 

  • The contrast screen passes its own test. The suggested-swatch caption sits on the pair’s own colour, so it now rides in a solid chip that is legible on anything — the one screen about contrast no longer fails it.
  • Findings read human-first in Simple mode. The type-scale finding leads with what the Fix button will do before the technical route.

1.4.2

 

 

  • Hardened by an adversarial review sweep. Three independent QA reviews ran against the new panel code; thirteen verified findings, all fixed same-day.
  • Contrast review is stricter about identity. An approval made for one text colour refuses to overwrite a colour an editor chose after the list was made; a value with junk after a valid colour is no longer “a colour”; and every alpha spelling is recognised, so a translucent replacement can never sneak past the AA gate.
  • Fix All’s confirm is bound to the plan it showed. If the stored audit changes between plan and confirm, the write is refused and the button re-plans — the write set can never silently differ from the shown set.
  • Smaller honesty fixes. The broken-token cause stops offering a fix that couldn’t fix it; the legal publish guard also recognises WordPress core’s own template Privacy Policy draft; waiver-driven score moves say so instead of blaming coverage; draft-page findings link to the editor instead of a 404.

1.4.1

 

  • Design panel render fix. A shadow role’s value printed as raw JSON in a cell that could not wrap, pushing the adopted-roles table’s Override column off the screen. Shadows now read as the shorthand a designer knows — offsets, blur, spread, colour — with the exact stored object in the tooltip. Render-only; no behaviour changes.

1.4.0

  • Every audit panel catches up to Design Guardian. One question asked of each audit: can you understand what is wrong and fix it without leaving the panel? All six now answer yes.
  • Contrast gets a review screen with swatches. Each failing pair renders as it really is, beside the nearest colour from this site’s own palette that clears WCAG AA — approve it or type your own, with a live preview. An approved suggestion writes the design-system token, and a replacement that would itself fail AA is refused with the ratio it would have had.
  • Hardcoded values get a one-click tokenize. Every literal matching exactly one design-system colour becomes that colour’s token, verified with zero modules lost. Ambiguous literals are listed with their candidates, never guessed.
  • Fix All shows its plan first. The first click runs the batch dry and lists, page by page, what would change; only the re-armed second click writes.
  • Open in builder, everywhere. Every finding row links straight into the Visual Builder on the page it names.
  • Simple mode finally reaches the findings. Token JSON reads as its label and tool-speak steps aside for the Advanced view; nothing is ever hidden entirely.
  • The score delta explains itself. Open “−1 since yesterday” and it names which audits moved, from per-suite snapshots the score history now keeps.
  • Legal drafts publish from the panel — refused while they still carry [REVIEW] placeholders, because a live legal page with blanks in it is worse than a missing one.

1.3.10

 

  • Missing alt text gets a review screen. The audit finding now shows each image, pre-fills the Media Library’s suggestion where one exists, and takes your own words instead — applied per item, journaled, undoable. Empty alt stays the deliberate decorative marker, and an approved description is refused if the image changed since the list was made.
  • Design Guardian’s four checks all grow Fix buttons. Slider autoplay switches off in one click — arrows, dots and swipe keep working — heading sizes snap back to this site’s own resolved type scale, and H1 repairs route through the headings engine’s existing guards.
  • Generic CTA copy is review-only, deliberately. No rule writes a good button label, so the screen shows each button, where it links, and a mechanical suggestion — an internal link becomes “See that page’s title” — for you to approve or rewrite. A replacement that is itself generic is refused: a fix that re-flags on the next audit fixed nothing.
  • Every review write carries every gate a Fix click carries — the free tier’s bound-page rule included — and the self-test grows to 1,714 checks.

1.3.9

  • Fixed: the paywall could fail open, and it was a fix in 1.3.8 that did it. 1.3.8 made the free-page binding heal itself when the bound page had been deleted — but it did that inside the read the page gate calls on every single tool call, and that gate does no existence check by design. A binding that could not be resolved became no binding, and all 21 page-scoped tools were waved onto any page.
  • The self-test caught it on the first run after the deploy: eight failures in Licensing & tiers, one of them reading “a second page is refused — NO ERROR, FREE TIER REACHED A SECOND PAGE”. It never reached a customer.
  • The healing moved to the Overview screen, where a human can see it: a binding pointing at a deleted page now says so and offers to release it, and that release does not count against the three-rebind cap. A paywall that heals itself into having no limit is worse than the stuck state it was fixing.

1.3.8

superseded

  • Superseded by 1.3.9 within the hour — a fix in this release made the free-tier page limit fail open. Do not run 1.3.8.
  • Fixed: the client report was downloadable without a licence. The handler had a capability check, a nonce, and no licence check at all, and the button was drawn on every audit screen, on every tier, in the default view. Free deliberately gets the whole site-wide audit, so one click turned a free install’s findings into the branded deliverable an agency resells.
  • The free tier’s one-page limit could be released and rebound without limit, and the message the agent receives named wp-admin as the place to do it — so it read as the sanctioned remedy. Release, build the next page, release again. Rebinding is now capped at three: enough for someone who bound the wrong page, nowhere near enough to walk a site.
  • Snapshot restore is free again. A rollback behind a paywall is worse than no rollback, and the people who need it most — a lapsed trialist, a lapsed customer — are on the free tier by definition.
  • Scheduled audits used to render the whole form on Free and refuse at the save, discarding what had been typed. It says it is Pro before the effort now.
  • Client report links expire after 14 days and nothing said so; a monthly schedule was deleting last month’s report, including a link already sent to a client.
  • The self-test now checks the wp-admin door as a class, not one handler at a time. That is why the report leak survived five releases after six others were fixed.

1.3.7

  • First-run pass. The Connection screen states the prerequisite nothing stated: you need a Claude account on a plan that can add custom connectors.
  • “Didn’t attach? Run the connection checks” now appears under the last step while a site has never connected. Diagnostics is Advanced-only and Simple is where a new install starts, so the one person who needed the checks was the only one who could not see them.
  • Connection says whether Claude has actually called in, and when. “Endpoint live” only ever meant the REST route answers.
  • The audit opens the most severe group you actually have, instead of only opening failures — on a site with warnings and no failures, every group used to render closed.
  • “How this is scored” on the score card. The reasoning was written out in full where only Claude ever read it.
  • Safety switches no longer look saved when they are not.

1.3.6

  • Fixed: three writes kept no step back, and one of them said it did. Tokenizing a page’s colour literals rewrote every module’s attributes, wrote directly, and returned “A revision holds the prior content” — with no revision and no undo slot anywhere in the function. False on every site, not only the ones with revisions disabled.
  • Theme Builder and Divi Library writes used a bare WordPress revision save, which does nothing on a site with revisions switched off. A header write there had no undo of any kind, and one bad header write is every page on the site. Both now get the same one-step-back guarantee as a page write.
  • The self-test says when a run was short of full coverage. Four groups need Divi’s builder framework, which wp-admin does not load, so they are skipped there and run through the connector. Both surfaces behaved correctly and both wrote a “Skipped” row, but the wp-admin headline read “All 1,494 checks passing” while the same suite reported 1,673 through the connector, with nothing explaining the gap.
  • The rescue scan lists at most 40 pages per category while its counts are complete, so a site with 200 Divi 4 pages was told 200 and handed 40. The result now says which lists were shortened and by how much.
  • “Every finding has a one-click fix” on the first-flight checklist. There are 83 check types and 11 have a Fix button.
  • Two strings still called tier gating off by default, which stopped being true in 0.90.0 — one of them in the payload Claude reads at the start of every session.

1.3.5

  • The score is now labelled “Autopilot Site Score” wherever it appears. “Site score” read like a WordPress feature; it is this plugin’s arithmetic and it should say so.
  • The dashboard’s own capability copy carried the same overstated claims that were corrected on this site, which is worse — that is the copy an evaluator reads while deciding whether to trust the thing. All four now match the code: alt text and heading fixes are journaled and undoable while legal pages arrive as drafts to review; one step back before every page write and a snapshot before every design-system write; rescue classifies up to 300 pages, not “every page”; five auditors read every page while legal reads the site.

1.3.4

  • Fixed: the step back kept for sites with WordPress revisions disabled was written and unreachable. Where wp_save_post_revision() does nothing — WP_POST_REVISIONS off, which several managed hosts ship and every “optimise your database” plugin offers — Checknaut stashes the outgoing page content in post meta, then told the operator to restore it with revision_id 0. There was no branch for 0, and the function that reads the slot back had no callers anywhere in the plugin. On exactly the sites least likely to have another backup, the recovery instruction was false.
  • station_restore_revision now accepts revision_id 0, with the same dry run, the same module-count verification and the same refusal to touch another page’s content as any revision restore. station_list_revisions lists the slot as its own row, first, with its module count and an explanation of what it is.
  • Restoring is undoable on both kinds of site now. The restore path called wp_save_post_revision() directly, which does nothing where revisions are off, so restoring the slot consumed the only step back and left none.
  • Three tool descriptions and the History screen claimed “every write saves a revision first” — true on most sites, false on the ones this feature exists for. All corrected.
  • New self-test group, “Undo slot”, which writes the slot the way a revisions-off site would and drives the whole round trip. The bug survived because every test ran on a site with revisions on, where the slot is never written.

1.3.3

  • Fixed: six paid capabilities were reachable without a licence in wp-admin — fixing a cause across every page, scheduled audits, the emailed client report, installing legal drafts, adopting a design system, and restoring one. All six are gated on both surfaces now, and the scheduler refuses to arm or to fire without a licence, so a lapsed site stops rather than quietly continuing.
  • Fixing from wp-admin now respects the Free tier’s bound page — the rule the tool surface already applied.
  • station_restore_snapshot joined the design-system group, so “Protect the design system” covers it. It rewrites colours, variables and presets site-wide and was the one write that switch could not see.
  • The Free tool count is 42, not 43.
  • One icon set across the plugin and this site. The ten dashboard icons were Unicode glyphs from six different blocks, two of them emoji codepoints the operating system rendered in colour while the rest stayed monochrome. All inline SVG now, on one 24px grid.
  • New “Next” row on Overview: at most three tiles naming the next action and what it does, ordered by what blocks what.

1.3.2

  • Fixed: every surgical write — edit, add, move, delete module — was returning the whole page’s token digest instead of the warnings for the node just written. Forty consecutive edits each came back with the same eleven warnings and their full node lists. New warnings still come back in full; pre-existing ones now collapse to one counted line.

1.3.1

  • Fixed: the full self-test reported eight failures on a Free site when nothing was wrong. Six were the licence gates correctly refusing paid writes, and two were an adopted design system correctly outranking the miner. Every one of those refusals is now asserted as the correct behaviour it is, on both tiers.
  • The full suite is meant to be run from wp-admin on any tier. It now stays green there whether or not the site is licensed, and whether or not a design system has been adopted.

1.3.0

design system

  • Reads the design system your site already has. Scans pages, posts, Theme Builder layouts and the Library — presets resolved, newest work weighted heaviest — and proposes your real colours, type, radii, shadows and spacing as a scale rather than an inventory. Adopt it and Claude resolves every design question against it first.
  • Four new resolver domains — elevation, radius, spacing, sizing — that answer with a paste-ready Divi 5 attribute fragment at the correct root for the module you name.
  • Group presets are now resolved when auditing. Styling that lived only in a group preset was previously invisible to the contrast, accessibility and performance engines.

1.2.0

  • Licence keys can be activated after the fact: a masked key field on Diagnostics → Licensing, a one-line prompt on Overview, licence detail with seats used, and a “release this site” control so a seat can be moved.

1.1.0

  • Admin UI translated into French, German, Spanish, Italian, Brazilian Portuguese, Dutch, Polish and Japanese.

1.0.2

  • Findings table readability: primary finding text at full contrast (10.2:1).

1.0.1

  • Self-test reliability: content-classifier checks made deterministic across WordPress parser versions.

1.0.0

launch

  • Commercial launch. Tier enforcement on by default; GPL license file; pre-launch security audit fixes — design-guard bypass closed, uninstall data hygiene, endpoint rate-limiting, log secret-scrubbing.

Before 1.0 there were ninety-odd development releases. The two that matter: 0.98.x introduced the unreadable-content architecture, where audits declare what render-time modules they cannot read instead of reporting false counts or false cleans, and closed an API-key leak in page rendering. 0.91–0.97 brought the site score with disclosed arithmetic, per-finding owner waivers, first-flight onboarding, and auto-continuing chunked runs with progress.