Use .finally() instead of .then() so internal link navigation is
always restored after e.preventDefault(), even if the tracking
request rejects (e.g. server down or a throwing beforeSend callback).
Fixes#4304
Three bugs in src/tracker/index.js, all empirically reproduced.
1. handleClicks() missed clicks on container elements: closest('a,button')
could not find a non-anchor ancestor with data-umami-event, so a click on
any descendant of <div data-umami-event=...> went untracked. Reworked to
closest([data-umami-event]) so any annotated ancestor matches.
2. handlePush() called new URL(url, location.href) outside normalize()'s
try/catch, so a host page calling history.pushState({}, '', invalidUrl)
would have umami's wrapper throw a TypeError into the host's router.
normalize(url) already handles base resolution and catches parse errors.
3. The history hook ran the umami callback BEFORE native pushState, so a
failed native call (SecurityError on invalid URL, etc.) would still mutate
currentUrl/currentRef and schedule a phantom pageview. Run native first;
if it throws, the callback never fires and tracker state stays consistent.
Bug 2 verified: 4/10 representative click scenarios missed before
(span inside div, deep span inside div, a with no href, button inside a),
all 10/10 tracked after.
Bug 1+3 verified: pushState({}, '', invalidUrl) now leaves tracker state
unchanged (currentUrl unchanged, no phantom pageview).
The INP observer sorted Object.values(interactions) on every event entry,
even though metrics.inp is only read when sendPerformance() flushes.
Defer the sort + p98 computation to flush, and drain queued observer
entries via observer.takeRecords() to capture the most recent
interactions on pagehide/visibilitychange.
Output is identical (same INP value computed). Savings are largest on
interaction-heavy pages and low-end devices where the per-event sort
compounds.
The handlePush function was still calling track() on SPA navigations
regardless of the auto-pageview setting. Gate that call behind the
autoPageview flag while keeping URL state and performance flush active.
Implements full session recording with rrweb for DOM capture and rrweb-player
for playback. Includes: Prisma schema for SessionRecording model, chunked
gzip-compressed storage, recorder script built via Rollup, collection API
endpoint, recordings list/playback UI pages, website recording settings,
and cascade delete support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Analytic is important activity but not as important as main business
logic. Setting low priority doesn't mean the fetch call will be more
likely to be discarded etc. it's just a hint to the browser.