diff --git a/apps/web/src/components/chat/TimelineAssistantRows.tsx b/apps/web/src/components/chat/TimelineAssistantRows.tsx
index 49e4e0c19adb2..8a011a7a579ae 100644
--- a/apps/web/src/components/chat/TimelineAssistantRows.tsx
+++ b/apps/web/src/components/chat/TimelineAssistantRows.tsx
@@ -213,10 +213,10 @@ function AssistantMessageMeta({
{!message.streaming && (
}>
- {formatDayAwareTimestamp(message.updatedAt, ctx.timestampFormat)}
+ {formatDayAwareTimestamp(message.editedAt ?? message.updatedAt, ctx.timestampFormat)}
- {formatChatTimestampTooltip(message.updatedAt, ctx.timestampFormat)}
+ {formatChatTimestampTooltip(message.editedAt ?? message.updatedAt, ctx.timestampFormat)}
)}
diff --git a/apps/web/src/fork/displayMode/displayModeTimeline.browser.test.tsx b/apps/web/src/fork/displayMode/displayModeTimeline.browser.test.tsx
index 72ca35f69067b..dcd7b1ef87270 100644
--- a/apps/web/src/fork/displayMode/displayModeTimeline.browser.test.tsx
+++ b/apps/web/src/fork/displayMode/displayModeTimeline.browser.test.tsx
@@ -13,6 +13,7 @@ import { MESSAGES_ONLY_DISPLAY_MODE, NORMAL_DISPLAY_MODE } from "@t3tools/shared
import * as DateTime from "effect/DateTime";
import { createRef, type ComponentProps } from "react";
import { describe, expect, it, vi } from "vite-plus/test";
+import { page, userEvent } from "vite-plus/test/browser";
import "~/index.css";
@@ -21,6 +22,7 @@ import { MessagesTimeline } from "../../components/chat/MessagesTimeline";
import { deriveMessagesTimelineRows } from "../../components/chat/MessagesTimeline.logic";
import { __setClientSettingsForTests } from "../../hooks/useSettings";
import { deriveTimelineEntriesFromVisibleTurnItems } from "../../session-logic";
+import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat";
import { applyDisplayMode } from "./displayModeTimeline";
const threadId = ThreadId.make("display-mode-thread");
@@ -290,8 +292,10 @@ describe("Messages only timeline", () => {
input: { text: "The build is red." },
output: { ok: true, message: "Shown to the user." },
};
+ const editedAt = new Date(Date.UTC(2026, 10, 3, 0, 47, 0)).toISOString();
const replace = (id: string, status: "completed" | "running"): OrchestrationV2TurnItem => ({
...base(id, 3),
+ completedAt: DateTime.makeUnsafe(editedAt),
status,
type: "dynamic_tool",
toolName: "mcp__t3-code__replace_send_to_user",
@@ -304,11 +308,19 @@ describe("Messages only timeline", () => {
});
const sent = entriesOf([userMessage("rewrite-ask", 0, "Is the build green?"), send]);
- it("redraws the send's row with the new text when the rewrite arrives", async () => {
+ it("redraws the send's row with the new text and time when the rewrite arrives", async () => {
await using screen = await renderTimeline([
...applyDisplayMode(MESSAGES_ONLY_DISPLAY_MODE, sent),
]);
+ const shownTime = (at: string) => formatDayAwareTimestamp(at, "locale");
+ const rowOf = (text: string) =>
+ page.elementLocator(
+ screen.getByText(text).element().closest('[data-timeline-row-kind="message"]')!,
+ );
await expect.element(screen.getByText("The build is red.")).toBeVisible();
+ await expect
+ .element(rowOf("The build is red.").getByText(shownTime(time(1)), { exact: true }))
+ .toBeVisible();
const rewritten = [...sent, ...entriesOf([replace("rewrite-call", "completed")])];
await screen.rerender(
{
);
await expect.element(screen.getByText("The build is green again.")).toBeVisible();
expect(screen.container.textContent ?? "").not.toContain("The build is red.");
+ const reply = rowOf("The build is green again.");
+ const shown = reply.getByText(shownTime(editedAt), { exact: true });
+ await expect.element(shown).toBeVisible();
+ expect(reply.element().textContent).not.toContain(shownTime(time(1)));
+ await userEvent.hover(shown);
+ await expect
+ .element(page.getByText(formatChatTimestampTooltip(editedAt, "locale"), { exact: true }))
+ .toBeVisible();
});
it("draws no row for a replace call in any mode, running or done", () => {
diff --git a/apps/web/src/fork/displayMode/displayModeTimeline.ts b/apps/web/src/fork/displayMode/displayModeTimeline.ts
index 1b11e91a84992..781bd74e5af65 100644
--- a/apps/web/src/fork/displayMode/displayModeTimeline.ts
+++ b/apps/web/src/fork/displayMode/displayModeTimeline.ts
@@ -62,6 +62,7 @@ function replyEntry(
streaming: false,
createdBy: "agent",
createdAt: entry.createdAt,
+ // The turn fold's "Worked for" ends at updatedAt; the meta line shows editedAt instead.
updatedAt: entry.createdAt,
...(editedAt === undefined ? {} : { editedAt }),
},