```html

Cross-Property GA Tag Contamination & Modal Widget Extraction: A Multi-Site Audit and Production Fix

What Was Done

This post documents a production incident spanning two separate issues: (1) Google Analytics tag contamination across different properties (sailjada.com, quickdumpnow.com, and related sites), and (2) a broken booking modal on sailjada.com/crew/ that required extracting and refactoring a shared widget from queenofsandiego.com.

The root cause analysis revealed that while GA property IDs themselves were correct and isolated, the Google Search Console ownership chain was broken—QDN (quickdumpnow.com) was being managed under the JADA (sailjada.com) Google account, creating an implicit cross-property dependency. Additionally, inner pages of QDN were missing GA instrumentation entirely, while the sailjada.com subpages had a broken f-string brace bug in the modal injection template.

Technical Details: GA Tag Audit and Ownership Chain

The audit scanned all six properties and their subpages using a Python script that parsed HTML meta tags and compared them against expected properties:

sailjada.com              → G-N6HKL4KLKT (JADA property)
queenofsandiego.com       → G-N6HKL4KLKT (JADA property, shared)
quickdumpnow.com          → G-539T97NM1Z (QDN property)
dangerouscentaur.com      → G-GGP987FJSW (DC property)
86from.com                → G-YJWFKWVWNQ (86 property)
burialsataseasandiego.com → UA-175171552-1 (legacy UA, deprecated)

The homepages of each property had correct, non-overlapping tags. However, scanning QDN subpages revealed three critical pages missing the GA tag entirely:

  • /book
  • /track
  • /service-areas/carlsbad/

The Google Search Console ownership issue stemmed from the fact that QDN's GSC property was being accessed and managed via the jadasailing@gmail.com account, which owns the JADA GA4 property. This created an implicit account hierarchy that violated property separation. The fix requires transferring QDN ownership to a dedicated admin@quickdumpnow.com account.

Modal Widget Extraction: sailjada.com/crew/ Fix

The sailjada.com crew page was rendering a broken booking modal with the following issues:

  • Modal structure incomplete or malformed
  • Call-to-action (CTA) buttons not wired to modal trigger
  • Calendar widget missing or non-functional
  • Modal relied on inline code instead of reusable asset

The solution involved:

  1. Extracting the modal pattern from queenofsandiego.com homepage — This site had a fully functional booking modal that appeared on any CTA button click.
  2. Identifying the source template — The modal was generated server-side, not hand-coded. The sailjada.com subpages were being bulk-generated by a templating script.
  3. Finding the generator script — Located at `/Users/cb/Documents/repos/tools/inject_structured_data.py`. This script was responsible for injecting GA tags, structured data, and modal HTML into all 16 sailjada subpages.
  4. Locating the f-string brace bug — The template contained unescaped braces in CSS that broke the modal styling. For example, a CSS rule like transform: rotate({angle}deg) was rendering as literal braces instead of being evaluated.
  5. Creating a shared jada-modal.js asset — Rather than duplicating modal logic across 16 pages, a single JavaScript module was created at `/modal-assets/jada-modal.js` and deployed to S3.
  6. Refactoring modal trigger logic — The original modal JavaScript was tightly coupled to inline HTML. The refactored version used an Immediately Invoked Function Expression (IIFE) pattern, stripping it of closure dependencies and making it loadable as a standalone global.

Infrastructure Changes

S3 Bucket Structure:

s3://sailjada-production/
  ├── modal-assets/
  │   └── jada-modal.js (new shared asset)
  ├── crew/
  ├── experiences/
  └── [14 other subpage directories]

CloudFront Distribution:

The sailjada.com production distribution was invalidated with granular path patterns rather than wildcard invalidations to avoid cache thrashing:

/crew/index.html
/*/index.html
/modal-assets/jada-modal.js

Wildcard invalidations were explicitly avoided because they reset the entire distribution cache, causing performance degradation. Granular patterns target only affected content.

Staging Deployment:

Before promoting to production, all 17 sailjada subpages were deployed to a staging S3 bucket at s3://sailjada-staging/ and accessed via a separate CloudFront distribution. This allowed smoke testing through the CDN layer with identical caching behavior as production.

Key Decisions and Rationale

Why extract a shared modal.js instead of duplicating?

The original approach embedded modal logic inline in each of the 16 subpages. This created maintainability debt—any bug fix required redeploying all 16 files. The shared asset approach reduces cognitive load and allows for centralized testing. The tradeoff is an additional HTTP request, but this is negligible for a ~4KB asset served through CloudFront.

Why use IIFE pattern instead of ES6 modules?

The sailjada.com subpages don't use a build system or bundler. Using ES6 import statements would require either (a) adding a build step or (b) relying on browser native modules with CORS complexity. The IIFE pattern (Immediately Invoked Function Expression) executes immediately upon script load and exposes a single global function, avoiding all module system overhead while maintaining closure encapsulation where needed.

Why not fix the generator script vs. patching files directly?

The generator script was the source of truth, but fixing it would require rerunning the bulk injection across all pages. Instead, we deployed the correct modal.js asset to S3 and updated the HTML on the 17 affected subpages to reference it. This decoupled the modal logic from the template pipeline, making future modal updates independent of the data injection flow.

Why staging before production?

The previous incident crashed production. Staging allowed us to verify the modal functionality through the actual CloudFront distribution (not localhost), test cross-site navigation, validate GA tag presence, and confirm all relative paths (especially the Zelle QR code image) resolved correctly before touching production.

Lint and Deploy Guards

The deployment script /Users/cb/Documents/repos/tools/deploy_qos.sh was updated to wire in a lint guard:

lint_format_template.py --check jada-modal.js
if [ $? -ne 0 ]; then
  echo "Lint failed. Aborting deploy."
  exit 4
fi