=== Real Core Web Vitals by TheSEO ===
Contributors: theseo
Tags: core web vitals, performance, analytics, privacy, speed
Requires at least: 6.0
Tested up to: 7.0
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Measures LCP, INP, CLS and TTFB at your real visitors and shows the 75th percentile per page and per device. No cookie, no external service.

== Description ==

Plenty of plugins promise to improve your Core Web Vitals. This one measures
them, at the people who actually visit your site, and shows you what they got.

Every figure on the screens is the 75th percentile over the chosen period,
split into mobile and desktop, which is the way Google defines the thresholds.
Nothing is estimated, nothing is scored in a laboratory and no advice is
attached to a number the measurement cannot support.

= What is measured =

* **LCP**, Largest Contentful Paint. Good at 2.5 seconds or less, poor above 4
  seconds.
* **INP**, Interaction to Next Paint. Good at 200 milliseconds or less, poor
  above 500.
* **CLS**, Cumulative Layout Shift. Good at 0.1 or less, poor above 0.25.
* **TTFB**, Time To First Byte. Not a Core Web Vital, but it sits underneath
  LCP. Good at 800 milliseconds or less, poor above 1800.

The measuring is an own implementation of the definitions Google publishes,
written out in readable JavaScript rather than pulled in as a library, so
anybody can read exactly what happens. It runs on the browser APIs that produce
these numbers: PerformanceObserver for the largest paint, the layout shifts and
the event timings, and the navigation timing entry for the first byte.

Not every browser implements every one of those APIs. A browser that cannot
measure a metric sends nothing for it, and the plugin stores nothing for it
either. It never writes a zero in place of a measurement that did not happen,
because a zero would read as a perfect score that was never taken. What you see
on the screen as a result is a different number of measurements per metric, and
that is correct rather than a fault.

= The honesty rule of this plugin =

A percentile over four page views is not a percentile. A page only gets a
figure once it has enough measurements, twenty by default and adjustable. Below
that the screen says "too few measurements" and prints how many there are, in
place of a number that would look precise and mean nothing.

= What it stores =

Per measurement one row:

* the address of the page, without the query string and without the fragment
* which metric it was
* the value
* the device class, mobile or desktop
* the hour it happened in

= What it does not store =

* no IP address, not in plain text and not as a hash
* no session, no visitor number, no user id
* no cookie and nothing in localStorage or sessionStorage
* no user agent string
* no query string, so a token or an e-mail address in a url never reaches the
  table
* nothing that leaves your server

Two rows from the same person cannot be recognised as belonging together, and
that is the design rather than an accident. The device class comes from the
width of the browser window, not from the user agent, because a user agent is a
claim and a fingerprint and a width is neither. The moment is rounded down to
the hour, so a row does not line up with one single request in a server log.

The visitor address is used once, to count requests per minute so the endpoint
cannot be flooded. It becomes a keyed hash in a value that expires within a
minute and it never reaches the measurements table.

= Do Not Track =

When a browser sends Do Not Track or Global Privacy Control, the script stops
before it measures anything. This is on by default and can be switched off.

= What this plugin is not =

It is not a cache plugin and it does not make anything faster. It does not say
what causes a slow figure and it never suggests installing anything.

Its numbers are also not the numbers Google uses for search. Google works from
the Chrome User Experience Report, which covers Chrome users who opted in and
needs enough traffic before it publishes anything at all. This plugin measures
every browser that supports the underlying API, including on a quiet site where
that report stays empty. The two will therefore differ, and the plugin says so
on the screen rather than in a footnote.

= Retention =

A daily job deletes measurements older than the retention setting, 90 days by
default. Deactivating keeps the data and stops the job. Deleting the plugin
drops the table, the settings and the job.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/theseo-webvitals`, or
   upload the zip through Plugins, Add new, Upload plugin.
2. Activate the plugin.
3. Go to Web Vitals, Settings and switch measuring on. Leave the sampling at
   100 percent unless your site is busy.
4. Visit your own site in a logged out browser and browse a few pages.
5. Open Web Vitals, Overview. Set the minimum number of measurements to 1 for a
   moment if you want to see the first figures straight away, then put it back.

== Frequently Asked Questions ==

= Does anything leave my server? =

No. The plugin makes no outgoing HTTP request. The script talks to your own
admin-ajax.php and to nothing else, and the measurements go into your own
database table.

= Does it slow the site down? =

The script is a single file of a few kilobytes, loaded in the footer with no
dependency, and it is not render blocking. It sends one request per page view,
at the moment the page is being left, through sendBeacon.

= Why is there no nonce on the collecting endpoint? =

Because it would break the measurement silently. A nonce is printed in the
page, so on a site with a full page cache every visitor gets the same nonce and
it stops working within a day. On a public endpoint that anonymous visitors
must be able to reach it is not a security control either. The endpoint is
guarded by the master switch, a same origin check, a rate limit per address,
and validation that accepts only the four known metrics, only numbers inside
their published range, and only a url of this site. The admin screens and every
save do use a nonce and a capability check.

= Does it work with caching plugins? =

Yes, and that is part of the point. Nothing in the page markup expires, so a
cached page keeps measuring, and what it then measures is what your visitors
really get from the cache.

= Why do my figures differ from PageSpeed Insights? =

Because they measure different things. The laboratory score in PageSpeed
Insights is one simulated load on one simulated device. This plugin is your
real audience on their real phones and connections. The field data in PageSpeed
Insights comes from the Chrome User Experience Report, which only covers Chrome
users who opted in and needs enough traffic to appear at all.

= Why does a page say too few measurements? =

Because it has fewer measurements than the minimum on the settings screen,
twenty by default. A percentile over a handful of page views says nothing, so
the plugin refuses to print one.

= Is INP measured exactly the way Google does it? =

It follows the same definition. Events that belong to one interaction are
grouped by their interactionId and the longest event of an interaction is that
interaction's latency. With fewer than fifty interactions the worst one is
reported; above that, one interaction in every fifty is allowed to be worse
than the reported figure, which is the rule that keeps a single outlier from
defining a whole page. Browsers that do not support the event timing API report
no INP at all, and then the column simply stays empty.

= Why does one metric have fewer measurements than another? =

Because not every browser implements every measuring API. LCP, CLS and INP each
depend on a browser feature that some browsers do not have, while TTFB comes
from navigation timing, which is available almost everywhere. A browser that
cannot measure something contributes nothing for it. The plugin does not fill
that gap with a zero, so the counts differ per metric and every count is the
number of real measurements behind that figure.

= Does it work on multisite? =

Yes. Each site in the network gets its own table and its own settings. Deleting
the plugin cleans up every site.

== Changelog ==

= 1.0.0 =
* First version.
* Measures LCP, INP, CLS and TTFB at real visitors, with an own readable
  implementation of the published definitions.
* Overview with the 75th percentile per metric, the rating against Google's
  thresholds, the number of measurements it rests on, and a bar per day.
* Per page table with the 75th percentile per metric and the number of
  measurements per cell.
* Mobile and desktop are always shown apart, split on the width of the window
  rather than on the user agent.
* A minimum number of measurements before any figure appears, twenty by
  default.
* Sampling at 10, 50 or 100 percent, retention of 30, 90 or 365 days with a
  daily cleanup job or keep everything, and Do Not Track support on by default.
* A metric a browser cannot measure is left out rather than stored as a zero.
* No IP address, no session, no cookie, no browser storage and no outgoing
  request.

== Upgrade Notice ==

= 1.0.0 =
First version.
