Skip to content

Commit 23ba470

Browse files
Add analytics event tracking across desktop app
Instrument multiple desktop components to emit analytics events via @hypr/plugin-analytics. The change adds analyticsCommands imports and event calls for file uploads (audio/transcript), session export (pdf/vtt), session/recording deletion, AI provider configuration, data import, user sign-out, trial start and upgrade clicks, and settings changes. This ensures key user actions are tracked for product metrics, funnels, and feature adoption analysis.
1 parent af29066 commit 23ba470

File tree

9 files changed

+413
-2
lines changed

9 files changed

+413
-2
lines changed

ANALYTICS_TRACKING_PLAN.md

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
# Analytics Tracking Plan
2+
3+
This document outlines all analytics events tracked in Hyprnote, including their purpose, properties, and implementation status.
4+
5+
## Event Naming Convention
6+
7+
All events follow the pattern: `{object}_{past_tense_verb}` or `{action}_{object}`
8+
- Use snake_case for event names
9+
- Use past tense verbs (e.g., `created`, `deleted`, `started`)
10+
- Keep names concise but descriptive
11+
- Properties use snake_case
12+
13+
## Currently Implemented Events
14+
15+
### Authentication & Account Management
16+
17+
#### `user_signed_in`
18+
**When**: User successfully signs in to their account
19+
**Location**: `apps/desktop/src/auth.tsx:244`
20+
**Properties**: None
21+
**Purpose**: Track successful authentication events
22+
23+
#### `user_signed_out`
24+
**When**: User clicks sign out button
25+
**Location**: `apps/desktop/src/components/settings/general/account.tsx:46`
26+
**Properties**: None
27+
**Purpose**: Track when users end their session
28+
29+
#### `account_skipped`
30+
**When**: User chooses "Proceed without account" on Apple Silicon
31+
**Location**: `apps/desktop/src/components/onboarding/welcome.tsx:55`
32+
**Properties**: None
33+
**Purpose**: Track users who opt for local-only mode
34+
35+
### Monetization
36+
37+
#### `trial_started`
38+
**When**: User successfully starts a Pro trial
39+
**Location**: `apps/desktop/src/components/settings/general/account.tsx:204`
40+
**Properties**:
41+
- `plan`: string - Always "pro"
42+
43+
**Purpose**: Track trial conversions
44+
45+
#### `upgrade_clicked`
46+
**When**: User clicks the upgrade to Pro button
47+
**Location**: `apps/desktop/src/components/settings/general/account.tsx:213`
48+
**Properties**:
49+
- `plan`: string - Always "pro"
50+
51+
**Purpose**: Track upgrade funnel entry point
52+
53+
### Session Management
54+
55+
#### `session_started`
56+
**When**: User starts recording a new session
57+
**Location**: `apps/desktop/src/hooks/useStartListening.ts:42`
58+
**Properties**:
59+
- `has_calendar_event`: boolean - Whether session is linked to calendar event
60+
61+
**Purpose**: Track recording usage patterns
62+
63+
#### `session_deleted`
64+
**When**: User deletes a session (note + recording)
65+
**Location**: `apps/desktop/src/components/main/body/sessions/outer-header/overflow/delete.tsx:20`
66+
**Properties**:
67+
- `includes_recording`: boolean - Whether recording was also deleted
68+
69+
**Purpose**: Track data retention patterns
70+
71+
#### `recording_deleted`
72+
**When**: User deletes only the recording (keeps transcript/notes)
73+
**Location**: `apps/desktop/src/components/main/body/sessions/outer-header/overflow/delete.tsx:55`
74+
**Properties**: None
75+
**Purpose**: Track selective data management
76+
77+
### Content Creation & Editing
78+
79+
#### `note_created`
80+
**When**: User creates a new note
81+
**Location**:
82+
- `apps/desktop/src/components/main/shared.ts:35`
83+
- `apps/desktop/src/components/main/sidebar/timeline/item.tsx:187`
84+
85+
**Properties**:
86+
- `has_event_id`: boolean - Whether note is linked to calendar event
87+
88+
**Purpose**: Track content creation
89+
90+
#### `note_edited`
91+
**When**: User writes content in a note (first edit only)
92+
**Location**: `apps/desktop/src/components/main/body/sessions/note-input/raw.tsx:81`
93+
**Properties**:
94+
- `has_content`: boolean - Always true
95+
96+
**Purpose**: Track active note editing
97+
98+
#### `note_enhanced`
99+
**When**: AI enhances/summarizes a note (auto or manual)
100+
**Location**:
101+
- `apps/desktop/src/hooks/useAutoEnhance.ts:81` (auto)
102+
- `apps/desktop/src/components/main/body/sessions/note-input/header.tsx:254` (template)
103+
- `apps/desktop/src/components/main/body/sessions/note-input/header.tsx:476` (manual)
104+
105+
**Properties**:
106+
- `is_auto`: boolean - Whether enhancement was automatic
107+
- `template_id`: string (optional) - If using a template
108+
109+
**Purpose**: Track AI feature usage
110+
111+
### Data Operations
112+
113+
#### `data_imported`
114+
**When**: User successfully imports data from external source
115+
**Location**: `apps/desktop/src/components/settings/data/import.tsx:38`
116+
**Properties**:
117+
- `source`: string - Source type (e.g., "notion", "roam")
118+
119+
**Purpose**: Track data migration patterns
120+
121+
#### `session_exported`
122+
**When**: User exports a session to file
123+
**Location**:
124+
- `apps/desktop/src/components/main/body/sessions/outer-header/overflow/export-pdf.tsx:106` (PDF)
125+
- `apps/desktop/src/components/main/body/sessions/outer-header/overflow/export-transcript.tsx:62` (VTT)
126+
127+
**Properties**:
128+
- `format`: string - "pdf" or "vtt"
129+
- `has_transcript`: boolean (PDF only) - Whether transcript was included
130+
- `has_enhanced`: boolean (PDF only) - Whether enhanced notes were included
131+
- `word_count`: number (VTT only) - Number of words in transcript
132+
133+
**Purpose**: Track export feature usage and format preferences
134+
135+
#### `file_uploaded`
136+
**When**: User uploads audio or transcript file
137+
**Location**: `apps/desktop/src/components/main/body/sessions/floating/listen.tsx:256,278`
138+
**Properties**:
139+
- `file_type`: string - "audio" or "transcript"
140+
- `token_count`: number (transcript only) - Number of tokens imported
141+
142+
**Purpose**: Track file import feature usage
143+
144+
### Settings & Configuration
145+
146+
#### `settings_changed`
147+
**When**: User modifies app settings
148+
**Location**: `apps/desktop/src/components/settings/general/index.tsx:74`
149+
**Properties**:
150+
- `autostart`: boolean - Start at login setting
151+
- `notification_detect`: boolean - Auto-detect meetings setting
152+
- `save_recordings`: boolean - Save recordings setting
153+
- `telemetry_consent`: boolean - Analytics consent setting
154+
155+
**Purpose**: Track settings preferences and privacy choices
156+
157+
#### `ai_provider_configured`
158+
**When**: User configures an AI provider (API key, base URL)
159+
**Location**: `apps/desktop/src/components/settings/ai/shared/index.tsx:105`
160+
**Properties**:
161+
- `provider`: string - Provider type (e.g., "openai", "anthropic", "local")
162+
163+
**Purpose**: Track AI provider adoption
164+
165+
### Search & Discovery
166+
167+
#### `search_performed`
168+
**When**: User performs a search
169+
**Location**: `apps/desktop/src/contexts/search/ui.tsx:208`
170+
**Properties**: None
171+
**Purpose**: Track search feature usage
172+
173+
#### `tab_opened`
174+
**When**: User opens a new tab
175+
**Location**: `apps/desktop/src/store/zustand/tabs/basic.ts:39,47`
176+
**Properties**:
177+
- `view`: string - Tab type (e.g., "sessions", "templates", "contacts")
178+
179+
**Purpose**: Track feature discovery and navigation patterns
180+
181+
### Communication
182+
183+
#### `message_sent`
184+
**When**: User sends a chat message
185+
**Location**: `apps/desktop/src/components/chat/input.tsx:47`
186+
**Properties**: None
187+
**Purpose**: Track chat feature usage
188+
189+
## Not Yet Implemented (Future Tracking)
190+
191+
### High Priority
192+
193+
#### Collaboration
194+
- `share_link_copied` - When user copies share link
195+
- `person_invited` - When user invites someone to share
196+
- `access_revoked` - When user removes someone's access
197+
- `role_changed` - When user changes viewer/editor role
198+
199+
#### Integrations
200+
- `integration_connected` - When user connects an integration
201+
- Properties: `integration_name`, `integration_type`
202+
- `integration_disconnected` - When user disconnects
203+
- Properties: `integration_name`
204+
- `sync_triggered` - When user manually syncs
205+
- Properties: `integration_name`
206+
207+
#### Templates
208+
- `template_created` - When user creates a custom template
209+
- `template_edited` - When user modifies a template
210+
- `template_deleted` - When user deletes a template
211+
- `template_cloned` - When user duplicates a template
212+
213+
### Medium Priority
214+
215+
#### Onboarding
216+
- `onboarding_step_completed` - Track onboarding progress
217+
- Properties: `step_name`, `step_number`
218+
- `onboarding_skipped` - When user skips onboarding
219+
- Properties: `at_step`
220+
221+
#### Error Tracking
222+
- `config_error_encountered` - When user hits config error
223+
- Properties: `error_type`, `component`
224+
- `import_failed` - When import operation fails
225+
- Properties: `source`, `error_message`
226+
- `export_failed` - When export operation fails
227+
- Properties: `format`, `error_message`
228+
229+
#### Feature Discovery
230+
- `keyboard_shortcut_used` - Track shortcut usage
231+
- Properties: `shortcut_name`, `shortcut_key`
232+
- `feature_tooltip_viewed` - Track help tooltip views
233+
- Properties: `feature_name`
234+
235+
### Low Priority
236+
237+
#### Sort & Filter
238+
- `results_sorted` - When user changes sort order
239+
- Properties: `sort_by`, `view_type`
240+
- `results_filtered` - When user applies filters
241+
- Properties: `filter_type`, `filter_value`
242+
243+
#### Calendar Integration
244+
- `calendar_synced` - When calendar sync completes
245+
- Properties: `event_count`, `integration_name`
246+
- `calendar_event_selected` - When user selects event for note
247+
- Properties: `has_existing_note`
248+
249+
## Implementation Guidelines
250+
251+
### Adding New Events
252+
253+
1. **Follow naming convention**: Use `{object}_{past_tense_verb}` format
254+
2. **Import the analytics commands**:
255+
```typescript
256+
import { commands as analyticsCommands } from "@hypr/plugin-analytics";
257+
```
258+
259+
3. **Call the event at the right time**:
260+
```typescript
261+
void analyticsCommands.event({
262+
event: "event_name",
263+
property_key: property_value,
264+
});
265+
```
266+
267+
4. **Best practices**:
268+
- Track events in `onSuccess` callbacks when possible
269+
- Don't add complexity to code for tracking
270+
- Track the action, not the intent
271+
- Keep property names consistent across events
272+
- Use boolean properties for binary choices
273+
- Use string enums for categorical data
274+
- Use numbers for counts/amounts
275+
276+
### Privacy Considerations
277+
278+
- Never track PII (names, emails, content)
279+
- Track metadata and usage patterns only
280+
- Respect `telemetry_consent` setting
281+
- Use device fingerprint as `distinct_id`, not user emails
282+
- Anonymous by design
283+
284+
### Testing Events
285+
286+
1. Enable analytics in development mode
287+
2. Trigger the action that should fire the event
288+
3. Check PostHog dashboard for event arrival
289+
4. Verify all properties are correctly set
290+
5. Test edge cases (errors, empty states, etc.)
291+
292+
## Event Properties Reference
293+
294+
### Common Properties
295+
296+
- `plan`: "pro" | "free" - User's current plan
297+
- `provider`: string - AI provider name
298+
- `format`: "pdf" | "vtt" | "json" - Export format
299+
- `source`: string - Import source type
300+
- `is_auto`: boolean - Whether action was automatic
301+
- `has_*`: boolean - Presence indicators
302+
303+
### Property Naming
304+
305+
- Use snake_case
306+
- Use descriptive names
307+
- Prefix existence checks with `has_`
308+
- Use counts with `_count` suffix
309+
- Use timestamps with `_at` suffix
310+
311+
## Metrics & KPIs
312+
313+
### Activation Metrics
314+
- Users who start their first session
315+
- Users who create their first note
316+
- Users who configure AI provider
317+
- Users who perform first search
318+
319+
### Engagement Metrics
320+
- Sessions started per user per week
321+
- Notes created per user per week
322+
- AI enhancements triggered
323+
- Exports performed
324+
325+
### Retention Metrics
326+
- Weekly active users (perform any tracked action)
327+
- Session creation rate over time
328+
- Feature usage diversity (unique events per user)
329+
330+
### Conversion Metrics
331+
- Trial start rate
332+
- Upgrade click-through rate
333+
- Sign-up vs. local-only split
334+
335+
### Feature Adoption
336+
- AI provider configuration rate
337+
- Integration connection rate
338+
- Template creation rate
339+
- Search usage rate
340+
341+
## Related Documentation
342+
343+
- PostHog: https://posthog.com/docs/product-analytics
344+
- Plugin bindings: `plugins/analytics/js/bindings.gen.ts`
345+
- Command reference: `.cursor/commands/add-analytics.md`

apps/desktop/src/components/main/body/sessions/floating/listen.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "lucide-react";
1212
import { useCallback, useState } from "react";
1313

14+
import { commands as analyticsCommands } from "@hypr/plugin-analytics";
1415
import { commands as listener2Commands } from "@hypr/plugin-listener2";
1516
import { commands as miscCommands } from "@hypr/plugin-misc";
1617
import { Button } from "@hypr/ui/components/ui/button";
@@ -252,6 +253,12 @@ function OptionsMenu({
252253
created_at: new Date().toISOString(),
253254
});
254255
});
256+
257+
void analyticsCommands.event({
258+
event: "file_uploaded",
259+
file_type: "transcript",
260+
token_count: subtitle.tokens.length,
261+
});
255262
}),
256263
),
257264
);
@@ -269,6 +276,10 @@ function OptionsMenu({
269276
fromResult(miscCommands.audioImport(sessionId, path)),
270277
Effect.tap(() =>
271278
Effect.sync(() => {
279+
void analyticsCommands.event({
280+
event: "file_uploaded",
281+
file_type: "audio",
282+
});
272283
void queryClient.invalidateQueries({
273284
queryKey: ["audio", sessionId, "exist"],
274285
});

0 commit comments

Comments
 (0)