--- name: web-ui-repair description: "Fix common static web app UI issues: broken shared dependencies, readability, browser cachiness, and dev server setup." version: 1.0.0 author: Hermes Agent license: MIT platforms: [linux, macos] metadata: hermes: tags: [web, frontend, html, css, javascript, ui, debugging, repair] related_skills: [systematic-debugging, spike, codebase-audit] --- # Web UI Repair ## Overview Patterns for fixing common issues in static HTML/CSS/JS web apps — multi-page sites where some pages work and others don't, readability problems, and browser cache issues after fixes. This skill covers **post-investigation repair actions**. For root cause investigation methodology, use `systematic-debugging`. For diagnostic patterns (JS-overrides-HTML, missing script imports, class-based dark mode search), see `references/frontend-debugging-patterns.md` (absorbed from `frontend-debugging`). --- ## 1. Readability & Contrast Fixes ### The Problem Colored text on colored backgrounds (e.g., `text-yellow-600` on `bg-yellow-50`, or `text-red-500` on `bg-red-50`) makes content unreadable, especially in dark mode. ### The Fix Use a **white/dark card with colored accent border** pattern for alert banners OR a **subtle colored bg with bold text** pattern for stat badges: **Pattern A — Alert banners (white card + colored left-border accent):** ```
Overdue Tasks: 3 task(s) are overdue!
``` **Pattern B — Stat badges (colored bg + bold colored text):** ```
0
Overdue
``` **Key rules for readability:** - NEVER use colored text on a same-colored background (e.g. `text-yellow-700` on `bg-yellow-50`) - For the white-card + accent pattern: use `text-gray-900 dark:text-white` for headings, `text-gray-700 dark:text-gray-300` for body text - For the colored-bg badge pattern: use the **darkest shade** of the color for text (`text-red-700`, `text-amber-700`) — NOT a washed-out tone - Numbers/values should be bold and high-contrast - **Avoid washed-out grays** for primary content — reserve `text-gray-500` and below for secondary/helper text only **Color scheme mapping for badges/stat cards:** | Intent | Border accent | Value color | |--------|--------------|-------------| | Overdue / Error | `border-red-200 dark:border-red-900/50` | `text-red-600 dark:text-red-400` | | Due Today / Warning | `border-amber-200 dark:border-amber-900/50` | `text-amber-600 dark:text-amber-400` | | Upcoming / Info | `border-blue-200 dark:border-blue-900/50` | `text-blue-600 dark:text-blue-400` | | Success | `border-green-200 dark:border-green-900/50` | `text-green-600 dark:text-green-400` | --- ## 2. Browser Cache Busting ### The Problem After editing HTML/CSS/JS files on a static server (Python http.server, Nginx, etc.), the user's browser may serve cached versions of the old files — they won't see your changes. ### The Fix **Add cache-busting meta tags to the ``:** ```html ``` **Also tell the user to hard refresh:** - Desktop: `Ctrl+Shift+R` / `Cmd+Shift+R` - Phone: close tab completely, open fresh **Pitfalls:** - `Cache-Control` meta tags don't always work on all browsers (especially mobile Safari/Chrome). When they don't, the meta tags still act as a signal to the user to try again. - If meta tags aren't enough, restart the dev server after file changes (forces a fresh connection). - For production sites, use file-versioned URLs (e.g., `style.css?v=2`) or content hashing in the filename. **⚠️ Server-side cache pitfall — `Cache-Control: immutable`:** Nginx configs often set aggressive caching on static assets: ```nginx location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 30d; add_header Cache-Control "public, immutable"; } ``` The `immutable` directive tells the browser **"this file will never change — don't even ask the server if there's a newer version."** After editing JS/CSS files, the browser serves the stale cached version forever. Meta tags in HTML don't override this. **Fix:** Change to must-revalidate with a shorter expiry: ```nginx location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 1h; add_header Cache-Control "public, must-revalidate"; } ``` Then reload nginx: `sudo nginx -t && sudo systemctl reload nginx` **Verification:** ```bash curl -sI -k https://your-site.com/path/to/file.js | grep -i cache # Should show: Cache-Control: public, must-revalidate (NOT "immutable") ``` **⚠️ Before debugging JS logic, verify the served HTML matches what's on disk.** When a fix was applied to a file but the user reports it didn't take effect, the FIRST diagnostic step is to confirm what nginx is actually serving — not what's on the filesystem: ```bash # Compare what's on disk vs what's served grep 'fix-pattern' /path/to/file.html # Should match curl -sk https://localhost/file.html | grep 'fix-pattern' # Should ALSO match ``` If they differ, the problem is between disk and server — not in the JS logic. Common causes: - **Nginx serving from a different root** than the file you're editing (check `root` directive in all `server` blocks, especially `sites-enabled/` vs `sites-available/`) - **Multiple server blocks on different ports** — the `sites-enabled/` version may have different `listen` and `root` directives than the `sites-available/` version you inspected - **Symlink vs copy** — `sites-enabled/` entries may be symlinks to `sites-available/` OR standalone files with different content - **Browser cache** — curl bypasses browser cache; if curl shows the fix but the user doesn't see it, the issue is client-side caching **Runtime cache-busting for redirects — `?cb=` pattern:** When the user's browser already has a stale cached page (from before the nginx fix), even a hard refresh may not clear it. Add a cache-busting query parameter to ALL redirects so the browser treats each navigation as a fresh request: ```javascript // Auth guard redirect (with cache-busting) location.replace('login.html?cb=' + Date.now()); // Sign-out redirect localStorage.removeItem('pocketbase_auth'); location.replace('login.html?cb=' + Date.now()); // Login success redirect location.replace('index.html?cb=' + Date.now()); ``` Use `location.replace()` (not `location.href`) to prevent back-button loops. The `?cb=` parameter is ignored by static servers but forces the browser to bypass its cache and fetch a fresh copy. Apply this to ALL redirects: sign-out buttons, auth guards, login form success handlers — every path that navigates between pages. --- ## 3. Dev Server Port Selection ### The Problem Starting a static HTTP server on a port already in use causes it to fail silently or conflict with another service. ### The Fix 1. Check which ports are in use first: ```bash ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null ``` 2. Pick a port NOT in the list. Common safe picks that are rarely used by system services: - `5500`, `8081`, `8888`, `9999`, `3333`, `4444`, `5555`, `7777` 3. Verify the server started successfully: ```bash ss -tlnp | grep ``` 4. Common ports that ARE often in use and should be avoided: - `3000` (often used by dev tools like Selkies, React dev server) - `8000`, `8080` (common HTTP alternatives) - `8443`, `9090`, `9443` (dashboard tools) - `3001`, `8787`, `2283` (self-hosted apps) **Pitfalls:** - Some services (like Selkies) may use port 3000 without showing in `ss` output — avoid port 3000 by default. - Python's `http.server` needs `SIGINT` (Ctrl+C) or `kill` to stop cleanly before restarting on a new port. --- ## 5. Compact Nav-Bar Header Redesign ### Context Multi-page static web apps (Dashboard, Repair Orders, Customers, Appointments) often have a tall header with logo, title, subtitle, and a separate navigation bar below. Users may want a **compact, single-row header** that removes the logo/title section entirely. ### The Pattern Instead of two sections (top content + nav bar), combine everything into one nav bar row: ``` ┌──────────────────────────────────────────────────┐ │ [☰] Dashboard Repair Orders Appts Customers │ [🌙] [👤] │ ├──────────────────────────────────────────────────┤ │ Mobile Navigation (hidden) │ └──────────────────────────────────────────────────┘ ``` **Structure breakdown (left to right):** | Element | Desktop | Mobile | |---------|---------|--------| | Hamburger button (`#mobile-menu-btn`) | Hidden (`lg:hidden`) | Visible | | Desktop nav links | Visible (`hidden lg:flex`) | Hidden | | **Mobile page title** | Hidden (`lg:hidden`) | **Visible center** | | Dark mode toggle | Visible (sm+) | Hidden (in dropdown) | | User menu button | Visible | Visible | ### Implementation Steps 1. **Remove the top content section** — logo icon, page title `

`, subtitle `

` — everything inside the header-card's inner content div 2. **Merge controls into the nav bar** — move the mobile menu button, dark mode toggle, and user menu into the same flex row as the nav links 3. **Add mobile page title** — a `Page Name` between the left and right sections 4. **Tighten padding** — outer container: `p-2 md:p-4`, inner: `py-2 md:py-2.5`, nav link padding: `py-1.5 md:py-2` ### Key CSS Classes to Use **Desktop nav links:** - Active page: `bg-white/20 text-white shadow-sm` - Inactive pages: `text-blue-100 hover:text-white hover:bg-white/10` **Mobile nav links:** - Active page: `bg-white/20 text-white shadow-sm` - Inactive pages: `text-blue-100 hover:bg-white/10 hover:text-white` **Mobile navigation panel:** - Hidden by default: `lg:hidden hidden` - Toggled by `#mobile-menu-btn` click handler in `shared/header-functionality.js` ### Pitfalls - **Active page must be correct.** Each page's nav has the active page link styled with `bg-white/20 text-white shadow-sm`. Triple-check: if you copy-paste the header from dashboard.html to repair-orders.html, the active link (Dashboard with bg-white/20) will show incorrectly on the repair-orders page. - **⚠️ Active nav-link may be too subtle.** On a blue gradient header, `bg-white/20` (20% white) vs the parent's `bg-white/10` may not be enough contrast. The difference is particularly hard to see on mobile. Fix: add a dedicated CSS class for the active page with higher opacity (35%) and a subtle shadow: ```css .nav-link.active-page { background: rgba(255,255,255,0.35) !important; box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important; } .dark .nav-link.active-page { background: rgba(255,255,255,0.25) !important; } ``` Add the `active-page` class to the current page's nav link (``). Apply to ALL pages' active nav links for consistency. This makes the active pill visibly brighter than the inactive `text-blue-100` links. - **⚠️ Missing `.nav-link` CSS on individual pages.** The `nav-link` class may have associated CSS rules (`position: relative; overflow: hidden;` plus a `::before` hover shine animation) defined inline in some pages' `