# Client-side token counting validates user input before submission

- **Pattern:** `ab-000328` (`ai-token-optimization.streaming-performance.client-side-input-validation`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000328
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000328)

## Why it matters

Server-side-only validation for empty or oversized inputs forces every request through a full network round-trip before a basic error surfaces, wasting LLM API spend on requests that will be rejected and burning user patience on latency that could be zero. A user pasting a 200KB document into a chat box discovers the limit after the spinner, not before. Client-side input validation short-circuits these failures at the keyboard, cutting wasted provider tokens and improving perceived performance for the user-experience taxon this pattern tracks.

## Severity rationale

Low because the failure mode is latency and wasted tokens, not data loss or security compromise.

## Remediation

Add a character-count check and disabled submit button to the chat input component at `src/components/chat-input.tsx`. Track input length in local state, compute `isOverLimit` against a fixed ceiling, and block the form onSubmit when the input is empty or exceeds it. Show a live counter past a warning threshold so users self-correct before sending.

```tsx
const MAX_CHARS = 20000;
const isOverLimit = input.length > MAX_CHARS;
<button type="submit" disabled={!input.trim() || isOverLimit}>Send</button>
```

## Detection

- **ID:** `client-side-input-validation`
- **Severity:** `low`
- **What to look for:** In the chat input component or form, look for client-side validation of the input length before the form submits. This could be: a character or word count check, a disabled submit button when input is empty or exceeds a threshold, a visual token counter displayed to the user, or a client-side import of a tokenizer library. Also check for server-side-only validation where the request must round-trip before basic input errors are caught. Count all instances found and enumerate each.
- **Pass criteria:** The UI prevents or warns on submission when user input is empty or clearly exceeds a reasonable limit (e.g., character count > 20,000). Ideally, a token counter is shown so users can manage their input length proactively. At least 1 implementation must be confirmed.
- **Fail criteria:** Validation is server-side only — the frontend submits any input length without client-side checks, requiring a round-trip before basic errors (empty input, excessively long input) are caught.
- **Skip (N/A) when:** The application has no user input form — it operates entirely on server-side or programmatically generated prompts.
  Signal: No `<input>`, `<textarea>`, or `useChat` hook usage in any frontend component.
- **Detail on fail:** `"No client-side input validation — empty or oversized inputs require server round-trip to catch"`
- **Remediation:** Server-side validation for basic input errors (empty, too long) adds unnecessary latency and wastes server resources. Client-side checks catch these immediately.

  ```tsx
  // src/components/chat-input.tsx
  const MAX_CHARS = 20000;
  const WARN_CHARS = 15000;

  export function ChatInput({ onSubmit }: { onSubmit: (input: string) => void }) {
    const [input, setInput] = useState("");
    const isOverLimit = input.length > MAX_CHARS;
    const isNearLimit = input.length > WARN_CHARS;

    return (
      <form onSubmit={e => { e.preventDefault(); if (!isOverLimit && input.trim()) onSubmit(input); }}>
        <textarea
          value={input}
          onChange={e => setInput(e.target.value)}
          maxLength={MAX_CHARS + 100} // soft enforcement
        />
        {isNearLimit && (
          <p style={{ color: isOverLimit ? "red" : "orange" }}>
            {input.length.toLocaleString()} / {MAX_CHARS.toLocaleString()} characters
            {isOverLimit && " — input too long"}
          </p>
        )}
        <button type="submit" disabled={input.trim().length === 0 || isOverLimit}>
          Send
        </button>
      </form>
    );
  }
  ```

  Verify by attempting to submit an empty message (button should be disabled) and pasting a very long text (counter should appear and submission should be blocked).

Taxons: user-experience

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