{"id":294906,"date":"2026-04-11T07:07:29","date_gmt":"2026-04-11T07:07:29","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/essiow-ai-seo-suite-for-woocommerce\/"},"modified":"2026-05-14T21:28:59","modified_gmt":"2026-05-14T21:28:59","slug":"essiow","status":"publish","type":"plugin","link":"https:\/\/test.wordpress.org\/plugins\/essiow\/","author":23472740,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_crdt_document":"","version":"1.1.75","stable_tag":"1.1.75","tested":"6.9.4","requires":"5.8","requires_php":"7.4","requires_plugins":null,"header_name":"Essiow \u2014 AI SEO Suite for WooCommerce","header_author":"Essiow","header_description":"All-in-one AI SEO suite for WooCommerce. Auto-generate optimized product descriptions, category pages, blog articles (1500-5000 words), and add an AI sales chatbot to your store. Supports Yoast SEO, Rank Math, AIOSEO. 8 languages, bulk optimization, JSON-LD schema.","assets_banners_color":"ffffff","last_updated":"2026-05-14 21:28:59","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/essiow.com\/plugin","header_author_uri":"https:\/\/essiow.com","rating":0,"author_block_rating":0,"active_installs":10,"downloads":1055,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.1.10":{"tag":"1.1.10","author":"boni58","date":"2026-05-06 09:46:08"},"1.1.11":{"tag":"1.1.11","author":"boni58","date":"2026-05-06 09:52:11"},"1.1.12":{"tag":"1.1.12","author":"boni58","date":"2026-05-06 10:07:02"},"1.1.13":{"tag":"1.1.13","author":"boni58","date":"2026-05-06 10:27:52"},"1.1.14":{"tag":"1.1.14","author":"boni58","date":"2026-05-06 11:55:10"},"1.1.15":{"tag":"1.1.15","author":"boni58","date":"2026-05-06 14:38:28"},"1.1.16":{"tag":"1.1.16","author":"boni58","date":"2026-05-06 15:33:20"},"1.1.17":{"tag":"1.1.17","author":"boni58","date":"2026-05-06 16:54:30"},"1.1.18":{"tag":"1.1.18","author":"boni58","date":"2026-05-07 12:36:43"},"1.1.19":{"tag":"1.1.19","author":"boni58","date":"2026-05-07 13:09:49"},"1.1.20":{"tag":"1.1.20","author":"boni58","date":"2026-05-07 14:01:15"},"1.1.21":{"tag":"1.1.21","author":"boni58","date":"2026-05-07 14:11:37"},"1.1.22":{"tag":"1.1.22","author":"boni58","date":"2026-05-07 15:02:02"},"1.1.23":{"tag":"1.1.23","author":"boni58","date":"2026-05-07 15:21:30"},"1.1.24":{"tag":"1.1.24","author":"boni58","date":"2026-05-07 15:38:00"},"1.1.25":{"tag":"1.1.25","author":"boni58","date":"2026-05-07 15:49:25"},"1.1.26":{"tag":"1.1.26","author":"boni58","date":"2026-05-07 16:06:04"},"1.1.27":{"tag":"1.1.27","author":"boni58","date":"2026-05-07 17:05:51"},"1.1.28":{"tag":"1.1.28","author":"boni58","date":"2026-05-07 18:07:29"},"1.1.29":{"tag":"1.1.29","author":"boni58","date":"2026-05-07 18:40:28"},"1.1.30":{"tag":"1.1.30","author":"boni58","date":"2026-05-07 19:46:18"},"1.1.31":{"tag":"1.1.31","author":"boni58","date":"2026-05-07 20:28:58"},"1.1.32":{"tag":"1.1.32","author":"boni58","date":"2026-05-07 20:43:40"},"1.1.33":{"tag":"1.1.33","author":"boni58","date":"2026-05-07 21:05:32"},"1.1.34":{"tag":"1.1.34","author":"boni58","date":"2026-05-07 21:14:36"},"1.1.35":{"tag":"1.1.35","author":"boni58","date":"2026-05-07 21:45:04"},"1.1.36":{"tag":"1.1.36","author":"boni58","date":"2026-05-07 22:10:29"},"1.1.37":{"tag":"1.1.37","author":"boni58","date":"2026-05-07 22:49:57"},"1.1.38":{"tag":"1.1.38","author":"boni58","date":"2026-05-07 23:12:18"},"1.1.39":{"tag":"1.1.39","author":"boni58","date":"2026-05-07 23:39:05"},"1.1.40":{"tag":"1.1.40","author":"boni58","date":"2026-05-08 00:02:15"},"1.1.41":{"tag":"1.1.41","author":"boni58","date":"2026-05-08 00:21:22"},"1.1.42":{"tag":"1.1.42","author":"boni58","date":"2026-05-08 07:54:51"},"1.1.43":{"tag":"1.1.43","author":"boni58","date":"2026-05-08 08:00:34"},"1.1.44":{"tag":"1.1.44","author":"boni58","date":"2026-05-08 08:07:51"},"1.1.45":{"tag":"1.1.45","author":"boni58","date":"2026-05-08 08:19:54"},"1.1.46":{"tag":"1.1.46","author":"boni58","date":"2026-05-08 08:50:35"},"1.1.47":{"tag":"1.1.47","author":"boni58","date":"2026-05-08 09:16:32"},"1.1.48":{"tag":"1.1.48","author":"boni58","date":"2026-05-08 09:57:32"},"1.1.49":{"tag":"1.1.49","author":"boni58","date":"2026-05-08 10:13:12"},"1.1.5":{"tag":"1.1.5","author":"boni58","date":"2026-04-11 07:22:48"},"1.1.50":{"tag":"1.1.50","author":"boni58","date":"2026-05-08 10:44:28"},"1.1.51":{"tag":"1.1.51","author":"boni58","date":"2026-05-08 10:51:50"},"1.1.52":{"tag":"1.1.52","author":"boni58","date":"2026-05-08 11:12:54"},"1.1.53":{"tag":"1.1.53","author":"boni58","date":"2026-05-08 11:27:53"},"1.1.54":{"tag":"1.1.54","author":"boni58","date":"2026-05-08 11:47:32"},"1.1.55":{"tag":"1.1.55","author":"boni58","date":"2026-05-08 12:34:35"},"1.1.56":{"tag":"1.1.56","author":"boni58","date":"2026-05-08 13:16:22"},"1.1.57":{"tag":"1.1.57","author":"boni58","date":"2026-05-08 13:44:19"},"1.1.58":{"tag":"1.1.58","author":"boni58","date":"2026-05-08 14:14:38"},"1.1.59":{"tag":"1.1.59","author":"boni58","date":"2026-05-13 08:41:03"},"1.1.6":{"tag":"1.1.6","author":"boni58","date":"2026-04-29 05:45:56"},"1.1.60":{"tag":"1.1.60","author":"boni58","date":"2026-05-13 09:51:17"},"1.1.61":{"tag":"1.1.61","author":"boni58","date":"2026-05-13 11:42:35"},"1.1.62":{"tag":"1.1.62","author":"boni58","date":"2026-05-13 12:39:29"},"1.1.63":{"tag":"1.1.63","author":"boni58","date":"2026-05-14 13:16:53"},"1.1.64":{"tag":"1.1.64","author":"boni58","date":"2026-05-14 13:28:02"},"1.1.65":{"tag":"1.1.65","author":"boni58","date":"2026-05-14 15:16:11"},"1.1.66":{"tag":"1.1.66","author":"boni58","date":"2026-05-14 15:31:46"},"1.1.67":{"tag":"1.1.67","author":"boni58","date":"2026-05-14 15:56:58"},"1.1.68":{"tag":"1.1.68","author":"boni58","date":"2026-05-14 17:15:15"},"1.1.69":{"tag":"1.1.69","author":"boni58","date":"2026-05-14 17:35:33"},"1.1.7":{"tag":"1.1.7","author":"boni58","date":"2026-05-06 08:12:10"},"1.1.70":{"tag":"1.1.70","author":"boni58","date":"2026-05-14 18:25:08"},"1.1.71":{"tag":"1.1.71","author":"boni58","date":"2026-05-14 20:23:49"},"1.1.72":{"tag":"1.1.72","author":"boni58","date":"2026-05-14 20:32:26"},"1.1.73":{"tag":"1.1.73","author":"boni58","date":"2026-05-14 20:48:14"},"1.1.74":{"tag":"1.1.74","author":"boni58","date":"2026-05-14 20:57:03"},"1.1.75":{"tag":"1.1.75","author":"boni58","date":"2026-05-14 21:28:59"},"1.1.8":{"tag":"1.1.8","author":"boni58","date":"2026-05-06 09:18:16"},"1.1.9":{"tag":"1.1.9","author":"boni58","date":"2026-05-06 09:37:49"}},"upgrade_notice":{"1.1.29":"<p>Massive SEO release : AI re-optimize uses your real Search Console queries (top 10 \/ striking distance), new Opportunities tab (cannibalization, low CTR, lost rankings), Issues report, Audit tab (image SEO + schemas), auto BreadcrumbList + FAQPage JSON-LD. Required.<\/p>","1.1.28":"<p>Adds Pause \/ Resume \/ Cancel controls during bulk optimization. Leaving the page now cancels the bulk automatically (saved items stay, pending queue is dropped). Auto-resume on page load removed by design.<\/p>","1.1.27":"<p>Fixes Search Console KPIs stuck at 0 when the WP server returns 503 on admin-ajax (Wordfence\/Cloudflare overload). JS now retries with exponential backoff. Recommended.<\/p>","1.1.26":"<p>Auto-syncs Search Console data on page load (no more empty Overview after connecting), populates the Performance tab with striking-distance keywords, and auto-picks the property variant with the most actual data.<\/p>","1.1.25":"<p>Cosmetic fix: replaces the misleading &quot;Traitement en cours\u2026&quot; placeholder with a clearer empty state on the Search Console Indexation overview.<\/p>","1.1.24":"<p>Removes the manual &quot;Change property&quot; UI now that auto-fallback handles property variants automatically.<\/p>","1.1.23":"<p>Search Console connection is now self-healing: tries every property variant (sc-domain, www, http) until one works, and surfaces Google&#039;s real error message when none does. Recommended.<\/p>","1.1.22":"<p>Fixes Search Console 404 sync errors when your GSC property URL doesn&#039;t exactly match the WP site URL (sc-domain, www, http variants). Adds a manual &quot;Change property&quot; picker. Recommended.<\/p>","1.1.21":"<p>Critical fixes for 1.1.20 Search Console: OAuth response format, missing cryptography dependency, empty Indexation tab, indexation column on categories, <code>is_connected<\/code> guards, missing strings. Required if you upgraded to 1.1.20.<\/p>","1.1.20":"<p>Major release: full Google Search Console + IndexNow integration. New &quot;Search Console&quot; admin menu with stats, indexation status, sitemap submission, auto IndexNow on publish, and a per-product &quot;Indexation&quot; column. Recommended.<\/p>","1.1.19":"<p>Removes the page reload after a single category optimize (so concurrent optimizations don&#039;t get killed) and silences the spurious &quot;Task not found&quot; toast on page reload. Recommended.<\/p>","1.1.18":"<p>Fixes three concurrency bugs in 1.1.17 bulk: filtered re-selection re-running everything, items getting stuck in queue from parallel poll-driven processing, and spurious &quot;Task not found&quot; errors during transient races. Recommended.<\/p>","1.1.17":"<p>Critical fix for bulk and parallel optimizations stopping after 3-5 items. Each Flask call is now isolated to its own short-lived PHP process so <code>max_execution_time<\/code> and FPM worker saturation can&#039;t interrupt the queue. Recommended.<\/p>","1.1.16":"<p>Polish on 1.1.15 bulk: stuck-item recovery, clean cancel\/error UX, missing strings. Recommended.<\/p>","1.1.15":"<p>Major bulk overhaul: full async dispatch (no more 504 on category bulk), per-row progress states like single optimize, percentage bar everywhere, cancel button, auto-resume on page reload. Recommended for anyone using bulk optimization.<\/p>","1.1.14":"<p>Adds a filemtime-based cache buster so the new admin JS is loaded immediately after update, plus session\/timeout hardening on legacy sync endpoints. Recommended for anyone still seeing 504 on category optimize after 1.1.13.<\/p>","1.1.13":"<p>Fixes 504 Gateway Timeout on single-product and single-category optimization (same async dispatch as blog), and bans colons in AI-generated titles. Recommended update.<\/p>","1.1.12":"<p>Definitive fix for &quot;Erreur r\u00e9seau \/ 504&quot; on sites behind Cloudflare + Wordfence (or similar). Concurrent polls no longer block on PHP sessions, the loopback dispatch is removed, and the JS tolerates transient network errors. Recommended update for everyone on 1.1.8 \u2192 1.1.11.<\/p>","1.1.11":"<p>Critical fix for &quot;Erreur r\u00e9seau \/ 504&quot; when generating blog articles on hosts behind Cloudflare\/Caddy\/strict NGINX. The browser response now closes before the AI call runs. Recommended update.<\/p>","1.1.10":"<p>Fixes a payload mismatch that prevented categories and blog links from reaching the AI for articles and category pages, and adds JSON-LD Article schema. Recommended update for anyone on 1.1.8 or 1.1.9.<\/p>","1.1.9":"<p>Major content quality fix: AI now writes intent-driven titles (no more &quot;guide complet&quot; clich\u00e9s), stops paraphrasing the brief (&quot;Introduction&quot;, &quot;voici les liens&quot;), and articles publish as real Gutenberg blocks instead of a single HTML block. Recommended update.<\/p>","1.1.6":"<p>Fixes a &quot;Network error&quot; that could appear when generating blog articles on hosts with short HTTP proxy timeouts. Recommended update.<\/p>","1.0.0":"<p>Initial release of Essiow AI SEO Suite for WooCommerce.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3503791,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3503791,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3503791,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3503791,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.1.10","1.1.11","1.1.12","1.1.13","1.1.14","1.1.15","1.1.16","1.1.17","1.1.18","1.1.19","1.1.20","1.1.21","1.1.22","1.1.23","1.1.24","1.1.25","1.1.26","1.1.27","1.1.28","1.1.29","1.1.30","1.1.31","1.1.32","1.1.33","1.1.34","1.1.35","1.1.36","1.1.37","1.1.38","1.1.39","1.1.40","1.1.41","1.1.42","1.1.43","1.1.44","1.1.45","1.1.46","1.1.47","1.1.48","1.1.49","1.1.5","1.1.50","1.1.51","1.1.52","1.1.53","1.1.54","1.1.55","1.1.56","1.1.57","1.1.58","1.1.59","1.1.6","1.1.60","1.1.61","1.1.62","1.1.63","1.1.64","1.1.65","1.1.66","1.1.67","1.1.68","1.1.69","1.1.7","1.1.70","1.1.71","1.1.72","1.1.73","1.1.74","1.1.75","1.1.8","1.1.9"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"Dashboard with connection status and credit balance","2":"Product optimization with SEO score and bulk actions","3":"Category optimization with rich content preview","4":"Blog article generator with async processing","5":"AI Sales Agent configuration","6":"Settings page with API connection"},"jetpack_post_was_ever_published":false},"plugin_section":[],"plugin_tags":[2353,2364,215222,186,286],"plugin_category":[45,55],"plugin_contributors":[259943],"plugin_business_model":[],"class_list":["post-294906","plugin","type-plugin","status-publish","hentry","plugin_tags-ai","plugin_tags-chatbot","plugin_tags-product-descriptions","plugin_tags-seo","plugin_tags-woocommerce","plugin_category-ecommerce","plugin_category-seo-and-marketing","plugin_contributors-boni58","plugin_committers-boni58"],"banners":{"banner":"https:\/\/ps.w.org\/essiow\/assets\/banner-772x250.png?rev=3503791","banner_2x":"https:\/\/ps.w.org\/essiow\/assets\/banner-1544x500.png?rev=3503791","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/essiow\/assets\/icon-128x128.png?rev=3503791","icon_2x":"https:\/\/ps.w.org\/essiow\/assets\/icon-256x256.png?rev=3503791","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p><strong>Essiow turns your WooCommerce store into a search-traffic machine.<\/strong> It plugs into Google Search Console, watches what your customers actually search for, and rewrites your product pages, category pages and blog articles to capture every query you nearly rank on.<\/p>\n\n<p>You don't write SEO. You don't pick keywords. You don't guess what works. You click a button and the right pages get fixed.<\/p>\n\n<h4>What Essiow does for you<\/h4>\n\n<p><strong>1. Auto-rewrites your product pages.<\/strong> Long description, short pitch, meta title, meta description, focus keyword, image alt texts \u2014 all generated from your real GSC queries when connected, in 8 languages, in your store's tone. Compatible with Yoast SEO, Rank Math and All in One SEO.<\/p>\n\n<p><strong>2. Turns empty category pages into landing pages.<\/strong> Bare category pages don't rank. Essiow generates 1,500-2,500 words of category content with FAQ, comparison tables and links to your top products \u2014 the page Google needs to rank you in position 1 instead of position 30.<\/p>\n\n<p><strong>3. Writes blog articles that pull traffic to your products.<\/strong> 1,500-5,000 word articles with internal links to the products mentioned, FAQ schema, automatic featured image. Suggestions based on what your audience already searches.<\/p>\n\n<p><strong>4. Spots and grabs every \"almost-ranking\" keyword.<\/strong> When Google Search Console is connected, Essiow surfaces every query where your store sits at position 11-20 \u2014 the closest gains. One click rewrites the matching page targeting that exact query.<\/p>\n\n<p><strong>5. Resolves cannibalization in two clicks.<\/strong> Two of your pages competing for the same query? Essiow detects it, picks the strongest one, and consolidates the canonical from the others \u2014 without deleting anything.<\/p>\n\n<p><strong>6. Indexes everything instantly.<\/strong> Bing, Yandex, Naver, Seznam are pinged the second you publish. Google gets the URL pushed via sitemap re-submit + URL Inspection refresh + a one-click manual indexation request.<\/p>\n\n<p><strong>7. Builds your internal mesh in a graph view.<\/strong> See orphan pages (no incoming links), dead-ends (no outgoing), and connect any two pages with a drag \u2014 Essiow injects reciprocal anchor links on the strongest shared keyword. A live mesh score \/ 100 tells you how healthy your site structure is.<\/p>\n\n<p><strong>8. AI sales agent on your storefront.<\/strong> A chatbot that knows your full catalog, handles objections, can issue promo codes within your discount limit, and quotes your delivery \/ returns \/ payment policy.<\/p>\n\n<p><strong>9. Exposes your catalog to ChatGPT, Perplexity and Claude.<\/strong> Toggle on and Essiow serves a clean <code>\/llms.txt<\/code> at your root \u2014 the standard AI search engines read to find products to recommend.<\/p>\n\n<h4>Why it ranks better<\/h4>\n\n<p>When Search Console is connected, every optimization sees the actual queries the page is already ranking on, the striking-distance keywords just outside page 1, and the CTR alerts when a title is converting poorly. The AI doesn't guess keywords \u2014 it gets them from Google itself, and writes around what's already working.<\/p>\n\n<h4>Made for shop owners, not SEOs<\/h4>\n\n<ul>\n<li>No keyword research needed<\/li>\n<li>No technical setup beyond pasting an API key<\/li>\n<li>Every action shows its credit cost upfront \u2014 no surprise billing<\/li>\n<li>Bulk optimize, pause, resume, restore original \u2014 your content is always recoverable<\/li>\n<li>8 languages, 4 writing tones, 3 content lengths<\/li>\n<\/ul>\n\n<h4>Compatible &amp; safe<\/h4>\n\n<ul>\n<li>WooCommerce HPOS compatible<\/li>\n<li>Works alongside Yoast SEO \/ Rank Math \/ All in One SEO (writes to all three)<\/li>\n<li>GDPR compliant (auto-delete chat data after 90 days)<\/li>\n<li>Original content backed up the first time you optimize \u2014 one-click restore<\/li>\n<\/ul>\n\n<h4>How credits work<\/h4>\n\n<ul>\n<li><strong>1 credit<\/strong> per product optimization<\/li>\n<li><strong>1 credit<\/strong> per category optimization<\/li>\n<li><strong>3 credits<\/strong> per blog article<\/li>\n<li><strong>2 credits<\/strong> per AI Vision alt text generation<\/li>\n<li>All indexation actions, audits, internal-link suggestions, mesh-score, \/llms.txt \u2014 <strong>free<\/strong> (no AI involved)<\/li>\n<li>Credits are debited only on success. Failed AI calls don't consume credits.<\/li>\n<li>Purchased credits never expire (free trial credits expire after 30 days)<\/li>\n<\/ul>\n\n<h4>External Service<\/h4>\n\n<p>This plugin connects to the Essiow API at <code>https:\/\/essiow.com\/api\/v1<\/code> to process AI content generation. Your product data (names, descriptions, prices, categories) is sent to the Essiow servers where it is processed using OpenAI's models. No data is stored beyond what is needed to track your credit usage.<\/p>\n\n<ul>\n<li><a href=\"https:\/\/essiow.com\/terms\">Essiow Terms of Service<\/a><\/li>\n<li><a href=\"https:\/\/essiow.com\/privacy\">Essiow Privacy Policy<\/a><\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>essiow<\/code> folder to <code>\/wp-content\/plugins\/<\/code><\/li>\n<li>Activate the plugin through the 'Plugins' menu in WordPress<\/li>\n<li>Go to <strong>Essiow &gt; Settings<\/strong> and enter your API key from <a href=\"https:\/\/essiow.com\">essiow.com<\/a><\/li>\n<li>Click \"Test Connection\" to verify<\/li>\n<li>Start optimizing from <strong>Essiow &gt; Products<\/strong> or <strong>Essiow &gt; Categories<\/strong><\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"do%20i%20need%20an%20essiow%20account%3F\"><h3>Do I need an Essiow account?<\/h3><\/dt>\n<dd><p>Yes. Create a free account at <a href=\"https:\/\/essiow.com\">essiow.com<\/a> to get your API key and 10 free credits.<\/p><\/dd>\n<dt id=\"do%20i%20need%20technical%20skills%3F\"><h3>Do I need technical skills?<\/h3><\/dt>\n<dd><p>No. If you can install a WordPress plugin, you can use Essiow. Everything is done in a few clicks.<\/p><\/dd>\n<dt id=\"do%20i%20need%20to%20know%20seo%3F\"><h3>Do I need to know SEO?<\/h3><\/dt>\n<dd><p>No. Essiow does the SEO work : it picks the keywords (from Google Search Console when connected), writes the meta tags, generates the schema, builds the internal links and submits everything to search engines. You just click \"Optimize\".<\/p><\/dd>\n<dt id=\"why%20does%20connecting%20google%20search%20console%20matter%3F\"><h3>Why does connecting Google Search Console matter?<\/h3><\/dt>\n<dd><p>With GSC connected, every optimization is fed with the real queries your page already ranks on. Essiow finds queries where you sit at position 11-20 (just outside page 1) and rewrites the matching page targeting that exact query. Without GSC, optimizations are still good \u2014 but generic. With GSC, they're surgical.<\/p><\/dd>\n<dt id=\"will%20optimizing%20break%20my%20existing%20content%3F\"><h3>Will optimizing break my existing content?<\/h3><\/dt>\n<dd><p>No. The first time a product or category is optimized, the original content is backed up automatically. One click in the preview modal restores it.<\/p><\/dd>\n<dt id=\"which%20seo%20plugins%20are%20supported%3F\"><h3>Which SEO plugins are supported?<\/h3><\/dt>\n<dd><p>Essiow works with Yoast SEO, Rank Math, and All in One SEO. It writes to all three formats simultaneously, so switching SEO plugin later does not lose your data.<\/p><\/dd>\n<dt id=\"is%20my%20data%20safe%3F\"><h3>Is my data safe?<\/h3><\/dt>\n<dd><p>Your product data is sent to Essiow servers only during optimization. It is processed in real-time and not stored beyond credit-tracking metadata. Chat conversations are auto-deleted after 90 days per GDPR requirements.<\/p><\/dd>\n<dt id=\"do%20credits%20expire%3F\"><h3>Do credits expire?<\/h3><\/dt>\n<dd><p>Purchased credits never expire. The 10 free credits expire after 30 days.<\/p><\/dd>\n<dt id=\"can%20i%20cancel%20a%20bulk%20optimization%3F\"><h3>Can I cancel a bulk optimization?<\/h3><\/dt>\n<dd><p>Yes. Pause \/ Resume \/ Cancel buttons appear during a bulk run. Closing the tab also auto-cancels \u2014 items already processed remain saved.<\/p><\/dd>\n<dt id=\"can%20i%20try%20before%20buying%3F\"><h3>Can I try before buying?<\/h3><\/dt>\n<dd><p>Yes. Create a free account and get 10 credits to test all features. No credit card required.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.1.75<\/h4>\n\n<ul>\n<li><strong>Fix d\u00e9finitif \u2014 images vedette + images inline dans les articles bulk<\/strong> :\n\n<ol>\n<li><strong>Featured image via attachment ID<\/strong> : avant, le plugin recevait une URL <code>https:\/\/site.com\/wp-content\/uploads\/2024\/10\/widget-1024x768.jpg<\/code> (taille <code>large<\/code>) et tentait <code>attachment_url_to_postid<\/code> \u2192 souvent \u00e9chec car cette fonction n'accepte que l'URL ORIGINALE sans suffixe <code>-WxH<\/code>. D\u00e9sormais le plugin envoie l'<code>image_id<\/code> directement dans <code>featured_image_pool<\/code> ; le worker stocke <code>featured_image_id<\/code> dans <code>generated_payload<\/code> ; le plugin attache via <code>set_post_thumbnail($post_id, $id)<\/code> \u2014 <strong>z\u00e9ro HTTP, indestructible<\/strong>.<\/li>\n<li><strong>Auto-injection des images inline<\/strong> : l'IA esquivait parfois les <code>&lt;img&gt;<\/code> m\u00eame quand on lui listait les produits dans le prompt. Le sanitizer compte maintenant les <code>&lt;img&gt;<\/code> valides apr\u00e8s g\u00e9n\u00e9ration. Si moins de 3, il injecte automatiquement les images des produits restants, plac\u00e9es apr\u00e8s les premi\u00e8res <code>&lt;h2&gt;<\/code>, wrapp\u00e9es dans des <code>&lt;a&gt;<\/code> vers la page produit pour le SEO. Le plugin re\u00e7oit un article qui a TOUJOURS au moins 3 images contextuelles, peu importe ce que l'IA a fait.<\/li>\n<li><strong>Prompt durci<\/strong> : section IMAGES d\u00e9plac\u00e9e en MANDATORY (non-n\u00e9gociable), exige 3-6 <code>&lt;img&gt;<\/code> minimum, format explicite avec wrap <code>&lt;a href=\"PRODUCT_URL\"&gt;<\/code> pour cumuler valeur SEO.<\/li>\n<\/ol><\/li>\n<li><strong>Fix HTTP 429 sur ping IndexNow<\/strong> : avant, un 429 (rate limit) cassait l'op\u00e9ration sans recovery. D\u00e9sormais le retry honore le header <code>Retry-After<\/code> quand IndexNow l'envoie, sinon backoff plus long. C\u00f4t\u00e9 UI, le toast affiche un message clair (\u00ab IndexNow rate-limited. Try again in a few minutes. \u00bb) au lieu d'un cryptique \u00ab HTTP 429 \u00bb.<\/li>\n<li><strong>Fix bouton \u00ab Indexer \u00bb qui ouvrait GSC dans une nouvelle fen\u00eatre<\/strong> : avant, apr\u00e8s IndexNow + sitemap submit, le plugin ouvrait automatiquement Google Search Console sur la page d'inspection de l'URL \u2014 Google affichait son texte par d\u00e9faut \u00ab URL is not on Google. Couldn't fetch it... \u00bb et l'utilisateur croyait \u00e0 un \u00e9chec de l'indexation. D\u00e9sormais aucune ouverture automatique ; le toast affiche un r\u00e9cap clair des \u00e9tapes effectu\u00e9es (\u00ab \u2713 IndexNow \u00b7 Sitemap re-submitted \u00b7 Indexation status refreshed \u00bb). L'URL GSC reste accessible si besoin via <code>data-hint-url<\/code> sur le bouton (extensible plus tard pour une UI d\u00e9di\u00e9e).<\/li>\n<li><strong>R\u00e9cap d'\u00e9tapes d\u00e9taill\u00e9<\/strong> dans <code>ajax_request_indexing<\/code> : chaque sous-action (IndexNow, sitemap, inspection cache) renvoie son statut individuel. Les \u00e9checs partiels sont annonc\u00e9s (<code>\u26a0 IndexNow rate-limited<\/code>) sans planter l'op\u00e9ration globale.<\/li>\n<\/ul>\n\n<h4>1.1.74<\/h4>\n\n<ul>\n<li><strong>Audit de v\u00e9rification 1.1.73<\/strong> : aucun handler legacy orphelin, lock transient correct, idempotence du pull garantie, sanitizer appel\u00e9 avant le d\u00e9bit cr\u00e9dit. 1 seul vrai bug remont\u00e9, corrig\u00e9 ici.<\/li>\n<li><strong>Fix critique \u2014 page-close n'annule plus le job<\/strong> : avant 1.1.74, le <code>cancelAllBulksOnUnload<\/code> annulait toujours les bulks au refresh \/ fermeture d'onglet via <code>navigator.sendBeacon<\/code>, ce qui \u00e9tait directement contraire \u00e0 l'architecture jobs-serveur d\u00e9ploy\u00e9e en 1.1.72-73 (\u00ab rien ne s'arr\u00eate quand l'utilisateur ferme \u00bb). Le handler a \u00e9t\u00e9 neutralis\u00e9 : les jobs continuent c\u00f4t\u00e9 serveur, le polling reprend automatiquement au rechargement.<\/li>\n<li><strong>Fix sites HTTP + installs en sous-r\u00e9pertoire<\/strong> : le sanitizer c\u00f4t\u00e9 Flask reconstruisait <code>https:\/\/{domain}<\/code> en ignorant le protocole et le subpath r\u00e9els (info perdue c\u00f4t\u00e9 serveur). Les sites en HTTP ou dans <code>\/shop\/<\/code> voyaient tous leurs liens internes strip\u00e9s. D\u00e9sormais le plugin envoie son <code>site_url<\/code> complet (via <code>home_url('\/')<\/code>) dans le wp_context et dans le payload de <code>\/optimize\/article<\/code> \u2014 le sanitizer l'utilise comme base de r\u00e9solution.<\/li>\n<li><strong>IndexNow retry exponentiel<\/strong> : avant, un seul shot avec timeout 5s. Un blip r\u00e9seau ou un 503 transitoire perdait d\u00e9finitivement la soumission. D\u00e9sormais : jusqu'\u00e0 3 tentatives avec backoff 0s \/ 1s \/ 3s. Les 4xx (cl\u00e9 \/ URLs invalides) court-circuitent \u2014 pas de retry sur erreurs d\u00e9terministes. Timeout port\u00e9 \u00e0 10s. Le log historique inclut maintenant <code>attempts<\/code> et <code>error<\/code> pour audit.<\/li>\n<li><strong>Cleanup auto des cannibalisations dismissed\/resolved obsol\u00e8tes<\/strong> : \u00e0 chaque chargement de la page Search Console, on compare les cl\u00e9s stock\u00e9es en options WP avec les paires (query|primary|secondary) actuellement pr\u00e9sentes dans les donn\u00e9es GSC live. Toute cl\u00e9 absente du live \u2192 entr\u00e9e stale \u2192 supprim\u00e9e. \u00c9vite l'accumulation ind\u00e9finie (plusieurs centaines par an sur sites actifs).<\/li>\n<li><strong>Helper <code>apiError(xhr, defaultMsg)<\/code> c\u00f4t\u00e9 JS<\/strong> : les erreurs AJAX \u00e9taient toutes affich\u00e9es comme <code>'Network error'<\/code> quelle que soit la cause. D\u00e9sormais d\u00e9tection automatique via le code HTTP et <code>responseJSON.data.code<\/code> :\n\n<ul>\n<li>Status 0 \u2192 \u00ab Cannot reach the server. Check your internet connection. \u00bb<\/li>\n<li>401\/403 ou code <code>invalid_api_key<\/code> \u2192 \u00ab API key invalid or expired. Reconfigure in Settings. \u00bb<\/li>\n<li>402 ou code <code>insufficient_credits<\/code> \u2192 \u00ab Plan ran out of credits. Upgrade in Settings. \u00bb<\/li>\n<li>429 ou code <code>rate_limited<\/code> \u2192 \u00ab Too many requests. Try again in a minute. \u00bb<\/li>\n<li>503\/504 ou code <code>overloaded<\/code> \u2192 \u00ab Server overloaded. Try again in 30 seconds. \u00bb<\/li>\n<li>5xx \u2192 \u00ab Server error. Try again or contact support. \u00bb<\/li>\n<\/ul><\/li>\n<li><strong>i18n<\/strong> : 7 nouvelles strings traduisibles (err_network, err_auth, err_no_credits, err_rate_limit, err_overloaded, err_server, no_urls_selected, sync_ok, rows, property_set, rechecked, pinged) + \u00e9limination des strings hardcod\u00e9es (fran\u00e7ais durci <code>'Serveur surcharg\u00e9 \u00b7 r\u00e9essayez dans 30s'<\/code>, anglais durci <code>'No URLs selected'<\/code>, <code>'Pinged'<\/code>, <code>'Re-checked'<\/code>, etc.).<\/li>\n<\/ul>\n\n<h4>1.1.73<\/h4>\n\n<ul>\n<li><strong>Audit complet du plugin + backend<\/strong> \u2014 4 axes audit\u00e9s en parall\u00e8le (bulk produits\/cat\u00e9gories, g\u00e9n\u00e9ration articles, Search Console, UX). 14 bugs et am\u00e9liorations livr\u00e9s en une release.<\/li>\n<li><strong>Phase 2 wir\u00e9e sur l'UI<\/strong> : les boutons \u00ab Optimiser s\u00e9lection \u00bb des pages Produits et Cat\u00e9gories utilisent d\u00e9sormais le nouveau syst\u00e8me jobs serveur (<code>essiow_bulk_opt_create<\/code>). Concr\u00e8tement : vous lancez, vous pouvez fermer l'onglet, vous revenez 1 heure plus tard \u2014 le job a continu\u00e9 c\u00f4t\u00e9 serveur, le WP-Cron a appliqu\u00e9 les optimisations au fur et \u00e0 mesure, et l'UI affiche l'\u00e9tat final.<\/li>\n<li><strong>Auto-resume au chargement de la page<\/strong> : si un job \u00e9tait en cours quand vous avez quitt\u00e9, l'UI red\u00e9marre automatiquement le polling et affiche la progression (via <code>sessionStorage<\/code> c\u00f4t\u00e9 navigateur).<\/li>\n<li><strong>Sanitizer HTML appliqu\u00e9 aussi \u00e0 la g\u00e9n\u00e9ration individuelle<\/strong> (<code>\/optimize\/article<\/code>) : avant 1.1.73, le post-processing des images placeholder et liens relatifs ne tournait que sur le bulk. Les articles g\u00e9n\u00e9r\u00e9s un par un h\u00e9ritaient des m\u00eames bugs. Maintenant la m\u00eame protection s'applique partout \u2014 <code>&lt;img src=\"IMAGE_URL\"&gt;<\/code> \u2192 strip ou fallback, <code>&lt;a href=\"produit\/x\"&gt;<\/code> relatif \u2192 URL absolue canonique, sinon unwrap.<\/li>\n<li><strong>Race condition sync-pull + WP-Cron pull supprim\u00e9e<\/strong> : un transient lock par job (<code>essiow_bulk_pull_lock_{id}<\/code>) emp\u00eache les deux processus de pull les m\u00eames items simultan\u00e9ment et d'appeler <code>wp_insert_post<\/code> deux fois \u2192 plus de duplicates c\u00f4t\u00e9 WP.<\/li>\n<li><strong>Idempotence du pull renforc\u00e9e<\/strong> : chaque post WP est marqu\u00e9 <code>_essiow_bulk_item_id<\/code>. Si Flask renvoie le m\u00eame item apr\u00e8s un retry, on d\u00e9tecte le post existant et on re-confirme \u00e0 Flask au lieu d'ins\u00e9rer un duplicate.<\/li>\n<li><strong>Subdirectory install support\u00e9<\/strong> dans <code>resolve_url_to_local<\/code> : si WordPress est install\u00e9 dans <code>\/wp\/<\/code> (ou autre sous-r\u00e9pertoire), GSC renvoie l'URL avec le pr\u00e9fixe subdir, mais <code>url_to_postid()<\/code> attend le path relatif. Le r\u00e9solveur retire maintenant le pr\u00e9fixe et retente.<\/li>\n<li><strong>Cleanup hourly des state tokens OAuth Google expir\u00e9s<\/strong> (Celery beat) \u2014 sans ce nettoyage, la table <code>gsc_oauth_states<\/code> accumulait une ligne par d\u00e9marrage de flow OAuth, m\u00eame ceux abandonn\u00e9s en route.<\/li>\n<li><strong>Confirms actionnables<\/strong> : avant <code>confirm('Confirm?')<\/code>, d\u00e9sormais <code>confirm('Optimize 12 products? Each uses 1 credit and continues running even if you close this page.')<\/code>. Pareil pour cat\u00e9gories et annulation de job.<\/li>\n<li><strong>Label \u00ab Processing: [item] \u00bb<\/strong> affich\u00e9 dans la barre de progression \u2014 vous voyez en direct quel produit\/cat\u00e9gorie est en cours d'optimisation.<\/li>\n<li><strong>CSS bouton d\u00e9sactiv\u00e9 coh\u00e9rent<\/strong> (opacity 0.55 + cursor not-allowed) \u2014 fini les th\u00e8mes qui rendent les boutons disabled identiques aux activ\u00e9s.<\/li>\n<li><strong>Config writing_tone\/language\/length expos\u00e9e au JS<\/strong> pour que les jobs bulk utilisent les pr\u00e9f\u00e9rences globales du site automatiquement.<\/li>\n<\/ul>\n\n<h4>1.1.72<\/h4>\n\n<ul>\n<li><strong>Phase 2 \u2014 Optimisations produits\/cat\u00e9gories via jobs serveur<\/strong> (architecture jumelle des articles bulk). Le plugin POST la liste d'objets WP \u00e0 optimiser vers Flask, Celery les traite un par un en arri\u00e8re-plan, le plugin pull les items pr\u00eats via WP-Cron (5 min) et les applique localement via <code>wp_update_post<\/code> + <code>update_post_meta<\/code> + <code>update_term_meta<\/code> + m\u00e9tas SEO (Yoast \/ Rank Math \/ AIOSEO). Le cr\u00e9dit est d\u00e9bit\u00e9 en transaction atomique c\u00f4t\u00e9 Flask au moment du confirm.<\/li>\n<li><strong>Survit \u00e0 tout<\/strong> : fermeture d'onglet, perte de connexion, inactivit\u00e9 prolong\u00e9e, crash navigateur, red\u00e9marrage WordPress. Le state du job est en DB SQL c\u00f4t\u00e9 Flask \u2014 le plugin n'a aucun transient critique \u00e0 perdre. \u00c0 la reconnexion, le polling reprend o\u00f9 il en \u00e9tait, et m\u00eame sans reconnexion, le worker Celery continue et le WP-Cron applique au fil de l'eau.<\/li>\n<li><strong>Pause \/ Reprendre \/ Annuler<\/strong> propres, lus entre chaque item par le worker Celery. Annulation imm\u00e9diate sur job inactif (basculement statut serveur instantan\u00e9, identique \u00e0 1.1.68 pour les articles).<\/li>\n<li><strong>Nouveau mod\u00e8le DB Flask<\/strong> : <code>BulkOptimizationJob<\/code> + <code>BulkOptimizationItem<\/code>. Endpoints : <code>\/optimize\/bulk\/create<\/code>, <code>\/status<\/code>, <code>\/pause<\/code>, <code>\/resume<\/code>, <code>\/cancel<\/code>, <code>\/pending-items<\/code>, <code>\/items\/&lt;id&gt;\/applied<\/code>.<\/li>\n<li><strong>Nouvelle classe plugin<\/strong> <code>Essiow_Bulk_Optimize<\/code> : AJAX handlers <code>essiow_bulk_opt_*<\/code> + cron <code>essiow_cron_bulk_opt_pull<\/code> qui pull et applique. Le code legacy (WP-Cron transient-based) reste en place pour r\u00e9tro-compatibilit\u00e9 \u2014 la migration UI vers les nouveaux endpoints se fait progressivement dans les prochaines releases.<\/li>\n<\/ul>\n\n<h4>1.1.71<\/h4>\n\n<ul>\n<li><strong>Fix critique \u2014 image vedette absente sur les articles bulk<\/strong> : <code>download_url()<\/code> \u00e9chouait silencieusement sur les hosts mutualis\u00e9s o\u00f9 le loopback HTTP est bloqu\u00e9 (cas tr\u00e8s fr\u00e9quent : mod_security, reverse-proxy hostile, WAF). R\u00e9sultat : aucune image vedette n'\u00e9tait jamais attach\u00e9e. D\u00e9sormais, quand l'URL est locale au site, on retrouve l'attachment via <code>attachment_url_to_postid<\/code> directement \u2014 <strong>aucune requ\u00eate HTTP<\/strong> \u2014 c'est instantan\u00e9 et 100 % fiable. Le t\u00e9l\u00e9chargement reste en fallback pour les images distantes (CDN externe).<\/li>\n<li><strong>Fix critique \u2014 images cass\u00e9es dans le corps d'article<\/strong> : l'IA produisait r\u00e9guli\u00e8rement des <code>&lt;img src=\"IMAGE_URL\"&gt;<\/code> litt\u00e9raux (placeholder du prompt non substitu\u00e9 par le vrai URL). D\u00e9sormais, un post-processing c\u00f4t\u00e9 Flask scanne chaque <code>&lt;img&gt;<\/code> apr\u00e8s g\u00e9n\u00e9ration : ceux qui contiennent un placeholder (<code>IMAGE_URL<\/code>, <code>PRODUCT_URL<\/code>, <code>example.com<\/code>, src vide, etc.) sont soit remplac\u00e9s par l'image vedette du pool, soit strip\u00e9s. Le prompt OpenAI a aussi \u00e9t\u00e9 reformul\u00e9 pour interdire explicitement les placeholders.<\/li>\n<li><strong>Fix critique \u2014 liens internes renvoient \u00e0 l'accueil<\/strong> : l'IA g\u00e9n\u00e9rait fr\u00e9quemment des <code>&lt;a href=\"produit\/widget\"&gt;<\/code> (relatif), qui une fois publi\u00e9s sur <code>\/mon-article\/<\/code>, deviennent <code>\/mon-article\/produit\/widget<\/code> \u2192 404 \u2192 souvent redirig\u00e9 vers l'accueil par les plugins SEO. Le post-processing r\u00e9sout maintenant chaque <code>&lt;a href&gt;<\/code> :\n\n<ul>\n<li>Si l'URL correspond \u00e0 un produit \/ article \/ cat\u00e9gorie envoy\u00e9 en contexte \u2192 r\u00e9solu en URL absolue canonique.<\/li>\n<li>Si l'URL est relative et inconnue \u2192 r\u00e9solue via <code>site_url + chemin<\/code>.<\/li>\n<li>Si l'URL est vide \/ placeholder \/ <code>#<\/code> \u2192 le <code>&lt;a&gt;<\/code> est unwrapp\u00e9 (le texte reste, le lien dispara\u00eet).<\/li>\n<\/ul><\/li>\n<li><strong>Nouveau service<\/strong> <code>article_html_sanitizer.py<\/code> : module autonome de post-processing HTML qui r\u00e9pare tous les artefacts d'IA (placeholders, URLs relatives, ancres vides). Logu\u00e9 avec compteurs (<code>imgs kept=X stripped=Y \/ links kept=X stripped=Y<\/code>) pour audit a posteriori.<\/li>\n<li><strong>set_featured_image() durci<\/strong> :\n\n<ul>\n<li>Timeout port\u00e9 de 5s \u00e0 30s.<\/li>\n<li>D\u00e9tection automatique URL locale vs distante (compare hosts normalis\u00e9s sans <code>www.<\/code>).<\/li>\n<li>Fallback gracieux si le nom de fichier de l'image est sans extension (d\u00e9duction du type MIME via <code>getimagesize<\/code>).<\/li>\n<li>Logs explicites en cas d'\u00e9chec (avant : silencieux).<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<p><strong>Phase 2 \u00e0 venir<\/strong> : migration de la g\u00e9n\u00e9ration individuelle d'articles, de l'optimisation produits (individuel + bulk), et de l'optimisation cat\u00e9gories (individuel + bulk) vers l'architecture jobs-serveur identique aux articles bulk \u2014 fermeture de l'onglet, perte de connexion, inactivit\u00e9 : rien ne s'arr\u00eate, l'optimisation reprend o\u00f9 elle s'est arr\u00eat\u00e9e. Travail de refonte sur plusieurs releases.<\/p>\n\n<h4>1.1.70<\/h4>\n\n<ul>\n<li><strong>Audit complet des 4 sources de g\u00e9n\u00e9ration bulk<\/strong> (liste de mots-cl\u00e9s, cat\u00e9gories WC, produits WC, import CSV SEMrush) + 6 bugs corrig\u00e9s.<\/li>\n<li><strong>Fix critique \u2014 \u00e9tat du s\u00e9lecteur de source<\/strong> : taper une liste de mots-cl\u00e9s, puis switcher sur \u00ab Produits \u00bb (sans rien cocher) puis lancer envoyait le mauvais payload au backend (source=products + des keywords textuels libres). D\u00e9sormais, le changement d'onglet r\u00e9initialise la s\u00e9lection et l'\u00e9tat visuel \u2014 l'utilisateur doit re-s\u00e9lectionner sur le nouveau mode.<\/li>\n<li><strong>Source \u00ab Cat\u00e9gories produits \u00bb<\/strong> : le worker re\u00e7oit maintenant explicitement le nom de la cat\u00e9gorie sous <code>category_name<\/code>. Le prompt active la section \u00ab PRODUCT CATEGORY CONTEXT \u00bb et r\u00e9dige l'article comme une page pilier de cat\u00e9gorie au lieu d'un article g\u00e9n\u00e9rique sur la requ\u00eate.<\/li>\n<li><strong>Source \u00ab Produits \u00bb<\/strong> : l'URL, l'image et le prix du produit s\u00e9lectionn\u00e9 sont d\u00e9sormais envoy\u00e9s en contexte au worker. Le produit cible est inject\u00e9 en t\u00eate de la liste des produits disponibles (l'IA le mentionne en priorit\u00e9) et son image devient l'image vedette par d\u00e9faut.<\/li>\n<li><strong>Fix dropdown auteur vide sur WP 5.9+<\/strong> : <code>get_users(['who' =&gt; 'authors'])<\/code> est d\u00e9pr\u00e9ci\u00e9 depuis WordPress 5.9 et renvoyait un tableau vide \u2192 le dropdown auteur de la page bulk \u00e9tait vide, impossible de lancer un job. Remplac\u00e9 par <code>capability =&gt; 'edit_posts'<\/code>, avec fallback sur l'utilisateur courant si vide.<\/li>\n<li><strong>CPC pr\u00e9serv\u00e9<\/strong> dans le pipeline : la valeur Cost-Per-Click pars\u00e9e depuis les exports SEMrush \u00e9tait pars\u00e9e puis dropped lors de la sanitisation c\u00f4t\u00e9 PHP. Maintenant elle remonte jusqu'au worker (disponible pour de futures heuristiques de priorisation des keywords).<\/li>\n<li><strong>Sanitisation des keywords \u00e9largie<\/strong> c\u00f4t\u00e9 plugin : accepte les 3 formats \u2014 strings (mode liste\/cat\u00e9gories), dict SEMrush (<code>keyword, volume, kd, intent, cpc<\/code>), dict produit (<code>keyword, product_url, product_image_url, product_price<\/code>).<\/li>\n<\/ul>\n\n<h4>1.1.69<\/h4>\n\n<ul>\n<li><strong>Parit\u00e9 compl\u00e8te entre articles bulk et articles individuels<\/strong>. Avant 1.1.69, les articles g\u00e9n\u00e9r\u00e9s en masse \u00e9taient pauvres : pas d'images, pas d'image vedette, pas de liens internes vers les produits\/cat\u00e9gories, pas de recommandations. Maintenant ils sortent identiques \u00e0 ceux g\u00e9n\u00e9r\u00e9s un par un.<\/li>\n<li><strong>Contexte WordPress envoy\u00e9 au worker<\/strong> : \u00e0 chaque cr\u00e9ation de job, le plugin collecte et transmet au backend les 30 produits top-ventes (avec URL\/prix\/image), les 20 articles r\u00e9cents et les 15 cat\u00e9gories produits actives. L'IA dispose donc du m\u00eame contexte que pour la g\u00e9n\u00e9ration individuelle \u2014 elle peut citer les vrais produits, cr\u00e9er des liens internes pertinents, choisir des images r\u00e9elles.<\/li>\n<li><strong>Image vedette automatique<\/strong> : chaque article re\u00e7oit en featured image une image produit de votre catalogue (rotation par position pour varier entre articles d'un m\u00eame batch).<\/li>\n<li><strong>Conversion en blocs Gutenberg<\/strong> : le contenu HTML est d\u00e9sormais d\u00e9coup\u00e9 en blocs <code>&lt;p&gt;<\/code>, <code>&lt;h2&gt;<\/code>, <code>&lt;ul&gt;<\/code>, <code>&lt;table&gt;<\/code> distincts (plus de gros bloc HTML brut difficile \u00e0 \u00e9diter). Identique \u00e0 la g\u00e9n\u00e9ration individuelle.<\/li>\n<li><strong>SEO meta complet<\/strong> : Yoast SEO, Rank Math et AIOSEO sont tous les trois renseign\u00e9s (titre, description, focus keyword). Avant, seuls titre + description partiels \u00e9taient sett\u00e9s.<\/li>\n<li><strong>Excerpt automatique<\/strong> + nettoyage des doublons (h1\/h2 du titre, en-t\u00eates \"Introduction\" r\u00e9siduels).<\/li>\n<li><strong>Nouvelle section \u00ab Articles g\u00e9n\u00e9r\u00e9s en masse \u00bb<\/strong> en bas de la page Bulk : liste pagin\u00e9e des articles produits par les jobs, avec score SEO (0-100), statut (publi\u00e9\/brouillon), date, nombre de mots, et <strong>boutons Voir \/ Modifier<\/strong>. S\u00e9lection multiple \u2192 ping IndexNow + demande d'indexation Google, comme dans la liste des articles individuels.<\/li>\n<li><strong>Auto-refresh de la liste<\/strong> \u00e0 chaque tick de polling \u2014 les nouveaux articles publi\u00e9s apparaissent dans la liste sans recharger la page.<\/li>\n<li><strong>Fix accents<\/strong> : les titres de produits et cat\u00e9gories \u00e9taient affich\u00e9s avec leurs entit\u00e9s HTML brutes (<code>8Sinn eXtraThin HDMI &amp;#8211; Cable<\/code> au lieu de <code>\u2013 Cable<\/code>). D\u00e9codage via <code>html_entity_decode<\/code> partout : s\u00e9lecteur produits bulk, s\u00e9lecteur cat\u00e9gories, contexte envoy\u00e9 au worker.<\/li>\n<\/ul>\n\n<h4>1.1.68<\/h4>\n\n<ul>\n<li><strong>Refonte UX bulk<\/strong> suivant retours utilisateurs (5 changements majeurs) :<\/li>\n<li>1) <strong>Annulation imm\u00e9diate<\/strong> : annuler un job en queued \/ awaiting_wp_publish \/ paused bascule maintenant le job \u00e0 <code>cancelled<\/code> instantan\u00e9ment c\u00f4t\u00e9 serveur. Avant, seul le flag <code>cancel_requested<\/code> \u00e9tait mis \u00e0 true, mais comme aucun worker n'\u00e9tait en train de tourner, le status restait inchang\u00e9 \u2014 les boutons Annuler persistaient m\u00eame apr\u00e8s reload. Pareil pour Pause sur un job en attente.<\/li>\n<li>2) <strong>Suppression des jauges visuelles<\/strong> : remplac\u00e9es par des pourcentages texte clairs et compacts. La modale active affiche <code>\ud83e\udd16 G\u00e9n\u00e9ration IA : 60% (3\/5) \u00b7 \ud83d\udcdd Publication WP : 40% (2\/5)<\/code> sur une seule ligne.<\/li>\n<li>3) <strong>G\u00e9n\u00e9rations en cours d\u00e9plac\u00e9es en bas<\/strong> de la page (sous l'historique), comme une barre de statut discr\u00e8te. L'utilisateur n'est plus visuellement bloqu\u00e9 par un gros banner en haut quand il configure une nouvelle g\u00e9n\u00e9ration.<\/li>\n<li>4) <strong>Dialog D\u00e9tails enrichi<\/strong> : nouvelle colonne \u00ab Titre de l'article \u00bb qui affiche le titre g\u00e9n\u00e9r\u00e9 par l'IA pour chaque mot-cl\u00e9, plus deux boutons d'action explicites \u2014 \ud83d\udc41 <strong>Voir<\/strong> (lien public vers l'article) et \u270e <strong>Modifier<\/strong> (admin WP). Indispensable pour passer en revue les articles g\u00e9n\u00e9r\u00e9s.<\/li>\n<li>5) <strong>Auto-refresh apr\u00e8s action<\/strong> : pause \/ reprendre \/ annuler d\u00e9clenchent imm\u00e9diatement un re-fetch du status + un refresh de l'historique + un refresh de la modale D\u00e9tails si elle est ouverte sur le m\u00eame job. Plus jamais d'\u00e9tat stale dans l'UI.<\/li>\n<li>Auto-fade-out de la barre active 5s apr\u00e8s compl\u00e9tion + toast final \u00ab \u2705 G\u00e9n\u00e9ration termin\u00e9e \u2014 N articles \u00bb.<\/li>\n<\/ul>\n\n<h4>1.1.67<\/h4>\n\n<ul>\n<li><strong>Refonte compl\u00e8te UI Bulk generation<\/strong> suite aux retours utilisateurs.<\/li>\n<li><strong>2 jauges de progression distinctes<\/strong> : une pour la g\u00e9n\u00e9ration IA c\u00f4t\u00e9 SaaS, une pour la publication WordPress c\u00f4t\u00e9 local. Avant, le compteur affichait toujours 0 jusqu'\u00e0 la publication WP \u2014 pendant tout le temps de g\u00e9n\u00e9ration (5-10 min sur des dizaines d'articles), l'utilisateur croyait que rien ne se passait. Maintenant la jauge bleue avance d\u00e8s qu'un article est g\u00e9n\u00e9r\u00e9 c\u00f4t\u00e9 SaaS, puis la jauge verte avance quand le post WP est cr\u00e9\u00e9.<\/li>\n<li><strong>Boutons d'action fonctionnels<\/strong> avec feedback imm\u00e9diat : pause \/ reprendre \/ annuler envoient maintenant un toast de confirmation, forcent un rafra\u00eechissement imm\u00e9diat du status et de l'historique. Plus jamais d'action \u00ab silencieuse \u00bb.<\/li>\n<li><strong>Dialog \u00ab D\u00e9tails \u00bb<\/strong> sur chaque ligne d'historique + bouton dans la modale active : affiche tous les param\u00e8tres du job (statut, source, dates, config IA, mots-cl\u00e9s un par un avec leur statut individuel et lien direct vers l'article WP cr\u00e9\u00e9). Indispensable pour v\u00e9rifier o\u00f9 en est un job ou consulter ses anciens runs.<\/li>\n<li><strong>Bouton \u00ab D\u00e9tails \u00bb dans la modale active<\/strong> \u00e9galement, pour consulter la liste des keywords pendant la g\u00e9n\u00e9ration.<\/li>\n<li><strong>Affichage du mot-cl\u00e9 en cours<\/strong> dans la modale : <code>\u26a1 En cours : \"comment choisir un tracteur\"<\/code> \u2014 l'utilisateur sait exactement o\u00f9 en est le g\u00e9n\u00e9rateur.<\/li>\n<li><strong>Toast final<\/strong> \u00ab G\u00e9n\u00e9ration termin\u00e9e \u2014 N articles \u00bb \u00e0 la compl\u00e9tion du job (au lieu de la modale qui restait \u00e9ternellement).<\/li>\n<li><strong>Compteurs propres<\/strong> : on lit maintenant les bons noms de champs (<code>completed_count<\/code>, <code>total_count<\/code>, <code>generated_count<\/code>, <code>published_count<\/code>) avec alias r\u00e9tro-compat. Plus de \u00ab undefined \/ undefined \u00bb.<\/li>\n<\/ul>\n\n<h4>1.1.66<\/h4>\n\n<ul>\n<li><strong>Fix critical<\/strong> : sur les sites peu visit\u00e9s, WP-Cron ne tournait pas \u2192 les articles g\u00e9n\u00e9r\u00e9s c\u00f4t\u00e9 SaaS restaient en attente et n'\u00e9taient jamais publi\u00e9s. Le job passait \u00e0 <code>awaiting_wp_publish<\/code> mais le plugin ne pullait jamais les articles.<\/li>\n<li><strong>Sync-pull dans le polling<\/strong> : \u00e0 chaque poll status (toutes les 5s c\u00f4t\u00e9 JS), si le job est <code>running<\/code> \/ <code>awaiting_wp_publish<\/code> \/ <code>paused<\/code>, le plugin d\u00e9clenche un pull synchrone imm\u00e9diat \u2014 il pull les items pr\u00eats, fait <code>wp_insert_post<\/code> localement, et confirme \u00e0 Flask (qui d\u00e9bite le cr\u00e9dit). Le navigateur du user devient le moteur de cron, exactement comme on a fait pour l'Automesh sur shared hosting.<\/li>\n<li><strong>Boutons actions dans l'historique<\/strong> : nouvelle colonne \u00ab Actions \u00bb avec \u23f8 Pause \/ \u25b6 Reprendre \/ \u2715 Annuler pour chaque job actif. Plus besoin d'attendre que le banner du job actif s'affiche pour le piloter.<\/li>\n<li><strong>Toast FR direct<\/strong> : \u00ab G\u00e9n\u00e9ration en masse lanc\u00e9e \u00bb au lieu du fallback anglais \u00ab Bulk job started \u00bb quand les traductions PHP ne sont pas encore charg\u00e9es (transition de version).<\/li>\n<\/ul>\n\n<h4>1.1.65<\/h4>\n\n<ul>\n<li><strong>Fix critical (jauge)<\/strong>: la barre de progression du bulk apparaissait pleine 5 secondes au refresh avant de revenir \u00e0 0. Cause : <code>total_count<\/code> pass\u00e9 en string <code>'\u2026'<\/code> au render initial \u2192 calcul <code>(0+0+0)*100\/'\u2026'<\/code> = NaN \u2192 <code>width: NaN%<\/code> CSS invalide \u2192 fallback navigateur \u00e0 100%. Tous les compteurs sont d\u00e9sormais cast\u00e9s en <code>parseInt(... 10) || 0<\/code>, et le render initial part avec <code>total_count: 0<\/code> (jauge \u00e0 0%, plus de flash).<\/li>\n<li><strong>Traductions FR<\/strong> compl\u00e8tes de toute la nouvelle vue Bulk generation. Tous les libell\u00e9s sont en fran\u00e7ais dans la vue PHP, et les fallbacks JS \u00e9galement (statuts du job, sources, labels de progression, boutons pause\/reprendre\/annuler, configuration de g\u00e9n\u00e9ration, etc.).<\/li>\n<li><strong>Format date fran\u00e7ais<\/strong> dans l'historique (<code>toLocaleDateString<\/code> + heure HH:MM) au lieu de l'ISO brut.<\/li>\n<li>Labels lisibles pour status (<code>queued<\/code> \u2192 \u00ab En file \u00bb, <code>running<\/code> \u2192 \u00ab En cours \u00bb, <code>awaiting_wp_publish<\/code> \u2192 \u00ab Publication WordPress \u00bb, etc.) et source (<code>keyword_list<\/code> \u2192 \u00ab Liste de mots-cl\u00e9s \u00bb, <code>collections<\/code> \u2192 \u00ab Cat\u00e9gories produits \u00bb, etc.) \u2014 plus de codes techniques affich\u00e9s \u00e0 l'utilisateur.<\/li>\n<\/ul>\n\n<h4>1.1.64<\/h4>\n\n<ul>\n<li><strong>Fix critical (bulk articles)<\/strong> : audit complet de 1.1.63 \u2014 l'impl\u00e9mentation initiale n'aurait pas pu fonctionner. Trois probl\u00e8mes bloquants corrig\u00e9s.<\/li>\n<li>Fix 1 \u2014 <strong>API authentication broken<\/strong>. The bulk handlers used <code>get_option('essiow_api_key')<\/code> which reads an option that doesn't exist in clear (the API key is stored AES-encrypted in <code>essiow_api_key_enc<\/code>). They also missed the HMAC anti-replay signature headers (<code>X-Timestamp<\/code>, <code>X-Nonce<\/code>, <code>X-Domain<\/code>, <code>X-Signature<\/code>). Every call would have failed with 401 Unauthorized. \u2192 Refactored all handlers to use the canonical <code>Essiow_API_Client::instance()<\/code> which handles decryption + HMAC signing transparently.<\/li>\n<li>Fix 2 \u2014 <strong>JSON response unwrapping bug<\/strong>. <code>Essiow_API_Client::handle_response()<\/code> returns the decoded body directly (so <code>$resp['data']<\/code> IS the data), but the bulk code wrapped on a 3rd level (<code>$resp['data']['data']['jobs']<\/code>), which always evaluated to null. Status \/ list \/ create \/ pending-items \/ confirm-published \u2014 all 7 endpoints affected. \u2192 Aligned with the canonical client structure.<\/li>\n<li>Fix 3 \u2014 <strong>CSV preview moved to plugin-side parsing<\/strong>. The original implementation tried to POST a multipart upload to Flask without computing the HMAC signature for binary content. Replaced by a self-contained PHP CSV parser (header sniffing for keyword\/volume\/KD\/intent across English\/French\/German\/Spanish aliases, encoding detection with BOM stripping, delimiter auto-detect via comma\/semicolon\/tab\/pipe count). No more round-trip to Flask for previews \u2014 faster and avoids the signing problem entirely.<\/li>\n<\/ul>\n\n<h4>1.1.63<\/h4>\n\n<ul>\n<li><strong>New: Bulk article generation<\/strong> for WooCommerce \/ WordPress. Generate dozens to hundreds of SEO articles in one go from four sources:\n\n<ul>\n<li><strong>Product categories<\/strong>: one article per selected WooCommerce category<\/li>\n<li><strong>Products<\/strong>: one article per product (up to 500)<\/li>\n<li><strong>Keyword list<\/strong>: free-form textarea, comma or line separated<\/li>\n<li><strong>SEMrush CSV<\/strong>: upload your export (Keyword Magic Tool \/ Organic Research \/ Keyword Gap), preview keywords with volume\/KD\/intent, select which to keep<\/li>\n<\/ul><\/li>\n<li>Server-side orchestration via the Essiow backend. The browser only triggers the job \u2014 generation continues even if you close the tab. A WP-Cron worker pulls ready articles every 5 minutes and inserts them as WordPress posts (or via wp_insert_post triggered immediately on first launch).<\/li>\n<li><strong>1 credit = 1 article published<\/strong>: credits are debited only after the WordPress post is successfully inserted (atomic transaction backend-side). If credits run out mid-job, it auto-pauses; refill and resume.<\/li>\n<li><strong>Pause, resume, cancel<\/strong> any running job at any time. State is persisted server-side.<\/li>\n<li><strong>Crash resilient<\/strong>: if the worker dies mid-generation, the next pull picks up where it left off without re-paying for what was already generated.<\/li>\n<li><strong>Drip publishing<\/strong> option: spread articles over N days (1 every N days) instead of all at once \u2014 better for SEO patterns and avoids Google seeing a burst.<\/li>\n<li><strong>Per-post config<\/strong>: author, post category, publish status (immediate or draft), tone, length, language, fuzzy dedup toggle.<\/li>\n<li><strong>SEO meta filled out<\/strong>: Yoast \/ Rank Math \/ AIOSEO meta fields are set automatically when the SaaS returns SEO title\/description.<\/li>\n<li>Hard cap of 500 articles per job (anti-blast-radius).<\/li>\n<li>Job history with status per row, restart polling automatically when reopening the page on an active job.<\/li>\n<\/ul>\n\n<h4>1.1.62<\/h4>\n\n<ul>\n<li><strong>Performance overhaul<\/strong> of <code>_automesh_compute_plan<\/code> \u2014 targets sub-30s compute even on 10k-page sites, so the planning phase fits inside the tight PHP-FPM timeouts of shared hosts (EazyWP, low-tier hosts) and stops triggering the 1.1.61 \"planning_aborted\" abort.<\/li>\n<li><strong>Optim 1 \u2014 Batch WP term cache<\/strong> : <code>wp_get_post_terms()<\/code> was called once per product (10000 separate SELECTs on a 10k-product site). Now pre-warms the WP term cache via <code>update_object_term_cache($product_ids, 'product')<\/code> \u2014 1-2 SELECTs total, the per-product calls become free cache hits.<\/li>\n<li><strong>Optim 2 \u2014 Token memoization<\/strong> : every <code>_jaccard_titles()<\/code> call was re-tokenizing both titles from scratch. On a 5000-page site with 100-sibling avg pool, that's 500k tokenizations. Added an instance-scoped <code>_token_cache<\/code> keyed by title \u2192 ~5k tokenizations total. Speedup ~100\u00d7.<\/li>\n<li><strong>Optim 3 \u2014 Faster Jaccard intersection<\/strong> : replaced <code>array_intersect<\/code> (O(n\u00d7m)) with <code>array_flip + isset()<\/code> lookup (O(n)). 5\u00d7 cumulated gain.<\/li>\n<li><strong>Optim 4 \u2014 Pool size cap<\/strong> in <code>_best_topical_match*()<\/code>. When the candidate pool exceeds 1000 items, sample 500 random instead of scanning all. Statistically same top-K quality, but O(N\u00b2) explosion neutralized on massive sites.<\/li>\n<li><strong>Optim 5 \u2014 Skip anchor registry preload entirely above 5000 pages<\/strong>. The <code>AUTOMESH_EXACT_RATIO_MAX<\/code> (15%) guard rail already prevents over-using exact anchors, so an empty initial registry produces a balanced mesh from run 1 anyway.<\/li>\n<li>Result on a 10k-page site (measured on a dev VM): compute_plan dropped from ~180s to ~22s. Memory footprint divided by ~3.<\/li>\n<li>Note: if a 10k+ site still hits <code>planning_aborted<\/code> on your host after this update, the <code>max_execution_time<\/code> is &lt; 30s. Ask your host to set it to 60s+, or split into smaller imports.<\/li>\n<\/ul>\n\n<h4>1.1.61<\/h4>\n\n<ul>\n<li><strong>Fix critical<\/strong>: automesh planning phase stuck in infinite loop (\"Preparing your link plan\u2026 5000s+\"). On hosts where PHP-FPM kills requests at 60-120s, each worker attempt died before saving its progress \u2014 and every subsequent status poll relaunched a fresh attempt that died again. No exit, no error, just an ever-growing elapsed counter.<\/li>\n<li>Three combined fixes:\n\n<ol>\n<li><strong>Anchor registry sampling for large sites<\/strong> (&gt; 2000 pages). The biggest CPU\/memory bottleneck of <code>_automesh_compute_plan<\/code> is <code>_preload_anchor_registry<\/code>, which fetches and regex-parses the full HTML content of every page. On 10000 pages that's 10000 SQL + 500 MB of regex. Now sampled to 500 random pages on large sites \u2014 enough to estimate existing anchor-type ratios without killing the worker.<\/li>\n<li><strong>Max 5 planning attempts<\/strong> counter on the task. If the worker keeps dying mid-compute (PHP-FPM timeout, OOM), after 5 retries we mark the task as <code>failed<\/code> with a clear log: \"PHP-FPM\/host kill shorter than required compute time. Contact your host to raise max_execution_time and memory_limit, or split your site into smaller imports.\"<\/li>\n<li><strong>Cancel button<\/strong> in the in-progress modal + new <code>ajax_automesh_cancel<\/code> endpoint. Lets users escape a stuck task instantly: marks it failed, releases the <code>essiow_il_automesh_active<\/code> lock, clears scheduled WP-Cron events. Confirmation prompt explains that already-injected links remain (backed up, revertable).<\/li>\n<\/ol><\/li>\n<li>New: when a task ends in <code>failed<\/code> state, the UI fetches the detailed log line from the backend and shows it in the error toast \u2014 actionable info instead of \"Something went wrong.\"<\/li>\n<li>New strings: <code>automesh_cancel<\/code>, <code>automesh_confirm_cancel<\/code>, <code>automesh_cancelled<\/code> (i18n).<\/li>\n<\/ul>\n\n<h4>1.1.60<\/h4>\n\n<ul>\n<li><strong>Refactor critical<\/strong>: the Automesh plan computation is now fully deferred to the background worker. On very large sites (10000+ pages), the compute_plan was taking &gt; 3 minutes inside the HTTP request, hitting browser timeouts, Cloudflare 100s upstream limit, and PHP max_execution_time even after our 1.1.59 hardening. No amount of timeout-raising could solve that \u2014 the compute had to stop blocking HTTP entirely.<\/li>\n<li><strong>Architecture after 1.1.60<\/strong>:\n\n<ol>\n<li><code>ajax_automesh_plan<\/code> (preview) returns a fast <strong>heuristic estimate<\/strong> based only on the cached graph (orphans count, deadends count, hubs count). \u2264 10s on any site size. No compute_plan, no simulate_apply_plan.<\/li>\n<li><code>ajax_automesh_start<\/code> immediately creates a task with <code>status='planning'<\/code>, schedules the worker, and returns in &lt; 1s. No more \"Timed out\" on click.<\/li>\n<li><code>cron_automesh_run<\/code> (worker) detects <code>status='planning'<\/code> on its first tick \u2192 runs <code>build_graph<\/code> + <code>compute_plan<\/code> (the heavy work, up to 5 minutes), then switches to <code>status='pending'<\/code> and processes batches as before.<\/li>\n<li><code>ajax_automesh_status<\/code> distinguishes <code>planning<\/code> vs <code>running<\/code>. The sync-fallback path (shared hosting where WP-Cron is dead) also handles the planning tick \u2014 same trick: if no tick in 30s, the status request itself runs the worker.<\/li>\n<\/ol><\/li>\n<li><strong>JS UI<\/strong>: shows \"Preparing your link plan\u2026\" with an indeterminate progress bar during the planning phase, then switches to \"X \/ Y pages processed\" once the plan is ready. Elapsed counter visible (so the user knows things are moving). Failed-status detection added to clear the modal if the worker crashes during planning.<\/li>\n<li>New strings: <code>automesh_planning<\/code>, <code>automesh_planning_hint<\/code>, <code>automesh_failed<\/code> (i18n).<\/li>\n<li>Removed: the 1.1.59 transient cache for the plan \u2014 no longer needed since the start endpoint doesn't compute the plan anymore.<\/li>\n<\/ul>\n\n<h4>1.1.59<\/h4>\n\n<ul>\n<li><strong>Fix<\/strong>: \"Erreur r\u00e9seau\" toast on Automesh when the site has many links (1000+ pages, 4000+ planned links). Four compounding causes audited and patched:<\/li>\n<li>Fix 1 \u2014 <strong><code>ajax_automesh_plan<\/code> was less protected than <code>ajax_automesh_start<\/code><\/strong> despite doing MORE work (build_graph + compute_plan + simulate_apply_plan + 2\u00d7 score). It had <code>set_time_limit(180)<\/code> only \u2014 no <code>ignore_user_abort<\/code>, no <code>wp_raise_memory_limit<\/code>, no <code>try\/catch<\/code>. Aligned on <code>ajax_automesh_start<\/code> (300s, memory raised, try\/catch with clear error detail).<\/li>\n<li>Fix 2 \u2014 <strong>Double-compute bug<\/strong>: clicking preview then \"Run automesh now\" recomputed the plan twice (60s + 60s = 120s cumulative \u2014 kill on Cloudflare\/Nginx at 100s upstream timeout). Now the preview caches its plan for 5 min, and <code>ajax_automesh_start<\/code> reuses it instead of recomputing. One-shot cache (consumed on use).<\/li>\n<li>Fix 3 \u2014 <strong>JS <code>$.post<\/code> had no <code>timeout<\/code> on the preview<\/strong>. Default Chrome XHR timeout is multi-minute, but Cloudflare\/Nginx kill at 100s and the browser sees a network error. Explicit <code>timeout: 180000<\/code> (3 min) added, with status-specific error messages (504 detected separately to point at the host's reverse proxy).<\/li>\n<li>Fix 4 \u2014 <strong>Transient compression<\/strong>: large plans (&gt; 100 KB serialized) are now <code>gzcompress<\/code>'d before being stored in <code>wp_options.essiow_il_automesh_plan_cache<\/code>. Avoids hitting MySQL <code>max_allowed_packet<\/code> (default 4-8 MB on shared hosting), which would silently truncate the row and break the worker.<\/li>\n<li>All four fixes also help the original EazyWP \/ shared-hosting scenarios from 1.1.58 \u2014 they apply to the preview stage which 1.1.58 didn't cover.<\/li>\n<\/ul>\n\n<h4>1.1.58<\/h4>\n\n<ul>\n<li><strong>Fix critical<\/strong>: Automesh stuck on \"0 \/ undefined pages processed\" on shared \/ low-spec hosts (EazyWP, Hostinger, low-tier OVH, etc).<\/li>\n<li>Two combined root causes :\n\n<ol>\n<li><strong>Truncated AJAX response<\/strong> from <code>ajax_automesh_start<\/code> : on a 1000+ page site, computing the plan takes 30-60s. PHP's default <code>max_execution_time<\/code> (30s) on shared hosting truncates the JSON response mid-write. The plugin received a partial response with <code>success:true<\/code> but <code>total<\/code> undefined \u2192 <code>0 \/ undefined<\/code> displayed.<\/li>\n<li><strong>WP-Cron broken \/ disabled<\/strong> : many shared hosts disable <code>DISABLE_WP_CRON<\/code> without setting up a real cron, or block loopback HTTP (used by <code>spawn_cron()<\/code>). The worker <code>cron_automesh_run<\/code> was scheduled but never executed \u2192 0 pages processed indefinitely.<\/li>\n<\/ol><\/li>\n<li>Fix 1 (server-side hardening) : <code>ajax_automesh_start<\/code> now wraps the plan computation in <code>try\/catch<\/code>, sets <code>set_time_limit(300)<\/code>, raises memory via <code>wp_raise_memory_limit('admin')<\/code>, and returns a clean error with detail if  &hellip;<\/li>\n<\/ul>","raw_excerpt":"Get more Google traffic on WooCommerce. AI rewrites your products, categories and articles using your real Google Search Console data \u2014 and pings ever &hellip;","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/294906","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=294906"}],"author":[{"embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/boni58"}],"wp:attachment":[{"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=294906"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=294906"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=294906"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=294906"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=294906"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/test.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=294906"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}