PR UI Compare
Show the fix instead of describing it. PR UI Compare records your app before and after a change and turns the two recordings into one labeled, side-by-side GIF or PNG that you can use for your PR.
Ask your agent for a comparison gif. The extension starts your app twice, once from your working tree and once from the branch or commit you name, plays the same clicks, scrolls, and resizes through both in a headless browser, and renders the recordings side by side in perfect sync. Drag the result straight into the PR description.

Features
- Baseline pinned to an exact commit SHA
- Before side built in a temporary Git worktree
- Repository and workspace never modified
- One Playwright scenario replayed in both versions
- Paired actions begin and end in lockstep
- Side-by-side layout, stacked for very wide focus regions
- Optional crop that follows a chosen element
- Synthetic pointer shown only during clicks and hovers
- Static PNG mode for settled states
- Framed panes labeled with short commit IDs
- Combined GIF plus synchronized Before and After GIFs
- Recordings kept in VS Code storage
- Ships the
/create-pr-ui-comparison Agent Skill and #createPrUiComparison tool
Requirements
- VS Code 1.125 or later
- A trusted local Git workspace
- The package manager and runtime required by the application
- Playwright-managed Chromium
- FFmpeg
Setup
- Install the extension.
- Run PR UI Compare: Install Managed Chromium from the Command Palette.
- Run PR UI Compare: Install FFmpeg, unless FFmpeg is already on PATH or set in
prUiCompare.ffmpegPath.
- Open and trust the workspace you want to record.
Steps 2 and 3 download up to 240 MB: about 196 MB for the browser shell and 43 MB for FFmpeg. Both are skipped when the machine already has them, FFmpeg when it is on PATH and the browser when another project already uses the same Playwright version.
The headless browser shell is stored in Playwright's user cache. Each Playwright version requires a matching browser revision, but the extension does not install the larger headed Chromium build or launch Google Chrome from /Applications by default.
FFmpeg is resolved from the prUiCompare.ffmpegPath setting first, then from PATH, then from the copy the install command downloads into the extension's global storage. Labels are drawn with the bundled Noto Sans font so output looks the same on every platform.
Motivation
Recording this by hand is tedious. Check out the old code, start the app, record, switch back, record again, edit the recordings, stitch them together, and yet, the two clips still never line up. With PR UI Compare it is one request, the recordings play in lockstep, and nothing in your repository is created or modified.
"Before" can be any branch, tag, or commit SHA. "After" is always your working tree exactly as it is, uncommitted edits included. Animated GIF is the default output. Ask for an image for things that are static or finished changing, and setup interactions can run first so a single PNG captures the settled state. And note that this extension is for producing visual artifacts. Agents should know not to use this for code comparison questions.
The workflow is short. Make a visible UI change, generate the comparison, review it in VS Code, then drop the exported GIF into the GitHub PR description. An open pull request is not required.
Agent usage
Ask Copilot in plain language. Mention whether you want a gif or an image, the change you want to see, and what to compare against:
Make a comparison gif of this dropdown fix against main.
I want a before and after image comparing the new card styling to what's on main.
The agent picks the tool up from the request. It does not need the extension named, and it works out the start command, readiness URL, and scenario actions by inspecting the project.
The agent sets outputMode to image for an explicit static request or when the subject is a settled state that holds still. Image mode accepts scenario actions, and they run as setup rather than as the subject. Both versions replay the same actions, then a single PNG captures the settled final state, so a panel that only appears after loading a document or opening an editor can still be compared as a still image. Use animation when the change the user needs to see is the motion itself, such as a transition, loading sequence, resize, zoom, or scroll. When the request is ambiguous, animation is always the default. The synthetic pointer is not drawn in static images.
Animated scenarios can use setupActions to reach the exact state where recording should begin. Setup actions run after the initial route reaches networkidle, but before focus-region sampling, the synchronization beacon, and the visible actions timeline. They replay identically in both versions and are omitted from the rendered GIF. Use explicit waitFor actions for application state because networkidle does not guarantee that delayed rendering, background data, or animations have settled, then add a short hold when the final setup state needs a fixed settling period.
{
"name": "Editor menu transition",
"setupActions": [
{ "type": "click", "locator": "role=button[name=\"Load document\"]" },
{ "type": "waitFor", "locator": "text=Loading", "state": "hidden" },
{ "type": "waitFor", "locator": "role=main", "state": "visible" },
{ "type": "hold", "durationMs": 300 }
],
"actions": [
{ "type": "click", "locator": "role=button[name=\"More actions\"]", "holdAfterMs": 1000 }
]
}
Setup supports page actions such as navigation, clicks, input, scrolling, resizing, waits, and holds. Camera zoom belongs in visible actions. Animation still requires at least one visible action so the GIF has a nonempty timeline.

Animated GIF frame rate is configurable from 5 to 30 fps and defaults to 24. Use 30 fps for fast visual events, short transitions, or detailed camera movement. Lower rates reduce file size for slow and simple motion. Static image mode does not accept frame rate.
Use colorScheme to load both browser contexts in light, dark, or system mode. It emulates prefers-color-scheme before navigation so pages initialize in the requested appearance. For a direct light-versus-dark image, set beforeColorScheme and afterColorScheme independently:
{
"outputMode": "image",
"beforeColorScheme": "light",
"afterColorScheme": "dark"
}
Per-side values override colorScheme. The default is system, which disables explicit color-scheme emulation.
The contributed skill tells the agent to inspect project scripts and lockfiles, choose stable Playwright locators, keep the scenario short, and request confirmation before project commands run.
The workflow does not create or modify files in the target repository. Agents should inspect the project read-only and pass a declarative scenario directly to the extension tool. If the tool is disabled or unavailable, the agent should stop and ask the user to enable it rather than generate a Playwright helper script.
Set focusLocator for a stable region such as role=navigation, data-testid=menu-bar, or #settings-panel. Auto layout remains side by side for ordinary desktop and mobile captures. A region such as a full-width menu bar switches to top and bottom only when its aspect ratio is greater than 3:1. An exact 3:1 region remains side by side.
Visible actions support goto, click, hover, fill, press, scroll, resize, zoom, waitFor, and hold. Use locator strings such as role=button[name="Menu"], text=Settings, and data-testid=profile-panel.
Zoom is a recording-camera effect and does not change page layout, browser zoom, or interaction coordinates. It smoothly moves toward a target element and remains active for following actions until another zoom changes it. Use scale 1 without a locator to return to the full frame:
[
{ "type": "zoom", "locator": "role=toolbar", "scale": 1.8, "durationMs": 900, "holdAfterMs": 400 },
{ "type": "click", "locator": "role=button[name=\"More actions\"]", "holdAfterMs": 1000 },
{ "type": "zoom", "scale": 1, "durationMs": 900, "holdAfterMs": 400 }
]
Camera movement uses cosine easing for a smooth arrival and departure. Moderate scales and short holds help viewers orient without making the comparison feel busy.
borderColor accepts a six-digit hex color and defaults to GitHub dark border #30363d. It colors each pane frame, the divider, and unused canvas exposed by resize movement. beforeLabelAlignment and afterLabelAlignment accept top-left, top-right, bottom-left, or bottom-right. Their defaults are top-left for Before and top-right for After. Labels use a renderer-owned 22-pixel minimum size so agents cannot produce unreadably small text.
Use resize actions whenever viewport dimensions are part of the behavior being demonstrated. This includes breakpoint changes, fluid reflow, text wrapping, overflow, sticky or fixed positioning, viewport units, resize observers, canvas sizing, sidebars, and layout stability. Resize is optional and should not be added to unrelated comparisons.
A resize action animates the page viewport while the recording canvas remains fixed at the largest dimensions used by the scenario. The sizes do not need to cross a CSS breakpoint:
[
{ "type": "hold", "durationMs": 500 },
{ "type": "resize", "width": 390, "height": 844, "resizeMode": "keep-right-edge-fixed", "durationMs": 800, "holdAfterMs": 1200 },
{ "type": "resize", "width": 1280, "height": 720, "resizeMode": "keep-right-edge-fixed", "durationMs": 800, "holdAfterMs": 800 }
]
Set resizeMode on every resize action. Each value directly names the invariant:
resizeMode: "keep-left-edge-fixed" allows only the right edge to move.
resizeMode: "keep-right-edge-fixed" allows only the left edge to move.
resizeMode: "keep-window-centered" moves both edges simultaneously at the same rate.
For example, use keep-right-edge-fixed when shrinking must slide the left edge right without moving the right edge. Older movingEdge and anchor payloads remain accepted for compatibility but are no longer exposed to agents.
For breakpoint fixes, start on one side of the breakpoint and cross it during the recording rather than showing only a fixed mobile or desktop state.
Manual usage
Run PR UI Compare: Create PR UI Comparison from the Command Palette. The wizard first asks for animated versus static output and System, Light, or Dark browser appearance, then asks for the baseline ref, start command, readiness URL, optional baseline install command, route, optional focus locator, and scenario actions as JSON.
Use {port} in commands and URLs:
npm run dev -- --port {port}
http://127.0.0.1:{port}
The manual wizard defaults to a two-second static recording. Agent usage is preferred for interaction scenarios.
MCP server
The same engine runs outside VS Code as an MCP server, so Claude Code, Codex, Cursor, and Zed can create comparisons too. The server speaks stdio and exposes three tools: create_comparison (the schema the VS Code tool uses), install_browser, and install_ffmpeg. It also returns server instructions at initialization, so clients that read that field get the guidance the extension contributes to VS Code chat.
Claude Code:
claude mcp add --scope user pr-ui-compare -- npx -y pr-ui-compare
Keep --scope user. The server records whichever repository you point it at, so it belongs to you rather than to one project, and user scope makes it available in every repository you open. Leaving the scope out registers it for the current repository only, which looks like the tool is missing everywhere else. Use --scope project instead to write a shared .mcp.json at the repository root for everyone working on that one repository.
Codex, in ~/.codex/config.toml:
[mcp_servers.pr-ui-compare]
command = "npx"
args = ["-y", "pr-ui-compare"]
startup_timeout_sec = 60
tool_timeout_sec = 1800
Raise tool_timeout_sec. Codex cancels a tool call after 60 seconds by default, and a comparison starts and records two applications, so it needs minutes.
VS Code, Cursor, and Zed:
{
"servers": {
"pr-ui-compare": {
"command": "npx",
"args": ["-y", "pr-ui-compare"]
}
}
}
Put that JSON in your user-level configuration rather than a workspace file, for the same reason: a workspace copy only works in that one project.
A client reads its MCP configuration when it starts, so restart it after adding the server.
The server compares the repository at its working directory. Pass --workspace /path/to/repo, set PR_UI_COMPARE_WORKSPACE, or send workspacePath with an individual create_comparison call when one server configuration serves several repositories.
Run the install_browser and install_ffmpeg tools once before the first comparison unless managed Chromium and FFmpeg are already present. They download about 240 MB in total, once per machine and not once per repository: the browser goes to Playwright's shared cache and FFmpeg to PR UI Compare storage, so every repository you compare afterwards reuses the same copies. A machine that already has the matching Playwright version downloads no browser at all. Artifacts are written under ~/.pr-ui-compare and never into the repository; PR_UI_COMPARE_STORAGE_DIR overrides the location and PR_UI_COMPARE_RETENTION_DAYS controls cleanup (default 7). PR_UI_COMPARE_FFMPEG points at a specific FFmpeg build, and PR_UI_COMPARE_ALLOW_SYSTEM_BROWSER=1 enables the Chrome and Edge fallback.
Every result carries a downscaled still of the comparison, which clients that render images show inline in the chat, alongside the full-resolution paths. When the MCP client runs inside a VS Code window and the extension is installed, the server also asks that window to open the same preview panel the VS Code tool opens, with its review and export actions. Set PR_UI_COMPARE_IDE_PREVIEW=0 to stop that, =1 to force it where VS Code is not detected, and PR_UI_COMPARE_URI_SCHEME to target a fork, such as cursor.
The remaining difference from the extension is confirmation, which belongs to the MCP client, so the server runs the start and install commands it is given. Long calls report progress, which clients display and use to keep the call from looking idle.
Storage
Raw captures, timing metadata, and rendered GIF or PNG files are written under VS Code workspace storage. They do not appear in Source Control. The preview exports the combined, Before, and After artifacts together.
prUiCompare.retentionDays keeps temporary sessions for 1 to 90 days. The default is 7 days.
prUiCompare.allowSystemBrowser enables Google Chrome or Microsoft Edge fallback. It is disabled by default. On macOS, leaving it disabled avoids App Management warnings caused when a VS Code child process launches an application from the system Applications folder.
Current limitations
- The After side is always the current working tree. To compare two commits or two branches, check one of them out and name the other as the baseline. Naming both sides in one request is not supported yet.
- Firefox and WebKit are not supported yet.
- Playback is synchronized by action segment. Sequential playback is not exposed yet.
- GitHub attachment upload remains manual. Save the GIF and drag it into the PR editor.
- Authentication, database fixtures, environment files, and backend state remain project-specific.
- Git submodule worktrees and Git LFS projects may require additional setup.
Development
npm install
npm run compile
npm test
npm run test:e2e
npx vsce package
npm test runs model and renderer tests in a VS Code extension host. npm run test:e2e additionally uses managed Chromium to run the complete Git worktree, server, Playwright, FFmpeg, metadata, and cleanup pipeline. node scripts/mcp-e2e-smoke.js drives the built MCP server against a fixture repository over stdio.
Press F5 to open an Extension Development Host.
Architecture
This is intentionally one repository and one package with two thin shells over one engine: the VS Code extension and the MCP server. The engine modules depend on a small host abstraction instead of the VS Code API.
src/comparisonRunner.ts coordinates sessions and cleanup.
src/gitRepository.ts resolves refs and manages detached worktrees.
src/processes.ts owns commands, servers, ports, and readiness checks.
src/capture.ts tracks focus bounds and records Playwright scenarios.
src/renderer.ts selects layout, crops recordings, adds labels, and creates GIFs.
src/host.ts defines the engine-facing host interfaces.
src/previewPanel.ts provides review and export inside VS Code.
src/compareTool.ts exposes the runner to agents in VS Code.
src/mcpServer.ts exposes the runner to MCP clients over stdio.
skills/create-pr-ui-comparison/ teaches agents the pre-PR workflow.
Security
The extension requires Workspace Trust because install and start commands execute project code. Agent tool invocations show the baseline ref and commands before execution. Webview content uses a restrictive content security policy and can only load generated session resources.
License
PR UI Compare is open-source software available under the MIT License.