Skip to content

Widget Display Logic

This document provides a comprehensive overview of the decision flow that determines whether the Rose Widget displays on a given page. Understanding this flow is essential for debugging visibility issues and configuring the widget correctly.

For configuration options, see Client Configuration. For the technical implementation, see Widget Technical Knowledge.


Overview

The widget display decision involves multiple layers of checks, each with specific configuration options:

Layer Purpose Configuration
Force Display Bypass all checks (demo/testing) Runtime forceDisplay param
Traffic Control Gradual rollout percentage site_configs.traffic_allocated
URL Whitelist Only show on specific URLs site_configs.display_only_on_urls
URL Blacklist Never show on specific URLs custom_config.exclude_url_patterns
Device Check Mobile device handling custom_config.enable_mobile
Copilot Mode Show copilot form factor on CTA/form pages global_client_config.features.copilot_mode

Decision Flowchart

flowchart TD A[Page Loads] --> B{forceDisplay = true?} B -->|Yes| SHOW_DEFAULT[SHOW: Default mode] B -->|No| C{traffic_allocated = 0?} C -->|Yes| HIDE1[HIDE: Domain disabled] C -->|No| D{User bucket < traffic_allocated?} D -->|No| HIDE2[HIDE: Traffic bucket] D -->|Yes| E{display_only_on_urls defined?} E -->|Yes| F{URL matches whitelist?} E -->|No| G{URL matches exclude_url_patterns?} F -->|No| HIDE3[HIDE: Not whitelisted] F -->|Yes| G G -->|Yes| HIDE4[HIDE: URL excluded] G -->|No| H{Mobile device?} H -->|Yes| I{enable_mobile = true?} H -->|No| J{copilot_mode enabled?} I -->|No| HIDE5[HIDE: Mobile blocked] I -->|Yes| SHOW_DEFAULT2[SHOW: Default mode] J -->|No| SHOW_DEFAULT3[SHOW: Default mode] J -->|Yes| K{Page is form tracking page?} K -->|No| SHOW_DEFAULT4[SHOW: Default mode] K -->|Yes| SHOW_COPILOT[SHOW: Copilot mode]

Note: When copilot_mode is disabled (default), the widget uses default mode on all pages. Copilot mode activates on form tracking pages (CTA destinations, additional form tracking pages, thank-you pages) when the feature flag is enabled.


Layer 1: Force Display Override

Runtime parameter: forceDisplay

When forceDisplay is set to true, all other checks are bypassed and the widget always displays. This is used for:

  • Demo pages
  • Local development testing
  • Client preview environments
InboundXLoader.init({
  forceDisplay: true,  // Bypasses all display restrictions
  // ... other config
});

Layer 2: Traffic Control

Configuration: site_configs.traffic_allocated (0-100)

Controls what percentage of visitors see the widget using deterministic bucketing.

Value Behavior
0 Widget never shows (domain disabled)
1-99 Gradual rollout (e.g., 20 = 20% of visitors)
100 Widget always shows (default)

How Bucketing Works

  1. Generate a stable client ID (PostHog distinct_id or localStorage UUID)
  2. Hash the client ID to assign a bucket (0-99)
  3. If bucket < traffic_allocated, show the widget

Key benefit: Same user always gets the same result across sessions. Increasing traffic from 20% to 80% keeps the original 20% enabled.

Code location: frontend/widget/src/lib/widgetInit/trafficControl.ts


Layer 3: URL Whitelist

Configuration: site_configs.display_only_on_urls (string array)

If defined, the widget only displays on URLs matching these patterns.

{
  "display_only_on_urls": [
    "https://www.example.com/pricing",
    "https://www.example.com/demo"
  ]
}

Matching Logic

  • Uses simple substring matching (includes())
  • Trailing slashes are normalized before comparison
  • If array is empty or undefined, no whitelist restriction applies
  • Wildcards (*) are NOT supported (unlike exclude_url_patterns)

Code location: frontend/shared/src/context/SiteConfigContext.tsx (isCurrentURLAllowed, lines 665-694)


Layer 4: URL Blacklist

Configuration: custom_config.exclude_url_patterns (string array)

URLs matching these patterns will never show the widget.

{
  "custom_config": {
    "exclude_url_patterns": [
      "www.example.com/blog*",
      "www.example.com/admin*",
      "www.example.com/login"
    ]
  }
}

Matching Logic

  • Uses full wildcard pattern matching via matchesUrlPattern()
  • Supports * wildcards (e.g., *.example.com/internal*)
  • Checked after whitelist (if both defined, URL must pass both)

Code location: frontend/shared/src/utils/domain/siteRestrictions.ts


Layer 5: Device Check

Configuration: custom_config.enable_mobile (boolean)

Value Behavior
false (default) Widget hidden on mobile devices
true Widget visible on mobile devices

Mobile Detection

Mobile detection uses User Agent string matching only:

/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i

Note: The 1236px breakpoint in constants is used for responsive layout adjustments within the widget, not for display decisions.

Code location: frontend/shared/src/utils/device/mobileDetection.ts


Layer 6: CTA Page Visibility

Configuration: custom_config.hide_search_bar_on_cta_pages (boolean)

This configuration marks pages where special behavior should apply. When copilot_mode is enabled, these pages show the copilot form factor instead of hiding the widget. When copilot_mode is disabled, the widget shows in default mode on all pages (the old "hide" behavior is replaced by the copilot feature).

What Counts as a "Form Tracking Page"?

"Form tracking page" is the umbrella term for any page where the widget tracks form submissions. This includes CTA destination pages. A page is considered a form tracking page if it matches any of these sources:

Source Configuration Description
CTA Destinations site_configs.ctas[].langs[].url URLs from CTA button definitions
Additional Destinations custom_config.additional_cta_destinations Track-only URLs (no CTA buttons) ⚠️
Form Tracking Pages custom_config.additional_form_tracking_pages Extra patterns for form tracking
Thank-You Pages custom_config.thank_you_pages Confirmation page patterns

⚠️ Known issue: additional_cta_destinations is not currently checked by shouldEnableFormTracking() for widget hiding. It's used by getAllCTADestinations() for other purposes but doesn't affect whether the widget is hidden on these pages.

Configuration Example

{
  "custom_config": {
    "hide_search_bar_on_cta_pages": true,
    "additional_form_tracking_pages": [
      "https://start.example.com/*",
      "https://signup.example.com/trial"
    ],
    "thank_you_pages": [
      "*/thank-you*",
      "*/confirmation*"
    ]
  }
}

Code location: frontend/shared/src/config/SiteConfigManager.ts (shouldHideWidgetOnCTAPage, shouldEnableFormTracking)


Layer 7: Copilot Mode

Configuration: global_client_config.features.copilot_mode (boolean)

Value Behavior
false (default) Widget always uses default mode (search bar, centered positioning)
true Widget uses copilot mode on CTA/form tracking pages

What is Copilot Mode?

Copilot mode is an alternative widget form factor designed to assist users in completing forms on CTA destination pages. Instead of hiding the widget on these pages, copilot mode provides:

  • Minimized view: Small button in the bottom-right corner (instead of bottom-center search bar)
  • Expanded view: Right-side panel (instead of centered chat popup)
  • Welcome message: Initial assistant message with suggested questions related to the current page
  • Form assistance: Helps users understand form fields and complete their tasks

When is Copilot Mode Used?

Copilot mode is activated when ALL of the following conditions are true:

  1. copilot_mode feature flag is enabled in global_client_config.features
  2. Current device is NOT mobile (copilot is never used on mobile devices)
  3. Current page is a form tracking page (CTA destination, additional form tracking page, or thank-you page)

Mobile Restriction

Copilot mode is never enabled on mobile devices. On mobile, the widget always uses the default form factor regardless of the copilot_mode feature flag setting. This ensures optimal user experience on smaller screens.

Configuration Example

To enable copilot mode:

  1. Set the global feature flag in global_client_config table:

    {
      "features": {
        "copilot_mode": true
      }
    }
    

  2. Configure CTA pages in the site's site_configs:

    {
      "ctas": [
        {
          "cta_id": "demo",
          "langs": {
            "en": { "url": "https://example.com/book-demo", "text": "Book a demo" }
          }
        }
      ]
    }
    

Optionally, add additional form tracking pages in custom_config:

{
  "custom_config": {
    "additional_form_tracking_pages": [
      "https://signup.example.com/*"
    ]
  }
}

Code location: frontend/shared/src/config/SiteConfigManager.ts (isCopilotModeEnabled, shouldUseCopilotMode)

Mode Transition: Copilot to Default

When a user navigates from a copilot mode page (CTA page) to a default mode page (non-CTA page), the widget handles the transition gracefully:

Scenario Behavior
User interacted with copilot (sent a message) Conversation persists, widget shows in default mode with history
User only saw welcome message (no interaction) Conversation is cleared, widget shows in collapsed state

Why clear the welcome message?

The copilot welcome message ("How can I help you?") is a synthetic message designed to provide context-specific assistance on form pages. If the user navigates away without interacting, displaying this message on a non-CTA page creates poor UX because:

  1. The follow-up questions are related to the form context they left
  2. The welcome message appears without the user having initiated a conversation
  3. It suggests an ongoing conversation that never actually started

Technical implementation:

When the widget initializes in default mode, it checks if the conversation contains only the copilot welcome message (identified by COPILOT_WELCOME_MARKER). If so, the conversation is cleared and the widget resets to its initial collapsed state.

Code location: frontend/shared/src/components/RoseWidget.tsx (useEffect hook for clearing copilot-only conversation)


Key Functions Reference

Function Location Purpose
canInitializeWidget() widget/src/lib/widgetInit/initializationCheck.ts Orchestrates all initialization checks
checkTrafficAllocation() widget/src/lib/widgetInit/trafficControl.ts Deterministic traffic bucketing
isCurrentURLAllowed() shared/src/context/SiteConfigContext.tsx Checks URL whitelist (display_only_on_urls)
isUrlAllowed() shared/src/utils/domain/siteRestrictions.ts Checks URL against exclusion patterns
isCurrentDeviceAllowed() shared/src/utils/domain/siteRestrictions.ts Checks mobile device rules
shouldHideWidgetOnCTAPage() shared/src/config/SiteConfigManager.ts Checks if page is CTA with hide config enabled
shouldEnableFormTracking() shared/src/config/SiteConfigManager.ts Determines if page needs form tracking
isCurrentPageDemoURL() shared/src/config/SiteConfigManager.ts Checks if page is a CTA destination
isCopilotModeEnabled() shared/src/config/SiteConfigManager.ts Checks global copilot_mode feature flag
shouldUseCopilotMode() shared/src/config/SiteConfigManager.ts Determines if copilot form factor should be used

Debugging Display Issues

1. Check Browser Console

Enable debug logging to see the decision flow. The loader's debug option only enables loader logs. To see widget display-decision logs, enable debug mode after the widget loads:

// Option 1: Enable after widget is ready
window.InboundXWidget.onReady(() => {
    window.InboundXWidget.enableDebug();
});

// Option 2: Check status and enable manually
window.InboundXWidget.getStatus();  // See current state
await window.InboundXWidget.enableDebug();  // Enable debug logs

Look for these log patterns: - 🎯 shouldDisplayOnCurrentUrl - URL pattern evaluation - 🚦 Traffic control - Traffic allocation check - 📱 Mobile device detected - Device type detection

2. Verify Configuration

Check the site configuration in Supabase:

SELECT
  domain,
  traffic_allocated,
  display_only_on_urls,
  custom_config->'exclude_url_patterns' as exclude_patterns,
  custom_config->'enable_mobile' as enable_mobile,
  custom_config->'hide_search_bar_on_cta_pages' as hide_on_cta
FROM site_configs
WHERE domain = 'example.com';

3. Common Issues

Symptom Likely Cause Check
Widget never shows traffic_allocated = 0 Site config in Supabase
Widget missing on some pages URL exclusion pattern exclude_url_patterns config
Widget missing on mobile Mobile not enabled enable_mobile in custom_config
Widget missing on signup page CTA page hiding hide_search_bar_on_cta_pages
Widget shows inconsistently Traffic bucketing traffic_allocated < 100

4. Force Display for Testing

To bypass all restrictions temporarily:

InboundXLoader.init({
  forceDisplay: true,
  // ... other config
});