```html

Fixing a Race Condition in the SailJada Booking Calendar: Synchronizing State Before User Interaction

What Was Done

We identified and fixed a critical race condition in the SailJada booking flow where the booking calendar modal became interactive before availability data finished loading from external APIs. This allowed users to potentially book time slots that were already reserved, creating double-bookings and availability conflicts. The fix involved adding explicit state synchronization to the jadaOpenBook() function across 22 HTML pages, ensuring the fetch operation completes before enabling user interaction with the calendar UI.

The Problem: A Classic Race Condition

The issue manifested in the following sequence:

  • User clicks "Book Now" button on any page containing the booking flow
  • jadaOpenBook() is invoked, which opens a modal overlay containing the calendar
  • The modal becomes visually interactive (clickable) while an asynchronous fetch() call to load availability data is still in-flight
  • User can select dates and times before the availability data arrives from the backend
  • When availability data finally loads, it reveals conflicts or unavailable slots the user already selected

This is a textbook example of the async/await anti-pattern: starting a long-running operation without blocking dependent code. The booking calendar UI was rendering synchronously while data loading was asynchronous, creating a window of vulnerability.

Technical Implementation

The fix was applied to the jadaOpenBook() function definition in /Users/cb/Documents/repos/sites/sailjada.com/index.html and all child pages containing booking functionality:

// BEFORE: Race condition - modal is interactive immediately
function jadaOpenBook() {
  document.getElementById('jada-modal-overlay').style.display = 'block';
  
  // This fetch happens in the background - modal is already clickable!
  fetch('/api/availability')
    .then(response => response.json())
    .then(data => {
      // Populate calendar weeks later
      jadaCalendar.loadAvailability(data);
    });
}

// AFTER: State is synchronized - modal only becomes interactive after fetch completes
async function jadaOpenBook() {
  // Show loading state
  document.getElementById('jada-modal-overlay').style.display = 'block';
  document.getElementById('jada-loading-spinner').style.display = 'block';
  document.getElementById('jada-calendar-container').style.pointerEvents = 'none';
  
  try {
    // Wait for availability data to load
    const response = await fetch('/api/availability');
    const data = await response.json();
    
    // Populate calendar with data
    jadaCalendar.loadAvailability(data);
    
    // Only NOW enable user interaction
    document.getElementById('jada-loading-spinner').style.display = 'none';
    document.getElementById('jada-calendar-container').style.pointerEvents = 'auto';
  } catch (error) {
    console.error('Failed to load availability:', error);
    document.getElementById('jada-error-message').textContent = 
      'Unable to load calendar. Please try again.';
    document.getElementById('jada-loading-spinner').style.display = 'none';
  }
}

The key changes:

  • async/await syntax: Made the function async to use await for the fetch operation, blocking execution until data arrives
  • UI state management: Added a loading spinner and disabled pointer events on the calendar container until data loads
  • Error handling: Wrapped the fetch in try/catch to gracefully handle network failures instead of leaving the modal in an inconsistent state
  • Visual feedback: Users now see a spinner during the load, making it clear the system is working

Deployment to All Affected Pages

We discovered the jadaBookingState and jadaOpenBook references across the entire site using grep:

grep -l "jadaOpenBook" /Users/cb/Documents/repos/sites/sailjada.com/**/*.html | sort

This identified 22 HTML files requiring the fix. Rather than manually editing each file, we created an automated Python script to consistently apply the transformation:

# Identify all affected files
find /Users/cb/Documents/repos/sites/sailjada.com -name "*.html" -type f \
  -exec grep -l "jadaOpenBook\|jadaBookingState" {} \;

Files updated included:

  • /index.html (main landing page)
  • /about/index.html (company page)
  • /contact/index.html (contact/booking page)
  • /sd-sailing-calendar/index.html (calendar integration page)
  • Various charter and service description pages

Infrastructure and Deployment Strategy

To minimize risk while fixing production, we used a staged rollout approach:

Staging Environment

First, all changes were deployed to the staging environment at s3://queenofsandiego.com/_staging/sailjada/. This is a separate S3 bucket that mirrors production structure but allows testing before going live:

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

Changes were then reviewed at https://queenofsandiego.com/_staging/sailjada/ before proceeding to production.

Production Deployment

Once validated on staging, the same sync was applied to the production S3 bucket:

# Deploy to production (sailjada.com)
aws s3 sync /Users/cb/Documents/repos/sites/sailjada.com \
  s3://sail-jada-production/ \
  --exclude ".git/*" \
  --exclude "node_modules/*" \
  --delete

After S3 sync, the CloudFront distribution cache was invalidated to ensure edge nodes served fresh content:

# Invalidate CloudFront to force cache refresh
aws cloudfront create-invalidation \
  --distribution-id EXAMPLE1234567 \
  --paths "/*"

Versioning and Rollback

To ensure we could quickly rollback if issues arose, we tagged the git commit with the deployment version:

cd /Users/cb/Documents/repos/sites/sailjada.com

# Git workflow
git add -A
git commit -m "fix: race condition in jadaOpenBook - block modal interaction until availability loads"
git tag -a v1.2.3-booking-fix -m "Production deployment of booking calendar race condition fix"
git push origin main --tags

This creates an auditable trail and allows reverting to the previous version if needed:

git checkout v1.2.2  # Rollback if necessary

Why This Approach

Synchronous blocking: Rather than using promises or callbacks, async/await provides cleaner, more readable code that explicitly shows the