# Unhappypath

> Run the unhappypath CLI to find missing loading, empty, error, and retry UI states, then fix findings and re-run until the score target is met.

- Skill: `paladini/unhappypath` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paladini/unhappypath`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paladini/unhappypath/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: paladini (https://skillmd.com/u/paladini)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paladini/unhappypath

---


# unhappypath

## What this skill does

Runs the **unhappypath** scanner — a deterministic CLI that finds missing **unhappy-path UI** in Next.js/React apps:

- **Loading** — user sees blank screen while data fetches
- **Empty** — list renders nothing with no explanation or CTA
- **Error** — API failure shows no message or retry
- **Pending** — buttons/forms allow double-submit

It does **not** run the browser or call an LLM. It reads source files and reports findings with file + line + rule ID.

## Prerequisites

Install or build unhappypath once:

```bash
# From npm (when published)
npx unhappypath --help

# From clone
git clone https://github.com/paladini/unhappypath.git
cd unhappypath && npm ci && npm run build
# then use: node /path/to/unhappypath/dist/cli.js .
```

## Workflow (follow exactly)

1. **Scan** from the user's app root (where `package.json` is):

   ```bash
   npx unhappypath .
   # or: node /path/to/unhappypath/dist/cli.js .
   ```

2. **Parse output** — note score, band, and every finding (`RTE-*`, `QRY-*`, `MUT-*`, `FRM-*`).

3. **Fix in priority order:**
   - Routes (`RTE-*`) — add `loading.tsx`, `error.tsx`, `not-found.tsx`, `global-error.tsx`
   - Queries (`QRY-*`) — add `if (isLoading)`, `if (isError)`, empty-data branches
   - Mutations (`MUT-*`) — disable buttons while pending; never use empty `catch {}`
   - Forms (`FRM-*`) — wire `isSubmitting` / disabled submit

4. **Re-run** until score ≥ target (default **75**, production **90**):

   ```bash
   npx unhappypath . --min-score 75
   ```

5. **Report** final score and list any remaining findings.

## Fix recipes

| ID | Fix |
| --- | --- |
| RTE-01 | Add `loading.tsx` in route segment or parent `app/` |
| RTE-02 | Add `not-found.tsx`; use `notFound()` for missing resources |
| RTE-03 | Add `'use client'` `error.tsx` with `reset()` retry button |
| RTE-04 | Add `app/global-error.tsx` |
| QRY-01 | `if (isLoading) return <Loading />` |
| QRY-02 | `if (isError) return <ErrorUI error={error} onRetry={refetch} />` |
| QRY-03 | `if (!data?.length) return <EmptyState />` |
| MUT-02 | `disabled={pending}` + pending state around async handler |
| FRM-01 | `disabled={isSubmitting}` on submit button |

Full catalog: https://github.com/paladini/unhappypath/blob/main/docs/FINDINGS.md

## Rules

- **Always run the CLI** — never substitute an LLM-based code review for unhappypath.
- Prefer **minimal diffs** in flagged files unless a shared wrapper (e.g. `<UIStates>`) is clearly better.
- Prefer **skeletons** over spinners for loading; always offer **retry** on errors.
- If a finding looks like a false positive, note the ID and explain why — do not silently ignore.

