# UI smoothness capture

`app.performance_capture` attributes CDP trace samples to the exact Recipe Protocol v1 nodes that run inside an explicit window. Mobile and Extension use the same action contract.

```json
{
  "action": "app.performance_capture",
  "phase": "start",
  "id": "market-scroll",
  "max_duration_ms": 60000,
  "intent": "Start UI smoothness capture",
  "next": "scroll"
}
```

End the same ID with `phase: "end"`. The action writes bounded JSON and a self-contained HTML report. `app.performance_assert` can require a status, minimum frame count, and specific node intervals.

## Sources and status

- Mobile uses React Native's `Tracing.start`, `Tracing.dataCollected`, and `Tracing.end` implementation. iOS frame timings come from its built-in `CADisplayLink` observer. Android frame timings come from `Window.FrameMetrics`. The app build must report `unstable_frameRecordingEnabled: true`; otherwise native UI coverage is partial or unavailable.
- Extension uses Chromium's implementation of the same CDP Tracing domain. Frame and JavaScript data count only after the harness proves they belong to the MetaMask renderer. Unresolved browser-wide events produce partial or unavailable coverage.
- The harness records a CDP clock marker and maps trace timestamps onto recipe-node timestamps. It waits for `Tracing.tracingComplete` before writing evidence.
- The JSON report includes bounded trace evidence counts for `BeginFrame`, `DrawFrame`, `RunTask`, and `ProfileChunk`, plus data-loss and retention-overflow flags.
- UI frames and JavaScript work remain separate sources. `RunTask` events produce JavaScript task summaries. Sampling `ProfileChunk` events prove profiler data exists but are not converted to task timing or FPS. JavaScript work is never labelled as UI or JS FPS.
- `complete`, `partial`, and `unavailable` describe source coverage. Missing data is never converted to zero.

`app.performance_assert.minimum_frame_count` applies only to native UI or Chromium renderer frames. JavaScript task count cannot satisfy it.

Capture is explicit only. Use the action for comparisons on the same runtime and build. Automatic capture remains disabled until its overhead passes a matched benchmark.

## Sentry promotion boundary

This action does not send data to Sentry. A future product integration may promote only bounded aggregates after release-build overhead is independently accepted: source status, stable node/action name, frame count, p50/p95/p99, jank percentage, and longest frame. Do not send raw frame samples, recipe parameters, wallet/account identity, URLs, or artifact contents.
