Skip to content

Commit 7e5256e

Browse files
authored
fix(ui): show each grouped message's time beside its bubble (#667)
* fix(ui): show each grouped message's time beside its bubble Why: packages/ui/src/components/timeline/layout.ts#function itemHeight | bubble rows measure at content - STAMP; GroupRows must reserve the same STAMP or heights drift from the render * lane: sync fix-msg-times memory
1 parent 7119e57 commit 7e5256e

4 files changed

Lines changed: 41 additions & 8 deletions

File tree

‎.lane/memory/packages/ui/src/components/timeline/layout.ts/01M53FDJQ4C0BGDGXNAY5GK09T-a-file-block-s-cache-key-inc.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ anchor: '@file'
44
created: 2026-10-16T23:05:41Z
55
norm: '1'
66
sig: ce515648b5bedef2
7-
body_hash: 3696028d4b1ada45
8-
raw_hash: 67b90ab8452ae3e8
9-
vouched: 2026-10-22T21:00:46Z
7+
body_hash: 433c39db9d6384fd
8+
raw_hash: ea74d2e9c0710659
9+
vouched: 2026-10-22T22:05:34Z
1010
lines: 1-912
1111
---
1212

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
id: 01M5JTBNVCVRCB6KWWK6JSWF67
3+
anchor: function itemHeight
4+
created: 2026-10-22T22:05:29Z
5+
norm: '1'
6+
sig: 3c40d8dd5f73026a
7+
body_hash: 446268a0d25debb4
8+
raw_hash: e696d2f48fa62bb6
9+
lines: 742-831
10+
---
11+
12+
bubble rows measure at content - STAMP; GroupRows must reserve the same STAMP or heights drift from the render

‎packages/ui/src/components/timeline/layout.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const BUBBLE_GAP = 2;
5151
const BUBBLE_BORDER = 1;
5252
const BUBBLE_PAD_X = 12;
5353
const BUBBLE_PAD_Y = 5;
54+
// Bubbles in a message group leave room beside them for an earlier message's time.
55+
const STAMP = 52;
5456
const FILES_HEIGHT = 20;
5557
const TOOL_GROUP_ROW_HEIGHT = TOOL_ROW_HEIGHT;
5658
const TOOL_GROUP_PAD_Y = 6;
@@ -810,7 +812,7 @@ function itemHeight(
810812
} else {
811813
for (let row of group.rows) {
812814
let isBubble = group.role === "user" || group.role === "assistant";
813-
let w = isBubble ? content - BUBBLE_PAD_X * 2 : content;
815+
let w = isBubble ? content - STAMP - BUBBLE_PAD_X * 2 : content;
814816
let b = blocks.get(row.uid);
815817
if (b) h += measureRow(b, w).height;
816818
if (isBubble) h += BUBBLE_PAD_Y * 2;
@@ -901,6 +903,7 @@ export {
901903
PR_COMMENT_BUBBLE_PAD_BOTTOM,
902904
PR_COMMENT_GAP,
903905
REACTION_GAP,
906+
STAMP,
904907
summaryId,
905908
TAIL,
906909
textBlocks,

‎packages/ui/src/components/timeline/timeline.tsx‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
PR_COMMENT_BUBBLE_PAD_BOTTOM,
7575
PR_COMMENT_GAP,
7676
REACTION_GAP,
77+
STAMP,
7778
summaryId,
7879
TAIL,
7980
TOOL_GROUP_PAD_Y,
@@ -977,11 +978,17 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
977978

978979
let isUser = group.role === "user";
979980
let isBubble = isUser || group.role === "assistant";
980-
let inner = isBubble ? content - BUBBLE_PAD_X * 2 : content;
981-
let entries = group.rows.map(row => {
981+
let inner = isBubble ? content - STAMP - BUBBLE_PAD_X * 2 : content;
982+
let minutes = group.rows.map(row => timeFmt.format(row.ts * 1000));
983+
let entries = group.rows.map((row, i) => {
982984
let b = blocks.get(row.uid) || [];
985+
let time = minutes[i]!;
983986
return {
984987
b,
988+
// The byline shows the last message's time, so earlier ones show theirs where it changes.
989+
stamp: isBubble && time !== minutes[i - 1] && time !== minutes.at(-1)
990+
? { time, title: exactFmt.format(row.ts * 1000) }
991+
: undefined,
985992
agent: row.agent,
986993
// An unbreakable token wider than the lane must not widen the bubble past it.
987994
fit: isBubble ? Math.min(measureRow(b, inner).fit, inner) : inner,
@@ -1000,7 +1007,7 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
10001007
className={cn("flex flex-col", mine && "items-end")}
10011008
style={{ gap: isUser ? BUBBLE_GAP : CONTENT_GAP }}
10021009
>
1003-
{entries.map(({ b, agent, fit, reactions, uid }, i) => {
1010+
{entries.map(({ b, stamp, agent, fit, reactions, uid }, i) => {
10041011
let prev = entries[i - 1];
10051012
let next = entries[i + 1];
10061013
let last = i === entries.length - 1;
@@ -1029,7 +1036,7 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
10291036
{toolbar(uid, wide)}
10301037
<div
10311038
className={cn(
1032-
"rounded-2xl squircle",
1039+
"relative rounded-2xl squircle",
10331040
prev && !prevHasReact && (mine ? "rounded-tr-none" : "rounded-tl-none"),
10341041
prev && !self && !above && fit <= prev.fit && !prevHasReact
10351042
&& (mine ? "rounded-tl-none" : "rounded-tr-none"),
@@ -1071,6 +1078,17 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba
10711078
>
10721079
<BlockList blocks={b} width={fit} />
10731080
</div>
1081+
{stamp && (
1082+
<span
1083+
className={cn(
1084+
"absolute bottom-1 whitespace-nowrap text-[0.625rem] text-muted-foreground",
1085+
mine ? "right-full mr-1.5" : "left-full ml-1.5",
1086+
)}
1087+
title={stamp.title}
1088+
>
1089+
{stamp.time}
1090+
</span>
1091+
)}
10741092
</div>
10751093
{reactions && reactions.length > 0 && (
10761094
<Reactions

0 commit comments

Comments
 (0)