# Findings

Detectors read each session and report what they find: what went wrong in plain words, the source line it points at, and the recorded data it came from. Pick one to see a finding as Squiggle reports it.

## What a finding tells you

- **A title** that says what happened, with the numbers: “PriceTicker rendered 48× in one interaction”.
- **Where**: the file and line of the component, or the request or query it is about.
- **Evidence**: the renders, requests or metrics it was worked out from, so you can check it rather than take its word.
- **A severity**, `warn` or `error`.

Running the detectors again updates a finding instead of adding a copy.

## The detectors

Fifteen detectors look at performance. The limits below are the defaults.

| Detector | Reports |
| --- | --- |
| `render-storm` | A component rendering more than 15 times in one interaction, or 60 times in one second. |
| `memo-miss` | Five or more re-renders where every changed prop was a new object, array or function with the same contents. Fix with useMemo or useCallback. |
| `context-blast` | Twenty or more components re-rendering from one context change within 200 ms. |
| `long-task` | A JS task over 100 ms, with what the CPU was doing at the time. When the stall was profiled, the title names the function: JS thread blocked 484ms in useCartTotals (profiled). |
| `jank-attribution` | Dropped frames or a frame-rate dip, with the work around it ranked as suspects. |
| `js-driven-motion` | An animation driven from JS: one component committing 20 or more times a second for at least a second while the main thread runs at twice its idle rate. It freezes whenever JS is busy, so move it to the native driver or the UI thread. |
| `duplicate-request` | The same request twice within three seconds. |
| `waterfall-request` | Three or more requests in a row, each waiting for the last, over 500 ms in total. |
| `oversized-response` | A response over 2 MB. |
| `slow-ttfb` | A request waiting over a second for its first byte. |
| `slow-query` | A SQLite statement over 20 ms at the 95th percentile (100 ms for a transaction), or one run over five times that. |
| `heap-growth` | Memory that grows on every visit to the same screen, over three or more visits. |
| `startup-regression` | A startup phase more than 25% and 50 ms slower than in the baseline. |
| `nav-loop` | The same two screens or requests repeating four or more times, more than your other sessions ever do. Needs 20 stored sessions. |
| `heavy-render-window` | Half a second with more rendering than your app absorbs: the level is learned from your other sessions, where at least 80% of windows above it dropped frames. Needs 20 sessions with frame samples. |

Two levels come from your budgets when `.squiggle/config.jsonc` declares a `"*"` entry. `waterfall-request` then reports a chain longer than its `waterfallDepth`, and `jank-attribution` a moment that dropped more frames than its `droppedFramesPerInteraction`. With the defaults `squiggle init` writes, that is a chain of three requests and three dropped frames. See [Set a budget per screen](https://squiggle.sh/docs/performance/baselines#set-a-budget-per-screen).

Rage taps, dead zones and confusion are found the same way; see [Friction and the journey](https://squiggle.sh/docs/analytics/friction).

## In Studio

Findings are on the Performance screen’s **Insights** section. While your app is connected, Studio runs the detectors every few seconds, counts the findings on its card, and sends one notification for each new problem.

Click a finding’s evidence to highlight the request in the network list, select the component in the [Inspector](https://squiggle.sh/docs/inspector), or open its line in your editor.

## In the terminal

```shell
squiggle perf insights
```

Output:

```text
insights 4 · session mv6rxxns-1
  ▌ long-task JS thread blocked 484ms — js_long_task
  ▌ jank-attribution UI fps 38.7 — top suspect: NavigationContent
```

`squiggle perf watch` follows the store and prints each new finding once, as the detectors produce it.

## Have your agent fix one

The `propose_fix` tool turns a finding into a plan for your agent: the component’s code, what the detector measured, and a fix worded only as firmly as the evidence allows. It never edits a file. Your agent makes the change as a normal edit you can review, then runs the flow again to check the finding is gone.

It refuses a finding that names no component (a long task or a duplicate request), a component inside `node_modules`, and a file it can’t find on your Mac.

## See which detectors matter for your app

`squiggle perf detector-stats` compares flow runs that failed an assertion with runs that passed, and shows how often each detector fired on each:

```shell
squiggle perf detector-stats
```

Output:

```text
detector           fires on failed  fires on clean  lift
long-task          33 (37.5%)       335 (11.2%)      3.34×
jank-attribution   31 (35.2%)       496 (16.6%)      2.12×
render-storm       11 (12.5%)       385 (12.9%)      0.97×
```

> **Read the lift column**
>
> Lift is how much more often a detector fires on failed runs. Near 1× it fires about as often on clean runs, so failing a build on it would only add noise. A detector with a high lift is a good one to gate on; see [Baselines](https://squiggle.sh/docs/performance/baselines).
