Skip to content

Commit b730325

Browse files
committed
feat(analytics): session recording always-on + granular PM events
Session recording: disable_session_recording: false, startSessionRecording() already called in loaded callback — guaranteed for all prod visitors. New events: - app_loaded (view_mode, locale, is_returning_user, source) - view_toggled (list ↔ canvas) - audit_opened / audit_block_added - score_opened - output_format_changed - library_opened / template_applied (id, category) - version_panel_opened / version_saved / version_restored / version_diff_viewed - project_renamed
1 parent b4193f2 commit b730325

8 files changed

Lines changed: 57 additions & 7 deletions

File tree

‎app/src/App.tsx‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@ const App = () => {
5959
const toggleView = (v: 'list' | 'canvas') => {
6060
setCanvasView(v)
6161
localStorage.setItem('flompt-canvas-view', v)
62+
analytics.viewToggled(v)
6263
}
6364

6465
const handleOpenVersions = () => {
6566
openVersions(true)
6667
if (currentProjectId) loadVersions(currentProjectId)
68+
analytics.versionPanelOpened()
6769
}
6870

6971
const handleApplyDebugFix = (fixedPrompt: string) => {
@@ -87,7 +89,15 @@ const App = () => {
8789
// Init PostHog after first render — non-blocking
8890
useEffect(() => {
8991
initAnalytics()
90-
setSource(isExtension ? 'extension' : 'web')
92+
const source = isExtension ? 'extension' : 'web'
93+
setSource(source)
94+
const isReturning = !!localStorage.getItem('flompt-compile-count')
95+
analytics.appLoaded({
96+
view_mode: canvasView,
97+
locale,
98+
is_returning_user: isReturning,
99+
source,
100+
})
91101
}, [])
92102

93103
// Auto-save current project when flowStore changes (debounced)
@@ -191,7 +201,7 @@ const App = () => {
191201
>
192202
<PromptInput />
193203
<div className="panel-divider" role="separator" />
194-
<Sidebar onOpenLibrary={() => setLibraryOpen(true)} />
204+
<Sidebar onOpenLibrary={() => { setLibraryOpen(true); analytics.libraryOpened() }} />
195205
</aside>
196206

197207
<div

‎app/src/components/ProjectSelector.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const ProjectSelector = () => {
7070
const handleFinishRename = () => {
7171
if (renamingId && renameValue.trim()) {
7272
renameProject(renamingId, renameValue.trim())
73+
analytics.projectRenamed()
7374
}
7475
setRenamingId(null)
7576
}

‎app/src/components/PromptInput.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ const PromptInput = () => {
196196
}
197197
setAuditSeen(true)
198198
openAudit(true)
199+
analytics.auditOpened()
199200
}}
200201
data-tour="audit-btn"
201202
type="button"

‎app/src/components/PromptOutput.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ const PromptOutput = () => {
117117

118118
const handleCritic = async () => {
119119
if (!currentRaw) return
120+
analytics.scoreOpened()
120121
// Reuse cached result if prompt hasn't changed
121122
if (criticResult && criticCachedPrompt === currentRaw) {
122123
openCritic(true)
@@ -161,7 +162,7 @@ const PromptOutput = () => {
161162
<Tooltip key={format} content={title} side="top">
162163
<button
163164
className={`format-btn${outputFormat === format ? ' format-btn-active' : ''}`}
164-
onClick={() => setOutputFormat(format)}
165+
onClick={() => { setOutputFormat(format); analytics.outputFormatChanged(format) }}
165166
aria-label={title}
166167
aria-pressed={outputFormat === format}
167168
>

‎app/src/components/TemplateLibrary.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { TEMPLATES, TEMPLATE_CATEGORIES, CATEGORY_COLORS, LOCALE_TO_LANG } from
55
import type { TemplateCategory } from '@/lib/templates'
66
import { useFlowStore } from '@/store/flowStore'
77
import { useLocale } from '@/i18n/LocaleContext'
8+
import { analytics } from '@/lib/analytics'
89
import type { FlomptNode } from '@/types/blocks'
910

1011
const TemplateLibrary = ({ onClose }: { onClose?: () => void }) => {
@@ -40,6 +41,7 @@ const TemplateLibrary = ({ onClose }: { onClose?: () => void }) => {
4041
setEdges([])
4142
setCompiledPrompt(null)
4243
setLoadedId(templateId)
44+
analytics.templateApplied(templateId, tpl.category)
4345
setTimeout(() => {
4446
setActiveTab('canvas')
4547
setLoadedId(null)

‎app/src/features/audit/AuditPanel.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { X, CheckCircle2, XCircle, Plus, ShieldCheck } from 'lucide-react'
33
import { useAuditStore } from './useAuditStore'
44
import { useFlowStore } from '@/store/flowStore'
55
import { useLocale } from '@/i18n/LocaleContext'
6+
import { analytics } from '@/lib/analytics'
67
import { BLOCK_META, DEFAULT_RESPONSE_STYLE, generateResponseStyleContent } from '@/types/blocks'
78
import type { BlockType, FlomptNode } from '@/types/blocks'
89
import { findFreePosition, getCanvasSize } from '@/lib/layoutNodes'
@@ -53,6 +54,7 @@ export default function AuditPanel() {
5354

5455
addNode(node)
5556
setAdded(prev => new Set(prev).add(type))
57+
analytics.auditBlockAdded(type)
5658
}
5759

5860
return (

‎app/src/features/versioning/useVersionStore.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { nanoid } from 'nanoid'
33
import { getDB, type PromptVersion } from '@/lib/db'
44
import { useFlowStore } from '@/store/flowStore'
55
import { computeLineDiff, type DiffLine } from '@/lib/diff'
6+
import { analytics } from '@/lib/analytics'
67

78
interface VersionState {
89
versions: PromptVersion[]
@@ -57,12 +58,14 @@ export const useVersionStore = create<VersionState>((set, get) => ({
5758
const db = await getDB()
5859
await db.put('versions', version)
5960
set(state => ({ versions: [version, ...state.versions] }))
61+
analytics.versionSaved(versionNum)
6062
},
6163

6264
rollback: (version) => {
6365
const store = useFlowStore.getState()
6466
store.setNodes(version.nodes)
6567
store.setEdges(version.edges)
68+
analytics.versionRestored(version.version)
6669
},
6770

6871
showDiff: (idA, idB) => {
@@ -72,6 +75,7 @@ export const useVersionStore = create<VersionState>((set, get) => ({
7275
if (!vA || !vB) return
7376
const lines = computeLineDiff(vA.output ?? vA.prompt, vB.output ?? vB.prompt)
7477
set({ diffView: { idA, idB, lines } })
78+
analytics.versionDiffViewed()
7579
},
7680

7781
closeDiff: () => set({ diffView: null }),

‎app/src/lib/analytics.ts‎

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ export const initAnalytics = () => {
2727
autocapture: true,
2828
request_batching: true,
2929

30-
// Session replay — mask prompt content (privacy)
30+
// Session replay — always on, mask prompt content (privacy)
31+
disable_session_recording: false,
3132
session_recording: {
32-
maskAllInputs: false, // keep most inputs visible
33-
maskInputOptions: { textarea: true }, // mask prompt textarea only
34-
recordCrossOriginIframes: false,
33+
maskAllInputs: false, // keep most inputs visible
34+
maskInputOptions: { textarea: true }, // mask prompt textarea only
35+
recordCrossOriginIframes: false,
3536
},
3637

3738
// Heatmaps
@@ -83,6 +84,10 @@ export const setSource = (source: 'web' | 'extension', aiPlatform?: string) => {
8384
// ── Typed event helpers ───────────────────────────────────────────────────────
8485

8586
export const analytics = {
87+
// App lifecycle
88+
appLoaded: (props: { view_mode: string; locale: string; is_returning_user: boolean; source: string }) =>
89+
track('app_loaded', props),
90+
8691
// Tour
8792
tourStarted: () => track('tour_started'),
8893
tourStep: (step: number, title: string) => track('tour_step', { step, title }),
@@ -97,10 +102,33 @@ export const analytics = {
97102
promptCopied: () => track('prompt_copied'),
98103
promptExported: (format: 'txt' | 'json') => track('prompt_exported', { format }),
99104

105+
// View
106+
viewToggled: (to: 'list' | 'canvas') => track('view_toggled', { to }),
107+
100108
// Blocks
101109
blockAdded: (type: string) => track('block_added', { type }),
102110
blockDeleted: (type: string) => track('block_deleted', { type }),
103111

112+
// Audit
113+
auditOpened: () => track('audit_opened'),
114+
auditBlockAdded: (type: string) => track('audit_block_added', { type }),
115+
116+
// Score / Critic
117+
scoreOpened: () => track('score_opened'),
118+
119+
// Output
120+
outputFormatChanged: (format: string) => track('output_format_changed', { format }),
121+
122+
// Template library
123+
libraryOpened: () => track('library_opened'),
124+
templateApplied: (id: string, category: string) => track('template_applied', { template_id: id, category }),
125+
126+
// Version history
127+
versionPanelOpened: () => track('version_panel_opened'),
128+
versionSaved: (versionNum: number) => track('version_saved', { version_num: versionNum }),
129+
versionRestored: (versionNum: number) => track('version_restored', { version_num: versionNum }),
130+
versionDiffViewed: () => track('version_diff_viewed'),
131+
104132
// Settings
105133
localeChanged: (locale: string) => track('locale_changed', { locale }),
106134

@@ -117,6 +145,7 @@ export const analytics = {
117145
projectCreated: () => track('project_created'),
118146
projectSwitched: (name: string) => track('project_switched', { project_name: name }),
119147
projectDeleted: () => track('project_deleted'),
148+
projectRenamed: () => track('project_renamed'),
120149

121150
// Errors
122151
error: (context: string, message?: string) => track('error', { context, message }),

0 commit comments

Comments
 (0)