Content Performance Analyzer changelog

= 1.9.6 =
* An impression drop of 30% or more is now diagnosed on any page with 1,000 or more impressions, not only on pages with 5,000 or more. A page whose impressions fell while its position improved and its clicks rose is still not called decay
* The "pages that were previously healthy" notice now counts only pages that an earlier scan measured as healthy. Pages still waiting for their first scan and pages missing from one sync no longer read as newly flagged
* The below-peak trend note reads "45.4% below" instead of "-45.4% below"
* The bundled Freemius SDK is updated to 2.13.5
* save_fix_plan accepts every query that get_page and get_page_context show, including the 90-day rows added to a thin list, and the automatic primary keyword picks from the same rows
* serp_source now describes only the competitor cache, looked up for the plan's keyword and the page's own best query, so it no longer changes with the last saved plan. A new serp_features_source says whether the scan's own SERP reading exists
* A page that declares a canonical pointing at another URL, and a page older than 60 days with no search impressions in either window, are diagnosed as technical and flagged as SEO instead of reading as healthy
* Zero-width characters are removed from query text, and a two-letter query is never picked as a primary keyword

= 1.9.5 =
* A slow slide is now diagnosed when clicks fell in four of the last five 28-day periods and sit 30% or more below their 6-month peak, even if the newest period ticked up. A non-search drop in the same window is still named in the evidence
* A scan is completed by one request only, so two status checks arriving together no longer log the finish twice

= 1.9.4 =
* Data health now reports when GA4 returns no current-period views for more than 10% of the pages that had views last period, with the counts. A failed GA4 request during a scan is retried once with a longer timeout and is no longer reused by the rest of the scan
* A page missing from the GA4 pull is unknown, not zero: diagnosis text shows "n/a" instead of "0 → 0 views", GA4-based diagnoses and the GA4 drop flag skip the page, and the evidence says only Search Console was judged
* A scheduled scan now finishes itself: the last run sets the status to completed and records the finish time, a status that says "running" with nothing left to do repairs itself, and the assistant's start_scan works again after a finished scan. Scan start, finish and GA4 failures are kept in a short log that survives past 24 hours
* Reading level reads the article's own paragraphs instead of the first paragraph on the page, which was template text on every page
* Pages under 60 days old are labeled New page or Building baseline even when traffic is growing
* A zero-click diagnosis drops to weak, and says why, when the search results show no answer box and no AI Overview
* Impression drops of 30% or more on pages seen 5,000 times or more, clicks falling for three or more periods in a row, and impressions falling to zero are now diagnosed
* Diagnosis wording follows the numbers: clicks that rose say "rose", an improved position is never called "held" or "not recorded", a ranking slide drops the demand wording, a drop much larger than last year's is no longer called seasonal, and losing low-ranking impressions while clicks rise is not decay
* Evidence names its date windows: 30-day totals, 28-day trend periods and the 90-day cannibalization pull
* When a page's 30-day query list is thin, plans and the assistant get extra rows from the page's 90-day query pull, marked as 90-day rows
* The automatic primary keyword skips login, download and support searches for other pages, and queries another page earns more clicks on
* The question-heading check only counts headings phrased as questions and lists the headings that passed. Pages scanned earlier are judged by the same rule
* The assistant sees every section heading, filtered by the same rule as the coverage map; "Featured resources" and "Related resources" end the article
* Title variations keep the topic of a buyer's guide, add a year only to dated titles such as lists, guides and laws, keep the brand last, and never end on a dangling list word
* Smaller fixes: tooltips for the No issue and Content flags, plan and coverage local times in the report time zone, every saved plan carries estimated_impact, draft assets written for a replaced plan stop showing, near-empty page responses are fetched again, and the freshness record shows why the last edit did or did not count as a refresh

= 1.9.3 =
* Content Insights is now visible to every role that can edit posts (Administrators, Editors, Authors and Contributors). Subscribers still can't open it
* Non-admin roles see the table, the details modals and the CSV export. Scans, retries, AI generation, Mark fixed, refresh drafts and every other change to the site stay with administrators, and those controls are hidden for other roles
* The Edit link on each row only shows when the viewer can edit that post
* URL Management, Internal Links and Settings stay admin-only
* Developers can change the viewing capability with the `cpanalyzer_insights_capability` filter

= 1.9.2 =
* Pro: a new AI Engine choice in Settings — "Included hosted AI" (the default) or "My own assistant only (MCP)". With the assistant engine, Claude Code, Claude Desktop, Cursor, Codex or another MCP client writes fix plans, coverage maps, draft assets and refresh briefs on your own Claude or GPT subscription, and the site makes no AI request at all: scans, retries, background fill-in and every generate button stop before contacting the hosted AI
* Alt text, evidence audits, answer blocks and AI link suggestions have no MCP tool yet, so they stay off while the assistant engine is selected
* Settings warns when the assistant engine is selected but the MCP endpoint is off
* Notices, the scan progress bar and the sync status point at the assistant's fix_plan prompt instead of asking for a key or a license
* A background AI fill-in scheduled before the AI was switched off now stops instead of retrying

= 1.9.1 =
* Coverage labels match their evidence: Covered (an exact phrase, or a heading an AI verdict named), Likely covered (every meaningful word matched a heading), Mentioned in text, and No heading found. The summary reads "3 covered (1 exact), 9 mentioned, 2 not found", and the dashboard badge counts only exact matches
* A sibling page appears, and turns the advice into a link, only when it also ranks for one of the branch's queries, the query is not only brand words, and the page earns clicks on it. It is shown as "Also ranks for", never as already covering the branch
* A "What is ..." H1 can answer a definition branch
* With an AI key, the branch call also returns a verdict per branch; a verdict whose heading the page does not have is rejected and the rules verdict stands. Each branch shows whether AI or the rules judged it, and flags a covered-vs-missing disagreement as disputed. A connected assistant can send verdicts through save_coverage_map under the same check
* Competitor search never counts the site's own pages, even when called with a bare path, and cached results are filtered on read; caches from before 1.8.9 are cleared on update
* The plan-date line names the site's timezone

= 1.9.0 =
* Sibling pages come from Search Console: a page that ranks for one of a branch's own queries is its sibling, the most clicks winning. The title fallback now needs every specific label word, never matches on one word, and never picks a product page for an article branch. Each sibling says why (shared query or title) in the coverage table, the writer brief and the connected assistant
* Covered verdicts take an exact phrase first, compare words on light stems (detect/detecting, policy/policies), use each heading for one branch only, and need a real definition heading for "X definition" and "what is X" branches. Each covered branch says whether a phrase or its words matched
* Call-to-action headings inside articles ("Future-proof your workforce ...", "Get started today") no longer count as sections, on new scans and on stored pages (filter: cpanalyzer_cta_patterns)
* Title variations keep only the year, brand-position and trimmed forms; a trimmed title cuts at a colon, comma, dash or bar or not at all, and "vs.", "vs" and "v." are written "vs."
* Search Console query text is cleaned once (trailing punctuation and parentheticals dropped, the raw text kept). Primary keywords go to the most-clicked relevant query, and a brand-only query stays with the page that ranks best for it
* A page whose impressions halved at a steady position reads demand decay, not zero-click
* Cannibalization also reads each page's stored query rows, so two pages sharing a query at 50 or more impressions each, or three smaller ones, list each other

= 1.8.9 =
* The scraper reads section headings and page text from the article itself: notice bars, chat modals, tables of contents, related-article cards and calls to action are left out, and everything from a "What to read next" style heading on is dropped. Stored page text grows from 5,000 to 12,000 characters (filters: cpanalyzer_chrome_markers, cpanalyzer_tail_markers)
* Coverage maps ignore headings that are another page's title, read up to 120 headings, and are re-checked in the background after the update. Stored pages are cleaned without a rescan
* A sibling page needs the branch's words in its title or H1, not one shared word
* A question branch is covered only by a question or definition heading, and a one-word branch only by a short heading
* Keyword selection skips version-number, overlong and language-tail queries, brand-only queries on pages that do not own the brand, and queries past position 30 when closer ones exist; it prefers the whole phrase over a truncated one and needs a word from the title or H1. Coverage maps use the 90-day queries when the 30-day rows are empty
* Title variations keep acronyms and the page's own spelling (ADA, TimeClock), write "vs.", skip question forms that read wrong, and never exceed 70 characters
* Search Console keyword recovery continues across runs from where it stopped (50 pages per run by default) until every page missing queries has been tried
* A page whose impressions fell by half from 1,000 or more is no longer called low volume because its clicks were few
* The fix-plan tool schema states each text field's length limit; competitor search skips the site's own brand domains, login and error pages, and pages under 200 words; the details panel shows when the plan was written and which timezone it uses

= 1.8.8 =
* The refresh brief sends the page's top 10 Search Console queries with every column, its stored coverage map, cached competitor headings for its primary keyword, and a structured page excerpt (title, headings, intro, question answers, close) in place of the first 1,500 characters. A competitor gap claimed with no competitor data is removed
* Fix plans send 10 Search Console queries per page instead of 5
* GA4 reads the same 30 complete days as Search Console, ending at the same lag date, for views, engagement, conversions and the previous period
* Search Console keyword recovery is capped at 25 pages per scan by default (filter: cpanalyzer_gsc_keyword_recovery_limit, 0 lifts the cap)
* Every AI payload and the connected assistant's page output carry an evidence block: queries sent against queries stored, page text sent against stored, competitor pages analyzed against attempted, where search data came from, and how the freshness date is known
* Deterministic title variations (year, brand last, trimmed; the keyword-first and question forms were retired in 1.9.0) appear in the details panel, the connected assistant's page output, and Pro draft assets
* Pro draft assets now send each Search Console query's text; it was sent empty

= 1.8.7 =
* Coverage maps use word boundaries, ignore site-wide heading words for sibling suggestions, and require one heading to answer a branch before marking it covered. Existing maps refresh in the background.
* Primary keywords skip operator queries and unrelated Search Console terms. Coverage inputs label title fallbacks and do not search competitors with them.
* Coverage inputs merge up to 100 longer-window queries with sync queries; inbound links include rendered content links.
* MCP output explains why page impressions exceed listed queries and shows local dates alongside UTC dates.
* Scraped and stored titles, descriptions and headings decode HTML entities. Existing stored content is updated without a rescan.

= 1.8.6 =
* Sites in a subdirectory (a subdirectory multisite subsite, or WordPress installed under a folder) now resolve analyzed URLs to their posts: the site folder was being added twice, so every per-page feature reported no post there. Root-domain sites are unaffected

= 1.8.5 =
* Durable freshness: the plugin keeps its own per-page record of meaningful content changes, compared on every save of a tracked page, so revisions are optional evidence instead of a requirement. Cleaning revisions or deleting the history cache can no longer erase a detected refresh
* A meaningful change is measured, not guessed: formatting, block markup, punctuation, link targets, tracking parameters, a title edit or one changed year never count; several rewritten paragraphs, a new section, a material expansion, replaced statistics, or small edits that add up do. A lone revision with nothing to compare against is no longer treated as proof of a change
* Pages with no change on record get a baseline estimated from the last WordPress edit (or the publish date), frozen when the record is created, so later minor saves no longer keep pushing the refresh date back. Every place that shows freshness - the table, the details panel, the dashboard widget, the CSV export, AI fix plans and the connected assistant - says whether the date was detected, confirmed by you, or estimated
* The details panel now shows the publish date, the last WordPress edit, the last meaningful refresh (or "Unknown"), and the freshness baseline as separate facts, with revision and change-detection coverage
* New "I refreshed the content" control in the details panel records a manually confirmed refresh, shown as such and reversible. Mark fixed keeps its recovery tracking but no longer resets the refresh clock; existing Mark fixed entries keep their recovery verdicts and are not treated as content refreshes
* Freshness safeguards: paragraph splitting and source-line reflow do not count as rewrites; partial content cannot verify a whole-page refresh; the first small edit remains in cumulative comparisons; two saves of the same page landing at once can no longer leave the record describing the older version
* AI plans generated before the freshness upgrade are marked expired without deleting their content or starting automatic regeneration
* Status and the next due date now come from the same calendar: a page baselined on March 1 with a six-month cycle is due on September 1, not already due on August 30
* What to do now follows the diagnosis: a recent drop says monitor and recheck, an unconfirmed drop says find the cause first, a zero-click page is about winning back the click, demand decay says check demand before deciding, and a page that changed recently is checked before another rewrite. The same rules reach AI fix plans and the fallback text
* A plain save inside a traffic drop is no longer described as an edit; only a detected content change is. A technical diagnosis still mentions the last save, labeled as a save
* CSV export: eight columns appended (last WordPress edit, last meaningful refresh, refresh evidence, manual confirmation, freshness baseline and its source, next due date, change-detection coverage). Existing columns are unchanged
* For page builders that keep text outside the editor, the panel reports limited coverage instead of claiming a verified refresh; the cpanalyzer_freshness_content and cpanalyzer_freshness_coverage filters let a site supply that text

= 1.8.4 =
* Every heading on a page now reaches a connected assistant as the heading itself. They arrived as the word "Array", so a plan written outside the site was working from a page that appeared to have no headings at all
* A fix plan saved by a connected assistant can now carry the rest of what the site's own generator stores - the kind of work, the expected click-through and conversion change, how to measure it, a confidence and an estimate in hours - instead of dropping them on every save. The opportunity score stays the plugin's own arithmetic, whatever a plan sends
* Coverage maps stored before this release are recomputed in the background after updating, ten at a time: branch impressions, the "link to this page instead" suggestions, the covered, buried and missing verdicts, and the action each branch earns all catch up with the current rules, from data already on the site - no AI call and no competitor search

= 1.8.3 =
* A fix plan saved without an action title is now filed under an instruction written from the page's own diagnosis - "Refresh the content to recover rankings for hourly time tracking" - instead of the first sentence of the summary, which was usually the diagnosis restated and read as a quote
* A derived content priority now counts the clicks a page is leaving on the table, not only its decay: a page holding position 5 on 20,000 impressions at a 1% click-through rate comes back high priority even with nothing wrong with it, and the same number is available to a connected assistant as opportunity_clicks
* A Search Console query needs one click, or ten impressions, before it can be picked as a page's keyword automatically. On a page where every query has zero clicks, a two-impression misspelling used to win the column; now it stays blank until there is something real to put in it
* The "link to this page instead" suggestion on the coverage map now needs genuine overlap: two of the branch's words in the other page's headings and at least 60 per cent of them, a heading carrying the search word for word beats a loose match, and words every page shares - software, tool, platform, guide - no longer count as overlap at all

= 1.8.2 =
* A fix plan saved by a connected assistant can send its own action title, content priority and primary keyword, and each is stored as sent. Omit one and the site derives it from the page's own data - the title from the first sentence of the summary, the priority from the stored diagnosis, the keyword from the page's Search Console queries - instead of leaving the previous plan's label in place
* Coverage branches saved by a connected assistant now carry the Search Console impressions behind their own queries instead of zero, so the branch table and the action each branch earns read from real demand
* One text cleaner for every stored plan, whoever wrote it: "Oracle Time & Labor" keeps its ampersand instead of showing as "&amp;" on screen, and a stray backslash in front of a quote is dropped
* Suggested keywords and technical issues declare their limits and a plan that goes over is refused with a clear message, instead of being cut at 120 characters with no warning. A keyword can now be up to 240 characters, so a long conversational query is kept whole on both paths
* Search-operator searches, percent-encoded strings and bare URLs in Search Console data are no longer treated as keywords, so none of them can become a page's primary keyword or fill the Keyword column

= 1.8.1 =
* Pro MCP: collect_coverage_inputs reads People Also Ask, related searches and competitor headings from the cache only, and reports serp_source (cache, live, not_cached or none); pass fetch: true to spend one hosted search on a cache miss. save_coverage_map never searches. Previously a cache miss on either call ran a live search and spent one of the site's hosted searches, contrary to the read-tools promise
* Coverage map: when no SERP data was read, per-branch competitor counts are unknown (null) instead of reading as "0 of 0 competitors cover this"; a failed live search is reported the same way
* External Services: the included Pro AI service description now matches the relay — every request is answered by Kimi K3 on the plugin author's Modal deployment; the AI gateway path described in 1.8.0 was removed because it rejected server-to-server requests

= 1.8.0 =
* New Pro feature — Bring your own assistant (MCP): a Model Context Protocol endpoint at /wp-json/cpanalyzer/v1/mcp that lets Claude Code, Cursor or Claude Desktop work directly against this site's audit on your own AI subscription. Streamable HTTP with JSON responses, stateless, HTTPS only, and off until you enable it in Settings
* Seventeen tools: nine that read (site summary, page list, everything known about one page, the grounding payload, the coverage map and its inputs, cached competitor context, data health, scan status) and eight that write (fix plan, coverage map, draft assets, refresh draft, apply and undo title/meta, mark fixed, start scan)
* Five prompts — fix_plan, coverage_branches, draft_assets, refresh_brief and weekly_review — each carrying the same rules the plugin's own generator follows, so a client model is held to the same standard; and one resource, the site summary
* Tokens with scopes: create a token per client, read-only or read and write, shown once and never stored. Application Passwords work too. Rate limited to 120 requests a minute per token, with every call logged by tool name, scope and duration
* Writes are opt-in twice: the site setting "Allow write tools" and the token's write scope. Read tools never touch your pages, competitor context and coverage inputs read the cache unless you explicitly ask for a live search (fetch: true), saving a coverage map never searches, and no MCP call spends a hosted AI generation
* New Settings card with the endpoint URL, the token table and copy-ready snippets for each client
* On WordPress 6.9 and later the same tools also register as core Abilities under the cpanalyzer/ namespace, so the official WordPress MCP adapter and WP-CLI see them
* Internal refactors with no change in behaviour: the fix-plan rules and the draft-asset rules each moved into a single shared getter, and mark-as-fixed, apply/undo title and meta, and scan start are now callable methods behind their existing AJAX handlers

= 1.7.3 =
* GA4 and Search Console rows that normalize to the same page path (trailing slash, query string, letter case, http/https, www) are now summed instead of the last row overwriting the others: views, users, clicks and impressions add up, engagement, duration, CTR and position are recomputed as weighted averages. Affects the current and previous 30-day windows in both collectors and the year-over-year windows. Symptom fixed: a busy page showing 12 views and 4 impressions next to hundreds of AI referrals, then being diagnosed as an unconfirmed drop
* The scheduled weekly scan records previous-period clicks, CTR and position for every page, not only for pages that vanished from search — on that path the decay classifier had no Search Console baseline to compare against
* Keyword cannibalization counts each page once per query: several spellings of one URL no longer read as a page competing with itself, and the presence threshold applies to the page's summed clicks and impressions
* Single-page Search Console lookups (metric and keyword recovery, the demand check, the coverage map's query pull) and the GA4 metric recovery match every spelling of the page with an anchored regex filter instead of one exact URL, so a site whose canonical URLs end in a slash no longer gets empty results from them
* Year-over-year and cannibalization caches use new keys, so the first scan after the upgrade works from summed rows

= 1.7.2 =
* Page age gate: a page published under 30 days ago is "New page — not enough history", 30–59 days is "Building baseline"; neither is diagnosed or flagged as decay (a total impression collapse still reads as a technical issue at any age)
* Low-volume gate: a drop on fewer than 100 previous views or 10 previous clicks is "Insufficient traffic for decay analysis" with the raw numbers, not a decay call
* Trend gate: when only the latest 28-day click bucket fell and the five before it were flat or growing, the diagnosis is "Recent drop — monitor" (weak, unflagged) instead of decay; three or more declining buckets now confirm a ranking slide
* Search Console windows end on the last fully reported day (3 days ago by default, filter `cpanalyzer_gsc_lag_days`) in every pull — current and previous comparison, keywords, recovery, cannibalization, the coverage map's 90-day query pull, the demand check and the trend buckets — so both windows hold the same number of complete days
* The flag column shows the diagnosis ("Non-search drop", "Possible tracking break") in place of the generic "Decay" badge, and rows with a monitor state show it even when nothing is flagged
* The AI badge reads "AI referrals: N" with a tooltip that says what it counts and what it does not
* The scheduled daily refresh now runs the same decay classifier as the browser scan, with the page's publish date, and keeps the trend, year-over-year, cannibalization and SERP data the scan attached to each page, so the two paths agree and a refresh never wipes a diagnosis; an impression collapse or a tracking break is judged before the age and volume gates; a measured noindex or foreign canonical overrides the new states; year-over-year windows follow the same lag-aligned dates as the comparison windows

= 1.7.1 =
* Conversions that were not measured now stay unknown through the whole AI pipeline: an explicit null is no longer cast to 0 on its way into the payload, the insights rows or the CSV, and a current unmeasured value overrides an older stored count instead of letting it survive the merge
* Pro draft rules: a measured 0% click-through on a page with impressions now counts as weak CTR, so the title and meta description suggestions fire instead of being skipped
* The automatic off-peak full scan runs weekly instead of daily, on the day you choose in Settings (new Off-Peak Day option, default Sunday); changing the day or hour reschedules it immediately, an existing daily event is moved to the weekly slot on the next page load, and the Settings page shows the next scheduled run

= 1.7.0 =
* New Coverage tab in the page details: the likely information branches behind a page's main search, each marked covered, buried, or missing, with an action per branch. Mapping runs only when you click it; nothing changes on your pages. Pro adds an evidence audit, drafted sections, and a writer brief.
* Diagnosis integrity: pages whose search or GA4 traffic fell to zero now keep their previous-period baseline and get diagnosed instead of disappearing; one flag ruleset replaces four (low engagement no longer counts as decay; a page that could not be read never flags as thin); a demand-decay diagnosis is only confirmed when a query-level check agrees, and the consolidation advice is softened; a seasonal call now requires last year's window to have dropped too; Core Web Vitals show as unknown when unmeasured and are labeled as lab data, with field data when available; the wording-based freshness score is removed; the opportunity score is computed from real CTR units; AEO checks count lists and headings inside the content only and require the answer paragraph to relate to its heading. AEO scores can shift after this update because navigation menus no longer count as lists.
* Follow-up fixes from a second review: a zero row is synthesized only when the current-period result was complete and successful, never from a failed or truncated request; the demand check looks up queries missing from the top rows one by one and never assumes an unchanged ranking; the AI is told to hold consolidation and redirect advice until the diagnosis is confirmed; batch scans count the same conversions as scheduled scans (custom events, GA4 key events, then form_submit) and leave the count unknown when the request fails; a seasonal call also requires this year's decline to be comparable to last year's; a Slow real-visitor Core Web Vitals verdict now flags performance, and Max Potential FID is shown under its own name; the SEO flag and the opportunity score share one position-based CTR benchmark; thin content uses the same 120-word allowance for pages with a form or CTA as the AEO length check; the opportunity score is always the plugin's own arithmetic.

= 1.6.6 =
* Groq free provider: openai/gpt-oss-120b stays the default, qwen/qwen3.8-27b replaces llama-3.3-70b-versatile as the only fallback; a model chosen by hand in Settings still wins
* Qwen reasoning is off by default so the model spends its budget on the answer (filter `cpanalyzer_groq_reasoning_effort` to change), and any thinking tags that still appear are stripped before the JSON is read
* Groq requests get at least 120 seconds so a slow answer does not time out before the fallback gets its turn; the browser scan loop waits up to 3 minutes per chunk to match
* llama-3.3-70b-versatile removed from the model list (an existing manual selection keeps working)

= 1.6.5 =
* Pro: the full draft is now a rewrite of the real WordPress post rather than a fresh 600-900 words written from a summary. Each section is rewritten on its own, and every table, image, embed, shortcode, button and column layout is carried across untouched, in its original position
* Pro: a completeness check runs before anything is saved. A draft that drops a heading, misses a numbered section its title promises, or loses a table is refused and reported instead of stored — the AI gets one repair pass first. Length is reported as a warning, not a refusal
* Pro: the AI notice no longer sits in the draft's content, so it can never become the page's meta description. It shows as a notice in the editor instead, and the draft excerpt is never left empty
* Pro: new "Publish as replacement" copies the reviewed draft into the live article in place. The URL, publish date, author, categories, featured image and SEO settings stay exactly as they were, the previous content is kept as a WordPress revision, and one click undoes it
* Pro: the draft status line now tells you to click Open draft, instead of relying on a pop-up window that browsers block
* Pro: the direct-answer question heading now lands after the first paragraph, inside the opening hundred words, where search engines and AI answers actually read it
* Pro: a draft that still holds an [add: ...] placeholder cannot be published as a replacement, and the completeness check runs again at that moment in case the draft was edited
* Pro: a suggested title no longer stores the " | Site Name" suffix your SEO plugin or theme already adds
* Drafts written before this update keep the AI notice as their first paragraph, where it could become the meta description. Updating removes it from those drafts automatically, and any published page still carrying it is listed for review rather than edited for you

= 1.6.4 =
* Batch scans no longer stop after 20 pages or wait on WordPress cron to continue. The open browser tab drives the run, so a scan finishes in one sitting on hosts where cron never fires
* Data collection and AI fix plans are now two stages: every page gets its GA4, Search Console and on-page data first, in seconds each, and the AI plans for flagged pages are written afterwards. The Insights table fills within minutes instead of after an hour
* The Batch Processing box shows a live progress bar with the stage, the page count, the URL being scanned, and an estimated time left
* GA4 and Search Console data is pulled once per scan and reused for 15 minutes instead of being re-pulled for every batch
* A site with "Discourage search engines from indexing this site" turned on is now reported once as a site setting, rather than giving every page the same "remove the noindex tag" fix plan
* Closing the tab no longer loses the run: background processing continues, more slowly, and picks up where the scan stopped
* The hourly stuck-process cleanup now reads the value the batch actually writes, so an abandoned run clears itself

= 1.6.3 =
* When a page's HTML cannot be downloaded (self-signed certificate, firewall), the plugin now records the failure and its reason instead of storing an empty result that read as 0 words, 0 links, no H1, and no CTAs — the AEO score shows a dash, the AI is told the on-page checks are unavailable, and the row says "Page content not read — rescan". Rows from earlier scans in that state are marked the same way on update
* On local and development hosts the scraper no longer verifies TLS certificates by default; a saved Verify SSL setting still wins
* Batch scans process several pages per run: the time guard measures the whole run instead of one page, cron runs may use most of PHP's own limit, rows stuck in progress for ten minutes are released automatically, and the next run is scheduled five seconds later instead of thirty
* Pro: status messages say "Contacting Content Performance AI" instead of the pre-upgrade provider, and the provider option self-heals on the next admin visit
* Pro: writing drafts for a page whose content was never read explains that instead of "The AI returned no usable drafts"; a page that already passes every draft check says so too

= 1.6.2 =
* Pro: an active license now routes AI recommendations to the hosted engine as soon as it is activated. Previously the plugin kept calling the provider and key chosen before the upgrade until Settings was saved once, and an expired personal key produced "Invalid API Key" errors on a paid plan
* Pro: when the plan lapses, the provider returns to the key you used before, rather than staying on a hosted engine the site can no longer use
* Pro: hosted AI failures now report one clear reason instead of a per-model list

= 1.6.1 =
* The Freshness cell is now a status badge (Fresh, Refresh due soon, Refresh due, Refresh overdue) with one fact line and a View details button. The panel shows when the page was published, its last content update, its last save, how many content updates it has had, the refresh cycle and why it applies, and a single What to do action chosen from the decay type — so a page losing non-search traffic no longer reads as "refresh now, refreshing is pointless"
* The legend explains the four freshness states and the 6/9/12-month cycles
* The AI Action column now says "AI plan expired" when a recommendation is older than its data; the previous "Expired - needs refresh" read as if the page needed refreshing

= 1.6.0 =
* Added AI crawler rules: a per-bot allow or block checklist that writes User-agent rules into the WordPress robots.txt, with a recommended preset that allows AI search engines and blocks training-only crawlers, a preview of the exact block, and a warning when a physical robots.txt file means WordPress is not serving it
* Added /llms-full.txt alongside /llms.txt, carrying the same page index plus each page's text with shortcodes and markup stripped, capped at 60 pages and 3,000 words per page, cached for six hours
* Added IndexNow: publishing, updating, or unpublishing a page notifies api.indexnow.org, which relays to Bing, Yandex, Seznam, Naver, and Yep. Includes a served key file, a test ping, a 10-minute debounce per URL, a skip on non-production sites, and a log of the last 20 pings. Google does not participate in IndexNow
* Pro: apply the suggested title or meta description to Rank Math, Yoast SEO, or SEOPress in one click from the drafts modal, with undo restoring the previous value

= 1.5.3 =
* Freshness column wording: the headline now always reads from the same clock as the status — "Content updated N ago" for a revision-backed change, "Marked refreshed N ago" after Mark fixed, or "No recorded content refresh" when no such revision exists. A plain save is shown only as "last saved" and is never counted as a content update
* Pages with revisions switched off read "edit history unavailable" instead of "0 updates", and the AI fix plan uses the same evidence-aware wording

= 1.5.2 =
* The Age column is now Freshness: each page shows when it was published, how many recorded content updates it has had since, and when the last one landed. A content update means a saved revision that changed the title or at least 10% of the text; a plain save is reported separately as "last saved" and never counted as a content change
* Added a refresh baseline: decaying pages are due every 6 months, SEO-opportunity pages every 9, everything else every 12, with Fresh, Due soon, Due, and Overdue states. Pages with no content update on record count from their publish date
* Freshness reaches the dashboard widget, the CSV export, and the AI fix plan, so recommendations stop suggesting a refresh for a page that was just revised
* Flag badges, the legend, toolbar buttons, settings headings, and status messages now use WordPress Dashicons instead of emoji, so they render identically on every site — previously, sites that could not reach the emoji image CDN showed a broken-image icon beside each flag

= 1.5.1 =
* Added a one-time review request that appears only after the audit has delivered real results — at least five pages analyzed and fix plans written — and only on the plugin's own screens. Answering or declining silences it permanently, and it shares its once-only state with the existing post-recovery ask

= 1.5.0 =
* Integrated the current Freemius WordPress SDK for Pro licensing, trials, in-dashboard checkout, and paid updates
* Added optional hosted AI for active Pro licenses, with no OpenAI, Groq, or Gemini key required and a visible monthly allowance
* Added page-specific ready-to-use drafts for titles, meta descriptions, headings, FAQ answers, quotable passages, and internal-link suggestions
* Added complete 600–900 word content generation into a separate WordPress draft; the live page is never overwritten
* Kept problem discovery and clear recommendations free: GA4/GSC analysis, decay diagnosis, AEO checks, question gaps, exports, recovery tracking, and BYOK AI remain available without Pro
* Pro replaces the provider picker and API key fields with a single hosted-AI panel showing the remaining monthly allowance — nothing to configure
* Pro raises the hand-picked PageSpeed selection from 15 pages to 30
* Pro includes competitor gap analysis with no Serper key — 600 searches a month through the hosted service, with a personal key still taking precedence
* Deleting the leftover free copy after upgrading to Pro no longer removes your data — uninstall cleanup now runs through the licensing SDK, which skips it while either copy is still active
* Organized the page-plan modal into tabs, so a long recommendation set is scannable instead of one continuous column
* Reads an existing title and meta description from Rank Math, Yoast, SEO Press, or All in One SEO before reporting either as missing

= 1.4.7 =
* Fixed AI recommendations being cut off mid-word
* The sync banner now reports the whole job and turns green only when data collection and AI have both finished
* Added warnings when Search Console returns no data, page content cannot be read, or scheduled work has stalled
* Flags copy that presents an earlier year as current, while leaving genuine historical references alone
* A page marked as fixed with no recorded traffic now reads "waiting for traffic data" instead of "no lift"
* Recovery verdicts also run on the daily cron, and deactivation clears three previously orphaned scheduled tasks

= 1.4.6 =
* Recovery tracking: mark a page as fixed from the insights table and the plugin records the moment's clicks, views, and pre-decay peak, then judges the fix after 28 days — Recovered (back near its peak), Improving (clearly above the pre-fix level), or No lift (final at day 60). Verdicts average whole weeks from the local snapshot history, so one good day cannot fake a recovery
* One-click refresh drafts: create a draft copy of any page — content, categories, tags, and featured image — with the page's diagnosis and AI fix plan embedded at the top as an editorial note. The live page is never touched, and repeat clicks reopen the same draft instead of stacking duplicates
* Dashboard widget: the top priority pages (worst decay first, ranked by clicks lost) and a summary of how your applied fixes are performing, on the main WordPress dashboard
* New Age column in the insights table shows time since each page's last update, highlighting pages untouched for over 18 months
* Verdict notices: when a fix you marked crosses to Recovered, Improving, or its final no-lift verdict, a dashboard notice tells you — no need to keep checking the table. Opening Content Insights clears it
* A one-time review ask appears with your first Recovered verdict — the moment the plugin has actually proven a fix worked. One click to dismiss forever; it never asks again either way
* The homepage now resolves to an editable post in the insights table when a static front page is set

= 1.4.5 =
* Added a local weekly per-page performance snapshot data layer.
* Captures traffic, flags, decay type, and AEO score after data refreshes, at most once per day.
* Keeps data on the site, retains 78 weeks of history, and removes snapshot data on uninstall; no settings or external calls were added.

= 1.4.4 =
* Analysis is no longer "done" with gaps: flagged pages the AI run could not finish now fill in automatically in the background, one page every couple of minutes
* The queue paces itself under free-tier limits, backs off when rate-limited, skips pages that cannot fit even after compression, and stops with a clear message on configuration errors
* The insights page shows calm automatic progress with the next attempt time while leaving the existing manual retry available

= 1.4.3 =
* Oversized AI requests now recover automatically: when a content-heavy page produces a request bigger than the provider's token budget or context window (for example Groq's free-tier 8,000 tokens per minute), the plugin retries once with a compressed payload — trimmed body text, no competitor excerpts — instead of failing the page. All metrics and the decay diagnosis stay in the request, so recommendations remain grounded
* The insights page no longer shows an endless "Processing…" spinner when nothing is running: the spinner now appears only while work is genuinely in flight, and flagged pages left without AI suggestions after a failed or rate-limited run get an honest notice explaining what happened and pointing to the Retry button
* Sites without an AI key configured now see clear guidance on flagged pages instead of a permanent processing state

= 1.4.2 =
* Clearer non-search diagnosis: when a page had real GA4 traffic but almost no Google search clicks and dropped, the plugin now says the drop came from another channel (direct, email, social, referral, or internal links) and points to the GA4 channel breakdown — instead of the vague "not enough Search Console history, re-check next run"
* Pages with a search footprint but negligible clicks are classified as a non-search drop with a channel-breakdown fix; pages with no search footprint at all get a cautious hypothesis that also suggests confirming the page is indexed
* Reflects a real distinction: GA4 views count every channel, while Search Console clicks count only Google organic search — a page can have high views and near-zero search clicks
* Includes the homepage and per-page metric collection fixes: host-aware Search Console paths so subdomains no longer overwrite the homepage row, GA4 ordering so top pages are never truncated out, and targeted recovery for any analyzed URL the bulk pull missed
* CSV export now includes the full AI recommendation set (SEO, AEO, GEO, engagement, measurement plan), model used, decay type and evidence, AEO readiness score, AI referral sessions, and inbound internal link counts
* Added a link to the plugin privacy policy on the settings screen

= 1.4.1 =
* Seasonality control: every demand-decay call is checked against the same 30-day window one year ago in Search Console. A drop that matches last year's numbers is diagnosed "Seasonal pattern (expected)" — shown in green, never flagged, and the AI is told no action is needed
* Technical/indexing detection: impressions collapsing to near zero is diagnosed as an indexing problem, not demand decay. The live-page scrape confirms it — a noindex meta tag or a canonical pointing at a different URL upgrades the diagnosis to confirmed with the exact cause named
* Tracking-break detection: GA4 views collapsing to near zero while Search Console still records clicks is diagnosed as a broken GA4 tag, not a traffic loss — check the tag before touching the content
* AI Overview confirmation: the live SERP check now detects Google AI Overviews in addition to answer boxes when confirming zero-click capture
* noindex tags and canonical mismatches now appear in every page's content issues list, whether or not traffic dropped
* AI recommendations updated for all three new diagnosis types: technical issues lead with the indexing fix, tracking breaks lead with the tag check, and seasonal dips get no busywork
* Cleaner settings: the legacy OAuth Client ID/Secret fields are hidden now that Connect with Google is one click (they still appear for sites already using their own credentials), and PageSpeed options only show while the feature is enabled

= 1.4.0 =
* Decay-type diagnosis: every traffic drop is classified from real period deltas — ranking decay (position slid), zero-click capture (still ranking, SERP answers before the click), demand decay (topic losing searches), or a non-search drop (organic healthy, look at other channels) — with the evidence and the matching fix shown on each page
* Self-inflicted decay detection: if a page was edited inside the drop window, the diagnosis warns to compare the previous revision before assuming natural decay — only possible from inside WordPress
* 6-month click trends: each page's clicks are tracked across six 28-day periods to expose sustained declines and distance from peak, not just one week-over-week dip
* Keyword cannibalization detection: queries where two or more of your own pages split clicks over the last 90 days, listed per page with positions, so you can consolidate instead of rewriting
* Live SERP confirmation (optional, with a Serper key): confirms zero-click capture when an answer box sits above your result, and flags intent drift when the top 10 shifts to video, forum, or commerce formats
* Orphan-page detection: pages no other content links to are flagged, with an editorial inbound-link count per page (menus and widgets excluded)
* New-issue alerts: after any scan — including the daily automatic one — a dashboard notice lists pages that were healthy last run and are flagged now
* AI recommendations now match the fix to the diagnosed decay type: no more content-refresh advice for pages whose topic lost demand, and non-search drops are never framed as SEO problems
* Previous-period clicks, position, and engagement rate are now collected and shown alongside impressions, completing the period-over-period picture
* Fixed: decay flag compared a page against the wrong page's history in one code path; AI referral session counts could drop out of the insights table after a re-sync

= 1.3.0 =
* AEO readiness score: every page rated 0-100 on answer-engine quotability with a named pass/fail checklist (question headings, direct answers, structured data, lists, freshness)
* Question gaps: Search Console queries you rank for (positions 2-20) that no page heading answers — each a featured-snippet and AI-answer opportunity
* One-click answer blocks: AI writes a concise answer grounded in the page's own content, appends it as a Q&A block, and adds FAQPage schema (skipped if the page already has one)
* AI referral tracking: sessions arriving from ChatGPT, Perplexity, Copilot, Gemini, and Claude shown per page
* Optional llms.txt: a machine-readable site guide for AI crawlers, with automatic conflict detection if one already exists
* Choose your exact AI model per provider from a dropdown with cost hints, or leave it automatic; every AI action now shows which model produced it
* OpenAI automatic mode upgraded to the flagship model with graceful fallbacks; gpt-4o-mini retired
* Requests adapt automatically to newer OpenAI model requirements (max_completion_tokens, fixed temperature) and to restricted-project model allowlists
* Rate-limit resilience: no more retry cascades that burn free-tier quotas, partial results are saved with honest progress messages, and Gemini runs are paced automatically
* AI errors now name every model that failed and why, instead of only the last one
* Sharper recommendations: prompts require citing the page's actual data and concrete deliverables, and SEO/AEO/GEO fields are precisely defined (GEO now means generative-engine visibility, not local SEO)
* Live search on both page pickers; select-all applies to search results; the URL import picker loads automatically
* URL Status table refreshes automatically when background scans finish
* Groq is now the recommended free provider based on output-quality testing; status labels reflect the active provider

= 1.2.0 =
* One-click "Connect with Google" — sign in and pick your account, no Google Cloud project or OAuth credentials needed (your own credentials still work as an advanced option)
* GA4 property and Search Console site selection via dropdowns loaded from your connected account, replacing hand-typed IDs
* One-click page import: load every published page and post with checkboxes and select-all, instead of pasting URLs
* Content-only mode: fresh installs can scan for content issues before connecting anything, with a guided getting-started flow
* AI alt text fixes: see exactly which images lack alt text, then generate and apply alt text in one click — vision-based on OpenAI and Gemini, with library and page content both updated and existing alt text never overwritten
* Google Gemini added as an AI provider with a generous free tier, alongside OpenAI and Groq
* PageSpeed Insights now works without an API key through a free hosted service — the API key field is gone entirely
* Pick exactly which pages get PageSpeed analysis with a checkbox list (up to 15), or leave it automatic
* AI responses now use enforced JSON output for more reliable recommendations across all providers
* API key fields warn when the key format doesn't match the selected provider
* Image counts no longer double-count on sites using lazy-loading plugins

= 1.1.2 =
* Expired or revoked Google connections now show a clear reconnect notice on plugin pages instead of silently reporting zero GA4 and Search Console data
* OAuth failure logs now include Google's error code (for example invalid_grant) for faster diagnosis
* Fixed the analysis waiting banner staying visible indefinitely when pages in good standing were present

= 1.1.1 =
* Removed the Freemius integration and its activation opt-in screen for a simpler, self-contained setup
* Added a standalone uninstall routine that fully cleans up options, scheduled tasks, and plugin data on deletion

= 1.1.0 =
* All features are now fully free — removed license requirement and premium gating
* Data collection, batch processing, AI suggestions, internal links, and smart interlinking work without activation
* Conversions now use GA4 key events by default, with form_submit fallback and an optional custom event list
* PageSpeed analysis is now selective (flagged and high-traffic pages) instead of every URL on every scan
* Daily automatic rescan is now opt-in for new installs to protect your API quotas
* Safer smart interlinking: links are only inserted on exact anchor matches, never inside HTML tags or existing links
* Fixed applied interlinks being dropped on save in some Gutenberg blocks
* Data collection now reports API failures instead of showing success with missing data
* Off-peak scheduling defaults to the site timezone
* Security and cleanup: removed debug endpoints and dead code, hardened CSV export, moved inline scripts and styles to enqueued assets

= 1.0.0 =
* Progressive content analysis - analyzes all pages without artificial limits
* Smart waiting banner with realistic time estimates and auto-refresh
* Priority-based processing - flagged pages analyzed first for faster AI suggestions
* Intelligent AI timing - AI waits for complete content data for optimal recommendations
* Plugin page integration - direct Settings and View Insights links
* 3x performance improvement - reduced from 15.5s to ~3s average per page
* Dynamic batch sizing (6-12 pages) based on priorities and system performance
* Dynamic timeout prediction with 75-second safety buffer prevents all timeouts
* Real-time progress tracking with detailed status updates
* Enhanced user experience with transparent progress communication

= Previous Version =
* Complete rewrite with modular architecture
* Added automatic keyword analysis and optimization suggestions
* Implemented AI suggestion expiry system
* Enhanced dashboard with better UX
* Batch URL upload and retry/rescan features
* WordPress coding standards compliance
