Dashboards
summarize(events) takes raw events from any number of page views and returns the numbers a UX dashboard needs. It’s a pure function that runs anywhere: the browser, a Node script, or an edge function.
import { summarize } from 'pagepal/analytics';
const events = await db.select('ux_events').where('ts', '>', lastWeek);const report = summarize(events);40page views
48%had a rage or dead click
1.9savg. wait per view
10%dismissed the mascot
Frustration hotspots
button#apply-coupon "Apply"button#pay "Pay $##"div.plan-card "Enterprise"
Form errors by field
input[name="zip"]input[name="email"]
form#checkout: 20 submitted, 10 abandoned, most often on input[name="zip"]
Failing and slow requests
GET /api/shipping/ratesPOST /api/payments
Sample data rendered from summarize() output, like the analytics section on the home page.
What it returns
Section titled “What it returns”interface UXSummary { views: number; counts: Partial<Record<UXEventType, number>>; frustrationRate: number; // share of views with a rage or dead click dismissRate: number; // share of views where the mascot was dismissed avgWaitMs: number; // average time per view spent waiting on requests offlineMs: number; // total offline time
// Top 10 each, worst first: frustration: { element; page; rageClicks; deadClicks; total }[]; formErrors: { form; field; validity; count }[]; forms: { form; submits; successes; abandons; errors; topAbandonField }[]; requests: { endpoint; errors; slow; worstMs }[]; // endpoint = "POST /api/payments"}Reading it
Section titled “Reading it”frustrationis your list of broken or misleading UI. A highdeadClickscount on something that isn’t a button usually means it looks like one.formErrorstells you which validation rules users trip over. A field that often fails withpatternMismatchusually needs a clearer hint or a more forgiving pattern.forms[].topAbandonFieldis where people give up. Look at that field first.requestspoints at the endpoints behind “the app is slow” and “it didn’t save”.dismissRatetells you whether users like the mascot. If it’s high, make it smaller, quieter, orinteractive: false.
In the browser
Section titled “In the browser”For the current page view, without a server:
const ux = analytics();createPal({ plugins: [ux] });
console.table(ux.summary().frustration);The analytics demo builds a live dashboard this way.
The pal in the corner is pagepal itself.