| name | ui-fix-draft-pr |
| description | Use when fixing any UI, CSS, or layout bug and producing a PR with visual evidence. Triggers when the user reports something looks broken on mobile or desktop (often with a screenshot), when you need before/after screenshots for a PR, when fixing Tailwind responsive issues, or when a layout overflows on certain viewports. Also use when the user says things like "fix this on mobile", "the layout is broken", "take a screenshot to verify", or "create a PR with screenshots". Covers diagnosis, fix, Playwright screenshot capture at mobile + desktop, image upload to GitHub without committing, and draft PR creation. |
UI Fix with Playwright Screenshots
Fix a UI/layout issue and produce a draft PR with before/after Playwright screenshots at mobile and desktop viewports — images hosted on GitHub but never committed to the branch.
Before Starting: Interactive or Autonomous?
Ask the user whether they want interactive or autonomous mode before doing anything else.
- Interactive — after applying CSS fixes (Phase 2), stop and show the
git diff to the user. Wait for their feedback before proceeding to screenshots and PR. This lets the user request tweaks (spacing, alignment, which elements to show/hide at breakpoints) without needing to redo the entire screenshot → upload → PR cycle each time. Only move to Phase 3 once the user approves.
- Autonomous — run the full pipeline end-to-end without stopping: diagnose, fix, screenshot, upload, PR.
Default to interactive if the user doesn't specify — layout fixes often need a round or two of visual tuning that's hard to get right without feedback.
Workflow
[Ask: interactive or autonomous?]
→ Read components → Diagnose root causes → Apply CSS fixes
→ (interactive? show diff, wait for approval)
→ Write Playwright test (mock API, capture before/after + desktop)
→ Upload screenshots to GitHub draft release
→ Commit code only, create draft PR with embedded images → Clean up
Phase 1: Diagnose
Find the page component and read its tree. Compare what the user reports (screenshot, description) against the rendered layout. Common mobile overflow culprits:
| Symptom | Likely cause |
|---|
| Page scrolls horizontally, content clipped on one side | A long unbreakable string (ID, hash, URL) forcing the page wider than the viewport |
| Buttons/tabs cut off at edge | Flex container without flex-wrap or overflow-x-auto |
| Two columns cramped on narrow screens | Missing flex-col sm:flex-row responsive stacking |
| Elements overlap or compress | Fixed widths that don't adapt, missing min-w-0 on flex children |
Phase 2: Fix
Apply minimal Tailwind class changes. Prefer the smallest diff that solves the problem — don't refactor surrounding code. Common fixes:
| Problem | Fix pattern |
|---|
| Long string overflows | min-w-0 truncate on the text element, min-w-0 on its flex parent |
| Tabs/nav overflow | overflow-x-auto on the scrollable container |
| Side-by-side cramped on mobile | flex flex-col gap-2 sm:flex-row sm:gap-4 |
| Buttons wrap awkwardly | flex flex-col gap-2 sm:flex-row sm:flex-wrap |
After fixing, verify TypeScript compiles with no new errors in the changed files.
Phase 3: Playwright Test
Write a standalone .spec.ts test file rather than using Playwright MCP tools. Standalone tests are more reliable — MCP connections can time out during route mocking, and a test file is rerunnable and produces consistent results.
The test needs to render the real React components (not injected HTML) because Tailwind's JIT compiler only generates CSS classes that exist in the source code. Injected HTML uses classes the compiler never saw, so they render unstyled.
To render real components without a running backend, mock the API layer via page.route(). Read references/playwright-template.md for the full template and API mocking patterns (tRPC, REST, GraphQL).
The test file should capture:
- Mobile after — screenshot at 375×812 with the fix applied
- Mobile before — same page, then
page.evaluate() to revert the CSS class changes on the live DOM, then screenshot (this reproduces the broken layout without needing to check out old code)
- Desktop — screenshot at 1280×800 showing the layout works at full width
- Overflow assertion —
scrollWidth <= clientWidth for the fixed state, scrollWidth > clientWidth for the reverted state (quantifies the fix)
mkdir -p tests/e2e/screenshots
npx playwright test tests/e2e/your-test.spec.ts --project=chromium
Compose Side-by-Side Images
After capturing screenshots, use references/compose-screenshots.py to create labeled before/after composites. These look much better in PRs than markdown tables — they have red/green "Before"/"After" labels and a dark background.
pip3 install --break-system-packages Pillow 2>/dev/null
python3 references/compose-screenshots.py \
tests/e2e/screenshots \
"Mobile (375x812):tests/e2e/screenshots/mobile-before.png:tests/e2e/screenshots/mobile-after.png"
This produces side_by_side_mobile_375x812.png in the screenshots directory. If you have multiple pairs (e.g. mobile + desktop before/after), pass them all and it also produces a stacked all_comparisons.png.
Phase 4: Upload Screenshots to GitHub
Images belong in the PR description, not in the git history. Use a draft GitHub release as a free CDN — draft releases are hidden from the public but their asset URLs are permanent.
REPO="Owner/repo"
TOKEN=$(gh auth token)
RELEASE_ID=$(gh api repos/$REPO/releases \
--method POST \
-f tag_name="screenshots-temp-$$" \
-f name="temp-screenshots" \
-F draft=true -F prerelease=true \
--jq '.id')
for img in tests/e2e/screenshots/side_by_side_*.png tests/e2e/screenshots/desktop.png; do
[ -f "$img" ] || continue
NAME=$(basename "$img")
URL=$(curl -s \
-H "Authorization: token $TOKEN" \
-H "Content-Type: image/png" \
--data-binary @"$img" \
"https://uploads.github.com/repos/$REPO/releases/$RELEASE_ID/assets?name=$NAME" \
| jq -r '.browser_download_url')
echo "$NAME → $URL"
done
The upload host is uploads.github.com, not api.github.com — the gh api command doesn't handle binary uploads and returns 404 if you try. Use curl with --data-binary. Keep the draft release after uploading; deleting it breaks the asset URLs.
Phase 5: Draft PR
Stage only the code changes. Create the PR with embedded <img> tags using the URLs from Phase 4.
Use the composed side-by-side image (from Phase 3) instead of a markdown table — it renders consistently across GitHub clients and looks much better:
## Summary
- Bullet points describing each fix
## Mobile — Before vs After (375×812, Playwright)
<img src="SIDE_BY_SIDE_URL" width="800" />
## Desktop (1280×800, Playwright)
<img src="DESKTOP_URL" width="800" />
## Test plan
- [x] Playwright overflow assertion at mobile viewport
- [ ] Manual verification on device
Phase 6: Clean Up
Remove the test file and screenshots directory — they were only needed to produce the PR evidence.
rm -f tests/e2e/your-test.spec.ts
rm -rf tests/e2e/screenshots
Pitfalls
| Mistake | Why it happens | What to do instead |
|---|
| Mock with injected HTML | Seems faster, but Tailwind JIT won't generate classes for HTML that isn't in source files | Mock the API layer so real React components render |
| Use Playwright MCP for route mocking | MCP connections time out on long-running operations like route interception + page load | Write a .spec.ts file and run with npx playwright test |
| Commit screenshots to git | Images bloat the repo and trigger large-file warnings | Upload via draft release, reference URLs in PR body |
| Delete draft release after PR | Seems like cleanup, but asset URLs are tied to the release | Leave the draft release — it's hidden and harmless |
| Check out old branch for "before" | Slow, needs dependency reinstall, risks build failures | Render with fixed code, then page.evaluate() to strip fixes from the DOM |
| Missing API procedures in mock | Page hangs on loading spinner for queries that never resolve | Map every tRPC/REST/GraphQL call the page makes — return null data for non-essential ones |