| name | visual-polish |
| description | Use after features are built and before shipping, when the UI needs a quality pass, when the product needs to look premium instead of functional, or when preparing for a demo or investor meeting. |
Visual Polish
Overview
Run the systematic quality pass that turns "it works" into "this feels professional." Output is .forge/polish-checklist.md — pass/fail per item, with file:line for every failure. Reads design-system, component-library, page-composition (all optional, but each makes the pass sharper). Sits between /build and /ship in the pipeline.
When to Use
- Features are built, tests pass, and the UI is about to be shown to anyone outside the team
- A demo or investor meeting is scheduled within the week
- The product "works" but the team feels something is off and can't name it
- A redesign was promised and the team needs to confirm the redesign actually landed
- Before promoting a beta to general availability
When NOT to Use
- Features aren't built yet — polish is the last 10%, not the first 90%
- A backend-only service
- Polish was done this sprint and nothing visual changed since
Common Rationalizations
| Thought | Reality |
|---|
| "It works, that's enough" | Working is table stakes. Users judge quality in the first 3 seconds. Polish is the difference between "this works" and "this feels professional." |
| "We'll polish before launch" | Launch is always tomorrow. If polish isn't in the process, it never happens. |
| "Users don't notice these details" | Users can't articulate what they notice. They feel "janky" or "solid." Polish is what they're feeling. |
| "It's just a demo" | Demos are the highest-stakes UI moment. Every missing hover state says "prototype." |
| "We'll fix it after the demo" | After the demo means after the impression. The first impression is the only impression. |
Red Flags
- Browser default focus rings (
outline: auto)
- Empty state that says only "No data" with no context or CTA
- Error message that says "Something went wrong" with no next step
- Layout shift on content load (CLS > 0.1)
- Missing favicon — the tab is generic
- Page title that says
localhost:3000 or Vite App
- Horizontal scroll appearing at 320px or 1440px
Core Process
Step 1: Read available UI artifacts
Load whichever of these exist: .forge/design-system.md, .forge/component-library.md, .forge/page-composition.md, .forge/brand-identity.md, .forge/interaction-patterns.md, references/motion-system.md. Use them as the rubric — polish is "the runtime matches the system."
Step 2: Micro-interaction audit
For every interactive element:
- Every button has
hover + active + focus-visible states
- Every toggle/switch transitions smoothly (not instant swap; per
references/motion-system.md)
- Every link has a hover state (underline or color shift)
- Focus rings are visible and consistent (not browser defaults; not
outline: none without replacement)
- Form inputs have
focus + error + disabled styling
Step 3: Empty state audit
For every list, table, chart, dashboard panel, sidebar group:
- An empty state exists with helpful headline + description + CTA
- No blank areas; no "No data" without context
- Empty-state voice matches
brand-identity voice rules
Step 4: Error state audit
- Every form has inline validation with specific messages (not just a red border)
- Every network call has error handling with retry where applicable
- A 404 page exists and matches the product's visual language
- Toast notifications for async errors are styled (not
alert())
- Error messages name what happened and what to do next
Step 5: Loading audit
- Every async content area has a skeleton screen (not a spinner; per
interaction-patterns)
- Skeletons match the shape of real content (same dimensions, same approximate density)
- Zero layout shift when content loads (skeleton → content occupies identical box)
- Spinners only inside buttons (
spinner-in-button + disabled state)
Step 6: Responsive edge case audit
Test at the extremes:
- 320px — smallest common phone; no horizontal scroll, no text overflow without ellipsis
- 1440px+ — large desktop; content respects the max-width cap from
page-composition
- Touch targets ≥ 44pt on mobile
- Long content (multi-word labels, 100-character strings, pasted URLs) tested in every container
Step 7: Meta audit
- Favicon exists and matches
brand-identity
- Apple touch icon for iOS home-screen save
- OG meta tags:
og:title, og:description, og:image — image is 1200×630 and matches brand
<title> is descriptive per route (not the same string everywhere)
<meta name="theme-color"> set for mobile browser chrome
Step 8: Dark mode audit (if applicable)
- Every screen rendered in dark mode
- All text/background pairings pass WCAG AA (4.5:1 for body, 3:1 for large text)
- Images and illustrations have dark-mode variants or transparent backgrounds
- No hardcoded
#FFFFFF leaking through (grep for raw hex in component files)
- Inline SVGs use
currentColor where appropriate
Step 9: Write .forge/polish-checklist.md
For every audit, list each item as pass/fail. Every failure includes:
- The audit it belongs to
- A
file:line (or route + selector) pointer
- The smallest specific fix
Prepend a forge:meta header (generated_by: visual-polish, generated_at: <ISO 8601 UTC with Z>, depends_on: [<each UI artifact that existed at run time>] — paths only, never hashes, generated_from: {<each upstream path>: <its content_hash AT generation time>}, content_hash: <sha256 first 8 of THIS file's body>).
After writing, report a summary: total items checked, total failed, the three highest-impact failures.
Verification