Added
-
Choose how long a returning reader is recognised. Settings → Privacy has a new Visitor identity panel: Session only, 24 hours, 7 days or 30 days. Until you choose, a site keeps today's behaviour — a returning reader is recognised for the rest of the calendar month in your site's timezone — and the menu shows that as Calendar month (current).
A window is a ceiling, not a promise. A reader first seen near the end of a window is recognised for less than it, exactly as a reader first seen on the 30th is recognised for one day today. Every sentence in the product now says "up to".
Session only changes what "visitors" means on that site. No visitor identity is written at all, so a reader is counted once per day and never recognised on a later visit — the number becomes daily session counting, the way it was for every site before 26 August 2026. The dashboard's Unique visitors definition, the Visitors page and the privacy-policy snippet all say so on a site set that way, rather than asserting the month. The public read API's
visitorsfield keeps its name and its shape; on such a site it inherits the same meaning, and the day you change the window is a measurement boundary for that site — identities before it cannot be re-derived, so a range that crosses it counts a returning reader once on each side.Changing the window is deliberate and recorded. The panel says what the change costs before you save it — every identity is re-minted from that moment, readers recognised under the old window count as new people, and nothing can be applied backwards — and every change is written to your audit trail.
-
Optional interaction capture: see what visitors did, not only which pages they saw. A second, optional script records the label of a button or link that was clicked, how much text was copied and which forms were submitted — and the visit trail describes them in place.
It is not part of your tracking tag and does nothing until you add it. Settings → Site → General has a switch that writes the extra
<script>line for you, with a separate switch for each of the three kinds.What it never records, by design and pinned by tests: anything you type, the text that was copied, any form field's value, any form field's name, and anything about where an element sat on the screen — no CSS selectors, no coordinates.
What it strips before anything is sent, in the visitor's own browser: email addresses become
[email], long digit runs become[number], and labels are cut to 60 characters. Adddata-pulse-ignoreto any element and nothing inside it is ever recorded, at any depth.The main script is unchanged and still 2.6 KB gzipped — the new code is a separate file, so a site that does not want interaction capture pays nothing for it.
-
A visitor's visits now read oldest to newest, and their steps group by page. The Visits list was the only thing on the visitor page running newest-first, while the month ribbon above it reads day 1 to 31 and the steps inside each visit read forwards in time — so you went down the steps forwards and up the visits backwards. Page 1 still holds the most recent visits; it just reads in the same direction as everything around it.
Nothing was ever shown twice. Opening a visit used to print one row per event, and a custom event carries the page it fired on — so a page with two events on it appeared on three consecutive rows. Now a row is a page, with the events that fired while it was open listed beneath it. One real example went from 17 rows to 8, and the "—" that used to sit in the duration column of every event row is gone: an event has no duration to report, which is what the dash was saying nine times over.
A page you genuinely visited twice still appears twice, because those are two separate pageviews.
-
Filter a visit's steps by type. An expanded visit now carries small counters — Pages, Outbound, Downloads, Events — and clicking one hides or shows that kind. The counts describe everything loaded and do not change as you filter, so a counter you switched off can always be switched back on. The last active one cannot be switched off, because an empty list looks identical to a visit that recorded nothing.
-
Outbound clicks and downloads are described in words. Instead of an event name beside a
urlchip beside apage_pathchip repeating the row's own path, a step now reads "Left for stripe.com/pricing" or "Downloaded price-list.pdf".Your own custom events are unchanged, and deliberately so: only you know what
checkout_stepmeans, so Pulse shows its name and every property rather than guessing at a sentence. The distinction is drawn from the event's exact shape, not its name — one site sends an event calledoutbound_clickthat is about garments.
Fixed
-
The line connecting the steps of a visit now runs through them. It sat 12 pixels to the left of the dots it was joining.
-
Visitors — a per-visitor view of your own analytics, off by default. A new page under Analytics (
G V) lists the readers seen in a range and drills into one of them: their visits, the pages of each visit in order, dwell per page, and any custom events they fired with their full properties. There is a live mode with rolling windows down to the last 30 minutes.This changes what Pulse will show you, not what Pulse collects. Every column the page reads was already being written for every site; the switch controls whether anyone can read it at visitor grain. It is off for every site, including existing ones, and turning it on is a deliberate act by a site owner that is recorded in your organisation's audit trail. Settings → Site → Privacy → Visitor views.
What a "visitor" is here, exactly. A server-derived pseudonymous key, re-minted at the start of every calendar month in your site's timezone. It carries no name, no email, no account, no cookie and nothing stored on the reader's device, it cannot be linked to the same person on another site, and at the end of the month it stops existing — a returning reader comes back as a new visitor, under a new name. The page says so on its face rather than in a footnote. History begins 26 Aug 2026, the day monthly identity was introduced; the date picker will not offer earlier days, because earlier rows have no monthly key at all and showing them would label per-day identities as if they were per-month ones.
What it deliberately cannot reach. Visitor-level data never appears on a public share link, in the public read API, in an export, or on the internal admin surface — four exclusions pinned by tests rather than by convention, because Pulse runs a live public demo through the share surface. Sessions Cerberus has convicted stay invisible here exactly as everywhere else. The n≥5 privacy floor on the public API is untouched.
Some of our own wording had to change with it: several pages said Pulse keeps "no individual records" or is "architecturally incapable of identifying individual people". The second half of that was always the load-bearing part and is unchanged — Pulse still cannot tell you who someone is — but "no individual records" stopped being true the moment this page existed, so it has been rewritten rather than left to age.
Changed
-
The steps of a visit are bigger, and each one branches off the line. In an expanded visit the page path, the sentences beneath it and their marks all stepped up a size, and every step now hangs from the visit's line by a short tick instead of merely sitting under its page. A 14-step visit is about a fifth taller than before; nothing new is boxed or bordered, because the line the eye already follows is what draws the hierarchy.
-
Visit duration measures time people spent, and 04-09-2026 is a measurement boundary. The tracking script used to report the wall-clock time since a page loaded as its time on page. A tab left open overnight counted — even on a laptop that was asleep — and a hidden background tab that reloaded itself after a deploy started a visit nobody was on. Measured on production: 73% of the Pulse dashboard's own summed visit time came from single pages carrying more than 30 minutes, and one page carried eight and a half hours. Two defects in the server's arithmetic compounded it: the first page of a second visit in a day contributed zero instead of its own time, and a multi-page visit counted its first page twice while never counting its last.
Tracking script v1.2.0 now measures engaged time — seconds the page was visible and the visitor active — in bounded one-second ticks, so a sleeping machine, a frozen tab or a walked-away desk adds at most a tick. The clock pauses while a tab is hidden and after two minutes without scrolling, clicking or typing, and resumes on activity. A page that loads hidden is not a pageview until it is shown; a URL rewrite that only changes the query string or hash is state, not a navigation (the dashboard's own metric picker had been minting a pageview per click). A page's time is credited to that page and never exceeds the gap to the next one.
Expect visit duration to step down on most sites and pageviews to step down on single-page apps. Pages recorded before the boundary carry no engaged measurement and cannot be re-measured; they are bounded by their visible time and capped at 30 minutes each, and history is recomputed on that basis — on a few sites the average steps up, because the old arithmetic never read a visit's last page. The public read API's
avg_durationis the same field with the corrected meaning, no wire change, as withvisitorson 26-08-2026. An SRI-pinned embed on v1.1.0 keeps the old bytes and the old measurement until the tag is updated; the rollingjs.ciphera.net/script.jsembed picks the change up automatically. Audit:Pulse/docs/audits/04-09-2026-visit-duration-audit.md. -
Funnels, redrawn and simplified. The funnel page is now one chart and one sentence: tall gradient columns show each step's survivors against the entry total, the loss between steps is drawn as the wedge between columns with the drop and the median time to the next step written in the gap, and the headline — conversion, completed of entered, change vs the previous period, median time to convert — is stated once, above the chart, and nowhere else. The five-tile KPI band and the duplicated daily rails are gone; the daily chart and the drop-off and breakdown panels remain. The drop-off panel now names the step it describes and opens on the step that loses the most visitors. The breakdown panel now shows each segment's end-to-end funnel conversion (it used to follow the selected step, which made the default view read "100% conv" for every row — true only in the useless sense), and every breakdown dimension is available, not a curated seven. List cards carry the same chart in miniature. Conversion rates below 10% now show one decimal — a 0.4% funnel no longer prints "0%".
-
Funnel steps may repeat a page or event — revisit funnels work now. A funnel like
/ → /pricing → /used to be rejected when saving ("a later duplicate step can never be matched") because the engine assigned each pageview to only one step. Matching is now evaluated per step, so a later step completes on any strictly-later matching event. Funnels whose steps don't overlap measure exactly as before; if two of your steps do overlap (say, a "contains" step that also covers an "exact" step's page), sequences the old engine silently missed are now counted — that is a correction, and numbers can step up slightly. -
Page "contains" steps are now case-insensitive, and
%/_match literally. This was the only case-sensitive "contains" in the product, and a literal percent or underscore in the value acted as a hidden wildcard. A contains step may match slightly more than before; exact and regex steps are unchanged. -
Funnel regexes are validated by the engine that runs them. Saving a funnel checks the pattern against the database's regex dialect, so a pattern that saves is a pattern that reads. Previously a pattern could pass the save-time check and then fail on every view of the funnel, forever.
-
Funnels need at least two steps, exact page steps must start with
/, and a site can hold up to 50 funnels. Creating and editing funnels and goals is now recorded in your organisation's audit trail. -
The tracking script sends engagement to a path ad blockers do not cancel, and 26-08-2026 is a measurement boundary for time-on-page and scroll depth. The beacon carrying duration, visible duration and scroll depth used to post to
/api/v1/metrics. EasyPrivacy carries a bare, domain-agnostic substring rule for exactly that path — written for an unrelated vendor, matching ours by coincidence — so uBlock Origin, Brave and everything else shipping that list cancelled the request client-side while the pageview itself landed. Those visitors were counted, and their engagement was not. The script now posts to/api/v1/engagement; the old path stays live server-side indefinitely for scripts already cached at the edge and for SRI-pinned embeds. Expect time-on-page, visible time and scroll depth to step up. That is the instrument widening, not readers behaving differently, and it cannot be backfilled — a blocked beacon never reached us to be recovered. Measured before the change: 89.9% of pageviews carried a duration over 30 days, lowest onciphera.netat 61.1%. The step will be smaller than that gap suggests, because the script also withholds the beacon by design when there is no genuine engagement to report. Ciphera is on no filter list; this was a path-name collision, verified against EasyPrivacy, EasyList, uBlock's uAssets, AdGuard and Disconnect. Analysis:Pulse/docs/audits/26-08-2026-psi-err-blocked-by-client-metrics.md. -
Tracking script v1.1.0. New bytes require a new immutable version, so the SRI-pinned embed snippet now offers
v1.1.0. An existing pinned tag keeps working and keeps posting to the legacy path — a pin is a promise that those bytes never change, so adopting the fix means updating the tag. The rollingjs.ciphera.net/script.jsembed, which carries no integrity attribute, picks the fix up automatically. -
"Unique visitors" now counts people, and 26-08-2026 is a measurement boundary. Visitor identity used to rotate every day, so a returning reader was re-counted once per day (and the public API's
visitorsfield inherited that). Since 26-08-2026, identity is deduplicated within each calendar month in the site's own timezone: a returning reader counts once, on the dashboard and in the public read API'svisitorsfield alike — same field, corrected meaning, no wire change. Expect the number to step down; that is the correction, not a traffic drop. Days before the boundary can never be re-deduplicated (the address the identity derives from is discarded on arrival and was never stored), so ranges reaching back keep per-day counting for those days. Session boundaries also moved from UTC midnight to the site's own midnight, which ends the long-standing off-by-one between the headline and the daily chart — and means the chart's daily values can now sum to more than the headline, correctly: the difference is your returning readers.
Removed
- The funnel conversion window is gone — it never did anything. The setting (24 hours to 30 days) shipped in March and was removed from the product in August: session identity resets at your site's midnight, so no conversion could ever span more than one session and every window setting measured identically (verified on production data before removal). Funnels state the real rule instead: a conversion counts when a visitor completes every step within one session. The March entry below is kept as history.
- The privacy scanner is gone. The Settings > Privacy scan — a single-request check that printed "Privacy score 100" for pages it could not meaningfully judge — has been removed end to end, including its stored results. The Privacy settings tab itself (page rules, path grouping, query-parameter allowlist) is unchanged.
- Scheduled email reports and outbound webhooks are gone; the Reports tab is now Alerts. Scheduled analytics reports and the notification-webhook integration have been removed. Uptime alert channels — email and in-app, fired when a monitor goes down or recovers — are unchanged and keep their tab (now named Alerts). Two fixes landed with the change: a paused alert channel no longer disappears from settings, and alert channels no longer consume report slots.
- Custom roles are gone. Roles are now the built-in three — Owner, Admin, Member — assigned through invite links. The Roles view remains as a read-only matrix of what each role can do. Existing memberships were unaffected; the only custom role ever created was a test artifact and was removed.
- The CDN page's world map is gone. It plotted bandwidth by Bunny edge datacenter, which read as visitor geography and was nothing of the kind. The ranked "Served from" region list — the honest version of the same data — remains, as does the Audience map (which really is visitor geography).
- The "Visitor recognition" setting is gone. The tracking script never read the storage attributes the control emitted — visitor identification has been fully server-side for months. Removing the control removes the pretense; nothing about tracking behavior changed.
- The Campaigns card's Export and Build URL buttons are gone. The card's own CSV download and the in-dashboard UTM builder (including the empty state's "Build a UTM URL" button) have been removed; the Source / Medium / Campaign / Term / Content tabs are unchanged. Campaign rows still ship in the dashboard's Export (the Campaigns sheet in the spreadsheet and the Campaigns table in the PDF), and the public UTM builder at
/tools/utm-builderis untouched.
Changed
- A share password no longer travels in the URL. Password-protected share links now authorize only through the short-lived cookie set when the password is entered; a
?password=query parameter is ignored (it used to leak into browser history and server logs). The share page also re-prompts for the password when access expires instead of quietly showing stale numbers.