# Stop generation button is present during streaming

- **Pattern:** `ab-000331` (`ai-ux-patterns.core-interaction.stop-generation`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000331
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000331)

## Why it matters

A streaming AI response can run 10–30 seconds. Without a stop control wired to AbortController, users who trigger an unwanted response — wrong prompt, sensitive topic, runaway context — have no recourse except a full page reload, which destroys conversation history. This directly violates iso-25010:2011 reliability.fault-tolerance: the system must handle user-initiated cancellation gracefully. Beyond UX, holding an open streaming connection for a generation the user abandoned wastes API credits and server resources. On metered plans this creates real cost exposure.

## Severity rationale

Critical because the user has no escape from an unwanted or runaway generation except destroying the page — a hard blocker for any conversational AI product.

## Remediation

Wire `useChat`'s `stop()` or an `AbortController` to a button that renders only while `isLoading` is true. The control must be in the DOM and visible during streaming — dead code in JSX does not satisfy this check.

```tsx
const { stop, isLoading } = useChat()

<button
  onClick={stop}
  aria-label="Stop generating"
  className={isLoading ? 'flex items-center gap-1' : 'hidden'}
>
  <SquareIcon className="w-4 h-4" /> Stop
</button>
```

For raw `fetch` implementations, store an `AbortController` in a ref and call `.abort()` from the stop handler in `src/components/chat/ChatInput.tsx`.

## Detection

- **ID:** `stop-generation`
- **Severity:** `critical`
- **What to look for:** Count all streaming state variables (`isLoading`, `isStreaming`, `isPending`) across the codebase. For each, enumerate whether a stop, cancel, or abort control is conditionally rendered. Check for `AbortController` usage in API calls or `stop()` function calls from AI SDK hooks. Verify the stop control replaces or supplements the submit button while generation is in progress. A stop button that exists in JSX but is never conditionally shown during streaming does not count as pass — do not pass based on dead code.
- **Pass criteria:** A stop or cancel button is visible while the AI is generating a response. The button calls `AbortController.abort()` or an equivalent cancellation mechanism, ending the stream. At least 1 streaming entry point must have a wired stop control. Report: "X of Y streaming entry points have stop controls."
- **Fail criteria:** No stop button or cancel control found. The user must wait for generation to complete or refresh the page to cancel a long-running or unwanted response.
- **Skip (N/A) when:** Same as `regeneration-button` — project has no AI generation interface.
- **Detail on fail:** `"No stop/cancel button found — streaming state detected (isLoading flag in useChat or similar) but no AbortController or stop() handler wired to a UI control"`.
- **Remediation:** Long AI responses can take 10-30 seconds. Without a stop button, users who realize the response is wrong have no recourse.

  ```tsx
  const { stop, isLoading } = useChat()

  <button
    onClick={stop}
    aria-label="Stop generating"
    className={isLoading ? 'block' : 'hidden'}
  >
    <SquareIcon className="w-4 h-4" />
    Stop
  </button>
  ```

  With a manual fetch approach:

  ```tsx
  const abortControllerRef = useRef<AbortController | null>(null)

  const startGeneration = () => {
    abortControllerRef.current = new AbortController()
    fetch('/api/chat', { signal: abortControllerRef.current.signal, ... })
  }

  const stopGeneration = () => abortControllerRef.current?.abort()
  ```

---

## External references

- iso-25010 reliability.fault-tolerance

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-000331
