# Nav Consistency Pattern (Jul 12, 2026)

## Principle

All portal pages must load nav from `/nav.html` — the single source of truth for nav links, hamburger toggle, and nav CSS. The only exception is index.html (dashboard), which keeps an inline nav that matches nav.html link-for-link.

## Current state (post-Jul 12 fix)

| Page | Nav source | Status |
|------|-----------|--------|
| index.html | Inline (matches nav.html) | OK — all 11 links + hamburger |
| services.html | Fetched | OK |
| servers.html | Fetched | OK |
| backups.html | Fetched | OK |
| audit.html, cost.html, cron.html, network.html, config.html, logs.html | Fetched | OK |

## Conversion procedure (inline → fetched)

1. **Replace the `<nav class="ops-nav">...</nav>` block** with `<div id="nav"></div>`.

2. **Move `initNav()` into the fetch callback.** The old pattern of calling `window.initNav()` before the nav HTML is injected is a race condition — it tries to wire up toggle listeners on elements that don't exist yet:
   ```javascript
   // WRONG (initNav runs before nav HTML exists):
   window.initNav();
   //  ... later ...
   fetch('/nav.html')...

   // RIGHT (initNav runs AFTER nav HTML is injected):
   fetch('/nav.html').then(function(r) { return r.text(); }).then(function(h) {
     document.getElementById('nav').innerHTML = h;
     if (typeof window.initNav === 'function') window.initNav();
   }).catch(function(e) { console.warn('Nav failed:', e); });
   ```

3. **Keep `window.initNav()`** — never use `Ops.initNav()` or bare `initNav()` inside a strict-IIFE. The nav init function is exported on `window` in `app.js`/`utils.js`.

## Index.html: inline nav maintenance

The dashboard keeps inline nav. When `/nav.html` gains or loses links, update index.html's inline nav to match. Key elements:
- `class="nav-logo-text"` on the brand span
- Hamburger toggle: `<button class="nav-toggle" id="navToggle" ...>`
- All 11 links with `data-path` attributes
- `class="nav-link"` and `target="_blank"` on Grafana

## Verification checklist

- [ ] `<div id="nav">` placeholder present (fetched pages only)
- [ ] `initNav()` called AFTER `.innerHTML = h` in fetch callback
- [ ] No orphan `window.initNav()` outside fetch block
- [ ] index.html has all current nav.html links + hamburger toggle
- [ ] HTTP 200 on all pages
- [ ] `/nav.html` serves directly (not just through FastAPI proxy)
