Skip to content

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

  1. button#apply-coupon "Apply"/checkout · 0 rage, 10 dead10
  2. button#pay "Pay $##"/checkout · 6 rage, 0 dead6
  3. div.plan-card "Enterprise"/pricing · 0 rage, 5 dead5

Form errors by field

  1. input[name="zip"]patternMismatch10
  2. input[name="email"]typeMismatch4

form#checkout: 20 submitted, 10 abandoned, most often on input[name="zip"]

Failing and slow requests

  1. GET /api/shipping/rates0 errors, 10 slow · worst 4.2s10
  2. POST /api/payments3 errors, 0 slow · worst 0.8s3

Sample data rendered from summarize() output, like the analytics section on the home page.

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"
}
  • frustration is your list of broken or misleading UI. A high deadClicks count on something that isn’t a button usually means it looks like one.
  • formErrors tells you which validation rules users trip over. A field that often fails with patternMismatch usually needs a clearer hint or a more forgiving pattern.
  • forms[].topAbandonField is where people give up. Look at that field first.
  • requests points at the endpoints behind “the app is slow” and “it didn’t save”.
  • dismissRate tells you whether users like the mascot. If it’s high, make it smaller, quieter, or interactive: false.

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.