== Changelog ==

Complete version history for Stedica SEO. The three most recent releases
also appear in readme.txt; everything is kept here.

= 3.22.1 =
* Adds a single request for a WordPress.org review, shown after your third completed audit and only on Stedica SEO's own screens. Dismissing it is permanent, it is never shown to anyone who has already answered it, and it offers nothing in exchange — a review you were paid for is not worth having. Site owners who want it gone entirely can add `add_filter( 'stedica_seo_show_review_prompt', '__return_false' );`.

= 3.22.0 =
* **New: Reports (Premium).** A report written for the person who has to explain SEO to someone who does not do SEO. It opens with a plain-language summary, charts issue counts over time, and lists what is worth fixing next — then answers the question the summary raises: what did the work actually earn?
* **What changed, and what happened next.** Stedica records the moment a fix lands on a page, and stores that page's Search Console figures at every audit. The report compares the pages you changed against the pages you did not, over the same period, so seasonal movement is visible rather than claimed as a result. It states plainly that this is evidence rather than proof.
* **Six to twelve months of history.** A new per-page snapshot table keeps a narrow row per page per capture, well past the point where a full audit is recycled. Existing audits are used to seed it on first run, and periods are offered only once that much history exists.
* Reports print, download as PDF, and arrive by email to the site administrator each month. A test-send button is included, because a monthly schedule is a slow way to find out that mail is not working.

= 3.21.0 =
* **The free plugin is now fully functional, with nothing held back.** The WordPress.org review team found that audits were capped at 100 URLs and redirects at 25, with a licence lifting both. They were right to call it: limiting a feature that is built in and working, purely to sell the removal of the limit, is not allowed — and it should not have shipped that way. Both caps are gone. Free audits every URL on your site and stores as many redirects as you need.
* **Everything else that was switched off by a licence check is now simply on.** The advanced schema editor, bulk fixing, the broken-link scan, quick wins and content decay all had working code in the free download behind a licence test. That is the same fault in a different place, so the licence gating has been removed entirely. Stedica SEO Premium is a separate add-on that adds code this plugin does not contain — it no longer unlocks anything that was already here.
* Fixed a cross-site scripting hole: product data reached a JSON-LD block without escaping `<` and `>`, so a `</script>` in a product title could break out of it. The same fix was applied to the multi-location schema.
* All inline `<script>` and `<style>` blocks now go through the WordPress enqueue API, and the old welcome pop-up has been removed — the setup wizard added in 3.12.0 had made it redundant.
* Chart.js updated to 4.5.1; IndexNow's terms and privacy policy are now linked in the readme; and translations are left to translate.wordpress.org rather than bundled.
* Fixed four settings that were stored empty and so ignored their own defaults — the AI provider, audit frequency, organisation type and where organisation schema is placed. On PHP 8.1 and later this also produced a "Using null as an array offset" notice on the Dashboard, Home and Issues screens. The next save of the Settings screen repairs the stored values.
* IndexNow is opt-in everywhere now. One code path read the setting with the opposite default, so on an install old enough to have no stored value, using "Request indexing" could submit the URL to IndexNow without the setting ever having been switched on.
* Hardened authorisation on five handlers that wrote to a post without checking the caller could edit that specific post — schema apply and clear, single alt-text save, focus-keyword save and alt-text generation. The SEO capability is grantable to any role that can edit posts, so this mattered; the sibling handlers already checked, and these were the omission.
* The free plugin no longer shows controls for features that live in the Premium add-on. Google connection, AI meta generation, scheduled scanning, the weekly digest, Core Web Vitals and the broken-link checker were rendering settings that stored values nothing could read, and "Run Scan Now" reported a completed scan that never ran. Anything configured while Premium is active is left untouched.
* Fixed the Chart.js update not reaching existing installs: the file was updated to 4.5.1 but still enqueued as 4.4.4, so browsers kept the cached copy.
* Corrected the IndexNow terms-of-use link in the readme, which pointed at a Bing page that no longer exists.

= 3.20.0 =
* **A table of contents.** Add the Table of Contents block in the editor, or `[stedica_toc]` anywhere else, and it lists the headings on the page with links to each. Readers jump to the part they came for, and every heading gains a stable anchor that other pages — and Google's own sitelinks — can point at.
* Headings only get an id on a page that actually asks for a contents list. Rewriting every heading across a whole site to support a feature two pages use is not a change a plugin should make on your behalf, and an id you set yourself is always left alone in case something already links to it.
* Fewer than three headings and nothing is rendered, because a contents list for two sections is noise rather than navigation.
* Works in the block editor, the classic editor and page builders alike, and pairs with cornerstone content — which since 3.17.0 is held to nine hundred words, and is exactly the length that needs one.

= 3.19.0 =
* **Headline analysis in the SEO box.** Type a meta title and it is scored as you go, with the reasons listed rather than just a number: how it will render in a search result, whether the focus keyword appears and how early, whether it opens with a number or asks a question, and whether anything is simply wrong — capitals, double spaces, a repeated word.
* **It is honest about what it knows.** Length, keyword placement and structure are measurements, true in any language. The "does it use persuasive words" check is a rough English rule of thumb, so it can only ever ADD to the score — a good headline written without those words is never marked down, and a headline in another language is never penalised for being in it.
* The score exists to prompt a second look, not to be optimised. The panel says so: a headline that describes the page honestly beats a higher number, and every observation tells you what it actually noticed.

= 3.18.0 =
* **An HTML sitemap.** The XML sitemap is for crawlers and unreadable by design; this is the other half — one page listing everything you publish, for the people who land on your site and cannot find the thing they remember reading. Add `[stedica_html_sitemap]` to any page.
* It is also the cheapest internal linking there is: every page listed gains a link from a page most sites put in the footer, which is exactly the shortfall the cornerstone check started reporting in 3.17.1. Cornerstone pages are marked so they stand out in the list.
* **It hides what the XML sitemap hides.** A page marked noindex, or password-protected, was withheld deliberately, and publishing its address in a human-readable directory advertises it just as effectively. Anything hidden from one is hidden from both.
* Pages nest as a tree, posts list flat, and a child whose parent is missing is promoted rather than silently dropped. Attributes let you choose post types, add dates, exclude pages or change the sort.
* Rendered output is cached and cleared whenever anything is published, edited, trashed or deleted, so a large site pays the cost once rather than on every visit.

= 3.17.1 =
* **Cornerstone pages are now checked for internal links, not just for having none.** Zero inbound links has always been flagged as an orphan. One is barely better on the page you have said matters most — internal links are how a site signals importance, and the page you would most like to rank is the page most worth linking to. A cornerstone page with fewer than three pages linking to it is now flagged, with the count.
* Inbound links are counted as **distinct pages**, not link occurrences: three links from one page is still one page, and counting them as three would have hidden exactly the problem this looks for. Links from a page to itself do not count.
* The audit's link suggestions already tell you which pages should link to a given page, so a flagged cornerstone page comes with the answer attached.

= 3.17.0 =
* **Cornerstone content.** An audit used to treat every page as equally important, which no site does — a service page that brings in the business and a thank-you page nobody links to produced the same issue at the same priority. You can now mark a page as cornerstone from the SEO box, and the audit takes it seriously.
* Its problems outrank the same problems elsewhere: a cornerstone page missing a description sorts above an ordinary page missing one. A cornerstone page with nothing wrong still scores zero — importance raises the stakes of a problem, it does not invent one.
* It is also held to a longer standard. Three hundred words is a fair floor for an ordinary page and far too generous for the page meant to be the best answer on your site to its question, so the thin-content thresholds move to 300 and 900 words.
* Nothing about the page's output changes — no tag, no schema, no sitemap entry. This is entirely about which problems the audit puts in front of you first.

= 3.16.0 =
* **Missing alt text can now be filled from what your site already knows.** It is the most common thing an audit finds, and until now the only way to fix it here was the AI writer in Premium — a strange thing to need a language model for, when most images already carry a caption or a descriptive filename. Stedica now moves that text into the alt attribute for you, in the free plugin.
* Sources in order of how much a human meant them: the image's caption, then its description, then its title (unless that is just the filename echoed back, which is what WordPress sets on upload), then the filename cleaned into words.
* **It refuses to write nonsense.** `IMG_4821.jpg`, `screenshot-2024-11-03.png`, a hashed upload name — none of those describe a picture, and "img 4821" as alt text is worse than nothing, because a screen reader reads it aloud and a search engine indexes it. Where no real source exists, Stedica writes nothing and tells you why.
* Alt text is written to the **media item**, not to one page's markup, so fixing an image fixes it everywhere it appears — including pages you have not audited and pages you write next year.
* Repeated alt text on one page is skipped, since duplicate alts are their own problem. External images are reported rather than touched: their alt lives in the page markup, which this does not rewrite.
* Works one page at a time or across a whole audit in batches, and there is a preview mode that shows exactly what would be written before anything is.

= 3.15.0 =
* **A Health screen.** The setup wizard gets an install configured; nothing told you when that configuration stopped being true. A Google connection expires, a host disables WP-Cron, somebody uploads a real robots.txt, permalinks get switched to plain — each quietly disables something, and until now the way you found out was noticing months later that a feature had never run.
* Nine checks in one place: database tables, when the last audit ran, whether scheduled tasks are actually scheduled, permalinks, the sitemap's URL rule, robots.txt, other SEO plugins, the Google connection and rank capture, and how much audit data is stored. Each says what is wrong and links to where it is fixed.
* Three states, and the distinction is deliberate: working, worth knowing, and **not running** — the last meaning something you switched on that never happens. A feature you turned off on purpose is never reported as a problem, because a screen that cries wolf is a screen nobody opens.
* It reads only your own settings and scheduled events. Opening it contacts nothing and changes nothing, so it is safe to leave open and refresh.
* Included: the check for "switched on but not on the schedule", which is how a weekly digest or a nightly rank capture silently never runs on hosts where cron has been disabled.

= 3.14.0 =
* **AI agent access.** A small MCP server ships alongside the plugin, so an assistant like Claude can read your SEO state and act on it: check what the plugin found, page through an audit's issues, look at one page's problems, and update a title or description. It runs on your own machine and talks to the REST API added in 3.13.0 using an application password — nothing new is exposed on the website itself.
* The agent gets seven tools, six of which only read. The one that writes says plainly that it edits the live site, and only ever changes the fields it is given.
* **New: what did the audit find on THIS page?** `/wp-json/stedica-seo/v1/issues/{post_id}` returns one page's findings, so working on a single page no longer means paging through an entire audit. It distinguishes "nothing wrong" from "never audited" rather than reporting both as empty.
* Fixed: filtering audit results by page had no effect — the filter was accepted and silently ignored, which returned the first row of the whole audit and looked like it had worked.

= 3.13.0 =
* **A REST API.** Everything the plugin does has only ever been reachable by clicking in wp-admin, which stops being enough the moment you look after more than a handful of sites. There is now a small API at `/wp-json/stedica-seo/v1/` — ask what state the plugin is in, list audits and their issues, start an audit, and read or write a page's title and description.
* It is deliberately narrow rather than a mirror of the admin. Bulk operations, AI generation, licensing and settings are not exposed: they are destructive, expensive or credential-adjacent, and none of them belongs behind a token stored in somebody's CI configuration.
* Authentication is WordPress' own — an application password from outside, or the usual cookie and nonce from inside. Every route checks the same permission introduced in 3.11.0, so an API caller can do exactly what that role can do in the admin and no more. Editing a page's fields additionally checks that you can edit that particular page.
* The status endpoint reports whether Google is connected as a yes or no, never the account, property or token, because it is the endpoint most likely to be polled from somewhere less private than wp-admin.
* Issue lists are paginated and capped, and send the same `X-WP-Total` headers as WordPress' own endpoints, so existing REST clients page through them without special handling.

= 3.12.0 =
* **A setup wizard.** New installs used to get a welcome box with two links and no guidance. Stedica now asks the four questions that actually matter — whether to import your existing SEO data, what content to audit, which features to switch on — and writes the answers, so the first audit runs against the right pages with the right settings.
* **It offers to bring your existing work with you.** If Yoast, Rank Math, SEOPress or All in One SEO has written titles and descriptions on this site, the wizard finds them, says how many pages are affected, and copies them across in batches. The other plugin is not changed, deactivated or removed, and nothing already written in Stedica is overwritten.
* It shows how many items each post type actually has, and — on the free plan — how that compares with the audit limit, so the number is visible before the first audit rather than after it.
* The wizard opens once, on a first activation only. Reactivating a site that is already configured does not drag it back through setup, and activating several plugins at once never steals the page from the others.

= 3.11.0 =
* **SEO work no longer requires an administrator account.** Every Stedica screen has needed the "can change anything about this site" permission since version 1, so the only way to let a contractor or a client fix a meta description was to make them an administrator — which also lets them install plugins, edit users and take the site down. Agencies have been doing exactly that, because there was no alternative.
* Tick any role in Settings and it can run audits, fix issues, manage redirects, track rankings and edit the SEO fields on any post. It works with the roles your site already has, including ones added by other plugins, rather than forcing everyone through a new one.
* **Settings and AI Costs stay administrator-only.** They hold your Google connection, your AI provider keys and what has been spent against them, and handing those over is a different decision from letting somebody fix titles. Granting the SEO permission never reaches them.
* There is also a ready-made **SEO Manager** role for the common case of a contractor who should not be an editor of anything else: it can edit posts and pages and do SEO work, but cannot publish, delete, install anything, or open Settings. Create it with one button; removing it moves anyone still holding it to Subscriber rather than leaving them with no role at all.
* Subscribers can never be granted it, and administrators always have it — so no configuration can lock you out of your own plugin.
* Uninstalling removes the permission from every role and deletes the role, rather than leaving a grant behind that nothing will ever explain.

= 3.10.1 =
* **Audit results are now recycled instead of accumulating forever.** Every audit stores one row per page, so a 500-page site on the weekly schedule was writing about 26,000 rows a year and deleting none of them. Stedica now keeps the ten most recent audits and drops the oldest beyond that. Set it to any number you like in Settings, or to 0 to keep everything.
* **Your trend chart is untouched.** Audit history is seven numbers per audit in a separate table and is deliberately never pruned, so the whole history stays on screen while the bulky per-page rows behind it are recycled.
* A floor of three audits applies whatever the setting says, because measuring content decay compares the current audit against an earlier one — prune to a single audit and that comparison quietly has nothing to stand on.
* Settings shows how many rows and audits are currently held, so the effect of the number you choose is visible rather than theoretical.

= 3.10.0 =
* **Multi-location Local SEO.** The Organization settings describe one business at one address, which is right until the business opens a second branch — from then on every branch page claims the head office's postcode. You can now add a row per branch, point each at the page for that branch, and that page gains a Local Business entry describing that address. The settings above keep describing the business as a whole.
* Each branch carries its own type, address, phone, email, coordinates, opening hours, price range and area served, and inherits the brand's logo and name so you are not retyping them per row.
* Locations are records, not a new post type. A post type would create a second set of indexable URLs for pages that on a real multi-branch site already exist and already rank, and the plugin would then be auditing its own duplicates.
* Coordinates are checked before they are published: a latitude past the poles, a longitude past the date line, or half a pair is dropped rather than placing the branch in the sea. Country must be a two-letter code, because that is what the field means.
* Gap-filled like every other tag here — if something else on the page already published a Local Business, Stedica stays quiet instead of offering a second answer.

= 3.9.1 =
* Premium: cleared 18 Plugin Check errors. Stedica SEO Premium deliberately uses the free plugin's text domain, because it ships no translation catalogues of its own — every premium file carries a note explaining that, and a suppression so the tool stops reporting it. Three files added since 3.8.0 were missing the suppression, so Plugin Check flagged every translatable string in them. No strings, behaviour or translations changed.
* Premium: cleared four Plugin Check warnings about the rank-tracking queries. The table name is built from the site's own database prefix and can never come from a request, and every value in those queries was already passed through prepare(); the annotation naming that simply did not list the tool's own sniff.
* The build now refuses to package a premium file that has translatable strings without the accompanying note, so this cannot come back.

= 3.9.0 =
* **Internal linking suggestions can now be acted on.** Stedica has always been able to tell you which pages ought to link to the one you are editing. Until now that was where it stopped, and you went off to open each of those pages and add the link by hand. Every matched phrase in the suggestions panel is now a button: click it and the link is written into that page.
* **It changes exactly one thing, or nothing at all.** Only the first occurrence is linked, and only where the phrase is genuine body text — never inside an existing link, a heading, a shortcode, a block delimiter, a script, or a tag attribute where it would corrupt the markup. Whole words only, so "cat" is never linked inside "category". Your own capitalisation is kept.
* **It will not touch page-builder layouts.** Elementor, Divi, WPBakery, Bricks, Beaver Builder and Oxygen keep their structure in their own format, and rewriting that is not something a plugin should do behind your back. Those pages are refused by name, so you know to add the link in the builder.
* **Nothing is inserted twice.** A page that already links to the target is skipped, including when the existing link is written with or without a trailing slash.
* Every insertion is a normal WordPress edit, so the previous version of the page is one revision away if you want it back.
* Permission is checked against the page being edited, not the one you are looking at — being able to edit this post has never meant being able to rewrite somebody else's.

= 3.8.6 =
* Housekeeping: the full version history has moved to changelog.txt, and readme.txt now carries only the three most recent releases. Fifty-six entries had grown past the length WordPress.org will display, so the directory was truncating the section and reporting it as a warning. Nothing is lost — changelog.txt ships with the plugin and holds every entry.

= 3.8.5 =
* **Stedica now tells you when it has lost its connection to Google, instead of waiting for you to find out.** A dead connection stops the daily rank capture, Search Console data and sitemap submission — silently, and often for days, because the one thing you are not doing while it is broken is visiting the screens that would have shown you.
* The warning appears on any admin screen, not just Stedica's, and says what Google actually reported alongside a Reconnect button. This is the one notice here that is not confined to our own pages, and that is deliberate.
* It costs nothing to show. The failure is recorded at the moment it happens — during the nightly capture, an audit, a manual snapshot — so displaying it never calls Google.
* Dismissing it sticks for as long as the connection stays broken, per administrator. If it breaks again after being fixed, the warning comes back.
* It clears itself the moment a token refresh succeeds, and never appears for an account that was simply never connected or was disconnected on purpose.

= 3.8.4 =
* **Fixed: an expired Google connection reported itself as a Search Console error.** When refreshing the access token failed, Stedica handed the old, dead token to Google anyway — so instead of "reconnect your Google account" you got `GSC error 401: Request had invalid authentication credentials`. The reason was known and thrown away. Every Google-backed feature shared this, not just rank tracking.
* **Stedica now recovers on its own where it can.** A 401 triggers one forced token refresh and one retry, which is the whole fix whenever the stored expiry and Google merely disagree.
* **And explains itself where it cannot.** If the refresh genuinely fails, the message says to reconnect — and, when Google reports `invalid_grant`, adds the cause people actually hit: an OAuth consent screen left in "Testing" expires refresh tokens after about a week, so the connection drops every seven days until it is published.
* A 403 now names the property it was refused, so a permissions gap on the wrong property is obvious instead of looking like an outage.

= 3.8.3 =
* **Fixed: "Record a snapshot now" reported no traffic when the real problem was something else.** Every failure — an expired Google token, the wrong property, a quota, or no property chosen at all — came back as "Search Console returned nothing for that day", which is the one explanation you can do nothing about. Each cause now names itself.
* **The most likely cause has its own fix.** Connecting your Google account and choosing a Search Console property are two separate steps, and rank tracking only needs the second one to be missing to go quiet. The screen now says so plainly and links to the setting, and it shows which property it is reading from so a mismatch is visible at a glance.
* **A quiet day no longer means a quiet site.** Search Console settles each day's figures on its own schedule, and three days is a floor rather than a promise. Asking for exactly one day and treating silence as an answer made a busy site look like it had no traffic; Stedica now walks back until it finds a day that has actually settled.
* The Rank Tracking heading now carries the Stedica icon, matching every other screen.

= 3.8.2 =
* **A screen for rank tracking.** The daily history added in 3.8.1 had nowhere to be read. There is now a Rank Tracking page listing the biggest movers over 7, 28 or 90 days — where each term sits today, where it sat then, and the distance between the two — with the pages linked straight to their editors.
* A **positive** movement number means the page moved **up**, matching how 3.8.1 stores it and how anyone would say it out loud.
* Snapshots can be recorded on demand instead of waiting for the daily run, which is what you want the first time you open the screen. Running it twice in one day replaces the day rather than duplicating it.
* Three empty states rather than one blank table: Search Console not connected, tracking switched off, and the honest one — a single snapshot cannot show movement, so the first comparison appears tomorrow.
* Fixed: the rank tracking on/off setting existed but had no control anywhere in the admin, so it could never actually be switched off. It is now in Settings.
* **Fixed: twenty of the twenty-three checkboxes on the Settings screen could not be switched off.** Unticking a box and saving appeared to work, then the box came back on the next load. Sitemap, robots.txt, IndexNow, redirects, breadcrumbs, hreflang, llms.txt, link checking and the sitemap's own include options were all affected. An unticked box sends nothing at all, and the save was reading that silence as "this form did not offer the setting" and keeping the old value — correct for a setting the screen never showed, wrong for one just turned off.

= 3.8.1 =
* **Keyword rank tracking.** Stedica now records, once a day, which search terms each page ranks for and where — building the history that makes a position mean something. A number on its own says nothing; the same number next to last month's is the whole point. Requires Stedica SEO Premium with Search Console connected.
* Movement is reported the way you would say it out loud: a **positive** number means the page moved **up**. Search Console counts positions like golf scores, where smaller is better, and a column in which up is down is a support ticket waiting to happen.
* The whole site is collected in a single request rather than one per page. That quota belongs to your Google account, and a thousand-page site should not spend a thousand requests to learn what one request already returns.
* Figures are taken from a day Search Console has finished settling, so a snapshot is never a half-counted one.
* History is kept for six months and then trimmed. This is the first thing in the plugin that removes anything on its own, and a daily per-keyword record is exactly the kind of table that grows quietly until somebody wonders why their site got slow.

= 3.8.0 =
* **Content decay — the pages that used to earn traffic and quietly stopped.** After each audit Stedica compares every page's search impressions against an earlier audit and flags the ones that have fallen sharply. This is the most valuable thing an audit can tell you: the writing is already paid for, and the page just needs refreshing. Requires Stedica SEO Premium with Search Console connected.
* Measured against the most recent earlier audit that actually holds Search Console figures, rather than simply the previous one — an audit that ran before Search Console was connected would otherwise read as a total collapse on every page.
* Pages that barely registered to begin with are left alone. Four impressions falling to one is noise, and reporting it as a 75% collapse would bury the pages that genuinely lost their traffic.
* Nothing new had to be collected for this. Every audit has always recorded search impressions per page, and audits are never discarded, so the history was already there — this release simply reads back through it.

= 3.7.3 =
* Updated the plugin description shown in your plugins list.

= 3.7.2 =
* **A change Stedica makes can now be undone.** Applying a fix records what was there first, and one click puts it back. Free, deliberately: bulk fixing is a Premium feature, but the way out of a change you did not want is not something to charge for — and Fix Mode lets anyone rewrite a great many pages one click at a time.
* **The undo also reverses what was written into your other SEO plugin.** When Yoast, Rank Math, SEOPress or All in One SEO is active, Stedica writes the new title and description into that plugin's fields as well, because that is what actually renders on the page. An undo that only put back Stedica's own copy would look like it worked and change nothing a visitor sees.
* A page that had no title or description before an edit goes back to having none, rather than being left with empty values — which is not the same thing, particularly for indexing and canonical settings.
* Hardened: the two functions that write social preview and indexing settings now check permission themselves, as the title and description writer always has. Both callers already checked, so nothing changes today; the point is that they no longer depend on the caller remembering.
* Updated the plugin description shown in your plugins list.

= 3.7.1 =
* **Author pages now have their own SEO settings.** Every user profile gains an SEO title, a meta description and a "keep this out of search results" option for that author's archive — the same fields posts and categories already had. Useful straight away for the shared or administrative account that publishes nothing worth ranking.
* **Author credentials are now published for search engines.** A job title, qualifications, and links to the same person elsewhere — LinkedIn, ORCID, a professional register, a university page — can be filled in on any user profile and are published as structured data on their author archive. This is the information Google looks for when judging whether a page was written by somebody who actually knows the subject, and the links are the part that matters: they are what turns a claim of expertise into something a search engine can go and verify.
* Skipped automatically on any page where another plugin already describes the author, so you never end up with two competing versions of the same person.
* A profile link that is not a real web address is now refused rather than quietly turned into one. A broken link in this list is worse than no link at all, because the entire point is that it can be checked.
* Fixed: on category, tag and author pages Stedica opened its block of tags and never closed it. Harmless to render, but it made the plugin's own output hard to tell apart from the theme's when reading the page source.

= 3.7.0 =
* **Pages built with Divi, Bricks, Oxygen, Beaver Builder or WPBakery are now read properly.** Only Elementor was ever understood. Everywhere else the audit was judging something other than your page. Bricks, Oxygen and Beaver Builder keep your content outside the post itself, so word count, headings, images, readability and every keyword check ran against an empty page. Divi and WPBakery were worse in a quieter way: the builder's own layout codes were counted as if they were your writing, inflating the word count and feeding markup into readability scoring, the AI and your llms.txt file. All five now read the real text, the real heading and the real images.
* Elementor pages and ordinary Gutenberg or Classic pages are unaffected — the same numbers before and after.
* **Expect issue counts to move on builder-built sites after the next scan.** Pages that looked empty will start reporting real word counts, and pages that looked long enough may turn out not to be. The numbers change because they were measuring the wrong thing.

= 3.6.11 =
* **Consistent 6px corners throughout the interface.** Buttons, cards, panels, notices, form fields, tabs and badges were rounded at anything from 2px to 20px depending on when each was written. They are all 6px now, driven by a single value so it stays that way. Genuinely round elements — status dots, the avatar and the pill-shaped badges — are unchanged.
* Fixed a stray bracket in the admin stylesheet that had been there for several releases. Browsers were quietly recovering from it, so nothing looked wrong, but the file was not valid CSS.

= 3.6.10 =
* **Fixed: scheduled scans, the weekly digest and broken-link checking ran without a licence.** All three are Premium features, but each was switched on by its own setting alone with no licence check anywhere in the file — so an install whose licence had lapsed carried on running them indefinitely, and broken-link checking, which is on by default, ran even where a licence had never been active at all. They now stop when the licence does. The check happens both when the schedule is built and when a job that was already queued comes due, because an event sitting on the schedule outlives the licence that created it.

= 3.6.9 =
* **Your sitemap address can no longer be taken over by another plugin.** Stedica now answers its own address directly, before any other plugin gets the chance. On a live site running All in One SEO, `/stedica-sitemap.xml` was being answered by AIOSEO's own empty sitemap — it claims every address ending in `-sitemap.xml` — while the child sitemaps carried on working normally, so the sitemap looked half-broken with nothing anywhere to explain why. As a side effect this also makes the sitemap immune to the problem fixed in 3.6.8: it no longer depends on WordPress's stored address list at all.
* **Stedica's sitemap is now your site's sitemap.** The addresses other sitemap plugins answer on — `/sitemap.xml`, `/sitemap_index.xml`, `/wp-sitemap.xml` and the rest — are sent to yours, so a search engine that already knows one of them arrives at your sitemap instead of at a second, competing one. The other plugin is not switched off and nothing is deleted; it is only the addresses that are redirected, and switching this off in Settings restores them immediately.
* **Stedica now tells you when another SEO plugin is running.** It is built to work alongside one and keeps your page head free of duplicate tags, but it cannot referee what happens outside the head: two sitemaps, two robots.txt blocks, two plugins claiming the same addresses. The dashboard now names what else is active and recommends importing its data and then removing it, so one plugin owns your SEO.

= 3.6.8 =
* **Fixed: your XML sitemap could quietly start returning "not found".** WordPress keeps a stored list of the custom addresses plugins add, and rebuilds it only when asked. Another plugin rebuilding that list at a moment when Stedica's address is not registered leaves it out — and from then on `/stedica-sitemap.xml` returns "not found" while everything else looks perfectly healthy: the sitemap still generates, the setting is still ticked, Search Console still has the address. Seen on a live site straight after another SEO plugin was installed. Stedica now notices when its address has gone missing and restores it, at most once an hour so that a site which genuinely cannot store the list is not slowed down by retrying.

= 3.6.7 =
* **Duplicate titles, descriptions and headings now appear in your issue list.** They were already being detected and were already costing the page points, but the issue itself never reached the list you read — so a page showed a worse score with nothing to explain it, and one of the most fixable problems in SEO stayed invisible. No scores change; the points you were already paying now say what they are for.
* **"Missing Description" can now actually be reported.** The audit was reading a description built from your page text where none existed, so a page publishing no description at all was never flagged — and was instead told its description was "too short", sending you to lengthen something that was not there. The audit now judges the description your visitors actually get.
* **No more keyword warnings about a keyword you never chose.** When no focus keyword is set, Stedica works one out from the page slug so the rest of the plugin has something to go on. The audit was then holding pages to it — reporting "Keyword missing from Description" on a brand-new page with neither a keyword nor a description. Those checks now run only on a keyword you actually chose.
* Expect your issue counts to change after the next scan. Pages genuinely missing a description will start saying so, and some keyword warnings will disappear. The totals move because they were wrong, not because your site changed.

= 3.6.6 =
* **Fixed: importing from another SEO plugin invented values it never had.** Bringing your data over from Yoast, Rank Math, SEOPress or All in One SEO also wrote a description built from your page text where the other plugin had none, and stored your page's own address as a canonical URL. The first froze raw body text as your meta description permanently; the second would keep pointing at your old address if you ever moved the site. Only what the other plugin genuinely held is copied now — and anything you had already written in Stedica is still left alone.
* **Fixed: hreflang was narrowing your language targeting.** With Polylang or WPML, a page in Spanish went out as `es-ES` — Spanish *in Spain* — instead of the `es` your translation plugin uses, so Google stopped preferring it for readers in Mexico, Argentina or anywhere else. Stedica now publishes the same language codes your translation plugin does, and still adds the x-default tag that neither Polylang nor WPML publishes on its own.

= 3.6.5 =
* **A page with no meta description now falls back to your excerpt.** Until now it was published with no description at all. If you wrote an excerpt, that is used — and only a real excerpt you typed yourself, never the opening lines of the page. Google writes its own snippet when yours is weak, matched to what the reader actually searched for, and a paragraph cut off mid-thought usually loses to it.
* **Social previews fall back further, to the page text.** Facebook, LinkedIn and WhatsApp write nothing of their own, so an empty description there means your link is shared bare. Different problem, different answer.
* Password-protected posts are excluded from both, so protected text never reaches a meta tag.
* Either way the audit still reports the page as missing a description, so nothing is hidden from you. There is a switch under Settings if you would rather publish nothing you did not write yourself.

= 3.6.4 =
* **Fixed: redirects could not be saved at all on a new installation.** The redirects table was created without the column that regex matching has needed since 3.3.0, and the plugin recorded the database as already up to date — so the repair that adds it never ran. Saving a redirect failed silently and nothing appeared in the list. Sites that upgraded from an earlier version were never affected, which is exactly why this went unnoticed. New installations now get the right table, and any site already in this state is repaired automatically on update.
* **Fixed: your blog page had no SEO tags whatsoever.** The page assigned under Settings → Reading → "Posts page" was not recognised, so it published with no meta description, no canonical and no social tags — and a description typed into its Stedica box was discarded without warning. It is now treated like any other page, and what you write there appears.
* When the blog index is your front page it now publishes a canonical and falls back to your site tagline for a description, instead of publishing nothing.

= 3.6.3 =
* **Fixed: your XML sitemap was not the one search engines found.** WordPress serves a sitemap of its own at `/wp-sitemap.xml` and announces it in robots.txt. Stedica's announcement stood down whenever it saw any sitemap already listed there, so on a normal install robots.txt pointed search engines at WordPress's sitemap and Stedica's — the one that covers taxonomies, authors, archives and images, and honours your noindex settings — was reachable only by typing the address. Stedica's is now the sitemap the site advertises, and WordPress's own is switched off so the two no longer compete.
* **Fixed: every sitemap address answered with a redirect.** WordPress was adding a trailing slash to `/stedica-sitemap.xml`, and because the index links to the unslashed addresses, a crawler took a redirect on the index and again on every child sitemap. Search Console reports a submitted sitemap that redirects as a problem. They now answer directly.
* **Fixed: llms.txt descriptions could stop mid-phrase.** On a page with no meta description the line was cut at a word count with nothing to mark it, so it read as though the sentence had been lost. It now ends on a sentence, or on a whole word with an ellipsis.

= 3.6.2 =
* **Fixed: product pages carried two conflicting `og:type` tags.** 3.6.1 wrote `og:type=product` early in the page and then a second `og:type=website` in its own block, so Facebook, WhatsApp and LinkedIn saw contradictory values — and because the price tags only count on a product, a shared product could lose its price. There is now exactly one `og:type` per page, decided in one place, and a stray one left by a theme is removed rather than joined.
* Product structured data no longer carries an `itemCondition` on the product itself. The condition belongs on the offer, which is where Google reads it and where Stedica already put it, so the extra copy was a property nothing consumed.

= 3.6.1 =
* **Fixed: WooCommerce stores built with a page builder had no product rich results at all.** WooCommerce only generates its Product data from its own single-product template. Elementor, Divi, Bricks and Oxygen replace that template, so on those stores WooCommerce publishes nothing — and 3.6.0, which added to WooCommerce's data, therefore had nothing to add to. Stedica now publishes a complete Product block itself when it finds none, including price, availability, SKU, brand and identifiers. Stores where WooCommerce does publish its own are untouched, so there is still exactly one Product block on the page.
* **Variable products now report a price range** rather than a single price, which is what Search Console expects when a product sells at several prices.
* **Fixed: product meta descriptions were cut off mid-sentence.** They ran past the length Google displays and stopped in the middle of a phrase. They now end at a sentence where there is one, and at a whole word with an ellipsis where there is not.
* **Pages with no Open Graph tags now get them.** Until now Stedica only wrote og:title, og:description and og:image if you had filled in the social fields by hand, so a site whose theme writes none shared as a bare link on WhatsApp and LinkedIn. They are now filled in from your SEO title, description and featured image — but only when nothing else on the page provides them, so a theme that already writes good Open Graph is still left alone.

= 3.6.0 =
* **WooCommerce product rich results.** Stedica now fills in the fields Google asks for and WooCommerce does not supply — brand, GTIN, MPN and item condition — reading them from the brand taxonomy, product meta or product attributes your store already uses. Nothing is guessed: a field only appears when the store genuinely has a value, because an invented GTIN can get a Merchant Center account suspended.
* It **adds to WooCommerce's own product data rather than publishing a second copy.** Two competing descriptions of one product is how a store loses its rich results entirely, so everything goes through WooCommerce's own extension point and the page keeps exactly one Product block.
* **Products shared on social now show a price.** Product pages get `og:type=product` with price, currency and stock status, instead of being shared as a generic article.
* **A product with no meta description now falls back to its short description** — a written summary, rather than the first words of the long description starting mid-sentence.
* Product categories and tags already gained SEO fields and sitemap entries in 3.1.1, and the shop archive in the same release, so a store is now covered end to end.

= 3.5.0 =
* **hreflang for multilingual sites.** If you run WPML or Polylang, Stedica now publishes the hreflang annotations that tell search engines which page is the translation of which — so your Spanish page is served to Spanish readers instead of competing with the English one as duplicate content. It also adds `x-default`, which neither plugin emits on its own and which decides where a visitor lands when none of your languages matches theirs.
* It **replaces** the hreflang tags WPML or Polylang already print rather than adding a second set. That is deliberate: Google discards hreflang wholesale when it finds conflicting annotations, so two well-meaning sets are worse than one.
* Only languages a page actually exists in are listed, and nothing at all is emitted on a single-language site. A `stedica_seo_hreflang_alternates` filter is available for sites whose languages live on separate domains.

= 3.4.0 =
* **Move in from Yoast, Rank Math, SEOPress or All in One SEO in one click.** Stedica reads the titles, descriptions, canonical URLs and noindex settings those plugins already wrote and copies them into its own fields. It works even if the old plugin has already been deactivated — the data outlives it — and it never modifies or removes the other plugin, so you can import and still change your mind. Values you have already set in Stedica are kept unless you ask for them to be replaced.
* **Settings can be saved to a file and applied to another site.** Useful for moving a configuration from staging to production, or setting up a second site the same way. API keys, connected accounts and the GA4 property are deliberately left out: the first are secrets that should not sit in a file you email to yourself, and the last would point the other site's reporting at this one.
* The import runs in batches from your browser, so a site with tens of thousands of posts finishes instead of timing out halfway with nothing to show for it.

= 3.3.0 =
* **Redirects can now match a pattern, not just one exact path.** Choose *Regex pattern* when adding a rule and one line moves a whole section: `^/blog/(.+)$` sending to `/news/$1` catches every post under it, with `$1`…`$9` carrying the captured parts into the target. Exact matching is unchanged and still runs first, so nothing about your existing rules changes.
* **Automatic 404 rescue.** When a URL 404s and a published post has exactly the same slug, Stedica can send the visitor there with a 301 instead — which recovers old links after a permalink change without writing a rule for each one. Matching is exact, never a guess: a wrong redirect hides the problem instead of fixing it. Off by default, in Settings.
* Patterns are checked when you save them, so a rule that could not compile is refused at the point of entry rather than failing silently on your front end.

= 3.2.0 =
* **Your site now publishes /llms.txt.** This is the file ChatGPT, Claude, Perplexity and AI-powered search look for: a short Markdown summary naming your site, what it does, and the pages that matter, each with a one-line description. It is built from content you already publish — using the meta descriptions you have written where they exist — and anything marked noindex is left out. On by default, with a switch in Settings, and it contacts nothing.

= 3.1.1 =
* **Category, tag, author and date archives now get real SEO output.** Until now the plugin only wrote a title, description, canonical and robots tag for single posts and pages — archives got nothing, which meant 3.1.0 was listing pages in the sitemap that the plugin then left unoptimised. Archives now receive all of it, falling back to the term name and term description when you have not written your own.
* **SEO fields on categories, tags and custom taxonomies.** Editing a term now offers an SEO title, meta description, canonical URL and noindex / nofollow switches, in the same place WordPress already puts the name and description.
* **A term set to noindex is no longer listed in the sitemap.** Advertising a URL while telling search engines not to index it is a contradiction Search Console reports as an error.
* Archives left alone stay exactly as they were: with nothing saved, the plugin emits no robots tag at all and whatever your theme or another SEO plugin sets is untouched. Updating cannot change how your archives are indexed.
* Paginated archives now carry a self-referencing canonical rather than pointing every page at page one.

= 3.1.0 =
* **The XML sitemap now covers taxonomy, author and date archives.** Until now it listed post types only. Category, tag and custom taxonomy archives are included by default — they are real landing pages, and both major competitors list them out of the box. Author and date archives are available but off by default: they are frequently thin, and an author sitemap publishes every contributor's archive URL. Each gets its own child sitemap in the index, paginated at 2,000 URLs like the existing ones, and the index invalidates when a term or user changes.
* **Fixed: a sitemap whose post type or taxonomy slug ends in a hyphen and digits returned 404.** A slug like `top-10` was being split into the name `top-1` and page `0`. The name is now resolved against the real list of sitemaps before the URL is rejected.

= 3.0.21 =
* Plugin header fix: the Plugin URI now points at wordpress.stedica.com and the Author URI at stedica.com. WordPress.org rejects an upload when the two are identical, which they were. No functional change.

= 3.0.20 =
* **IndexNow is now off by default.** WordPress.org's guidelines require a plugin to get your explicit consent before it contacts an external server. IndexNow submits the URL of each post you publish to a third-party endpoint, so it now ships switched off and the checkbox in Settings is the consent. **If you already turned it on, it stays on** — only new installs are affected. The setting now says plainly what enabling it sends and where.
* **Fuller external-services disclosure in the readme.** The OpenStreetMap/Nominatim lookup behind the Local SEO *Find coordinates* button, and Freemius licensing, are now documented alongside the services that were already listed.

= 3.0.19 =
* Code housekeeping in the Premium add-on so it passes the WordPress plugin review tooling cleanly. Nothing in the free plugin changed.

= 3.0.18 =
* Documented why the Gemini model-list request does not use the WordPress 7.0 AI Client: it is a model-discovery call rather than a prompt, and the plugin supports WordPress 6.0 and up. No functional change.

= 3.0.17 =
* **Fixed: apostrophes were being saved escaped.** Values sent from the plugin's own screens — meta titles and descriptions, focus keywords, redirect paths, schema fields — were sanitised but never unslashed, so a title typed as *Bob's SEO* could reach the database as `Bob\'s SEO`, gaining another backslash each time it was saved. All request handling now goes through one set of typed accessors, so the step cannot be missed for any field.
* **Safer admin redirects.** The redirect back from a Google connection now uses WordPress's `wp_safe_redirect()`.
* **Full WordPress.org coding-standards pass.** Every remaining warning from the official Plugin Check tool is now resolved or documented in the code — request input, output escaping, direct database access, prefixing and translator comments. Nothing else about how the plugin behaves has changed.

= 3.0.16 =
* **Compliance pass for the WordPress.org plugin directory.** Every issue reported by the official Plugin Check tool is resolved: output escaping tightened across the admin screens, database queries reviewed and documented, and the editor SEO badge moved out of inline PHP into a real stylesheet the browser can cache. Nothing about how the plugin behaves has changed.

= 3.0.15 =
* **The Premium Version link now opens the Stedica SEO plans directly.** It goes to the Freemius checkout page for the Premium plan, which loads reliably, rather than the in-admin pricing screen that came up blank on some hosts. Every upgrade link in the plugin points to the same place.

= 3.0.14 =
* **Reverted 3.0.13's change to the Premium Version link.** It pointed at a page that does not exist. Upgrade links go to the Freemius plans again, as they did before.

= 3.0.13 =
* **Fixed: the Premium Version link opened a blank page.** It now opens the Stedica SEO pricing page on stedica.com. Every upgrade link in the plugin goes to the same place, so none of them can land on an empty screen.

= 3.0.12 =

**Stedica SEO is now free on WordPress.org, with an optional Premium upgrade.**

Up to 2.7.2 there was a single paid plugin containing every feature. 3.0 splits that into two: this plugin, free and complete for a small site, and **Stedica SEO Premium**, which installs alongside it and lifts the limits. Both stay active — you will see two rows in your plugins list — and Premium builds on this plugin rather than replacing it.

**The free plugin is limited by scale, not by capability.** Every core SEO job can be finished within it:

* Full site audit with severity scoring, up to 100 URLs per scan.
* Titles and meta descriptions, with templates, dynamic variables and a live Google preview.
* Rule-based title and description suggestions — no API key, no cost.
* Canonical URLs and per-post meta robots, merged correctly into WordPress' own robots output.
* Open Graph and Twitter Cards, with an image picker.
* Core JSON-LD: Organization, WebSite, WebPage and BreadcrumbList. Breadcrumbs included.
* XML sitemap and robots.txt editor. IndexNow submission to Bing, Yandex, Seznam and Naver.
* Redirects — up to 25 rules (301, 302, 307, 410) plus a 404 log.
* Missing alt-text scanner, readability scoring, CSV export, Elementor compatibility.
* Reads and writes Yoast, Rank Math, SEOPress and All in One SEO metadata, so it can be added to an existing setup without migrating anything.

**Stedica SEO Premium adds** the AI suite across five providers using your own API key, with a screen that tracks tokens and spend; unlimited scan size; scheduled scans and digest emails; Fix Mode approvals and bulk apply; Google Search Console and Analytics 4; Core Web Vitals; unlimited redirects with importers for Redirection, Yoast Premium, Rank Math, AIOSEO and SEOPress; the broken link checker; internal linking and orphan detection; keyword cannibalisation; the full schema suite with a per-node editor; advanced Local Business schema; and multisite. There is a 7-day trial and it does not ask for a card.

**If you already have a 2.7.2 licence you lose nothing** — between them the two plugins contain every feature 2.7.2 had.

Other notes:

* When a site has more pages than the free scan covers, the Audit and Issues screens say so plainly rather than leaving you to guess whether the rest were clean. Sites within the limit see nothing.
* Redirects are never dropped if a licence lapses. Existing rules keep working and can still be edited or deleted; only adding new ones past the free limit is blocked.
* Spanish translation complete — all 1,207 strings, including everything new in 3.0.
* Tested up to WordPress 7.1.

= 2.7.2 =
* **Fixed: "Export CSV" on the Issues screen only ever exported 50 rows.** The row limit was being passed in a way the query never read, so the export silently fell back to a default of 50 — a site with 1,200 issues downloaded 50 of them with nothing to indicate the rest had been dropped. The export now returns the full filtered set (up to 5,000 rows), and tells you if it had to stop there.
* **Fixed: redirects pointing at another domain sent visitors to the login screen.** Any redirect whose target was on a different site — including every external rule brought in by the importer from Redirection, Rank Math, AIOSEO, Yoast Premium or SEOPress — was rejected as unsafe and quietly rewrote to `/wp-admin/`. Cross-domain redirects now work. Targets are validated when saved and again when served, and only `http`/`https` URLs or site-relative paths are accepted.
* **Fixed: the Issues CSV export ignored the NoIndex filter.** Viewing "NoIndex only" and exporting produced a file containing exactly the rows that were *not* on screen.
* **Fixed: the weekly digest email arrived at the wrong time on any site not set to UTC.** It was scheduled for 08:00 UTC rather than 08:00 in your site's timezone, so it landed at 18:00 on a UTC+10 site and midnight on UTC-8. It now respects the site timezone, and stays at 08:00 across daylight-saving changes.
* **The Redirects screen and the Dashboard widget are now translatable.** Both contained no translatable strings at all, so they stayed in English on every site regardless of the installed language. Along with the onboarding dialog, that is roughly 60 further strings now available to translators. Counts on these screens also use locale-aware number formatting and correct plural rules.
* **Fixed: the Redirects screen loaded without its stylesheet.** Its styles were inlined in the page because the plugin's stylesheet was never enqueued on that screen. The styles have moved into `admin.css` and the screen now loads it like every other.
* Relative timestamps on the Redirects screen ("2 hours ago") were off by the site's UTC offset.
* `#current_year` and `#current_month` in title templates now follow the site's timezone and language — `#current_month` previously always rendered in English.
* Diagnostic messages are no longer written to the host's error log unless `WP_DEBUG_LOG` is enabled, and the browser console tracing in the audit and editor-badge scripts is now gated behind `WP_DEBUG`. One of these logged a line per database row, which on a site with a schema problem meant thousands of entries per scan.
* Internal: a copyright and licence header has been added to every first-party source file; a Freemius deployment secret with no purpose in this build has been removed; assorted WordPress coding-standards cleanups.

= 2.7.1 =
* **Spanish translation completed.** All 1,110 strings are now translated, including the JavaScript-rendered screens exposed in 2.7.0. 327 entries that had been produced by an automated word-swap rather than a real translation — leaving sentences like "No entrada associated with this result" — have been rewritten.
* Consistent terminology throughout: "sitemap" is used everywhere (it was previously mixed with "mapa del sitio"), and the schema/scan/audit vocabulary now matches across every screen.
* **The translation template now explains itself.** `languages/stedica-seo.pot` carries full instructions for translators: how to name the file for your locale, which strings must stay in English and why, how the `%s`/`%d` placeholders work and what happens if they are altered, and how to keep a translation safe across plugin updates.
* Both `stedica-seo.pot` and the finished `stedica-seo-es_MX.po` ship with the plugin, so a new language can be started from either.

= 2.7.0 =
* **The whole interface is now translatable.** Previously only the parts rendered by PHP could be translated; everything drawn in the browser by JavaScript — most of the Issues screen, plus parts of Settings and the post editor panel — was hardcoded English and unreachable by any translation. 896 strings have been made translatable, taking the template from 556 strings to 1,114.
* Also newly translatable: the scheduled-audit and weekly-digest emails, which were entirely hardcoded English, and around 140 status and error messages that the admin screens display.
* **Fixed:** the approval dropdown in Fix Mode stored whatever label was displayed. It worked only because the label happened to be English — translating it would have written a translated word into the database and silently broken approvals, Apply Fixes and the CSV export. The stored value and the visible label are now separate.
* **Fixed:** on the Settings screen, the "Submit sitemap to Google now" button reverted to English after being clicked, on any non-English site, because the script that restores the label used a hardcoded string.
* **Fixed:** priority badges on the Issues rows showed untranslated English next to an already-translated chart legend.
* **Fixed:** CSV export column headings are now quoted and escaped like the data cells. With translatable headings, a language whose wording contains a comma would otherwise have shifted every column in the file.
* Translations for JavaScript are compiled by the build and shipped alongside the plugin, and the same "any Spanish locale" fallback that applies to the rest of the plugin applies to them.

= 2.6.0 =
* **Spanish translation included.** The plugin now ships a complete Mexican Spanish (es_MX) translation — all 556 translatable strings — and uses it automatically the moment you activate it. Nothing to download or configure.
* **Every Spanish locale is covered.** WordPress normally only loads a translation whose name matches your site language exactly, so a site set to Spain, Colombia, Argentina or any other Spanish variant would have seen English. Stedica now falls back to the bundled Spanish for any Spanish locale. If you add your own translation for a specific variant, yours still takes priority.
* **The licensing and account screens are Spanish too.** Those are rendered by Freemius, which ships Spanish for Spain only — Spanish-locale sites now get it instead of English.
* **Fixed:** the "Once a Month" auto-scan frequency label always displayed in English, whatever the site language, because it was translated before translations had loaded.
* **Fixed:** the translation template shipped an invalid plural-forms header, which made any translation created from it fail to compile. Anyone translating Stedica into a new language was blocked by this.
* Translations are compiled by a new `bin/make-mo.sh` build step, which also re-syncs each translation against the current template so a newly added string can never ship silently untranslated.
* **Note:** this release translates the plugin's PHP-rendered interface. Text generated in the browser by JavaScript — much of the Issues screen, and parts of Settings and the post editor panel — is not yet translatable and still displays in English. See the FAQ.

= 2.5.91 =
* **Fixed (critical):** the Stedica SEO panel in the post editor was completely broken in 2.5.84–2.5.90. An error in the permission check made it call itself endlessly, so loading the panel, saving a meta title or description, and every AI, schema, keyword and FAQ action returned a server error. All of it works again.
* **Fixed (critical):** pages you had marked "noindex" in another SEO plugin (Yoast, Rank Math, AIOSEO, SEOPress) were being published to Google as indexable. Stedica removed the other plugin's robots tag and replaced it with its own "index, follow". Stedica now only takes over robots directives on pages where you have set them in Stedica.
* **Fixed:** an audit could never finish if a page was trashed or unpublished while it ran — the progress bar stuck just short of 100% and the page kept retrying forever. Completion is now based on the actual work remaining. Scheduled audits hit the same problem and stopped sending their email reports.
* **Fixed:** if audit results could not be saved, the audit retried the same pages indefinitely — with AI enabled, paying for the same requests on every pass. It now stops and explains what went wrong.
* **Fixed:** clicking "Rescan" on a page with a duplicate title, H1 or description scored it too low afterwards, sometimes dropping it off the Critical list even though nothing had been fixed.
* **Fixed:** if a database update failed three times, the plugin marked itself up to date and never retried, leaving the Issues page permanently empty with no explanation. It now tells you what failed and offers a retry.
* **Fixed (multisite):** network-activating the plugin overwrote every subsite's URL rules with the main site's, which could 404 custom post types or break pretty URLs entirely. Also: deactivating across a network now clears scheduled tasks on every site, and deleting a subsite removes its Stedica tables instead of leaving them behind.
* **Fixed:** pages you had excluded were still counted as issues in the dashboard totals, the trend chart and the scheduled-audit email subject.
* **Fixed:** duplicate Open Graph and Twitter tags were added to pages Stedica had no meta for, so Facebook, LinkedIn and X received two conflicting titles.
* **Privacy:** password-protected pages could still reach the AI provider through their headings and link text. Nothing from a protected page is sent now.
* **Security:** the "Export CSV" button on the Issues page did not neutralise spreadsheet formulas. A page title crafted as a formula could execute when the export was opened in Excel. (The Audit page exports were already protected in 2.5.88.)
* **Fixed:** the 404 log cleanup could delete far more entries than the configured limit — and in some cases empty the log completely.
* **Fixed:** Core Web Vitals results were never saved. The database write included a field that is not a column, so MySQL rejected the whole statement and every mobile/desktop score, LCP, CLS and TBT value stayed empty after a CWV check. Scores now persist, and a display-only field can no longer discard an entire row update.
* **Performance:** "Check Links" and "Redirects" no longer run past the server's time limit. Both could need several minutes on a page with many links, so the request was killed before returning and nothing was cached — the same work was repeated on every click. They now work to a time budget, remember each link they have already tested, and report what is still outstanding so a second click carries on where the first stopped.
* **Performance:** Core Web Vitals checks are time-budgeted the same way. On a host with a short execution limit the mobile score is saved and the desktop score is fetched on the next click, instead of the request dying with nothing stored.
* **Performance:** CSV exports stream in batches and read only the columns they need, instead of loading every row of every column into memory. Large sites could previously exhaust the PHP memory limit mid-download.
* **Performance:** internal-link suggestions are cached for 12 hours. The lookup runs unindexable wildcard searches over post content and Elementor data, which is slow on large sites; the cache is keyed to the page's keywords and last-modified time, so it refreshes itself when anything relevant changes.

= 2.5.90 =
* Uninstall cleanup now runs through the Freemius `after_uninstall` hook instead of a standalone uninstall.php, so licence cleanup and plugin cleanup both run. Removal behaviour is unchanged — all 7 tables, 18 options, post meta, transients and scheduled tasks are still removed.

= 2.5.89 =
* **Privacy:** password-protected pages are no longer sent to AI providers, and are excluded from the XML sitemap. Their content is deliberately gated, so it should never be transmitted off-site or advertised publicly.
* **Fixed:** Yoast SEO users were shown "Missing structured data" on any page where they hadn't hand-written a meta title. Yoast always outputs structured data; the check was keyed on an unrelated field.
* **Cost:** clicking "3 Variants" could silently bill four AI requests instead of one when the model returned unparseable JSON. It now costs at most two.
* **Performance:** the sitemap cache is invalidated with a single option write instead of ~100 database deletes on every post save.
* **Performance:** page text is extracted once per scanned post instead of twice.

= 2.5.88 =
* **Fixed:** word counts were wrong on any language using accented characters. Spanish, Portuguese, French and German content was over-counted by roughly 30–50% (accents were treated as word breaks, so "instalación" counted as two words). This also hid genuinely thin pages and skewed the readability score.
* **Fixed:** readability scoring deleted accented vowels before counting syllables, making non-English text score as easier to read than it is.
* **Fixed:** Rank Math users were told "Missing structured data" on every page — the schema check compared against the wrong internal name and never ran.
* **Fixed:** saving Settings added a backslash before every apostrophe, compounding on each save.
* **Fixed:** changing the broken-link check frequency had no effect after the first time it was scheduled.
* **Fixed:** when the browser stopped waiting on a slow scan, the scan was marked complete without running duplicate, orphan or cannibalisation detection.
* **Fixed:** a failed database upgrade is no longer recorded as successful, so it can retry instead of leaving the schema permanently broken.
* **Security:** CSV exports now neutralise spreadsheet formula injection.
* **Security:** the dashboard widget is restricted to administrators; it previously exposed scan data and page titles to all logged-in users.
* **Security:** hardened deserialisation when importing redirects from other plugins.
* The 404 log is now pruned (90 days / 5,000 rows by default, both filterable) instead of growing without limit from bot traffic.
* Google tokens are no longer loaded on every front-end request.

= 2.5.87 =
* **Performance:** a scan no longer re-reads every post ID on every batch. The scanner now asks the database only for the next slice of unscanned posts, so scanning is proportional to the batch size instead of the square of the site size.
* **Performance:** added the missing database indexes. The Issues list sorts by priority score by default and previously had no index to sort on, forcing MySQL to sort the whole scan on every page load.
* **Performance:** orphan detection now loads posts in chunks with a bounded memory budget, instead of loading every post's full content at once — which could exhaust PHP's memory limit on large sites.
* New plugin icon.

= 2.5.86 =
* Added licensing, automatic updates and a 14-day free trial via Freemius. One version with every feature included — plans differ by the number of sites licensed, not by feature.

= 2.5.85 =
* **Fixed:** the scheduled auto-scan could hang indefinitely. Its batch loop had no exit condition other than success, so a single post that failed to record a result would spin forever, and a large site could exhaust the request running the whole scan in one process. The loop is now bounded by stall detection, an iteration ceiling and a time budget, and a scan stopped early is left resumable instead of being falsely reported as complete.
* **Fixed:** uninstalling the plugin now removes everything it created. Previously it dropped 3 of 7 database tables and deleted 2 of ~16 options, orphaning the redirects, 404 log, broken-link and scan-history tables plus all Google OAuth tokens, the IndexNow key, every `_stedica_*` post meta row and the sitemap caches.
* **Fixed:** Multisite. Network activation created database tables for one site only, and the other sites were permanently flagged as "schema up to date" so they never repaired themselves. Activation is now per-site, new sites added later are set up automatically, and a missing schema now self-heals.

= 2.5.84 =
* **Security:** the post-editor AJAX endpoints accepted an arbitrary post ID while only checking the generic "can edit posts" capability. A lower-privileged user could read or modify the SEO data of any post — including other authors' drafts — and send that content to a configured AI provider. All endpoints now authorise against the specific post.
* **Security:** TLS certificate verification is now enabled on all outbound requests (it was disabled for link checking and redirect tracing, allowing man-in-the-middle interception). Sites using self-signed certificates can opt out with the `stedica_seo_sslverify` filter.
* **Security:** the redirect-chain tracer no longer follows non-HTTP(S) or private/loopback targets supplied by a remote server (SSRF hardening).
* **Security:** JSON-LD output is now emitted with `JSON_HEX_TAG`, so a `</script>` sequence inside any schema value can no longer break out of the script block (stored XSS).
* **Privacy / compliance:** Chart.js is now bundled with the plugin instead of being loaded from a third-party CDN. This removes a supply-chain risk, stops sending administrator IP addresses to a third party, and lets the Issues page work on firewalled installs.
* **Fixed:** the plugin no longer removes other plugins' and WordPress core's admin notices on its own screens — including critical security and PHP-version warnings. Notices are now positioned correctly instead of being suppressed.
* **Fixed:** deactivating the plugin now clears every scheduled task. Previously the auto-scan, weekly digest and pending sitemap submission stayed scheduled.
* **Fixed:** the onboarding dismissal endpoint was missing a capability check.

= 2.5.83 =
* **Fixed (critical):** canonical and robots meta tags were being stripped from every singular page and never re-emitted, so pages shipped with no canonical URL and user-set `noindex` was silently ignored. Both are now emitted authoritatively.
* **Fixed (critical):** `noindex` pages were still listed in the XML sitemap and still submitted to IndexNow, because the exclusion checked a meta key the plugin never wrote. Includes a migration that backfills existing pages.
* **Fixed (critical):** the plugin failed to load on PHP 8.0 (a `never` return type, which requires PHP 8.1), causing a fatal error on activation.
* Added `readme.txt`, `LICENSE`, and directory index files.

= 2.5.82 =
* Fixed meta title/description not saving on brand-new posts.

= 2.5.81 =
* Internationalised the admin UI (546 translatable strings) and added a translation template.

= 2.5.79 =
* Fixed the Social OG / Schema / Robots tabs in the post editor jumping back to Meta Info.

= 2.5.77 =
* Fixed "HTTP 411" when submitting the sitemap to Google Search Console.

= 2.5.76 =
* Fixed the audit progress bar appearing stuck during finalisation.

