---
name: click-path-audit
description: "Trace each user button, form, and touch point through its full state change path. Find bugs where calls work alone but undo each other, race, or leave the UI in the wrong state. Use this when a button looks broken, normal checks find no cause, or a large change touches shared state."
origin: community
---

# Click Path Audit

> Community skill. Full credit goes to the original author.

Find bugs that code checks may miss. These bugs happen when state changes clash, run in the wrong order, or undo each other.

## Use This Skill When

Use this skill when:

- A button looks broken, but its handler does run.
- A form ends in the wrong state.
- A menu, modal, or panel opens and then closes.
- Two state actions work alone but fail when used together.
- A slow request can replace newer data.
- A large code change touched shared state.
- Tests pass, but the UI still feels wrong.

## What This Skill Checks

Normal checks often ask:

- Does the function exist?
- Is the event linked to it?
- Does the code crash?
- Does the function return the right type?

This audit also asks:

- Does the final UI match the button label?
- Does a later call undo an earlier call?
- Does a shared state action clear state it does not own?
- Can two async tasks finish in the wrong order?
- Can a second click run before the first click ends?
- Can a page change or unmount leave stale work running?
- Do error and cancel paths restore the right state?
- Does saved or local state bring back an old value?
- Does a form submit twice from both click and submit events?

## Core Rule

Trace the full path in the exact order it can run.

Do not stop at the click handler. Follow every call that can read or write state. Include state actions, hooks, effects, request callbacks, timers, route changes, and cleanup code.

## Step 1: Map Shared State

Before checking buttons, list each state action in the target area.

For each action, record:

- State it reads.
- State it sets.
- State it clears.
- Other actions it calls.
- Async work it starts.
- Effects that run after its state change.

Use this form:

```text
STORE: emailStore

ACTION: setComposeMode(value)
  reads: none
  sets: composeMode
  clears: none

ACTION: selectThread(thread)
  reads: thread
  sets: selectedThread, messages, drafts
  clears: composeData, selectedDraft
  also sets: composeMode = false

RISKY RESET:
  selectThread changes composeMode.
  composeMode is mainly owned by setComposeMode.
```

Mark an action as risky when it:

- Clears many fields.
- Changes state owned by another feature.
- Uses old state after async work.
- Has a broad name such as `reset`, `clearAll`, or `select`.
- Starts work that has no cancel or stale-result check.

## Step 2: List Every Touch Point

Find all user actions in the target area.

Include:

- Buttons and links.
- Form submit events.
- Inputs, selects, and toggles.
- Menu items.
- Keyboard shortcuts.
- Drag and drop.
- Swipe and touch events.
- Route changes.
- Auto-save and timer actions.

For each one, record:

```text
TOUCH POINT: New Email
LOCATION: ComposeButton.tsx:24
EVENT: onClick
EXPECTED END STATE: Compose view is open with no thread selected.
```

If the label is not clear, use the nearby text and UI flow to state the likely goal. Mark it as an assumption.

## Step 3: Trace Calls in Order

Write every call in the order it starts.

For each call, note:

- What it reads.
- What it writes.
- What it clears.
- Whether it is sync or async.
- What runs after it ends.
- What can run at the same time.

Use this form:

```text
CALL PATH:
  1. setComposeMode(true)
     writes: composeMode = true

  2. selectThread(null)
     writes: selectedThread = null
     also writes: composeMode = false

FINAL STATE:
  composeMode = false
  selectedThread = null

EXPECTED STATE:
  composeMode = true
  selectedThread = null

RESULT:
  Bug found. Step 2 undoes step 1.
```

## Step 4: Check Every End Path

Check more than the success path.

Check:

- Success.
- Error.
- Cancel.
- Timeout.
- Double click.
- Fast repeat click.
- Back button.
- Route change.
- Component unmount.
- Empty data.
- Missing data.
- Old saved state.
- Slow first request and fast second request.

Ask these questions:

1. What is the final state?
2. Does it match what the user asked for?
3. Did a later call undo an earlier call?
4. Can old async work replace new state?
5. Can an effect run and change the state again?
6. Is loading cleared on every path?
7. Is an error shown and then cleared by mistake?
8. Can the same action run twice?
9. Can cleanup code clear state used by a new screen?
10. Can local storage or cached data restore a bad value?

## Step 5: Report Findings

Report each bug with proof. Do not say only that a call is unsafe.

Use this form:

```text
FINDING: New Email closes itself
SEVERITY: High
LOCATION: ComposeButton.tsx:24
EXPECTED: Open a blank compose view.
ACTUAL: Compose mode ends as false.

CALL ORDER:
  1. setComposeMode(true)
  2. selectThread(null)
  3. selectThread sets composeMode to false

CAUSE:
  selectThread clears state owned by the compose flow.

FIX IDEA:
  Select or clear the thread before opening compose mode.
  Better: split thread selection from compose cleanup.

TEST:
  Click New Email while a thread is open.
  Check that composeMode is true and selectedThread is null.
```

Use these levels:

- **High:** The main action fails or data may be lost.
- **Medium:** The UI ends in the wrong state but the user can recover.
- **Low:** The state is odd but has little user harm.

## Concrete Example

A search box sends a request after each change:

```js
async function onSearch(text) {
  setQuery(text);
  const results = await search(text);
  setResults(results);
}
```

The user types `cat`, then quickly types `cats`.

Possible order:

```text
1. Start request for "cat".
2. Start request for "cats".
3. "cats" finishes. The UI shows results for "cats".
4. "cat" finishes. The UI now shows old results for "cat".
```

Audit result:

```text
FINDING: Old search results replace new results
SEVERITY: Medium
EXPECTED: Results match "cats".
ACTUAL: Results may match "cat".
CAUSE: The first request can finish last.
FIX IDEA: Cancel the old request or check that its query is still current.
TEST: Delay the "cat" request and let the "cats" request finish first.
```

## Completion Check

The audit is done when:

- Every touch point in scope is listed.
- Every call path is traced in order.
- Shared state writes and clears are mapped.
- Success, error, cancel, and async paths are checked.
- Each finding shows the expected and real final state.
- Each finding has a clear test.
- Areas not checked are listed as out of scope.