From 1e87029261f9b81061a2a7420849b9eeaf1a2ebe Mon Sep 17 00:00:00 2001
From: Yukun Shan <92423096+nateEc@users.noreply.github.com>
Date: Sat, 15 Aug 2026 18:43:50 +0800
Subject: [PATCH] fix(web): preserve Claude insight line breaks (#4344)
---
.../components/chat/MessagesTimeline.logic.test.ts | 12 ++++++++++++
.../src/components/chat/MessagesTimeline.logic.ts | 4 ++++
apps/web/src/components/chat/MessagesTimeline.tsx | 2 ++
3 files changed, 18 insertions(+)
diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts
index 6d74204bc1cae3c94aeeae8afbe8b7187191318e..70a330d4630340f2a5ea8df3893b005e68bf454d 100644
--- a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts
+++ b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts
@@ -5,8 +5,20 @@ import {
deriveMessagesTimelineRows,
normalizeCompactToolLabel,
resolveAssistantMessageCopyState,
+ shouldPreserveAssistantLineBreaks,
} from "./MessagesTimeline.logic";
+describe("shouldPreserveAssistantLineBreaks", () => {
+ it("preserves Claude insight formatting without changing regular markdown", () => {
+ expect(
+ shouldPreserveAssistantLineBreaks(
+ "★ Insight ─────────────────\\nFirst observation\\nSecond observation\\n─────────────────",
+ ),
+ ).toBe(true);
+ expect(shouldPreserveAssistantLineBreaks("A normal\\nmarkdown paragraph")).toBe(false);
+ });
+});
+
describe("computeMessageDurationStart", () => {
it("returns message createdAt when there is no preceding user message", () => {
const result = computeMessageDurationStart([
diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts
index 6bc0a2a6203c355149d22ede43223f64a90bb5f9..c89bbd0557d9eaf924df977a95cc4d593e3d3228 100644
--- a/apps/web/src/components/chat/MessagesTimeline.logic.ts
+++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts
@@ -52,6 +52,10 @@ export function resolveTimelineIsAtEnd(
return contentLength - scroll - scrollLength - endInset <= TIMELINE_FOLLOW_REARM_THRESHOLD_PX;
}
+export function shouldPreserveAssistantLineBreaks(text: string): boolean {
+ return /^★ Insight(?:\s|─)/mu.test(text);
+}
+
export function resolveTimelineMinimapHeightStyle(itemCount: number): string {
const naturalHeight = Math.max(1, (itemCount - 1) * TIMELINE_MINIMAP_ITEM_SPACING);
return `min(${naturalHeight}px, ${TIMELINE_MINIMAP_MAX_HEIGHT_CSS})`;
diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx
index f9ad57ff3b83daef28abde4504a77c82d5b75797..f5c529ff315f33f6e54e49430638f3ae4eda30eb 100644
--- a/apps/web/src/components/chat/MessagesTimeline.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.tsx
@@ -83,6 +83,7 @@ import {
resolveTimelineMinimapIndexFromPointer,
resolveTimelineMinimapInteractiveWidth,
resolveTimelineMinimapTopPercent,
+ shouldPreserveAssistantLineBreaks,
type StableMessagesTimelineRowsState,
type MessagesTimelineRow,
TIMELINE_MINIMAP_MIN_ITEMS,
@@ -1113,6 +1114,7 @@ function AssistantTimelineRow({ row }: { row: Extract