Files
2026-07-12 10:17:17 -04:00

11 KiB

name, title, description
name title description
frontend-debugging Frontend Debugging Patterns Non-trivial debugging patterns for static HTML/CSS/JS web apps — JS-overrides-HTML, missing script imports, cache-busting, and common pitfalls.

Frontend Debugging Patterns

When Static HTML Edits Don't Take Effect

Check if JS dynamically generates the HTML at runtime. This is the #1 cause of "I edited the file but nothing changed."

  • Search the JS files for innerHTML, createElement, textContent =, or DOM appendChild calls that target the same element IDs/classes
  • The DOM inspector in DevTools shows live state — if it differs from your source file, JS is overwriting it
  • Fix: edit the JS template string or the JS rendering function, not just the static HTML

Special case: color/readability fixes. When a user reports "yellow on yellow" or "hard to read" and you fix the static HTML but they still see the old colors, the culprit is almost certainly a JS innerHTML template string that dynamically generates the colored elements. Search for .innerHTML in JS files and look for text-yellow-*, bg-yellow-*, or similar color classes — that's where the real fix lives.

Missing Script/Module Imports

A page element exists in HTML but has no event handler → the JS file that wires it up probably isn't loaded.

  • Check the <script> tags at the bottom of the page — compare against a page where the feature works
  • Common misses: shared module files like shared/header-functionality.js, shared/dropdown-manager.js
  • Also check that the JS actually targets the right element ID (typo in getElementById)

When Inline onclick Exists but the Button Still Fails

An inline onclick handler is present on the element and the logic looks correct, but clicking does nothing. Two silent causes:

Cause A — The target element is missing from this page. The onclick calls getElementById('some-modal') but that modal div only exists on other pages. The if(m) guard silently returns — no error, no visual feedback. Fix: add the missing modal HTML to the page. Audit all getElementById targets across pages with:

for page in *.html; do
  grep -oP "getElementById\('([^']+)'\)" "$page" | \
    sed "s/getElementById('//;s/')//" | sort -u | while read id; do
    grep -q "id=\"$id\"" "$page" || echo "MISSING: $id in $page"
  done
done

Cause B — File permissions blocking the module chain. A shared dependency (e.g., pocketbase.js, shared/sanitize.js) has owner-only permissions (600). nginx runs as www-data and returns HTTP 403. The browser can't load it → every ES module that imports it silently fails → no JS handlers attach. Even inline onclick on other elements may work, but module-reliant features die. Fix: find . -name "*.js" -exec chmod 644 {} +, then curl -skI https://site.com/key-file.js | head -1 to verify 200.

Cause C — Module handler double-toggle conflict. An inline onclick and a JS module handler (e.g., from shared/header-functionality.js) both attach to the same button and BOTH toggle the same hidden class. The inline fires first → toggles hidden OFF (visible). The module fires second → toggles hidden ON (hidden again). Net result: nothing happens. This is especially common with hamburger menus and user dropdowns after inline onclick fallbacks are added to pages that also load the module-driven header.

Fix: wrap the inline onclick in an IIFE that calls e.stopImmediatePropagation() to prevent the module handler from also firing:

<!-- BEFORE (double-toggle, no visible effect) -->
<button onclick="var n=document.getElementById('mobile-navigation');if(n){n.classList.toggle('hidden')}">

<!-- AFTER (inline fires first, module suppressed) -->
<button onclick="(function(e){e.stopImmediatePropagation();var n=document.getElementById('mobile-navigation');if(!n)return;var hidden=n.classList.toggle('hidden');n.style.display=hidden?'none':'block';n.style.zIndex='10000'})(event)">

Key details:

  • Use stopImmediatePropagation() (not just stopPropagation) — the module handler is on the same element
  • Wrap in IIFE (function(e){...})(event) so stopImmediatePropagation receives the actual event object
  • Explicitly set style.display (not just class toggle) for defense-in-depth against CSS specificity
  • index.html (dashboard) often does NOT load header-functionality.js — no conflict, no fix needed
  • Pages that DO load the module (repair-orders, appointments, customers) all need the fix

Class-Based Dark Mode: When Fixes Don't Stick

Symptom: You add class="dark" to <html>, or fix a dark mode toggle handler, but something still switches the site to light mode.

Root cause: Multiple JS files independently control the .dark class. You patched one but missed others hiding in settings sync code.

The fix requires patching EVERY instance of classList.toggle('dark', ...) and classList.remove('dark') across ALL JS files — not just the obvious UI toggle handlers:

grep -rn "classList.*toggle.*dark\|classList.*remove.*dark" *.js **/*.js

Common hidden locations:

  • Settings sync code in dashboard.js, customers.js, settings.js
  • Remote settings listeners that apply dark mode after a server sync
  • Mobile-specific toggle handlers in shared modules

Verification after patching:

  1. Hard refresh — should load dark
  2. Click every UI element that previously controlled dark mode — no change
  3. Open settings modals — any dark mode toggle should be inoperable
  4. Search localStorage in DevTools — darkMode should always be 'true'

Pitfall: Don't rely solely on CSS !important overrides to force dark colors (e.g. .bg-white { background: #1f2937 !important; }). Tailwind CDN generates styles dynamically that can compete with your overrides. Patching the JS at the source is more reliable.

Cache-Busting for Static Servers

Python's http.server and similar dev servers serve files fresh each request, but browsers cache aggressively, especially on mobile.

Solutions in order of effectiveness:

  1. Open page in incognito/private tab
  2. Hard refresh (Ctrl+Shift+R / Cmd+Shift+R)
  3. Add cache-busting meta tags to <head>:
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    
  4. Restart the server to force fresh TCP connections

Mobile Responsiveness Pitfalls

Hamburger Menu Not Working

Check four things in order:

  1. The hamburger button exists in HTML (search for mobile-menu-btn)
  2. The target #mobile-navigation panel exists in the HTML
  3. Module conflict: shared/header-functionality.js adds its own click handler that also toggles hidden — the inline onclick and module handler cancel each other out. Add stopImmediatePropagation() to the inline onclick (see Cause C above)
  4. The JS file that handles the click is loaded (check <script> tags)

Hidden-by-Default Mobile Elements

Mobile nav panels are usually hidden by default and toggled by JS. If the panel never appears:

  • Check the CSS class includes hidden in the initial state
  • Verify the JS toggle uses classList.toggle('hidden') and isn't broken by JS errors earlier in execution

CSS Consistency Across Pages

Escaped Characters in Inline Scripts Breaking Selectors

Symptom: An inline <script> block has a document.addEventListener('click', ...) handler that checks for close buttons via [aria-label="Close"], but clicking the (X) button does nothing. The button clearly has aria-label="Close" in the HTML. The same handler works on other pages. Console shows no errors.

Root cause: Double-escaping in inline <script> blocks. When the HTML source contains \\\" (backslash-backslash-quote), the browser's HTML parser resolves it to a literal backslash-quote sequence (\") in the JavaScript string. A CSS selector like [aria-label=\"Close\"] then matches the literal attribute value "Close" (with quote characters) instead of the actual HTML attribute value Close (without quotes).

The broken pattern:

// In the HTML source, this looks like: [aria-label=\\\"Close\\\"]
// After HTML parsing, JS sees: [aria-label=\"Close\"]
// This selector matches: aria-label='"Close"' (quotes in value) — NEVER correct
var closeBtn = e.target.closest('[aria-label=\\\"Close\\\"]');

The fix — use normal quotes:

// In the HTML source, this looks like: [aria-label="Close"]
// After HTML parsing, JS sees: [aria-label="Close"]
// This selector matches: aria-label="Close" — CORRECT
var closeBtn = e.target.closest('[aria-label="Close"]');

Alternative fix — use single quotes in JS:

var closeBtn = e.target.closest("[aria-label='Close']");

Diagnosis: Use read_file on the HTML and look at the raw text. If you see \\\" sequences inside <script> tags, the selectors are silently broken. The terminal command sed -n 'LINENOp' file.html | cat -A will show the raw bytes — \\ displays as literal backslashes.

Verification after fixing: curl -sk https://site.com/page.html | grep 'aria-label.*Close' should show aria-label="Close" (no backslashes between label and value).

When making the same header/nav change across multiple pages:

  • Do ONE page first, get user approval, then apply the same diff pattern to the others
  • Search for the exact HTML structure in each file — they may have drifted (different padding classes, different wrapper divs)
  • For shared CSS classes (.header-card, .nav-link), editing the CSS file once fixes all pages

IIFE Capture-Phase Handler + data-close-modal on Save Buttons

Symptom: On a page with the Universal Modal Manager IIFE, clicking the Save button in a settings modal closes the modal but changes don't persist. The same modal works perfectly on other pages. Close/Cancel buttons work fine — only Save is broken.

Root cause: ensureSiteSettingsCloseAttributes() (or dashboard.js) sets data-close-modal on the Save button. The IIFE capture-phase handler matches [data-close-modal], calls stopPropagation(), and closes the modal — before any save handler fires.

Diagnosis: Compare a working page against the broken page:

  • Working pages (e.g., index.html, appointments.html) don't have the capture-phase IIFE
  • Broken page (repair-orders.html) has the IIFE + save button tagged with data-close-modal

Fix: See references/event-propagation-iife-conflict.md for full trace, three-part fix, and alternative approach.

Color/Readability Rules of Thumb

  • Never use colored text on the same-colored background (e.g. text-yellow-700 on bg-yellow-50)
  • Use white/dark cards with colored left-border accents for alert banners — clean, modern, readable
  • For count badges: use subtle colored background (bg-red-50) with bold darker text (text-red-700), not washed-out tones
  • Labels should use neutral gray tones (text-gray-500/600/700) for readability across themes