<!-- source-hash: f066f48b7e750fe429b96a6f9a50da79 -->
A `localStorage`-backed React hook providing optimistic up/down voting for roadmap cards, with one vote per task per user, automatic vote toggling/switching, and server sync via a configurable API endpoint.

## Key Components

### Types

| Type | Description |
|------|-------------|
| `VoteType` | `'up' \| 'down' \| null` — current vote state for a task |
| `VoteState` | Record mapping `taskId → VoteType` |
| `UseRoadmapVotingOptions` | Configuration interface for endpoint and storage key |

### Hook Return Values

| Value | Type | Description |
|-------|------|-------------|
| `votes` | `VoteState` | All current votes keyed by `taskId` |
| `isLoading` | `boolean` | `true` while hydrating from `localStorage` |
| `getVote` | `(taskId) => VoteType` | Returns current vote for a task |
| `toggleVote` | `async (taskId, voteType) => result` | Casts or removes a vote with optimistic update |
| `clearVotes` | `() => void` | Clears all votes from state and `localStorage` |

### Core Behavior

- **Optimistic updates** — UI reflects vote immediately; reverts on API failure
- **Toggle semantics** — clicking the active vote removes it; switching direction sends a remove + add pair to keep server totals consistent
- **Namespace isolation** — `storageKey` option allows multiple roadmap surfaces on the same origin without vote cross-contamination
- **Key change safety** — state resets and re-hydrates whenever `storageKey` changes mid-lifecycle

## Usage Example

```typescript
import { useRoadmapVoting } from './use-roadmap-voting';

function RoadmapCard({ taskId }: { taskId: string }) {
  const { getVote, toggleVote, isLoading } = useRoadmapVoting({
    voteApiEndpoint: '/api/roadmap/vote',
    storageKey: 'roadmap_votes_v1_main',
  });

  const currentVote = getVote(taskId);

  const handleVote = async (type: 'up' | 'down') => {
    const { success, newVote } = await toggleVote(taskId, type);
    if (!success) console.warn('Vote failed, reverted to:', newVote);
  };

  if (isLoading) return <span>Loading...</span>;

  return (
    <div>
      <button
        onClick={() => handleVote('up')}
        style={{ fontWeight: currentVote === 'up' ? 'bold' : 'normal' }}
      >
        👍
      </button>
      <button
        onClick={() => handleVote('down')}
        style={{ fontWeight: currentVote === 'down' ? 'bold' : 'normal' }}
      >
        👎
      </button>
    </div>
  );
}
```