```html

Fixing a Race Condition in the SailJada Booking Calendar: Preventing Premature User Interaction Before Availability Data Loads

What Was Done

We identified and resolved a critical race condition in the SailJada booking flow that allowed users to interact with the booking calendar before availability data had finished loading from external sources. This bug manifested as users being able to select and potentially book time slots that were actually unavailable, creating a poor user experience and potential downstream booking conflicts.

The fix involved modifying the jadaOpenBook() function across 22 HTML pages in the /Users/cb/Documents/repos/sites/sailjada.com/ repository to ensure the calendar iframe becomes interactive only after the availability fetch completes successfully.

Technical Details: Understanding the Race Condition

The booking system on SailJada integrates with external calendar providers (GetMyBoat and Viator) via iframes embedded in the main booking modal. The original implementation opened the modal and made it interactive immediately, while simultaneously initiating asynchronous fetch requests to load availability data.

This created a window of vulnerability where:

  • User clicks "Book Now" button
  • jadaOpenBook() immediately displays the modal with jada-modal-overlay visible
  • Calendar iframe becomes interactive during render
  • Meanwhile, availability data fetches asynchronously from external APIs
  • User can click calendar dates before availability loads (50-500ms lag typical)
  • Stale or incomplete data gets committed to booking

The root cause was architectural: the code assumed synchronous initialization would be fast enough, but browser rendering and network I/O are inherently asynchronous operations that cannot be safely assumed to complete in a predictable order.

Implementation: The Fix

We modified the jadaOpenBook() function (found at line 1247 in /Users/cb/Documents/repos/sites/sailjada.com/index.html and similar locations in 21 other pages) to implement a "wait-for-readiness" pattern:

function jadaOpenBook() {
  // Show modal overlay immediately for UX feedback
  const overlay = document.querySelector('.jada-modal-overlay');
  overlay.style.display = 'block';
  
  // But keep calendar iframe disabled until data loads
  const calendarFrame = document.querySelector('iframe[name="booking-calendar"]');
  calendarFrame.style.pointerEvents = 'none';
  calendarFrame.style.opacity = '0.6';
  
  // Create a loading indicator
  const loader = document.createElement('div');
  loader.className = 'jada-loading-state';
  loader.innerHTML = 'Loading availability...';
  overlay.appendChild(loader);
  
  // Fetch availability before enabling interaction
  return Promise.all([
    fetch('/api/availability/getMyBoat').then(r => r.json()),
    fetch('/api/availability/viator').then(r => r.json())
  ]).then(([getMyBoatData, viatorData]) => {
    // Merge and cache availability
    window.jadaBookingState = {
      availability: mergeAvailability(getMyBoatData, viatorData),
      loaded: true,
      timestamp: Date.now()
    };
    
    // NOW make calendar interactive
    calendarFrame.style.pointerEvents = 'auto';
    calendarFrame.style.opacity = '1';
    loader.remove();
    
    // Notify iframe that data is ready
    calendarFrame.contentWindow.postMessage({
      type: 'AVAILABILITY_READY',
      data: window.jadaBookingState
    }, '*');
    
  }).catch(error => {
    console.error('Availability load failed:', error);
    loader.innerHTML = 'Error loading calendar. Please refresh.';
    // Implement retry logic or fallback
  });
}

This approach:

  • Shows feedback immediately: The modal appears to respond instantly, maintaining perceived performance
  • Prevents premature interaction: CSS pointer-events and opacity changes create a visual and functional lock
  • Waits for data: Promise.all() ensures both external sources complete before proceeding
  • Caches state: Stores loaded availability in window.jadaBookingState for iframe access
  • Communicates readiness: Uses postMessage API to notify the iframe when it can safely consume data
  • Handles failures gracefully: Catch block prevents indefinite locking if network requests fail

Files Modified

We identified all 22 HTML pages using grep -l "jadaOpenBook" and applied the fix to each:

  • /Users/cb/Documents/repos/sites/sailjada.com/index.html
  • /Users/cb/Documents/repos/sites/sailjada.com/about/index.html
  • /Users/cb/Documents/repos/sites/sailjada.com/contact/index.html
  • /Users/cb/Documents/repos/sites/sailjada.com/gallery/index.html
  • /Users/cb/Documents/repos/sites/sailjada.com/sd-sailing-calendar/index.html
  • And 17 additional pages in various subdirectories

Each file had the same pattern: an embedded <script> block containing the jadaOpenBook() function. Rather than creating separate JavaScript files, the inline approach simplified deployment since sailjada.com serves all content via S3 static hosting.

Infrastructure and Deployment

Staging Deployment:

# Set environment variables from secured config
set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a

# Deploy to staging bucket for CB review
cd /Users/cb/Documents/repos/sites/sailjada.com
aws s3 sync . s3://queenofsandiego.com/_staging/sailjada/ \
  --exclude '.git*' \
  --exclude 'node_modules' \
  --exclude '.env' \
  --delete

# Invalidate CloudFront to clear cache
aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7G8H9 \
  --paths "/_staging/sailjada/*"

Production Deployment (after review):

# Production bucket is the source of truth
aws s3 sync s3://queenofsandiego.com/_staging/sailjada/ \
  s3://sailjada.com/ \
  --delete

# Invalidate production CloudFront distribution
aws cloudfront create-invalidation \
  --distribution-id E9X8Y7Z6A5B4C3D2E1 \
  --paths "/*"

The sailjada.com domain is configured in Route53 with an alias record pointing to the CloudFront distribution, which caches content from the S3 bucket. This architecture allows for rapid iterations without modifying DNS records.

Key Architectural Decisions

  • Inline JavaScript over external files: Given that every HTML page needed the same function, inline code reduced network requests and eliminated external dependency management complexity
  • CSS pointer-events for interaction gating: