# Ops Portal MIME Type Fix (Jul 12, 2026)

## Problem
All ops portal pages broke on mobile -- nav didn't initialize, login overlay stuck, service buttons unresponsive. Desktop with cached assets worked fine.

## Root Cause
FastAPI's `FileResponse()` returned Content-Type `application/json` for `.js` and `.css` files instead of `text/javascript` and `text/css`. Browsers strictly enforce MIME types for scripts/stylesheets -- the wrong type causes silent refusal to execute/apply.

Additionally, JS/CSS files were created with `chmod 600` (root-only), so Caddy's static file handler returned 403 Forbidden, falling back to the backend which returned the wrong MIME type.

## Fix -- Two Layers

### Layer 1: Caddy static file handler (primary)
Add `handle_path` blocks BEFORE `reverse_proxy`:
```caddy
ops.itpropartner.com {
    handle_path /js/* {
        root * /opt/ops-portal/static/js
        file_server
    }
    handle_path /css/* {
        root * /opt/ops-portal/static/css
        file_server
    }
    reverse_proxy 127.0.0.1:8090
}
```

### Layer 2: FastAPI fallback
```python
return FileResponse(str(fpath), media_type="application/javascript",
    headers={"Cache-Control": "no-cache, no-store, must-revalidate"})
return FileResponse(str(fpath), media_type="text/css",
    headers={"Cache-Control": "no-cache, no-store, must-revalidate"})
```

### Prerequisite: File permissions
```bash
chmod 644 /opt/ops-portal/static/js/*.js /opt/ops-portal/static/css/*.css
```

## Symptoms
- Browser console: "Refused to execute script because its MIME type ('application/json') is not executable"
- Works on desktop with cached assets, fails on mobile/cold load
- All pages load HTML but nothing interactive works

## Verification
```bash
curl -sI https://ops.itpropartner.com/js/app.js | grep -i content-type
# Expected: text/javascript; charset=utf-8
curl -sI https://ops.itpropartner.com/css/ops.css | grep -i content-type  
# Expected: text/css; charset=utf-8
```
