Skip to content

Conversation

@tyiuhc
Copy link
Collaborator

@tyiuhc tyiuhc commented Dec 18, 2025

Summary

Add exit_intent page trigger. This trigger will fire when the upward cursor movement near viewport top is detected, and the user has spent at least minTimeOnPage (if specified).

Checklist

  • Does your PR title have the correct title format?
  • Does your PR have a breaking change?: No

@macroscopeapp
Copy link

macroscopeapp bot commented Dec 18, 2025

Add exit_intent trigger and publish exit_intent messages with durationMs from SubscriptionManager.setupExitIntentPublisher when mouse leaves top edge (clientY <= 50) after optional minTimeOnPageMs

Introduce ExitIntentPayload and ExitIntentTriggerValue; track pageLoadTime; publish exit_intent on top-edge mouseout; match pages by minTimeOnPageMs; reset timing on URL changes. Core logic lives in SubscriptionManager.setupExitIntentPublisher in subscriptions.ts.

📍Where to Start

Start with SubscriptionManager.setupExitIntentPublisher in subscriptions.ts and then review the matching logic in SubscriptionManager.doesMessageMatchPage and the new payload in message-bus.ts.

Changes since #246 opened

  • Introduced a bug in SubscriptionManager.setupExitIntentPublisher method that calculates minTimeOnPageMs as Math.min(0, triggerValue.minTimeOnPageMs ?? 0), which always evaluates to 0 and causes the exit intent listener to be installed immediately regardless of configured minimum time values [b211504]
  • Changed exit intent detection in SubscriptionManager.setupExitIntentPublisher method from listening to the mouseout event to the mouseleave event [b211504]
  • Refactored page filtering and minimum time aggregation logic in SubscriptionManager.setupExitIntentPublisher method to use flatMap and filter operations with early return when no exit intent pages exist, and replaced nested loops with a single loop for computing minimum time on page [b211504]

📊 Macroscope summarized b211504. 3 files reviewed, 3 issues evaluated, 2 issues filtered, 1 comment posted. View details


// Install listener after minimum time requirement
if (minTimeOnPageMs > 0) {
setTimeout(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setupExitIntentPublisher leaks state: re-init can leave a stale setTimeout, add duplicate mouseout handlers, and allow exit_intent to fire multiple times. Suggest making it one-shot by tracking a single handler and timeout on the instance, clearing/removing them on re-init and after first publish, and nulling refs to prevent duplicates.

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

@tyiuhc tyiuhc requested a review from a team December 18, 2025 23:27
@promptless
Copy link

promptless bot commented Dec 18, 2025

📝 Documentation updates detected!

Updated existing suggestion: Document new page trigger types for Web Experiment


private setupExitIntentPublisher = () => {
// Check if any page objects use exit_intent trigger
const hasExitIntentTriggers = Object.values(this.pageObjects).some(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the flow but does this pageObjects already filter only the active page objects?

nit: if you're using pages multiple times, you could assign it at the top first:

const pages = Object.values(this.pageObjects).flatMap((pages) => Object.values(pages));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pageObjects here is static, so it includes all page objects for all experiments.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyiuhc so then should all these functions be checking isPageObjectActive?

minTimeOnPageMs =
minTimeOnPageMs === 0
? triggerValue.minTimeOnPageMs
: Math.min(minTimeOnPageMs, triggerValue.minTimeOnPageMs);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like you can shorten this to minTimeOnPageMs = Math.min(minTimeOnPageMs, triggerValue.minTimeOnPageMs)

// Install listener after minimum time requirement
if (minTimeOnPageMs > 0) {
setTimeout(() => {
this.globalScope.document.addEventListener('mouseout', handleMouseOut);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you definitely want mouseleave since mouseout fires on any descendant


// Install listener after minimum time requirement
if (minTimeOnPageMs > 0) {
setTimeout(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These timeouts also need to be cancelled when user navigates to another page etc. It looks like you can already use addPageChangeSubscriber() for this or we could use a pattern of returning and/or collecting cleanup functions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted - I will add this in a follow up PR so we can centralize cleanup logic across all triggers

Comment on lines +394 to +401
let minTimeOnPageMs = 0;
for (const page of pages) {
const triggerValue = page.trigger_value as ExitIntentTriggerValue;
minTimeOnPageMs = Math.min(
minTimeOnPageMs,
triggerValue.minTimeOnPageMs ?? 0,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minTimeOnPageMs always resolves to 0 because it starts at 0 and uses Math.min(...). Consider initializing to Infinity and defaulting to 0 if none are set so configured delays take effect.

-    let minTimeOnPageMs = 0;
+    let minTimeOnPageMs = Infinity;
     for (const page of pages) {
       const triggerValue = page.trigger_value as ExitIntentTriggerValue;
       minTimeOnPageMs = Math.min(
         minTimeOnPageMs,
         triggerValue.minTimeOnPageMs ?? 0,
       );
     }
+    if (minTimeOnPageMs === Infinity) { minTimeOnPageMs = 0; }

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants