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.compoints 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