Skip to content
Open
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
31 changes: 31 additions & 0 deletions apps/web/src/components/chat/MessagesTimeline.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,23 @@ function buildUserTimelineEntry(text: string) {
};
}

function buildAssistantTimelineEntry(text: string) {
return {
id: "assistant-entry-1",
kind: "message" as const,
createdAt: MESSAGE_CREATED_AT,
message: {
id: MessageId.make("assistant-message-1"),
role: "assistant" as const,
text,
turnId: null,
createdAt: MESSAGE_CREATED_AT,
updatedAt: MESSAGE_CREATED_AT,
streaming: false,
},
};
}

describe("MessagesTimeline", () => {
it("uses the larger leading inset only when the top fade is enabled", () => {
const timelineEntries = [buildUserTimelineEntry("Hello")];
Expand Down Expand Up @@ -530,6 +547,20 @@ describe("MessagesTimeline", () => {
expect(markup).toContain('data-user-message-footer="true"');
});

it("keeps completed assistant message actions visible without hover", async () => {
const { MessagesTimeline } = await import("./MessagesTimeline");
const markup = renderToStaticMarkup(
<MessagesTimeline
{...buildProps()}
timelineEntries={[buildAssistantTimelineEntry("A completed response ready to copy.")]}
/>,
);

expect(markup).toContain('aria-label="Copy link"');
expect(markup).toContain('data-assistant-message-footer="persistent"');
expect(markup).not.toMatch(/data-assistant-message-footer="persistent"[^>]*opacity-0/);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard can never fail: React emits className before the data attribute, so the rendered tag is <div class="mt-1.5 ..." data-assistant-message-footer="persistent"> and opacity-0 can never appear after the marker within the same tag. If the hover-gating classes were reintroduced, this assertion would still pass. Matching the whole tag keeps the guard meaningful.

Suggested change
expect(markup).not.toMatch(/data-assistant-message-footer="persistent"[^>]*opacity-0/);
expect(markup).not.toMatch(/<div[^>]*opacity-0[^>]*data-assistant-message-footer="persistent"/);

Posted via Macroscope — UI Consistency

});

it("renders context compaction entries in the normal work log", () => {
const markup = renderToStaticMarkup(
<MessagesTimeline
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,10 @@ function AssistantTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "mess
onOpenTurnDiff={ctx.onOpenTurnDiff}
/>
{row.showAssistantMeta ? (
<div className="mt-1.5 flex items-center gap-2 text-xs tabular-nums opacity-0 transition-opacity duration-200 focus-within:opacity-100 group-hover/assistant:opacity-100">
<div
className="mt-1.5 flex items-center gap-2 text-xs tabular-nums"
data-assistant-message-footer="persistent"
>
Comment on lines +1127 to +1130

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropping group-hover/assistant:opacity-100 here leaves the group/assistant marker on the row wrapper (line 935) with no consumer anywhere in the repo, so the row still advertises a hover group that nothing reacts to. Consider removing that marker in the same change so hover ownership stays truthful.

-        row.kind === "message" && row.message.role === "assistant" ? "group/assistant" : null,

Posted via Macroscope — UI Consistency

<AssistantCopyButton row={row} />
{!row.message.streaming && (
<Tooltip>
Expand Down
Loading