---
name: review-pre-deploy
description: >-
Run a structured pre-deploy review on AI-generated front-end code before it
ships. Use after completing a feature, page, or component — before commit,
merge, or deploy — to catch the errors that AI coding tools introduce most
often.
---
# Pre-Deploy Review
AI coding tools write functional code fast, but "it works on my machine" is
not ship-ready. This skill is the final gate between generated code and
production. Run it after the feature is "done" — it catches the
AI-generated code that needs debugging before users see it.
This is a review skill, not a writing skill. It doesn't tell the agent how to
build — the `frontend-*` skills handle that. It tells the agent how to *audit*
what it already built.
## How to Use This Skill
After completing any feature, component, or page, run a pre-deploy review by
checking every section below in order. Fix issues before committing.
## Error Handling
### Every async operation must handle failure
AI-generated code almost always shows the happy path. Check every `fetch`,
`await`, `Promise`, and event handler for error handling:
```javascript
// AI typically generates this
const data = await fetch('/api/users').then(r => r.json());
// Ship this instead
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
const data = await response.json();
} catch (error) {
showErrorState(error);
}
```
Check for:
- `fetch` calls without `.catch()` or `try/catch`
- `async` functions that never handle rejection
- Event listeners that assume success
- JSON parsing without error handling
- Missing timeout handling for network requests
### Every user-facing state needs an error state
If a component has a loading state, it also needs an error state. If it fetches
data, it needs an empty state. Check every component:
| State | What the user sees | Often missing? |
|-------|-------------------|----------------|
| Loading | Skeleton or spinner | Rarely |
| Success | The content | Never |
| Error | Message + retry action | **Almost always** |
| Empty | "No items yet" message | **Often** |
| Offline | Cached content or message | **Almost always** |
## Debug Artifacts
### Remove all debug code before deploy
AI tools leave debug artifacts in code constantly. Search for and remove:
```javascript
// Every one of these must be removed
console.log()
console.warn()
console.error() // unless it's intentional error logging
console.debug()
console.table()
debugger
alert()
```
Also check for:
- `TODO` and `FIXME` comments that indicate incomplete work
- Commented-out code blocks (either delete or restore)
- Hardcoded test data (`[email protected]`, `12345`, `lorem ipsum`)
- Placeholder text that was never replaced
- `localhost` URLs or hardcoded ports
- Test IDs or mock data left in production markup
## Hallucinated APIs and Dependencies
AI tools regularly invent things that do not exist: npm packages, CDN URLs,
HTTP headers, MDN paths, Tailwind classes, framework hooks. Verify every
external reference before shipping.
### Check every imported package
For every `import` statement in the diff, verify the package exists on npm
and the version is current. Common AI fabrications:
- Plausibly-named npm packages that were never published
- Real packages with hallucinated subpath imports
- Packages that were renamed or deprecated years ago
- Hooks or utilities that don't exist in the package's actual API
- Tailwind classes that look right but aren't in the codebase's config
If a package was added in this diff, run `npm view <name>` or open the
package's npm page before merging. If a hook is imported, verify it's in the
library's actual exports.
### Check every external URL
Hardcoded URLs in the diff — CDN scripts, image sources, fetch endpoints —
must resolve to a real, reachable resource:
- CDN script tags that point at dead or moved hosts
- Image `src` values for stock images that no longer exist
- API endpoints with the wrong base URL or path
- Documentation links to MDN or framework sites that 404
Prefer vendoring assets locally (under `assets/`) over loading from third-party
CDNs the AI may have made up.
## Hardcoded Values
### Extract magic numbers and strings
AI-generated code is full of hardcoded values that should be variables,
constants, or configuration:
```css
/* AI generates this */
.card { max-width: 384px; padding: 24px; border-radius: 12px; }
/* Should use design tokens */
.card {
max-width: var(--size-card-max);
padding: var(--space-lg);
border-radius: var(--radius-lg);
}
```
Check for:
- Pixel values that should be design tokens (spacing, sizing, radii)
- Color hex/rgb values that should be token references
- Breakpoint values that should use the project's breakpoint scale
- API URLs that should come from environment config
- Timeout durations that should be named constants
- z-index values that should use a z-index scale
## CSS Quality
### No `!important` in component styles
AI tools add `!important` to override specificity conflicts instead of
fixing the selector. Every `!important` in component CSS is a code smell —
it cascades into maintenance debt. Fix the root cause: restructure the
selector to win by specificity or source order.
### Use design tokens for all colors
If the code has more than a few hardcoded hex, rgb, or rgba values, they
should be CSS custom properties. AI generates unique color values per
component instead of pulling from a shared palette.
### Use logical properties
AI defaults to physical CSS properties (`margin-left`, `padding-right`,
`width`). Use logical equivalents (`margin-inline-start`,
`padding-inline-end`, `inline-size`) for automatic RTL/LTR support.
### Animations need a reduced-motion query
Every `animation` or `transition` in CSS must be wrapped in
`@media (prefers-reduced-motion: no-preference)` or disabled in a
`prefers-reduced-motion: reduce` query. AI never adds this.
## Responsive Design
### Test every breakpoint, not just desktop
AI tools build for the viewport they're prompted about — usually desktop.
Check every new component at these widths:
| Width | What to check |
|-------|--------------|
| 320px | Nothing overflows, text is readable, touch targets are 44x44px minimum |
| 375px | iPhone SE — most common small phone |
| 768px | Tablet — layout transitions work |
| 1024px | Small laptop — sidebar/content ratios hold |
| 1440px | Desktop — max-width containers prevent ultra-wide stretching |
Common AI failures at small viewports:
- Horizontal overflow from fixed-width elements
- Text that's too small to read (below 16px on mobile)
- Touch targets smaller than 44x44px
- Images without `max-width: 100%`
- Flex containers that don't wrap (`flex-wrap: wrap` missing)
- Absolute positioning that breaks on narrow screens
- Modals and dropdowns that extend past the viewport
### Check `overflow` behavior
```css
/* AI often generates containers that overflow on mobile */
.container {
overflow-x: hidden; /* Masks the problem */
}
/* Fix the actual cause instead */
.container {
max-inline-size: 100%;
}
```
## Forms and Validation
### Every form input needs validation
AI-generated forms almost never have complete validation. Check:
- Required fields have `required` attribute AND client-side messaging
- Email fields validate format before submit
- Password fields show requirements and validate against them
- Number inputs have `min`, `max`, and `step` where appropriate
- File inputs validate type and size before upload
- Submit button disables during submission to prevent double-submit
- Form shows inline errors next to the field, not just at the top
### Test form edge cases
- Submit with all fields empty
- Submit with only whitespace in text fields
- Paste content longer than expected
- Use browser autofill — does it work?
- Submit, get error, fix, resubmit — does the error clear?
## Loading and Interaction States
### Every interactive element needs visual feedback
| Element | States to check |
|---------|----------------|
| Buttons | Default, hover, focus, active, disabled, loading |
| Links | Default, hover, focus, visited |
| Inputs | Default, focus, filled, error, disabled |
| Cards | Default, hover (if clickable) |
| Toggles | On, off, disabled |
AI-generated code typically handles 2-3 of these. Production code needs all
of them.
### Buttons must prevent double-click
```html
<button type="submit" aria-busy="false">
Submit
</button>
```
On submit: set `aria-busy="true"`, disable the button, show a spinner. On
complete or error: restore the button. AI almost never generates this pattern.
## Images and Media
### Every image needs dimensions and alt text
Check every `<img>` element:
- `width` and `height` attributes set (prevents CLS)
- `alt` text is descriptive (content images) or empty (decorative)
- `loading="lazy"` on below-fold images
- `decoding="async"` on all images
- Images are not larger than needed (check actual display size vs file size)
### Check for missing responsive images
If an image displays at different sizes across breakpoints, it needs `srcset`
and `sizes`. A 1200px hero image served to a 320px phone is a performance
bug.
## Cross-Browser Basics
### Check feature support for new APIs
AI tools use the latest APIs without checking browser support. Audit for:
- CSS features that need fallbacks (container queries, `:has()`, `oklch`)
- JavaScript APIs that need polyfills or detection (`structuredClone`,
`navigation`, `scheduler.yield`)
- HTML elements that behave differently across browsers (`<dialog>`,
`popover`)
The rule: if the project's browser matrix includes Safari 16 or Firefox ESR,
verify every modern API against caniuse.com before shipping.
## Environment and Configuration
### No secrets or environment-specific values in source
Check the entire diff for:
- API keys, tokens, or passwords in source code
- `.env` values committed to the repository
- Development-only URLs that should be environment variables
- Debug flags that should be off in production
- Feature flags hardcoded as `true` instead of using a config system
## Security Smells
A static smell test on the diff. None of these are full security audits — they
catch the patterns AI tools regurgitate that never belong in production.
### Dangerous DOM injection
- `dangerouslySetInnerHTML` with any value the user could influence
- Direct `element.innerHTML = userInput` writes
- Template literals interpolating user input into HTML strings
- `document.write` calls anywhere in the diff
- `eval`, `new Function(...)`, or `setTimeout(string)` form
If any of these are used, the input must be either a literal known-safe
constant or sanitized through a vetted library before assignment.
### Exposed secrets
Regex sweep the diff for any of:
- `sk-` (OpenAI), `xoxb-` (Slack), `ghp_` / `gho_` (GitHub)
- `AKIA...` (AWS), `AIza...` (Google), `key-` followed by 30+ chars
- Anything literal that looks like a JWT (`eyJ...`)
- `.env` values committed verbatim
Move every match to environment variables before commit.
### Unsafe defaults
- `target="_blank"` links missing `rel="noopener noreferrer"`
- `fetch(...)` posts without CSRF tokens on state-changing endpoints
- Auth tokens stored in `localStorage` instead of httpOnly cookies
- Missing or wildcard `Content-Security-Policy`
- CORS configured with `Access-Control-Allow-Origin: *`
## HTML Semantics
### Use semantic elements, not div soup
AI wraps everything in `<div>`. If there are more than a few `<div>`
elements and zero `<main>`, `<nav>`, `<article>`, `<section>`, or `<aside>`
elements, the markup needs restructuring. Screen readers and search engines
rely on landmark elements.
### Use `<button>`, not `<div onclick>`
Every `<div onclick>`, `<span onclick>`, or `<div role="button">` is a bug.
Native `<button>` elements provide keyboard support, focus management, and
screen reader semantics for free. Replace them.
### No vague link text
Link text like "click here", "read more", "learn more", "here", or "link"
is meaningless out of context. Screen reader users navigate by link list.
Link text should describe the destination: "View pricing plans" not "click
here."
### Set the `lang` attribute on `<html>`
AI often generates `<html>` without `lang="en"` (or the correct language).
Screen readers use this to select the correct pronunciation engine.
## Accessibility Quick Check
### Run the 5-point check from the accessibility skill
Even if the `frontend-accessibility` skill was active during development,
verify:
1. **Tab through the page** — can you reach everything? Is focus visible?
2. **Check heading hierarchy** — h1, then h2, then h3, no skipped levels
3. **Check all images** — every `<img>` has `alt`
4. **Check all forms** — every input has a `<label>`
5. **Check color contrast** — 4.5:1 for text, 3:1 for UI elements
## Performance Quick Check
### Verify required meta tags are present
Every HTML page needs:
- `<meta charset="UTF-8">` as the first element in `<head>`
- `<meta name="viewport" content="width=device-width, initial-scale=1.0">`
AI-generated pages frequently omit one or both of these. Missing charset
causes encoding bugs. Missing viewport breaks mobile rendering.
### Verify the LCP element loads fast
- The largest above-fold element should not have `loading="lazy"`
- If *all* images on the page have `loading="lazy"`, the LCP image is
being delayed — remove `lazy` from the hero or main visual
- Critical CSS should not be behind a render-blocking stylesheet
- No synchronous `<script>` tags in `<head>` without `defer` or `async`
- Third-party scripts should not block the main thread
## The Pre-Deploy Checklist
Run this checklist on every feature before committing:
- [ ] All `fetch`/`async` operations have error handling
- [ ] Every component has loading, error, and empty states
- [ ] No `console.log`, `debugger`, or `alert()` in production code
- [ ] No hardcoded test data, placeholder text, or localhost URLs
- [ ] Every imported npm package exists; every CDN URL resolves
- [ ] All magic numbers extracted to tokens or constants
- [ ] No `!important` in component CSS
- [ ] No hardcoded color values — all colors use design tokens
- [ ] Animations wrapped in `prefers-reduced-motion` query
- [ ] Tested at 320px, 375px, 768px, 1024px, and 1440px
- [ ] No horizontal overflow at any viewport
- [ ] All form inputs have validation and error messages
- [ ] All buttons have hover, focus, active, disabled, and loading states
- [ ] All images have width, height, alt, and appropriate loading strategy
- [ ] LCP image does NOT have `loading="lazy"`
- [ ] No render-blocking `<script>` in `<head>` without `defer`/`async`
- [ ] `<html>` has a `lang` attribute
- [ ] `<meta charset>` and `<meta viewport>` are present
- [ ] No API keys, secrets, or env-specific values in source
- [ ] No `dangerouslySetInnerHTML` / `innerHTML` writes from user input
- [ ] Heading hierarchy is correct (h1 → h2 → h3, no skips)
- [ ] No `<div onclick>` or `<span role="button">` — use `<button>`
- [ ] No vague link text ("click here", "read more", "learn more")
- [ ] Semantic elements used (`<main>`, `<nav>`, `<article>`) not just `<div>`
- [ ] Every interactive element is keyboard-accessible
- [ ] No new browser APIs used without checking caniuse.com
A feature is not done until every box is checked.
## Score Your Output
After running the checklist, score the code to decide if it's ship-ready.
Start at 100 and deduct points for each finding:
| Severity | Deduction | Examples |
|----------|-----------|----------|
| Error | -8 points | Missing error handling, `console.log` in production, `innerHTML` with user content, exposed secrets, hallucinated package, missing viewport meta |
| Warning | -3 points | Missing `alt` text, no `prefers-reduced-motion`, `!important` in CSS, vague link text, hardcoded colors |
| Info | -1 point | Physical CSS properties instead of logical, could use semantic elements |
### Grade scale
| Score | Grade | Ship? |
|-------|-------|-------|
| 90–100 | A | Ship it |
| 80–89 | B | Ship it, but fix warnings soon |
| 70–79 | C | Fix errors before shipping |
| 60–69 | D | Significant rework needed |
| 0–59 | F | Do not ship — critical issues |
Report the grade, score, and a summary of findings before marking the task
complete. Example output:
```
Pre-deploy score: 84/100 (B)
- 1 error: fetch() without error handling in UserProfile
- 3 warnings: missing alt text (2), !important in card.css (1)
- 1 info: physical CSS properties in layout.css
Fix the error before shipping. Warnings should be addressed in the next PR.
```
The goal is not a perfect 100 on every commit — it's to never ship an F.
## Anti-Patterns
**Never do these:**
- Ship code with `console.log` — it leaks internal state to anyone with
DevTools open
- Use `overflow: hidden` to mask layout bugs — fix the actual cause
- Skip the error state because "the API is reliable" — it's not
- Use `placeholder` as the only form label — it disappears on input
- Hardcode API URLs — they will change between environments
- Assume desktop-first is fine — over 50% of web traffic is mobile
- Skip the loading state — perceived performance matters
- Leave TODO comments in production — they indicate incomplete work
- Commit commented-out code — use version control for history
- Trust that AI-generated code handles edge cases — it doesn't
- Trust that an imported package exists — verify it on npm