=== WP BaseStation ===
Contributors: abcreative
Tags: ai, claude, mcp, audit, accessibility, seo, divi
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 2.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Connect Claude to WordPress. Build pages by conversation on Divi 5 and the block editor, audit any site on any theme, and fix what it finds — reversibly.

== Description ==

WP BaseStation is a remote MCP server for WordPress by Abcreative, LLC — your site's base station for AI. Add the connector URL to Claude and it can read your site's design system, build and restructure pages natively on Divi 5 and the block editor, and audit any site on any theme — accessibility, SEO, performance, legal furniture, site QA and design consistency — with one-click fixes, an undo journal, and a single WP BaseStation Site Score that shows its own arithmetic.

* 100 MCP tools (44 free) — pages, design system, Theme Builder, WooCommerce, Loop Builder, dynamic content, media, snapshots, block-editor posts
* 7 audit suites that state what they cannot see — the seventh, Site Health, audits the install itself on any theme; — content generated at render is declared unreadable, never guessed at
* Per-finding waivers only the site owner can grant; the agent deliberately has no tool for them
* Scheduled audits that email only when something changed; client report export
* Reads and adopts the design system your site already has, then builds to it
* A 1,700+ check self-test against the live install
* Admin UI in nine languages (EN, FR, DE, ES, IT, PT-BR, NL, PL, JA)

Free tier: all free tools, site-wide read-only audits, and fixes on one bound page. Pro: fixes everywhere, fix a cause across every page at once, scheduled audits, client reports, the full design-system toolset.

Audit output is informational only — not a certification of ADA/WCAG or legal compliance, and not legal advice.

WordPress is a registered trademark of the WordPress Foundation. Divi is a registered trademark of Elegant Themes, Inc. WP BaseStation is an independent product, not affiliated with, authorized by, endorsed by, or sponsored by the WordPress Foundation or Automattic Inc., and, not affiliated with, authorized by, endorsed by, or sponsored by Elegant Themes, Inc.

== Installation ==

1. Upload and activate the plugin.
2. Open WP BaseStation → Overview and copy the connector URL (your key is inside it — treat it like a password).
3. In claude.ai or the Claude desktop app: Settings → Connectors → Add custom connector → paste the URL.
4. Ask Claude to audit or build a page. Diagnostics → Self-test proves the connection if anything refuses.

Safety posture: staging first, writes off until you are ready, independent backups on. The plugin's revisions, snapshots and one-step undo are conveniences, not backups.

== Frequently Asked Questions ==

= Does it change my live site? =
Only when writes are enabled and you ask it to. Every write preserves the prior content; owner safety switches (read-only, draft-only, protect design) can only be tightened from a conversation — loosening them is wp-admin only.

= Is a clean audit a compliance guarantee? =
No. Audits are automated static analysis: a floor, not a certification. Each suite states its blind spots in its own output.

= Which languages? =
The admin UI ships in nine languages and follows your WordPress site language. Audit findings are English in this release.

== Changelog ==

= 2.2.0 =
* RENAMED: this plugin is now WP BaseStation. The product outgrew its 2.0 name: 2.1.0 made every audit work on any theme with any builder, and the 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:
* — The pre-2.2.0 REST namespace still serves every route, byte-identically, so a connector added before this release keeps working. Undocumented and unadvertised; it exists so nothing stops working, not as a second API.
* — All 100 tools are renamed divi_* to station_*, and the old names are still accepted silently. A saved skill, a site memory or a model context holding divi_audit_site calls station_audit_site and is journaled under the new name. The manifest lists the canonical 100 only.
* — The pre-2.2.0 admin URL 301s to the new one, keeping every other query argument, so a bookmarked tab still lands where it did.
* — The dedicated auth header follows the product (X-BaseStation-Key), and the two older names are still read, because that header is the fallback for hosts that mangle Authorization — the one path where a caller cannot discover a rename from a 404.
* — Settings, tokens, snapshots, history and the journal all survive, and the new install picks them up the moment it activates. They are keyed on the dvc_ option prefix and the DVC_ class prefix, which do NOT move: those are storage keys, not branding, and renaming them would orphan every option, token and snapshot on an upgraded site. The OAuth scope strings (divi:read / divi:write) stay for the same reason — they are already granted to live tokens.
* UPGRADING FROM 2.1.x — the order matters, and the last step is the one to read. WordPress sees a new plugin, so: (1) install and activate WP BaseStation; (2) deactivate the 2.1.x plugin — two ACTIVE copies load every class twice and fatal on redeclaration; (3) LEAVE the old one installed, or delete it knowing what that does. Deleting a plugin runs its uninstall sweep, and 2.1.x's sweep removes the dvc_ options — settings, static token, snapshots and history — because it has no way to know a successor is holding them. Deactivating removes nothing. If you do delete it, everything above is gone and the connector needs its key again; the pages, audits and fixes on the site are untouched either way. Your licence carries over: the Freemius product record is migrated to the new slug the first time 2.2.0 loads, so no key needs re-entering.
* Verified end to end before release: 2,441 checks green through the connector on Divi 5.12.0 with WooCommerce and Gravity Forms — including the write path, and with the connector still addressing the pre-rename namespace and tool names, which is the promise above being tested rather than asserted. 2,435 green on Divi 5.12.1 the day it shipped, and 1,426 on a Divi-less install running Elementor and Beaver Builder.

= 2.1.0 =
* SITE HEALTH — THE SEVENTH GUARDIAN GROUP, 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 (a same-branch core release pending is a failure, not a warning); pending plugin and theme updates; inactive plugins and plugins with no wordpress.org release in two years or a "tested up to" two majors behind; the security posture — guessable administrator logins, display names that equal logins, open registration and its default role, XML-RPC (Jetpack-aware), anonymous user enumeration over /wp/v2/users and ?author=, missing login protection, backup and security plugins (host-provided ones acknowledged, not assumed), default salts, wp-config permissions, a browsable uploads directory; the live home response — missing HSTS / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / Permissions-Policy, version disclosure in headers, meta generator and readme.html, mixed content on https; the database — autoloaded options past WordPress's own 800 KB line with the five largest named, revision and expired-transient backlogs, orphaned postmeta, MyISAM tables, a full trash; content hygiene — "Hello world!" and "Sample Page" still published, a soft 404 or a catch-all redirect, a robots.txt that disallows everything while indexing is on or names no sitemap, menu locations with nothing assigned, a front page or posts page that is not published, comments open with no spam protection, spam and moderation queues; mail — SPF (and a +all), DMARC (and p=none), MX for the site domain, PHP mail() with no SMTP plugin, an admin email on another domain; and every WordPress core Site Health direct test this engine does not already measure, folded in with core's own label and fix. Every probe that needs the network (self-fetches, DNS, wordpress.org) is cached, time-boxed and reported as UNCHECKED by name when it cannot run — a host that blocks loopback requests produces a shorter list, never a cleaner one. It states what it cannot see: passwords, PHP execution inside uploads, host-level firewalls and backups, DKIM, malware. Scored like legal (weight 1.5), on the schedule, in the client report and the admin Audit row. 100 tools: 51 free, 49 Pro (station_set_hardening).
* HARDENING SWITCHES — THE FIXES BEHIND THE POSTURE FINDINGS. station_set_hardening (Pro) and station_fix_finding on the matching health-* check flip one of seven reversible, option-backed switches applied on every request: xmlrpc_off, hide_versions (generator tag and the core ?ver= only — plugin cache busters are left alone), block_user_enumeration (anonymous /wp/v2/users → 401, /?author=N → home), security_headers (nosniff, SAMEORIGIN, strict-origin-when-cross-origin, a restrictive Permissions-Policy — each sent only when the server has not), hsts (HTTPS only, 180 days, no preload, refused on http), disable_file_edit (defines DISALLOW_FILE_EDIT), robots_sitemap (the SEO plugin's sitemap or wp-sitemap.xml in the virtual robots.txt). Two content fixes with their own undo: health-default-content trashes "Hello world!" and "Sample Page" (never a configured front page), health-comments closes comments by default and on every published page. Every fix journals for station_undo_fix; the health report lists every switch and its state under "hardening". Fixes that need a file, the server or another plugin (salts, 2FA, backups, a WAF) stay as instructions — a button that pretends is worse than none.
* DOCUMENT-LEVEL ACCESSIBILITY, ANY THEME. The Accessibility group now reads the rendered home page once per run for the facts that belong to the theme rather than to a page: a missing <html lang>, no skip link, no <main>/<nav> landmark, a viewport that blocks pinch-zoom (a failure — WCAG 1.4.4) or is missing, duplicate ids, positive tabindex, autoplaying media with sound, and inline CSS that removes the focus outline without replacing it. Reported once, site-wide, under the home page; a failed fetch is reported as unchecked, never as clean. Found the day it shipped: Converge's Divi header blocks pinch-zoom and removes the focus outline.
* Fixed: DVC_Probe::fetch_home() declared a memo it never filled, so the second caller in a request got null — the a11y document checks found four things and the same run then reported the home page unfetchable. Guardian runs now carry the site-level engines' `unchecked` list (and a note) so a run on a host that blocks loopback requests reads as shorter, not cleaner.
* RENDERED-PAGE CONTROLS ARE JUDGED, NOT NOTED. On a page read from its render (every Elementor, Bricks, Breakdance, Oxygen, Beaver or WPBakery page) the Accessibility group now decides three things the stored-content note used to defer to the form plugin: form fields with no accessible label (a placeholder is not a label; for=, wrapping <label>, aria-label and title are), buttons with no accessible name (the icon-only hamburger, search and close buttons), and data tables with no header cells (role="presentation" tables excluded). Verified on an Elementor 4.2.4 install built for the purpose.
* THE FIRST REAL ELEMENTOR SITE. The rendered-HTML adapter had only ever been exercised by its own pins. Standing up Elementor 4.2.4 and auditing it found: (1) every post on a vendor-led site was read from the render — "Sample Page" and the classic blog posts included — so the block pages lost the storage-level checks and carried the unreadable note; classify() now routes only the vendor's own pages to the render, block markup to the block adapter and classic HTML to its freeform reader, and the site context says which pages are the WRITABLE exceptions (writable_pages, and a MIXED SITE block in the skill that reads the other way round). (2) Design Guardian ran over five Elementor pages and reported "5 clean pages" from an empty model — a group the active builder cannot assess is now refused by station_audit_site with dvc_group_not_assessable instead of counted clean. (3) The document-level focus-outline check fired on every block theme because core's own .wp-block-navigation__responsive-close:focus{outline:none} matched; only a GLOBAL selector (a:focus, button:focus, *:focus, a bare :focus) counts now. (4) Elementor's element cache (_elementor_element_cache) serves a stale render after a data write until Elementor regenerates it — the audit reads what the visitor gets, which is the right thing, but an agent editing Elementor data through its own connector must clear that cache before re-auditing.
* Self-test suites now run green in three configurations: Divi 5.12.0 (Converge), a block theme with no builder, and a block theme with Elementor active and 75% of pages built in it. Pins that assumed "the block adapter is active" on a no-Divi site were rewritten to ask which adapter OWNS the page.
* DESIGN GUARDIAN READS EFFECTIVE ATTRIBUTES. A heading whose level and size live in its PRESET (the right way to build) has no inline headingLevel, and the taste walk read inline attributes only — so the product site's own Pricing and Contact pages were warned missing-h1 while the heading repairer, which resolves presets, called the outline correct. Two engines, one page, two answers; now one. The manifest pin also accepts station_fix_link_text being withheld on a pure-Divi install (the mix rule), which the wp-admin self-test on the product site flagged as its single failure.
* DOGFOODED AS A JOB: the six hardening fixes were applied to a live staging site over MCP and verified from the outside (headers present, generator gone, /wp/v2/users → 401, xmlrpc.php → 403). Found and fixed on the way: (1) the health probes are cached in transients, and forget_probes() deleted them with a LIKE on wp_options — on a site with a persistent object cache (Redis, Object Cache Pro) the transients are not there, so the re-audit kept reading ten-minute-old probes while the switches were verifiably live; the cache is now generation-stamped and forgetting is one option bump. (2) A physical robots.txt (GoDaddy ships one) means WordPress never serves its virtual file; the sitemap finding now says so and the robots_sitemap fix refuses with the reason instead of "applying" nothing. (3) When wp-admin has never fetched the plugin update list, Site Health fetches it once itself before giving up on the abandonment check.
* A SECOND VENDOR, AND THE MIX WITHIN THE MIX. Beaver Builder Lite beside Elementor on the same sandbox: the Beaver page was labelled "Elementor", handed an action=elementor edit link, and — because only the headline vendor's meta counted as renderable — read as an EMPTY model and audited clean with two H1s on it. The generic adapter now identifies the vendor that built EACH page from that page's own meta (vendor_of), labels, edit links, the "fix there" suffix and the unreadable note all name it, and every vendor's meta counts as content. The block adapter's classify() reads the same vendor table, so a Beaver page on a block-led site routes to the render instead of being filed as classic-and-empty. NEW QA check builder-inactive (fail): a page whose builder's plugin is deactivated really does render empty for visitors — it used to be reported as "rendered at runtime". Bricks and Breakdance edit URLs added.
* Suites green in four configurations: Divi 5.12.0 (Converge), blocks only, blocks + Elementor-led, blocks + Beaver Builder.
* THE SELF-FETCH READ ONE HEADER. wp_remote_retrieve_headers() returns a CaseInsensitiveDictionary, and an (array) cast of that object yields its one private store — so every probe saw "1 header", every security-header check failed, no cache header was ever seen, and the hardening switches were live for the browser and "missing" for the audit. Fixed (getAll()); every Site Health run now lists what each self-fetch actually received (probe_notes: status, header count, server, cache status, "cached N minutes ago" when replayed) so a reader can tell a probe from a guess. Undoing one hardening fix restored the whole pre-fix switch set — the first of six undone switched the other five off; undo now restores only its own switch. Cloudflare's MISS/DYNAMIC/BYPASS no longer count as a page cache (only a HIT-class status does), and the probe cache key carries the plugin version so a deploy never reads the previous build's answers.
* EVERY PUBLIC POST TYPE IS AUDITED. The per-page groups queued pages and posts (products for a11y/perf/seo); portfolio items, team members, events, testimonials, courses — anything a visitor reaches — were never looked at. Every publicly queryable post type now queues, minus attachments, builder templates, Woo internals and WordPress's own housekeeping types (DVC_Guardian::NEVER_AUDITED_TYPES).
* Every default group list (report, scheduled digest, tools, admin badges) derives from DVC_Guardian::group_keys() instead of a hand-typed six — the class of bug that once dropped SEO from every scheduled digest.
* Self-test: a new "Site Health" group (24 checks) — surface, shape, budget, the checks that can be decided on any install, the unchecked contract, the Guardian run, and post-type coverage.

= 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, one styling write each) read back from a real browser — and the plugin now says 5.12.0 where it says what it measured.
* THE RENDER PROBE READ THE WRONG CLASSES. Divi 5.12 numbers a page's instances from the page's own count of each type (a 4-section page renders .et_pb_section_4…_7, one button renders _1): a second render pass that never resets its counters. The probe predicted _0 and reported every styled block on a page "silent" against a stylesheet that styled all of them. It now reads the instance classes Divi actually rendered and aligns them to the stored blocks by ordinal; modules whose module.json carries no D4 shortcode (breadcrumbs, group-carousel, payment-button, tooltip, group) are probed by their real class; a block with no rendered instance is listed as unprobed, never as silent. The 5.12 matrix: 55 of 55 modules emit their background and 54 of 54 emit their primary text colour on the documented path — the one exception, divi/breadcrumbs, whose trail.decoration.font is dead (Divi's own style default included), is in renders_from with the breadcrumb/home/separator paths that render.
* A COLUMN WITH NO flexType RENDERS FULL WIDTH. A three-column row written with flexColumnStructure "equal-columns_3" and bare columns rendered three stacked 24_24 bands on desktop — the front end sizes columns from each column's flexType and never reads the row. The write now fills an equal split when the count divides 24 (as the Visual Builder would) and warns; five or seven columns are warned about and left for the author.
* GRADIENT BACKGROUNDS RENDER ON 5.12 — literal stops and gradient tokens both (a 5.12 changelog fix; on 5.11 a REST-written section gradient emitted nothing). Documented, with the version condition, in the skill's tokens section.
* SEVEN OF EIGHT SECTIONS LANDED OUTSIDE THE WRAPPER. A cold-start build anchored station_add_module "after node 0" — the divi/placeholder wrapper — and the tool obeyed: the sections rendered, and the Visual Builder does not manage blocks outside its wrapper, so the client could not have edited them. Before/after on the wrapper now means inside it (the result says so); every normalising write folds stray top-level sections back into the wrapper; station_add_module returns the node_ids of what it inserted.
* THE WRITE PATH WARNED THAT htmlAttributes "DOES NOT RENDER". Measured false on 5.12: module.advanced.htmlAttributes.<bp>.value.{id,class} reaches the wrapper (the id when nothing else sets one), module.decoration.attributes renders any attribute and wins an id conflict. The warning and a migration that targeted a shape Divi never writes are gone; both forms are in the skill and pinned on a real render.
* DIVI 4 ICON STRINGS RENDER NO ICON. "&#xe0e5;||divi||400" stores, validates and draws nothing on Divi 5; the write rewrites it into the object Divi 5 reads (imageIcon.innerContent.<bp>.value.icon beside useIcon:"on" on a blurb; icon.innerContent.<bp>.value on divi/icon and icon-list-item) and the raw-markup audit names it.
* WARNING node_ids MATCH station_get_tree. Audits on unwrapped content counted inter-block whitespace as siblings and reported "node_id: 6.0.0.0" for what the tree calls 0.3; every path-reporting audit now parses raw content the way the tree will see it.
* THE PRESET STORE CAN CARRY A RECORD UNDER THE SENTINEL ID. A preset once created as "default" was never applied, could not be deleted, and sorted first in every list — three unrelated self-test pins picked it as their fixture and failed on 5.12. It is now listed as corrupt with the repair named, station_delete_preset removes it while it exists, and a pin plants and repairs one. Preset writes invalidate the read cache at the write.
* DIVI 5 CACHES A PAGE'S MODULE LIST IN POST META (Dynamic Assets) to decide which scripts to load; a slider or tooltip added over REST had no script until the builder saved. Every page write clears it. The compiled-CSS size probe counts every file in the page's et-cache dir — on 5.12 the module CSS is et-divi-dynamic-*.css and et-core-unified-*.css holds one rule.
* THE WORDPRESS SIDE OF A BUILD. Nine tools that end "ask the owner to do it in wp-admin": station_get_site_setup (title, tagline, what "/" shows and whether that page is published, menus with locations, every form the site already has); station_update_site_setup (title, tagline, front page — a draft is refused unless publish:true — with cache flush, touch-save and the previous values for undo); station_list_menus / station_set_menu (validated item tree, page items refuse draft targets, theme locations, mode replace/append); station_list_forms (Gravity Forms, CF7, WPForms, Ninja, Fluent, Formidable with the embed for each); station_search_media (title, file name, alt); station_check_links (every link and image on a page fetched from the server, sorted ok / broken / redirected / insecure / unchecked, cached a day); station_search_replace (dry by default, literal, refuses anything that could break block JSON, structure-verified, revision per page); station_set_structured_data (JSON-LD per page or site-wide, printed in wp_head, refuses invented ratings). station_set_post_fields gains seo_title and meta_description, written into Yoast / Rank Math / SEOPress's own fields or printed by this plugin when no SEO plugin is active. 98 tools: 50 free, 48 Pro.
* ACCESSIBILITY: the blank page template renders no title, so a landing page built on it is no longer told the template already supplies an H1; a page on any other non-default template gets no borrowed verdict. Widget semantics (Accordion/Toggle/Tabs) re-measured on 5.12.0: still absent, the finding stands and says so. Charts: the canvas carries role="img" and the chart title as its name — announced, data still pixels; the finding says that instead of "invisible".
* THE COLOUR RESOLVER REFUSES TO HAND OVER AN UNUSABLE VALUE: a role pair below AA is marked unusable_on_<role> with the best AA colour from this site's own palette suggested, never an invented one. station_set_colors on one of Divi's five default slots says label_ignored when the label had nowhere to go. The design-system sniffer reports heading roles that set a weight but no size instead of dropping them.
* SKILL: a new "dynamic" chapter (loop, dynamic-content and display-condition storage shapes copied from real markup, the REST vocabularies to ask, what is unchecked and why); ids and classes; icon objects; the form title's default H1; gradients on 5.12; font-variable edits; browser verification rules; self-test etiquette; the stale tool list after a plugin update.
* SELF-TEST: a new "Site plumbing" group (50 checks, every new tool called against the live install) and ~40 new pins across the findings above; the whole suite runs 1,344/1,344 on a block-editor sandbox and 2,359/2,359 on Divi 5.12.0 with the write path exercised (66 groups); the four preset-store failures the first 5.12 run surfaced are fixed and pinned. CI: the harness runner syncs its tests as well as the plugin; the compatibility sweep excludes the PHP 8.6 trim() form-feed sniff with the reason written beside it.
* BUILDER ADAPTERS (Phase A). One core, N builders: a DVC_Adapter contract (identity, capabilities, sniff, classify, page_model, template_models, design_tokens, resolve_colour, six write primitives, snapshot/restore, context), a registry that picks the site's builder by sniff score (dvc_adapter option overrides; forcing an absent builder is refused), a Divi adapter that is a thin delegate to the existing DVC_Divi code, and a rendered-HTML adapter for sites built with anything else that audits from the rendered page and refuses every write by name. Divi behaviour is unchanged — the Divi adapter calls the same functions with the same arguments, and the self-test pins page_model()->to_acc() strictly equal to DVC_A11y::collect().
* Tools the active builder cannot serve are dropped from the manifest instead of listed (a Divi preset tool on a Bricks site does not exist); calling one anyway returns dvc_tool_unavailable naming the builder and its own route. station_get_site_context carries an `adapter` block: id, label, version, capabilities, every sniff score, and the builder mix of the newest pages.
* The undo slot moved to DVC_Undo (DVC_Divi::preserve_prior / prior_undo delegate to it) so every builder's write path shares one step back. DVC_Probe::fetch() is the builder-agnostic front-end fetch.
* Self-test: new "Adapter contract" and "Generic adapter" groups. CI: adapter-parity and no-builder-boot harnesses.

= 1.9.5 =
* THE VALIDATOR PASSED A SHAPE THAT FATALED THE PAGE. divi/contact-field given an object where its fieldItem.innerContent value is a string validated clean, saved, and returned HTTP 500 to every visitor ("Expected a string value, but a array value was given"). From one agent's cold-start build of a 17-section landing page over the MCP (the field report is in the repo). 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 / render_error instead of warnings:[].
* A bare string on a blurb title (or button / image innerContent) stored fine and rendered no title — the renderer reads ['text']. It is coerced to {"text":…} on write with a warning; the schema response carries inner_content_shapes so the rule is visible: link-carrying innerContent is an object, heading/text/contact-field innerContent is a string.
* divi/blurb's known_paths advertised imageIcon.advanced.width.<bp>.value.image, which Divi stores and never reads; width renders from imageIcon.decoration.sizing.<bp>.value.width. The real path is in known_paths, the dead one is mapped under renders_from, and writing to it warns.
* station_get_module_schema returns roots (sizing_root, spacing_root, shadow_root, border_root) per module — the reason a shadow on divi/image belongs at image.decoration.boxShadow and its spacing under module.advanced.
* A divi/row with maxWidth and no width warns: rows default to 80% width, so maxWidth alone never applies.
* A FONT VARIABLE CREATED A FONT NOTHING ENQUEUED. station_create_variables type:font "Saira" resolved, printed --gvid tokens, and rendered as the OS serif — no @font-face, no Google Fonts link, and station_get_design_system called it active. Google families are now enqueued (Divi's own loader when present, else a raw <link> printed on wp_head at priority 1 so Divi's dequeue never sees it); the response carries font.enqueued and the method; the performance audit warns on a font variable no stylesheet backs.
* station_upload_media returned mixed http:// and https:// URLs on a site whose siteurl is still http — every returned URL is pinned to the scheme the request arrived on, and station_get_site_context reports the scheme mismatch.
* SVG is accepted as a source and rasterized to PNG at width px (Imagick; a clear dvc_svg_needs_rasterize error names the width when Imagick is absent). Raw SVG is never stored.
* station_create_page / station_set_post_fields accept template:"blank" (Divi's page-template-blank.php) — et_full_width_page only removes Theme Builder chrome, the theme's own header and footer still rendered.
* station_get_site_context.license carries activate_url so an agent can hand the owner the exact page; station_duplicate_page returns preview_url for a draft copy.
* Accessibility: a link that sets its own colour inline (style="color:var(--gcid-…)") is rated on that colour; a link with no readable colour is still rated on the global link colour but the finding says so and carries assumed:true. On a real build the nav links were flagged 2.75:1 against a colour they did not use.
* Skill text: string-vs-object innerContent, blurb sizing path, row width, icon-list-item alignItems, fonts, scheme, SVG, blank template, contact-form labels and select placeholder, no date field, <br> stripped from buttons, station_clear_css_cache after every station_update_page.
* Self-test: a new "Field report 1.9.5" group pins every item above.

= 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 — the normal state of a site with auto-continue on — each read the option, appended their pages and wrote it back whole, so the slower one erased the faster one's progress. On an 800-page store this showed as an audit that "stalled" at 48% for an hour and a diff that reported the previous run as never finished. Each group now has its own option and a 90-second lock; a second caller during a batch gets the summary back with busy:true instead of a second batch.
* GUARDIAN AUDITS PUBLISHED AND PRIVATE CONTENT ONLY. Drafts and pending posts were queued alongside published pages, so a store with 35 legacy drafts scored on pages no visitor can reach and a "fix these 35 pages" plan pointed at content the owner had already taken down. Drafts are excluded from the queue; the summary says how many were skipped.
* GUARDIAN ROLL-UPS ARE COMPLETE UNDER THE CAP. The "causes" summary was built from stored findings, so once the 600-finding cap dropped notes the cause counts were wrong and the diff against the previous run compared two truncated lists. Every finding is now tallied into per-cause counters as it is admitted or dropped, so occurrence counts, page counts and examples are complete however many findings the store keeps; the diff uses tallies when either run dropped findings and says so (basis:"causes").
* WooCommerce products are audited by the a11y, performance and SEO groups when WooCommerce is active; the Legal group runs as a single-shot site check instead of queueing every page.
* Legal: a store now requires a Refund and Returns Policy alongside Privacy and Terms — detected from WooCommerce or a Divi payment button — and the installer can write one with [REVIEW] markers. Document resolution prefers the page WordPress or WooCommerce designates, then a published page by slug or title, and only then a draft, so a published privacy policy is never out-ranked by an older draft with the same title.
* Rescue scans reachable content only and reports drafts_skipped; orphan-shortcode pages are counted per shortcode tag.
* a11y: a page whose template renders the title as H1 no longer carries both "N H1s" and "renders with N+1 H1s" for the same cause.
* SEO: product excerpts count toward word count; cart, checkout and account pages get a single "functional page" info instead of thin-content warnings.
* Contrast, a11y, SEO and performance audits declare divi/woocommerce-* modules and form shortcodes ([gravityform], [contact-form-7], [wpforms], [ninja_forms], [fluentform], [formidable]) 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. The result now lists them under unresolved with the reason, and the note says to audit that page in a browser.
* SITE QA CALLED DIVI'S OWN WRITES INVALID. A page the Visual Builder had saved carried themeBuilderArea on every block, the Custom CSS group (css) on a column, and divider hand-off colours on converted sections; none of those keys are in any module's harvested schema, so the audit filed 34 "not an attribute group … renders nothing" warnings against Divi's own storage on one page, and a video module's overlay image ("thumbnail", declared through attrName rather than as a map key) got the same. Builder-written keys are accepted, and every attrName head in the panel map now counts as a group.
* SITE QA FILED EVERY BLOG POST AS A WARNING. A post written in the block editor is the normal state of a blog — it renders through the Theme Builder post template and the client edits it in the post editor — but the "not-divi" check warned on each one: 709 warnings on a 749-item site, drowning 40 real page findings and pushing the run past the findings cap. Posts now get an info (not-divi-post); pages keep the warning.
* The 1.9.4 self-test's Guardian pins read the per-group archive before the lazy migration had split the legacy one, so on restore they deleted the archive the migration had just written and the next Site QA run reported first_run. The pins migrate first now.
* SEO: a WooCommerce product with no description was a "Only 0 words of readable text" FAILURE with a fix that talked about page rebuilds; 27 cohorts on one store. It is now a warning that says the product has no description and points at the product's Description and Short description fields.
* Guardian cause grouping: a quoted title containing its own quotes ('THE POWER OF "NO"') split one cause into several; the parenthesised title is collapsed first.
* station_update_page wrapped block-editor content in Divi's placeholder block, so a page with no Divi block in it read as a Divi 5 page without builder meta and Site QA failed it. Only Divi content is wrapped now, and the builder meta follows the content on update (set when a Divi layout is written, cleared when block-editor content replaces one) — the result says which.
* A running Guardian batch echoed the whole cause table with every page id plus forty findings on every call — 120 KB a call, twenty calls a run, on an 800-item store. A batch now carries the causes with counts and one example, only the failures inline, and the page ids arrive with the finished run.
* DIVI_RUN_SELF_TEST NEVER SAID IT HAD PAUSED. The suite has paused cleanly between chunks since 0.74.0 and recorded that fact, but the MCP tool never read it: a paused run came back as {total:242, fail:0, "All checks pass"} with no status field, so an agent reading it as a verdict had 242 of 1,677 checks. The result now carries status (running|finished) and progress, and the note says PARTIAL until the last chunk.
* Self-test: Guardian probes are created private and the QA run archive is saved and restored around the Guardian pins; four new pins cover the WooCommerce and form-shortcode signals and the contrast engine's use of the shared one.

= 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 (Converge's does), 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. station_offer_page mints a single-use ticket the target site redeems anonymously; the serve leg then re-ran a per-user read check that user 0 fails on every post, so the first real pull answered 403 on a published page and burned the ticket with it. The ticket is now the read authorization it was always meant to be (minting already proves read access as the authenticated caller), a bare anonymous export still refuses, and four self-test checks run the serve leg AS user 0 — mint, pull, checksum, replay-refused.
* Client reports no longer cry wolf. The since-last-run delta counted info-state notes — audit blind-spot disclosures whose own body says "Nothing to fix here" — as issues, so a report could open with "1 new issue(s) appeared" over a note. Only failures and warnings move the fixed / new / persisting numbers now; notes still appear in the findings list. Three self-test checks pin it.
* station_delete_token's refusal now names the posts that reference the token instead of saying "retarget them first" with nowhere to look — ten refusals in a row traced to a single draft style-guide page prompted that one.
* 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. station_create_theme_template accepted the documented short conditions ("singular:post") and stored them verbatim; Divi kept them happily and resolved none of them — the template sat "Unassigned" in the Theme Builder UI and never matched on the front end. Conditions are now translated to the setting ids Divi resolves (singular:post becomes singular:post_type:post:all), real ids pass through verbatim, and an id this install cannot resolve is refused with the real list instead of stored as a dud.
* OMITTED SLOTS INHERIT THE GLOBAL CHROME. A body-only template used to store "no layout" for header and footer, so Divi answered with the THEME's stock header on every page the template matched — silently replacing the site chrome. Empty slots now inherit the default template's layouts (the Theme Builder UI's "Use Global Header"), and deleting a template with its layouts can no longer trash a layout another template still references — which, with inheritance, could have been the site's global header.
* Loop Builder fields (loop_post_title, loop_post_link and friends) are now recognized as dynamic content by the token audit. The pattern the Loop Builder requires — a $variable embedded in markup — warned "Token id does not exist and will resolve to nothing" while resolving perfectly. Found building this plugin's own blog index.
* Nine new self-test checks pin all three: condition round-trips are asserted in Divi's vocabulary against the live settings options, an omitted slot must read back the global layout, a deleted template must leave shared layouts standing, and loop fields must pass the token audit.
* The fixture reclaim from 1.8.1 now covers design tokens too. An aborted self-test run leaked its 'unique' test color; because every leaked copy carries the same fixture value, the next run's tokenize check saw an AMBIGUOUS literal and the whole group false-failed permanently. Token, preset and probe-page fixtures are now reclaimed on entry — found on this plugin's own marketing site, three leaked colors deep.

= 1.8.1 =
* GUTENBERG AND CLASSIC CONTENT ARE NOW READ BY THE AUDITS. The content collector only understood Divi modules: core blocks keep their markup in innerHTML, which was never scanned, and classic-editor content was skipped outright. A Gutenberg post therefore measured zero headings and zero internal links — and the SEO audit told the owner it "links to no other page on the site" about posts full of links. Found dogfooding this plugin's own blog on the day it launched.
* The collector now runs non-Divi block innerHTML and freeform chunks through the same HTML scanner the Divi text modules use, so headings, links, images, iframes and inputs in posts count everywhere downstream: the SEO internal-link check, the accessibility heading, alt and link-text checks, and the client report.
* Five new self-test checks pin it: a core/heading counts as a heading, a core/paragraph anchor is harvested with its href and classifies as internal through the exact counter the audit uses, and classic content is scanned rather than skipped.
* The self-test now reclaims pattern fixtures a killed or concurrent run left behind, so leaked QA debris can no longer fail every later run with "a pattern with id dvc-qa-copied already exists" — found the same evening, on our own second site.

= 1.8.0 =
* THE LAST TWO ITEMS FROM THE CUSTOMER PANEL, SHIPPED. The panel's top all-panels frustration — "I fixed a finding and the tab badge still said ×5" — and its Agency ask, white-label reports, were the two open rows on its build list. Both close here.
* A LANDED FIX RE-CHECKS ITS PAGE. Every fix door — single fix, review applies (alt, CTA, contrast), Fix All, and undo — now re-audits the touched page inside the stored run: findings swapped, counts corrected, waivers re-anchored, and the rail badge updated from the response. The fix message says what the re-check found instead of sending you to re-run a forty-page audit. Fix All re-checks inside a time budget and says plainly when the remainder waits for the next run; score HISTORY still only records at finished audits, so the series stays a record of measurements.
* WHITE-LABEL CLIENT REPORTS, on every paid plan — the plans differ by seats, and that stays true. Settings → Client reports takes your business name and a logo from the Media Library; the report header and footer then carry your brand and drop ours. The logo is embedded into the report file itself, so the document keeps its no-external-requests promise offline and in email. The engine version stays in an HTML comment for support, and the Divi trademark notice stays because the content still names Divi.
* Eleven new self-test checks pin both features, including that a re-audit never writes score history and that a white-labelled report drops the product attribution without dropping the trademark notice.

= 1.7.3 =
* LICENCE SEATS ARE NOW RELEASED ON UNINSTALL — the oldest documented open bug is closed. WordPress runs a plugin's uninstall.php INSTEAD of any registered uninstall hook, so shipping that file silently stopped Freemius ever being told the plugin was removed: delete the plugin, reinstall on another site, and a single-site licence was already spent. Freemius' deployment checker refuses zips carrying the file for exactly this reason.
* The full cleanup sweep (credentials first, multisite-aware, audit table, report files) moved from uninstall.php into DVC_Uninstall, registered on Freemius' after_uninstall action — so the SDK releases the seat, then the sweep runs. When the SDK is absent, a register_uninstall_hook fallback keeps the sweep; the multisite walk now also clears prefixed transients per subsite, which the old file only cleared on the main site.
* The packager now refuses any build containing uninstall.php, and the self-test pins 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's own test fixtures were being audited as the customer's content. Four Guardian probe pages stranded by runs the 1.7.1 lock bug killed were titled outside every swept prefix — so the SEO audit reported "only 3 words of readable text" and Site QA reported a broken attribute path, on pages the customer never made. Both audits went clean the moment the debris was removed.
* The probes are retitled under the swept 'dvc qa ' prefix, the old name is legacy-swept so pages leaked by earlier versions are reclaimed on the next run, and the fixture closure now documents why every title it takes must stay under the prefix (it is invisible to the sweep-coverage scan).
* Two new self-test checks pin the prefix and the legacy sweep.

= 1.7.1 =
* FOUND DOGFOODING ON WP ENGINE, FIXED SAME DAY. On hosts with a persistent object cache (WP Engine, and any memcached/Redis drop-in), a chunked self-test could pin itself at the same cursor forever: the run lock was a transient with a TTL, which on such hosts lives ONLY in the cache — never the database — so routine cache eviction erased the lock mid-pause, and the resume path reaped the paused run as abandoned. Every continue call restarted, re-ran the same twenty seconds, and was reaped again.
* The lock is now a DB-backed option with its own expiry stamp, and every read and write of the lock AND the paused-state option flushes the object-cache key — the host's cache can neither evict the run nor serve it stale.
* This is the cross-request half of a lesson the code had already learned in-request (0.61.1, same host): shared state you cannot afford to lose belongs in the database, read back from the database.
* Uninstall sweeps the lock in both its lives; three new self-test checks pin the DB backing, the cache-busting, and the cleanup.

= 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, quiet while recent, 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 HTTP connector holds no socket open, so there is no true "connected right now" — the pill shows the one thing the server actually knows, the time of the last authenticated call, and never pretends otherwise.
* The timestamp is a durable latch recorded beside the existing first-contact latch on every authenticated request (throttled to one write a minute), so it survives the 200-row activity log aging out or being switched off. The Connection tab's status line reads the same latch and no longer claims a bare "connected" with no time on long-idle sites.
* Uninstall removes the latch; new self-test checks pin the recorder, the reader, the pill states and the cleanup.

= 1.6.1 =
* THE ADVERSARIAL AGENT SWEEP, ROUND TWO — four independent review agents against the whole surface; twelve confirmed findings, all fixed same-day.
* PAYWALL: a crafted request could run a Pro site-level fix (noindex, permalinks, template images) on Free by riding any page id past the gate. Both doors — wp-admin AJAX and the MCP tool gate — now ask the fixer's own dispatch whether a check is site-wide.
* FIX ALL: an apply must now present the plan token of the plan it displayed; the 1.4.2 allowance for a token-less apply protected nobody (the script cache-busts on every release) and let the staleness check be skipped.
* TOKENIZE: the verify step scanned the whole page for swapped literals while the swap correctly touches only block-attribute JSON — a page that also DISPLAYS one of its colours (a code sample, a style guide) rolled back a clean write with an error, every time. The verify now reads exactly what the swap wrote.
* CONTRAST: rgb() channels above 255 clamp to what the browser will actually paint — an overdriven "white" could previously rate above real white and slip a below-AA colour past the review's gate.
* TYPE SPECIMENS: the mined-convention and token phrasings now render specimens too — the 1.6.0 regex only matched the adopted-design-system phrasing, so the most common source of this finding drew nothing.
* SEO LENGTH CHECKS measure the decoded title and description — the same ruler as the search preview — so an &amp; no longer counts as five characters and the check can never contradict the card beneath it.
* SCORE DELTA: "since X ago" now reads the score history's own clock; on a non-UTC site it was off by the site's whole UTC offset.
* FIX BUTTONS SURVIVE ERRORS. A network blip or an expired session removed the control until a full page reload; every error path now restores the button with the reason beside it. Server refusals that spoke a different field name now reach the UI instead of a generic fallback.
* REVIEW PANELS load once — toggling a panel mid-load no longer fires a second fetch that wipes what you were typing — and one audit chunk loop runs per screen, so Continue and Start over can no longer race the same cursor.
* FIX-ALL PLAN AND APPLY AGREE ON ARITHMETIC: the tokens plan counted distinct literals while the apply counted occurrences — "1 change" planned, "12 changes" written, for identical work.
* The review panels' fifteen hardcoded strings now pass through localization like everything else.
* New self-test group, Agent sweep, pins every one of these — behaviourally where cheap, by source needle where not.

= 1.6.0 =
* EVERY AUDIT SECTION SHOWS ITS FINDING, NOT JUST DESCRIBES IT. The alt-text and contrast reviews set the pattern; the rest of the panels catch up.
* SEO TITLE AND META FINDINGS RENDER THE SEARCH RESULT. "Title is 74 characters" becomes the actual result card with the last 14 characters dimmed exactly where Google cuts them — the same effective title and description the checks read, so the preview and the finding can never disagree. A missing title or description says what search engines will do instead.
* PERFORMANCE FINDINGS SHOW THE IMAGES. "3 image(s) over 300 KB: hero.jpg…" opens itself into the actual images — thumbnail, weight and width badges — with hotlinked or unmatched files listed honestly beside the reason nothing can compress them.
* HEADING FINDINGS SHOW THE PAGE'S OUTLINE. Every heading in document order, indented at its level, with a "level skipped" flag exactly where the jump happens and "extra H1" on every H1 after the first — the picture the heading checks were describing all along.
* TYPE-SCALE DRIFT RENDERS THE TWO SIZES AS TYPE. Aa at 19px beside Aa at 40px, so the change is seen before it is applied; display capped, labels truthful.
* A COLOUR NAMED IN ANY FINDING SHOWS ITSELF. Every hex in every finding carries a small swatch dot painted that exact colour.
* New self-test group, Section visuals, pins each of these against fixtures — truncation points, outline flags, hotlink refusals, size caps.

= 1.5.0 =
* FIX ALT TEXT WHERE YOU SEE IT. Missing-alt findings no longer hide the review behind a click: the panel opens itself, so the row arrives with the image thumbnail, the best suggestion pre-filled, and the input ready — describe the picture and Apply, right there. The Review images button becomes a hide/show toggle, the Open in builder link stays for anyone who wants the full page around the image, and a page with many findings loads the first dozen and opens the rest on click.
* EVERY AUDIT PILL SAYS WHEN IT LAST RAN. "Should I re-run?" used to mean reading one sentence inside one section at a time; now each of the six pills carries a compact age — 35m, 3h, 6d — with the full time in the tooltip. A run older than a week, or an audit never run, shows amber, because that is the point at which the counts on the pill stop describing the site.

= 1.4.3 =
* The contrast review's suggested-swatch caption failed contrast on light backgrounds — the label sits on the pair's own colour, so it now rides in a solid dark chip that is legible on anything. The one screen about contrast no longer fails it.
* The type-scale finding leads with a human sentence ("Fix snaps this heading back to the size named above") so Simple mode has something to say before the tool path.

= 1.4.2 =
* THE QA AGENT SWEEP, FIXED SAME-DAY. Three independent adversarial reviews ran against the 1.4.x code; thirteen findings survived verification and every one ships fixed here.
* Contrast review: BOTH halves of a pair are identity now — an approval made for one text colour refuses to overwrite a colour an editor chose after the list was made (the background already refused; the foreground did not). Colour parsing is anchored, so a value with trailing junk after a valid colour is no longer "a colour"; and alpha detection now knows every alpha spelling (space syntax, percent), so a translucent replacement can never be rated as opaque, pass AA, and silently make the page worse.
* Site QA: the broken-token FAILURE cause no longer draws the tokenize button — an invented token id needs a human to pick the real token, and the row now says so instead of dead-clicking or claiming success for work it never did.
* Legal: the publish guard also recognises WordPress core's own auto-created Privacy Policy draft ("Suggested text:" sections) and refuses to publish template boilerplate under your business name.
* Fix All: the confirm click is cryptographically bound to the plan it displayed — if the stored audit changes between plan and confirm, the write is refused and the button re-plans. A refused site-wide fix no longer renders in success green, an error no longer strands the plan list on screen, and the plan's aggregate now counts what it found instead of reporting zero.
* Score delta: a movement caused purely by waiving findings now says "waived findings N → M" instead of blaming coverage.
* Simple mode: two design tokens in one sentence no longer swallow the words between them; findings on draft pages link to the editor (the Visual Builder front-end link 404s on drafts); the plan-confirm strings joined the translation table.
* Nine new self-test pins hold all of it.

= 1.4.1 =
* Design → This site's system: a SHADOW role's value rendered as its raw JSON in a cell that could not wrap, which made the whole adopted-roles table wider than its card and pushed the Override column off the screen for every row. Shadows now read as the shorthand a designer knows — offsets, blur, spread, colour — with the exact stored object in the tooltip, and the value cell is allowed to wrap. Caught live minutes after 1.4.0 shipped; render-only, no behaviour changes.

= 1.4.0 =
* EVERY AUDIT PANEL CATCHES UP TO DESIGN GUARDIAN. A panel review of the product asked one question of each audit: can I understand what is wrong AND fix it without leaving the panel? Guardian answered yes; the other five now do too.
* CONTRAST GETS A REVIEW SCREEN WITH SWATCHES. Each failing pair renders as it actually is — the text colour on its real background — beside the nearest colour from this site's OWN palette that clears WCAG AA, as a second swatch with its ratio. Approve it, or type your own; typing previews live. An approved suggestion writes the design-system TOKEN, not another literal; a replacement that would itself fail AA is refused with the ratio it would have had; the site-wide Link Color pairs say why one module cannot fix them. Journaled and undoable like every write.
* SITE QA'S "HARDCODED VALUE" WARNINGS GROW A FIX. One click routes the page through the tokenize engine: every literal matching exactly one design-system colour becomes that colour's token, verified literal-for-token with zero modules lost, prior content preserved. Ambiguous literals — one value, several tokens — 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; the button re-arms as the confirm, and only the second click writes. The engines have planned like this since 0.69.0 — the button just never showed anyone.
* OPEN IN BUILDER, EVERYWHERE. Every finding row (and every single-page cause) links straight into the Visual Builder on the page it names, so "Node 0.4.1.0.1" stops being a coordinate you cannot visit.
* SIMPLE MODE FINALLY REACHES THE FINDINGS. In Simple view, token JSON reads as its label ("use the DA Sky token") and sentences that speak tool — station_edit_module, attribute paths — step aside; Advanced shows the engine's exact words, unchanged. Nothing is ever hidden entirely.
* THE SCORE DELTA EXPLAINS ITSELF. "−1 since yesterday" is now a disclosure: open it and it names which audits moved — failures and warnings, from → to, per audit — from per-suite snapshots the score history now keeps. An audit counted for the first time says so, rather than reading as new damage.
* LEGAL DRAFTS PUBLISH FROM THE PANEL — with the one guard that matters: a document still carrying [REVIEW] placeholders is refused, because a live legal page with blanks in it is worse than a missing one.
* Site-level findings with no button get a door instead of a dead end (e.g. "no SEO plugin" links to the plugin browser).
* New self-test group, Panel catch-up: refusals first (still-failing colours, changed backgrounds, unknown documents), then the writes, then the undos.

= 1.3.10 =
* ALT TEXT GETS A REVIEW SCREEN. The automatic fix fills from the Media Library and refuses to invent — correct, and it left the images that most need a human (no library alt, external file, renamed upload) as unactionable "skipped" rows in a response nobody reads. The audit's missing-alt findings now carry a Review images button: each image is shown in the row, the best default is pre-filled where one exists, and the human approves it or types their own. Writes go through the same journaled, undoable path as every other fix, and an approved item is refused if the image changed since the list was made.
* DESIGN GUARDIAN GROWS FIX BUTTONS — all four checks. The audit as it stood was warnings that never moved and never offered a hand. Missing and duplicated H1s route to the headings engine, which already promotes under guards and demotes duplicates. Auto-rotating sliders get autoplay switched off in stored attributes — arrows, dots and swipe all keep working; the slider just waits for the visitor. Heading sizes drifted off the site's own resolved type scale snap back to it, against the same adopted/token/mined sources the check fires against and never the shipped default.
* Generic CTA copy is review-only, deliberately. No rule writes a good button label, so the one-click path writes nothing and says so; Review copy shows each button, its current text, where it links, and a mechanical suggestion (an internal link becomes "See {that page's title}") for the human to approve or rewrite. A replacement that is itself on the generic-copy list is refused — a fix that re-flags on the next audit fixed nothing.
* Review writes carry every gate a Fix click carries — the free tier's bound page rule included — because a review screen must not become the door the paywall audit keeps finding open.
* Guardian fixes journal under taste, not perf, so the journal reads as what happened.
* New self-test group, Review fixes: the list offers what the page really holds, empty and stale items are refused with reasons, the CTA one-click stays inert, and every written fix undoes.

= 1.3.9 =
* THE PAYWALL COULD FAIL OPEN, and it was a fix in 1.3.8 that did it. 1.3.8 made DVC_License::free_page() heal a binding whose page had been deleted, so a stale binding would stop refusing every write for a reason nothing on screen explained. That read is what page_gate() calls on every single tool call, and page_gate does no existence check by design — it compares ids. So a binding whose page could not be resolved became NO binding, and the gate waved all 21 page-scoped tools through onto any page. A paywall that heals itself into having no limit is worse than the stuck state it was fixing: it fails open, and silently.
* The self-test caught it on the first run after the deploy — eight failures in Licensing & tiers, including "a second page is refused" reporting NO ERROR — FREE TIER REACHED A SECOND PAGE. It never reached a customer. This is the whole argument for the suite existing.
* free_page() is a pure read again. The healing moved to the Overview screen, where a human can see it happen: a binding pointing at a deleted page now says so, offers to release it, and does not charge that release against the three-rebind cap — charging someone for a mess the plugin made would be its own small insult.
* The two new wp-admin paywall pins from 1.3.8 had windows too tight to survive the comments explaining them, and reported a false failure. Widened. A pin that fails because somebody documented the code is a pin that gets deleted.

= 1.3.8 =
* LICENSING PASS BEFORE LAUNCH. Two paid capabilities were reachable without a licence, and the limit the whole Free tier rests on had no floor under it.
* THE CLIENT REPORT WAS ONE UNGATED CLICK. DVC_Report::download() had a capability check, a nonce and no licence check at all, and the button was drawn on every one of the six audit screens, on every tier, in the default view. station_export_report was gated correctly; this produced the identical artifact through a different door. Free deliberately gets the whole site-wide audit, so a Free install had complete findings and one click turned them into the branded deliverable an agency resells. This is the seventh leak of the family 1.3.3 closed six of.
* The wp-admin door is now covered as a CLASS in the self-test, which is why the seventh survived: 1.3.3 fixed six handlers one at a time and nothing pinned the shape of the problem. The licensing group now asserts that every paid admin action carries its guard, that the report download is gated, that the report button is not drawn to a tier that cannot use it, and that fixing and restoring in wp-admin both respect the free page binding.
* THE FREE PAGE WAS A REVOLVING DOOR, AND THE REFUSAL EXPLAINED HOW TO USE IT. Releasing the binding was a bare delete with no counter and no cap, and the message the agent receives named wp-admin as the place to do it — so Claude relayed it as the sanctioned remedy. Release, build the next page, release again. That leaked far more than the eleven automated fixes: station_create_page and every surgical editor are page-scoped free tools, so it reopened the exact hole page_gate() was hardened to close, at one click per page. Rebinding is now capped at three — enough for someone who bound the wrong page on their first call, nowhere near enough to walk a site — and the sentence naming the workaround is gone.
* Restoring a page revision from wp-admin ignored the free page binding, while the same capability was bound on the tool surface. Little revenue rode on it, but an invariant that holds on one door and not the other is not an invariant.
* THE SEATBELT CAME BACK OUT FROM BEHIND THE PAYWALL. 1.3.3 moved station_restore_snapshot to paid, reasoning that a free install cannot change the design system so has nothing to restore. True in steady state, false in the two cases where a rollback matters most: a trial grants full Pro, so a trialist can rewrite the whole palette and then drop to Free holding snapshots they can no longer use, and a lapsed customer is in the same position — while taking a snapshot stayed free the entire time. A pure rollback can only return state the site already had and nobody buys a licence for it. Restoring the design system from wp-admin, and releasing an adopted one, are free again for the same reason. The real 1.3.3 fix is untouched: it is still in DESIGN_TOOLS, so "Protect the design system" still covers it. Free tools are 43 again, Pro 43.
* The scheduled-audits form rendered in full on Free and refused at the save, discarding what had been typed. It is now a locked card that says it is Pro before the effort, the same treatment Fix-all already had.
* Client report links expire and nothing said so. Every save prunes reports older than 14 days, so a monthly schedule was deleting last month's report — including the link an agency had already forwarded to a client. The result and the digest email both say so now.
* A lapsed licence left the schedule toggle showing ON beside "Nothing scheduled", and the activation hook re-armed the cron on any deactivate/reactivate cycle. The state is written down when the licence check unschedules it.
* A free page binding pointing at a deleted page refused every write with no visible cause. It now heals itself, and costs no rebind.

= 1.3.7 =
* FIRST-RUN PASS. Somebody who has just installed this and has no idea what MCP is was walked through the plugin screen by screen, and every place they got stuck was fixed.
* The Connection screen now states the prerequisite nothing stated: you need a Claude account on a plan that can add custom connectors. Not the plugin, not either readme and not one page of the marketing site said so, so the failure mode was buy it, install it, read a green "Ready", follow all five steps, and find out at step two that the account cannot do it — having been told the whole way that everything was working. It links to Claude's own page rather than naming plan tiers, which change.
* "Didn't attach? Run the connection checks" now sits under the last step, and only while the site has never been connected. Diagnostics is Advanced-only by design, and Simple is where a new install starts, so the one person who needed the connection checks was the only one who could not see them. The button promotes the view and lands on the checks in a single click.
* Connection reports whether Claude has actually called in, and when. "Endpoint live" only ever meant the REST route answers; the plugin already knew the difference — it is what the first-flight checklist reads — but the fact was not on the screen where somebody had just pasted a URL and wanted to know if it worked.
* The audit opens the most severe group you actually have, instead of only opening failures. Grouping by cause rather than by page is the best idea in this product, and on a site with warnings and no failures every group rendered closed, so a first-time visitor saw one line of small text above two shut rows and no hint a Fix button lived three levels down.
* "How this is scored" on the Overview score card. The reasoning — a failure counts as six warnings, accessibility and legal weigh half again for liability, design taste weighs half because its findings are opinions — was written out in full in station_get_score's description, where only Claude ever read it. The person looking at the number was the one not being told.
* The waived-findings line read like a rendering bug: "5 findings are not counted in this score (5 by design)" is the same digit twice in a sentence that seems to repeat itself. It now names the mechanism once, says who did it, and links to the findings.
* The safety switches no longer look saved when they are not. Their Save button sits at the top of a card you scroll down through, so flipping "Read-only mode", watching it animate and navigating away was easy — and those switches decide what an AI may do to a live client site. The button is inert until something changes, and leaving with changes pending asks first.
* The rescue fix plan's first line told the agent that station_snapshot was the rollback point every later step assumed. It is the design-system snapshot, not page content. It now says what it does cover, what it does not, and that neither replaces a real backup before a migration that size.
* "Rebuild index" says what index. The design counters are read live from Divi on every render and that button has never touched them; it re-reads Divi's module definitions after a Divi update.
* Dropped "MCP" from the first sentence of the Connection screen. It was the first acronym a Divi customer met and it was expanded nowhere in the product.

= 1.3.6 =
* THREE WRITES THAT KEPT NO STEP BACK, one of which said it did. 1.3.4 closed this hole on the two page-content writers and did not sweep the rest of the file.
* station_repair_preset_tokens with scope=tokenize rewrote colour literals across every module on a page, called wp_update_post() directly, and returned "A revision holds the prior content." There was no preserve_prior() and no wp_save_post_revision() in the function at all. False on every site, not only the ones with revisions disabled, and it is the widest-blast-radius page write in the plugin. It now preserves first and reports which kind of step back it made.
* Theme Builder slot writes and Divi Library writes called a bare wp_save_post_revision(), which does nothing on a site with WP_POST_REVISIONS off. A header write there had no undo of any kind — and one bad header write is every page on the site. Both now go through preserve_prior(), and both results say where the step back is. A global Library layout write, which changes every page referencing it, was the highest-blast-radius write in the plugin with no fallback behind it.
* The self-test now says when a run was short of full coverage. Four groups — Accessibility engine, Theme Builder, Divi Library and Content coverage — need Divi's builder framework, which wp-admin does not load, so they are skipped there and run through the connector. Both surfaces already behaved correctly and each 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 on screen explaining the ~180 difference.
* The rescue scan's page lists are capped at 40 per bucket while its counts are complete, so a site with 200 Divi 4 pages was told 200 and handed 40 ids with nothing saying so. The result now reports lists_capped with the shortfall per bucket and what to do about it. The existing scan_capped covers the 300-page scan cap and was being read as covering this one too.
* "Every finding has a one-click fix" on the first-flight checklist. There are 83 check types and 11 of them have a Fix button. That is step four of the checklist an evaluator reads in their first ten minutes.
* Two strings still described tier gating as shipping OFF, which stopped being true in 0.90.0. One of them is in the licence status payload that station_get_site_context returns, so Claude was being told the paywall was unwired at the start of every session.
* The fixes tile named alt text and headings and hid the other nine fixable checks — image compression, page weight, and the SEO meta and noindex fixes are all journaled and individually undoable too. 1.3.5 over-corrected this one.
* The connector URL is described honestly: it survives updates and deactivation, but uninstall.php deletes the key by design, so deleting the plugin does issue a new URL.
* Corrected against the code: the undo journal keeps 100 fixes, not 30; the rescue tool description said "classifies every page" where every other surface says up to 300; the design-system sniffer discloses its 300-item cap to the agent, not only in its output; the rescue tile read as though the scan converts pages, when it produces a plan Claude then works through; four more tool descriptions promised a revision where preserve_prior() may have used the undo slot instead; the WP BaseStation Site Score rename reached station_get_score, both readmes and the onboarding checklist; and the checklist said six auditors read every page where five do and legal reads the site.

= 1.3.5 =
* The score is now labelled "WP BaseStation Site Score" on the Overview dashboard. "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 just corrected on the marketing site, which is worse — this is the copy an evaluator reads while deciding whether to trust the thing. All four are now what the code does: fixes say alt text and headings are journaled and undoable while legal pages arrive as drafts to review; the safety-net tile says one step back before every page write and a snapshot before every design-system write; rescue says up to 300 pages instead of "every page"; and the audit tiles say five auditors read every page while legal reads the site.
* Same correction on the Audit and Rescue screens' own intro text.

= 1.3.4 =
* FIXED, and it is the one that matters most: the step back kept for sites with WordPress revisions disabled was written and unreachable. preserve_prior() stashes the outgoing page content in post meta when wp_save_post_revision() will do nothing — WP_POST_REVISIONS off, which several managed hosts ship and which every "optimise your database" plugin offers — and then told the operator to restore it with station_restore_revision using revision_id 0. There was no branch for 0. get_post(0) is null, so the answer was "Id 0 is not a revision." prior_undo(), the function that reads the slot back, had no callers anywhere in the plugin, station_list_revisions never listed it, and the dashboard never rendered it. On exactly the sites least likely to have another backup, the recovery instruction was false.
* station_restore_revision now accepts revision_id 0 and restores the undo slot, 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, id 0, first, with its module count and its own note explaining what it is — an unexplained row in a restore list is worse than no row. The response also reports has_undo_slot and whether this site stores WordPress revisions at all.
* Restoring is now undoable on both kinds of site. 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. It goes through preserve_prior() now, and says which of the two kinds of undo it made. Restoring the slot twice toggles rather than repeating.
* History in the dashboard shows the undo slot with an "undo slot" pill, and the confirm text no longer promises a revision it may not be able to save.
* Three tool descriptions and the History screen's own note claimed "every write saves a revision first". True on most sites, false on the ones this feature exists for. All now state what actually happens.
* 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 =
* SECURITY/LICENSING: the paywall was enforced on the MCP tool surface only. Six capabilities sold as paid were one ungated click away in wp-admin — fixing a cause across every page, scheduled audits and the emailed client report, installing legal drafts, adopting a design system, overriding a design role, and restoring the design system. All are now gated on both surfaces, 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 same rule the tool surface applies. Previously any page could be fixed from the audit screen regardless of the binding.
* station_restore_snapshot joins the design-system tool group, so "Protect the design system" now covers it. It rewrites colours, variables and presets site-wide and was the one write that switch could not see. It is now a paid tool, alongside the rest of the design system.
* station_snapshot, station_restore_snapshot and station_check_legal have left the page-scoped free list. None of them accepts a page argument, so the page gate could never bind them — membership read as a guarantee and was decorative. The self-test now asserts against each tool's real schema instead of a hand-built argument array, which is why this went unnoticed.
* Free tool count is 42, not 43 (station_restore_snapshot moved to paid). The readme said 43 and the README said 42; both now say 42.
* One icon set across the plugin and the marketing site. The dashboard's ten capability icons were Unicode glyphs from six different blocks, two of them emoji codepoints that the operating system rendered in colour while the rest stayed monochrome — so the same screen looked different on macOS, Windows and Linux. All are now inline SVG 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. The capability map below it is a catalogue; this is the direction.

= 1.3.2 =
* Fixed: every surgical write — station_edit_module, station_add_module, station_move_module, station_delete_module — was returning the whole page's token digest, not the warnings for the node you just wrote. On a real build, forty consecutive edits each came back with the same eleven hardcoded-value warnings and their full node lists, unchanged. That is tens of thousands of tokens of an agent's context spent restating what it was told on the previous call, with the one warning that belonged to the edit buried in the middle. station_update_page was deltaed when this was first diagnosed; the surgical writers, which are the ones called in a loop, were not.
* New warnings still come back in full. Pre-existing ones collapse to a single counted line naming station_validate_markup for the whole list. A page with no prior content — station_create_page — still returns everything.

= 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 =
* 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 =
* 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).

= 0.98.x =
* Unreadable-content architecture: audits declare what render-time modules they cannot read instead of reporting false counts or false cleans; API-key leak in page rendering closed.

= 0.91–0.97 =
* Site score with disclosed arithmetic, per-finding owner waivers, first-flight onboarding, auto-continuing chunked runs with progress.

== Upgrade Notice ==

= 1.9.5 =
station_validate_markup passed a contact-field shape that returned HTTP 500 to visitors; font variables did not enqueue their font. Update; page writes now report render_ok.

= 1.9.4 =
Guardian audit groups could overwrite each other's progress and audited drafts; stores get a refund-policy check and WooCommerce/form content is declared unreadable instead of passing silently. Update and re-run Site QA.

= 1.9.3 =
On sites with more than 600 audit findings, failures on the newest pages could be silently dropped from the stored Guardian run. Update and re-run Site QA.

= 1.9.2 =
WooCommerce safety: station_build_woo_page now plans by default and refuses to replace a cart, checkout or shop page with a layout that would stop it working; myaccount is refused while Divi has no account module. Rescue and Site QA no longer advise rebuilding WooCommerce's own pages. If you automate store builds, add apply=true to your calls.

= 1.9.1 =
Fixes two 1.9.0 defects found on a live WooCommerce store: the compiled-CSS size probe looked in the wrong directory (so the truncation warning could never fire), and the markup validator flagged Divi's own WooCommerce checkout styling as invented. Update if you are on 1.9.0.

= 1.9.0 =
New station_clear_css_cache recovers a site that renders unstyled after Divi writes a truncated CSS file — previously only a human could fix that. Project memories that overflow the injection cap are no longer dropped silently: the cap is raised, an overflowing save is refused by name, and station_list_memory says plainly when memories are not reaching sessions. Run station_list_memory once after updating to see whether this site was already losing rules.

= 1.8.4 =
Connection screen cleanup: the connector URL had two homes with two drifting sets of instructions. The Connection tab now owns the how-to, Overview keeps just the URL and a copy button, and the troubleshooting prose is folded behind one disclosure. No functional change to the connector itself — your existing URL keeps working.

= 1.8.3 =
Cross-site fix found by dogfooding: the station_offer_page pull handoff answered 403 on every real site-to-site transfer (the anonymous serve leg failed a per-user read check user 0 can never pass) and burned the single-use ticket with it. Update BOTH sites before retrying a pull. Client-report deltas also stop counting informational notes as "new issues."

= 1.8.2 =
Theme Builder fix found by dogfooding: templates created over MCP stored conditions Divi never resolved (they showed "Unassigned" in the Theme Builder UI), and body-only templates swapped the global site header for the theme's stock one. Both fixed; loop-field tokens also no longer trip a false validator warning. If you created a template through the connector before 1.8.2, open Divi → Theme Builder once to confirm its assignment.

= 1.8.1 =
Audit accuracy fix found by dogfooding our own blog: Gutenberg and classic-editor content was invisible to the collector, so posts measured zero headings and zero internal links and the SEO audit reported a false "no internal links" warning on them. Blog-heavy sites should re-run their audits after updating — expect warnings to disappear, not appear.

= 1.8.0 =
Fixes now re-check the page they touched, so audit numbers stay current instead of describing a page that no longer looks like that. And client reports can carry your own name and logo — Settings → Client reports, available on every paid plan.

= 1.7.3 =
Licensing fix: deleting the plugin now tells Freemius, so your licence seat is freed for the next site instead of staying burned. The cleanup sweep is unchanged in what it removes — it just runs on the uninstall hook now, which also lets multisite subsites clear their cached transients.

= 1.7.2 =
Cleanup hardening: test-fixture pages stranded by interrupted runs on earlier versions could show up in your audits as SEO and QA failures on pages you never made. They are now titled under the swept prefix, and leftovers from earlier versions are reclaimed automatically on the next self-test run.

= 1.7.1 =
Hosting fix found by dogfooding on WP Engine: chunked self-test runs could loop forever at the same cursor on hosts with a persistent object cache, because the run lock was a cache-only transient that eviction could erase mid-pause. The lock is now database-backed and the whole pause/resume round-trip is cache-proof.

= 1.7.0 =
A connection pill in the header of every screen shows when the site last heard from Claude — green while active, amber after a silent week, grey when never connected — so connected vs disconnected is visible at a glance instead of taken on faith.

= 1.6.1 =
Hardening from the second adversarial agent sweep: a Free-tier paywall bypass on site-level fixes closed on both doors, the tokenize verify no longer rolls back clean writes on pages that display their own colours, contrast math clamps rgb() channels the way browsers do, type specimens render for all three finding phrasings, SEO length checks measure what a searcher sees, and fix buttons survive transient errors instead of vanishing.

= 1.6.0 =
Every audit section now shows its finding: SEO findings render the search result cut where Google cuts it, performance findings show the offending images with weight badges, heading findings show the page outline with the breaks flagged, type-scale drift renders both sizes as type, and every hex named anywhere carries its own colour dot.

= 1.5.0 =
Missing alt text is now fixed in place — the finding arrives with the image, the suggestion and the input already open — and every audit pill shows when it last ran, going amber when the run is stale.

= 1.4.3 =
Cosmetic: the contrast review's own swatch caption now passes contrast everywhere, and the type-scale finding reads human-first in Simple mode.

= 1.4.2 =
Same-day hardening of 1.4.0's new surfaces after an adversarial review sweep: contrast review refuses stale approvals on both halves of the pair and can't be fooled by junk-suffixed or translucent colours, Fix All's confirm is bound to the plan it showed, the legal publish guard knows core's template draft, and the broken-token cause stops offering a fix that couldn't fix it.

= 1.4.1 =
Render fix for Design → This site's system: shadow roles no longer blow the table off the screen. Update straight past 1.4.0.

= 1.4.0 =
Every audit panel catches up to Design Guardian: contrast fixes with before/after swatches from your own palette, one-click tokenizing for hardcoded values, Fix All shows its plan before writing, every finding links into the Visual Builder, Simple mode stops speaking tool, the score delta names which audits moved, and legal drafts publish from the panel (unless they still carry [REVIEW] blanks).

= 1.3.10 =
Design Guardian grows Fix buttons on all four checks, and missing alt text gets a review screen — the image shown in the row, the plugin's best default pre-filled, your own words taken instead. Everything journaled and undoable.

= 1.3.9 =
Supersedes 1.3.8 — do not run 1.3.8. A fix in it made the free-tier page limit fail open when the bound page had been deleted, waving every page-scoped tool onto any page. Caught by the self-test before it reached anyone, but 1.3.8's zip was published, so this replaces it.

= 1.3.8 =
Licensing fix — update before you sell anything. The client report was downloadable without a licence, and the Free tier's one-page limit could be released and rebound without limit. Also returns snapshot restore to the free tier: a rollback should never be behind a paywall.

= 1.3.7 =
First-run pass. The Connection screen now states that you need a Claude account on a plan that supports custom connectors — the prerequisite nothing stated — and offers a way into the connection checks when it does not attach. No behaviour changes to any write path.

= 1.3.6 =
Recovery fix, and it affects Theme Builder and Library writes. Three write paths kept no step back on sites with WordPress revisions disabled, and one of them claimed it had, on every site. Update before letting anything write to a header, a footer or a global Library layout.

= 1.3.5 =
Copy only, no behaviour change: the score is now "WP BaseStation Site Score", and four dashboard claims that overstated what the plugin does have been corrected to match the code.

= 1.3.4 =
Recovery fix. On sites with WordPress revisions disabled, WP BaseStation saved a step back that nothing could restore, and told you to restore it anyway. Now restorable, listed, and covered by its own self-test group. Update if any site you run has WP_POST_REVISIONS off.

= 1.3.3 =
Important licensing fix: six paid capabilities were reachable from wp-admin without a licence. If you sell this plugin, update. Also unifies the dashboard icon set and adds a Next-steps row to Overview.

= 1.3.2 =
Context-efficiency fix for anyone driving WP BaseStation from an agent. Surgical edits now report only the warnings that edit introduced, instead of re-listing the whole page's token digest on every call. No functional change to what is written.

= 1.3.1 =
Self-test accuracy fix. Recommended for every Free install: the previous release reported its own paywall as eight failures. No functional changes.

= 1.3.0 =
Adds site design-system extraction and four new design domains. No breaking changes; nothing is adopted until you adopt it.

= 1.2.0 =
Adds a licence key field for customers who bought Pro after installing Free. No breaking changes.

= 1.1.0 =
Adds nine admin languages. No breaking changes; settings and runs carry over.
