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
26 changes: 16 additions & 10 deletions apps/web/src/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,21 @@ function MarkdownCodeBlock({ code, children }: { code: string; children: ReactNo

return (
<div className="chat-markdown-codeblock leading-snug">
<button
type="button"
className="chat-markdown-copy-button"
onClick={handleCopy}
title={copied ? "Copied" : "Copy code"}
aria-label={copied ? "Copied" : "Copy code"}
>
{copied ? <CheckIcon className="size-3" /> : <CopyIcon className="size-3" />}
</button>
<Tooltip>
<TooltipTrigger
render={
<button
type="button"
className="chat-markdown-copy-button"
onClick={handleCopy}
aria-label={copied ? "Copied" : "Copy code"}
/>
}
>
{copied ? <CheckIcon className="size-3" /> : <CopyIcon className="size-3" />}
</TooltipTrigger>
<TooltipPopup side="top">{copied ? "Copied" : "Copy code"}</TooltipPopup>
</Tooltip>
{children}
</div>
);
Expand Down Expand Up @@ -489,7 +495,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({
side="top"
className="max-w-[min(40rem,calc(100vw-2rem))] font-mono text-[11px] leading-tight"
>
<div className="markdown-file-link-tooltip-scroll overflow-x-auto whitespace-nowrap">
<div className="tooltip-scrollbar-thin overflow-x-auto whitespace-nowrap">
{displayPath}
</div>
</TooltipPopup>
Expand Down
15 changes: 6 additions & 9 deletions apps/web/src/components/ChatView.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2250,7 +2250,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
const runButton = await waitForElement(
() =>
Array.from(document.querySelectorAll("button")).find(
(button) => button.title === "Run Lint",
(button) => button.getAttribute("aria-label") === "Run Lint",
) as HTMLButtonElement | null,
"Unable to find Run Lint button.",
);
Expand Down Expand Up @@ -2329,7 +2329,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
const runButton = await waitForElement(
() =>
Array.from(document.querySelectorAll("button")).find(
(button) => button.title === "Run Test",
(button) => button.getAttribute("aria-label") === "Run Test",
) as HTMLButtonElement | null,
"Unable to find Run Test button.",
);
Expand Down Expand Up @@ -3068,8 +3068,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
});

try {
const initialModeButton = await waitForInteractionModeButton("Build");
expect(initialModeButton.title).toContain("enter plan mode");
await waitForInteractionModeButton("Build");

window.dispatchEvent(
new KeyboardEvent("keydown", {
Expand All @@ -3081,7 +3080,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
);
await waitForLayout();

expect((await waitForInteractionModeButton("Build")).title).toContain("enter plan mode");
await waitForInteractionModeButton("Build");

const composerEditor = await waitForComposerEditor();
composerEditor.focus();
Expand All @@ -3096,9 +3095,7 @@ describe("ChatView timeline estimator parity (full app)", () => {

await vi.waitFor(
async () => {
expect((await waitForInteractionModeButton("Plan")).title).toContain(
"return to normal build mode",
);
expect(await waitForInteractionModeButton("Plan")).toBeTruthy();
},
{ timeout: 8_000, interval: 16 },
);
Expand All @@ -3114,7 +3111,7 @@ describe("ChatView timeline estimator parity (full app)", () => {

await vi.waitFor(
async () => {
expect((await waitForInteractionModeButton("Build")).title).toContain("enter plan mode");
expect(await waitForInteractionModeButton("Build")).toBeTruthy();
},
{ timeout: 8_000, interval: 16 },
);
Expand Down
179 changes: 108 additions & 71 deletions apps/web/src/components/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { useSettings } from "../hooks/useSettings";
import { formatShortTimestamp } from "../timestampFormat";
import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell";
import { ToggleGroup, Toggle } from "./ui/toggle-group";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

type DiffRenderMode = "stacked" | "split";
type DiffThemeType = "light" | "dark";
Expand Down Expand Up @@ -534,35 +535,41 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
</div>
</button>
{orderedTurnDiffSummaries.map((summary) => (
<button
key={summary.turnId}
type="button"
className="shrink-0 rounded-md"
onClick={() => selectTurn(summary.turnId)}
title={summary.turnId}
data-turn-chip-selected={summary.turnId === selectedTurn?.turnId}
>
<div
className={cn(
"rounded-md border px-2 py-1 text-left transition-colors",
summary.turnId === selectedTurn?.turnId
? "border-border bg-accent text-accent-foreground"
: "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80",
)}
<Tooltip key={summary.turnId}>
<TooltipTrigger
render={
<button
type="button"
className="shrink-0 rounded-md"
onClick={() => selectTurn(summary.turnId)}
data-turn-chip-selected={summary.turnId === selectedTurn?.turnId}
aria-label={summary.turnId}
/>
}
>
<div className="flex items-center gap-1">
<span className="text-[10px] leading-tight font-medium">
Turn{" "}
{summary.checkpointTurnCount ??
inferredCheckpointTurnCountByTurnId[summary.turnId] ??
"?"}
</span>
<span className="text-[9px] leading-tight opacity-70">
{formatShortTimestamp(summary.completedAt, settings.timestampFormat)}
</span>
<div
className={cn(
"rounded-md border px-2 py-1 text-left transition-colors",
summary.turnId === selectedTurn?.turnId
? "border-border bg-accent text-accent-foreground"
: "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80",
)}
>
<div className="flex items-center gap-1">
<span className="text-[10px] leading-tight font-medium">
Turn{" "}
{summary.checkpointTurnCount ??
inferredCheckpointTurnCountByTurnId[summary.turnId] ??
"?"}
</span>
<span className="text-[9px] leading-tight opacity-70">
{formatShortTimestamp(summary.completedAt, settings.timestampFormat)}
</span>
</div>
</div>
</div>
</button>
</TooltipTrigger>
<TooltipPopup side="top">{summary.turnId}</TooltipPopup>
</Tooltip>
))}
</div>
</div>
Expand All @@ -586,30 +593,50 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
<Columns2Icon className="size-3" />
</Toggle>
</ToggleGroup>
<Toggle
aria-label={diffWordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"}
title={diffWordWrap ? "Disable line wrapping" : "Enable line wrapping"}
variant="outline"
size="xs"
pressed={diffWordWrap}
onPressedChange={(pressed) => {
setDiffWordWrap(Boolean(pressed));
}}
>
<TextWrapIcon className="size-3" />
</Toggle>
<Toggle
aria-label={diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
title={diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
variant="outline"
size="xs"
pressed={diffIgnoreWhitespace}
onPressedChange={(pressed) => {
setDiffIgnoreWhitespace(Boolean(pressed));
}}
>
<PilcrowIcon className="size-3" />
</Toggle>
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={
diffWordWrap ? "Disable diff line wrapping" : "Enable diff line wrapping"
}
variant="outline"
size="xs"
pressed={diffWordWrap}
onPressedChange={(pressed) => {
setDiffWordWrap(Boolean(pressed));
}}
/>
}
>
<TextWrapIcon className="size-3" />
</TooltipTrigger>
<TooltipPopup side="top">
{diffWordWrap ? "Disable line wrapping" : "Enable line wrapping"}
</TooltipPopup>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Toggle
aria-label={
diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"
}
variant="outline"
size="xs"
pressed={diffIgnoreWhitespace}
onPressedChange={(pressed) => {
setDiffIgnoreWhitespace(Boolean(pressed));
}}
/>
}
>
<PilcrowIcon className="size-3" />
</TooltipTrigger>
<TooltipPopup side="top">
{diffIgnoreWhitespace ? "Show whitespace changes" : "Hide whitespace changes"}
</TooltipPopup>
</Tooltip>
</div>
</>
);
Expand Down Expand Up @@ -683,26 +710,36 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
<FileDiff
fileDiff={fileDiff}
renderHeaderPrefix={() => (
<button
type="button"
className={cn(
"inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors hover:bg-foreground/10 focus-visible:outline-hidden",
getDiffCollapseIconClassName(fileDiff),
)}
aria-label={collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`}
aria-expanded={!collapsed}
title={collapsed ? "Expand diff" : "Collapse diff"}
onClick={(event) => {
event.stopPropagation();
toggleDiffFileCollapsed(fileKey);
}}
>
{collapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)}
</button>
<Tooltip>
<TooltipTrigger
render={
<button
type="button"
className={cn(
"inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors hover:bg-foreground/10 focus-visible:outline-hidden",
getDiffCollapseIconClassName(fileDiff),
)}
aria-label={
collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`
}
aria-expanded={!collapsed}
onClick={(event) => {
event.stopPropagation();
toggleDiffFileCollapsed(fileKey);
}}
/>
}
>
{collapsed ? (
<ChevronRightIcon className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)}
</TooltipTrigger>
<TooltipPopup side="top">
{collapsed ? "Expand diff" : "Collapse diff"}
</TooltipPopup>
</Tooltip>
)}
options={{
collapsed,
Expand Down
49 changes: 32 additions & 17 deletions apps/web/src/components/ProjectScriptsControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "./ui/menu"
import { Popover, PopoverPopup, PopoverTrigger } from "./ui/popover";
import { Switch } from "./ui/switch";
import { Textarea } from "./ui/textarea";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

const SCRIPT_ICONS: Array<{ id: ProjectScriptIcon; label: string }> = [
{ id: "play", label: "Play" },
Expand Down Expand Up @@ -219,17 +220,24 @@ export default function ProjectScriptsControl({
<>
{primaryScript ? (
<Group aria-label="Project scripts">
<Button
size="xs"
variant="outline"
onClick={() => onRunScript(primaryScript)}
title={`Run ${primaryScript.name}`}
>
<ScriptIcon icon={primaryScript.icon} />
<span className="sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5">
{primaryScript.name}
</span>
</Button>
<Tooltip>
<TooltipTrigger
render={
<Button
size="xs"
variant="outline"
onClick={() => onRunScript(primaryScript)}
aria-label={`Run ${primaryScript.name}`}
/>
}
>
<ScriptIcon icon={primaryScript.icon} />
<span className="sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5">
{primaryScript.name}
</span>
</TooltipTrigger>
<TooltipPopup side="top">{`Run ${primaryScript.name}`}</TooltipPopup>
</Tooltip>
<GroupSeparator className="hidden @3xl/header-actions:block" />
<Menu highlightItemOnHover={false}>
<MenuTrigger
Expand Down Expand Up @@ -289,12 +297,19 @@ export default function ProjectScriptsControl({
</Menu>
</Group>
) : (
<Button size="xs" variant="outline" onClick={openAddDialog} title="Add action">
<PlusIcon className="size-3.5" />
<span className="sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5">
Add action
</span>
</Button>
<Tooltip>
<TooltipTrigger
render={
<Button size="xs" variant="outline" onClick={openAddDialog} aria-label="Add action" />
}
>
<PlusIcon className="size-3.5" />
<span className="sr-only @3xl/header-actions:not-sr-only @3xl/header-actions:ml-0.5">
Add action
</span>
</TooltipTrigger>
<TooltipPopup side="top">Add action</TooltipPopup>
</Tooltip>
)}

<Dialog
Expand Down
Loading
Loading