Heatmap

For AI agents: this page is Markdown at https://squiggle.sh/docs/analytics.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.

The heatmap shows where people touch each screen of your app, and the friction detectors flag the spots where those touches did nothing. There are no events to add: the SDK sees every touch.

Tap the phone. Try the coupon button, or the picture, a few times fast.
9:41

No touches yet.

  1. No findings. Checkout works, so tapping it never counts.

Turn it on

In Studio’s device panel, open the Overlays menu and pick Heatmap. Every touch is drawn on the simulator as a blob, coloured by how many other touches are near it: a lone tap is green, a crowded spot is red. New touches never change the colour of the ones already there.

OverlaysStudio tour · 3 steps

What it shows

  • Every touch. Not only taps on controls, but taps on pictures, text and empty space, which is where people try things you didn’t build.
  • One screen at a time. The heatmap shows the touches made on the screen your app is on, and follows it when the screen changes. The Overlays menu’s screen filter picks another screen, or All screens.
  • Touches that stay with the content. On a list, a blob moves with the row that was touched as you scroll, and leaves the view when the row does.
  • Swipes as arrows. From where the finger went down to where it lifted, thicker for a faster swipe, with a dashed line to where the list stopped scrolling.

See an earlier session’s touches

Every session keeps its touches, so you can draw an earlier one over the device:

  • A past run. In Analytics, pick the run and press Show heatmap on device.
  • A recording, as you scrub it. In the Replay player, turn on Heatmap. The heatmap shows the touches up to the playhead and follows it as you scrub or play. All so far switches to the last 5 seconds before the playhead. Turn on Drive device too, and the app goes to each screen as the recording reaches it.

A label at the top of the screen names the session on view. Press it, or pick any overlay, to go back to the live session. Your agent reads the same touches with the product_heatmap tool.

If your project builds for iOS and macOS, the Analytics screen’s iOS, macOS and All switch picks whose runs you see, as on Product analytics.

What a drag meant

A drag on a react-native-gesture-handler pan is also read as one of four intents: a swipe to dismiss, a pull to refresh, a drag to reorder, or a scrub. Each is a guess with its evidence attached: how the finger moved, and what the app did next.

  • high: the motion fits and the app did the matching thing. A dismiss popped a screen, a refresh sent a request, a reorder or a scrub changed what was on screen.
  • possible: the motion of a dismiss or a reorder fits in every respect, but the app did nothing that confirms it. A pull or a scrub moves like an ordinary drag, so those two are only ever labelled when the app refreshed or the value followed the finger.

A drag that fits no intent is left unlabelled. A pull on a list that is not scrolled to its top is never read as a refresh. In Studio, the Analytics screen lists a run’s intents under Gestures; click one to see the motion and the outcome it rests on. Your agent reads the intents per screen with product_heatmap and include: ["intents"].

Which element a touch landed on

While your app is connected, the Analytics screen’s Touched elements list counts the touches on each control, per screen: the heatmap, one element at a time. A touch is named by the control it pressed. When it pressed nothing your app renders, such as a native tab bar, Studio names what the accessibility tree of a simulator it launched had under the finger, if it read that tree within the second before the touch, and the row says so. A touch neither can name is counted as not attributed, never given a nearby element’s name. A past run keeps no element names, so the list is for the live run only.

What your app needs

Nothing. <Squiggle> records every touch by default. To stop it, set touchCapture={false}; see the SDK reference.

More