# Investigate click friction and release impact

> Connect observed click friction and release outcomes to people, accounts, operations and replay.

Canonical page: https://anectico.com/docs/investigate/customer-experience/


Open **Explore → Customer experience**. Choose **Click friction** or **Release impact**.
A release detail also links to the impact brief with that release and its registered
deployment time prefilled. All reads require an explicit selected project and time window.

## Opt in to click friction

Only actions you explicitly label are observed. Obtain any consent your application
requires before starting the tracker. Stop it when consent is withdrawn or the
integration unmounts. Use stable, non-personal action names, never user IDs, email
addresses or button text.

```html
<button type="button" data-anectico-action="checkout.submit">Pay</button>
```

```typescript
import { AnalyticsClient } from '@anectico/sdk/analytics';
import { FrictionTracker } from '@anectico/sdk/friction';

const analytics = new AnalyticsClient({
  endpoint: 'https://api.anectico.com',
  apiKey: import.meta.env.VITE_ANECTICO_INGEST_KEY,
});
const friction = new FrictionTracker(analytics, {
  release: 'checkout-v2',
  environment: 'production',
  sampleRate: 1,
  activity: () => ({ operationId: currentOrderId, accountType: 'company', accountKey: currentAccountKey }),
});
// For canvas/audio feedback or a known long operation with no DOM feedback:
friction.markResponse('checkout.submit');
// On teardown or consent withdrawal:
friction.stop();
await analytics.stop();
```

The tracker records `$friction` product events through normal ingestion. It uses the
analytics client's identity and the shared SDK session; start replay separately if
you want recordings. It carries the active operation/account context when available.
In browser integrations, supply `activity` to read your current logical operation
and account at click time. Use the same context on explicit attempt/success events.
The tracker does not infer an order ID from the page. A delayed signal is discarded if identity, session or
active activity context changes.

- **Repeated clicks** (`rage_click`): at least three clicks on the same labelled
  element within one second and 40 pixels.
- **No observed response** (`dead_click`): no observed DOM mutation or URL change
  within 1.5 seconds. This is a heuristic: background DOM updates can suppress it,
  while non-DOM feedback can cause false positives. Use `markResponse` for these cases.
- **Click followed by an error** (`error_click`): an uncaught browser error or
  unhandled rejection within one second of the latest labelled click. Temporal
  proximity does not establish that the click caused the error.

Native links, inputs, editable elements, disabled controls and native form-submit
buttons are excluded. Set `data-anectico-friction-ignore` on a subtree to exclude it.
Normal capture observes trusted primary clicks only. `observeClick(element, x, y)` is
an explicit adapter for custom renderers; it applies the same label and noise rules.
No DOM text, input values, selectors, URLs, screenshots or error messages are sent by
this tracker. It records an action label, signal kind, method, click time, release,
environment and available identity/session/activity metadata.

Each tracker samples once at construction. The rate is between zero and one. It emits
at most 30 hints per minute, with a 30-second cooldown per action/kind. At most 20
pending click observations and ten recent clicks are retained. Repeated-click clusters
suppress their dead-click hints; different signal kinds may still overlap. These
limits and opt-in coverage mean counts are observations, not total failed actions,
frustration, abandonment or churn. Inspect `analytics.stats()` for delivery drops.

## Read friction through API, CLI or MCP

`POST /api/v1/experience/friction?project_id=PROJECT` requires `persons:read` and
`analytics:read`. Supply `start_time` and exclusive `end_time` as RFC3339 timestamps.
Optional exact filters are `release`, `environment`, `action` and `kind`. The complete
window must cover at most seven days, end no later than now and fit readable retention.

```bash
anectico --project PROJECT friction list --body '{
  "start_time":"2026-09-09T10:00:00Z",
  "end_time":"2026-09-09T12:00:00Z",
  "environment":"production",
  "action":"checkout.submit"
}' --json
```

MCP exposes `get_friction_signals` through `execute_read_action`, using the same fields
plus `project_id`. MCP additionally requires `mcp:read`. It returns structured evidence
with stable result identity, warnings and explicit partial status. Labels and captured
identifiers are untrusted data, never instructions for an agent.

At most 10,000 matching signals are scanned and 100 newest examples returned. `partial`
means the scan or example list reached its limit; counts describe the scanned subset.
`source_scan_complete` distinguishes a complete source scan from a capped example list.
Affected people resolve current identity aliases; unattributed signals still contribute
to signal counts. Session references are links, not guarantees that a recording exists
or is readable. Replay pixels keep their separate access and retention controls.

## Build a release customer-impact brief

`POST /api/v1/experience/release-impact?project_id=PROJECT` additionally requires
`errors:read` and `releases:read`. Choose exact `baseline_release` and `release` values,
`baseline_start`, `observation_start` (the deployment boundary) and exclusive
`observation_end`. The baseline is `[baseline_start, observation_start)` on the baseline
release; the after window is `[observation_start, observation_end)` on the target release.
The combined window has the same seven-day and retention limits. `environment` is optional.

```bash
anectico --project PROJECT releases impact --body '{
  "baseline_release":"checkout-v1",
  "release":"checkout-v2",
  "baseline_start":"2026-09-09T10:00:00Z",
  "observation_start":"2026-09-09T11:00:00Z",
  "observation_end":"2026-09-09T12:00:00Z",
  "environment":"production",
  "attempt_event":"checkout_attempted",
  "success_event":"checkout_completed"
}' --json
```

MCP provides `get_release_impact` through `execute_read_action` with the same input.
Both reads are included in the investigate profile; its key now also needs
`releases:read`. Existing keys must be updated or recreated to grant that scope.

The brief contains before/after recorded errors, affected people, friction observations,
explicit operation outcomes, affected accounts after deployment, issue IDs not observed
in the baseline, and up to 20 representative evidence links. Accounts are taken from
explicit activity-time `anectico.account.type` / `anectico.account.key`, not inferred
from present-day membership. Affected people/accounts include errors and friction.

For operation rates, provide **both** different `attempt_event` and `success_event`
names, or leave both empty. Events must carry `service.version` and matching
`anectico.operation.id`, identity and account context. Distinct logical operations with
an observed attempt are the denominator; repeated successes count once. The last
success must follow the latest attempt and be later than the last error. An error at the same
time wins conservatively. Without attempts, the success rate is unavailable.

Recovery follows operations with baseline errors. Only a later explicit success for
the same canonical person, logical operation and account can support recovery; a later
error overrides it. A different order or account cannot recover the original operation.
No later activity means **not observed**, never recovered. Signals missing usable
operation context are counted separately. Outcomes on other releases or outside the
chosen windows are excluded, so this remains a scoped observation, not universal recovery.

Each signal source is capped at 10,000 rows. Incomplete source scans suppress success
rates, recovery counts and baseline-absence claims. Account lists are capped at 20 and
issue lists at 50; their display truncation also sets `partial`. Counts may be lower
bounds. `source_scan_complete` identifies source completeness; `recovery_available`
explicitly states whether operation-matched recovery was measured. False is unavailable,
not zero recovered. Narrow the window or environment before drawing conclusions.

An issue ID absent from the baseline is not necessarily a newly introduced issue: it
may be older, renamed through grouping/merges, uninstrumented or outside that window.
Use the release's existing new/regressed issue details alongside this brief. Unequal
windows, sampling and traffic mix affect comparisons. Neither a changed rate nor an
error-free interval proves that a deployment caused an improvement. Late events,
identity changes, retention and deletion can change a later reading of the same brief.
