Launch readiness · 13 min read
Frontend launch readiness: 14 checks before AI code goes live
Cursor wrote your checkout flow. Claude Code added a settings page in twenty seconds. Codex generated the whole signup form. Everything looks fine in dev — until someone opens it on a phone, in dark mode, with a screen reader, on the same Wi-Fi as a malicious frame. Here’s the deterministic 14-point checklist your AI didn’t run.
Most launch checklists are vibes. “Make sure responsive works. Test dark mode. Run an accessibility audit.” Items vague enough that anyone can tick them off without actually checking anything. Then production breaks.
This one is different. Every check is a deterministic ESLint rule that either passes or fails on every render of every file — no LLM in the check path, no judgement calls. Run it once, get a 0–100 score and a list of the exact lines to fix. Then run it again on the next AI-generated PR. Then on the one after that.
Fourteen checks across five categories — design tokens, responsive coverage, WCAG 2.2, dark mode, and the frontend-safety basics that a freshly-generated React app routinely ships without. We walk all fourteen below, with the rule that catches each one and a two-line bad/good diff. The closing section turns the whole list into a single command: npx deslint launch-check.
None of this is novel. The rules have existed in some form for years in the ESLint ecosystem. What’s new is the volume of code AI agents are now writing — and the fact that they consistently ship the same fourteen mistakes. The checklist exists because the agents don’t.
Category 1 / 5
Design tokens (3 checks)
Tailwind made the design system visible in className. AI agents make it disappear again with arbitrary values: p-[13px] instead of p-3, bg-[#1A5276] instead of bg-primary. Three checks catch every common drift before it sediments into the codebase.
No hardcoded Tailwind spacing
p-[Npx], m-[Npx], gap-[Npx], w-[Npx] and friends, and auto-fixes to the nearest scale entry.No hex colors outside the palette
className ships a brand color that isn’t in your tokens, won’t flip in dark mode, and won’t pass contrast on every surface. Catches bg-[#...], text-[rgb(...)], border-[hsl(...)] and rewrites them to the closest token. CSS variables (var(--brand)) are allowed by default.No magic numbers in grid / flex layout
grid-cols-[200px_1fr] because it matches the design at one breakpoint. It also breaks the moment a label gets longer or the language switches. The rule flags raw pixel values in grid, flex, and order utilities — CSS functions like minmax() and repeat() pass through.Category 2 / 5
Responsive (3 checks)
Most AI agents never opened DevTools. They built your UI at one viewport size, the one in their training distribution. Three checks catch the layouts that pretend desktop is everywhere.
No fixed-width containers without breakpoints
w-[800px] ships horizontal scroll on every phone in the world. The rule flags any fixed-width container (w, max-w, min-w with a px value) that doesn’t also declare a responsive variant under the configured breakpoints (sm:, md: by default).Viewport meta does not block zoom
user-scalable=no or maximum-scale=1 from a 2016 Stack Overflow answer that was wrong then too. Both block users with low vision from pinch-zooming and fail WCAG 1.4.4. The rule flags the offending viewport meta in the document head.Interactive targets ≥ 24×24
WCAG 2.5.8<button>, <a>, and form control and computes the rendered click box from Tailwind sizing utilities.Category 3 / 5
Accessibility — WCAG 2.2 (4 checks)
Accessibility is the first thing AI strips when it's 'cleaning up' code. Every check below cites the WCAG success criterion it enforces, so when reviewers ask what spec line you fail, the answer is in the lint message.
Every <img> has meaningful alt
WCAG 1.1.1alt attribute, or with placeholder text like alt="image" and alt="photo". The rule treats both as failures, distinguishes them in the message (missing vs. meaningless), and accepts alt="" on decorative images that also carry role="presentation" or aria-hidden.Every form input has a programmatic label
WCAG 1.3.1 · 3.3.2<label htmlFor>, a wrapping <label> ancestor, or an aria-label/aria-labelledby, and reports the ones that have none.No generic link text
WCAG 2.4.4No outline:none without a focus indicator
WCAG 2.4.7outline-none on every interactive element to make designs look “clean,” and forgets to add a replacement. Keyboard users can no longer see where they are. The rule flags any element that nukes the outline without declaring at least one of focus-visible:, focus:ring-*, focus:outline-*, or a corresponding utility.Category 4 / 5
Dark mode (1 check)
Either you support dark mode everywhere or you don't ship it. Half-coverage is worse than no coverage — users land on a white modal in a black app and lose trust.
Dark mode applied across, not on a sample
dark: variants to half the file and called it done. The rule walks every element with a color or background utility, checks for a paired dark: variant on the same property, and reports the ones still in light mode. Off in the recommended config — turn it to warn when you start shipping dark mode.Category 5 / 5
Frontend safety (3 checks)
The basics every shipped app should pass. AI generates these patterns confidently and incorrectly: rendered comments via dangerouslySetInnerHTML, target=_blank without rel, embedded iframes without sandbox. All three landed as new rules in Deslint 0.8.
No dangerouslySetInnerHTML on untrusted data
dangerouslySetInnerHTML and never sanitizes. The rule flags every JSX element that uses the prop, with three deliberate whitelist exceptions: <script type="application/ld+json"> (Schema.org structured data is always dev-controlled), <style> (CSS injection has a different threat model), and Next.js’s <Script> component (inline scripts via the framework’s loading strategy).<a target="_blank"> has rel="noopener noreferrer"
noopener, the new tab can navigate the opener via window.opener (reverse tab-nabbing). Without noreferrer, the destination sees the source URL in headers. The rule flags missing rel attributes and partial rel values (only one of the two tokens), and autofixes on JSX.<iframe> has a sandbox attribute
<iframe> missing the attribute. Suggestion only: the right sandbox value depends on what the embed needs to do, so we don’t auto-fix.Run the whole checklist in one command
Reading a 14-point list is helpful exactly once. The goal is for it to run on every PR before anyone else has to think about it. Two commands do that:
Both commands run locally, with zero LLM in the check path and zero code leaving your machine. See the full HTML report the first command writes to .deslint/report.html. Once a project clears the checklist on local, wire it into the agent loop with the MCP server so Cursor / Claude Code / Codex / Windsurf can’t silently regress what you just fixed, and at the merge gate with the GitHub Action so PRs that drop the score are blocked.
Why a deterministic checklist beats “run it through another AI”
A second LLM reviewing the first one feels productive and isn’t. Two reasons.
Same input, different output. Run the same scan twice and an AI reviewer flags different things on the second pass. Run a deterministic ESLint rule twice and the messages are byte-identical. When the merge gate fails, you can point at a line. When you fix it, the failure goes away.
No exfiltration surface. Every rule on this checklist runs on your machine against your files. No code leaves your laptop, your CI runner, or your air-gapped enterprise environment. The MCP server uses stdio — the same protocol your editor already uses to talk to language servers — so the data path is local, auditable, and indistinguishable from any other lint run.
The rules in this checklist exist because the patterns AI gets wrong are the same ones humans got wrong before AI — we just now hit them an order of magnitude more often. ESLint, Tailwind, and the WCAG specs already encode the answers. A linter is the shape of tool that turns those answers into a one-command checklist you can ship behind.
Related reading
How to fix design drift in AI-generated code
The same agent that ships these 14 mistakes also ships token drift. A deterministic playbook for catching it at generation time.
The hidden cost of Tailwind arbitrary values
Why
p-[13px]andbg-[#1a5276]are more expensive than they look, and why AI agents amplify the cost.Tailwind v4 ESLint migration: a deterministic upgrade guide
A working playbook for moving an ESLint setup from Tailwind v3 to v4 — what changes, what goes stale, and the deterministic checks that make the migration boring instead of risky.