feat(skills): document user-attribute → campaign targeting timing#17
feat(skills): document user-attribute → campaign targeting timing#17kherembourg wants to merge 3 commits into
Conversation
Setting a user attribute saves the value (persisted across sessions) but does not re-trigger or re-evaluate any placement/campaign. The value is applied only on the next placement fetch or campaign trigger, which for APP_STARTED fires shortly after SDK start. A custom-attribute audience therefore misses on the first launch and matches from the next session once the value is persisted — the classic hit-or-miss symptom. Document the reliable first-launch recipe: allowCampaigns(false) → set attribute → allowCampaigns(true) (ordering: start → attributes → campaigns). - user-attributes-targeting.md: new timing section - campaigns.md: custom-attribute audience section + anti-pattern - common-issues.md §7: first-launch campaign race symptom/fix - purchasely-sdk-expert / purchasely-debug skills: timing rule - CHANGELOG: Unreleased entries Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| purchasely/references/concepts/campaigns.md | Adds "Custom-attribute audiences" section with root-cause explanation, two-option guidance, and a Swift code recipe. Anti-pattern list extended with a setUserAttribute re-evaluation misconception entry. Content is technically accurate and consistent with existing API patterns. |
| purchasely/references/concepts/user-attributes-targeting.md | Adds timing section explaining that setUserAttribute is not an event, with platform-specific notes for placements vs. campaigns. The simplified Swift snippet omits the safety timeout note, but explicitly cross-references campaigns.md for the full recipe. |
| purchasely/references/troubleshooting/common-issues.md | Appends a Related note to §7 covering the hit-or-miss first-launch symptom, its cause, and the allowCampaigns fix. Content is accurate; the long single-line format is consistent with existing troubleshooting entries. |
| purchasely/skills/purchasely-sdk-expert/SKILL.md | Adds an Attribute timing for custom-attribute audiences bullet with the full mechanism, the symptom (works once / hit-or-miss), the fix, and a directive to load the reference files. Relative paths to references are correct. |
| purchasely/skills/purchasely-debug/SKILL.md | Adds a new row to the Common Fixes Database table covering the hit-or-miss campaign symptom. The fix and relative path to campaigns.md are correct. |
| CHANGELOG.md | CHANGELOG updated correctly under [Unreleased] per CLAUDE.md convention with Added and Changed entries that are factual and user-facing. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App
participant SDK as Purchasely SDK
participant Cache as SDK Disk Cache
participant Server as Purchasely Server
Note over App,Server: BEFORE - attribute set too late, first launch miss
App->>SDK: apiKey(...).start()
SDK->>Server: Evaluate APP_STARTED trigger (no attribute yet)
Server-->>SDK: Audience: no match
App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
SDK->>Cache: Persist attribute value
Note over SDK: Campaign already resolved, attribute ignored
Note over App,Server: AFTER - attribute set before trigger resolves, first launch match
App->>SDK: apiKey(...).allowCampaigns(false).start()
Note over SDK: APP_STARTED trigger queued, not yet resolved
App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
SDK->>Cache: Persist attribute value
App->>SDK: allowCampaigns(true)
SDK->>Server: Evaluate APP_STARTED trigger (attribute present)
Server-->>SDK: Audience: match
SDK-->>App: Campaign displayed
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App
participant SDK as Purchasely SDK
participant Cache as SDK Disk Cache
participant Server as Purchasely Server
Note over App,Server: BEFORE - attribute set too late, first launch miss
App->>SDK: apiKey(...).start()
SDK->>Server: Evaluate APP_STARTED trigger (no attribute yet)
Server-->>SDK: Audience: no match
App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
SDK->>Cache: Persist attribute value
Note over SDK: Campaign already resolved, attribute ignored
Note over App,Server: AFTER - attribute set before trigger resolves, first launch match
App->>SDK: apiKey(...).allowCampaigns(false).start()
Note over SDK: APP_STARTED trigger queued, not yet resolved
App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
SDK->>Cache: Persist attribute value
App->>SDK: allowCampaigns(true)
SDK->>Server: Evaluate APP_STARTED trigger (attribute present)
Server-->>SDK: Audience: match
SDK-->>App: Campaign displayed
Reviews (1): Last reviewed commit: "feat(skills): document user-attribute → ..." | Re-trigger Greptile
Contexte
Suite à un thread support Slack : un client n'arrivait pas à faire matcher une campaign ciblée sur une audience construite sur un attribut custom (campaign AppsFlyer) au premier launch — ça ne marchait qu'à partir de la 2ᵉ session, de façon intermittente.
Test des skills sur la question du thread : l'agent
purchasely-sdk-expertrépondait déjà à ~80 % comme l'équipe (cause racine + gatingallowCampaigns), mais il manquait deux points et une imprécision de mécanisme.Ce qui manquait
setUserAttribute(...)sauvegarde la valeur (persistée entre sessions) mais ne re-déclenche aucun placement/campaign.allowCampaigns(false), le deeplink de trigger est mis en file non résolu ; le match d'audience n'a lieu qu'auperform()déclenché parallowCampaigns(true). Différer corrige donc bien le 1ᵉʳ launch.Note API : le vrai nom est
allowCampaigns(pluriel, defaulttrue) — confirmé dans la source iOS/Android.Changements
references/concepts/user-attributes-targeting.mdreferences/concepts/campaigns.mdreferences/troubleshooting/common-issues.mdskills/purchasely-sdk-expert/SKILL.mdskills/purchasely-debug/SKILL.mdCHANGELOG.md[Unreleased]Vérification
Re-test de l'agent
purchasely-sdk-expertaprès mise à jour : la réponse couvre désormais non-réévaluation + 2ᵉ session + recetteallowCampaigns(false)→ set attribut →allowCampaigns(true), avec le mécanisme exact.🤖 Generated with Claude Code