DEV Community

Fernando Paladini
Fernando Paladini

Posted on

Turn Missing UI States into a CI Gate with unhappypath

AI-assisted development makes it easy to finish the success case and forget everything that happens when the network is slow, the result is empty, a record is missing, or a user clicks twice. The application can look complete in a demo while real users still encounter blank screens and silent failures.

Those gaps are hard to review consistently. A code review may notice a missing loading state in one component and miss the same problem in another route. End-to-end tests can catch some failures, but they need a running application, test data, and carefully designed scenarios.

unhappypath takes a narrower approach. It statically scans a React or Next.js codebase, reports missing UI branches, and produces a deterministic score that can be used as a CI threshold. It does not run a browser or ask an LLM to judge the interface.

TL;DR

Build the current public repository, scan the included broken and good fixtures, and compare the results. The broken fixture scores 38/100 with 14 findings. The good fixture scores 100/100 with zero findings. Then add the min-score option to your own check so a change below your team's threshold exits with status 1.

What it checks

The current README describes four dimensions:

  • Routes: Next.js App Router loading, error, not-found, and global-error boundaries
  • Queries: loading, error, and empty states around data fetching
  • Mutations: failure handling and pending or disabled feedback for actions
  • Forms: submitting or pending feedback during submission

The scanner reports rule IDs such as RTE-01, RTE-02, RTE-03, QRY-01, QRY-02, QRY-03, FRM-01, and MUT-02. Each finding includes a file and line location, so the score is a triage signal rather than a replacement for reading the code.

The project is version 0.1.0 in its package metadata, uses TypeScript, requires Node.js 20 or newer, and declares the MIT license. The current default branch has no release tag and no package-lock file, so the commands below use the public main checkout and npm install.

Prerequisites

You need:

  • Node.js 20 or newer
  • npm and Git
  • A React or Next.js project to scan
  • A terminal that can read the source tree

The scanner is designed for source code, not a running server. It does not need credentials, a database, a browser, or network access to analyze the project.

Install and build from source

Clone the repository and build the CLI:

git clone https://github.com/paladini/unhappypath.git
cd unhappypath
npm install
npm run build
Enter fullscreen mode Exit fullscreen mode

The compiled entry point is dist/cli.js. Check the current command surface:

node dist/cli.js --help
Enter fullscreen mode Exit fullscreen mode

The documented invocation is:

node dist/cli.js /path/to/your-app
Enter fullscreen mode Exit fullscreen mode

The path is optional and defaults to the current directory. The CLI also supports JSON, Markdown, min-score, badge, out, and quiet options.

Reproduce the broken and good scores

The repository includes two fixtures that make the behavior easy to verify without creating a sample application.

Run the intentionally incomplete fixture:

npm run demo:broken
Enter fullscreen mode Exit fullscreen mode

The current output reports:

Score: 38/100
14 findings
Enter fullscreen mode Exit fullscreen mode

It detects a Next.js App Router project, TanStack Query, and React. The findings include missing route boundaries, query states, a form submitting state, and an async button pending state.

Now run the covered fixture:

npm run demo:good
Enter fullscreen mode Exit fullscreen mode

Its current output reports:

Score: 100/100
0 findings
Enter fullscreen mode Exit fullscreen mode

The two demos give you a reproducible contract: the scanner distinguishes a fixture with deliberate gaps from one that includes the expected branches.

Scan your application

From the built checkout, scan a project root:

node dist/cli.js /path/to/your-next-app
Enter fullscreen mode Exit fullscreen mode

Use JSON when another tool will consume the result:

node dist/cli.js /path/to/your-next-app --json --out unhappypath-report.json
Enter fullscreen mode Exit fullscreen mode

A report contains the total score, dimension scores, detected technologies, and findings. A finding is intended to point you toward a code change. For example, a query finding may tell you that a useQuery call lacks a loading, error, or empty branch. A route finding may point to a missing loading.tsx, error.tsx, or not-found.tsx.

Do not treat a finding as proof that the user experience is poor. Static analysis can detect the presence or absence of recognizable branches, but it cannot judge whether the message is understandable, whether a retry really succeeds, or whether the layout remains usable on a small screen.

Turn the score into a CI gate

The useful automation boundary is the min-score option:

node dist/cli.js . --min-score 75
Enter fullscreen mode Exit fullscreen mode

The documented exit behavior is:

  • 0 when the score meets the threshold
  • 1 when the score is below the threshold
  • 2 for a usage error

The repository README also shows this GitHub Actions pattern:

- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with:
    node-version: 20
- run: npx unhappypath . --min-score 75
Enter fullscreen mode Exit fullscreen mode

The npx example is a documented future path because the current README lists npm publishing as a roadmap item. Until a package is published, use the compiled local CLI in your own workflow:

- run: node unhappypath/dist/cli.js . --min-score 75
Enter fullscreen mode Exit fullscreen mode

That command assumes the workflow has already cloned or built the scanner. Choose the threshold as a team policy. The README describes 75 as launchable and 90 as production UX, but those labels are project guidance, not a guarantee that an application is ready to ship.

Fix findings in a useful order

The README recommends fixing routes, queries, mutations, and forms in that order. Route-level boundaries can prevent a whole screen from becoming blank, while component-level feedback handles narrower interactions.

For a missing route loading state, add the convention expected by the framework:

app/
  dashboard/
    page.tsx
    loading.tsx
    error.tsx
    not-found.tsx
Enter fullscreen mode Exit fullscreen mode

For a query, make the states explicit before rendering the successful result:

if (isPending) return <LoadingPanel />;
if (isError) return <RetryPanel onRetry={refetch} />;
if (!data?.length) return <EmptyPanel />;
return <Results items={data} />;
Enter fullscreen mode Exit fullscreen mode

For a mutation or form, disable the action while the request is pending and show the user what is happening. The scanner can identify recognizable omissions, but the final copy and interaction still belong to your application.

Why a deterministic score helps

The tool intentionally avoids an LLM in the scanning loop. It uses filesystem discovery and TypeScript-oriented analysis, then produces the same kind of report for the same source tree. That makes a failing CI check easier to reproduce and review.

This keeps responsibility clear. The scanner finds candidate gaps. A developer decides whether a branch is required, fixes the code, and runs application tests that prove the behavior. unhappypath complements browser tests and manual QA; it does not replace them.

Limitations and security boundaries

The project is static analysis only. It does not run your app in a browser, simulate a slow network or a 500 response, verify UX copy, or prove that a retry action works. It currently focuses on the documented React and Next.js patterns. The roadmap lists Vue, Nuxt, SvelteKit, Remix conventions, MCP support, and npm publishing as future work.

Because it reads source files, run it only against directories you intend to inspect. It does not need network access for the scan, but dependencies still come from npm during installation. Pin and review dependencies according to your normal project policy.

Do not use the score as a security guarantee or as a substitute for accessibility, performance, integration, or end-to-end checks. A project can score well and still have a broken API, misleading error message, or inaccessible interaction.

FAQ

Does the scanner need a running Next.js app?

No. It scans source code and filesystem conventions.

Does it replace Playwright?

No. Use browser tests for runtime behavior. The scanner is a fast static complement.

Can I fail CI on findings instead of score?

The documented CLI gate is min-score. Choose a threshold that reflects the minimum coverage you want, then use the report to inspect individual findings.

Is npx unhappypath ready to use?

The current README labels npm publishing as a future option. Build from the repository until the project documents a published package.

Takeaway

A successful demo is only one UI state. Run unhappypath before calling an AI-built React or Next.js screen complete, use its file-and-line findings to guide fixes, and enforce a minimum score where a deterministic CI signal is useful. Then verify important paths with real tests and a browser.

What minimum score would be meaningful for your team: a first threshold around 75, a stricter 90, or a dimension-specific policy?

Disclosure: AI assistance was used to organize and edit this tutorial. The commands, version, output, limitations, and security boundaries were checked against the current public paladini/unhappypath README, package metadata, license, source checkout, and local build, test, and demo results.

Top comments (0)