=== Flexa Cache ===
Contributors: flexatech
Tags: cache, page cache, object cache, redis, performance
Requires at least: 5.9
Tested up to: 7.1
Requires PHP: 8.2
Stable tag: 1.3.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Page cache, Redis object cache, and front-end optimization: static HTML before WordPress boots plus a persistent object cache, zero configuration.

== Description ==

📌 **Documentation:** [flexa-cache.flexacommerce.com](https://flexa-cache.flexacommerce.com/) has the full user guide, technical reference and benchmark.

Flexa Cache is a WordPress page cache and front-end optimization plugin, with an optional Redis object cache. The page cache is a file cache: it saves each page as a static HTML file (plus a gzip copy) and an `advanced-cache.php` drop-in serves that file before WordPress loads, so a cache hit runs no theme, plugin or database code. WP Fastest Cache and W3 Total Cache use the same technique. It works on Apache and nginx.

**What the page cache stores, and what it skips**

Only anonymous `GET` requests for normal front-end pages are cached. These are always served live and never stored:

* Logged-in users (always, whatever the settings say), and visitors with a comment-author or post-password cookie
* WooCommerce cart, checkout and account pages, and any visitor with items in the cart or a WooCommerce session
* URLs with a query string, and any request that is not `GET` (form posts, for example)
* wp-admin, `admin-ajax.php`, the REST API (`/wp-json/`), WP-Cron, feeds, search results, previews, 404 pages and password-protected posts
* Pages that define `DONOTCACHEPAGE`
* Anything on your own exclusion lists: URLs, cookies and user agents. A new install starts with `/cart`, `/checkout` and `/my-account` in the URL list.

Cached files have no expiry time. They are replaced when one of the events below clears them.

**When the cache is cleared**

* Publishing, updating, trashing, unpublishing or deleting a post clears that post's page, the home page, the post type archive, its category, tag and other term archives, its author archive and its date archives, including their paginated pages. When a slug changes, the old address is cleared too.
* Adding or removing a category or tag on a published post clears both the old and the new term archive.
* A new, edited or re-moderated comment clears its post.
* Saving a WooCommerce product or changing its stock clears the product page.
* Switching theme, saving the Customizer, editing a menu or moving widgets clears everything.
* Saving settings with a changed exclusion list, or turning the page cache or the master switch off and on, clears everything.
* Updating Flexa Cache clears everything once, so pages built by the old version are not kept.
* You can clear one URL or everything from the admin screen, WP-CLI or the REST API. If "Preload" is on, a full clear is followed by a crawl that rebuilds the cache.

**Turning things off**

Every feature has its own switch. "Enable page cache" only controls the page cache: with it off, the optimizations, widget cache and object cache you turned on keep running. To stop everything at once, turn off **Enable Flexa Cache**, the master switch at the top of General Settings. That removes the page cache drop-in, the `.htaccess` rules and the object cache drop-in, and nothing is cached or optimized. Your other settings stay saved, so turning the master switch back on restores them as they were.

**Host and CDN caches**

Flexa Cache only manages its own files. It does not purge Cloudflare, Varnish, LiteSpeed, Nginx FastCGI cache or a managed host's server cache. It does detect them (along with other caching plugins) and shows a warning on the Cache screen and in Site Health. If another layer caches your HTML, either turn one of them off or clear that layer after you publish changes; otherwise it can keep serving pages Flexa Cache has already cleared.

**Page Cache**

* Static HTML cache served by a pre-WordPress drop-in (no PHP/MySQL on cache hits)
* Gzip pre-compressed `.gz` files served natively
* Separate mobile cache variant (`index-mobile.html`) when Mobile Theme is enabled
* Smart invalidation: purge on new post, updated post, comments, theme/menu/widget changes, and WooCommerce product/stock updates
* Per-URL cache purge from the admin UI, WP-CLI, or REST API
* Cache preloader via Action Scheduler (or WP-Cron fallback) with start/cancel/status controls
* Widget output cache with O(1) versioned-key invalidation

**Front-End Optimization**

* Minify HTML (whitespace collapse) and Minify HTML Plus (inline CSS/JS trim)
* Minify CSS and Minify CSS Plus (deeper punctuation squeeze)
* Combine CSS — adjacency-only buffer fusion preserving cascade order
* Combine JS / Combine JS Plus — concatenate header and footer scripts
* Gzip — mod_deflate `.htaccess` block for dynamic (non-cached) responses
* Browser Caching — Expires/Cache-Control `.htaccess` block
* Disable Emojis — remove WordPress emoji scripts, styles, and DNS prefetch
* Lazy Load — native `loading="lazy"` + `decoding="async"` on images and iframes
* Render Blocking JS — add `defer` to render-blocking scripts
* Google Fonts Async — rewrite Google Fonts `<link>` to preload + `onload` swap with `<noscript>` fallback
* Delay JS — neutralise non-critical external scripts until first user interaction or 7-second idle fallback

**Developer-Friendly**

* WP-CLI: `wp flexa-cache clear [--url=<url>]`, `wp flexa-cache status`, `wp flexa-cache preload [start|status|cancel]`
* REST API: `/flexa-cache/v1/cache/purge`, `/cache/stats`, `/cache/preload`
* Filterable hooks: `flexa_cache/engine/is_cacheable`, `flexa_cache/engine/allowed_hosts`, `flexa_cache/engine/buffer`, `flexa_cache/store/dir`, `flexa_cache/purged`, `flexa_cache/settings/languages`, and more
* Kill switch: `?flexa_cache_optimize=0` disables optimization for a single admin request
* Exclusion lists for URLs, cookies, user agents, CSS handles, and JS handles

**Object Cache (Redis)**

* Persistent Redis object cache: caches WordPress options, transients, and post/term/user meta and query results across requests, cutting database load
* Installs a self-contained `object-cache.php` drop-in with an in-memory L1 + Redis L2 layout
* One-click enable/disable and a "Test & save connection" form that writes the config to `wp-config.php` (secrets never touch the database)
* Crash-safe by design: if Redis becomes unreachable the site keeps running (degrades to in-memory) with an admin notice and a circuit breaker — never a white screen
* Serializers (PHP, igbinary, JSON, msgpack) and compression (LZ4, Zstd, LZF), key prefetch, and alloptions splitting
* Live analytics: hit-ratio / operations / memory charts, a per-group key table, Query Monitor panel, and WP-CLI (`analytics`, `watch`, `groups`, `slowlog`)
* Multisite aware, with per-site and network flush scopes
* Advanced Redis topologies — Cluster, Sentinel, Replicated (master/replica), and the Relay/Predis clients — are available through the separate **Flexa Cache Pro** add-on

== Installation ==

1. Upload the plugin files to `/wp-content/plugins/flexa-cache`, or install through the WordPress **Plugins** screen.
2. Activate the plugin through the **Plugins** screen.
3. Open **Flexa Cache** from the admin sidebar (located below Settings).
4. Make sure **Enable Flexa Cache** and **Enable page cache** are on, then click **Save Settings**.

After an update you do not need to do anything. The `advanced-cache.php` and `object-cache.php` drop-ins are refreshed on the next admin page load when they no longer match the bundled copy, and the page cache is cleared once.

== Screenshots ==

1. General settings: master switch, page cache, preload the cache by crawling the site, widget cache and a separate mobile copy.
2. Exclusions: skip the cache for logged-in users or mobile, plus one-per-line lists of excluded URLs, user agents, and cookies.
3. Cache invalidation: automatically purge the cache when a post or page is published or updated.
4. Minify & Combine: minify HTML, CSS, and JS (with aggressive modes) and combine stylesheets and scripts to cut HTTP requests.
5. Performance: gzip compression, browser caching, disable emojis, eliminate render-blocking JS, async Google Fonts, lazy load images, and delay JS execution.
6. Cache status: cached file count and size, last-cleared time, drop-in state, clear a single URL, purge everything, and preload on demand.
7. Object Cache (Redis): connection status, live metrics (hit ratio, hits, misses, memory), and hit-ratio / operations / memory analytics.
8. Benchmark before Flexa Cache: Catchpoint Instant Test from Singapore, Lighthouse Performance 89, TTFB 0.753 s, LCP 1.97 s.
9. Benchmark after Flexa Cache: same page and test settings, Lighthouse Performance 98, TTFB 0.203 s, LCP 1.156 s.
10. Benchmark summary: TTFB 73% faster, LCP 41% faster, render-blocking requests cut from 27 to 13.

== Frequently Asked Questions ==

= How does the page cache work? =
On activation, Flexa Cache installs an `advanced-cache.php` drop-in into `wp-content/` and adds `define('WP_CACHE', true)` to `wp-config.php`. On the next uncached request, the plugin generates a static HTML file. Every subsequent request for that URL is served directly from the file — no PHP execution, no database query.

= Does Flexa Cache work with WooCommerce? =
Yes. Cart, checkout and account pages are never cached, and once a visitor has something in the cart (or a WooCommerce session) they get live pages for the rest of their visit. Shop pages with filter or sort parameters in the URL are not cached either. A product's cached page is cleared when the product is saved or its stock changes. The JS optimizers also skip cart, checkout and account pages.

= Does it work on nginx? =
The static file cache works on any server (the drop-in uses PHP `readfile()`). The Gzip and Browser Caching toggles write Apache `.htaccess` rules; the plugin's class docblocks include the nginx equivalent snippets for hosts that need them.

= How do I clear the cache? =
Click **Clear all cached files** on the Cache screen, use `wp flexa-cache clear` from the command line, or call `POST /wp-json/flexa-cache/v1/cache/purge`. To clear a single URL, use the per-URL input in the Cache Stats panel or `wp flexa-cache clear --url=https://example.com/page/`.

= What does "Minify JS" do? =
The Minify JS toggle is a documented safe no-op when used alone. JavaScript is only ever concatenated (via Combine JS), never minified in isolation — the plugin ships no JS parser. If you want JS minification, enable Combine JS together with Minify JS.

= Will it conflict with other caching plugins? =
Flexa Cache never overwrites an `advanced-cache.php` that another plugin installed. If one is present, Flexa Cache does not install its own drop-in, and the Cache screen and Site Health warn that another plugin's `advanced-cache.php` is in place. Deactivate the other plugin (it should remove its own drop-in), then save the Flexa Cache settings again. Flexa Cache also warns you when it finds another caching plugin, a host-level cache or a CDN that caches HTML; see the next question.

= Can I use it with Cloudflare or my host's cache? =
Yes, but Flexa Cache cannot clear those caches. When it clears a page, a CDN or server cache in front of your site can keep serving the old copy until its own expiry. Either let only one layer cache HTML, or clear the CDN or host cache after you publish changes. Static files (images, CSS, JS) on a CDN are not affected. If you use "Separate mobile cache" or "Exclude mobile", Flexa Cache sends `Vary: User-Agent`, but many CDNs ignore it for HTML, so a CDN that caches your pages must be set to tell phones and desktops apart. The Cache screen and Site Health list what was detected, based on the plugins you run, your hosting environment and the response headers of your home page.

= Which requests are never cached? =
Logged-in users, WooCommerce cart/checkout/account pages and shoppers with a cart, URLs with a query string, non-`GET` requests, wp-admin, `admin-ajax.php`, the REST API, WP-Cron, feeds, search, previews, 404 pages, password-protected posts, pages that define `DONOTCACHEPAGE`, and whatever you add to the URL, cookie and user agent exclusion lists. Developers can add their own rule with the `flexa_cache/engine/is_cacheable` filter.

= How do I check that a page is being cached? =
Open the page in a private window twice and look at the response headers: a page served from the cache has the `X-Flexa-Cache: HIT` header. You can also watch the cached file count on the Cache screen. To test an exclusion, visit the page, check for the header, then add the exclusion and reload: saving an exclusion change clears the cache, so the page should no longer be a `HIT`.

= How do I turn Flexa Cache off without losing my settings? =
Turn off **Enable Flexa Cache** at the top of General Settings and save. The page cache, every optimization, the widget cache, the Gzip and browser caching rules and the Redis object cache all stop, but each of their switches keeps its saved value. Turn the master switch back on and everything comes back as it was. "Enable page cache" on its own only turns off the page cache. Deactivating the plugin also stops everything, and also keeps your settings.

= Where are the plugin settings? =
In the WordPress admin sidebar, click **Flexa Cache** (located below Settings). Settings are organised into five sections: Cache, Invalidation, Performance, Exclusions, and Danger Zone.

= What is the Object Cache and do I need Redis? =
The Object Cache is a persistent store for WordPress's internal data (options, transients, meta, query results) that normally lives in the database. Backing it with Redis means those lookups are served from memory across requests, which reduces database load — especially on WooCommerce and other dynamic sites. It requires a reachable Redis server and the PHP `redis` extension. It is independent of the page cache; you can run either or both.

= What if my site already has an object-cache.php? =
Flexa Cache leaves it alone. Its own drop-in carries a signature, and any `object-cache.php` without it is treated as belonging to another plugin or your host: the Object Cache screen shows it as a foreign drop-in and the enable button stays off. Disabling, deactivating or uninstalling Flexa Cache only deletes `object-cache.php` when the file is its own. The only way to replace a foreign drop-in is the explicit `wp flexa-cache oc enable --force` command.

= How do I know the Redis connection works? =
Enter the connection details and click **Test & save connection**. Flexa Cache connects to Redis first and saves nothing if the test fails. Once enabled, the Object Cache screen shows the connection status, hit ratio and memory use, and `wp flexa-cache oc status` or `wp flexa-cache oc diagnostics` show the same from the command line.

= What happens if Redis goes down? =
Nothing breaks. Flexa Cache detects the failure mid-request, serves from its in-memory layer for the rest of the request, and opens a short-lived circuit breaker so the next requests do not stampede a dead server. An admin notice and a Site Health warning appear, and the cache recovers automatically once Redis is back.

= Does enabling the Object Cache store my Redis password in the database? =
No. The connection settings (including the password) are written to a marked block in `wp-config.php`, never to the options table, and the password is masked in every diagnostic, REST, and CLI output.

= Can I use Redis Cluster, Sentinel, or Relay? =
Those advanced topologies (Cluster, Sentinel, Replicated master/replica, and the Relay/Predis clients) ship in the separate **Flexa Cache Pro** add-on. The free plugin covers a single Redis server, which suits the large majority of sites.

== External services ==

Caching and optimization run entirely on your own server. The plugin connects to one external service, and only in the admin, for the reason below.

**Deactivation feedback (Flexa Product Intelligence)**

When you go to deactivate Flexa Cache on the Plugins screen, a short optional survey asks why. It is served by Flexa's product intelligence service at `https://product-intelligence.flexacommerce.com`. It runs only on `wp-admin/plugins.php`, never on the front end, and never blocks or delays deactivation.

* **What is sent, and when:** on opening the Plugins screen, a request to `/api/v1/config` (the product slug and tier) loads the survey configuration, cached for 6 hours. When you deactivate or interact with the survey, the reason you pick and any optional message you type are sent to `/api/v1/deactivations`, `/api/v1/events`, `/api/v1/feedback`, `/api/v1/feature-requests`, and `/api/v1/recovery-events`. If you later reactivate the plugin, a single `/api/v1/recovery-events` request records the reactivation and how long the plugin was inactive.
* **What is included:** an anonymous per-site identifier (a random UUID), the plugin version and tier, and by default your WordPress/PHP version and locale. No email, site domain, user identity, or raw IP is collected.
* **Opt out:** turn the environment data off with `add_filter( 'flexa_cache/deactivation_survey/config', fn( $c ) => array( 'collect_environment' => false ) + $c );`, or disable the whole survey with `add_filter( 'flexa_cache/deactivation_survey/enabled', '__return_false' );`.

Service terms and privacy policy: https://flexacommerce.com/pages/terms and https://flexacommerce.com/pages/privacy

== Changelog ==

= 1.3.2 =
* New: Flexa Cache now tells you when another page cache is running on your site. It checks for other caching plugins, an `advanced-cache.php` drop-in owned by another plugin, managed hosts with their own server cache (WP Engine, Kinsta, SiteGround, Pantheon and others), and CDNs or proxies that cache your pages (Cloudflare, Varnish, LiteSpeed, Nginx, CloudFront, Bunny, Fastly, Sucuri). Warnings appear on the Cache settings screen and in Site Health. It only reports what it finds; nothing in the other cache is changed.
* Developers: new REST route `flexa-cache/v1/cache/layers` and filters `flexa_cache/layers/findings` and `flexa_cache/layers/plugins`.
* New: turn on object cache analytics from the Object Cache tab and choose how many requests are sampled. You no longer need to edit `wp-config.php` by hand.
* Fix: turning the object cache back on no longer makes plugins look deactivated. Redis could still hold old settings from the last time it was used, including the list of active plugins. The old data is now cleared when the object cache is switched on. If your site is already affected, updating fixes it: the cached data is cleared once on the first page load after the update.
* Fix: a post moved to the trash could stay cached and keep showing to visitors. Its cached page is now cleared when it is trashed, unpublished or deleted, and the old address is also cleared when you change a post's slug.
* Fix: with "Exclude mobile" on, phones and tablets could still get a cached page that a desktop visitor had created. Mobile visitors now always get a fresh page.
* Fix: visitors matching "Excluded user agents" could still get a cached page that another visitor had created. They now always get a fresh page.
* Change: the page cache is now cleared automatically after Flexa Cache is updated, so pages cached by the old version do not keep showing.
* Fix: with "Minify HTML" on, text in neighbouring elements could run together, for example a sale price showing as "$48$43" or menu items with no gap. The space between them is now kept.
* Fix: with "Render-blocking JS" or "Delay JS" on, WooCommerce product pages could show "wp is not defined" errors because a WordPress core script ran before the script it needs. Scripts that others depend on now load in the right order.
* Fix: lazy load no longer delays the logo and the main product image that WordPress marks as high priority.
* Fix: lazy load no longer changes image markup inside inline scripts, which could break those scripts.
* Fix: after you add a page to the exclusions, its old cached copy kept showing. Saving a change to any exclusion list now clears the page cache, so the change takes effect straight away.
* Fix: on sites with WooCommerce or another plugin that ships Action Scheduler, the preloader stopped after the first few pages and the progress bar stayed at the same count. It now finishes the run, and a run that is already stuck continues on its own after updating once you open the Flexa Cache screen.
* Fix: a page opened by someone who had left a comment could be cached with their name and email filled in the comment form, and then shown to other visitors. Those visitors' pages are no longer stored.
* Fix: with widget cache on, search forms and dropdown widgets lost their search box and select list. They now keep working, and the categories dropdown is left uncached so choosing a category still opens it.
* Fix: widget cache showed the same menu or category highlight on every page. Widgets are now cached per page, and filtered shop pages are not cached.
* Fix: "Separate mobile cache" now takes effect as soon as you save it.
* Fix: turning caching off and back on no longer shows pages cached before it was switched off.
* Fix: turning on "Preload" now starts warming the cache straight away, and the cache is no longer crawled while caching is off.
* Tweak: the Language option is hidden while English is the only choice.
* Fix: re-saving the Redis connection with the password field left blank removed the saved password from `wp-config.php`. It is now kept, as the "unchanged" hint says.
* Tweak: the longest object cache chart view now covers the last 2 hours (was 5 hours), matching how long the history is kept.
* Fix: the page cache could store copies of your site for any made-up domain name a request claimed to be for, which a bot could use to fill the disk. Only your site's own domain is cached now. Developers can allow more domains with the `flexa_cache/engine/allowed_hosts` filter.
* Fix: after a post was published, edited or removed, its category, tag, author and date archives (and their page 2, 3 and so on) kept showing the old list. They are now cleared too, including when you only add or remove a category or tag.
* Fix: a page could still be cached when something during rendering made it uncacheable, such as a product added to the cart. Every check now runs again before the page is stored.
* Fix: with "Browser caching" on, CSS and JavaScript files were told to expire after one month in one header and one year in another. Both now say one year. The rules are refreshed when you update the plugin.
* Fix: a guest could see another guest's WooCommerce "Recently Viewed Products" list, in the widget cache and in cached pages. Visitors with that list now get a fresh page, and the Recently Viewed and Cart widgets are never cached. The Cart widget also fills in correctly again with widget cache on.
* Fix: with "Separate mobile cache" or "Exclude mobile" on, pages now send a `Vary: User-Agent` header so a proxy or CDN in front of your site keeps phone and desktop copies apart.
* Fix: Kindle, Silk, BlackBerry and Opera Mobi devices are now recognised as mobile by the page cache in the same way as by WordPress, so "Exclude mobile" and "Separate mobile cache" apply to them.
* Fix: with "Separate mobile cache" on, widgets are cached separately for phones and desktops.
* New: an "Enable Flexa Cache" master switch at the top of General Settings. Turning it off stops everything the plugin does (page cache, optimizations, widget cache, browser caching and Gzip rules, Redis object cache) without losing your settings. Turn it back on and everything returns as it was.
* Tweak: the old "Enable caching" toggle is now called "Enable page cache", since it only controls the page cache.
* Tweak: the Exclusions tab shows "Exclude logged-in users" again, locked on, with a short explanation. Logged-in users have always bypassed the cache since 1.3.1; the toggle is there so you can see it.

= 1.3.1 =
* Fix: clearing the cache for one post no longer empties the whole page cache. Every post purge also clears the home page, and clearing a URL used to delete everything stored beneath it, so a single edit wiped every cached page on the site. Now only the files for that exact URL are removed.
* Fix: pages rendered for a logged-in user are never stored. With "Exclude logged-in users" turned off, a logged-in visit could be cached (admin bar, account name and all) and then served to anonymous visitors. Logged-in users now always bypass the cache, so that toggle has been removed from the Settings screen.
* Fix: the "Excluded cookies" list now works. It was saved but never checked. Any visitor with a cookie whose name contains one of the listed values now skips the cache, both when pages are written and when the drop-in serves them.
* Fix: after a plugin update, the `advanced-cache.php` and `object-cache.php` drop-ins are refreshed automatically on the next admin page load if they no longer match the bundled copy. Drop-ins that belong to another plugin are never touched.
* Fix: Site Health no longer reports the object-cache drop-in as out of date on every site. It was compared with the plugin version instead of the drop-in's own version.
* Fix: `wp flexa-cache oc enable --force` now actually replaces an `object-cache.php` from another plugin, as documented. Without `--force` a foreign drop-in is still left alone.
* Fix: deactivating the plugin now flushes and removes the object-cache drop-in, which previously kept running with the plugin switched off. Your Redis connection settings in `wp-config.php` are kept, and reactivating reinstalls the drop-in when the object cache is enabled.

= 1.3.0 =
* New: an optional deactivation feedback survey. If you deactivate Flexa Cache from the Plugins screen, a short survey asks why, so we can fix what is not working. It is anonymous (a random per-site id, no email or domain), runs only in the admin, and never blocks or delays deactivation. If you reactivate later, that win-back is recorded too (anonymously). Disable it all with `add_filter( 'flexa_cache/deactivation_survey/enabled', '__return_false' );`. See *External services* for exactly what is sent.

= 1.2.0 =
* New: a fresh install now turns on the safe basics automatically, so the plugin caches and optimizes from the moment you activate it. Activation enables the page cache (and installs the drop-in), gzip and browser caching (.htaccess), HTML and CSS minify, lazy loading, emoji removal, async Google Fonts, and cache purge on new/updated posts. Logged-in users bypass the cache by default. The JS/CSS combine options and the aggressive Delay JS / Render-Blocking JS optimizers stay off until you opt in, so a new install never risks a broken front end unattended. Every toggle remains editable on the Settings screen.
* New: a one-time setup wizard opens the first time you visit the plugin screen on a fresh install. Three quick steps: pick a preset (Safe, Balanced, or Manual), optionally head to Redis object-cache setup, and optionally run preload. A "Skip setup" link keeps the safe defaults and closes it. Once finished, the wizard never shows again.
* Note: this applies to new installs only. Updating an existing site keeps your current settings exactly as they are, and the wizard does not appear.

= 1.1.3 =
* Fix: WooCommerce cart, checkout, and account pages are no longer altered by the JS optimizers. Delay JS and Render-Blocking JS apply to every front-end response, not just cached ones, and their shared safety gate did not skip these per-user pages; deferring or delaying their scripts could leave the cart partially rendered (most visibly the block-based cart, which needs JS to hydrate). The gate now excludes `is_cart()`, `is_checkout()`, and `is_account_page()`, matching the page cache, which already skipped them. The checks are guarded so they stay inert when WooCommerce is not active.
* Change: a fresh install now seeds the Excluded URLs list with `/cart`, `/checkout`, and `/my-account`, so those pages are never cached or optimized out of the box. The list remains fully editable.

= 1.1.2 =
* Fix: the exclusion textareas on the Settings screen (Excluded URLs, user agents, cookies, stylesheets, and scripts) now accept the Enter key to start a new line. Each keystroke was previously trimmed and had empty lines stripped before being redisplayed, so the caret could never move onto a fresh line; the field now keeps your raw text while typing and only normalizes to one entry per line when the settings are saved.

= 1.1.1 =
* Fix: Delay JS / Render-Blocking JS / Combine JS no longer break scripts that carry inline code. A script with `wp_add_inline_script()` before/after data (most visibly WordPress core's `wp-i18n`, whose after-script calls `wp.i18n.setLocaleData()`) is now left un-delayed, un-deferred, and un-combined, because that inline companion runs at parse time and would otherwise throw "wp is not defined". The Combine JS path recovers the handle from the tag's `id` attribute so it can protect the same scripts even though it operates on the rendered buffer. This also resolves knock-on breakage of scripts that depend on those globals.
* Fix: the admin-bar "Flexa Cache" submenu no longer stays hidden on hover when JS optimization is enabled. The WordPress toolbar script depends on `hoverintent-js`; Combine JS / Delay JS / Render-Blocking now auto-exclude it alongside `admin-bar` (it was previously left in), so the submenu opens reliably.
* Compatibility: tested and confirmed working with WordPress 7.1. No other code changes were required — the admin app loads only on the plugin's own settings screen (not the iframed block editor) and uses no `@wordpress/components`, jQuery UI, or block-editor assets affected by 7.1.

= 1.1.0 =
* New: **Object Cache (Redis)** module — a persistent object cache installed as an `object-cache.php` drop-in (in-memory L1 + Redis L2). Caches options, transients, and meta/query data across requests.
* New: admin Object Cache tab — status banner, "Test & save connection" (writes `wp-config.php`, secrets never in the database), metrics, groups table, and flush controls.
* New: crash-safe resilience — in-request degrade to in-memory, cross-request circuit breaker with cooldown, `on_error` policy, and a Site Health check. Redis outages never cause a white screen.
* New: serializers (PHP/igbinary/JSON/msgpack) and compression (LZ4/Zstd/LZF), key prefetch, alloptions splitting, and per-`*-queries` TTL.
* New: observability — hit-ratio/ops/memory analytics, per-group key table, Query Monitor panel, request footnote, and a vendor-neutral `flexa_cache/object_cache/trace` hook.
* New: WP-CLI `wp flexa-cache oc` (`enable`, `disable`, `status`, `flush`, `diagnostics`, `analytics`, `watch`, `groups`, `slowlog`, `cli`, `reset`), and REST endpoints under `/flexa-cache/v1/object-cache`.
* New: multisite support with per-site and network flush scopes; TLS, unix socket, and ACL (username) connections.
* Note: advanced Redis topologies (Cluster, Sentinel, Replicated, Relay/Predis) are provided by the separate **Flexa Cache Pro** add-on.

= 1.0.3 =
* Cleanup: removed the unused `/ping` starter REST endpoint left over from the plugin scaffold.
* Docs: the readme source-code section now documents the build tooling (`npm install` → `npm run build`) alongside the public repository link.

= 1.0.2 =
* Fix: CSS minify (aggressive HTML minify and CSS combine) no longer strips whitespace around `:`, which previously fused descendant combinators into compound selectors before a pseudo-class (`:root :where(…)` → `:root:where(…)`) and silently dropped WordPress global-styles rules — most visibly Gutenberg button styles. Whitespace around `{ } ; ,` is still collapsed.

= 1.0.1 =
* Security: cache purge, stats, and preload REST endpoints now require the `manage_options` capability instead of `upload_files`, so only administrators can trigger site-wide cache operations.
* Hardening: cached widget output is now passed through `wp_kses_post()` before it is echoed.
* Cleanup: removed a dead pre-WordPress-5.7 raw `<script>` fallback in the delay-JS loader; the core `wp_print_inline_script_tag()` helper is now always used.
* CSS/JS combine and minify now rewrite the URL of the stylesheet/script tag WordPress already enqueued and printed, instead of emitting a freshly-built tag — preserving the original tag attributes.

= 1.0.0 =
* Initial release: page cache drop-in, 23 optimization toggles, WP-CLI, REST API, cache preloader, widget cache, mobile theme cache, i18n-ready.

== Upgrade Notice ==

= 1.3.2 =
Fixes plugins looking deactivated after the Redis object cache was turned off and on (affected sites recover after updating). Adds a warning when another page cache runs alongside Flexa Cache, and a switch for object cache analytics.

= 1.3.1 =
Fixes several page-cache and object-cache bugs, including a single post edit clearing the whole cache and logged-in pages being cached when that exclusion was off. Recommended for all sites.

= 1.3.0 =
Adds an optional, anonymous deactivation feedback survey shown only on the Plugins screen. It never blocks deactivation and can be turned off with a filter. Caching behavior is unchanged.

= 1.2.0 =
New installs now enable the safe caching and optimization basics on activation and open a one-time setup wizard. Existing sites keep their current settings unchanged.

= 1.1.3 =
Fixes WooCommerce cart/checkout/account pages rendering incompletely under JS optimization, and excludes them by default.

= 1.1.2 =
Fixes the Settings exclusion textareas so the Enter key adds new lines while typing.

= 1.1.1 =
Compatibility release for WordPress 7.1. No functional changes.

= 1.1.0 =
Adds an optional Redis Object Cache module (persistent object cache with a crash-safe fallback). The page cache is unchanged. Enable the Object Cache from the new admin tab; a reachable Redis server and the PHP redis extension are required.
