---
name: run-frontend
description: Kill stale Vite, npm run dev, verify /, retry loop
group: RUN
recommended_model: haiku
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: process management
---

# Skill: Run Frontend — kill → launch → verify → retry

## Inputs

- Worktree cwd (you're there)
- Web dir: `web/{appcode}-web/` (lowercase AppCode)
- Expected port: from `vite.config.ts` server.port OR 3000 default
- Expected API URL: from env `VITE_API_URL` OR http://localhost:5142

## Loop (max 3 attempts)

```
attempt = 1
LOOP:
  cd web/{appcode}-web

  # ── 1. Kill stale vite/node ──────────────────
  Get-Process | Where-Object { $_.CommandLine -like '*vite*' -and $_.Path -like '*{worktree}*' } | Stop-Process -Force
  # OR Unix: pkill -f "vite.*{worktree}"
  Free port: netstat -ano | findstr :3000 → taskkill /PID <pid> /F

  # ── 2. Ensure deps ───────────────────────────
  IF NOT exists node_modules/:
    npm install --silent
    IF exit != 0: ESCALATE (npm install failed)
  END

  # ── 3. Ensure scaffold ───────────────────────
  IF NOT exists index.html: ESCALATE (index.html missing, needs debug/frontend)
  IF NOT exists src/main.tsx: ESCALATE (main.tsx missing)

  # ── 4. Launch ────────────────────────────────
  # Validate VITE_API_URL points to a reachable backend BEFORE launching Vite,
  # otherwise the UI will load but every API call will fail silently (500s).
  VITE_API_URL ?= http://localhost:5142
  api_probe = curl -s -o /dev/null -w "%{http_code}" $VITE_API_URL/health
  IF api_probe not in (200, 204, 404):
    WARN: "VITE_API_URL=$VITE_API_URL is unreachable (code $api_probe). Launching Vite anyway — expect broken UI."
  END

  Spawn: npm run dev -- --port 3000 --host localhost
  Export VITE_API_URL=$VITE_API_URL in the env

  # ── 5. Wait for "Local: http://localhost:PORT/" in stdout (max 20s) ──
  FOR i in 1..10:
    sleep 2s
    grep "Local:\s*https?://localhost:\d+" stdout
    IF match: real_port = extracted port → break loop
    IF "ready in" AND no "Local:": real_port = 3000 (best guess) → break
  END

  IF no Vite ready after 20s:
    capture stderr last 20 lines
    IF same_as_prev: break (non-transient)
    attempt++ → CONTINUE
  END

  # ── 6. Verify HTTP 200 on / ──────────────────
  curl -fsS http://localhost:{real_port}/ > /dev/null
  IF HTTP 200: return SUCCESS with port=real_port
  IF HTTP 404:
    Check: does index.html exist at root? → if not, ESCALATE
    Check: does vite.config.ts have base != '/'? → report in output
  IF ECONNREFUSED: retry (port probably still binding)
END LOOP

IF attempt > 3: FAIL with summary
```

## Recognized transient errors (auto-retry)

- `Port 3000 is in use, trying another one...` → Vite auto-bumps, accept real_port from logs
- `ECONNREFUSED` on first curl → Vite still binding, wait 2s + retry
- `Cannot find module 'vite'` right after fresh clone → node_modules corrupted, `rm -rf node_modules && npm install`

## Non-transient (escalate)

- `index.html` missing at web root → `debug/frontend` can auto-repair
- `SyntaxError` in main.tsx → config error, `debug/frontend`
- `useLocation() may be used only...` → missing BrowserRouter, `debug/frontend`
- `Cannot read properties of undefined (reading 'extensions')` → missing config prop, `debug/frontend`
- `Failed to resolve import "..."` → missing dep, `debug/frontend`

## Output format (concise)

Success:
```
[frontend] ✓ OK (port 3000, HTTP 200, Vite ready in 680ms, attempt 1)
```

Port bump:
```
[frontend] attempt 1: port 3000 busy, Vite picked 3001 → ✓ OK on 3001
```

Escalation:
```
[frontend] attempt 1: index.html missing at web root
[frontend] ✗ Non-transient. Run: claude -p "@.claude/skills/development/debug/frontend/SKILL.md"
```

## Rules

- **Kill stale node processes** whose cwd matches the worktree (don't kill user's other Vite instances)
- **Never touch index.html or main.tsx** — that's debug/'s job, escalate instead
- **Read the real port from Vite's "Local:" line** — don't assume it's the one we requested
- **VITE_API_URL matters** — pass it as env, don't put it in CLI args
- Max 3 attempts — escalate after
