When users report broken preview tabs, blank screens, or missing browser previews, this skill knows exactly what to check. It reads the actual registry files at `/data/previews.json` and `/data/preview_history.json` to diagnose what's running versus what crashed. More importantly, it prevents the classic mistake of telling users to visit localhost URLs that they can't actually reach from their browser. It also catches the absolute path gotcha that breaks static assets when served through the reverse proxy. Think of it as the troubleshooting manual for Claude's iframe preview system, with step-by-step debugging that actually works.
npx -y skills add starchild-ai-agent/official-skills --skill browser-preview --agent claude-codeInstalls into .claude/skills of the current project.
You already know preview_serve and preview_stop. This skill fills the gap: what happens after preview_serve returns a URL — how the user actually sees it.
The frontend has a right-side panel with three tabs: Files, Preview, and Jobs. The Preview tab renders preview URLs inside an iframe. When you call preview_serve, the frontend automatically opens a Preview tab loading that URL.
Key facts:
preview_serve call creates one Preview tabhttps://<host>/preview/{id}/The user's browser CANNOT access localhost or 127.0.0.1. These addresses point to the server container, not the user's machine. The preview architecture uses a reverse proxy:
User's Browser → https://<host>/preview/{id}/path → (reverse proxy) → 127.0.0.1:{port}/path
Rules:
http://localhost:{port} or http://127.0.0.1:{port} — they cannot reach it/preview/{id}/ (or the full URL https://<host>/preview/{id}/)curl http://localhost:{port} is for your own server-side diagnostics only — never suggest it to the user as a way to "test" the previewurl field returned by preview_serve (format: /preview/{id}/)Because previews are served under /preview/{id}/, absolute paths in HTML/JS/CSS will break. The reverse proxy strips the /preview/{id} prefix before forwarding to the backend, but the browser resolves absolute paths from the domain root.
Example of the problem:
<!-- ❌ BROKEN: browser requests https://host/static/app.js → 404 (bypasses preview proxy) -->
<script src="/static/app.js"></script>
<!-- ✅ WORKS: browser requests https://host/preview/{id}/static/app.js → proxied correctly -->
<script src="static/app.js"></script>
<script src="./static/app.js"></script>
Common patterns to fix:
| Broken (absolute) | Fixed (relative) |
|---|---|
"/static/app.js" | "static/app.js" or "./static/app.js" |
"/api/users" | "api/users" or "./api/users" |
"/images/logo.png" | "images/logo.png" or "./images/logo.png" |
url('/fonts/x.woff') | url('./fonts/x.woff') |
fetch('/data.json') | fetch('data.json') |
Check ALL places where paths appear:
src, href attributesfetch(), XMLHttpRequest, dynamic importsurl() references'/static/' in template strings or concatenation)publicPath, base, assetPrefix)⚠️ Be thorough — it's common to fix CSS url() but miss JS string literals like '/static/' (with single quotes). Search for ALL occurrences of absolute paths across all file types.
Never look at workspace directories like preview/, output/, or random folders to understand preview state. Those are user data, not preview service state.
The only sources of truth:
/data/previews.json (running services)/data/preview_history.json (all past services)preview_serve / preview_stop toolscurl (server-side only, for your diagnostics)Do NOT use ls/find on workspace directories to diagnose preview issues. Do NOT call unrelated tools like list_scheduled_tasks. Stay focused.
When a user reports any Preview panel problem, follow this exact sequence:
cat /data/previews.json 2>/dev/null || echo "NO_REGISTRY"
⚠️ Your bash CWD is /data/workspace/. The registry is at /data/previews.json (absolute path, one level up). Always use the absolute path.
JSON structure:
{
"previews": [
{"id": "f343befc", "title": "My App", "dir": "/data/workspace/my-project", "command": "npm run dev", "port": 9080, "is_builtin": false}
]
}
If registry has entries → Go to Step 3 (verify services) If registry is empty or missing → Go to Step 4 (check history)
For each preview in the registry, check if the port is responding server-side (this is your diagnostic, not for the user):
curl -s -o /dev/null -w "%{http_code}" http://localhost:{port}
If port responds (200):
/preview/{id}/preview_stop(id) then preview_serve(dir, title, command) using the info from the registry. This forces the frontend to re-register the tab.If port does NOT respond:
preview_stop(id="{id}")
preview_serve(dir="{dir}", title="{title}", command="{command}")
When there are no running services, use a two-tier lookup to find projects the user can preview:
cat /data/preview_history.json 2>/dev/null || echo "NO_HISTORY"
JSON structure:
{
"history": [
{
"id": "f343befc",
"title": "Trading System",
"dir": "/data/workspace/my-project",
"command": "python main.py",
"port": 8000,
"is_builtin": false,
"created_at": 1709100000.0,
"last_started_at": 1709200000.0
}
]
}
History entries are never removed by preview_stop — they persist across restarts. Entries are automatically pruned only when the project directory no longer exists.
If history has entries:
preview_serve with the dir, title, and command from the history entryIf user says a project is missing from history → fall through to Tier 2.
find /data/workspace -maxdepth 2 \( -name "package.json" -o -name "index.html" -o -name "*.html" -o -name "app.py" -o -name "main.py" -o -name "vite.config.*" \) -not -path "*/node_modules/*" -not -path "*/skills/*" -not -path "*/memory/*" -not -path "*/prompt/*" -not -path "*/.git/*" 2>/dev/null
Then:
preview_serve with the appropriate directoryDon't just say "no services running" and stop. Always check history first, then scan, and offer options.
| User says | You do |
|---|---|
| "tab disappeared" / "tab 不见了" | Step 1 → 2 → 3 or 4 |
| "blank page" / "白屏" | Check port (server-side), if dead → recreate; if alive → check for absolute path issues |
| "not updating" / "内容没更新" | Suggest refresh button in Preview tab, or recreate preview |
| "port conflict" / "端口冲突" | preview_stop old → preview_serve new |
| "can't see service" / "⋮ menu empty" | preview_stop + preview_serve to force re-register |
| "where's my project" / "what did I build" | Read /data/preview_history.json and list entries |
| "resource load failed" / "JS/CSS 404" | Check for absolute paths (/static/, /api/), fix to relative paths |
When you can't do something, tell the user the manual action (e.g., "click refresh in Preview tab"). If manual action doesn't work, recreate the preview with preview_stop + preview_serve.
url() paths but missing JS string literals with absolute paths/preview/{id}/ as the user-facing URLcurl localhost:{port} only for your own server-side diagnosticspreview_stop + preview_serve to restart, then tell user to check Preview panelsickn33/antigravity-awesome-skills
moizibnyousaf/ai-agent-skills
github/awesome-copilot