v1.2.0
  1. ds
  2. Agent
  3. Reasoning

Reasoning

A collapsible thinking rail with live words and token counts.

Thinking 0:42 · 1.8k tokens
The slow path is the row lock, not the network. Two things need checking: whether the retry burst reaches the same order, and whether the new index is used by the plan. If the plan shows a sequential scan, the composite index is the fix. If it does not, the drain has to move out of the lock.
Code
<details class="reasoning" data-live="true" open>
  <summary>
    <svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 18V5"/><path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4"/><path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5"/><path d="M17.997 5.125a4 4 0 0 1 2.526 5.77"/><path d="M18 18a4 4 0 0 0 2-7.464"/><path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517"/><path d="M6 18a4 4 0 0 1-2-7.464"/><path d="M6.003 5.125a4 4 0 0 0-2.526 5.77"/></svg>
    <span class="reasoning-title">Thinking</span>
    <span class="reasoning-word" aria-hidden="true" data-word>planning</span>
    <span class="reasoning-meta">0:42 · 1.8k tokens</span>
    <svg class="i reasoning-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>
  </summary>
  <div class="reasoning-body">The slow path is the row lock, not the network. Two things need checking: whether the retry burst reaches the same order, and whether the new index is used by the plan. If the plan shows a sequential scan, the composite index is the fix. If it does not, the drain has to move out of the lock.</div>
</details>

Reasoning sits on a rail to the left of the answer, not in a card. The node is quiet while the model thinks and fills with the accent once tokens arrive. The summary carries the plain word, the live activity, the elapsed time and the token count. The body is one click away and shimmers only while the stream is open.

The live word cycles through the work the agent is doing. It is decorative and aria-hidden; the static "Thinking" is what a screen reader reads. The shimmer is a gradient over the text, so it stops under reduced motion and the text returns to the muted colour.

Completed and empty

Thought for 42 seconds 1.8k tokens
The retry drain runs inside the transaction. It is waiting on the same row the checkout holds, so the lock is held for the length of the retry burst. Moving the drain behind the queue keeps the critical section short. No edge change is needed.
Code
<details class="reasoning">
  <summary>
    <svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 18V5"/><path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4"/><path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5"/><path d="M17.997 5.125a4 4 0 0 1 2.526 5.77"/><path d="M18 18a4 4 0 0 0 2-7.464"/><path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517"/><path d="M6 18a4 4 0 0 1-2-7.464"/><path d="M6.003 5.125a4 4 0 0 0-2.526 5.77"/></svg>
    <span class="reasoning-title">Thought for 42 seconds</span>
    <span class="reasoning-meta">1.8k tokens</span>
    <svg class="i reasoning-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>
  </summary>
  <div class="reasoning-body">The retry drain runs inside the transaction. It is waiting on the same row the checkout holds, so the lock is held for the length of the retry burst. Moving the drain behind the queue keeps the critical section short. No edge change is needed.</div>
</details>

When the turn ends, the live marker goes away, the title becomes the duration, and the word is removed. The body stays available for the reader who wants the trail.

Thinking stream ended
Code
<details class="reasoning" open>
  <summary>
    <svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 18V5"/><path d="M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4"/><path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5"/><path d="M17.997 5.125a4 4 0 0 1 2.526 5.77"/><path d="M18 18a4 4 0 0 0 2-7.464"/><path d="M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517"/><path d="M6 18a4 4 0 0 1-2-7.464"/><path d="M6.003 5.125a4 4 0 0 0-2.526 5.77"/></svg>
    <span class="reasoning-title">Thinking</span>
    <span class="reasoning-meta">stream ended</span>
    <svg class="i reasoning-chev" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>
  </summary>
  <div class="reasoning-body"></div>
</details>

An empty stream is a real state. Say so in the body instead of leaving a blank panel, so a collapsed rail that opens still explains itself.

API

ClassOnWhat it does
.reasoningdetailsThe rail. data-live="true" for a streamed turn.
.reasoning > summarysummaryIcon, title, live word, metadata and chevron.
.reasoning-titlespanThinking while live, the duration when done.
.reasoning-wordspanThe cycling activity. Decorative, so aria-hidden.
.reasoning-metaspanDuration and token count, pinned right.
.reasoning-chevsvgRotates when the rail opens.
.reasoning-bodydivThe reasoning text. Keeps line breaks.
data-live="true".reasoningAccent node, pulsing marker and text shimmer.
Do
  • Put the duration and token count in the summary.
  • Open while streaming, collapse when the answer starts.
  • Keep the trail for the turn, even after it ends.
Don't
  • Reasoning as the first thing on every turn. Collapse it once the answer arrives.
  • A spinner with no word for what is happening.
  • Making the reasoning look like the answer. It is quieter and one level down.

Accessibility

  • The rail is a native details element. The summary is the control and keeps focus.
  • The cycling word is aria-hidden. Do not put the only status text there; the title and metadata already say what happened.
  • The shimmer changes colour, not layout, and is disabled under prefers-reduced-motion.
  • At 390px the rail keeps its 2px line and the metadata drops to the smaller mono step. Nothing is hidden.