Both packages are required at runtime but were not declared in package.json:
- react-simple-maps imports prop-types but does not list it as a peer or
direct dependency, so prop-types must be provided by the host project.
- @umami/react-zen lists react-aria-components in peerDependencies, so
the host project must provide it.
These are auto-installed by pnpm 8+ when auto-install-peers is true (the
project default), which masks the issue. They are not auto-installed by
npm or by pnpm with auto-install-peers disabled, causing build failures
on a fresh checkout.
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.
Address Greptile review feedback on #4243.
- Cloud-mode link.updateMany / pixel.updateMany now filter where: { ..., deletedAt: null } so a previously soft-deleted row keeps its original deletion timestamp instead of being restamped with the current time.
- Pre-transaction findMany now selects deletedAt; the Redis invalidation list filters to only live slugs, avoiding harmless but wasted DEL calls for already-soft-deleted entries.
Note: the share.deleteMany cleanup still uses the broad entityId list (not filtered by deletedAt) so that orphan share rows of already-soft-deleted links/pixels are still cleaned up. Filtering the prefetch itself, as Greptile's exact suggestion proposed, would skip those shares while link.deleteMany still hard-deletes the rows, leaving orphan share rows behind. Verified empirically with a 3-scenario reproduction.
deleteUser and deleteTeam left link/pixel/board rows (and their share rows)
in the database after the owner was removed. /q/<slug> and /p/<slug>
also kept serving deleted entries because the routes did not filter
deletedAt and Redis cached lookups for 24h.
- deleteUser: clean up link/pixel/board + shares for the deleted user.
Cloud mode: soft-delete link/pixel, hard-delete board, only userId-owned.
Non-cloud: hard-delete everything matching userId or owned teamIds.
- deleteTeam: same cleanup, scoped to teamId.
- /q and /p route handlers: filter deletedAt: null at the call sites
(not in findLink/findPixel helpers, which would null-deref the
permission checks at src/permissions/link.ts and pixel.ts).
- Post-transaction Redis invalidation mirrors deleteWebsite.