| name | debug-ui |
| description | Drive and visually QA the dev-3.0 UI in a real browser (headless Chromium via agent-browser). Use when verifying a UI/UX change, reproducing a visual bug, taking screenshots of the running app, or self-QA before review. Triggers — "check the UI", "screenshot the app", "does this render", "QA this screen", "verify the UI change in a browser", "drive the app". |
debug-ui — QA the dev-3.0 UI in a real browser
See and drive the running dev-3.0 UI in headless Chromium — click, type, screenshot, read
console errors — instead of guessing whether a UI change works. No desktop/native dependency;
it works the same in a plain terminal session.
This is dev-internal tooling for the dev-3.0 repo — NOT one of the skills dev3 ships to
its users (those live in src/bun/agent-skills.ts).
The whole flow
This task's dev-server is the web UI: bun run dev serves the full app in local remote
mode at a stable per-machine token and a CLI-derivable port — no separate dev3 remote. The
loop is always the same four beats: values → server → browser → clean up.
export AGENT_BROWSER_SESSION="dev3-${DEV3_TASK_ID%%-*}"
CODE=$(cat "$HOME/.dev3.0/dev-web-access-code" 2>/dev/null || bun scripts/dev-web-code.ts)
PORT=${DEV3_PORT0:-$(dev3 dev-server status | grep -oE 'DEV3_PORT0=[0-9]+' | cut -d= -f2)}
dev3 dev-server start
until curl -sf "http://localhost:$PORT/?token=$CODE" >/dev/null; do sleep 2; done
agent-browser set viewport 1440 900
agent-browser open "http://localhost:$PORT/?token=$CODE&streamer=on"
agent-browser wait --load networkidle
sleep 2
agent-browser snapshot -i -d 6
agent-browser screenshot "/tmp/dev3-ui-${DEV3_TASK_ID%%-*}.png"
agent-browser errors
agent-browser close
dev3 dev-server stop
That's it. DEV3_REMOTE_PORT=${DEV3_PORT0:-0} is wired into the repo's dev script and
portCount: 1 is committed in .dev3/config.json, so the dev app binds the exact port shown
above (see decision 093).
Scoped QA: a throwaway board instead of the real one
dev3 dev-server start boots a full dev3 instance on your real board — another task's
"Branch Merged — mark completed?" dialog is live and clickable in your browser, and another task's
terminal is reachable by navigation. For anything that does not specifically need real data, run the
scoped instance instead:
dev3 pane run "cd $PWD && bun run dev --qa"
dev3 pane run "cd $PWD && bun run dev --qa=virgin"
dev3 pane logs <run-id>
It binds the same DEV3_PORT0 and prints the port, so steps 3–4 of the flow above are unchanged.
The scoped root is stable per worktree, so a restart reuses the same board; rm -rf the printed
root to start over.
What it does NOT isolate — name these rather than assuming a clean room: the tmux socket
directory (only TMUX_TMPDIR moves it, and dev3 sets it nowhere), the PowerShell history file on
Windows, the user's own ~/.codex / ~/.claude agent configs, and dev3 CLI commands run from
inside a REAL worktree (cwd-based task detection outranks $DEV3_HOME by design).
Gotchas
-
The browser is a machine-global singleton — isolate per task or agents stomp each other.
Every agent-browser call with no session lands in one shared "default" session: one
browser process, one global viewport. When two task agents QA at the same time they collide
— agent B's open silently replaces agent A's page, so A's next screenshot captures B's
UI. The fix is step 1: export AGENT_BROWSER_SESSION="dev3-${DEV3_TASK_ID%%-*}" gives each
task its own isolated session/profile (verify with agent-browser session / session list), and agent-browser close then closes only your session. The Bash tool
reinitializes the shell per call, so an export does not carry across separate
invocations — the line derives from the always-present $DEV3_TASK_ID, so just repeat it
at the top of each block, or pass --session "dev3-${DEV3_TASK_ID%%-*}" on every command.
(If you ever must share one browser machine-wide instead, serialize QA across agents so only
one drives at a time.)
-
The dev-server is a build snapshot — no watch/HMR. Your code only appears after a
(re)start. After changing code, dev3 dev-server restart, re-wait for the port, then
agent-browser reload — a bare reload re-serves the old bundle. Don't keep a stale server
around; never hand-run vite build.
-
Is the running build actually yours? An already-running app/remote is often production or
another worktree — it won't have your changes. (Re)start THIS task's dev-server and confirm
with dev3 --version (commit hash should match git log -1) + that your change actually
renders. Don't assume.
-
Tell the user before dev3 dev-server start (visible side effect), and stop it after
(step 4) unless they want it kept.
-
No DEV3_PORT0? (portCount 0, or an older worktree where it was never allocated) — run
your own fixed-port server instead:
dev3 remote --no-detach --no-tunnel --static-code $CODE --port 47823 → :47823/?token=$CODE.
-
Every screenshot in streamer mode — no exceptions by default. The app shows the
developer's REAL identity (account emails, orgs, home-dir paths, tunnel URLs, QR codes), and
a QA screenshot easily ends up in a PR/issue/recording. in the URL forces the
privacy masking on for the whole session (it persists in this session's localStorage;
forces it back off). The masking is CSS blur on elements
— see and decision 161. Only capture unmasked when the task
is explicitly about those values (e.g. testing the accounts UI itself), and say so when
presenting the image.