Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .claude/skills/propose-analytics-event/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
name: propose-analytics-event
description: Use when someone wants to add, change, or remove an analytics or Mixpanel event or property, or asks how to track, measure, or count something users do in the app. Written for non-engineers (marketing, growth, product) who describe what they want in plain English.
user-invocable: true
---

# Propose an analytics event

The person asking is usually not an engineer. They know what they want to measure, not how
the app is built. Your job is to turn that into a catalogue change an engineer can review and
wire up, in a **draft** pull request, without making them touch git, Kotlin, or Gradle.

The catalogue is `libs/analytics-events/events.toml`. Its header explains every field: read it
first. Both apps send events from this one file (iOS through the shared KMP package), so a
proposal here covers Android and iOS.

## Talk like this

Say screens, taps, and numbers. Never make the person deal with files, builders, Gradle, the
"share sheet", SharedCore, or KMP. If you must name an event, give its Mixpanel name.

| Instead of | Say |
|---|---|
| "add an `[[event]]` to events.toml" | "add a new event to the list both apps send" |
| "wire the call site" | "an engineer connects it to the button" |
| "share sheet" | "the phone's Share menu" |
| "property" | "a detail recorded with it, like which method they picked" |
| "Publish SharedCore" | "it reaches iPhone in a later release" |

## 1. Set up quietly

Run `git fetch origin` and `git switch -c feat/analytics-<short-slug> origin/code/cash`. The base
branch is `code/cash`. There is no `main`. Don't ask the person anything about this step.

## 2. Ask before you build

Ask one question at a time, and offer concrete choices taken from the real app (read the
screen's code to find them). Start each ask with question 1, then sort it with step 3. Only
asks that end up as new or changed events need questions 2 to 4. Don't edit anything until
those asks have all four answers:

1. **The question behind it.** "What will you decide or report with this number?" Their answer
tells you whether an event is the right tool at all.
2. **The exact moment.** What they see and tap, and whether it counts **attempts** ("tapped
Share") or **completions** ("the card was sent"). Words like "share", "engaged", "uses" and
"converts" have several meanings. List the ones the app supports and let them pick. Check
for other paths to the same outcome (a second share button, a share-to-chat screen).
3. **Breakdowns.** What they'd filter by in Mixpanel: method, source, success/failure.
4. **Both apps?** The default is yes.

## 3. Check what already exists

Read `events.toml` and `libs/analytics-events/EVENTS.md`. To see whether an event is actually
sent, grep for its builder outside tests:
`git grep -n 'ProfileEvents.cardShown(' -- apps ':!*/test/*'`. Then sort each ask into one row:

| What you find | What to do |
|---|---|
| An event already covers it and is sent | Tell them its Mixpanel name and what it counts. No PR. |
| It's in the catalogue but nothing sends it | Don't add a duplicate. Put it under "Needs wiring" in the PR. |
| An existing event is missing one detail | Add a property, or a value to an existing list. |
| It can be computed from existing events: thresholds ("3+ messages in a week"), funnels, retention, totals | Explain how to build it as a Mixpanel report. No app change. |
| The app can't see it: installs from a link, ad or campaign attribution, anything after the phone's Share menu, anything outside the app | Say so plainly, list it under "Not included" in the PR, and leave the design to engineering. Never relabel an existing event to stand in for it. |
| Genuinely new | Draft it (step 4). |

Before you recommend a Mixpanel report on an existing event, read that event's properties in
`EVENTS.md`. Does it also fire on failure (then the report filters on `State` or `Error`)? Does it
say which chat, group, or currency (most events don't, so "per group" is out of reach)? Tell the
person each limit.

"Needs wiring" covers only catalogued events that would answer one of their asks. Unrelated
unsent events don't go in the PR.

## 4. Draft, then confirm

Follow the existing naming: `Domain: Past-Tense Action` (`Group: Card Shown`), Title Case
property names, and reuse an existing list wherever its values fit. Copy the shape of a
neighbour in the same domain, including whether it sets `builder`. One action reached by
several routes is one event with a list property for the route, as `Group: Invite Shared` does
with `Method`. Different actions are different events. An event may fire when the phone's
Share menu opens, as long as its `description` says the app can't see what happens next. Never record personal
data: no names, phone numbers, emails, message text, or contact details.

Before you edit, show the person a short card for each event and wait for a yes:

> **Profile: Card Exported** — fires when someone saves or shares their profile card image.
> Records: Format (PNG or SVG). Answers: how many people pass their card on.

Renaming an event or property starts a new line in Mixpanel and breaks every existing chart
that uses it. Warn them before you agree to a rename, and offer to add a new one instead.

## 5. Edit only these files

- `libs/analytics-events/events.toml`: the entry, with a `description` that names the moment.
- `libs/analytics-events/EVENTS.md`: regenerated (step 6).
- `libs/analytics-events/src/commonTest/kotlin/com/flipcash/analytics/<Domain>EventsTest.kt`:
one test per new or changed event that asserts the event name and properties as string
literals, like its neighbours.
- `kmp/shared-core/spm/Sources/SharedCoreKit/AnalyticsEvents.swift`: one typealias, only for a
new list or domain.

Change no app code: screens, view models, DI, Gradle. Wiring is the reviewer's job, and the
person proposing can't review Kotlin or Swift.

A property added to an existing event is always `optional = true`. The generated builder gives
it a `null` default, so the app keeps compiling and existing sends leave the property out until an
engineer wires it. If a build error points into app code after a change like this, the property
isn't optional: fix the entry, never the app.

## 6. Check it

Run these as two commands. In one command, Gradle may check before it writes:

```
./gradlew :libs:analytics-events:writeAnalyticsEventsPage
./gradlew :libs:analytics-events:checkAnalyticsEventsPage :libs:analytics-events:testAndroidHostTest
```

Gradle needs Java 21 and an Android SDK. If the SDK exists but `local.properties` is missing,
write `sdk.dir=<path>` to it (the file is gitignored). The build stops on a bad entry with a
message naming the event and the fix, such as asking for `builder = "..."` when two event names
collide. Apply that fix yourself.

If Gradle can't run here, edit `EVENTS.md` by hand to match the neighbouring entries exactly,
and say in the PR that it wasn't run locally. CI's Unit tests job runs the same check.

## 7. Open a draft PR

Commit as `feat(analytics): <what it measures>` and use the same line as the PR title. Then run
`git push -u origin HEAD` and
`gh pr create --draft --base code/cash --assignee @me --reviewer bmc08gt --title "<title>" --body-file <file>`.
Draft status matters: the catalogue says both apps send every event in it, and that stays
untrue until an engineer wires the event up.

The PR body has these sections:

- **Why**: the question from step 2, in the person's own words.
- **Events**: a table of Mixpanel name, when it fires, and what it records.
- **Where to send it**: per event, the screen and the moment, including every path to that
moment from step 2. This section is for engineers, so name the Android class or function
you found. For iOS, name the screen only, because this checkout has no iOS code. Note that iOS receives it only after the next Publish
SharedCore release.
- **Needs wiring**: events already in the catalogue that nothing sends. Leave the section out
if there are none.
- **Not included**: asks from step 3 that need a Mixpanel report or an engineering design, each
with one line on why.

Then run `gh pr checks --watch`. If a check fails, read the log with `gh run view --log-failed`,
fix the catalogue, and push again.

## 8. Close out with the person

In plain words, tell them what was proposed, what wasn't proposed and why, and what happens
next: an engineer reviews it and connects it in both apps, and the numbers appear in Mixpanel
after the next app release on each platform. Give them the PR link.
Loading