authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 10:06:47-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 10:20:34-08:00
log7c7fcf46c2307a0fa0348d8af9012791eb9ebb2d
tree577db5d30b407168bfa601e7189155a068bbfeda
parent0c39733cbf505ce1bf7f15d9b4576ba6ee4fd6b7

Handle system timeline messages with assistant height rules

- Route `system` messages through assistant height estimation - Keep user attachment height logic scoped to user messages - Add regression test for system-message sizing behavior

2 files changed, 23 insertions(+), 6 deletions(-)

apps/web/src/components/timelineHeight.test.ts+9
...@@ -12,6 +12,15 @@ describe("estimateTimelineMessageHeight", () => {...@@ -12,6 +12,15 @@ describe("estimateTimelineMessageHeight", () => {
12 ).toBe(122);12 ).toBe(122);
13 });13 });
1414
15 it("uses assistant sizing rules for system messages", () => {
16 expect(
17 estimateTimelineMessageHeight({
18 role: "system",
19 text: "a".repeat(144),
20 }),
21 ).toBe(122);
22 });
23
15 it("adds one attachment row for one or two user attachments", () => {24 it("adds one attachment row for one or two user attachments", () => {
16 expect(25 expect(
17 estimateTimelineMessageHeight({26 estimateTimelineMessageHeight({
apps/web/src/components/timelineHeight.ts+14-6
...@@ -67,16 +67,24 @@ export function estimateTimelineMessageHeight(...@@ -67,16 +67,24 @@ export function estimateTimelineMessageHeight(
67 message: TimelineMessageHeightInput,67 message: TimelineMessageHeightInput,
68 layout: TimelineHeightEstimateLayout = { timelineWidthPx: null },68 layout: TimelineHeightEstimateLayout = { timelineWidthPx: null },
69): number {69): number {
70 if (message.role !== "user") {70 if (message.role === "assistant") {
71 const charsPerLine = estimateCharsPerLineForAssistant(layout.timelineWidthPx);71 const charsPerLine = estimateCharsPerLineForAssistant(layout.timelineWidthPx);
72 const estimatedLines = estimateWrappedLineCount(message.text, charsPerLine);72 const estimatedLines = estimateWrappedLineCount(message.text, charsPerLine);
73 return ASSISTANT_BASE_HEIGHT_PX + estimatedLines * LINE_HEIGHT_PX;73 return ASSISTANT_BASE_HEIGHT_PX + estimatedLines * LINE_HEIGHT_PX;
74 }74 }
7575
76 const charsPerLine = estimateCharsPerLineForUser(layout.timelineWidthPx);76 if (message.role === "user") {
77 const charsPerLine = estimateCharsPerLineForUser(layout.timelineWidthPx);
78 const estimatedLines = estimateWrappedLineCount(message.text, charsPerLine);
79 const attachmentCount = message.attachments?.length ?? 0;
80 const attachmentRows = Math.ceil(attachmentCount / ATTACHMENTS_PER_ROW);
81 const attachmentHeight = attachmentRows * USER_ATTACHMENT_ROW_HEIGHT_PX;
82 return USER_BASE_HEIGHT_PX + estimatedLines * LINE_HEIGHT_PX + attachmentHeight;
83 }
84
85 // `system` messages are not rendered in the chat timeline, but keep a stable
86 // explicit branch in case they are present in timeline data.
87 const charsPerLine = estimateCharsPerLineForAssistant(layout.timelineWidthPx);
77 const estimatedLines = estimateWrappedLineCount(message.text, charsPerLine);88 const estimatedLines = estimateWrappedLineCount(message.text, charsPerLine);
78 const attachmentCount = message.attachments?.length ?? 0;89 return ASSISTANT_BASE_HEIGHT_PX + estimatedLines * LINE_HEIGHT_PX;
79 const attachmentRows = Math.ceil(attachmentCount / ATTACHMENTS_PER_ROW);
80 const attachmentHeight = attachmentRows * USER_ATTACHMENT_ROW_HEIGHT_PX;
81 return USER_BASE_HEIGHT_PX + estimatedLines * LINE_HEIGHT_PX + attachmentHeight;
82}90}