=== VelocityForge ===
Contributors: avakodeforge
Tags: page speed, core web vitals, image optimization, critical css, caching
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.2.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Core Web Vitals from your real visitors, named down to the element, file and plugin behind them, with safe delivery hints and regression checks.

== Description ==

VelocityForge gives WordPress sites measurable Core Web Vitals improvements without the catastrophic "white screen after update" failures that aggressive caching plugins are famous for. It measures on your own server and keeps every measurement in your own database — nothing is loaded from a CDN, and no field reading ever leaves the site.

It does not stop at the number. A slow interaction is reported with the control that was hit, the split between input delay, your own handler and the repaint afterwards, and the plugin that shipped the script; a slow first paint is reported with the element and the file it loaded. Those are different problems with different fixes, and until a measurement carries one of them a number is only a number.

= Free =

Everything here is measurement and the hints that cost nothing to give. None of it is capped to make a paid tier look better.

* Core Web Vitals beacon — passive LCP / INP / CLS / TTFB / FCP collection from real visitors, stored in your own database
* And what each measurement points at: which element was your LCP, which control the slow interaction hit, what moved during a layout shift — and, for an interaction, whether the time went on a busy main thread, on the site's own handler, or on the repaint afterwards. Three different problems with three different fixes.
* What slows INP and what delays LCP — two cards on the Monitoring screen: the ten controls that cost the most interaction time and the five elements that most delay the first paint, each with the file and the plugin behind it. Every figure is a 75th percentile per element over 28 days and appears only once that element has at least three measurements, because a p75 of two measurements is the larger of two under a borrowed name
* 30 days of field-data history by default, and up to 90 if you ask for it in Settings
* LCP hints — `fetchpriority="high"` and `<link rel="preload">` on the detected hero, `decoding="async"` everywhere, `loading="lazy"` below the fold
* Page profiler — manual single-URL or batch audit of this site's own pages, up to 50 URLs per scan
* Speculation rules — steer the prefetch/prerender WordPress 6.8 already does, with a cart, checkout and add-to-cart exclusion list that cannot be switched off
* Asset inventory — full script/style handle map with owner attribution
* Database health — autoloaded-option weight, expired transients, overdue cron, and the twenty heaviest autoloaded options each named with the plugin that wrote it. "wp_options is four megabytes" is not something you can act on until the rows have owners beside them
* Regression monitor — post-update CWV delta check, and the e-mail to the site owner when one lands
* And what the update changed: the regression row names the scripts and stylesheets the front end gained, lost or resized, read from a snapshot of the real page taken immediately before the files were replaced. "LCP is up 400 ms" is a symptom; "this update added three render-blocking stylesheets" is something you can take to its author
* Trend chart — daily 75th percentile for LCP, INP, CLS and TTFB over the last 30 days, on the Monitoring screen. Inline SVG, no JavaScript, no chart library and no CDN, with the same numbers in a table for a screen reader
* Cache plugin coexistence — seventeen caching and optimisation plugins are recognised, from WP Rocket, W3 Total Cache and LiteSpeed to Jetpack Boost, SiteGround Optimizer, WP-Optimize, Breeze and WP Fastest Cache, and our overlapping work stands down while one of them is active. The notice names every feature that stepped back, not only the one that obviously overlaps
* Safe Mode — one-click panic recovery that disables all VelocityForge mutations, and a toggle in the admin bar on the front end as well as in wp-admin, because the front end is where you notice the breakage. It goes through the same rollback manager the form does, so it lands in the same audit trail
* Recovery drill — a button that presses the escape hatches on a day when nothing is wrong: the panic constant, a real loopback request to the secret panic route with the real secret, the safe-mode option's round trip, and the autoload backups table. Everything it touches is put back exactly as it was found. It writes no files.
* Rollback manager — restore previously saved rule snapshots
* Performance budgets — LCP 2500 ms, INP 200 ms, CLS 0.100, TTFB 800 ms and Total Blocking Time 300 ms, in one map on the Settings screen, against which a regression is banded. TBT is the odd one out and the screen says so: no field source populates it, because Total Blocking Time needs a long-tasks trace the real-user beacon does not capture, so that budget is read only by a Pro lab audit's comparison

= Pro =

VelocityForge Pro is a separate add-on, installed alongside this free plugin. Its features are Pro because their code ships in the add-on, not because this plugin refuses to run code you already downloaded.

* The Optimiser, as one purchase — everything that changes how the page is built:
  * Asset rules — defer / async / delay-until-interaction / dequeue / preload / preconnect per handle, scoped globally, to the front end, to a post type or to a URL pattern
  * Critical CSS — PHP heuristic extractor with a 14 KB inline cap and safe preload+swap defer
  * Image conversion — WebP / AVIF sidecars (Imagick or GD; originals are never deleted)
  * Autoload and transient cleanup — switch autoload off with a mandatory backup and a deny-list, and purge expired transients
* Rules that take themselves back — when a rule starts applying, VelocityForge records what real visitors measured over the previous week, waits out a window (24 hours by default, settable 6 to 72), then compares. If LCP or INP is worse by more than your threshold (10 % by default) it reverts the rule through the same revert your own button uses, writes the reason onto the rollback event, and e-mails you what was applied, what the numbers were and how to put it back. It refuses six ways first: no licence, no beacon, fewer measurements than your sample floor (30 by default), Safe Mode on, a hard-railed handle, and a rule you pinned. One open watch per rule and one verdict per watch, so a retried job cannot revert twice or send a second e-mail
* Regression alerts to Telegram, Slack or your own webhook — for the sites whose mailbox nobody reads. A card per channel with Add, Remove and a "Send test" that really sends and saves nothing, instead of a textarea of raw JSON. Bot tokens, webhook URLs and HMAC secrets are encrypted at rest and shown back only as their last four characters; leaving a field empty keeps what is stored, so the first save through the new form migrates an old clear-text secret without you touching it. Every URL goes through the same SSRF guard on the test path as on the save path — https only, and never a private or loopback address
* The CSV export of your field data
* AI Performance Advisor — reads your own field data and handle list, returns an ordered plan. It costs one AI credit per run, it refuses to answer when there is no field data to reason about, and it applies nothing: you read the plan and click the rules you agree with
* Scheduled lab audits — Lighthouse runs against a list of your pages once a day through your own PageSpeed Insights key. The results draw a second line on the free trend chart beside what real visitors measured, and a lab regression lands in the same history and reaches you through the same alerts. Useful exactly where field data cannot help: a page nobody has visited yet, a staging site, a change made at midnight. Lab runs band Total Blocking Time alongside LCP, CLS and TTFB, and the panel's status line is the run's own — nothing started says nothing, an open run says when it was queued, and a finished one repeats exactly the wording the history table uses, so the line and the table can never disagree.

VelocityForge is part of the **Forge Suite**. Learn more and get Pro at https://avakode.com.

== External services ==

Field data from the beacon is stored in your own database and is never sent anywhere — no measurement leaves your site in either tier.

This free plugin's own performance work does make HTTP requests, and every one of them is aimed at your own site: the page profiler fetches the pages of this site you audit or batch-scan (a URL on another host is refused), the regression monitor fetches your front page after you update a plugin or theme so it can compare the assets before and after, the asset inventory sends a HEAD request to each script and stylesheet your pages load from this site to read its size (assets served from other hosts are listed without one), and the recovery drill makes a real loopback request to your own secret panic route when you press its button. None of them is aimed at us or at anyone else.

The code also names some third-party hosts — js.stripe.com, paypal.com/sdk, www.google.com/recaptcha, challenges.cloudflare.com and the chat widgets of Intercom, Drift, HubSpot, Crisp, Tawk.to, Zendesk and Olark. They are match patterns, not services: a script from one of them that is already on your pages is recognised and left alone, so payments, captchas and chat keep working. This plugin never connects to any of them.

The free plugin also bundles the shared Forge Suite admin layer, which is the only part of this download that contacts our servers. Both calls happen in wp-admin only, and neither carries any field data:

* **Service health check** — a GET to https://api.avakode.com/health when you open the Forge dashboard screen, so it can say whether our API is reachable.
* **Feedback box** — a POST to https://api.avakode.com/feedback carrying your rating and your optional comment, sent only when you press Send on the box, and never on its own.

**Telemetry.** This free plugin sends no telemetry and has no switch for it. With the separate VelocityForge Pro add-on installed, Settings shows "Share anonymous usage telemetry", off by default, and Pro sends nothing until you turn it on.

= Bundled libraries =

* **web-vitals 4.2.4** by Google Chrome, licensed Apache-2.0 (compatible with this plugin's GPLv2-or-later). This is the script that measures LCP / INP / CLS / TTFB / FCP in the visitor's browser. It is bundled with the plugin and served from your own site — it is not loaded from a CDN, and it makes no request of its own. Source: https://github.com/GoogleChrome/web-vitals

= The Pro add-on =

The Pro add-on talks to three parties, each for a reason you can see in Settings:

* **Our Forge API** (https://api.avakode.com): licence validation when you activate a key and once a day after that; account connect if you link the site to your Avakode account; the credits balance and price list for the advisor button; plugin update checks for the add-on; and usage telemetry only after you opt in on the consent notice — nothing is sent while consent is off.
* **Your alert channels** — Telegram, Slack or your own webhook — only when you configure one and a regression fires.
* The AI performance advisor, and only when you run it: your aggregated Core Web Vitals and the list of render-blocking handles this site enqueues are sent for a plan. No page content, no visitor data, and nothing at all until you press the button.
* Scheduled lab audits, and only after you paste your own PageSpeed Insights API key. This call goes to Google (https://pagespeedonline.googleapis.com), not to us — it carries the URL of the page being audited and your key, and it spends your own Google quota under your own billing relationship with Google. Nothing is proxied through our servers and we never see the key: it is encrypted at rest with a value from your wp-config.php. Google's terms: https://developers.google.com/speed/docs/insights/v5/about — Google's privacy policy: https://policies.google.com/privacy

Terms: https://avakode.com/terms — Privacy: https://avakode.com/privacy

== Installation ==

1. Upload the `velocityforge` folder to `/wp-content/plugins/`, or install through Plugins → Add New.
2. Activate VelocityForge through the Plugins menu.
3. Open VelocityForge → Dashboard to run your first page audit.
4. Visit VelocityForge → Settings to tune thresholds and enable modules.

== Frequently Asked Questions ==

= Do I need an account or API key for the free version? =

No. Measuring and diagnosing happen entirely on your own server, and no field reading ever leaves the site. See "External services" above for exactly what this download does and does not contact.

On the Avakode channel, the bundled Forge Suite admin layer does contact us twice from wp-admin — a service health check when you open the Forge dashboard, and the feedback box if you choose to answer it — but neither needs an account or a key, and neither carries a measurement.

= How do I get the Pro features? =

Pro features ship in a separate "VelocityForge Pro" add-on. Install this free plugin first, then add Pro from https://avakode.com.

= Will VelocityForge break my site? =

All mutations are opt-in and every change has a rollback path. Safe Mode (one click) disables all output mutations instantly without deactivating the plugin, and it sits in the admin bar on the front end as well as in wp-admin — the front end being where you usually notice. Below that there are two hatches that do not need the admin at all: a secret URL that flips Safe Mode, whose secret is also written to `wp-content/.velocityforge-panic` so you can read it over SFTP, and `define('VELOCITYFORGE_PANIC', true);` in wp-config.php, which is checked before any database read, because the day you need it the database is often the problem.

= How do I know the escape hatches work before I need them? =

Run the recovery drill on the Rollback screen. It presses the real ones — including a genuine loopback request to your secret panic route with your real secret, since a wrong token would only prove the 403 branch works — and restores the safe-mode option, the stored secret and its on-disk copy exactly as it found them. A drill that left the file disagreeing with the database would break the one escape hatch it exists to verify.

= Can VelocityForge undo a rule on its own? =

Only with the Pro add-on, only for a rule you have just created, and only after refusing six ways: no licence, the beacon switched off, fewer measurements than your sample floor, Safe Mode on, a hard-railed handle, or a rule you pinned. When it does act it reverts that one rule through the ordinary revert path, writes the reason `auto-watch` onto the rollback event, and e-mails you the numbers and how to put it back. Nothing is reverted on thin evidence: below the sample floor the verdict is "inconclusive" and the rule stays exactly as it was.

= An update slowed the site down. What does VelocityForge actually tell me? =

Which assets changed. Before an update replaces any file, the plugin fetches your real front page and reads its script and stylesheet queue out of the served HTML; afterwards the regression row reads the difference and names what was added, what disappeared and what grew. The snapshot refuses any non-2xx response before parsing it, because during a bulk update WordPress serves a 503 maintenance page that parses perfectly into an empty list — which would have made your entire real asset queue look like an addition.

= Why does a card say "theme" instead of naming a plugin? =

Because the owner resolver only claims what it can support: a prefix it knows, a handle this site enqueues, or the `velocityforge/interaction_owner` filter. Where none of those answer it says "theme", or nothing. A confident wrong name is worse than an honest gap.

= Can I run it alongside my caching plugin? =

Yes — that is the assumption, not the exception. VelocityForge is not a page cache and does not want to be one. Seventeen caching and optimisation plugins are recognised and our overlapping work stands down while one of them is active, and the notice names every feature that stepped back rather than only the obvious overlap.

= Why does the Total Blocking Time budget never trigger anything? =

On field data it cannot. TBT needs a long-tasks trace, which the real-user beacon does not capture, so that budget is read only by the Pro scheduled lab audits' own comparison. It lives in the same budget map as the other four so there is one place to set them, and the Settings screen says what it does and does not measure.

== Changelog ==

= 1.2.4 =
* Field data is kept for up to a year on every install; the three-month ceiling without the Pro add-on is gone.
* The licence screen, the plan badge in the header and the upgrade card moved into the Pro add-on. This plugin only notes which features the separate add-on provides, with a link to its product page.
* A scan on the Assets screen runs only from the screen's own Scan button or "Create rule" link, which now carry a nonce.
* The readme lists the third-party hosts VelocityForge recognises on your pages and never contacts.

= 1.2.3 =
* The recovery drill no longer writes into wp-content. It checks the panic constant, the secret panic URL, the safe-mode option and the autoload backups, and leaves the filesystem alone.
* The page profiler and the asset inventory stay on this site: a pasted URL on another host is refused, and scripts or styles served from another host are listed without a size instead of being fetched.
* The notice about an outdated Pro add-on appears on VelocityForge screens, the plugin list and the dashboard only.
* The usage-telemetry switch appears only where something honours it — the Pro add-on — instead of in every install.
* Request input is sanitised where it is read, database queries are prepared or documented, and admin output is escaped.

= 1.2.2 =
* **This release needs VelocityForge Pro 1.2.0 or newer.** An older add-on is switched off whole rather than left running: 1.2.0 is where the free screens started promising that a rule which begins applying is measured against real visitors for a day and reverted on its own, and every part of that decision is in the add-on — beside a 1.1.x add-on the sentence is printed and nothing measures anything. Nothing you have saved is touched, an admin notice names both versions and what stopped, and installing the current add-on brings all of it back.
* Deleting the last Forge product on a site now clears the job queue the Forge plugins share — its table, its two scheduled events and its version marker. While any other Forge product is still installed, deleting this one leaves the queue exactly as it is, because the pending jobs in those rows are that product's too.
* Admin script globals carry the full velocityforge prefix instead of spf.
* The plugin's site is now velocityforge.app: the Forge Suite dashboard and the translations link there, as do the links to Guard Forge (guardforge.app) and Flow Forge (flowforgewp.app).

= 1.2.1 =
* Suite banner: the eleven products and the three-step set discount are up to date.

= 1.2.0 =
* **New: a measurement now names what it points at.** An interaction is recorded with the control that was hit and with the time split three ways — the wait before the handler ran, the handler itself, and the repaint afterwards — plus whether the page was still loading at the time. A largest paint is recorded with its element and the file that element loaded. Those live in their own table and are counted rather than read one at a time, because "one visitor waited 340 ms on button.add-to-cart" is an anecdote and "this is the p75 of two hundred interactions, and three hundred of those milliseconds are the handler" is a task.
* Two cards on the Monitoring screen say it out loud: the ten controls that cost the most interaction time and the five elements that most delay the first paint, each with the file and, where it can be established, the plugin that shipped it. The owner is resolved from a prefix table, then from this site's own enqueued handles, then from a filter — and where none of the three answers it says "theme" or nothing, never a confident wrong name.
* Every figure is a 75th percentile per element over 28 days, and an element appears only once it has at least three measurements. A p75 of two measurements is the larger of two under a borrowed name.
* An LCP file is linked only when its host is your own site. Resource URLs arrive through an unauthenticated beacon, so a stranger's link is named but never made clickable.
* **New: the regression says what the update changed.** Immediately before an update replaces any file, VelocityForge fetches your real front page and reads its script and stylesheet queue out of the served HTML — the rendered page, not the handle registry — and afterwards the regression row names what the front end gained, lost or resized. A non-2xx response is refused before the body is parsed, because a bulk update serves a 503 maintenance page that parses perfectly into an empty list, which would have made your whole real asset queue look like an addition.
* That snapshot is bounded on purpose: at most twenty-four assets are sized per run, and sizing stops after a three-second budget, so updating twenty plugins cannot eat `max_execution_time` from inside the installer. Both the page fetch and the size probes go through the SSRF guard, because the URLs come out of somebody else's HTML.
* **New: the Database screen names owners.** The twenty heaviest autoloaded options are listed with the plugin that wrote each one, resolved from the option-name prefix. Read-only, as the free health report has always been.
* **New: the recovery drill (free).** One button runs five independent checks — the panic constant, a probe write and delete in the panic file's directory, a real loopback request to the secret panic route with the real secret, the safe-mode option's round trip, and the autoload backups table — and puts everything it touched back exactly as it found it, the on-disk copy of the secret included. A drill that left the file disagreeing with the database would have broken the SFTP escape hatch it exists to verify.
* **New: Safe Mode in the admin bar**, on the front end as well as in wp-admin, because a broken site is usually noticed on the front end. It calls the same rollback manager the form does, so it lands in the same audit trail rather than becoming a second way to write the same state.
* **New in Pro: rules that take themselves back.** A rule that starts applying is measured for a window — 24 hours by default, settable 6 to 72 — against what real visitors measured the week before. If LCP or INP is worse by more than your threshold it is reverted through the same call your own button makes, the reason `auto-watch` is written onto the rollback event, and you get an e-mail with the numbers and how to put it back. It refuses on no licence, no beacon, too few measurements, Safe Mode, a hard-railed handle and a pinned rule; one watch is open per rule and one verdict is taken per watch, so a retried job cannot revert twice or send a second e-mail. The free plugin carries the table and announces that a rule was created or enabled; every decision is in the add-on.
* **Pro alerts are configurable by a person.** Bot tokens, webhook URLs and HMAC secrets used to sit in a JSON textarea in clear text and be printed straight back into it. They are now encrypted at rest and shown only as their last four characters, the screen is a card per channel with Add, Remove and a "Send test" that really sends and writes nothing, and an empty field means "leave it as it is" — so the first save through the new form re-encrypts an old clear-text value without you doing anything. The test path runs the same https-only, no-private-address guard as the save path, so it cannot be used to probe your internal network.
* **New: a Total Blocking Time budget**, 300 ms, in the same map as LCP, INP, CLS and TTFB. It is honest about itself: no field source populates it, because TBT needs a long-tasks trace the beacon does not capture, so it is read only by the Pro lab audits' comparison. Banding needed no new code — it keys off the metric name and does not care which instrument produced the value.
* The lab-audit panel's status line is now the run's own state instead of a fixed "Queued — refresh in a minute": nothing started says nothing, an open run says when it was queued, and a finished one repeats exactly the wording the history table uses, so the two can never disagree.
* Five more caching and optimisation plugins are recognised — Jetpack Boost, SiteGround Optimizer, WP-Optimize, Breeze and WP Fastest Cache — bringing the list to seventeen. The coexistence notice now names every feature we stood down rather than only the overlapping one: for a plugin that owns the whole asset pipeline the optimiser stands down entirely, and a banner claiming we had only stepped back from minification while four other features were silently off was worse than saying nothing.
* **New: the trend, on the Monitoring screen.** Thirty days of daily 75th percentile for LCP, INP, CLS and TTFB, so a regression row has the fortnight either side of it to be read against. A percentile rather than an average, because the average of a day where nine visitors waited a second and one waited eleven is two seconds, and two seconds is a number nobody experienced. A day with no measurements is a gap in the line, never a zero.
* Free, and it draws whatever it has. On a site with the Pro add-on and scheduled lab audits switched on, the same chart carries a second line.
* **New in Pro: scheduled lab audits.** Lighthouse runs against a list of your pages once a day through your own PageSpeed Insights key — your key, your Google quota, your billing relationship with Google, and no proxy of ours in between. The key is encrypted at rest with a value from wp-config.php and is never shown back to you.
* Lab results are ordinary measurements in the ordinary table, so the trend, the regression history, the e-mail and the Telegram / Slack / webhook alerts all treat them the same way. They are never compared against a real visitor's measurement, though: a reading from Google's edge and a reading from a browser in Jakarta are not the same instrument, and comparing them reports a regression whose only cause is the yardstick.
* **New: a Speculation panel.** WordPress 6.8 prefetches the next page on its own, conservatively and with no way to say "but not that link" short of writing a filter. This screen gives you the two controls — prefetch or prerender, and how eagerly — plus a list of paths to leave alone.
* It steers the rule set WordPress already prints rather than adding one of its own. That is not tidiness: a page may carry several speculation rule sets and the browser takes their union, so a second one can only add speculation, never take it away. "Prerender the site but never the cart" is unreachable any other way.
* Cart, checkout, my-account, logout, anything carrying a nonce and anything carrying `add-to-cart=` are excluded and cannot be switched on. Cart and checkout paths are read from the site — a shop whose cart lives at /basket/ is excluded at /basket — rather than guessed from slugs. Prefetch is railed as tightly as prerender, because a prefetch reaches the server too, and a speculated add-to-cart link puts a product in a real basket.
* The exclusions you write are checked and a bad line is refused, not repaired. One pattern the browser cannot read makes it discard every speculation rule on the page, so the screen drops the line and tells you how many it dropped.
* Defaults reproduce what WordPress does today — prefetch, conservative — so upgrading changes nothing about your pages until you move a control.
* On WordPress below 6.8 the panel says so plainly and the settings still save, ready for the upgrade. Nothing here is greyed out and nothing asks about a licence.
* **Corrected: what this plugin says about the requests it makes.** The readme claimed in four places that the free plugin makes no external request. That was never true of this download: the shared Forge Suite admin layer bundled with every Forge product checks our API's health when you open the Forge dashboard and posts your answer if you fill in the feedback box, and the plugin's own performance work fetches your audited URLs, your front page and each asset it sizes. Nothing about the code changed — no measurement has ever left the site, and none does now — but "Third-Party Services" now lists both calls to our API with their endpoints and what triggers them, which is what that section is for.

= 1.1.0 =
* Five things that were Pro are now Free, because their code was in this download all along. WordPress.org rejected a plugin like ours in August for exactly that — a licence check standing on top of code the free user already has — and it was right to. What moved:
  * **LCP hints.** `fetchpriority="high"` on the hero image and the matching `<link rel="preload">`. WordPress core has set fetchpriority on its own LCP guess since 6.3, so this was a charge for the platform.
  * **Field-data retention.** Free was clamped to seven days, which is too short to see a weekly pattern twice. It is thirty by default now, and will honour up to ninety.
  * **Batch scans.** Twenty-five URLs free, two hundred paid. There is now one cap, fifty, for everybody: it exists so a shared host survives a batch of synchronous self-requests, and that reason does not care who paid.
  * **The regression e-mail.** It was repaired in 1.0.1 and sold as Pro in the same release. Charging for a fix is not a business model. Detection was already free; being told is free too.
  * **The database health report.** Reading how much your site autoloads was never worth money.
* What Pro is now, stated plainly: the optimiser (asset rules, critical CSS, WebP/AVIF conversion, autoload and transient cleanup) as a single purchase rather than three separate keys; regression alerts delivered to Telegram, Slack or a webhook; a year of field data with CSV export; and the AI advisor. Every one of them is code that ships in the add-on.
* No screen in the free plugin greys a control out because you have not paid. Where a feature lives in the add-on, the page says so in a sentence and shows you the part that is free — the asset inventory, the autoload table, the health numbers, the regression history.
* Existing Pro licences are unaffected: the three optimiser keys became one, and everything they covered stays included.
* Every field measurement now records what it points at — the LCP element, the interaction target, the node that shifted, and for an interaction the split between input delay, processing and presentation. VelocityForge has always downloaded the build of web-vitals that carries this; it used to throw it away on arrival.
* **Fixed: no schema change had ever reached a site that already had the tables.** Every migration was written as `CREATE TABLE IF NOT EXISTS`, and dbDelta reads the table name straight after `CREATE TABLE` — so it believed the table was called `IF`, found none, and compared no columns, while the stored version number recorded a migration that had not happened. Corrected, and a test now fails on any migration written that way.

= 1.0.2 =
* The Core Web Vitals beacon now measures. The bundled copy of Google's web-vitals library had never been vendored — a placeholder file shipped in its place, so the measuring script was never defined and every field-data screen stayed empty on every install. web-vitals 4.2.4 is now bundled for real. If your beacon has been on and showing nothing, this is why.
* Removed "Scheduled CWV Monitoring" from the Pro feature list. No recurring audit was ever implemented behind it: the licence key unlocked nothing, so it should not have been sold. Post-update regression detection is unaffected — it is free, always on, and continues to fill the Monitoring screen. Regression e-mails remain Pro.
* Removed a daily cron left behind by that feature. It had no handler, so it woke wp-cron once a day to do nothing; existing sites are cleaned up automatically on upgrade.
* Translations caught up with the plugin. The catalogue had not been regenerated since 1.0.0, so the regression e-mail, the field-data export screen and the rewritten Monitoring notice all rendered in English on the fifteen translated locales. All fifteen are complete again, and Arabic, Hindi and Chinese now declare their plural rules instead of borrowing English ones.

= 1.0.1 =
* Regression alerts actually send. The "Email me on regressions" toggle had existed since launch with nothing behind it.
* Field-data CSV export, streamed — the half of "90-day retention" that was advertised and missing.
* Removed two licence entries with no code behind them (per-page profiles was a second name for asset rules; agency reports was not built).

= 1.0.0 =
* Initial public release. WordPress.org Free core; Pro features delivered by the separate VelocityForge Pro add-on.

== Upgrade Notice ==

= 1.2.4 =
Field data is kept up to a year on every install. Needs VelocityForge Pro 1.2.5 or newer if you run the add-on: the licence screen moved into it, and an older add-on stays paused until it is updated.

= 1.2.3 =
The recovery drill and the page profiler stay inside your own site: no writes to wp-content, no requests to other hosts. Needs VelocityForge Pro 1.2.0 or newer if you run the add-on.

= 1.2.2 =
Needs VelocityForge Pro 1.2.0 or newer — an older add-on is switched off whole rather than left running, because the rule watch the free screens promise lives entirely in 1.2.0 and later.

= 1.2.1 =
Suite banner: the eleven products and the three-step set discount are up to date.

= 1.2.0 =
Field measurements now name what they point at: the slow control, the delayed element and the plugin behind each. Adds the recovery drill, a trend chart and asset changes in the regression report. Needs VelocityForge Pro 1.2.0 or newer if you run the add-on.

= 1.1.0 =
LCP hints, 30-day field-data retention, 50-URL batch scans, the regression e-mail and the database health report are now part of the free plugin.

= 1.0.2 =
Fixes Core Web Vitals collection, which never ran: the measuring library shipped as a placeholder. Also drops a Pro feature that was listed but never built.

= 1.0.1 =
Regression e-mails now send, and field data can be exported. Nothing to do on upgrade.

= 1.0.0 =
Initial release.
