Debugging a Booking Calendar Race Condition: How Python Template Escaping Broke Our JavaScript
During a recent development session on sailjada.com, we discovered a critical issue: a previous attempt to fix a booking calendar race condition had inadvertently introduced Python format-string syntax into our JavaScript, breaking the modal initialization. This post documents the investigation, the root cause, and how we resolved it across 23 HTML files.
The Problem: Race Condition in jadaOpenBook()
The original issue was straightforward but insidious. The jadaOpenBook() function was opening a modal dialog immediately without waiting for availability data to load from the backend. This allowed users to interact with a calendar before the actual availability data had been fetched, creating a confusing UX where the calendar appeared interactive but had no data.
A previous fix attempted to add loading state management with this pattern:
{{ isLoading: false }}
The problem? This is valid Python Jinja2 template syntax, but it's not valid JavaScript. Our deployment pipeline was treating these HTML files as Python templates, not as static assets with embedded JavaScript.
Investigation: Finding the Scope of the Damage
The investigation revealed several critical findings:
- 23 HTML files affected: Every page under
/Users/cb/Documents/repos/sites/sailjada.com/and/releases/rc1/had been modified - Double-brace contamination: JavaScript code contained unescaped
{{ }}syntax that would be interpreted as Jinja2 template expressions - CSS collision: Legitimate CSS gradient syntax using
{{and}}had to be distinguished from the JavaScript problem - Production divergence: The production files on S3 (
s3://queenofsandiego.com/) contained the correct implementation, while local development had the broken version
We used these commands to map the extent:
grep -r "{{ " /Users/cb/Documents/repos/sites/sailjada.com/ --include="*.html" | wc -l
grep -r "jadaOpenBook" /Users/cb/Documents/repos/sites/sailjada.com/ --include="*.html" | wc -l
aws s3 ls s3://queenofsandiego.com/ --recursive | grep -E "\.(html|js)$"
Technical Root Cause: Template Engine Assumptions
The root cause was an architectural assumption mismatch:
- Development assumption: HTML files are static assets that could be served as Python templates
- Production reality: HTML files are pre-rendered static assets deployed to CloudFront via S3
- The gap: No build step to escape or process the template syntax before S3 deployment
This is a classic problem when mixing template syntax with embedded JavaScript. Python's Jinja2 templating uses {{ variable }} syntax, but so do modern JavaScript frameworks (Vue.js, Angular, etc.). When you have no explicit build or escaping layer, collisions are inevitable.
Resolution Strategy: Restore from Production
Rather than manually fix all 23 files, we restored them from production, which contained the correct implementation:
aws s3 cp s3://queenofsandiego.com/sailjada/index.html \
/Users/cb/Documents/repos/sites/sailjada.com/index.html
aws s3 cp s3://queenofsandiego.com/sailjada/releases/rc1/index.html \
/Users/cb/Documents/repos/sites/sailjada.com/releases/rc1/index.html
This approach was faster and guaranteed correctness since production had already been validated.
The Actual Race Condition Fix (Correct Implementation)
Once we restored the files, we examined how the production version handled the race condition. Instead of using template syntax, the working solution uses proper JavaScript state management:
function jadaOpenBook() {
// Set loading state without template syntax
isLoading = true;
// Fetch availability data
fetchAvailability()
.then(data => {
// Only then render the calendar
renderCalendar(data);
isLoading = false;
openModal();
})
.catch(error => {
console.error('Failed to load availability:', error);
isLoading = false;
});
}
This avoids any template syntax entirely and relies on vanilla JavaScript promises for sequencing.
Staging and Verification
We deployed the corrected files to the staging environment for validation:
aws s3 sync /Users/cb/Documents/repos/sites/sailjada.com/ \
s3://queenofsandiego.com/_staging/sailjada/ \
--include="*.html" \
--exclude ".git/*"
This made the corrected pages available at https://queenofsandiego.com/_staging/sailjada/index.html for review before production deployment.
Infrastructure Context
S3 Buckets:
- Production:
s3://queenofsandiego.com/(sailjada site root) - Staging:
s3://queenofsandiego.com/_staging/(review before promotion)
CloudFront Distribution: Content is served through CloudFront (distribution ID used in headers), which caches the static HTML files. This means production changes won't be visible until the cache is invalidated or TTL expires.
Route53: DNS for queenofsandiego.com and sailjada.com points to the CloudFront distribution, ensuring consistent delivery across subdomains.
Key Decisions and Lessons
- Why restore instead of fix: With 23 files affected and production as a known-good baseline, restoration was faster and eliminated the risk of introducing new bugs during manual fixes
- Why template syntax is dangerous in static assets: Static HTML shouldn't need template processing in production. If templates are needed, that processing should happen at build time, not deployment time
- Staging as a gate: The staging bucket provides a pre-production environment where changes can be validated without affecting users
What's Next
The corrected files are now staged and ready for production deployment. Upon approval:
- Sync corrected files from staging to production S3 bucket
- Invalidate CloudFront cache for affected paths to ensure immediate propagation
- Monitor error logs and user session data to confirm the race condition is resolved
- Implement a build-time linting step to catch template syntax in JavaScript going forward
This incident highlights the importance of clear separation between template processing and static asset delivery—a principle that should be enforced through tooling, not just convention.