---
name: ux-audit
description: "Walk through a product UI as a real user — take screenshots, find broken flows, and produce a structured report with every fix listed. Use when: audit the UI or UX, do a UX review, QA a feature, check if something looks right, verify a user flow or onboarding, walk through a journey, 'is X broken?', 'check how X works', 'verify the feature we shipped'. Do NOT use for: fixing a specific known bug, reading a component's code, deploying, writing tests, explaining how an API works, or answering questions about code structure."
compatibility: Requires the `thinkrun` CLI (cloud or local mode) or an equivalent browser-control tool with navigate + click + type + screenshot + evaluate.
metadata:
  version: 1.0.0
---

# UX Audit Skill

You are a world-class UX designer and QA engineer. Your job is to walk through a
user journey on a web app using a real browser session, take screenshots
at every meaningful step, and produce a structured UX report with a complete fix list.

---

## Mode Selection

**Cloud mode** — use for public-facing URLs, no login required, or when you want an isolated, disposable browser:
- Provisions an isolated browser session in the cloud via `thinkrun cloud start`
- Best for: marketing pages, public flows, pre-auth screens

**Local mode** — use when the app needs your real login/cookies (an app behind auth you're already signed into):
- Controls your actual browser via the ThinkRun extension + native host
- All commands operate on the tab you attach to
- Best for: any app behind a login, internal dashboards, staging environments only reachable from your machine

---

## Step 0 — Configure the Audit

```bash
PRODUCT="<product name>"
PRODUCT_URL="<https://your-app.example.com>"  # or http://localhost:<port>, e.g. http://localhost:3000
AUDIT_DATE=$(date +%Y-%m-%d)
JOURNEY="<one-line description of the journey to test>"
```

If testing locally, check what's actually on that port before assuming the right app is there (replace 3000 with your port):
```bash
lsof -iTCP:3000 -sTCP:LISTEN 2>/dev/null | head -3
```

---

## Step 1 — Start Session

### Cloud Mode (no auth required)

```bash
thinkrun cloud start
thinkrun navigate "$PRODUCT_URL"
sleep 2
```

`thinkrun cloud stop` ends the session when you're done. Use `thinkrun cloud list` / `thinkrun cloud use <id>` if you need to juggle more than one session.

### Local Mode (auth required)

```bash
# 1. Health check first
thinkrun doctor

# 2. Open a dedicated window — avoids clashing with other agents or tabs you already have open
thinkrun new-window "$PRODUCT_URL"

# 3. List tabs to confirm the new tab is attached
thinkrun tabs
```

`new-window` opens a fresh window and auto-attaches to it, so no other agent or human can accidentally navigate away mid-audit. If `TAB_OWNED_BY_OTHER_SESSION` appears, another session already owns that tab — run `thinkrun session debug --json` to see who, then open a new window and attach to that instead. `TAB_NOT_OWNED` is different: it means *this* session was never attached to any tab — run `thinkrun attach <tabId>` first.

Once attached, all subsequent commands route to that tab automatically — no session ID needed.

---

## Step 2 — Helper Functions

```bash
# Navigate and wait for page load
nav() {
  thinkrun navigate "$1"
  sleep 2
}

# Screenshot — saves to /tmp, read the path however your agent harness views images
shot() {
  local label="$1"
  thinkrun screenshot --output "/tmp/ux_${label}.png"
  echo "Screenshot: /tmp/ux_${label}.png"
  # Then view that image path however your agent harness supports (e.g. Claude Code's Read tool)
}

# Click by CSS selector
click_sel() {
  thinkrun click "$1"
  sleep 1
}

# Click by visible button/link text (avoids ambiguous selectors)
# Encodes the label as a JSON string first so quotes/backslashes in the text
# can't break out of the evaluated script.
click_text() {
  local label_json
  label_json=$(printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
  thinkrun evaluate "[...document.querySelectorAll('button,a,[role=button]')].find(el=>el.textContent.trim()===${label_json})?.click()"
  sleep 1
}

# Fill a plain HTML input
fill_field() {
  thinkrun fill "$1" "$2"
}

# Get current URL
current_url() {
  thinkrun url
  # Returns: {"data": "http://..."} — data is a plain string
}

# Scroll the page
scroll_down() {
  thinkrun scroll --down "${1:-500}"
}

scroll_up() {
  thinkrun scroll --up "${1:-500}"
}

# Extract visible text
read_text() {
  thinkrun evaluate "([...document.querySelectorAll('h1,h2,h3,p,label,span')].map(e=>e.textContent.trim()).filter(t=>t.length>4).join('\n')).slice(0,2000)"
}
```

---

## Step 3 — Walk the Journey

For each step:
1. Perform the action (navigate, click, type, scroll)
2. Wait for state to settle (`sleep 1` or `sleep 2`)
3. Take a screenshot: `thinkrun screenshot --output "/tmp/ux_NN_label.png"`
4. View the screenshot (e.g. with Claude Code's Read tool) to observe it
5. Note: **what you see** vs **what you expected**

### React Input Pattern (use this for any React-controlled form)

`thinkrun fill`/`type`, or a plain JS `value=` assignment, won't reliably trigger React's `onChange` — React tracks the native input event, not the property write. Use the native setter trick:

```js
thinkrun evaluate "
const input = document.querySelector('input[type=email]');
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
setter.call(input, 'user@example.com');
input.dispatchEvent(new Event('input', {bubbles: true}));
"
```

Repeat for each field, then click the submit button. If `thinkrun click` is a no-op, use evaluate to click instead:
```js
thinkrun evaluate "document.querySelector('button[type=submit]').click()"
```

### Standard Journey Template

```bash
# 1. Landing / Homepage
thinkrun navigate "$PRODUCT_URL"
sleep 2
thinkrun screenshot --output "/tmp/ux_01_homepage.png"
# → View: /tmp/ux_01_homepage.png
# Observe: CTA clarity, value proposition, onboarding path

# 2. Auth / Login (if required)
thinkrun navigate "${PRODUCT_URL}/login"
sleep 1
thinkrun screenshot --output "/tmp/ux_02_login.png"
# → View: /tmp/ux_02_login.png

# An agent should never type a real password. Hand off and wait:
# "I'm at the login screen — please sign in, then let me know when you're done."
# Only fill the password field yourself if the app gives you a dedicated
# test/staging account made for automated checks — never a real user's credentials.
thinkrun screenshot --output "/tmp/ux_03_post_login.png"
# → View: /tmp/ux_03_post_login.png
# Check: Did login succeed? What URL are we on?
thinkrun url

# 3. Core Action
thinkrun screenshot --output "/tmp/ux_04_core_action_start.png"
# ... perform the action ...
sleep 2
thinkrun screenshot --output "/tmp/ux_05_core_action_result.png"

# 4. Scroll to see full content
thinkrun scroll --down 600
thinkrun screenshot --output "/tmp/ux_06_scrolled.png"

# 5. Error Path (submit empty form, invalid input, etc.)
# navigate back, try invalid actions...
thinkrun screenshot --output "/tmp/ux_07_error_state.png"

# 6. Accessibility snapshot (reveals element structure, roles, text)
thinkrun snapshot
```

**Mobile caveat**: the CLI has no device-emulation flag, so a desktop screenshot can't answer "does the layout hold on mobile." Score the Mobile dimension only if you can actually narrow your own browser window to ~375-400px (local mode) or check on a real device — otherwise mark it "not tested" rather than guessing from a desktop-sized screenshot.

---

## Step 4 — Observation Framework

At each screenshot, read the image and score against these dimensions:

| Dimension | Questions to Ask |
|-----------|-----------------|
| **Clarity** | Is it immediately obvious what to do next? |
| **Feedback** | Does the UI communicate what's happening? |
| **Progress** | Can the user tell how far along they are? |
| **Output** | Is the result visible and readable? |
| **Error visibility** | Are failures explained clearly, with a recovery path? |
| **Navigation** | Easy to go back, forward, or to related sections? |
| **Performance feel** | Does it feel fast or sluggish? Is there a loading indicator? |
| **Empty states** | What does a new user see before any data exists? |
| **Mobile** | Does the layout hold at 375px? — only score this if you actually narrowed the viewport or tested on a real device; otherwise mark "not tested" |

Rate each: Good / Needs work / Broken

---

## Step 5 — CLI DX Checklist (if the product ships its own CLI)

```bash
PRODUCT_CLI="<your-cli-name>"   # leave empty to skip

if [ -n "$PRODUCT_CLI" ]; then
  $PRODUCT_CLI --help
  $PRODUCT_CLI --version
fi
```

---

## Step 6 — Report Format

```
## UX Audit: <PRODUCT> — <YYYY-MM-DD>

### Journey Tested
<one-line description>

### Journey Map
| Step | Expected | Actual | Status |
|------|----------|--------|--------|
| 1. Homepage | Clear CTA | ... | Good/Needs work/Broken |

### Critical Issues
- **Issue title**: Description. Screenshot: `ux_07_error_state.png`. Impact: ...

### Moderate Issues
- **Issue title**: Description. Screenshot: `ux_03_post_login.png`. Impact: ...

### What Works Well
- Feature and why it works well.

### Scores by Dimension
| Dimension | Score | Notes |
|-----------|-------|-------|
| Clarity | Good | ... |
| Feedback | Broken | ... |

### All Fixes (Priority Order)
List every actionable fix found — not just the top 3. Include file/location where known.

1. **Fix title** — file: `path/to/file.ts:NN`. Rationale and expected impact.
2. **Fix title** — file: `path/to/file.tsx:NN`. Rationale and expected impact.
3. (continue for all issues found)
```

---

## Known Gotchas

- **React inputs** — see "React Input Pattern" in Step 3. Standard `fill`/`type` often fails; use the native setter trick.
- **Screenshot returns a local file path** — `{"data": {"path": "/tmp/thinkrun-xxx.png"}}`. View that path however your agent harness supports (e.g. Claude Code's Read tool).
- **`thinkrun url` returns a plain string** — `{"data": "http://..."}` not `{"data": {"url": "..."}}`. Parse as `d['data']`.
- **Scroll syntax** — `thinkrun scroll --down 500` (not `scroll down 500`)
- **Never use a generic `button` selector when multiple buttons exist** — use `button[type=submit]` or the `click_text` helper
- **`evaluate` can time out** — keep scripts short. Avoid `document.body.innerText` on large single-page apps; use targeted selectors instead
- **CSRF-protected forms** — some auth forms need a CSRF token that must be fetched before submit. Use `sleep 2` after navigation before filling.
- **Tab becoming unresponsive (local mode)** — if commands time out, use `thinkrun tabs` to find a responsive tab, then `thinkrun attach <newTabId>` to switch
- **`switch-tab` ≠ `attach`** — `switch-tab` changes the browser's visually active tab; `attach` changes which tab receives CLI commands. You need both when switching tabs.
- **Never type a real password for the user** — sign-in is always a human step; wait for the user to complete it, then continue.
