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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ plugins/posthog/local-skills/

# Symlinked copies of posthog, to make developing against those APIs easier
posthog-sym
.pi-lens/
Progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ToolCall } from "@features/sessions/types";
import { render } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ExecuteToolView } from "./ExecuteToolView";

vi.mock("@phosphor-icons/react", () => ({
Terminal: () => <span data-testid="icon-terminal">TerminalIcon</span>,
Minus: () => <span data-testid="icon-minus">MinusIcon</span>,
Plus: () => <span data-testid="icon-plus">PlusIcon</span>,
}));

vi.mock("@utils/path", () => ({
compactHomePath: (p: string) => p,
}));

const baseToolCall: ToolCall = {
toolCallId: "test-exec-1",
title: "Run tests",
kind: "execute",
status: "completed",
content: [],
locations: [],
rawInput: {
command: "pnpm test -- --run",
description: "Run tests",
},
};

describe("ExecuteToolView", () => {
it("aligns icon vertically centered with text content", () => {
const { container } = render(<ExecuteToolView toolCall={baseToolCall} />);
// The outermost flex should use align="center" for consistent icon alignment
// Radix renders this as rt-r-ai-center
const outerFlex = container.querySelector(".min-w-0");
expect(outerFlex?.className).toContain("rt-r-ai-center");
});

it("applies min-w-0 to prevent text overflow", () => {
const { container } = render(<ExecuteToolView toolCall={baseToolCall} />);
const outerFlex = container.querySelector(".min-w-0");
expect(outerFlex?.className).toContain("min-w-0");
});

it("truncates long commands to prevent overflow", () => {
const longCommand =
"find /very/long/path -type f -name '*.ts' | xargs grep -l 'pattern' | head -100";
const { container } = render(
<ExecuteToolView
toolCall={{
...baseToolCall,
rawInput: { command: longCommand },
}}
/>,
);
// Command text should have truncation class
// The command text should be in a truncated container
const truncatedContainer = container.querySelector(".truncate");
expect(truncatedContainer).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export function ExecuteToolView({
className={`group py-0.5 ${isExpandable ? "cursor-pointer" : ""}`}
onClick={handleClick}
>
<Flex gap="2" className="min-w-0">
<Box className="shrink-0 pt-px">
<Flex align="center" gap="2" className="min-w-0">
<Box className="shrink-0">
<ExpandableIcon
icon={Terminal}
isLoading={isLoading}
Expand All @@ -65,9 +65,11 @@ export function ExecuteToolView({
/>
</Box>
<Flex align="center" gap="2" wrap="wrap" className="min-w-0">
{description && <ToolTitle>{description}</ToolTitle>}
{description && (
<ToolTitle className="min-w-0 truncate">{description}</ToolTitle>
)}
{command && (
<ToolTitle className="min-w-0 truncate">
<ToolTitle className="shrink-0 truncate">
<span className="font-mono text-accent-11" title={command}>
{truncateText(compactHomePath(command), MAX_COMMAND_LENGTH)}
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ThoughtView } from "./ThoughtView";

// Mock phosphor icons to avoid rendering issues in jsdom
vi.mock("@phosphor-icons/react", () => ({
Brain: () => <span data-testid="icon-brain">BrainIcon</span>,
Plus: () => <span data-testid="icon-plus">PlusIcon</span>,
Minus: () => <span data-testid="icon-minus">MinusIcon</span>,
}));

describe("ThoughtView", () => {
it("applies left padding consistent with other tool views (pl-3)", () => {
const { container } = render(
<ThoughtView content="some thought" isLoading={false} />,
);
// ThoughtView root should have pl-3 to align with AgentMessage and ToolCallBlock
const root = container.firstElementChild;
expect(root?.className).toContain("pl-3");
});

it("renders the Thinking label", () => {
render(<ThoughtView content="some thought" isLoading={false} />);
expect(screen.getByText("Thinking")).toBeInTheDocument();
});

it("applies min-w-0 for text overflow containment", () => {
const { container } = render(
<ThoughtView content="some thought" isLoading={false} />,
);
const root = container.firstElementChild;
expect(root?.className).toContain("min-w-0");
});

it("aligns icon vertically centered with text", () => {
const { container } = render(
<ThoughtView content="some thought" isLoading={false} />,
);
// The button should use flex with items-center for icon alignment
const button = container.querySelector("button");
expect(button?.className).toContain("items-center");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ThoughtView = memo(function ThoughtView({
: contentLines.slice(0, COLLAPSED_LINE_COUNT).join("\n");

return (
<Box>
<Box className="min-w-0 pl-3">
<button
type="button"
onClick={() => hasContent && setIsExpanded((v) => !v)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { ToolCall } from "@features/sessions/types";
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ToolCallView } from "./ToolCallView";

vi.mock("@phosphor-icons/react", () => ({
ArrowsClockwise: () => (
<span data-testid="icon-arrows-clockwise">ArrowsClockwise</span>
),
ArrowsLeftRight: () => (
<span data-testid="icon-arrows-left-right">ArrowsLeftRight</span>
),
Brain: () => <span data-testid="icon-brain">BrainIcon</span>,
ChatCircle: () => <span data-testid="icon-chat-circle">ChatCircle</span>,
Command: () => <span data-testid="icon-command">Command</span>,
FileText: () => <span data-testid="icon-file-text">FileText</span>,
Globe: () => <span data-testid="icon-globe">Globe</span>,
MagnifyingGlass: () => (
<span data-testid="icon-magnifying-glass">MagnifyingGlass</span>
),
Minus: () => <span data-testid="icon-minus">MinusIcon</span>,
PencilSimple: () => (
<span data-testid="icon-pencil-simple">PencilSimple</span>
),
Plus: () => <span data-testid="icon-plus">PlusIcon</span>,
Terminal: () => <span data-testid="icon-terminal">Terminal</span>,
Trash: () => <span data-testid="icon-trash">Trash</span>,
Wrench: () => <span data-testid="icon-wrench">WrenchIcon</span>,
}));

vi.mock("@utils/path", () => ({
compactHomePath: (p: string) => p,
}));

const baseToolCall: ToolCall = {
toolCallId: "test-1",
title: "Test Tool",
kind: "other",
status: "completed",
content: [],
locations: [],
rawInput: { key: "value" },
};

describe("ToolCallView", () => {
it("aligns icon vertically centered with text content", () => {
const { container } = render(<ToolCallView toolCall={baseToolCall} />);
// The outer flex container should use align="center" for
// consistent icon vertical alignment with other tool views like ToolRow
// Radix renders align="center" as rt-r-ai-center
const outerFlex = container.querySelector(".min-w-0");
expect(outerFlex?.className).toContain("rt-r-ai-center");
});

it("applies min-w-0 to prevent text overflow from expanding container", () => {
const { container } = render(<ToolCallView toolCall={baseToolCall} />);
const outerFlex = container.querySelector(".min-w-0");
expect(outerFlex?.className).toContain("min-w-0");
});

it("wraps title text with overflow handling for long titles", () => {
const longTitle =
"This is a very long tool call title that should not overflow its container";
render(<ToolCallView toolCall={{ ...baseToolCall, title: longTitle }} />);
// Title text should have truncation or break-words to prevent overflow
const titleElement = screen.getByText(longTitle);
const parent = titleElement.closest(".min-w-0");
expect(parent).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ export function ToolCallView({
className={`group py-0.5 ${isExpandable ? "cursor-pointer" : ""}`}
onClick={handleClick}
>
<Flex gap="2" className="min-w-0">
<Box className="shrink-0 pt-px">
<Flex align="center" gap="2" className="min-w-0">
<Box className="shrink-0">
<ExpandableIcon
icon={KindIcon}
isLoading={isLoading}
Expand All @@ -108,9 +108,9 @@ export function ToolCallView({
/>
</Box>
<Flex align="center" gap="1" wrap="wrap" className="min-w-0">
<ToolTitle>{displayText}</ToolTitle>
<ToolTitle className="min-w-0 truncate">{displayText}</ToolTitle>
{inputPreview && (
<ToolTitle>
<ToolTitle className="shrink-0">
<span className="font-mono text-accent-11">{inputPreview}</span>
</ToolTitle>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,23 +210,23 @@ export function ExpandableIcon({
return <IconComponent size={ICON_SIZE} className={ICON_CLASS} />;
}
return (
<>
<span className="relative inline-flex size-[12px] shrink-0">
<IconComponent
size={ICON_SIZE}
className={`${ICON_CLASS} group-hover:hidden`}
/>
{isExpanded ? (
<Minus
size={ICON_SIZE}
className={`hidden ${ICON_CLASS} group-hover:block`}
className={`absolute inset-0 hidden ${ICON_CLASS} group-hover:block`}
/>
) : (
<Plus
size={ICON_SIZE}
className={`hidden ${ICON_CLASS} group-hover:block`}
className={`absolute inset-0 hidden ${ICON_CLASS} group-hover:block`}
/>
)}
</>
</span>
);
}

Expand Down