-
+
+
-
+
+ {transcriptModalOpen ? (
+
+
+
{agent.name} · Transcript
+
+
+
+
{}}
+ renderBlocks={renderBlocks}
+ liveThinkingText={liveThinkingText}
+ liveAssistantText={liveAssistantText}
+ showTypingIndicator={showTypingIndicator}
+ outputLineCount={visibleOutputLines.length}
+ liveAssistantCharCount={liveAssistantText.length}
+ liveThinkingCharCount={liveThinkingText.length}
+ runStartedAt={agent.runStartedAt}
+ scrollToBottomOnOpenKey={`${scrollToBottomOnOpenKey}:modal`}
+ scrollToBottomNextOutputRef={scrollToBottomNextOutputRef}
+ pendingExecApprovals={[]}
+ emptyStateTitle={emptyStateTitle}
+ lastUserMessage={agent.lastUserMessage}
+ latestPreview={agent.latestPreview}
+ previewItems={agent.previewItems}
+ />
+
+
+ ) : null}
);
};
diff --git a/src/features/agents/operations/studioBootstrapWorkflow.ts b/src/features/agents/operations/studioBootstrapWorkflow.ts
index 69d3e50..4dbe3f8 100644
--- a/src/features/agents/operations/studioBootstrapWorkflow.ts
+++ b/src/features/agents/operations/studioBootstrapWorkflow.ts
@@ -209,7 +209,10 @@ export function planFocusedPreferenceRestore(params: {
};
}
- const restoredFilter = preference.filter === "running" ? "all" : preference.filter;
+ const restoredFilter =
+ preference.filter === "running" || preference.filter === "approvals"
+ ? "all"
+ : preference.filter;
return {
preferredSelectedAgentId: preference.selectedAgentId,
focusFilter: restoredFilter,
diff --git a/tests/unit/agentChatPanel-controls.test.ts b/tests/unit/agentChatPanel-controls.test.ts
index 8463a17..0353160 100644
--- a/tests/unit/agentChatPanel-controls.test.ts
+++ b/tests/unit/agentChatPanel-controls.test.ts
@@ -178,6 +178,36 @@ describe("AgentChatPanel controls", () => {
expect(onNewSession).toHaveBeenCalledTimes(1);
});
+ it("opens read-only transcript modal from expand icon", async () => {
+ render(
+ createElement(AgentChatPanel, {
+ agent: {
+ ...createAgent(),
+ outputLines: ["> show transcript", "Hello from assistant"],
+ },
+ isSelected: true,
+ canSend: true,
+ models,
+ stopBusy: false,
+ onLoadMoreHistory: vi.fn(),
+ onOpenSettings: vi.fn(),
+ onModelChange: vi.fn(),
+ onThinkingChange: vi.fn(),
+ onDraftChange: vi.fn(),
+ onSend: vi.fn(),
+ onStopRun: vi.fn(),
+ onAvatarShuffle: vi.fn(),
+ })
+ );
+
+ fireEvent.click(screen.getByLabelText("Expand transcript"));
+ expect(screen.getByText(/Agent One · Transcript/i)).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: /Close/i }));
+ await waitFor(() => {
+ expect(screen.queryByText(/Agent One · Transcript/i)).not.toBeInTheDocument();
+ });
+ });
+
it("does_not_render_inline_status_badge_markers", () => {
const { rerender, container } = render(
createElement(AgentChatPanel, {
diff --git a/tests/unit/agentChatPanel-scroll.test.ts b/tests/unit/agentChatPanel-scroll.test.ts
index a7236c6..81525ba 100644
--- a/tests/unit/agentChatPanel-scroll.test.ts
+++ b/tests/unit/agentChatPanel-scroll.test.ts
@@ -50,7 +50,8 @@ describe("AgentChatPanel scrolling", () => {
});
it("shows jump-to-latest when unpinned and new output arrives, and jumps on click", async () => {
- (Element.prototype as unknown as { scrollIntoView: unknown }).scrollIntoView = vi.fn();
+ const scrollIntoView = vi.fn();
+ (Element.prototype as unknown as { scrollIntoView: unknown }).scrollIntoView = scrollIntoView;
const agent = createAgent();
const { rerender } = render(
@@ -102,10 +103,8 @@ describe("AgentChatPanel scrolling", () => {
fireEvent.click(screen.getByRole("button", { name: "Jump to latest" }));
- expect(
- (Element.prototype as unknown as { scrollIntoView: ReturnType
})
- .scrollIntoView
- ).toHaveBeenCalled();
+ expect(scrollEl.scrollTop).toBe(1000);
+ expect(scrollIntoView).not.toHaveBeenCalled();
});
it("scrolls to the bottom when a different agent is opened", async () => {
@@ -137,12 +136,6 @@ describe("AgentChatPanel scrolling", () => {
Object.defineProperty(scrollEl, "scrollHeight", { value: 1000, configurable: true });
Object.defineProperty(scrollEl, "scrollTop", { value: 0, writable: true, configurable: true });
- await waitFor(() => {
- expect(scrollIntoView).toHaveBeenCalled();
- });
-
- scrollIntoView.mockClear();
-
rerender(
createElement(AgentChatPanel, {
agent: createAgent({
@@ -167,8 +160,9 @@ describe("AgentChatPanel scrolling", () => {
);
await waitFor(() => {
- expect(scrollIntoView).toHaveBeenCalled();
+ expect(scrollEl.scrollTop).toBe(1000);
});
+ expect(scrollIntoView).not.toHaveBeenCalled();
});
it("shows history truncation banner only when scrolled to top", () => {
@@ -209,4 +203,68 @@ describe("AgentChatPanel scrolling", () => {
fireEvent.scroll(scrollEl);
expect(screen.getByText(/Showing latest 200 turns/i)).toBeInTheDocument();
});
+
+ it("keeps the transcript pinned to the chat container when sending", async () => {
+ const scrollIntoView = vi.fn();
+ (Element.prototype as unknown as { scrollIntoView: unknown }).scrollIntoView = scrollIntoView;
+ const onSend = vi.fn();
+ const agent = createAgent({
+ draft: "Investigate the scroll jump",
+ outputLines: ["> earlier question", "earlier answer"],
+ });
+ const { rerender } = render(
+ createElement(AgentChatPanel, {
+ agent,
+ isSelected: true,
+ canSend: true,
+ models,
+ stopBusy: false,
+ onLoadMoreHistory: vi.fn(),
+ onOpenSettings: vi.fn(),
+ onModelChange: vi.fn(),
+ onThinkingChange: vi.fn(),
+ onDraftChange: vi.fn(),
+ onSend,
+ onStopRun: vi.fn(),
+ onAvatarShuffle: vi.fn(),
+ })
+ );
+
+ const scrollEl = screen.getByTestId("agent-chat-scroll");
+ Object.defineProperty(scrollEl, "clientHeight", { value: 100, configurable: true });
+ Object.defineProperty(scrollEl, "scrollHeight", { value: 1000, writable: true, configurable: true });
+ Object.defineProperty(scrollEl, "scrollTop", { value: 900, writable: true, configurable: true });
+
+ fireEvent.click(screen.getByRole("button", { name: "Send" }));
+ expect(onSend).toHaveBeenCalledWith("Investigate the scroll jump");
+
+ Object.defineProperty(scrollEl, "scrollHeight", { value: 1040, writable: true, configurable: true });
+
+ rerender(
+ createElement(AgentChatPanel, {
+ agent: {
+ ...agent,
+ draft: "",
+ outputLines: ["> earlier question", "earlier answer", "> Investigate the scroll jump"],
+ },
+ isSelected: true,
+ canSend: true,
+ models,
+ stopBusy: false,
+ onLoadMoreHistory: vi.fn(),
+ onOpenSettings: vi.fn(),
+ onModelChange: vi.fn(),
+ onThinkingChange: vi.fn(),
+ onDraftChange: vi.fn(),
+ onSend,
+ onStopRun: vi.fn(),
+ onAvatarShuffle: vi.fn(),
+ })
+ );
+
+ await waitFor(() => {
+ expect(scrollEl.scrollTop).toBe(1040);
+ });
+ expect(scrollIntoView).not.toHaveBeenCalled();
+ });
});
diff --git a/tests/unit/markdownPreWrapRegression.test.ts b/tests/unit/markdownPreWrapRegression.test.ts
new file mode 100644
index 0000000..17c23b2
--- /dev/null
+++ b/tests/unit/markdownPreWrapRegression.test.ts
@@ -0,0 +1,19 @@
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import { describe, expect, it } from "vitest";
+
+describe("markdown wrap regression guard", () => {
+ it("keeps pre/code blocks constrained and wrappable in chat markdown", () => {
+ const cssPath = path.resolve(process.cwd(), "src/app/styles/markdown.css");
+ const css = readFileSync(cssPath, "utf8");
+
+ expect(css).toContain(".agent-markdown pre {");
+ expect(css).toContain("max-width: 100%;");
+ expect(css).toContain("min-width: 0;");
+ expect(css).toContain(".agent-markdown pre,");
+ expect(css).toContain(".agent-markdown pre code {");
+ expect(css).toContain("white-space: pre-wrap;");
+ expect(css).toContain("overflow-wrap: anywhere;");
+ expect(css).toContain("word-break: break-word;");
+ });
+});
diff --git a/tests/unit/studioBootstrapWorkflow.test.ts b/tests/unit/studioBootstrapWorkflow.test.ts
index 2d79d67..ae2a3c7 100644
--- a/tests/unit/studioBootstrapWorkflow.test.ts
+++ b/tests/unit/studioBootstrapWorkflow.test.ts
@@ -180,7 +180,7 @@ describe("studioBootstrapWorkflow", () => {
})
).toEqual({
preferredSelectedAgentId: "agent-3",
- focusFilter: "approvals",
+ focusFilter: "all",
});
expect(