> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dualmindlab.tech/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> DualMind Arena API base URL: https://api.dualmindlab.tech. Public endpoints only; admin routes are not part of this API.
> Authenticate with the access token from an Arena session (a Supabase JWT) as `Authorization: Bearer <token>`. There are no API keys yet; never invent one.
> For battles call POST /api/arena/dualchat, then vote with POST /api/arena/model-vote using the returned comparisonId. Keep model names hidden from voters until they vote.
> GET /api/arena/model-stats (leaderboard) and GET /health need no token.

# Voting

> The four vote options, what happens when you vote, and how a vote reaches the leaderboard.

export const BattleDemo = () => {
  const battle = {
    prompt: "Explain a race condition to a new developer in two sentences.",
    a: {
      text: "A race condition happens when two pieces of code touch the same data at the same time and the result depends on which one finishes first. It's like two people editing one document without talking: whoever saves last silently wins.",
      name: "Alpha 70B",
      rating: 1040
    },
    b: {
      text: "Race conditions are concurrency bugs involving shared mutable state and non-deterministic scheduling. Use locks, atomics or message passing to serialize access to critical sections.",
      name: "Beta 8B",
      rating: 1000
    }
  };
  const [vote, setVote] = useState(null);
  const [started] = useState(() => Date.now());
  const [elapsed, setElapsed] = useState(0);
  const choices = [["left", "👈 Left is better"], ["tie", "🤝 It's a tie"], ["both-bad", "👎 Both are bad"], ["right", "👉 Right is better"]];
  const k = 32;
  const expA = 1 / (1 + Math.pow(10, (battle.b.rating - battle.a.rating) / 400));
  const scoreA = vote === "left" ? 1 : vote === "right" ? 0 : 0.5;
  const decisive = vote === "left" || vote === "right";
  const dA = vote && vote !== "both-bad" ? Math.round(k * (scoreA - expA)) : 0;
  const dB = vote && vote !== "both-bad" ? -dA : 0;
  const fmt = d => d > 0 ? `+${d}` : `${d}`;
  const cast = choice => {
    setElapsed(Date.now() - started);
    setVote(choice);
  };
  return <div className="dm-widget not-prose" role="group" aria-label="Blind battle demo">
      <div className="dm-widget-head">
        <div>
          <span className="dm-label">Interactive demo</span>
          <p className="dm-widget-title">Try a blind battle</p>
        </div>
        {vote && <button type="button" className="dm-ctl" onClick={() => setVote(null)}>
            Reset
          </button>}
      </div>

      <div className="dm-battle-prompt">
        <span className="dm-label">Prompt</span>
        <div style={{
    marginTop: "0.25rem"
  }}>{battle.prompt}</div>
      </div>

      <div className="dm-grid-2">
        {[["A", battle.a, vote === "left"], ["B", battle.b, vote === "right"]].map(([slot, side, won]) => <div key={slot} className="dm-answer" data-winner={won ? "true" : "false"}>
            <span className="dm-label dm-answer-tag">
              {vote ? `${side.name} (demo)` : `Model ${slot}`}
              {won ? " · winner" : ""}
            </span>
            <div>{side.text}</div>
          </div>)}
      </div>

      {!vote && <div className="dm-vote-row" role="group" aria-label="Which response was better?">
          {choices.map(([id, text]) => <button key={id} type="button" className="dm-ctl" onClick={() => cast(id)}>
              {text}
            </button>)}
        </div>}

      <div aria-live="polite">
        {vote && <div className="dm-reveal">
            <strong>Names revealed.</strong>{" "}
            {decisive ? `Your vote moves ${Math.abs(dA)} ELO points: ${battle.a.name} ${fmt(dA)}, ${battle.b.name} ${fmt(dB)}.` : vote === "tie" ? `In ELO a tie counts as half a win each, nudging the ratings together: ${battle.a.name} ${fmt(dA)}, ${battle.b.name} ${fmt(dB)}.` : 'A "Both are bad" vote records the battle with no winner.'}
            <div className="dm-muted" style={{
    fontSize: "0.8rem",
    marginTop: "0.4rem"
  }}>
              Demo answers and model names are made up. In the Arena the answers are live and the names are real.
            </div>
            <pre className="dm-code" aria-label="The vote request the Arena sends">{`POST /api/arena/model-vote
{
  "comparisonId": "3f6c1a2b-9d4e-4f7a-8b1c-0e2d3f4a5b6c",
  "voteChoice": "${vote}",
  "voteDurationMs": ${elapsed}
}`}</pre>
          </div>}
      </div>
    </div>;
};

After both answers arrive, the Arena asks **Which response was better?** Your vote is the signal the whole leaderboard is built on.

<BattleDemo />

## Vote options

| Button | API value | Meaning | Winner recorded |
| :- | :- | :- | :- |
| 👈 **Left is better** | `left` | Model A (left) gave the better answer | Left model |
| 👉 **Right is better** | `right` | Model B (right) gave the better answer | Right model |
| 🤝 **It's a tie** | `tie` | Both were good, and about equally so | None |
| 👎 **Both are bad** | `both-bad` | Neither answer was helpful | None |

<Tip>
  Use **Both are bad** honestly. It keeps two weak answers from being counted as a tie between good ones.
</Tip>

## What happens when you vote

```mermaid theme={null}
sequenceDiagram
    autonumber
    actor You
    participant UI as Arena
    participant API as DualMind API
    participant DB as Votes & leaderboard
    You->>UI: Click a vote button
    UI->>API: POST /api/arena/model-vote<br/>{comparisonId, voteChoice}
    API->>DB: Store one vote for (you, this comparison)
    API->>DB: Mark the comparison as revealed
    API-->>UI: Vote recorded
    UI-->>You: Model names revealed
    Note over DB: Leaderboard stats include the new vote
```

<Steps>
  <Step title="One vote per battle">
    Each signed-in user gets one vote per comparison. A second vote on the same comparison is rejected, so votes can't be changed or stacked.
  </Step>

  <Step title="Reveal">
    The vote marks the comparison as revealed and the Arena shows which model was which. After a decisive vote the Arena highlights the winning answer; you can still expand the other one.
  </Step>

  <Step title="Ranking">
    The vote counts toward both models' wins, losses or ties and their ELO score on the [leaderboard](/concepts/leaderboard).
  </Step>
</Steps>

## Good voting habits

<CardGroup cols={2}>
  <Card title="Read both answers fully" icon="book-open-check">
    The first answer you read becomes an anchor. Give the second one the same attention.
  </Card>

  <Card title="Ignore speed" icon="timer-off">
    Both answers arrive together. How fast a model is says little about how good its answer is.
  </Card>

  <Card title="Watch for system notes" icon="triangle-alert">
    A reply that starts with **\[System Note]** came from a fallback model because the original was unavailable.
  </Card>

  <Card title="Skip if unsure" icon="skip-forward">
    You don't have to vote. A thoughtful vote is worth more than a guess.
  </Card>
</CardGroup>

<Note>
  Voting from code? See [Submit a vote](/api-reference/voting/submit-vote) and the [Run a battle](/api-reference/guides/run-a-battle) tutorial.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.