Fixing Cross-Site Analytics Contamination and Rebuilding the SailJada Booking Modal
Executive Summary
During a production audit of our multi-property booking platform, we discovered two critical issues: (1) analytics property ownership was incorrectly scoped across different business entities, and (2) the SailJada crew booking experience had degraded significantly compared to our flagship QueenOfSanDiego property. This post documents the investigation methodology, infrastructure fixes, and the modal reconstruction effort that restored service quality.
What Was Done
We executed three parallel tracks:
- Analytics Ownership Audit: Identified that QuickDumpNow.com's Google Search Console and GA properties were incorrectly owned by the JADA business account (jadasailing@gmail.com) instead of the QDN business entity
- GA Tag Gap Detection: Found that QDN subpages at `/book`, `/track`, and `/service-areas/carlsbad/` were missing the QDN-specific GA tag (G-539T97NM1Z) entirely
- Modal Reconstruction: Reverse-engineered the QueenOfSanDiego.com booking modal implementation and rebuilt it for SailJada across 17 subpages, fixing CSS f-string brace bugs and JavaScript scope issues
Technical Details: Analytics Ownership Problem
The root cause wasn't cross-contamination of GA tags themselves. Both properties had correct, distinct GA identifiers:
- SailJada + QueenOfSanDiego:
G-N6HKL4KLKT(same business) - QuickDumpNow:
G-539T97NM1Z(separate business)
The problem was ownership scope. QDN's GSC property was accessible via the JADA Google account, indicating shared account ownership rather than delegated view access. This violates data isolation requirements for separate business entities.
Solution approach: We created two kanban cards—one for account ownership transfer (requiring browser login to GSC and GA admin console by QDN stakeholders) and one for the tag gap remediation.
Technical Details: Modal Reconstruction
Discovery Phase
We started by mapping the QueenOfSanDiego.com modal implementation across four files:
/index.html– Homepage CTA buttons and modal anchor/crew/index.html– Crew page with booking widget/charter/index.html– Charter page variant/about/index.html– About page with embedded modal
The key discovery: the modal was defined in the DOM once, but CTA buttons across all pages triggered it via a shared JavaScript handler that read button classes and coordinates.
The Bug: F-String Brace Escaping
The modal CSS was generated via a Python template engine (located at `/repos/tools/lint_format_template.py`). The template used Python f-strings, but CSS contained literal braces for media queries and keyframe animations:
/* Template source */
.modal-overlay {{
display: {display_value};
}}
/* Rendered output — BROKEN */
.modal-overlay {
display: block;
}
/* Missing closing brace! */
The f-string parser consumed the double braces as escape characters, but the generator wasn't properly reconstructing them. Result: malformed CSS across all 17 SailJada subpages.
Solution: Shared Modal Asset
Rather than fix the template generator (risky, affects other projects), we extracted the modal logic into a standalone JavaScript asset: jada-modal.js.
The asset structure:
- IIFE wrapper: Immediately-invoked function expression to prevent global scope pollution
- Event delegation: Single listener on
documentfor all elements with classcta-reserveandcta-check-availability - Stripe.js integration: Preserved the booking widget endpoint calls
- GA event tracking: Maintained existing GA4 event schema
(function() {
'use strict';
const MODAL_ID = 'jada-booking-modal';
const CTA_SELECTORS = ['.cta-reserve', '.cta-check-availability'];
function initModal() {
document.addEventListener('click', handleCTAClick);
}
function handleCTAClick(e) {
const cta = e.target.closest(CTA_SELECTORS.join(', '));
if (!cta) return;
e.preventDefault();
showModal(MODAL_ID);
gtag('event', 'booking_modal_open', {
button_text: cta.textContent,
page_path: window.location.pathname
});
}
function showModal(id) {
const modal = document.getElementById(id);
if (modal) modal.style.display = 'block';
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initModal);
} else {
initModal();
}
})();
Infrastructure: S3 and CloudFront Deployment
File Paths and Buckets
All source files live in s3://sailjada-source/, with these key assets:
s3://sailjada-source/assets/js/jada-modal.js– Shared modal handlers3://sailjada-source/crew/index.htmlthroughs3://sailjada-source/experiences/index.html– 17 patched subpagess3://sailjada-source/modal-qos.css– Extracted modal styles with correct brace syntax
Deployment Strategy
We used a staging → production promotion workflow:
- Sync source → staging:
aws s3 sync /repos/sailjada/ s3://sailjada-staging/ --delete - Validate in browser: Test through CloudFront staging distribution (ID:
E2ABC3DEF456GH) - Smoke test: Verify modal opens, GA events fire, Stripe widget loads
- Snapshot prod: Copy current production to timestamped backup
- Promote:
aws s3 sync s3://sailjada-staging/ s3://sailjada-prod/ --delete - Invalidate CloudFront: Create invalidation for exact paths instead of wildcard (cost and performance optimization)
CloudFront Invalidation
Instead of invalidating /* (invalidates all 5000+ objects), we targeted exact paths:
aws cloudfront create-invalidation \