diff --git a/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M53FDJQ4C0BGDGXNAY5GK09T-a-file-block-s-cache-key-inc.md b/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M53FDJQ4C0BGDGXNAY5GK09T-a-file-block-s-cache-key-inc.md index 3ba42a06..d5b2fbce 100644 --- a/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M53FDJQ4C0BGDGXNAY5GK09T-a-file-block-s-cache-key-inc.md +++ b/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M53FDJQ4C0BGDGXNAY5GK09T-a-file-block-s-cache-key-inc.md @@ -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 --- diff --git a/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M5JTBNVCVRCB6KWWK6JSWF67-bubble-rows-measure-at-conte.md b/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M5JTBNVCVRCB6KWWK6JSWF67-bubble-rows-measure-at-conte.md new file mode 100644 index 00000000..dda78807 --- /dev/null +++ b/.lane/memory/packages/ui/src/components/timeline/layout.ts/01M5JTBNVCVRCB6KWWK6JSWF67-bubble-rows-measure-at-conte.md @@ -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 diff --git a/packages/ui/src/components/timeline/layout.ts b/packages/ui/src/components/timeline/layout.ts index 8badb432..5cfaf545 100644 --- a/packages/ui/src/components/timeline/layout.ts +++ b/packages/ui/src/components/timeline/layout.ts @@ -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; @@ -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; @@ -901,6 +903,7 @@ export { PR_COMMENT_BUBBLE_PAD_BOTTOM, PR_COMMENT_GAP, REACTION_GAP, + STAMP, summaryId, TAIL, textBlocks, diff --git a/packages/ui/src/components/timeline/timeline.tsx b/packages/ui/src/components/timeline/timeline.tsx index bd8a21cd..dea2c610 100644 --- a/packages/ui/src/components/timeline/timeline.tsx +++ b/packages/ui/src/components/timeline/timeline.tsx @@ -74,6 +74,7 @@ import { PR_COMMENT_BUBBLE_PAD_BOTTOM, PR_COMMENT_GAP, REACTION_GAP, + STAMP, summaryId, TAIL, TOOL_GROUP_PAD_Y, @@ -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, @@ -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; @@ -1029,7 +1036,7 @@ function GroupRows({ props, state, toolbar }: GroupRender & { toolbar: RowToolba {toolbar(uid, wide)}