Skip to content

Commit 308e1bb

Browse files
committed
test(web): stabilize timeline height parity coverage
1 parent bed6475 commit 308e1bb

8 files changed

Lines changed: 2234 additions & 177 deletions

apps/web/src/components/chat/MessagesTimeline.browser.tsx

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

apps/web/src/components/chat/MessagesTimeline.logic.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import { type MessageId } from "@t3tools/contracts";
2+
3+
import { type TimelineEntry } from "../../session-logic";
4+
import { type TurnDiffSummary } from "../../types";
5+
16
export interface TimelineDurationMessage {
27
id: string;
38
role: "user" | "assistant" | "system";
@@ -27,3 +32,108 @@ export function computeMessageDurationStart(
2732
export function normalizeCompactToolLabel(value: string): string {
2833
return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim();
2934
}
35+
36+
type TimelineMessage = Extract<TimelineEntry, { kind: "message" }>["message"];
37+
type TimelineProposedPlan = Extract<TimelineEntry, { kind: "proposed-plan" }>["proposedPlan"];
38+
type TimelineWorkEntry = Extract<TimelineEntry, { kind: "work" }>["entry"];
39+
40+
export type MessagesTimelineRow =
41+
| {
42+
kind: "work";
43+
id: string;
44+
createdAt: string;
45+
groupedEntries: TimelineWorkEntry[];
46+
}
47+
| {
48+
kind: "message";
49+
id: string;
50+
createdAt: string;
51+
message: TimelineMessage;
52+
assistantDiffSummary: TurnDiffSummary | null;
53+
durationStart: string;
54+
showCompletionDivider: boolean;
55+
}
56+
| {
57+
kind: "proposed-plan";
58+
id: string;
59+
createdAt: string;
60+
proposedPlan: TimelineProposedPlan;
61+
}
62+
| { kind: "working"; id: string; createdAt: string | null };
63+
64+
export function resolveMessagesTimelineRows(options: {
65+
timelineEntries: TimelineEntry[];
66+
completionDividerBeforeEntryId: string | null;
67+
turnDiffSummaryByAssistantMessageId: Map<MessageId, TurnDiffSummary>;
68+
isWorking: boolean;
69+
activeTurnStartedAt: string | null;
70+
}): MessagesTimelineRow[] {
71+
const nextRows: MessagesTimelineRow[] = [];
72+
const durationStartByMessageId = computeMessageDurationStart(
73+
options.timelineEntries.flatMap((entry) => (entry.kind === "message" ? [entry.message] : [])),
74+
);
75+
76+
for (let index = 0; index < options.timelineEntries.length; index += 1) {
77+
const timelineEntry = options.timelineEntries[index];
78+
if (!timelineEntry) {
79+
continue;
80+
}
81+
82+
if (timelineEntry.kind === "work") {
83+
const groupedEntries = [timelineEntry.entry];
84+
let cursor = index + 1;
85+
while (cursor < options.timelineEntries.length) {
86+
const nextEntry = options.timelineEntries[cursor];
87+
if (!nextEntry || nextEntry.kind !== "work") {
88+
break;
89+
}
90+
groupedEntries.push(nextEntry.entry);
91+
cursor += 1;
92+
}
93+
nextRows.push({
94+
kind: "work",
95+
id: timelineEntry.id,
96+
createdAt: timelineEntry.createdAt,
97+
groupedEntries,
98+
});
99+
index = cursor - 1;
100+
continue;
101+
}
102+
103+
if (timelineEntry.kind === "proposed-plan") {
104+
nextRows.push({
105+
kind: "proposed-plan",
106+
id: timelineEntry.id,
107+
createdAt: timelineEntry.createdAt,
108+
proposedPlan: timelineEntry.proposedPlan,
109+
});
110+
continue;
111+
}
112+
113+
nextRows.push({
114+
kind: "message",
115+
id: timelineEntry.id,
116+
createdAt: timelineEntry.createdAt,
117+
message: timelineEntry.message,
118+
assistantDiffSummary:
119+
timelineEntry.message.role === "assistant"
120+
? (options.turnDiffSummaryByAssistantMessageId.get(timelineEntry.message.id) ?? null)
121+
: null,
122+
durationStart:
123+
durationStartByMessageId.get(timelineEntry.message.id) ?? timelineEntry.message.createdAt,
124+
showCompletionDivider:
125+
timelineEntry.message.role === "assistant" &&
126+
options.completionDividerBeforeEntryId === timelineEntry.id,
127+
});
128+
}
129+
130+
if (options.isWorking) {
131+
nextRows.push({
132+
kind: "working",
133+
id: "working-indicator-row",
134+
createdAt: options.activeTurnStartedAt,
135+
});
136+
}
137+
138+
return nextRows;
139+
}

0 commit comments

Comments
 (0)