From 46c69bc35ccbc89f48fb2a64784d12025d7a9102 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 8 Oct 2026 08:24:05 +1000 Subject: [PATCH] fix(session-ui): show latest thinking summary heading while streaming (#53815) --- .../src/timeline/projection.test.ts | 41 +++++++++++++++- .../session-ui/src/timeline/projection.ts | 49 ++++++++----------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/packages/session-ui/src/timeline/projection.test.ts b/packages/session-ui/src/timeline/projection.test.ts index 9d7e8248c4e..d33515443e8 100644 --- a/packages/session-ui/src/timeline/projection.test.ts +++ b/packages/session-ui/src/timeline/projection.test.ts @@ -6,7 +6,14 @@ import type { SessionMessageInfo, } from "@opencode/client/promise" import { createStore } from "solid-js/store" -import { createTimelineProjection, reuseTimelineRows, Timeline, TimelineRow, type PartGroup } from "./projection" +import { + createTimelineProjection, + reasoningHeading, + reuseTimelineRows, + Timeline, + TimelineRow, + type PartGroup, +} from "./projection" const context = (key: string, partIDs: string[], identity: { userMessageID?: string; messageID?: string } = {}) => new TimelineRow.AssistantPart({ @@ -325,3 +332,35 @@ describe("createTimelineProjection", () => { ]) }) }) + +describe("reasoningHeading", () => { + test("returns the most recent thinking summary heading as sections stream in", () => { + const sections = [ + "**Designing Interactive Experience**\nI'm focusing on crafting a fun, tactile, and interactive visual toy.", + "**Analyzing Reading Recommendations**\nI'm currently dissecting whether Luke Parker should read the *Throne of Glass* series.", + "**Constructing Reader Tools**\nI'm now building out interactive tools.", + ] + + expect(reasoningHeading(sections[0])).toBe("Designing Interactive Experience") + expect(reasoningHeading(sections.slice(0, 2).join("\n\n"))).toBe("Analyzing Reading Recommendations") + expect(reasoningHeading(sections.join("\n\n"))).toBe("Constructing Reader Tools") + }) + + test("keeps the previous heading while the next bold heading is still streaming", () => { + const text = [ + "**Designing Interactive Experience**\nI'm focusing on crafting a fun, tactile, and interactive visual toy.", + "**Analyzing Reading", + ].join("\n\n") + + expect(reasoningHeading(text)).toBe("Designing Interactive Experience") + }) + + test("returns the latest heading across markdown heading styles and ignores inline bold spans", () => { + expect(reasoningHeading("## First step\n\nDetails.\n\n## Second step\n\nMore details.")).toBe("Second step") + expect(reasoningHeading("
Body
\n