Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: '@file'
created: 2026-10-16T23:05:41Z
norm: '1'
sig: ce515648b5bedef2
body_hash: 3696028d4b1ada45
raw_hash: 67b90ab8452ae3e8
vouched: 2026-10-22T21:00:46Z
body_hash: 433c39db9d6384fd
raw_hash: ea74d2e9c0710659
vouched: 2026-10-22T22:05:34Z
lines: 1-912
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5JTBNVCVRCB6KWWK6JSWF67
anchor: function itemHeight
created: 2026-10-22T22:05:29Z
norm: '1'
sig: 3c40d8dd5f73026a
body_hash: 446268a0d25debb4
raw_hash: e696d2f48fa62bb6
lines: 742-831
---

bubble rows measure at content - STAMP; GroupRows must reserve the same STAMP or heights drift from the render
5 changes: 4 additions & 1 deletion packages/ui/src/components/timeline/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const BUBBLE_GAP = 2;
const BUBBLE_BORDER = 1;
const BUBBLE_PAD_X = 12;
const BUBBLE_PAD_Y = 5;
// Bubbles in a message group leave room beside them for an earlier message's time.
const STAMP = 52;
const FILES_HEIGHT = 20;
const TOOL_GROUP_ROW_HEIGHT = TOOL_ROW_HEIGHT;
const TOOL_GROUP_PAD_Y = 6;
Expand Down Expand Up @@ -810,7 +812,7 @@ function itemHeight(
} else {
for (let row of group.rows) {
let isBubble = group.role === "user" || group.role === "assistant";
let w = isBubble ? content - BUBBLE_PAD_X * 2 : content;
let w = isBubble ? content - STAMP - BUBBLE_PAD_X * 2 : content;
let b = blocks.get(row.uid);
if (b) h += measureRow(b, w).height;
if (isBubble) h += BUBBLE_PAD_Y * 2;
Expand Down Expand Up @@ -901,6 +903,7 @@ export {
PR_COMMENT_BUBBLE_PAD_BOTTOM,
PR_COMMENT_GAP,
REACTION_GAP,
STAMP,
summaryId,
TAIL,
textBlocks,
Expand Down
26 changes: 22 additions & 4 deletions packages/ui/src/components/timeline/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
PR_COMMENT_BUBBLE_PAD_BOTTOM,
PR_COMMENT_GAP,
REACTION_GAP,
STAMP,
summaryId,
TAIL,
TOOL_GROUP_PAD_Y,
Expand Down Expand Up @@ -977,11 +978,17 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba

let isUser = group.role === "user";
let isBubble = isUser || group.role === "assistant";
let inner = isBubble ? content - BUBBLE_PAD_X * 2 : content;
let entries = group.rows.map(row => {
let inner = isBubble ? content - STAMP - BUBBLE_PAD_X * 2 : content;
let minutes = group.rows.map(row => timeFmt.format(row.ts * 1000));
let entries = group.rows.map((row, i) => {
let b = blocks.get(row.uid) || [];
let time = minutes[i]!;
return {
b,
// The byline shows the last message's time, so earlier ones show theirs where it changes.
stamp: isBubble && time !== minutes[i - 1] && time !== minutes.at(-1)
? { time, title: exactFmt.format(row.ts * 1000) }
: undefined,
agent: row.agent,
// An unbreakable token wider than the lane must not widen the bubble past it.
fit: isBubble ? Math.min(measureRow(b, inner).fit, inner) : inner,
Expand All @@ -1000,7 +1007,7 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
className={cn("flex flex-col", mine && "items-end")}
style={{ gap: isUser ? BUBBLE_GAP : CONTENT_GAP }}
>
{entries.map(({ b, agent, fit, reactions, uid }, i) => {
{entries.map(({ b, stamp, agent, fit, reactions, uid }, i) => {
let prev = entries[i - 1];
let next = entries[i + 1];
let last = i === entries.length - 1;
Expand Down Expand Up @@ -1029,7 +1036,7 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
{toolbar(uid, wide)}
<div
className={cn(
"rounded-2xl squircle",
"relative rounded-2xl squircle",
prev && !prevHasReact && (mine ? "rounded-tr-none" : "rounded-tl-none"),
prev && !self && !above && fit <= prev.fit && !prevHasReact
&& (mine ? "rounded-tl-none" : "rounded-tr-none"),
Expand Down Expand Up @@ -1071,6 +1078,17 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
>
<BlockList blocks={b} width={fit} />
</div>
{stamp && (
<span
className={cn(
"absolute bottom-1 whitespace-nowrap text-[0.625rem] text-muted-foreground",
mine ? "right-full mr-1.5" : "left-full ml-1.5",
)}
title={stamp.title}
>
{stamp.time}
</span>
)}
</div>
{reactions && reactions.length > 0 && (
<Reactions
Expand Down