Live verification of frontend changes in dev-app.nicolify.com using Chrome DevTools MCP from WSL2. Use when: verifying a frontend bug fix, reproducing a user-reported UI bug step-by-step, checking real-time SSE/polling behavior, validating card/form interactions, inspecting DOM or console or network live, or any task where 'me lo muestres en el navegador' would be cheaper than guessing. Triggers: 'verifica en el navegador', 'prueba con chrome-devtools', 'reproduce el bug en vivo', 'abrí el navegador', 'chrome-devtools-mcp', 'live test', 'verify UI fix'.
75
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Project memory: Nicolify (AISALESHT), Next.js 16 + React 19 + Clerk. Chrome runs on Windows host, Claude Code runs in WSL2. Bridge needed.
Check MCP reachable before assuming anything:
curl -sf http://127.0.0.1:9222/json/version | head -3If it returns Chrome JSON ("Browser": "Chrome/...") → bridge alive, call mcp__chrome-devtools__list_pages and go.
If empty/timeout/reset → bridge not up. Use STABLE PATH below. Do NOT use @dbalabka/chrome-wsl (v4tov4 portproxy points to wrong address, silently fails with "Empty reply" — confirmed broken on every Nicolify session).
Verified working 2026-04-24. Zero portproxy, zero socat, zero admin PowerShell after initial .wslconfig setup. Only prerequisite: Windows 11 22H2+.
One-time setup (survives reboot):
%USERPROFILE%\.wslconfig (Notepad, create if missing):
[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
firewall=truewsl --shutdownlocalhost:PORT = WSL localhost:PORT.claude mcp remove chrome-devtools -s local 2>/dev/null
claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest --browser-url=http://127.0.0.1:9222/exit + reopen Claude Code (MCP subprocess captures args at startup).Per-session (user runs this, PowerShell normal, NOT admin):
Start-Process "C:\Program Files\Google\Chrome\Application\chrome.exe" -ArgumentList '--remote-debugging-port=9222','--user-data-dir=C:\Temp\chrome-debug-profile','https://dev-app.nicolify.com'Profile at C:\Temp\chrome-debug-profile isolates session (Chrome 136+ refuses remote debugging on default profile). Clerk session persists across restarts in this profile — login once, reuse.
WSL verification:
curl -s http://127.0.0.1:9222/json/version | head -3Expected: JSON with "Browser": "Chrome/...".
| Attempt | Outcome | Root cause |
|---|---|---|
npx @dbalabka/chrome-wsl | "Empty reply from server" | Sets portproxy v4tov4 → 127.0.0.1. Chrome Windows bindes ::1 IPv6. Connect reaches nothing. |
Manual portproxy v4tov6 → ::1 | Works temporarily | Requires PowerShell admin every reboot; brittle; breaks if Chrome relaunched on different address. |
--remote-debugging-address=0.0.0.0 + firewall rule | Works but requires admin firewall setup | Extra attack surface, still needs portproxy if Windows < 22H2. |
.wslconfig networkingMode=mirrored (this path) | Stable, zero-admin per session | Mirrored mode makes Windows localhost directly reachable from WSL. Requires Win 11 22H2+ only. |
& "path with spaces" -flag=value — WORKS when & is first token."path with spaces" --flag=value — FAILS: PS reads the string as literal, interprets --flag as operator. Use Start-Process or prefix &.$env:TEMP\chrome-debug-profile inside a double-quoted PowerShell argument gets expanded differently than inside a single-quoted one. Prefer literal paths (C:\Temp\chrome-debug-profile) — immune to expansion surprises.Start-Process ... -ArgumentList '--flag=val','--flag2=val2','url' — the cleanest cross-PS-version spawn. Use this over raw & chrome.exe.Only use if wsl --version shows Windows < 22H2. Launch Chrome with --remote-debugging-address=0.0.0.0, open firewall, hit http://$WIN_GW_IP:9222 from WSL (where WIN_GW_IP=$(ip route show default | awk '{print $3}')). Not recommended for repeat use.
claude mcp add/remove change needs /exit + reopen. Don't retry tool calls hoping the config reloaded.Start-Process -ArgumentList or & chrome.exe ....--user-data-dir=C:\Temp\chrome-debug-profile (or similar isolated path).--headless=new works for static sites but Clerk sign-in has CF bot detection → painful. Use headful bridged Chrome, persist session via user-data-dir.npx @dbalabka/chrome-wsl portproxy is wrong. Sets v4tov4 → 127.0.0.1; Chrome Windows listens on ::1. Result: "Empty reply from server". Do NOT use this helper — use STABLE PATH above.click times out, fall back to evaluate_script with document.querySelectorAll('button').find(b => b.textContent === '…').click().navigate_page reload throws timeout ~10s but page still reloads — follow with wait_for instead of trusting the error.7_* → 11_*) resets. Always re-snapshot.1. list_pages → find dev-app tab, confirm URL
2. take_snapshot → get current UIDs
3. fill / click → drive the user action that reproduces
4. wait_for + take_snapshot → confirm the visible state after
5. list_network_requests → catch 4xx/5xx polls, wrong URLs
6. list_console_messages → client errors
7. DB cross-check:
- docker exec visionarias_postgres psql -U postgres -d visionarias_logs -c "SELECT … FROM copilot_trace_event WHERE conversation_id=…"
- Pills land in `copilot_conversations.messages` as assistant msgs with `blocks[].card_kind`
8. Fix, restart affected container (brain/worker), hard reload tab:
- navigate_page type=reload ignoreCache=true
9. Repeat 1–6 to verify.// Click by visible text (bypasses stale uids)
() => {
const btn = Array.from(document.querySelectorAll('button'))
.find(b => b.textContent?.trim() === 'Empezar desde cero');
btn?.click();
return btn ? 'clicked' : 'not found';
}
// Inspect Zustand copilot store from devtools
() => {
const store = window.__COPILOT_STORE__;
if (!store) return 'store not exposed';
const s = store.getState();
return {
msgCount: s.messages.length,
activeJobs: Object.keys(s.activeJobs),
status: s.status,
};
}
// Raw fetch with session cookie (bypasses auth helpers)
async () => {
const res = await fetch('/api/v1/brand/tools/extract-full-brand/status/<jobId>', { credentials: 'include' });
return { status: res.status, body: await res.text() };
}list_network_requests for the poll endpoint. 404s = route mismatch (verify /tools/ prefix). All 200s = FE invalidation issue.block.payload.card_status. FE must auto-resolve historical cards (see resolveHistoricalCards in CopilotChatPanel.tsx).toolCalls lost on hydration because wire ConversationMessage doesn't carry tool_calls. Render chip from activeJobs store via ActiveJobsPoller, not from message.toolCalls.clarify call before extraction dispatched. Fix in copilot_system.j2 — explicit rule: clarify only ⇒ no "inicié/comencé" text.brand_tools mounted at /api/v1/brand/tools/…. Tool hardcodes poll_endpoint in copilot/application/tools/extraction_tools.py — must match the mount prefix.# Latest conversation for tenant
docker exec visionarias_postgres psql -U postgres -d visionarias_logs -t -A -F'|' -c \
"SELECT id, LEFT(title,60) FROM copilot_conversations WHERE tenant_id='<uuid>' ORDER BY updated_at DESC LIMIT 3;"
# Trace events for a conversation
docker exec visionarias_postgres psql -U postgres -d visionarias_logs -t -A -F'|' -c \
"SELECT event_type, name, status, LEFT(data::text, 100) FROM copilot_trace_event WHERE conversation_id='<uuid>' ORDER BY created_at;"
# Check Redis progress key
docker exec visionarias_redis redis-cli -n 0 GET 'brand_extract:<tenant>:<job_id>'When fix verified live, stop using MCP tools unless needed again. Don't leave polling loops running. If you ran a new conversation that littered DB, optionally note it in the summary — no cleanup required (dev env).
d31f7bc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.