Skip to content

Commit 3bf63af

Browse files
committed
fix: preserve latest reasoning status without empty rows
1 parent 23497ff commit 3bf63af

2 files changed

Lines changed: 75 additions & 26 deletions

File tree

src/features/messages/components/Messages.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,42 @@ describe("Messages", () => {
220220
expect(workingText?.textContent ?? "").not.toContain("Old reasoning title");
221221
});
222222

223+
it("keeps the latest title-only reasoning label without rendering a reasoning row", () => {
224+
const items: ConversationItem[] = [
225+
{
226+
id: "reasoning-title-only",
227+
kind: "reasoning",
228+
summary: "Indexing workspace",
229+
content: "",
230+
},
231+
{
232+
id: "tool-after-reasoning",
233+
kind: "tool",
234+
title: "Command: rg --files",
235+
detail: "/tmp",
236+
toolType: "commandExecution",
237+
output: "",
238+
status: "running",
239+
},
240+
];
241+
242+
const { container } = render(
243+
<Messages
244+
items={items}
245+
threadId="thread-1"
246+
workspaceId="ws-1"
247+
isThinking
248+
processingStartedAt={Date.now() - 1_000}
249+
openTargets={[]}
250+
selectedOpenAppId=""
251+
/>,
252+
);
253+
254+
const workingText = container.querySelector(".working-text");
255+
expect(workingText?.textContent ?? "").toContain("Indexing workspace");
256+
expect(container.querySelector(".reasoning-inline")).toBeNull();
257+
});
258+
223259
it("merges consecutive explore items under a single explored block", async () => {
224260
const items: ConversationItem[] = [
225261
{

src/features/messages/components/Messages.tsx

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type MessageRowProps = {
7373

7474
type ReasoningRowProps = {
7575
item: Extract<ConversationItem, { kind: "reasoning" }>;
76+
parsed: ReturnType<typeof parseReasoning>;
7677
isExpanded: boolean;
7778
onToggle: (id: string) => void;
7879
onOpenFileLink?: (path: string) => void;
@@ -124,6 +125,9 @@ type MessageListEntry =
124125
| { kind: "item"; item: ConversationItem }
125126
| { kind: "toolGroup"; group: ToolGroup };
126127

128+
const SCROLL_THRESHOLD_PX = 120;
129+
const MAX_COMMAND_OUTPUT_LINES = 200;
130+
127131
function basename(path: string) {
128132
if (!path) {
129133
return "";
@@ -225,23 +229,6 @@ function parseReasoning(item: Extract<ConversationItem, { kind: "reasoning" }>)
225229
};
226230
}
227231

228-
function reasoningWorkingLabel(items: ConversationItem[]) {
229-
for (let index = items.length - 1; index >= 0; index -= 1) {
230-
const item = items[index];
231-
if (item.kind === "message") {
232-
break;
233-
}
234-
if (item.kind !== "reasoning") {
235-
continue;
236-
}
237-
const parsed = parseReasoning(item);
238-
if (parsed.workingLabel) {
239-
return parsed.workingLabel;
240-
}
241-
}
242-
return null;
243-
}
244-
245232
function normalizeMessageImageSrc(path: string) {
246233
if (!path) {
247234
return "";
@@ -743,12 +730,13 @@ const MessageRow = memo(function MessageRow({
743730

744731
const ReasoningRow = memo(function ReasoningRow({
745732
item,
733+
parsed,
746734
isExpanded,
747735
onToggle,
748736
onOpenFileLink,
749737
onOpenFileLinkMenu,
750738
}: ReasoningRowProps) {
751-
const { summaryTitle, bodyText, hasBody } = parseReasoning(item);
739+
const { summaryTitle, bodyText, hasBody } = parsed;
752740
const reasoningTone: StatusTone = hasBody ? "completed" : "processing";
753741
return (
754742
<div className="tool-inline reasoning-inline">
@@ -1004,12 +992,11 @@ const CommandOutput = memo(function CommandOutput({ output }: CommandOutputProps
1004992
}
1005993
return output.split(/\r?\n/);
1006994
}, [output]);
1007-
const maxStoredLines = 200;
1008995
const lineWindow = useMemo(() => {
1009-
if (lines.length <= maxStoredLines) {
996+
if (lines.length <= MAX_COMMAND_OUTPUT_LINES) {
1010997
return { offset: 0, lines };
1011998
}
1012-
const startIndex = lines.length - maxStoredLines;
999+
const startIndex = lines.length - MAX_COMMAND_OUTPUT_LINES;
10131000
return { offset: startIndex, lines: lines.slice(startIndex) };
10141001
}, [lines]);
10151002

@@ -1105,7 +1092,6 @@ export const Messages = memo(function Messages({
11051092
userInputRequests = [],
11061093
onUserInputSubmit,
11071094
}: MessagesProps) {
1108-
const SCROLL_THRESHOLD_PX = 120;
11091095
const bottomRef = useRef<HTMLDivElement | null>(null);
11101096
const containerRef = useRef<HTMLDivElement | null>(null);
11111097
const autoScrollRef = useRef(true);
@@ -1133,7 +1119,7 @@ export const Messages = memo(function Messages({
11331119
const isNearBottom = useCallback(
11341120
(node: HTMLDivElement) =>
11351121
node.scrollHeight - node.scrollTop - node.clientHeight <= SCROLL_THRESHOLD_PX,
1136-
[SCROLL_THRESHOLD_PX],
1122+
[],
11371123
);
11381124

11391125
const updateAutoScroll = () => {
@@ -1183,17 +1169,42 @@ export const Messages = memo(function Messages({
11831169
});
11841170
}, []);
11851171

1186-
const latestReasoningLabel = useMemo(() => reasoningWorkingLabel(items), [items]);
1172+
const reasoningMetaById = useMemo(() => {
1173+
const meta = new Map<string, ReturnType<typeof parseReasoning>>();
1174+
items.forEach((item) => {
1175+
if (item.kind === "reasoning") {
1176+
meta.set(item.id, parseReasoning(item));
1177+
}
1178+
});
1179+
return meta;
1180+
}, [items]);
1181+
1182+
const latestReasoningLabel = useMemo(() => {
1183+
for (let index = items.length - 1; index >= 0; index -= 1) {
1184+
const item = items[index];
1185+
if (item.kind === "message") {
1186+
break;
1187+
}
1188+
if (item.kind !== "reasoning") {
1189+
continue;
1190+
}
1191+
const parsed = reasoningMetaById.get(item.id);
1192+
if (parsed?.workingLabel) {
1193+
return parsed.workingLabel;
1194+
}
1195+
}
1196+
return null;
1197+
}, [items, reasoningMetaById]);
11871198

11881199
const visibleItems = useMemo(
11891200
() =>
11901201
items.filter((item) => {
11911202
if (item.kind !== "reasoning") {
11921203
return true;
11931204
}
1194-
return parseReasoning(item).hasBody;
1205+
return reasoningMetaById.get(item.id)?.hasBody ?? false;
11951206
}),
1196-
[items],
1207+
[items, reasoningMetaById],
11971208
);
11981209

11991210
useEffect(() => {
@@ -1281,10 +1292,12 @@ export const Messages = memo(function Messages({
12811292
}
12821293
if (item.kind === "reasoning") {
12831294
const isExpanded = expandedItems.has(item.id);
1295+
const parsed = reasoningMetaById.get(item.id) ?? parseReasoning(item);
12841296
return (
12851297
<ReasoningRow
12861298
key={item.id}
12871299
item={item}
1300+
parsed={parsed}
12881301
isExpanded={isExpanded}
12891302
onToggle={toggleExpanded}
12901303
onOpenFileLink={openFileLink}

0 commit comments

Comments
 (0)