77import type { WizardSession } from '@lib/wizard-session' ;
88import type { ApiUser } from '@lib/api' ;
99import { v4 as uuidv4 } from 'uuid' ;
10- import { debug } from './debug' ;
10+ import { debug , logToFile } from './debug' ;
1111
1212/**
1313 * Extract a standard property bag from the current session.
@@ -55,6 +55,7 @@ export class Analytics {
5555 private appName = 'wizard' ;
5656 private activeFlags : Record < string , string > | null = null ;
5757 private groups : Record < string , string > = { } ;
58+ private personProperties : Record < string , string > = { } ;
5859
5960 constructor ( ) {
6061 this . client = new PostHog ( ANALYTICS_POSTHOG_PUBLIC_PROJECT_WRITE_KEY , {
@@ -85,6 +86,34 @@ export class Analytics {
8586 } ) ;
8687 }
8788
89+ /**
90+ * Identify the authenticated user. Sets the distinct id and records their
91+ * person properties (email, name) so events carry them and feature flags can
92+ * target the individual user. Without the email here, the wizard only sends
93+ * `$app_name`, so flags targeted by email never match.
94+ */
95+ identifyUser ( user : ApiUser ) {
96+ this . setDistinctId ( user . distinct_id ) ;
97+ const props : Record < string , string > = { } ;
98+ if ( user . email ) props . email = user . email ;
99+ const name = [ user . first_name , user . last_name ]
100+ . filter ( Boolean )
101+ . join ( ' ' )
102+ . trim ( ) ;
103+ if ( name ) props . name = name ;
104+ this . personProperties = props ;
105+ this . client . identify ( { distinctId : user . distinct_id , properties : props } ) ;
106+ // The flag snapshot is per identity. Anything evaluated before login (the
107+ // intro screen reads the tools-menu flag) was anonymous — drop it so the
108+ // next read re-evaluates as this user.
109+ this . activeFlags = null ;
110+ }
111+
112+ /** Person properties sent with flag evaluation: app name plus the user's. */
113+ private flagPersonProperties ( ) : Record < string , string > {
114+ return { $app_name : this . appName , ...this . personProperties } ;
115+ }
116+
88117 setTag ( key : string , value : string | boolean | number | null | undefined ) {
89118 this . tags [ key ] = value ;
90119 }
@@ -125,9 +154,7 @@ export class Analytics {
125154 const distinctId = this . distinctId ?? this . anonymousId ;
126155 return await this . client . getFeatureFlag ( flagKey , distinctId , {
127156 sendFeatureFlagEvents : true ,
128- personProperties : {
129- $app_name : this . appName ,
130- } ,
157+ personProperties : this . flagPersonProperties ( ) ,
131158 } ) ;
132159 } catch ( error ) {
133160 debug ( 'Failed to get feature flag:' , flagKey , error ) ;
@@ -146,8 +173,13 @@ export class Analytics {
146173 }
147174 try {
148175 const distinctId = this . distinctId ?? this . anonymousId ;
176+ logToFile ( '[flags] evaluating as' , {
177+ distinctId,
178+ identified : this . distinctId !== undefined ,
179+ personProperties : this . flagPersonProperties ( ) ,
180+ } ) ;
149181 const result = await this . client . getAllFlagsAndPayloads ( distinctId , {
150- personProperties : { $app_name : this . appName } ,
182+ personProperties : this . flagPersonProperties ( ) ,
151183 } ) ;
152184 const flags = result . featureFlags ?? { } ;
153185 const out : Record < string , string > = { } ;
@@ -156,6 +188,7 @@ export class Analytics {
156188 out [ key ] = typeof value === 'boolean' ? String ( value ) : String ( value ) ;
157189 }
158190 this . activeFlags = out ;
191+ logToFile ( '[flags] evaluated' , out ) ;
159192 return out ;
160193 } catch ( error ) {
161194 debug ( 'Failed to get all feature flags:' , error ) ;
0 commit comments