Skip to content

Commit f038288

Browse files
committed
refactor(ui): move chat transcript components
Move reusable Chats transcript rendering pieces into ui/src/features/transcript with Transcript* names. Keep TaskDetail's audit-trail conversation layout separate instead of forcing it into chat bubbles.
1 parent b7ca3fd commit f038288

9 files changed

Lines changed: 60 additions & 60 deletions

ui/src/features/chats/ChatView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import type { RuntimeConsoleViewModel } from "../../app/useRuntimeConsole";
44
import { describeGatewayError, formatErrorCode } from "../../lib/error-diagnostics";
55
import type { AgentAdapterRecord, AgentChatActivityRecord, AgentChatSessionRecord, AgentChatUsageRecord } from "../../types/runtime";
66
import { AgentAdapterPicker, CodeBlock, Icon, Icons, InlineError, ModelPicker, ProviderPicker } from "../shared/ui";
7+
import { TranscriptMessageRow } from "../transcript/TranscriptMessageRow";
78
import { AgentApprovalAutoModeBanner, AgentApprovalsBanner } from "./AgentApprovalBanner";
89
import { AgentApprovalModal } from "./AgentApprovalModal";
9-
import { MessageRow } from "./MessageRow";
1010

1111
type Props = {
1212
state: RuntimeConsoleViewModel["state"];
@@ -622,7 +622,7 @@ export function ChatView({ state, actions, onNavigate }: Props) {
622622
? formatAgentRuntimeMeta(m.run_id, m.duration_ms, m.trace_id, m.native_session_id)
623623
: "";
624624
return (
625-
<MessageRow
625+
<TranscriptMessageRow
626626
key={m.id}
627627
id={m.id}
628628
role={role}

ui/src/features/chats/AgentActivityTimeline.test.tsx renamed to ui/src/features/transcript/TranscriptActivityTimeline.test.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
22
import { describe, expect, it } from "vitest";
33

44
import type { AgentChatActivityRecord } from "../../types/runtime";
5-
import { ActivityTimeline, DiffStatList, formatDiffStatSummary } from "./AgentActivityTimeline";
5+
import { TranscriptActivityTimeline, DiffStatList, formatDiffStatSummary } from "./TranscriptActivityTimeline";
66

77
describe("formatDiffStatSummary", () => {
88
it("returns the 'N files changed' line when present", () => {
@@ -43,17 +43,17 @@ describe("DiffStatList", () => {
4343
});
4444
});
4545

46-
describe("ActivityTimeline", () => {
46+
describe("TranscriptActivityTimeline", () => {
4747
it("renders nothing when activities is empty", () => {
48-
const { container } = render(<ActivityTimeline activities={[]} />);
48+
const { container } = render(<TranscriptActivityTimeline activities={[]} />);
4949
expect(container).toBeEmptyDOMElement();
5050
});
5151

5252
it("renders the summary with running status when no terminal activity is present", () => {
5353
const activities: AgentChatActivityRecord[] = [
5454
{ type: "tool_call", title: "read_file", status: "running", kind: "fs" },
5555
];
56-
render(<ActivityTimeline activities={activities} />);
56+
render(<TranscriptActivityTimeline activities={activities} />);
5757
expect(screen.getByText(/running/)).toBeInTheDocument();
5858
});
5959

@@ -62,7 +62,7 @@ describe("ActivityTimeline", () => {
6262
{ type: "tool_call", title: "read_file", status: "completed" },
6363
{ type: "completed", title: "Final answer", status: "completed" },
6464
];
65-
render(<ActivityTimeline activities={activities} />);
65+
render(<TranscriptActivityTimeline activities={activities} />);
6666
expect(screen.getByText(/completed/)).toBeInTheDocument();
6767
});
6868

@@ -72,7 +72,7 @@ describe("ActivityTimeline", () => {
7272
{ type: "plan", title: "Step 2", status: "in_progress" },
7373
{ type: "plan", title: "Step 3", status: "pending" },
7474
];
75-
render(<ActivityTimeline activities={activities} />);
75+
render(<TranscriptActivityTimeline activities={activities} />);
7676
expect(screen.getByText("Step 1")).toBeInTheDocument();
7777
expect(screen.getByText("Step 2")).toBeInTheDocument();
7878
expect(screen.getByText("Step 3")).toBeInTheDocument();
@@ -82,7 +82,7 @@ describe("ActivityTimeline", () => {
8282
const activities: AgentChatActivityRecord[] = [
8383
{ type: "tool_call", title: "read_file", status: "completed", kind: "fs", detail: "src/index.ts" },
8484
];
85-
render(<ActivityTimeline activities={activities} />);
85+
render(<TranscriptActivityTimeline activities={activities} />);
8686
expect(screen.getByText("read_file")).toBeInTheDocument();
8787
expect(screen.getByText("fs")).toBeInTheDocument();
8888
expect(screen.getByText("src/index.ts")).toBeInTheDocument();
@@ -92,7 +92,7 @@ describe("ActivityTimeline", () => {
9292
const activities: AgentChatActivityRecord[] = [
9393
{ type: "tool_call", title: "read_file", status: "completed" },
9494
];
95-
render(<ActivityTimeline activities={activities} diffStat="src/foo.ts | 3 +-" />);
95+
render(<TranscriptActivityTimeline activities={activities} diffStat="src/foo.ts | 3 +-" />);
9696
expect(screen.getByText(/files changed/)).toBeInTheDocument();
9797
});
9898

@@ -102,7 +102,7 @@ describe("ActivityTimeline", () => {
102102
{ type: "tool_call", title: "read_file", status: "completed" },
103103
{ type: "completed", title: "Final answer" },
104104
];
105-
render(<ActivityTimeline activities={activities} />);
105+
render(<TranscriptActivityTimeline activities={activities} />);
106106
expect(screen.queryByText("Started")).toBeNull();
107107
});
108108
});

ui/src/features/chats/AgentActivityTimeline.tsx renamed to ui/src/features/transcript/TranscriptActivityTimeline.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function formatDiffStatSummary(diffStat: string): string {
4545
return lines.find(line => /\bfiles? changed\b/.test(line)) || lines[0] || "";
4646
}
4747

48-
export function ActivityTimeline({ activities, diffStat }: { activities: AgentChatActivityRecord[]; diffStat?: string }) {
48+
export function TranscriptActivityTimeline({ activities, diffStat }: { activities: AgentChatActivityRecord[]; diffStat?: string }) {
4949
const visible = compactAgentActivities(activities);
5050
if (visible.length === 0) return null;
5151
const terminal = terminalAgentActivity(activities);

ui/src/features/chats/AgentDiffReview.test.tsx renamed to ui/src/features/transcript/TranscriptDiffReview.test.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
33
import { describe, expect, it, vi } from "vitest";
44

55
import type { AgentChatChangedFileDiffRecord, AgentChatChangedFileRecord } from "../../types/runtime";
6-
import { AgentDiffReview } from "./AgentDiffReview";
6+
import { TranscriptDiffReview } from "./TranscriptDiffReview";
77

88
function file(overrides: Partial<AgentChatChangedFileRecord> = {}): AgentChatChangedFileRecord {
99
return {
@@ -26,10 +26,10 @@ function fileDiff(overrides: Partial<AgentChatChangedFileDiffRecord> = {}): Agen
2626
};
2727
}
2828

29-
describe("AgentDiffReview", () => {
29+
describe("TranscriptDiffReview", () => {
3030
it("renders the static diff stat fallback when review APIs aren't wired", () => {
3131
render(
32-
<AgentDiffReview
32+
<TranscriptDiffReview
3333
sessionID=""
3434
messageID="m1"
3535
diffStat="src/foo.ts | 3 +-"
@@ -43,7 +43,7 @@ describe("AgentDiffReview", () => {
4343
const onListFiles = vi.fn(async () => [file({ path: "src/a.ts" }), file({ path: "src/b.ts" })]);
4444
const user = userEvent.setup();
4545
render(
46-
<AgentDiffReview
46+
<TranscriptDiffReview
4747
sessionID="s1"
4848
messageID="m1"
4949
diffStat="2 files changed"
@@ -64,7 +64,7 @@ describe("AgentDiffReview", () => {
6464
});
6565
const user = userEvent.setup();
6666
render(
67-
<AgentDiffReview
67+
<TranscriptDiffReview
6868
sessionID="s1"
6969
messageID="m1"
7070
diffStat="2 files changed"
@@ -82,7 +82,7 @@ describe("AgentDiffReview", () => {
8282
const onGetFileDiff = vi.fn(async () => fileDiff());
8383
const user = userEvent.setup();
8484
render(
85-
<AgentDiffReview
85+
<TranscriptDiffReview
8686
sessionID="s1"
8787
messageID="m1"
8888
diffStat="1 file changed"
@@ -105,7 +105,7 @@ describe("AgentDiffReview", () => {
105105
});
106106
const user = userEvent.setup();
107107
render(
108-
<AgentDiffReview
108+
<TranscriptDiffReview
109109
sessionID="s1"
110110
messageID="m1"
111111
diffStat="1 file changed"
@@ -126,7 +126,7 @@ describe("AgentDiffReview", () => {
126126
const onRevertFiles = vi.fn(async () => true);
127127
const user = userEvent.setup();
128128
render(
129-
<AgentDiffReview
129+
<TranscriptDiffReview
130130
sessionID="s1"
131131
messageID="m1"
132132
diffStat="1 file changed"
@@ -148,7 +148,7 @@ describe("AgentDiffReview", () => {
148148
const onRevertFiles = vi.fn(async () => false);
149149
const user = userEvent.setup();
150150
render(
151-
<AgentDiffReview
151+
<TranscriptDiffReview
152152
sessionID="s1"
153153
messageID="m1"
154154
diffStat="1 file changed"
@@ -169,7 +169,7 @@ describe("AgentDiffReview", () => {
169169
const onRevertFiles = vi.fn(async () => true);
170170
const user = userEvent.setup();
171171
render(
172-
<AgentDiffReview
172+
<TranscriptDiffReview
173173
sessionID="s1"
174174
messageID="m1"
175175
diffStat="2 files changed"

ui/src/features/chats/AgentDiffReview.tsx renamed to ui/src/features/transcript/TranscriptDiffReview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { SyntheticEvent } from "react";
33

44
import type { AgentChatChangedFileDiffRecord, AgentChatChangedFileRecord } from "../../types/runtime";
55
import { CodeBlock, InlineError } from "../shared/ui";
6-
import { DiffStatList, formatDiffStatSummary } from "./AgentActivityTimeline";
6+
import { DiffStatList, formatDiffStatSummary } from "./TranscriptActivityTimeline";
77

88
type Props = {
99
sessionID: string;
@@ -15,7 +15,7 @@ type Props = {
1515
onRevertFiles?: (sessionID: string, messageID: string, paths: string[]) => Promise<boolean>;
1616
};
1717

18-
export function AgentDiffReview({
18+
export function TranscriptDiffReview({
1919
sessionID,
2020
messageID,
2121
diffStat,

ui/src/features/chats/Markdown.test.tsx renamed to ui/src/features/transcript/TranscriptMarkdown.test.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,73 @@
11
import { render, screen } from "@testing-library/react";
22
import { describe, expect, it } from "vitest";
33

4-
import { Markdown } from "./Markdown";
4+
import { TranscriptMarkdown } from "./TranscriptMarkdown";
55

6-
describe("Markdown", () => {
6+
describe("TranscriptMarkdown", () => {
77
it("renders bold inline marks", () => {
8-
render(<Markdown content="this is **bold** text" />);
8+
render(<TranscriptMarkdown content="this is **bold** text" />);
99
expect(screen.getByText("bold").tagName).toBe("STRONG");
1010
});
1111

1212
it("renders inline code with monospace styling", () => {
13-
render(<Markdown content="use `useState` to track state" />);
13+
render(<TranscriptMarkdown content="use `useState` to track state" />);
1414
const code = screen.getByText("useState");
1515
expect(code.tagName).toBe("CODE");
1616
});
1717

1818
it("renders fenced code blocks", () => {
19-
render(<Markdown content={"```ts\nconst x = 1;\n```"} />);
19+
render(<TranscriptMarkdown content={"```ts\nconst x = 1;\n```"} />);
2020
expect(screen.getByText(/const x = 1/)).toBeInTheDocument();
2121
});
2222

2323
it("renders headings of varying levels", () => {
24-
const { container } = render(<Markdown content={"# H1\n## H2\n### H3\nbody"} />);
24+
const { container } = render(<TranscriptMarkdown content={"# H1\n## H2\n### H3\nbody"} />);
2525
expect(container.textContent).toContain("H1");
2626
expect(container.textContent).toContain("H2");
2727
expect(container.textContent).toContain("H3");
2828
});
2929

3030
it("renders unordered lists", () => {
31-
render(<Markdown content={"- one\n- two\n- three"} />);
31+
render(<TranscriptMarkdown content={"- one\n- two\n- three"} />);
3232
expect(screen.getByText("one").tagName).toBe("LI");
3333
expect(screen.getByText("two").tagName).toBe("LI");
3434
expect(screen.getByText("three").tagName).toBe("LI");
3535
});
3636

3737
it("renders ordered lists", () => {
38-
const { container } = render(<Markdown content={"1. first\n2. second"} />);
38+
const { container } = render(<TranscriptMarkdown content={"1. first\n2. second"} />);
3939
const ol = container.querySelector("ol");
4040
expect(ol).not.toBeNull();
4141
expect(ol?.querySelectorAll("li")).toHaveLength(2);
4242
});
4343

4444
it("renders task lists with completed and incomplete states", () => {
45-
render(<Markdown content={"- [x] done\n- [ ] todo"} />);
45+
render(<TranscriptMarkdown content={"- [x] done\n- [ ] todo"} />);
4646
expect(screen.getByLabelText("Completed task")).toBeInTheDocument();
4747
expect(screen.getByLabelText("Incomplete task")).toBeInTheDocument();
4848
});
4949

5050
it("renders horizontal rules", () => {
51-
const { container } = render(<Markdown content={"before\n\n---\n\nafter"} />);
51+
const { container } = render(<TranscriptMarkdown content={"before\n\n---\n\nafter"} />);
5252
expect(container.querySelector("hr")).not.toBeNull();
5353
});
5454

5555
it("rewrites unsafe link hrefs to # while keeping link text", () => {
56-
render(<Markdown content="see [docs](javascript:alert(1)) for details" />);
56+
render(<TranscriptMarkdown content="see [docs](javascript:alert(1)) for details" />);
5757
const link = screen.getByText("docs") as HTMLAnchorElement;
5858
expect(link.tagName).toBe("A");
5959
expect(link.getAttribute("href")).toBe("#");
6060
});
6161

6262
it("preserves http(s) and mailto link hrefs", () => {
63-
render(<Markdown content="see [docs](https://example.com) and [me](mailto:x@y.z)" />);
63+
render(<TranscriptMarkdown content="see [docs](https://example.com) and [me](mailto:x@y.z)" />);
6464
expect((screen.getByText("docs") as HTMLAnchorElement).getAttribute("href")).toBe("https://example.com");
6565
expect((screen.getByText("me") as HTMLAnchorElement).getAttribute("href")).toBe("mailto:x@y.z");
6666
});
6767

6868
it("renders tables with headers and rows", () => {
6969
const md = "| Name | Age |\n|------|-----|\n| Ada | 36 |\n| Bob | 27 |";
70-
render(<Markdown content={md} />);
70+
render(<TranscriptMarkdown content={md} />);
7171
expect(screen.getByText("Name")).toBeInTheDocument();
7272
expect(screen.getByText("Ada")).toBeInTheDocument();
7373
expect(screen.getByText("36")).toBeInTheDocument();

ui/src/features/chats/Markdown.tsx renamed to ui/src/features/transcript/TranscriptMarkdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type React from "react";
33
import { parseInlineNodes, parseMarkdownBlocks } from "../../lib/markdown";
44
import { CodeBlock } from "../shared/ui";
55

6-
export function Markdown({ content }: { content: string }) {
6+
export function TranscriptMarkdown({ content }: { content: string }) {
77
const blocks = parseMarkdownBlocks(content);
88
return (
99
<div style={{ fontSize: 13, color: "var(--t0)", lineHeight: 1.7 }}>

0 commit comments

Comments
 (0)