Aller au contenu

Concepts fondamentaux

Presage repose sur un petit ensemble de primitives composables. Comprendre ces concepts vous aidera à concevoir des interfaces adaptatives efficaces.

Le UserContext est la source de vérité unique sur l’identité de l’utilisateur et son comportement. Il combine trois dimensions :

interface UserContext {
traits: UserTraits // Static data you set (role, plan, company)
signals: BehavioralSignals // Auto-computed from tracked events
maturity: Maturity // Automatic segment: 'new' | 'onboarding' | 'active' | 'power' | 'dormant'
}

Les traits sont des attributs statiques que vous définissez explicitement, typiquement depuis votre système d’authentification ou le profil utilisateur.

interface UserTraits {
userId?: string
role?: string
plan?: string
signupDate?: string
company?: string
companySize?: number
locale?: string
[key: string]: unknown // Extensible with any custom field
}

Définissez les traits avec client.identify() ou client.updateTraits().

Les signaux comportementaux sont calculés automatiquement à partir des événements suivis. Vous ne les définissez jamais directement.

interface BehavioralSignals {
sessionCount: number
totalEvents: number
featureUsage: Record<string, number>
lastSeenAt: string
firstSeenAt: string
currentSessionDuration: number
daysSinceSignup: number
clickMap: Record<string, number>
customSignals: Record<string, number>
}

Par exemple, après avoir suivi client.track('feature_used', { featureId: 'export' }) trois fois, signals.featureUsage.export vaudra 3.

La maturité est un segment calculé dérivé des signaux comportementaux. Elle classe les utilisateurs en cinq niveaux :

SegmentSignification
newPeu de sessions (par défaut : 3 ou moins)
onboardingEn phase d’apprentissage (par défaut : 4-10 sessions)
activeUtilisateur régulier ayant dépassé la phase d’intégration
powerUtilise de nombreuses fonctionnalités (par défaut : 5+ fonctionnalités distinctes)
dormantInactif depuis trop longtemps (par défaut : 14+ jours)

Les seuils sont configurables via MaturityConfig.

Un point d’adaptation est un endroit de votre interface où différentes variantes peuvent être affichées. Il est défini par :

interface AdaptationPoint {
id: string // Unique identifier (e.g. 'onboarding')
variants: readonly string[] // Available variant IDs
defaultVariant: string // Fallback when no rule matches
strategy: Strategy // How to select (currently: { type: 'rules' })
}

En React, le composant <Adaptive> crée un point d’adaptation implicitement à partir de ses props et enfants.

Une règle associe un ensemble de conditions à une action. Quand les conditions correspondent au UserContext courant, l’action détermine ce qui se passe au point d’adaptation.

interface AdaptationRule {
id: string // Unique identifier
adaptationId: string // Which adaptation point this rule targets
priority: number // Higher priority rules are evaluated first
conditions: ConditionGroup // Boolean logic tree
action: AdaptationAction // What to do when conditions match
}

Une Condition vérifie un seul champ dans le contexte utilisateur :

interface Condition {
field: string // Dot-path: 'traits.role', 'signals.sessionCount', 'maturity'
operator: ConditionOperator // One of 14 operators
value: unknown // Value to compare against
}

Les groupes combinent les conditions avec de la logique booléenne :

interface ConditionGroup {
all?: (Condition | ConditionGroup)[] // AND — all must match
any?: (Condition | ConditionGroup)[] // OR — at least one must match
not?: Condition | ConditionGroup // NOT — inverts the result
}

Les groupes peuvent être imbriqués à une profondeur arbitraire.

Une action décrit ce qui se passe quand une règle correspond :

type AdaptationAction =
| { type: 'show'; variantId: string } // Show a specific variant
| { type: 'hide' } // Hide the adaptation point entirely
| { type: 'reorder'; order: string[] } // Reorder items (e.g. navigation)
| { type: 'modify'; props: Record<string, unknown> } // Modify component props

Une stratégie détermine comment les variantes sont sélectionnées. Actuellement, seule l’évaluation par règles est disponible :

type Strategy = { type: 'rules' }

A venir dans la v0.2 : Des stratégies de type multi-armed bandit qui optimisent automatiquement la sélection des variantes en fonction des métriques de conversion.

Le tracker enregistre les événements utilisateur et alimente le pipeline de calcul des signaux.

// Track events with optional properties
client.track('feature_used', { featureId: 'dashboard-export' })
client.track('click', { elementId: 'nav-settings' })
client.track('custom_signal', { signalId: 'engagement', value: 5 })

Les noms d’événements spéciaux déclenchent des calculs de signaux spécifiques :

  • feature_used avec featureId — incrémente signals.featureUsage[featureId]
  • click avec elementId — incrémente signals.clickMap[elementId]
  • custom_signal avec signalId et value — incrémente signals.customSignals[signalId]

Vous pouvez également brancher des adaptateurs d’analytique externes (Segment, PostHog, etc.) pour transférer les événements.

La persistance conserve les traits et signaux entre les rechargements de page. Deux drivers intégrés sont fournis :

  • createLocalStorageDriver(prefix) — Stocke les données dans localStorage sous une clé namespacée
  • createMemoryDriver() — Stockage en mémoire pour le SSR et les tests

Quand un driver de persistance est configuré, le client lit les données en cache de manière synchrone à l’initialisation, ce qui évite un flash de contenu par défaut (anti-FOUC).

Quand un composant demande une variante, voici ce qui se passe :

  1. Le composant déclare un AdaptationPoint (id, variantes, défaut)
  2. Le moteur de règles filtre les règles par adaptationId
  3. Les règles sont évaluées par ordre de priorité (la plus haute d’abord)
  4. La première règle dont les conditions correspondent renvoie son action
  5. L’action détermine quelle variante est rendue
  6. Si aucune règle ne correspond, le defaultVariant est utilisé
  7. Un événement presage:impression est automatiquement suivi

L’ensemble de ce flux est synchrone et se produit dans un seul cycle de rendu.