# Product analytics

See how people use your app: which screens they visit, which features they use, where they drop out and whether they come back. It reads the same sessions as Performance, so a slow checkout and an abandoned one are in the same place.

## Name what matters

Screens, taps, renders and requests are recorded without any code. Three optional calls add what only you know: what an action is called, and who did it.

```ts
import { defineAction, identify, tag } from '@squiggle.sh/react-native';

// Name a user action. Each call is timed, and recorded as ok or error.
const addToCart = defineAction('add_to_cart', () => cart.add(product));
addToCart();

// Say who is using the app. Traits are merged into the session.
identify('user-123', { plan: 'pro', appVersion: '1.4.2' });

// Add context to the interaction in progress.
tag({ experiment: 'checkout-v2', variant: 'A' });
```

`defineAction` is the one to start with. Call the function it returns instead of the original. One name then works as a funnel step, a feature-usage count, a timing, and a flow assertion such as `assert: { action: { add_to_cart: { eq: 1 } } }`, which proves a journey did the thing, not only that a tap was sent. See the [SDK reference](https://squiggle.sh/docs/reference/sdk) for each call.

> **Your sessions, not your users' phones**
>
> These numbers come from the sessions recorded in your project: your own use, your flow runs and CI. Squiggle collects nothing from the people who install your app, because a store build doesn’t record. Traits and tags larger than 8 KB are dropped with a warning, not cut short.

## Engagement

The Product screen’s first tab:

| Section | What it shows |
| --- | --- |
| Screens | Visits, average time and exit rate per screen, and the paths people take most. |
| Feature usage | How often each defineAction ran, and how often it failed. Switch to adoption to see the share of sessions that used it instead. |
| Feature affinity | Actions people tend to use together, strongest first. |
| Friction | Rage taps, dead zones and confusion across sessions, by screen, with the worst spots first. |
| State | Pick a store value to see what it held on each screen, with how many sessions. Needs state samples from the app. |

Usage and adoption rank differently on purpose: a feature used ten times in one session is not the same as a feature used once in every session. Friction is explained in [Friction and the journey](https://squiggle.sh/docs/analytics/friction).

## From the terminal

```shell
squiggle product overview            # the headline numbers
squiggle product screens             # visits, time and exit rate per screen
squiggle product usage --adoption    # share of sessions that used each action
squiggle product affinity            # actions people use together
```

Each report covers every stored session of one platform. Pass `--last 20` for the twenty most recent, which is the usual way to ask whether something is getting better or worse, or `--session <id>` for one. Your agent has a matching `product_` tool for each report.

If your app sends state samples, add `--where "zustand:useCart.items#len >= 3"` to any report to split it in two: sessions that reached that state, and sessions that sent samples but never did. See [Compare groups](https://squiggle.sh/docs/product/funnels#compare-groups).

If your app builds for iOS and macOS, the two are counted separately: a report reads the platform of the app you ran most recently. Pass `--platform ios` or `--platform macos` to choose, or `--platform all` for both together. In Studio, the **iOS**, **macOS** and **All** switch at the top of the screen does the same. It appears when apps for both platforms are connected, or, with neither connected, when the project has sessions from both. Picking a platform also switches Live to that app; **All** leaves Live where it is.

## See what your app held

If your app sends [state samples](https://squiggle.sh/docs/reference/sdk), list the state paths your sessions have, most widely sampled first:

```shell
squiggle product state
```

Pass a path to see the values it held on each screen, each with its share of that screen’s samples, its number of sessions and up to three session ids to open. Add `--screen checkout` to keep one screen, by route name:

```shell
squiggle product state "zustand:useCart.items#len" --screen checkout
```

An empty answer means no state samples were recorded. See [Troubleshooting](https://squiggle.sh/docs/troubleshooting).

### State paths

A path is where a value lives, then `#` and what is measured about it. A sample holds measurements, never your values, unless you allowed a field’s values with `setStateAnalyticsAllow`.

| Path | What it holds |
| --- | --- |
| `nav#route` | The route the user is on, as the route names from the root down. |
| `nav#depth` | How many screens the current stack holds. |
| `nav#tab` | Which tab is selected, counting from 0. |
| `zustand:useCart.items` | A field of the Zustand store declared as useCart. A Legend State store starts with legend:. |
| `store:zustand-3` | A store the Babel plugin could not name, by the order it was created in. This moves when you add a store. |
| `#kind` | What the value is: null, boolean, number, string, array or object. |
| `#bool` | A boolean: 1 for true, 0 for false. |
| `#len` | An array: how many items it holds. |
| `#keys` | An object: how many keys it has. |
| `#sign` | A number: 1, 0 or -1. |
| `#mag` | A number: its order of magnitude, as a power of two (3 means 8 up to 15). |
| `#chars` | A string: its length, rounded down to a power of two. 0 when empty. |
| `#value` | A string you allowed with setStateAnalyticsAllow: the value itself. |
| `mmkv:settings#keys` | How many keys an MMKV instance holds. Instances whose id looks like data, such as user-48213, are added together as mmkv:other#keys. |
| `zustand:useCart#skipped` | How many values the store held that a sample cannot describe, such as functions and class instances. |
| `zustand:useCart#capped` | 1 when the store has more than 64 paths and some were left out. |

Squiggle reads two levels into each store and sends at most 64 paths per store, the same ones every time. A store name only holds steady from release to release when the Babel plugin names it; see [Store names](https://squiggle.sh/docs/reference/sdk#store-names).

## Find the states your tests never reach

If your app sends [state samples](https://squiggle.sh/docs/reference/sdk), Squiggle compares what real sessions held on each screen with what your library flows and save slots reach:

```shell
squiggle product state --coverage
```

Each row is a state, such as an empty cart on checkout, with the share of that screen’s real sessions that reached it. A session counts as real when no flow ran it. Each row also proposes a save slot that reaches the state: the nearest slot you already saved on that screen, with one field changed. Nothing is saved until you ask:

```shell
squiggle product state --coverage --save-gap 1 --as checkout-empty
```

Then `squiggle load checkout-empty` puts the app in that state for a flow to start from. A row has no proposal when you have no slot on that screen, or when the state is a count or a length range rather than one value. The row says which.

The [report card](https://squiggle.sh/docs/run/card) shows the same gaps: up to three per screen your change touches, each with the `--save-gap` command for it.

## See what changed between releases

With state samples and a `release` (or `appVersion`) trait set through `identify()`, Squiggle compares what sessions held on each screen in your latest release with the one before:

```shell
squiggle product state --drift
```

A state is listed when its share of a screen’s sessions moved by 20 points or more, such as an empty cart on checkout going from 25% to 75% of sessions. A screen needs at least three sessions in each release to be compared. Pass `--from` and `--to` to pick the releases.

`squiggle product state --perf` reads performance findings, such as render storms, jank and long tasks, against the state each happened in. It lists the store values that come up more often in findings than in the screen’s samples overall.

## More

- [Funnels](https://squiggle.sh/docs/product/funnels): Where people drop out of a journey, and how long it takes them.
- [Retention and audience](https://squiggle.sh/docs/product/audience): Who comes back, releases, users and sessions.
