```html

Multi-Property GA Tag Audit and Modal Widget Refactor: From Cross-Contamination to Production Deployment

Problem Statement

During a routine Google Search Console audit, we discovered that quickdumpnow.com (QDN) was being managed through a Google account that also owns sailjada.com and queenofsandiego.com (JADA properties). While the GA4 properties themselves were correctly segregated by tag ID, the underlying ownership and account access created a compliance risk. Additionally, a critical bug in the booking modal had rendered the entire crew booking experience broken across 17 sailjada.com subpages in production.

What Was Done

Phase 1: GA Tag Audit and Cross-Contamination Detection

We ran a comprehensive audit across all four properties to detect both actual tag cross-pollution and ownership misalignment. Using Python to scan S3 buckets and CloudFront distributions, we verified:

  • Homepage GA tags: Each property had the correct tag ID (JADA: G-N6HKL4KLKT, QDN: G-539T97NM1Z, DC: G-GGP987FJSW, 86from: G-YJWFKWVWNQ)
  • Subpage coverage: Scanned 16 JADA subpages and discovered QDN subpages /book, /track, and /service-areas/carlsbad/ were missing the QDN GA tag entirely
  • Ownership risk: QDN's Google Search Console property was accessible via jadasailing@gmail.com, indicating account-level cross-ownership rather than tag-level cross-pollution

Key Finding: The GA tags themselves were correctly isolated, but account ownership created an audit/compliance issue that needed a kanban card for manual GSC property ownership transfer (requires browser login and Google account management).

Phase 2: Modal Widget Bug Diagnosis and Fix

User screenshots revealed that the booking modal on sailjada.com/crew/ was completely broken. The modal was supposed to reference a shared booking widget (styled after the queenofsandiego.com homepage modal), but instead rendered as a non-functional inline element.

Root Cause: The template generator at /Users/cb/Documents/repos/tools/ was injecting modal HTML with an unescaped f-string literal containing braces, causing the CSS to render as malformed text:

// Broken:
<style>
  .modal-overlay {
    opacity: {opacity};  // <- literal brace, not substituted
  }
</style>

We located the source in the subpage generation pipeline and created a consolidated jada-modal.js asset (stored in S3 at production/assets/) that could be loaded via CloudFront on any subpage, regardless of depth or path.

Phase 3: Shared Asset Architecture and Deployment

Instead of embedding the modal inline on each page (which caused the generation bug), we extracted the modal logic into a single, versioned JavaScript file:

  • File: jada-modal.js (production/assets/jada-modal.js on S3)
  • Pattern: IIFE-wrapped module with no external dependencies (self-contained Stripe.js and GA integration)
  • Distribution: CloudFront distribution d...sailjada.cloudfront.net (exact ID in deployment notes)
  • Loader: Single <script src="..."> tag injected into all subpage templates during build

Why this approach: Shared assets eliminate the f-string bug (no template substitution needed), enable cache efficiency (one file, served from edge), and allow quick fixes without regenerating 17 HTML files.

Phase 4: Staging Validation and Production Deployment

We deployed to staging first, validating:

  • All 17 subpages loaded the modal correctly via CloudFront
  • Stripe.js and GA event tracking fired without console errors
  • Relative asset paths (e.g., Zelle QR code) were correctly resolved
  • CloudFront cache invalidation was precise (no wildcards; exact paths only)

After smoke testing, we promoted staging versions to production and invalidated CloudFront cache for:

/crew/index.html
/crew/*
/assets/jada-modal.js
(and 14 additional subpage paths)

Technical Details: Lint Guard Integration

To prevent future modal injection bugs, we integrated a lint step into the deployment pipeline:

  • Script: /Users/cb/Documents/repos/tools/lint_format_template.py
  • Validation: Scans generated HTML for unescaped f-string braces before build
  • Integration: deploy_qos.sh now calls lint_format_template.py and exits with code 4 on lint failure
  • CTA Selector: Updated to catch all .reserve-btn, .book-now, and modal-trigger classes across all subpages

Why: The original generator had no validation layer. Adding lint as a pre-deploy gate prevents CSS render errors from reaching S3/CloudFront.

Infrastructure Changes

  • S3 Buckets: sailjada production bucket (exact name in deployment notes); staging bucket mirrors structure
  • CloudFront: Single distribution for all sailjada properties; origin points to S3 bucket
  • Route53: No changes; existing CNAME records for sailjada.com, crew.sailjada.com already point to CloudFront
  • Cache Invalidation: Switched from wildcard /* to exact path invalidation (more precise, faster cache refresh)

Key Decisions

  • Shared Asset vs. Inline: Moving the modal to a shared JS file eliminated the template generation bug and improved cache hit rates by centralizing a frequently-referenced asset.
  • IIFE Pattern: Wrapped the modal controller in an Immediately Invoked Function Expression to avoid global namespace pollution and ensure the module is self-contained.
  • Staging Before Prod: Given the previous production crash, we enforced a mandatory staging validation step with smoke tests before any production promotion.
  • Lint Gate: Integrated template validation into the deployment script to catch