Feat/popups v2 rules - #84
anyelopetit wants to merge 33 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
ce1f901 to
385a6da
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce1f90104a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd3d873ba5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de17524ce2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Adds the browser half of the split evaluation. The server decides profile conditions and sends only the page conditions of the lanes that survived, so this treats the payload as the whole remaining question: lanes are OR'd, conditions inside a lane are AND'd, and no lanes means the popup may display. PopupDisplayRules mirrors Popup::DisplayRules::PageEvaluator, including complement semantics for unset values, so Test a URL and the runtime cannot disagree. Scroll depth and time on page only grow, so a popup gated on them is re-checked on scroll and once a second instead of being decided on connect. Watching starts only when a rule needs a measurement, and stops the moment the popup displays: a popup counts as shown when it actually appears, never when its rules merely match. An eligible: false response carries no markup and is treated as nothing to render rather than a failure.
de17524 to
beec397
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: beec397f97
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0d07955a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38acfaea16
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f048e59a7a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70df204cba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d15985a71c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ) | ||
| return | ||
|
|
||
| if (response.data.status === 202) continue |
There was a problem hiding this comment.
Clear identification state when polling is exhausted
If the status endpoint remains at 202 through the eighth attempt—such as when the identification job takes longer than roughly 16 seconds—this continue exits the loop without clearing identificationPending. Because identify() already unmounted the active popup and loadPopup() refuses to run while that flag is set, popup evaluation then remains disabled for the rest of the visit. Clear the pending state and restore anonymous popup evaluation when the retry budget is exhausted.
Useful? React with 👍 / 👎.
| // `eligible: false` with no markup when this visitor does not qualify. That is a | ||
| // deliberate outcome rather than an error, so it is treated the same as "nothing to | ||
| // render" instead of surfacing as a failure. | ||
| if (data.eligible === false || !data.html) return null |
There was a problem hiding this comment.
Keep SPA page tracking active without eligible markup
When the initial response is eligible: false, this early return means no popup controller is mounted, but PopupController.watchNavigation() is the only runtime component that records SPA route changes. For example, an anonymous visitor can navigate through several routes and then identify into an eligible audience; the reloaded popup receives a stale page-view count and a session.page_views rule may stay false. The remaining uncovered case is specifically this no-markup response, so route tracking needs to live outside the rendered popup controller or remain active while eligibility is false.
Useful? React with 👍 / 👎.
| ) | ||
| return | ||
|
|
||
| User.remember(details.externalId, details.source, details.fingerprint) |
There was a problem hiding this comment.
Cancel pending identification when forgetting the user
When an app awaits identify() and then logs the user out before receipt polling completes, identify() has already returned but forget() does not invalidate the background identification. A later successful poll reaches this line and recreates the user cookies that logout just cleared, so subsequent activity can be attributed to the previous user. forget() must advance the identification version, cancel polling, and clear the pending state before deleting the identity.
Useful? React with 👍 / 👎.
Summary
Implements the browser-runtime half of Popup Rules V2 for the paired Rails change in hellotext#5833.
The SDK evaluates current page, visit, and activity conditions immediately in the browser, follows modern SPA navigation correctly, and coordinates late identification with the new Rails receipt protocol before re-evaluating popup eligibility. Profile, audience, and signal conditions remain trusted server-side checks and are never evaluated from browser-visible contact data.
This PR releases the runtime contract as version 2.6.1.
Runtime contract with Rails
The client evaluator expects the canonical
{ lanes: [...] }document:The composer restricts the merchant to three alternatives, but the runtime stays defensive when evaluating persisted data.
Supported client-side context
page.patheven though the authoring label is “Page URL”. It matches a storefront path, not an arbitrary full href.document.titlenavigator.languageURLSearchParams; values are case-insensitive; URL encoding,+, and hash-route query parameters are handled.Profile fields, audience membership, signal values, and country fallback are deliberately absent from this table because Rails evaluates them before a public popup response is returned.
Navigation and route normalization
The popup runtime watches the navigation mechanisms that matter on modern storefronts:
pushStateandreplaceState;popstateandhashchange;document.titlemutations.It canonicalizes routes so a route change is meaningful rather than an incidental URL mutation:
#/collections/saleand#!/collections/saleare treated as routes;index.html;This keeps popup decisions stable for SPAs while allowing a merchant to target actual client-side routes.
Page URL authoring compatibility
The Rails editor intentionally names the path field “Page URL” for non-technical merchants. The SDK aligns with that decision:
The normalizer must not silently broaden a rule. Query strings, fragments, trailing slashes,
index.html, and hash routes have explicit documented behavior so the displayed input is not misleading.Activity semantics
Activities are evaluated only for the current business and visit. This prevents an event from another tenant, an older visit, or an arbitrary backdated timestamp from unlocking a popup rule.
Commerce fields are visible because they are product decisions, not because every platform is already proven to emit them. Each installed Shopify, Wix, and VTEX integration must be smoke-tested against a real successful action:
The runtime accepts
Hellotext.trackactivity events, but an integration must call it at the right successful storefront boundary.Late identification receipt protocol
Popup eligibility can change after
identify(), because a visitor may gain a profile, audience, or signal condition only after the server has mapped the identification.The SDK supports the receipt-aware Rails API:
identify()submits the identification and receives a short-lived receipt.Safety properties:
The intended rollout order is Rails receipt endpoint first, then this SDK version. The legacy path reduces compatibility risk but is not a substitute for deploying the server contract.
Failure behavior and privacy boundaries
Build and dependency updates
Validation
Release checklist
hellotext.js@2.6.1.identify().Intentionally out of scope