pristine
All examples

Operate · Tidepool

Analytics dashboard

A product analytics overview went from a stock shadcn dashboard of eight identical KPI cards and a four-series area chart to a release readout that answers one question in words: did last week's onboarding checklist move activation, and is the number final yet?

Persona
Sam Okafor, Head of growth
Their goal
Check on Monday whether last week's onboarding checklist moved activation, and see where the funnel still leaks, without exporting to a spreadsheet.
Lenses applied
clarify, harden, distill, animate, typeset, adapt
/pristine design "redesign the overview dashboard"

Full pages. Scroll either one; they move together.

Before4.5/10

Analytics dashboard before Pristine

After9.5/10

Analytics dashboard after Pristine

Verdict

4.5/10 Level 6

Clean, tidy and interchangeable with every shadcn starter. Eight identical cards each say "+x% from last month", Signups and New Signups repeat, MRR shows up again as Revenue, the chart stacks four unrelated series with no y-axis, and nothing tells Sam whether the checklist worked.

9.5/10 Level 10

The page opens on a sentence, not a card grid: activation jumped to 41.8% the week the checklist shipped, 7.6 pts above an 11-week range drawn behind the line. The dashed tail and "Still counting" note say the cohort isn't final until Oct 27, the funnel shows which two steps moved, and stale Stripe data, an expired HubSpot token and a live event stream each say what they are and what to do.

Scores

Every Dimension, before and after.

Dimension scores out of 10
DimensionBeforeAfterChange
Persona fit310
Hierarchy & clarity310
Layout & spacing69
Typography69
Color & theming59
Consistency69
Responsiveness59
Motion & interaction49
Accessibility49
Copy410
Robustness & performance310

The critique

What the red pen found.

Vibe-code tells

  • Stock shadcn "Dashboard" starter layout
  • Tabs for Overview / Analytics / Reports / Notifications
  • Identical KPI cards with duplicate metrics
  • "+x% from last month" under every number
  • Default --chart-1…5 palette assigned by index
  • Four unrelated series on one area chart
  • Every widget in an identical rounded-xl card
  • Unlabeled icon buttons and placeholder-only search
  • outline: none on custom nav links
  • Made-up vanity numbers
  1. P0 Lens: clarify

    The page doesn't answer Sam's question

    Activation is one of eight identical cards, shown as "+22.2% from last month" with no base. The chart has week labels but no y-axis and no marker for the checklist launch, so you can't connect the change to the number.

  2. P0 Lens: distill

    Duplicate and invented metrics

    Signups and New Signups show different numbers. MRR and Revenue both appear. "Total Users 24,812" and "Growth +24.5%" have no definition or comparison period.

  3. P1 Lens: colorize

    Chart palette encodes nothing

    The default `--chart-1…5` orange, teal, navy and yellow are handed out by index: four unrelated series (Signups, Activated, Visitors, MRR on a different scale) share one area chart, and every funnel step gets its own color. Color is assignment order, not a signal for up, down or selected.

  4. P1 Lens: typeset

    Every number has the same weight

    Every KPI is `text-2xl font-bold` with a 12px `text-muted-foreground` delta line, so a vanity total and the metric Sam cares about look identical. Figures are proportional, so columns of numbers jitter.

  5. P1 Lens: harden

    Focus is removed and there are no states

    The custom sidebar links set `outline: none` with only a hover background. The search box has a placeholder and no label, the trash button is an unlabeled icon, and there is no loading, empty or partial-data state, so a filter with no matches just shows a blank table.

  6. P2 Lens: quieter

    Identical cards, 32px row actions

    Twelve `rounded-xl border` cards with the same padding and shadow give no grouping or priority, and each event row carries three 32px buttons (View, Edit, delete) below the 44px target size.

The changes

Before, after, and why.

The report Pristine hands back after a run, with the real values from the two pages above.

LensBeforeAfterWhy
clarifyActivation "+22.2% from last month" as one of 8 cardsRelease readout: "Activation jumped the week the checklist shipped, clear of its 11-week range." 41.8% vs. 33.1–34.2%, +7.6 ptsSam's Monday question gets a sentence with its proof under it, before any chart.
hardenLast week plotted like every other weekDashed provisional tail, hollow "41.8% so far" dot, "Still counting… Final read Mon, Oct 27" with a running markSignups from the last 7 days can still activate. The chart says the number is partial instead of letting it be over-read.
clarifyFour stacked area series in --chart-1…4, week labels, no y-axisShaded band for the previous 11 weeks' range, "Checklist shipped" marker, metric switch, View as tableThe band is the baseline, so the jump reads against normal variation rather than one prior week.
distillFunnel bars in a different chart color each"Where it moved": step conversion bars with an 11-week-average tick, +8.2 / +4.7 pts deltas, checklist tags, 291-account leak calloutShows which steps the checklist changed and where the next drop still is.
hardenIntegrations shown as green "Connected" badgesData sources with live / retrying / needs-you / synced marks; MRR KPI says "Stripe sync retrying · as of 06:10"; Reconnect HubSpot runs → doneA number from stale data says so next to the number, and the fix is one click away.
animatetransition-colors on buttons only; numbers swap with no change shownclip-path draw-in 520ms, KPI values roll on range change, new events slide in every 6s; Pause queues them behind "Show 1 new event"Motion shows what changed and where. Pausing keeps rows from moving while Sam reads.
distill8 identical KPI cards incl. Signups + New Signups, MRR + Revenue4 KPIs in one <dl>; Activation definition after a 500ms hover; W4 heatmap cells outlined as the source of the week-4 KPIEach number appears once and you can see where it comes from.
typeset.kpi .value { font-size: 24px; font-weight: 700 } ×8; 12px muted delta lines13px labels in --text-2, tabular-nums, JetBrains Mono for event names and propertiesLabels meet contrast, numbers line up, and event payloads read as code.
harden.sidebar a { outline: none }; unlabeled search and trash icon; blank table on no match:focus-visible ring; "No events match" with Clear filter; dashed not-yet cohort cellsKeyboard users can reach every control, and missing data looks deliberately missing.
adaptmin-height: 100vh; 7-column events table scrolls sideways at 390px100dvh shell with a sticky sidebar; tables scroll inside .table-scroll; no page scroll at 320pxSam checks this on a phone too.