Skip to content

Frontend Overview

The frontend uses a modular architecture with shared components across multiple deployment targets.

Architecture

frontend/
├── shared/            # Shared package source (@inboundx/shared/* public entrypoints)
├── widget/            # Standalone JavaScript widget for websites
├── preprod-ui/        # Pre-production testing interface
├── chrome-plugin/     # Chrome extension
├── client-backoffice/ # Client configuration dashboard
├── e2e/               # End-to-end tests (Playwright)
└── justfile           # Build and development commands

Packages

shared - @inboundx/shared/*

Shared TypeScript package with scoped public entrypoints:

Entrypoint Description
@inboundx/shared/widget Reusable widget runtime (RoseWidget, DisplayController, widget providers/hooks, widget runtime utilities)
@inboundx/shared/config Config providers, resolvers, generated config contracts, deployment constants, and site-config helpers
@inboundx/shared/analytics Public analytics API, event types/constants, analytics provider/context, and parent-window event types
@inboundx/shared/platform Logger, adapters, Sentry/Supabase wrappers, services, retry/masking, and domain/web utilities
@inboundx/shared/ui App-agnostic UI primitives/renderers and CSS/color helpers
@inboundx/shared/types Generated Supabase types and shared domain/type contracts
@inboundx/shared/styles/* Published CSS assets
@inboundx/shared/test-support/* Shared test setup modules

Shared Package Boundary Model

The root @inboundx/shared import is intentionally not public. Frontend apps consume reusable code through scoped entrypoints so source files under frontend/shared/src can change without becoming accidental API.

Allowed examples:

import { logger } from '@inboundx/shared/platform';
import { DisplayController } from '@inboundx/shared/widget';
import type { Database } from '@inboundx/shared/types';

Forbidden app-level examples:

import { logger } from '@shared/utils/logger';
import { DisplayController } from '../../../widget/src/components/DisplayController';
import type { Database } from '@inboundx/shared/src/types/database.generated';

@/ always means the current package's local src directory. cd frontend && just lint includes the import-boundary checker that blocks new shared-internal and widget-source imports from app packages.

widget

Standalone JavaScript widget for website integration:

  • Output: inboundx-widget.js - UMD bundle optimized for embedding
  • Usage: Can be embedded in any website with simple script tag
  • Adapters: Uses WebStorageAdapter and FetchNetworkAdapter

preprod-ui

Pre-production testing interface built with Vite:

  • Purpose: Test widget with different client configurations
  • Features: Client selector, environment switching, live preview
  • Tech: Vite + React with hot reloading

chrome-plugin

Chrome extension using shared components:

  • Adapters: ChromeStorageAdapter and ChromeNetworkAdapter
  • Background Script: Handles webhook calls and CORS bypass
  • Content Script: Injects chat widget into web pages

client-backoffice

Client configuration dashboard deployed to Cloudflare Pages:

  • Purpose: Client management and configuration
  • Deployment: Cloudflare Pages with branch-based environments

Adapter Pattern

The architecture uses adapters to handle different environments:

Storage Adapters

Adapter Use Case Backend
WebStorageAdapter Web widgets, preprod-ui localStorage
ChromeStorageAdapter Chrome extension chrome.storage.local
ConfigurableStorageAdapter Testing Dynamic selection

Network Adapters

Adapter Use Case Method
FetchNetworkAdapter Web widgets, preprod-ui Direct fetch() API
ChromeNetworkAdapter Chrome extension chrome.runtime.sendMessage()

Benefits

  • Environment Abstraction: Same code works across deployment targets
  • CORS Handling: Chrome extension adapter bypasses CORS via background script
  • Storage Flexibility: Consistent API regardless of storage mechanism
  • Testing Support: Configurable adapters for comprehensive testing

Communication Architecture

Chrome Extension Message Flow

Content Script → Background Script → External API → Background Script → Content Script

Key Components:

  1. ChromeNetworkAdapter: Sends messages with tracking IDs
  2. Background Script: Makes HTTP requests (bypasses CORS)
  3. Message Types: toggleWidget, checkSiteSupport, callWebhook

Web Widget Communication

Widget Component → FetchNetworkAdapter → External API → Widget Component

Direct HTTP communication without message passing overhead.

Key Features

Widget Initialization

The widget follows a provider chain pattern:

  1. SiteConfigProvider - Initializes Supabase, loads site configuration, creates InboundXService
  2. AnalyticsProvider - Initializes PostHog with config data
  3. DisplayController - Waits for analytics ready, checks traffic allocation, evaluates display rules
  4. RoseWidget - Renders appropriate view based on state (collapsed/expanded/minimized)

Each layer must complete before the next begins. If any layer fails, the widget fails closed (doesn't render).

LocalStorage Structure

All data consolidated under single "rose" key (managed by RoseStorageManager):

localStorage.rose = {
  version: 1,
  widget: {
    settings: {
      logLevel: string | null,
      namedLogLevels: Record<string, string>
    },
    analytics: {
      counters: { widgetImpressions, messagesSent, ctaClicks, formsSubmitted, loginClicks },
      lastSessionId: string | null,
      lastActiveSessionId: string | null,
      lastActiveSessionDate: number | null,
      clientId: string | null,        // Stable client ID for traffic control bucketing
      apiVersion: string | null,
      frontendVersion: string | null,
      offlineQueue: { evaluations: [], conversations: [] }
    },
    trafficControl: {                 // Traffic allocation decision cache
      clientId: string,
      trafficAllocated: number,
      enabled: boolean,
      bucket: number,                 // 0-99 deterministic bucket
      timestamp: number
    } | null
  },
  preprod?: { config, endpoint, client, iframe }  // Only in preprod-ui
}

Additional keys used:

Key Pattern Purpose
inboundx_chat_state_${siteName} Per-domain conversation state (messages, expand/minimize)
inboundx_session_${apiUrl} Session ID per API endpoint (sessionStorage)

Form Detection & Tracking

Automatic form detection for conversion tracking:

  • SuperformStrategy: Webflow Superform multi-step forms
  • CTAPageFormStrategy: Standard forms on CTA pages
  • Cross-Subdomain Attribution: Cookie fallback for attribution

Build Outputs

Package Output Purpose
shared/dist/ TypeScript declarations, JS, CSS Shared library
widget/dist/ UMD bundle Website embedding
preprod-ui/dist/ Static files Firebase hosting
chrome-plugin/dist/ Extension files Chrome Web Store
client-backoffice/dist/ Static files Cloudflare Pages

Quick Commands

cd frontend

# Setup (run once per worktree)
just download-env all     # Download environment files
just install              # Install all dependencies

# Development
just dev                  # Build shared + start preprod UI (recommended)
just dev-shared           # Watch shared package only
just dev-preprod          # Pre-production UI only
just dev-client-backoffice # Client backoffice

# Building
just build                # Build all packages
just build-chrome-plugin  # Build Chrome extension

# Testing
just test                 # Run tests
just type-check           # Type check all projects

Further Reading