Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a7d5a69
feat(react): /react-doctor umbrella skill, in-house browser core, and…
aidenybai Jun 19, 2026
5b1dc39
feat(react): add `react-doctor mcp` Model Context Protocol server
aidenybai Jun 19, 2026
a3484a9
refactor(mcp): collapse the read-only browser tools into a registrati…
aidenybai Jun 19, 2026
29178ba
fix(mcp): harden debug fetch + clear viewport override (thermos review)
aidenybai Jun 19, 2026
bbf744e
refactor(cli): reuse the shared Viewport type for --viewport
aidenybai Jun 19, 2026
068f3b2
fix(mcp): allowlist debug endpoints + guard non-loopback bind (thermo…
aidenybai Jun 19, 2026
7665dad
refactor(browser): encapsulate playwright/axe laziness in the package
aidenybai Jun 19, 2026
c51eed3
feat(browser): add combined React + CPU profiler
aidenybai Jun 23, 2026
a7c4d4a
fix(cli): allowlist browser profile's --interaction flag (bugbot)
aidenybai Jun 23, 2026
c4e5658
refactor: drop comments that restate names or duplicate doc
aidenybai Jun 23, 2026
35e3661
fix(browser): stop the React profiler when a profiled interaction throws
aidenybai Jun 23, 2026
b02f102
refactor(browser): collapse the capture commands into eval --profile
aidenybai Jun 24, 2026
d47f33e
fix(browser): stop V8 sampling on the error path in inspect
aidenybai Jun 24, 2026
7f6f088
feat(browser): capture a DevTools timeline trace in eval --profile
aidenybai Jun 24, 2026
08c8da4
fix(browser): harden inspect from dogfooding eval --profile
aidenybai Jun 24, 2026
cdcf38c
fix(browser): render environment failures as actionable user errors
aidenybai Jun 24, 2026
4d2de17
fix(browser): add the missing close-launched-browser source files
aidenybai Jun 24, 2026
7cc684f
feat(browser): memory snapshot + headless launch/close + richer network
aidenybai Jun 24, 2026
e5dfeb8
feat(browser): make eval self-reporting and consolidate the skill
aidenybai Jun 24, 2026
ffa57e8
Merge remote-tracking branch 'origin/main' into feat/react-skill
aidenybai Jun 24, 2026
f4d735b
fix(browser): don't orphan launched Chrome on attach/close failure (b…
aidenybai Jun 24, 2026
f13c069
fix(browser): forget a dead launched endpoint after falling back (bug…
aidenybai Jun 24, 2026
909957b
fix(react): force-inline @react-doctor/browser into the CLI and MCP b…
aidenybai Jun 24, 2026
8461ca8
fix(browser): keep eval/profile diagnostics when the driven action fa…
aidenybai Jun 24, 2026
815d4d4
fix(browser): scope eval --profile LoAF/CLS to the post-action window…
aidenybai Jun 24, 2026
e5718ef
feat(browser): add eval --codegen (emit Playwright tests) and --video…
aidenybai Jun 24, 2026
12288a8
fix(browser): align profile windows + raise react-doctor playwright f…
aidenybai Jun 24, 2026
a626a7f
fix(cli): force-bundle @react-doctor/debug into the CLI (windows pack…
aidenybai Jun 24, 2026
82b431e
feat(install): make pkg.pr.new preview builds self-referential
aidenybai Jun 24, 2026
b918c46
fix(browser): make profile vitals navigation-aware + filter LCP (bugbot)
aidenybai Jun 24, 2026
d64c7ff
ci(smoke): verify the Linux-packed CLI tarball on Windows/macOS
aidenybai Jun 24, 2026
48dad7f
fix(ci): partition build cache by OS so a divergent build can't poiso…
aidenybai Jun 24, 2026
f2c1d54
fix(browser): don't adopt a foreign default-port Chrome over our laun…
aidenybai Jun 24, 2026
9417c96
fix(ci): publish deslop-js to pkg.pr.new so previews install
aidenybai Jun 24, 2026
a83df6d
chore(install): disable optional pre-commit + agent-hook setup
aidenybai Jun 24, 2026
2baa9c7
docs(skill): drop the non-existent /doctor command reference
aidenybai Jun 24, 2026
3d46a73
chore(deps): upgrade agent-install to 0.0.8
aidenybai Jun 24, 2026
5b4136c
feat(install): add global vs project skill install choice
aidenybai Jun 24, 2026
135fa5d
fix(browser): set perf recording marker without swallowing failures
aidenybai Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/react-doctor
51 changes: 0 additions & 51 deletions .agents/skills/react-doctor/SKILL.md

This file was deleted.

72 changes: 0 additions & 72 deletions .agents/skills/react-doctor/references/explain.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/browser-eval-codegen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Add `--codegen` to `browser eval` (and `codegen: true` to the `browser_eval` MCP tool): drive a Playwright expression as usual, then write it as a runnable Playwright regression test. The generated spec navigates to the page the session is on, replays the action, and asserts no console or page errors fired — the same signal `eval` already reports — so a verified interaction becomes a guarded test in one step. Writes to `--out` (default `react-doctor.spec.ts`); a failing action throws instead of writing a green-looking test.
5 changes: 5 additions & 0 deletions .changeset/browser-eval-errors-geometry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Make `browser eval` and `browser eval --profile` self-reporting about what an action did to the page. A driven action that triggers a page-side error (a `console.error` or an uncaught throw) now appends an "Errors during eval" section instead of failing silently, so a broken interaction surfaces without hand-wiring a console hook — including when the action itself throws (a missing locator, a timeout), where those page errors are appended to the thrown message rather than dropped. `--profile` no longer throws the whole recording away when the action fails: it returns the captured picture (console, network, CPU, timeline, React) with the failure as an `evalError`, since that picture is the failure's context. `--profile` (and the `browser_profile` MCP tool) now reports page geometry alongside memory — viewport size, devicePixelRatio, scroll offset, and how far the page scrolled while the action ran — so "did the element move, or did the page scroll under me?" is answerable from the output. Page scroll delta only prints when the viewport actually moved. Console and network capture now starts after the page settles, so the recording covers the driven action's window rather than pre-action load traffic.
5 changes: 5 additions & 0 deletions .changeset/browser-eval-snapshot-ergonomics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Make `browser eval` the one primitive for driving a page: when an expression just acts (returns nothing), it now hands back the resulting accessibility tree, so a single call both drives the page and shows the new state — no follow-up `snapshot`. Multi-statement source works without hand-wrapping it in an async IIFE, and a page-context `ReferenceError` (`window is not defined`) now explains that `eval` runs in Node with the Playwright `page` in scope and to reach page globals through `page.evaluate(() => …)`. The same applies to the `browser_eval` MCP tool. Locating stays pure Playwright — `browser snapshot`, or `page.locator(...).ariaSnapshot()` inside `eval` for a subtree.
5 changes: 5 additions & 0 deletions .changeset/browser-eval-video.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Add `--video [path]` to `browser eval` (and `video: "<path>.webm"` to the `browser_eval` MCP tool): record a `.webm` screen recording of the page while the expression runs, for playback. It works in any mode — plain `eval`, `--profile`, and `--codegen` — so a profiled run or a generated regression test can ship with a video you watch to verify what happened, and the saved path is reported in the summary (returned as `video` from the MCP tool). Uses Playwright's imperative screencast (1.59+), the only video API that records a CDP-attached page; encoding needs Playwright's bundled ffmpeg, so a missing one surfaces an actionable `npx playwright install ffmpeg` hint. Bumps the `playwright-core` floor to `^1.59.0` for the screencast API.
5 changes: 5 additions & 0 deletions .changeset/install-global-skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Add `--global` to `react-doctor install` (and an interactive "Where should the skill be installed?" prompt): install the `/react-doctor` skill into each agent's home directory (`~/.cursor`, `~/.claude`, …) so it applies to every project, instead of only this repo's local agent dirs. The default stays project-local; `--global` opts in, and non-interactive runs (`--yes`) remain local unless `--global` is passed.
5 changes: 5 additions & 0 deletions .changeset/react-browser-debug-skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-doctor": patch
---

Add the `browser`, `debug`, and `mcp` commands behind the unified `/react-doctor` skill. `browser` drives a real Chrome over CDP (attaching to your running session, launching a dedicated persistent profile only as a fallback): `open` a page, `eval` a Playwright expression, `snapshot` the accessibility tree, and `screenshot`. Adding `--profile` to `eval` records the whole runtime picture in one pass while the expression runs — console, network, performance (long animation frames with per-script attribution, LCP, CLS, plus a DevTools timeline roll-up of forced style-recalc/layout/hit-test/paint cost), an axe-core accessibility audit, a React render profile (slowest commits, hottest components by self time, unnecessary re-render counts), and a Chrome DevTools CPU profile via V8's sampling profiler over CDP (the hottest JS functions ranked by self time). It also writes the raw DevTools timeline trace to a file (`--out`, default `react-doctor-trace.json`) that loads in the DevTools Performance panel. `debug` runs an NDJSON logging server the debug job posts runtime evidence to. `mcp` runs a Model Context Protocol server over stdio that exposes the doctor scan and the browser/debug jobs as MCP tools, so any MCP-capable agent can run `react-doctor mcp` and call `doctor_scan`, the `browser_*` tools (`browser_eval` takes a `profile: true` argument that captures every signal together), and the `debug_*` log server directly.
60 changes: 58 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,27 @@ jobs:
pnpm build
pnpm check:published-deps

# Pack the publishable tarballs ONCE on Linux — the OS the release and the
# pkg.pr.new preview actually build on — then verify the install here and
# hand the same artifact to the windows/macos `smoke-packed-cli-cross-os`
# job. A per-OS rebuild would test a bundle that never ships (Windows
# rollup intermittently externalizes a private workspace dep), so this
# tests the real artifact on every OS instead of a per-OS false negative.
- name: Smoke test packed CLI install
if: ${{ matrix.node-version == '22.18.0' && (matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest') }}
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == '22.18.0' }}
run: |
pnpm build
pnpm smoke:packed-cli-install
pnpm smoke:packed-cli-install --pack-only "${{ runner.temp }}/packed-cli-tarballs"
pnpm smoke:packed-cli-install --tarballs "${{ runner.temp }}/packed-cli-tarballs"

- name: Upload packed CLI tarballs for cross-OS smoke
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == '22.18.0' }}
uses: actions/upload-artifact@v5
with:
name: packed-cli-tarballs
path: ${{ runner.temp }}/packed-cli-tarballs/*.tgz
retention-days: 1
if-no-files-found: error

# Allocates a real pseudo-terminal (`pty.openpty()`) so the CLI sees an
# interactive TTY and renders the multiselect prompt, then asserts the
Expand All @@ -133,3 +149,43 @@ jobs:
- name: Smoke test interactive TTY prompt
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == '22.18.0' }}
run: pnpm smoke:tty-prompt

# Install the Linux-packed tarballs (the exact artifact npm + pkg.pr.new ship)
# on Windows and macOS. The CLI bundle is platform-independent JS, so the only
# per-OS variable is install + native-binding resolution + runtime — which is
# what we want to test, without a per-OS rebuild's bundling drift.
smoke-packed-cli-cross-os:
needs: test
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false

- uses: pnpm/action-setup@v5

- uses: actions/setup-node@v5
with:
node-version: "22.18.0"
cache: pnpm

- run: pnpm install --frozen-lockfile --prefer-offline

# The verify step validates the CLI's JSON output against
# @react-doctor/core/schemas, so only core needs building here; the CLI
# under test comes from the downloaded Linux-packed tarball, never a local
# build of the package whose Windows bundle is the thing we're avoiding.
- run: pnpm --filter @react-doctor/core build

- uses: actions/download-artifact@v5
with:
name: packed-cli-tarballs
path: ${{ runner.temp }}/packed-cli-tarballs

- name: Smoke test packed CLI install
run: pnpm smoke:packed-cli-install --tarballs "${{ runner.temp }}/packed-cli-tarballs"
16 changes: 15 additions & 1 deletion .github/workflows/publish-any-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,31 @@ jobs:

- run: pnpm install --frozen-lockfile --prefer-offline

# Bake the preview's own immutable pkg.pr.new URL into the build so the
# shipped skill's `npx` commands and `react-doctor install` reference this
# exact commit — a beta tester then exercises the previewed branch instead
# of silently falling back to the published `react-doctor@latest`.
- run: pnpm build
env:
REACT_DOCTOR_PACKAGE_SPECIFIER: https://pkg.pr.new/react-doctor@${{ github.sha }}

- name: Publish packages (retry on transient failures)
run: |
max_attempts=4
attempt=1
while [ "$attempt" -le "$max_attempts" ]; do
# deslop-js MUST be published too: react-doctor depends on it
# (`deslop-js: workspace:*`), and pkg-pr-new only rewrites a
# workspace dep to a preview URL when that package is in the publish
# set. Omitting it shipped a tarball with a raw `workspace:*` spec
# that `npx https://pkg.pr.new/react-doctor@<sha> install` rejected
# with EUNSUPPORTEDPROTOCOL. (The private @react-doctor/* workspace
# devDependencies stay `workspace:*` but are ignored on install.)
if pnpm dlx pkg-pr-new publish \
./packages/react-doctor \
./packages/oxlint-plugin-react-doctor \
./packages/eslint-plugin-react-doctor; then
./packages/eslint-plugin-react-doctor \
./packages/deslop-js; then
exit 0
fi

Expand Down
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ review-*.md
/.agents/*
!/.agents/skills/
/.agents/skills/*
!/.agents/skills/react-doctor/
!/.agents/skills/react-doctor/**
!/.agents/skills/react-doctor
!/.agents/skills/rule-research/
!/.agents/skills/rule-research/**
!/.agents/skills/rule-writing/
Expand Down Expand Up @@ -46,3 +45,8 @@ review-*.md
/scripts/print-batch-input.mjs
/scripts/rule-prompts/
/rules.json

# Local-only artifacts written by `react-doctor browser` (timeline trace,
# screenshot) when run from the repo root during development.
/react-doctor-trace.json
/react-doctor-screenshot.png
32 changes: 32 additions & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@react-doctor/browser",
"version": "0.5.4",
"private": true,
"description": "Internal: React Doctor's browser driver. Attaches to a running Chrome over CDP (or launches one) and keeps the page open across commands, backing the debug and design jobs.",
"license": "MIT",
"type": "module",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && cross-env NODE_ENV=production vp pack",
"typecheck": "tsc --noEmit",
"test": "vp test run"
},
"dependencies": {
"axe-core": "^4.10.2",
"playwright-core": "^1.59.0"
},
"devDependencies": {
"@types/node": "^25.6.0",
"esbuild": "^0.25.12",
"react-devtools-inline": "^6.1.5"
},
"engines": {
"node": "^20.19.0 || >=22.13.0"
}
}
Loading
Loading