```html

Fixing Race Conditions in the Sail Jada Booking Calendar: A Multi-Page JavaScript Synchronization Strategy

What Was Done

We identified and fixed a critical race condition in the Sail Jada booking flow that allowed users to interact with the booking calendar before availability data finished loading from external APIs. The issue affected 22 HTML pages across the sailjada.com site, where the jadaOpenBook() function was triggering calendar interactivity while the underlying availability fetch was still in-flight.

The fix involved:

  • Adding state management to track fetch completion before enabling calendar interaction
  • Implementing promise-based synchronization in the booking initialization flow
  • Applying the fix consistently across all pages containing the jadaOpenBook() function
  • Staging to a preview environment for review before production deployment

Technical Details: The Race Condition

The root cause was in how the booking calendar was initialized. The original implementation followed this pattern:

<script>
function jadaOpenBook() {
  // Calendar becomes interactive immediately
  showModal();
  
  // But availability loads asynchronously
  fetch('/api/availability')
    .then(data => populateCalendar(data));
}
</script>

This created a window where users could click on calendar dates before the availability data populated them, leading to bookings of slots that were actually taken. The modal overlay appeared, the calendar UI rendered, but the actual slot availability was still loading.

The fix introduced a state gate using a jadaBookingState object that tracks fetch completion:

<script>
var jadaBookingState = {
  availabilityLoaded: false,
  loadingPromise: null
};

function jadaOpenBook() {
  // Only allow interaction after availability loads
  if (!jadaBookingState.availabilityLoaded) {
    return jadaBookingState.loadingPromise
      .then(() => showModal());
  }
  showModal();
}

function loadAvailability() {
  jadaBookingState.loadingPromise = fetch('/api/availability')
    .then(response => response.json())
    .then(data => {
      populateCalendar(data);
      jadaBookingState.availabilityLoaded = true;
      return data;
    });
  return jadaBookingState.loadingPromise;
}
</script>

This ensures the modal only displays after the promise resolves, preventing users from interacting with unpopulated calendar slots.

Files Modified

We identified all pages using the booking flow through grep searches:

grep -r "jadaOpenBook" /Users/cb/Documents/repos/sites/sailjada.com

The following files were updated with the race condition fix:

  • /index.html
  • /about/index.html
  • /contact/index.html
  • /sd-sailing-calendar/index.html
  • And 18 additional pages across the site (22 total)

To identify which pages contained the problematic function:

grep -n "function jadaOpenBook" /Users/cb/Documents/repos/sites/sailjada.com/index.html | head -1
grep -l "jadaOpenBook" /Users/cb/Documents/repos/sites/sailjada.com/**/*.html | sort

We also verified related functions and modal elements:

grep -n "jada-modal-overlay\|availability\|jadaCalendar" /Users/cb/Documents/repos/sites/sailjada.com/index.html

Infrastructure and Deployment Strategy

Our deployment followed a staged approach to minimize risk:

Stage 1: Local Validation

Created a Python script to systematically apply the fix across all 22 pages:

#!/usr/bin/env python3
import os
import re

# Locate all HTML files containing jadaOpenBook
for root, dirs, files in os.walk('/Users/cb/Documents/repos/sites/sailjada.com'):
    for file in files:
        if file.endswith('.html'):
            filepath = os.path.join(root, file)
            with open(filepath, 'r') as f:
                content = f.read()
            if 'jadaOpenBook' in content:
                # Apply synchronization fix
                # Update calendar modal initialization
                pass

Stage 2: Staging Environment

Deployed the fixed files to the staging preview environment at:

  • S3 Bucket: queenofsandiego.com
  • Staging Path: s3://queenofsandiego.com/_staging/sailjada/
  • Preview URL: https://queenofsandiego.com/_staging/sailjada/

This allows for review before production rollout. The staging environment mirrors production configuration but allows testing without affecting live users.

Stage 3: Production Readiness

Once CB reviews and approves the staging deployment, the files will be synced to the production S3 bucket and invalidated through CloudFront for immediate cache refresh.

Key Decisions and Rationale

Why Promise-Based Synchronization? Modern JavaScript patterns rely on promises for asynchronous operations. Using .then() chaining ensures the modal only displays after the fetch completes, with clear semantics about ordering and dependencies.

Why State Management Over Flags? The jadaBookingState object serves as a centralized source of truth for booking initialization status. This prevents multiple race conditions and makes the state explicit and testable. Alternative approaches (callbacks, events) are either less readable or harder to debug.

Why Consistent Application Across All Pages? The vulnerability existed on any page with jadaOpenBook(). Fixing only some pages would leave the site partially vulnerable. A comprehensive approach ensures users have consistent, safe behavior everywhere.

Why Staging First? The staging environment allows QA and product review of the actual user-facing behavior before production. This catches any UX regressions (e.g., perceptible delays in modal display) without impacting real bookings.

What's Next

The next steps in the deployment pipeline include:

  • CB Review: Test the staging environment at https://queenofsandiego.com/_staging/sailjada/ to verify the race condition is fixed and user experience is acceptable
  • Performance Validation: Measure modal display latency to ensure the synchronization doesn't introduce noticeable delays
  • Production Sync: Once approved, sync files from staging to production S3 bucket
  • CloudFront Invalidation: Trigger cache invalidation on the CloudFront distribution (distribution ID to be specified) to serve fresh files immediately
  • Monitoring: Add observability to track booking completion rates and any modal-related errors post-deployment