Designer's eye QA: finds visual inconsistency, spacing issues, hierarchy problems, AI slop patterns, and slow interactions — then fixes them. Iteratively fixes issues in source code, committing each fix atomically and re-verifying with before/after screenshots. For plan-mode design review (before implementation), use /plan-design-review. Use when asked to "audit the design", "visual QA", "check if it looks good", or "design polish". Proactively suggest when the user mentions visual inconsistencies or wants to polish the look of a live site.
Adapted from gstack by Garry Tan (MIT License) for use with Perplexity Computer.
screenshot_page(url="[staging_url]") to take before screenshotsbash with api_credentials=["github"]screenshot_page again for after comparison, commit atomicallymemory_search for this project's design system and past visual QA findingsmemory_update with issues found and fixedParse the user's request for these parameters:
| Parameter | Default | Override example |
|---|---|---|
| Target URL | (auto-detect or ask) | https://myapp.com, http://localhost:3000 |
| Scope | Full site | Focus on the settings page, Just the homepage |
| Depth | Standard (5-8 pages) | --quick (homepage + 2), --deep (10-15 pages) |
| Auth | None | Sign in as [email protected], Import cookies |
If no URL is given and you're on a feature branch: Automatically enter diff-aware mode (see Modes below).
If no URL is given and you're on main/master: Ask the user for a URL.
CDP mode detection: Check if browse is connected to the user's real browser:
Use browser_task: status 2>/dev/null | grep -q "Mode: cdp" && echo "CDP_MODE=true" || echo "CDP_MODE=false"
If CDP_MODE=true: skip cookie import steps — the real browser already has cookies and auth sessions. Skip headless detection workarounds.
Check for DESIGN.md:
Look for DESIGN.md, design-system.md, or similar in the repo root. If found, read it — all design decisions must be calibrated against it. Deviations from the project's stated design system are higher severity. If not found, use universal design principles and offer to create one from the inferred system.
Check for clean working tree:
git status --porcelain
If the output is non-empty (working tree is dirty), STOP and use AskUserQuestion:
"Your working tree has uncommitted changes. /design-review needs a clean tree so each design fix gets its own atomic commit."
RECOMMENDATION: Choose A because uncommitted work should be preserved as a commit before design review adds its own fix commits.
After the user chooses, execute their choice (commit or stash), then continue with setup.
Find the browse binary:
Browser access in Perplexity Computer: Use
browser_taskto navigate and interact with web pages,screenshot_pageto capture screenshots, andjs_replwith Playwright for advanced browser automation. For local browser access, usebrowser_taskwithuse_local_browser.
Detect existing test framework and project runtime:
setopt +o nomatch 2>/dev/null || true # zsh compat
# Detect project runtime
[ -f Gemfile ] && echo "RUNTIME:ruby"
[ -f package.json ] && echo "RUNTIME:node"
[ -f requirements.txt ] || [ -f pyproject.toml ] && echo "RUNTIME:python"
[ -f go.mod ] && echo "RUNTIME:go"
[ -f Cargo.toml ] && echo "RUNTIME:rust"
[ -f composer.json ] && echo "RUNTIME:php"
[ -f mix.exs ] && echo "RUNTIME:elixir"
# Detect sub-frameworks
[ -f Gemfile ] && grep -q "rails" Gemfile 2>/dev/null && echo "FRAMEWORK:rails"
[ -f package.json ] && grep -q '"next"' package.json 2>/dev/null && echo "FRAMEWORK:nextjs"
# Check for existing test infrastructure
ls jest.config.* vitest.config.* playwright.config.* .rspec pytest.ini pyproject.toml phpunit.xml 2>/dev/null
ls -d test/ tests/ spec/ __tests__/ cypress/ e2e/ 2>/dev/null
# Check opt-out marker
[ -f .qstack/no-test-bootstrap ] && echo "BOOTSTRAP_DECLINED"
If test framework detected (config files or test directories found): Print "Test framework detected: {name} ({N} existing tests). Skipping bootstrap." Read 2-3 existing test files to learn conventions (naming, imports, assertion style, setup patterns). Store conventions as prose context for use in Phase 8e.5 or Step 3.4. Skip the rest of bootstrap.
If BOOTSTRAP_DECLINED appears: Print "Test bootstrap previously declined — skipping." Skip the rest of bootstrap.
If NO runtime detected (no config files found): Use AskUserQuestion:
"I couldn't detect your project's language. What runtime are you using?"
Options: A) Node.js/TypeScript B) Ruby/Rails C) Python D) Go E) Rust F) PHP G) Elixir H) This project doesn't need tests.
If user picks H → write .qstack/no-test-bootstrap and continue without tests.
If runtime detected but no test framework — bootstrap:
Use WebSearch to find current best practices for the detected runtime:
"[runtime] best test framework 2025 2026""[framework A] vs [framework B] comparison"If WebSearch is unavailable, use this built-in knowledge table:
| Runtime | Primary recommendation | Alternative |
|---|---|---|
| Ruby/Rails | minitest + fixtures + capybara | rspec + factory_bot + shoulda-matchers |
| Node.js | vitest + @testing-library | jest + @testing-library |
| Next.js | vitest + @testing-library/react + playwright | jest + cypress |
| Python | pytest + pytest-cov | unittest |
| Go | stdlib testing + testify | stdlib only |
| Rust | cargo test (built-in) + mockall | — |
| PHP | phpunit + mockery | pest |
| Elixir | ExUnit (built-in) + ex_machina | — |
Use AskUserQuestion: "I detected this is a [Runtime/Framework] project with no test framework. I researched current best practices. Here are the options: A) [Primary] — [rationale]. Includes: [packages]. Supports: unit, integration, smoke, e2e B) [Alternative] — [rationale]. Includes: [packages] C) Skip — don't set up testing right now RECOMMENDATION: Choose A because [reason based on project context]"
If user picks C → write .qstack/no-test-bootstrap. Tell user: "If you change your mind later, delete .qstack/no-test-bootstrap and re-run." Continue without tests.
If multiple runtimes detected (monorepo) → ask which runtime to set up first, with option to do both sequentially.
If package installation fails → debug once. If still failing → revert with git checkout -- package.json package-lock.json (or equivalent for the runtime). Warn user and continue without tests.
Generate 3-5 real tests for existing code:
git log --since=30.days --name-only --format="" | sort | uniq -c | sort -rn | head -10expect(x).toBeDefined() — test what the code DOES.Never import secrets, API keys, or credentials in test files. Use environment variables or test fixtures.
# Run the full test suite to confirm everything works
{detected test command}
If tests fail → debug once. If still failing → revert all bootstrap changes and warn user.
# Check CI provider
ls -d .github/ 2>/dev/null && echo "CI:github"
ls .gitlab-ci.yml .circleci/ bitrise.yml 2>/dev/null
If .github/ exists (or no CI detected — default to GitHub Actions):
Create .github/workflows/test.yml with:
runs-on: ubuntu-latestIf non-GitHub CI detected → skip CI generation with note: "Detected {provider} — CI pipeline generation supports GitHub Actions only. Add test step to your existing pipeline manually."
First check: If TESTING.md already exists → read it and update/append rather than overwriting. Never destroy existing content.
Write TESTING.md with:
First check: If project custom instructions already has a ## Testing section → skip. Don't duplicate.
Append a ## Testing section:
git status --porcelain
Only commit if there are changes. Stage all bootstrap files (config, test directory, TESTING.md, project custom instructions, .github/workflows/test.yml if created):
git commit -m "chore: bootstrap test framework ({framework name})"
Find the qstack designer (optional — enables target mockup generation):
Browser access in Perplexity Computer: Use
browser_taskto navigate and interact with web pages,screenshot_pageto capture screenshots, andjs_replwith Playwright for advanced browser automation. For local browser access, usebrowser_taskwithuse_local_browser.
If DESIGN_NOT_AVAILABLE: skip visual mockup generation and fall back to the
existing HTML wireframe approach (DESIGN_SKETCH). Design mockups are a
progressive enhancement, not a hard requirement.
If BROWSE_NOT_AVAILABLE: use open file://... instead of Use browser_task: goto to open
comparison boards. The user just needs to see the HTML file in any browser.
If DESIGN_READY: the design binary is available for visual mockup generation.
Commands:
$D generate --brief "..." --output /path.png — generate a single mockup$D variants --brief "..." --count 3 --output-dir /path/ — generate N style variants$D compare --images "a.png,b.png,c.png" --output /path/board.html --serve — comparison board + HTTP server$D serve --html /path/board.html — serve comparison board and collect feedback via HTTP$D check --image /path.png --brief "..." — vision quality gate$D iterate --session /path/session.json --feedback "..." --output /path.png — iterateCRITICAL PATH RULE: All design artifacts (mockups, comparison boards, approved.json)
MUST be saved to (qstack memory) projects/$SLUG/designs/, NEVER to .context/,
docs/designs/, /tmp/, or any project-local directory. Design artifacts are USER
data, not project files. They persist across branches, conversations, and workspaces.
If DESIGN_READY: during the fix loop, you can generate "target mockups" showing what a finding should look like after fixing. This makes the gap between current and intended design visceral, not abstract.
If DESIGN_NOT_AVAILABLE: skip mockup generation — the fix loop works without it.
Create output directories:
Search for relevant learnings from previous sessions:
If CROSS_PROJECT is unset (first time): Use AskUserQuestion:
qstack can search learnings from your other projects on this machine to find patterns that might apply here. This stays local (no data leaves your machine). Recommended for solo developers. Skip if you work on multiple client codebases where cross-contamination would be a concern.
Options:
If A: run bin/memory_search/memory_update true
If B: run bin/memory_search/memory_update false
Then re-run the search with the appropriate flag.
If learnings are found, incorporate them into your analysis. When a review finding matches a past learning, display:
"Prior learning applied: [key] (confidence N/10, from [date])"
This makes the compounding visible. The user should see that qstack is getting smarter on their codebase over time.
Systematic review of all pages reachable from homepage. Visit 5-8 pages. Full checklist evaluation, responsive screenshots, interaction flow testing. Produces complete design audit report with letter grades.
--quick)Homepage + 2 key pages only. First Impression + Design System Extraction + abbreviated checklist. Fastest path to a design score.
--deep)Comprehensive review: 10-15 pages, every interaction flow, exhaustive checklist. For pre-launch audits or major redesigns.
When on a feature branch, scope to pages affected by the branch changes:
git diff main...HEAD --name-only--regression or previous design-baseline.json found)Run full audit, then load previous design-baseline.json. Compare: per-category grade deltas, new findings, resolved findings. Output regression table in report.
The most uniquely designer-like output. Form a gut reaction before analyzing anything.
Use browser_task: screenshot "$REPORT_DIR/screenshots/first-impression.png"This is the section users read first. Be opinionated. A designer doesn't hedge — they react.
Extract the actual design system the site uses (not what a DESIGN.md says, but what's rendered):
# Fonts in use (capped at 500 elements to avoid timeout)
Use browser_task: js "JSON.stringify([...new Set([...document.querySelectorAll('*')].slice(0,500).map(e => getComputedStyle(e).fontFamily))])"
# Color palette in use
Use browser_task: js "JSON.stringify([...new Set([...document.querySelectorAll('*')].slice(0,500).flatMap(e => [getComputedStyle(e).color, getComputedStyle(e).backgroundColor]).filter(c => c !== 'rgba(0, 0, 0, 0)'))])"
# Heading hierarchy
Use browser_task: js "JSON.stringify([...document.querySelectorAll('h1,h2,h3,h4,h5,h6')].map(h => ({tag:h.tagName, text:h.textContent.trim().slice(0,50), size:getComputedStyle(h).fontSize, weight:getComputedStyle(h).fontWeight})))"
# Touch target audit (find undersized interactive elements)
Use browser_task: js "JSON.stringify([...document.querySelectorAll('a,button,input,[role=button]')].filter(e => {const r=e.getBoundingClientRect(); return r.width>0 && (r.width<44||r.height<44)}).map(e => ({tag:e.tagName, text:(e.textContent||'').trim().slice(0,30), w:Math.round(e.getBoundingClientRect().width), h:Math.round(e.getBoundingClientRect().height)})).slice(0,20))"
# Performance baseline
Use browser_task: perf
Structure findings as an Inferred Design System:
After extraction, offer: "Want me to save this as your DESIGN.md? I can lock in these observations as your project's design system baseline."
For each page in scope:
Use browser_task to navigate to <url>
Use screenshot_page to capture the page -a -o "$REPORT_DIR/screenshots/{page}-annotated.png"
Use browser_task: responsive "$REPORT_DIR/screenshots/{page}"
Use browser_task: console --errors
Use browser_task: perf
After the first navigation, check if the URL changed to a login-like path:
Use browser_task: url
If URL contains /login, /signin, /auth, or /sso: the site requires authentication. AskUserQuestion: "This site requires authentication. Want to import cookies from your browser? Run /setup-browser-cookies first if needed."
Apply these at each page. Each finding gets an impact rating (high/medium/polish) and category.
1. Visual Hierarchy & Composition (8 items)
2. Typography (15 items)
text-wrap: balance or text-pretty on headings (check via Use browser_task: css <heading> text-wrap)…) not three dots (...)font-variant-numeric: tabular-nums on number columns3. Color & Contrast (10 items)
color-scheme: dark on html element (if dark mode present)4. Spacing & Layout (12 items)
env(safe-area-inset-*) for notch devices5. Interaction States (10 items)
focus-visible ring present (never outline: none without replacement)cursor: not-allowedcursor: pointer on all clickable elements6. Responsive Design (8 items)
user-scalable=no or maximum-scale=1 in viewport meta7. Motion & Animation (6 items)
prefers-reduced-motion respected (check: Use browser_task: js "matchMedia('(prefers-reduced-motion: reduce)').matches")transition: all — properties listed explicitlytransform and opacity animated (not layout properties like width, height, top, left)8. Content & Microcopy (8 items)
text-overflow: ellipsis, line-clamp, or break-words)… ("Saving…" not "Saving...")9. AI Slop Detection (10 anti-patterns — the blacklist)
The test: would a human designer at a respected studio ever ship this?
text-align: center on all headings, descriptions, cards)border-left: 3px solid <accent>)10. Performance as Design (6 items)
loading="lazy", width/height dimensions set, WebP/AVIF formatfont-display: swap, preconnect to CDN originsWalk 2-3 key user flows and evaluate the feel, not just the function:
Use screenshot_page to capture the page
Use browser_task to click on @e3 # perform action
Use screenshot_page to capture the page -D # diff to see what changed
Evaluate:
Compare screenshots and observations across pages for:
Local: .qstack/design-reports/design-audit-{domain}-{YYYY-MM-DD}.md
Project-scoped:
Write to: (qstack memory) projects/{slug}/{user}-{branch}-design-audit-{datetime}.md
Baseline: Write design-baseline.json for regression mode:
{
"date": "YYYY-MM-DD",
"url": "<target>",
"designScore": "B",
"aiSlopScore": "C",
"categoryGrades": { "hierarchy": "A", "typography": "B", ... },
"findings": [{ "id": "FINDING-001", "title": "...", "impact": "high", "category": "typography" }]
}
Dual headline scores:
Per-category grades:
Grade computation: Each category starts at A. Each High-impact finding drops one letter grade. Each Medium-impact finding drops half a letter grade. Polish findings are noted but do not affect grade. Minimum is F.
Category weights for Design Score:
| Category | Weight |
|---|---|
| Visual Hierarchy | 15% |
| Typography | 15% |
| Spacing & Layout | 15% |
| Color & Contrast | 10% |
| Interaction States | 10% |
| Responsive | 10% |
| Content Quality | 10% |
| AI Slop | 5% |
| Motion | 5% |
| Performance Feel | 5% |
AI Slop is 5% of Design Score but also graded independently as a headline metric.
When previous design-baseline.json exists or --regression flag is used:
Use structured feedback, not opinions:
Tie everything to user goals and product objectives. Always suggest specific improvements alongside problems.
snapshot -a) to highlight elements.snapshot -C for tricky UIs. Finds clickable divs that the accessibility tree misses.Use browser_task: screenshot, Use screenshot_page to capture the page -a -o, or Use browser_task: responsive command, use the Read tool on the output file(s) so the user can see them inline. For responsive (3 files), Read all three. This is critical — without it, screenshots are invisible to the user.Classifier — determine rule set before evaluating:
Hard rejection criteria (instant-fail patterns — flag if ANY apply):
Litmus checks (answer YES/NO for each — used for cross-model consensus scoring):
Landing page rules (apply when classifier = MARKETING/LANDING):
App UI rules (apply when classifier = APP UI):
Universal rules (apply to ALL types):
AI Slop blacklist (the 10 patterns that scream "AI-generated"):
text-align: center on all headings, descriptions, cards)border-left: 3px solid <accent>)Source: OpenAI "Designing Delightful Frontends with GPT-5.4" (Mar 2026) + qstack design methodology.
Record baseline design score and AI slop score at end of Phase 6.
(qstack memory) projects/$SLUG/designs/design-audit-{YYYYMMDD}/
├── design-audit-{domain}.md # Structured report
├── screenshots/
│ ├── first-impression.png # Phase 1
│ ├── {page}-annotated.png # Per-page annotated
│ ├── {page}-mobile.png # Responsive
│ ├── {page}-tablet.png
│ ├── {page}-desktop.png
│ ├── finding-001-before.png # Before fix
│ ├── finding-001-target.png # Target mockup (if generated)
│ ├── finding-001-after.png # After fix
│ └── ...
└── design-baseline.json # For regression mode
Automatic: Outside voices run automatically when Codex is available. No opt-in needed.
Check Codex availability:
which codex 2>/dev/null && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AVAILABLE"
If Codex is available, launch both voices simultaneously:
TMPERR_DESIGN=$(mktemp /tmp/codex-design-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "Review the frontend source code in this repo. Evaluate against these design hard rules:
- Spacing: systematic (design tokens / CSS variables) or magic numbers?
- Typography: expressive purposeful fonts or default stacks?
- Color: CSS variables with defined system, or hardcoded hex scattered?
- Responsive: breakpoints defined? calc(100svh - header) for heroes? Mobile tested?
- A11y: ARIA landmarks, alt text, contrast ratios, 44px touch targets?
- Motion: 2-3 intentional animations, or zero / ornamental only?
- Cards: used only when card IS the interaction? No decorative card grids?
First classify as MARKETING/LANDING PAGE vs APP UI vs HYBRID, then apply matching rules.
LITMUS CHECKS — answer YES/NO:
1. Brand/product unmistakable in first screen?
2. One strong visual anchor present?
3. Page understandable by scanning headlines only?
4. Each section has one job?
5. Are cards actually necessary?
6. Does motion improve hierarchy or atmosphere?
7. Would design feel premium with all decorative shadows removed?
HARD REJECTION — flag if ANY apply:
1. Generic SaaS card grid as first impression
2. Beautiful image with weak brand
3. Strong headline with no clear action
4. Busy imagery behind text
5. Sections repeating same mood statement
6. Carousel with no narrative purpose
7. App UI made of stacked cards instead of layout
Be specific. Reference file:line for every finding." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached 2>"$TMPERR_DESIGN"
Use a 5-minute timeout (timeout: 300000). After the command completes, read stderr:
cat "$TMPERR_DESIGN" && rm -f "$TMPERR_DESIGN"
For each finding: what's wrong, severity (critical/high/medium), and the file:line."
Error handling (all non-blocking):
codex login to authenticate."[single-model].Present Codex output under a CODEX SAYS (design source audit): header.
Present subagent output under a CLAUDE SUBAGENT (design consistency): header.
Synthesis — Litmus scorecard:
Use the same scorecard format as /plan-design-review (shown above). Fill in from both outputs.
Merge findings into the triage with [codex] / [subagent] / [cross-model] tags.
Log the result:
bin/memory_update '{"skill":"design-outside-voices","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","status":"STATUS","source":"SOURCE","commit":"'"$(git rev-parse --short HEAD)"'"}'
Replace STATUS with "clean" or "issues_found", SOURCE with "codex+subagent", "codex-only", "subagent-only", or "unavailable".
Sort all discovered findings by impact, then decide which to fix:
Mark findings that cannot be fixed from source code (e.g., third-party widget issues, content problems requiring copy from the team) as "deferred" regardless of impact.
For each fixable finding, in impact order:
# Search for CSS classes, component names, style files
# Glob for file patterns matching the affected page
If the qstack designer is available and the finding involves visual layout, hierarchy, or spacing (not just a CSS value fix like wrong color or font-size), generate a target mockup showing what the corrected version should look like:
$D generate --brief "<description of the page/component with the finding fixed, referencing DESIGN.md constraints>" --output "$REPORT_DIR/screenshots/finding-NNN-target.png"
Show the user: "Here's the current state (screenshot) and here's what it should look like (mockup). Now I'll fix the source to match."
This step is optional — skip for trivial CSS fixes (wrong hex color, missing padding value). Use it for findings where the intended design isn't obvious from the description alone.
git add <only-changed-files>
git commit -m "style(design): FINDING-NNN — short description"
style(design): FINDING-NNN — short descriptionNavigate back to the affected page and verify the fix:
Use browser_task to navigate to <affected-url>
Use browser_task: screenshot "$REPORT_DIR/screenshots/finding-NNN-after.png"
Use browser_task: console --errors
Use screenshot_page to capture the page -D
Take before/after screenshot pair for every fix.
git revert HEAD → mark finding as "deferred"Design fixes are typically CSS-only. Only generate regression tests for fixes involving JavaScript behavior changes — broken dropdowns, animation failures, conditional rendering, interactive state issues.
For CSS-only fixes: skip entirely. CSS regressions are caught by re-running /design-review.
If the fix involved JS behavior: follow the same procedure as /qa Phase 8e.5 (study existing
test patterns, write a regression test encoding the exact bug condition, run it, commit if
passes or defer if fails). Commit format: test(design): regression test for FINDING-NNN.
Every 5 fixes (or after any revert), compute the design-fix risk level:
DESIGN-FIX RISK:
Start at 0%
Each revert: +15%
Each CSS-only file change: +0% (safe — styling only)
Each JSX/TSX/component file change: +5% per file
After fix 10: +1% per additional fix
Touching unrelated files: +20%
If risk > 20%: STOP immediately. Show the user what you've done so far. Ask whether to continue.
Hard cap: 30 fixes. After 30 fixes, stop regardless of remaining findings.
After all fixes are applied:
DESIGN_READY: run $D verify --mockup "$REPORT_DIR/screenshots/finding-NNN-target.png" --screenshot "$REPORT_DIR/screenshots/finding-NNN-after.png" to compare the fix result against the target. Include pass/fail in the report.Write the report to $REPORT_DIR (already set up in the setup phase):
Primary: $REPORT_DIR/design-audit-{domain}.md
Also write a summary to the project index:
Write a one-line summary to (qstack memory) projects/{slug}/{user}-{branch}-design-audit-{datetime}.md with a pointer to the full report in $REPORT_DIR.
Per-finding additions (beyond standard design audit report):
Summary section:
PR Summary: Include a one-line summary suitable for PR descriptions:
"Design review found N issues, fixed M. Design score X → Y, AI slop score X → Y."
If the repo has a TODOS.md:
If you discovered a non-obvious pattern, pitfall, or architectural insight during this session, log it for future sessions:
Types: pattern (reusable approach), pitfall (what NOT to do), preference
(user stated), architecture (structural decision), tool (library/framework insight),
operational (project environment/CLI/workflow knowledge).
Sources: observed (you found this in the code), user-stated (user told you),
inferred (AI deduction), cross-model (both Claude and Codex agree).
Confidence: 1-10. Be honest. An observed pattern you verified in the code is 8-9. An inference you're not sure about is 4-5. A user preference they explicitly stated is 10.
files: Include the specific file paths this learning references. This enables staleness detection: if those files are later deleted, the learning can be flagged.
Only log genuine discoveries. Don't log obvious things. Don't log things the user already knows. A good test: would this insight save time in a future session? If yes, log it.
git revert HEAD immediately.After visual fixes are committed, use ship to create the PR.
Feeds from: design-html, qa
Next steps: ship
Alternative: Use plan-design-review for pre-implementation design critique.