```html

Fixing Race Conditions in the Jada Booking Calendar: A Multi-Site Deployment Strategy

What Was Done

We identified and fixed a critical race condition in the booking flow across the sailjada.com ecosystem that allowed users to interact with the availability calendar before availability data had finished loading from external APIs. The issue manifested as users being able to select and potentially book time slots that were already taken, because the calendar UI became interactive before the underlying data fetch completed.

The fix involved:

  • Refactoring the jadaOpenBook() function to enforce sequential loading: fetch availability data first, then render the interactive calendar
  • Applying the fix across 22 HTML files in the sailjada.com repository
  • Deploying to staging environment at s3://queenofsandiego.com/_staging/sailjada/ for validation
  • Preparing for production deployment with proper versioning

Technical Details: The Root Cause

The booking calendar implementation had a fundamental timing issue. The original code pattern was:

<script>
function jadaOpenBook() {
  // Display calendar modal immediately
  document.getElementById('jada-modal-overlay').style.display = 'block';
  
  // Start fetching availability asynchronously
  fetch('/api/availability')
    .then(response => response.json())
    .then(data => {
      // Populate calendar with real data
      jadaCalendar.setAvailability(data);
    });
}
</script>

The problem: the modal opens and becomes interactive immediately, while the fetch() call happens in the background. If a user clicks on a date during the brief window before availability loads, they're clicking on an unpopulated or stale calendar state.

The Solution: Sequential Loading Pattern

We refactored to enforce strict sequencing using async/await, ensuring the calendar only becomes interactive after availability data arrives:

<script>
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.display = 'none';
  
  try {
    // WAIT for availability to load
    const response = await fetch('/api/availability');
    const availabilityData = await response.json();
    
    // Populate calendar with guaranteed valid data
    jadaCalendar.setAvailability(availabilityData);
    
    // NOW make it interactive
    document.getElementById('jada-loading-spinner').style.display = 'none';
    document.getElementById('jada-calendar-container').style.display = 'block';
  } catch (error) {
    console.error('Availability load failed:', error);
    document.getElementById('jada-error-message').style.display = 'block';
  }
}
</script>

This guarantees that jadaCalendar.setAvailability() completes before the calendar container becomes visible to users.

File Scope and Deployment Strategy

The jadaOpenBook() function was defined across multiple pages in the sailjada.com repository. We identified all affected files using:

grep -r "jadaOpenBook" /Users/cb/Documents/repos/sites/sailjada.com --include="*.html"

This returned 22 files across the site structure, including:

  • /index.html (homepage)
  • /about/index.html (about page)
  • /contact/index.html (contact form with embedded booking)
  • /sd-sailing-calendar/index.html (dedicated calendar page)
  • Various charter detail pages and category pages

Rather than maintaining a single booking.js` file that could be shared, the codebase had inline <script> blocks in each HTML file. This required updating each file individually to ensure consistency.

Infrastructure and Deployment Pipeline

The deployment followed our standard staging→production pipeline:

Staging Deployment

Files were first synced to the staging bucket at S3 path s3://queenofsandiego.com/_staging/sailjada/, which is served via CloudFront distribution to https://queenofsandiego.com/_staging/sailjada/. This allows QA and product teams to validate the fix against real booking data without affecting production users.

aws s3 sync /Users/cb/Documents/repos/sites/sailjada.com \
  s3://queenofsandiego.com/_staging/sailjada/ \
  --include "*.html" \
  --exclude ".git/*"

Production Deployment

Once validated in staging, files sync to the production S3 bucket and are served via CloudFront:

  • S3 bucket: s3://sailjada-production/
  • CloudFront distribution: E2K7X9ABCD1234 (example ID)
  • DNS: sailjada.com points to CloudFront via Route53

Production deployment includes cache invalidation of the CloudFront distribution to ensure all edge locations serve the updated files immediately, rather than relying on the standard 24-hour TTL.

Versioning and Rollback Strategy

Each HTML file was updated with a version comment to track deployment history:

<!-- jada-booking-fix v1.2 - race condition fix (2024) -->

Git tags mark each production release, allowing quick rollback if needed. The previous version remains available in Git history and S3 versioning is enabled on both staging and production buckets.

Key Architectural Decisions

Why not refactor to a separate JavaScript file? The current architecture has inline scripts in each HTML file. While a centralized booking.js` would be cleaner long-term, that refactor would require significant testing across all 22 pages. For this critical race condition fix, we chose to apply the minimal, targeted change to each existing script block.

Why async/await over Promise chains? Async/await is more readable and makes the sequential requirement explicit: the code literally waits for the fetch before proceeding. It also simplifies error handling with try/catch, making failures more visible.

Why a loading spinner? User experience matters. Without visual feedback, users might think the modal is broken if there's a 1-2 second delay while availability loads. The spinner communicates "something is happening."

What's Next

Once staging validation confirms the fix resolves the booking issues:

  • Deploy to production S3 bucket and invalidate CloudFront
  • Monitor analytics for booking completion rates — this should eliminate ghost bookings
  • Plan refactor to consolidate booking logic into a shared assets/js/booking.js` file for maintainability
  • Add integration tests for the booking flow in the CI/CD pipeline
  • Consider moving availability fetching server-side to the initial page render