Skills DirectorySearch all skills ↗

AI agent skill · by @dust-tt

Dust Posthog Tracking

Track user interactions as PostHog events while implementing features in `front`, so their usage can be analysed later. Use when adding or changing a user-facing feature, flow, or surface, or when the user mentions PostHog, event tracking, or `trackEvent`.

Archived 2026-10-23 · Source updated 2026-10-11 · License: MIT. Check the original repository for the latest version.

SKILL.md instructions

# PostHog event tracking

A user-facing feature ships with the PostHog events needed to analyse how it is used. Add them in
the same change as the feature, not as a follow-up.

## What to track

Track the moments someone will ask about later:

- a surface is shown (`view`, `open`)
- a user acts on it (`click`, `select`, `submit`, `dismiss`, `close`)
- the outcome of that action (`create`, `delete`, `connect`), and outcomes only the server knows
  (a limit hit, a request refused, a model downgraded)

Do not track UI plumbing (hover, scroll, re-render). Check what the feature already sends before
adding: `git grep -n "trackEvent\|withTracking" front/components/<feature>`.

## Client events

Use `trackEvent` or `withTracking` from `@app/lib/tracking`, never `posthog` directly. The event
name is built as `${area}:${object}:${action}`:

- `area`: from `TRACKING_AREAS`, the product surface. Add a key only for a real new surface.
- `object`: `snake_case`, stable, names the thing acted on (`model_picker`, `suggestion_card`).
  Put variants in `extra`, not in the object.
- `action`: from `TRACKING_ACTIONS`, defaults to `click`.

```tsx
<Button onClick={withTracking(TRACKING_AREAS.BUILDER, "create_agent", handleCreate)} />

trackEvent({
  area: TRACKING_AREAS.TOOLS,
  object: "tool_select",
  action: TRACKING_ACTIONS.SELECT,
  extra: { tool_id: serverView.sId },
});
```

A feature with several events gets one `<feature>Tracking.ts` module next to its components, with
one typed helper per event. Components call the helpers. Models:
`front/components/model_picker/modelPickerTracking.ts`,
`front/components/markdown/suggestion/suggestionTracking.ts`.

Views fire from an effect, once per mount. For a list whose items change state, remember the ids
already seen in a ref so each item counts once (model: `useTrackSuggestionCardViews`):

```ts
const seenIds = useRef(new Set<string>());
useEffect(() => {
  for (const item of items) {
    if (!seenIds.current.has(item.id)) {
      seenIds.current.add(item.id);
      trackItemView(item.id);
    }
  }
}, [items]);
```

## Server events

Use `PostHogServerSideTracking.trackEvent` from `@app/lib/api/posthog` for outcomes the browser
cannot see. Name the outcome in flat `snake_case` (`premium_model_downgraded`), always pass
`distinctId: user.sId` and `workspaceId`, and never await it: tracking must not slow down or fail
the request.

```ts
PostHogServerSideTracking.trackEvent({
  distinctId: user.sId,
  event: "trigger_blocked",
  workspaceId: workspace.sId,
  extra: { trigger_id: trigger.sId },
});
```

## Properties

`extra` is a flat `Record<string, string | number | boolean>`, sent both spread for PostHog and as
one object for Snowflake.

- Carry the id that joins the event to the data it will be analysed with (`batch_id`,
  `agent_id`), or every id for an action on several items (`batch_ids`). If that id is not
  available where the event fires, skip the event.
- Ids are `sId`s, never `ModelId`s. Keys are `snake_case`, units are in the name (`duration_ms`).
- No PII and no user content: no emails, names, messages, prompts, file names, or typed text.
- Do not repeat what is added for every event (URL, referrer, UTM, user and workspace).
- Prefer a small string enum over a boolean when a third value is plausible
  (`surface: "pile" | "single"` rather than `in_pile`).

## Contracts and tests

When analysis relies on a guarantee about events (every event carries a join key, a view is
counted once), state it as a `@cc` contract on the tracking helper (see the `code-contracts`
skill).

Test the helpers by mocking only the emit and asserting the exact event:

```ts
vi.mock("@app/lib/tracking", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@app/lib/tracking")>();
  return { ...actual, trackEvent: vi.fn() };
});
```

For a view hook, `renderHook` it, `rerender` with the same and with changed items, and assert the
call count. Model: `front/components/markdown/suggestion/suggestionTracking.test.ts`. For server
events, mock `PostHogServerSideTracking.trackEvent` the same way.

Related skills