feat(site): add pinned thinking display mode for agents chat#25174
Draft
tracyjohnsonux wants to merge 14 commits into
Draft
feat(site): add pinned thinking display mode for agents chat#25174tracyjohnsonux wants to merge 14 commits into
tracyjohnsonux wants to merge 14 commits into
Conversation
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
Docs preview📖 View docs preview for |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new Pinned thinking display mode that keeps a "Thinking" indicator fixed in place while agent activity streams above it.
What it does
How to test
StreamingOutput>PinnedModeWithActivityandPinnedModeWithResponseFiles changed
codersdk/users.goThinkingDisplayModePinnedenum valueConversationTimeline.tsxpinnedin exhaustive switch (thinking disclosures stay collapsed)StreamingOutput.tsxPinnedStreamingContentcomponent with block splitting, height-constrained container, CSS mask fade, pinned indicatorDisplayModeSettings.tsxStreamingOutput.stories.tsxPinnedModeWithActivityandPinnedModeWithResponsestoriestypesGenerated.ts,docs.go,swagger.json,schemas.md