Skip to content

Frontend Overview

The frontend is a TypeScript/React workspace with shared public entrypoints and five application or extension targets. The Astro marketing website lives in the top-level website/ directory because it is deployed separately from this workspace.

Architecture

frontend/
├── shared/            # Shared package source (@inboundx/shared/* public entrypoints)
├── widget/            # Standalone JavaScript widget for websites
├── playground/        # Pre-production testing interface
├── chrome-plugin/     # Chrome extension (injects Rose onto any site)
├── stats-opt-out/     # Extension to exclude own browsing from stats
├── 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

playground

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

stats-opt-out

Standalone WXT browser extension for Rose staff and developers:

  • Purpose: Marks local browsing as widget-test so internal traffic is excluded from production analytics.
  • Boundary: Intentionally has no shared-package or environment dependency.
  • Distribution: Built as its own browser-extension artifact.

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, playground localStorage
ChromeStorageAdapter Chrome extension chrome.storage.local
ConfigurableStorageAdapter Testing Dynamic selection

Network Adapters

Adapter Use Case Method
FetchNetworkAdapter Web widgets, playground 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 playground
}

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
playground/dist/ Static files Firebase hosting
chrome-plugin/dist/ Extension files Chrome Web Store
stats-opt-out/.output/ WXT extension bundles Internal browser installation
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 playground UI (recommended)
just dev-shared           # Watch shared package only
just dev-playground       # Playground 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 check                # Lint, type-check, and test all projects

Further Reading