feat(chat): prompt navigator list preview, prompt filtering, shell status fix (#2211)

* feat(chat): prompt navigator list preview with prompt filtering

The hover preview is now an interactive scrolling mini-list of prompts:
rows render as bordered two-line cards, the highlighted row stays inside
a center dead zone and the list glides only near the window edges, wheel
steps the highlight, and the panel stays open when the pointer moves into
it so a click can be corrected inside the list.

Rail entries are filtered to real prompts: previews are built from
normalized user display parts (synthetic context stripped), fully
synthetic user messages are excluded, and shell-mode messages show their
extracted command via the shared shell bridge helpers.

* fix(chat): render shell command status transitions

The injected /shell text part carries live state in shellAction, which
the render-relevant part comparator ignored — a running→completed update
reached the store but never re-rendered the message row until the next
send. Compare shellAction command/output/status for text parts.

* fix(sync): stream shell bridge part updates while running

Streaming suspension keeps part updates out of the static message records
while an assistant message streams, relying on the live streaming-tail
path to render it. Shell-mode bridge messages are hidden from the
timeline and rendered inside the user row, so they have no live path —
suspension froze their output chunks and left the card without a Show
output action until the run finished. Exempt shell bridges (single bash
tool part parented to a synthetic shell-marker user message) from
suspension; their updates arrive at command-output pace, not delta pace.

* feat(chat): syntax-highlight shell command card

Render the shell-mode command and its output through the shared
WorkerHighlightedCode (Shiki) with bash grammar, matching the bash tool
part presentation, instead of plain pre blocks.
This commit is contained in:
Bohdan Triapitsyn
2026-07-14 00:59:07 +03:00
committed by GitHub
parent 68f1c1efe3
commit a1badccddd
7 changed files with 434 additions and 97 deletions
@@ -11,6 +11,7 @@ import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
import type { StreamPhase, ToolPopupContent, AgentMentionInfo } from './types';
import type { TurnChangedFile, TurnGroupingContext } from '../lib/turns/types';
import { cn } from '@/lib/utils';
import { WorkerHighlightedCode } from '@/components/code/WorkerHighlightedCode';
import { isEmptyTextPart, extractTextContent } from './partUtils';
import { FadeInOnReveal } from './FadeInOnReveal';
import { Button } from '@/components/ui/button';
@@ -278,6 +279,8 @@ const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
);
};
const SHELL_CODE_TAG_STYLE: React.CSSProperties = { background: 'transparent', backgroundColor: 'transparent' };
const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part }) => {
const [expanded, setExpanded] = React.useState(false);
const [copiedOutput, setCopiedOutput] = React.useState(false);
@@ -335,9 +338,14 @@ const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part })
</div>
{command ? (
<pre className="typography-meta mt-1.5 overflow-x-auto whitespace-pre-wrap break-words text-foreground/90 font-mono">
{command}
</pre>
<div className="typography-meta mt-1.5 overflow-x-auto font-mono">
<WorkerHighlightedCode
language="bash"
code={command}
codeStyle={SHELL_CODE_TAG_STYLE}
wrap
/>
</div>
) : null}
{hasOutput ? (
@@ -363,9 +371,14 @@ const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part })
</button>
</div>
{expanded ? (
<pre className="typography-meta mt-1.5 max-h-56 overflow-auto whitespace-pre-wrap break-words text-foreground/85 font-mono">
{output}
</pre>
<div className="typography-meta mt-1.5 max-h-56 overflow-auto font-mono text-foreground/85">
<WorkerHighlightedCode
language="bash"
code={output}
codeStyle={SHELL_CODE_TAG_STYLE}
wrap
/>
</div>
) : null}
</div>
) : null}
@@ -100,6 +100,18 @@ export const areRenderRelevantPartsEqual = (left: Part[], right: Part[]): boolea
if (readPartText(leftPart) !== readPartText(rightPart)) {
return false;
}
// Shell-mode user messages carry their live command state in an
// injected `shellAction` payload on a synthetic text part; without
// comparing it, a running→completed transition never re-renders.
const leftShell = (leftPart as { shellAction?: { command?: unknown; output?: unknown; status?: unknown } }).shellAction;
const rightShell = (rightPart as { shellAction?: { command?: unknown; output?: unknown; status?: unknown } }).shellAction;
if (leftShell || rightShell) {
if (leftShell?.command !== rightShell?.command
|| leftShell?.output !== rightShell?.output
|| leftShell?.status !== rightShell?.status) {
return false;
}
}
}
}