Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(site): add pinned thinking display mode for agents chat#25174

Draft
tracyjohnsonux wants to merge 14 commits into
mainfrom
tracy/pinned-thinking-display
Draft

feat(site): add pinned thinking display mode for agents chat#25174
tracyjohnsonux wants to merge 14 commits into
mainfrom
tracy/pinned-thinking-display

Conversation

@tracyjohnsonux
Copy link
Copy Markdown
Contributor

Adds a new Pinned thinking display mode that keeps a "Thinking" indicator fixed in place while agent activity streams above it.

What it does

  • A persistent "Thinking" shimmer indicator stays pinned at a fixed position
  • All agent activity (thinking blocks, tool calls, subagents) streams in a height-constrained container above the indicator
  • Content at the bottom of the container fades out via a CSS gradient mask, creating the effect of thoughts materializing as they scroll upward
  • When the agent produces response text, the thinking indicator disappears and the response renders at full brightness outside the constrained area

How to test

  1. Go to agent chat settings and select Pinned for Thinking Display mode
  2. Send a message that triggers thinking/tool use
  3. Observe the pinned "Thinking" indicator and fading activity container
  4. Storybook: StreamingOutput > PinnedModeWithActivity and PinnedModeWithResponse

Files changed

File Change
codersdk/users.go Add ThinkingDisplayModePinned enum value
ConversationTimeline.tsx Handle pinned in exhaustive switch (thinking disclosures stay collapsed)
StreamingOutput.tsx New PinnedStreamingContent component with block splitting, height-constrained container, CSS mask fade, pinned indicator
DisplayModeSettings.tsx Add "Pinned" option to dropdown with updated description
StreamingOutput.stories.tsx Add PinnedModeWithActivity and PinnedModeWithResponse stories
Generated files typesGenerated.ts, docs.go, swagger.json, schemas.md

Generated by Coder Agents

Add a new 'Pinned' thinking display mode that keeps a 'Thinking'
indicator fixed in place while all agent activity (thinking, tool
calls, subagents) streams above it in a height-constrained container
with a bottom-fade gradient. When the agent produces response text,
activity blocks stay in the constrained area and the response renders
at full brightness below the pinned indicator.

This creates a smoother visual experience during long thinking phases:
- Thinking indicator stays fixed, not jumping around
- Activity fades into view as it scrolls upward
- Clear visual transition when the agent starts responding

Changes:
- codersdk/users.go: Add ThinkingDisplayModePinned enum value
- ConversationTimeline.tsx: Handle 'pinned' in exhaustive switch
  (individual thinking disclosures stay collapsed in pinned mode)
- StreamingOutput.tsx: New PinnedStreamingContent component that
  splits blocks into activity vs response, renders activity in a
  max-h-48 container with CSS mask fade, and pins 'Thinking' below
- DisplayModeSettings.tsx: Add 'Pinned' option to dropdown
- StreamingOutput.stories.tsx: Add PinnedModeWithActivity and
  PinnedModeWithResponse stories
@github-actions
Copy link
Copy Markdown

Docs preview

📖 View docs preview for docs/reference/api/schemas.md

The Thinking indicator was jumping up and down as streaming blocks
were added because the container grew dynamically. Fix by using a
fixed-height (h-48) flex column: the activity area scrolls inside
flex-1, and the Thinking indicator sits in a shrink-0 slot at the
bottom. The container height never changes, so Thinking stays put.

When the agent starts responding, the fixed container is dropped
and the response renders normally below.
Three 'Thinking' labels were showing simultaneously:
1. ReasoningDisclosure 'Thinking >' from per-block rendering
2. StreamingThinkingPlaceholder 'Thinking...' from the pre-block fallback
3. PinnedThinkingIndicator shimmer from the pinned container

Fix: In pinned mode during streaming, suppress thinking blocks in
BlockList (the pinned indicator replaces them). Always route through
PinnedStreamingContent even before blocks arrive so the fixed-height
container and pinned indicator render from the first frame.
Filter out thinking blocks at the PinnedStreamingContent level
before passing to BlockList, rather than relying on BlockList's
async preference query to suppress them. This guarantees no
'Thinking >' disclosures appear in the activity area regardless
of query loading state. The pinned shimmer indicator at the bottom
is the only thinking signal in this mode.
The pinned Thinking indicator was only visible during the
'streaming' phase, missing 'starting', 'retrying', and
'reconnecting'. This meant it didn't appear when the user first
sent a message and vanished during retries.

Replace the isStreaming check with isAgentWorking which is true
for every non-idle, non-failed phase. The indicator now appears
the moment the user sends a message and stays put until response
text arrives or the turn ends.
The preference query loads async, so the first render falls through
to the default mode ('Thinking...' at text-[13px]). Once the query
resolves with 'pinned', the indicator switched to 'Thinking' at
text-sm (14px), causing a visible font jump.

Match PinnedThinkingIndicator to the same text ('Thinking...') and
font size (text-[13px] leading-relaxed) as the default StatusPlaceholder
so the transition is invisible.
The indicator vanished when response text appeared, leaving a big
empty gap while the agent was still working (e.g. spawning
subagents). The fixed-height box also disappeared, causing layout
shifts.

Consolidate into a single render path: the fixed h-48 box with the
pinned 'Thinking...' indicator stays visible for the entire turn
(any non-idle, non-failed phase). Response text renders below the
box at full brightness. The box only disappears when the turn
completes.
Response text was rendering outside the fixed-height container,
so expanding tool output (like workspace creation logs) could
push the Thinking indicator around. Remove the response/activity
block split entirely. All visible blocks (response text, tool
calls, files) now scroll inside the fixed h-48 box. Only the
Thinking indicator sits outside at the bottom, immovable.
Three different components rendered 'Thinking' with inconsistent
text and sizes, causing visible jumps during state transitions:

- StreamingThinkingPlaceholder: 'Thinking' at text-sm (14px)
- PinnedThinkingIndicator: 'Thinking...' at text-[13px]
- ReasoningDisclosure: 'Thinking' at text-[13px] (no leading-relaxed)

Normalize all to 'Thinking...' at text-[13px] leading-relaxed
(with dots for streaming variants, without for the collapsed
historical label). Transitions between states are now invisible.
The indicator lived inside PinnedStreamingContent which only
handles the live stream. When content got committed to the
conversation timeline during a long turn, the streaming output
would shrink or unmount, and the indicator vanished.

Move the indicator to LiveStreamTailContent, which renders at
the bottom of the chat after all historical messages. It now
persists for the entire active turn regardless of how many
messages get committed during that turn. The StreamingOutput
still shows the capped activity area with fade, but the
indicator itself is one level up.
The indicator was showing whenever the turn was active, even
while the agent was writing its visible response to the user.
Switching between chats caused the indicator to flash back
because the stream rehydrated without response blocks yet.

Now check streamState.blocks for a response block. Once the
agent starts producing visible text, the indicator hides so
the user focuses on the response, not 'Thinking...'.
Adjacent tool blocks use data-tool-call CSS selectors to collapse
padding between them. The extra space-y-3 wrapper was adding 0.75rem
gap on top of the tool padding, creating a visibly larger gap between
tool blocks (e.g. Spawned vs Waiting). Remove the wrapper divs and
render BlockList directly so tool adjacency CSS works correctly.
Scrap the overengineered approach (fixed-height containers, block
splitting, fade masks, block filtering, multi-level indicators)
and replace with two simple behaviors:

1. StreamingOutput: in pinned mode, all internal activity (thinking,
   tools, subagents) renders at 45% opacity until response text
   appears, then transitions to full brightness. No layout changes,
   no block filtering, no special containers.

2. LiveStreamTailContent: shows a 'Thinking...' shimmer at the
   bottom of the chat while the agent is working and hasn't
   started writing response text. Disappears when response arrives.

Reverts the BlockList thinking-block suppression and the
ConversationTimeline null-return hack. Everything uses the
normal rendering pipeline, just muted.
The indicator was inside the scroll container (LiveStreamTailContent)
so it scrolled with the content and got pushed around by expanding
tool output and message commits.

Move it to AgentChatPageView, between ChatScrollContainer and
ChatPageInput. It's now in the normal document flow outside the
scroll area, so it always appears in the same fixed spot just
above the chat input. Nothing inside the scroll container can
affect its position.

New PinnedThinkingBanner component reads the store directly and
renders when mode=pinned, agent is working, and no response text.

Text changed to 'Weeping...' temporarily for testing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant