Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7dc599b
Add copy path button to diff headers
ipanasenko Apr 29, 2026
558bb17
remove condition
ipanasenko Apr 29, 2026
ff25a41
address comments from cursorbot + inline onClick
ipanasenko Apr 29, 2026
2bca713
Merge branch 'main' into t3code/853b397b
ipanasenko May 1, 2026
75b9125
Merge branch 'main' into t3code/853b397b
ipanasenko May 3, 2026
6c6e93f
Merge branch 'main' into t3code/853b397b
ipanasenko May 15, 2026
07e1e81
Merge branch 'main' into t3code/853b397b
ipanasenko May 27, 2026
c52e669
Merge branch 'main' into t3code/853b397b
ipanasenko May 31, 2026
c06109f
Merge branch 'main' into t3code/853b397b
ipanasenko Jun 4, 2026
82b1c35
Merge branch 'main' into t3code/853b397b
ipanasenko Jun 12, 2026
f749211
Merge branch 'main' into t3code/853b397b
ipanasenko Jun 18, 2026
972821c
pass `renderHeaderMetadata` to `FileDiff`
ipanasenko Jun 18, 2026
b028ddf
fix
ipanasenko Jun 18, 2026
997ae2b
Merge commit 'd2c0a6a48f50f319815a8cf5501f9e2e9b8ca617' into t3code/8…
ipanasenko Jun 21, 2026
a29ebac
Merge branch 'main' into t3code/853b397b
ipanasenko Jun 21, 2026
d701d82
merge origin/main
ipanasenko Jun 22, 2026
a0d1551
Merge branch 'main' into t3code/853b397b
ipanasenko Jun 27, 2026
d8c3e04
Merge branch 'main' into t3code/853b397b
ipanasenko Jul 9, 2026
9de21c0
bump pierre/diffs
ipanasenko Jul 9, 2026
1324094
render copy in suffix
ipanasenko Jul 9, 2026
5129acd
Merge branch 'main' into t3code/853b397b
ipanasenko Jul 9, 2026
a59070b
Merge branch 'main' into t3code/853b397b
juliusmarminge Jul 17, 2026
65197d4
Merge branch 'main' into t3code/853b397b
ipanasenko Jul 19, 2026
c7dcbe0
Merge branch 'main' into t3code/853b397b
ipanasenko Jul 22, 2026
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
41 changes: 41 additions & 0 deletions apps/web/src/components/DiffFilePathCopyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CheckIcon, CopyIcon } from "lucide-react";
import { useRef } from "react";
import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
import { Button } from "./ui/button";
import {
ANCHORED_COPY_TOAST_TIMEOUT_MS,
showAnchoredCopyErrorToast,
showAnchoredCopySuccessToast,
} from "./ui/anchoredCopyToast";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

export function DiffFilePathCopyButton({ filePath }: { filePath: string }) {
const ref = useRef<HTMLButtonElement>(null);
const { copyToClipboard, isCopied } = useCopyToClipboard<void>({
onCopy: () => showAnchoredCopySuccessToast(ref),
onError: (error) => showAnchoredCopyErrorToast(ref, error),
timeout: ANCHORED_COPY_TOAST_TIMEOUT_MS,
});

return (
<Tooltip>
<TooltipTrigger
render={
<Button
ref={ref}
type="button"
size="icon-xs"
variant="ghost"
aria-label="Copy file path"
onClick={() => copyToClipboard(filePath, undefined)}
/>
}
>
{isCopied ? <CheckIcon className="size-3 text-success" /> : <CopyIcon className="size-3" />}
</TooltipTrigger>
<TooltipPopup>
<p>{isCopied ? "Copied" : "Copy path"}</p>
</TooltipPopup>
</Tooltip>
);
}
4 changes: 4 additions & 0 deletions apps/web/src/components/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { useProject, useThread } from "../state/entities";
import { resolveThreadRouteRef } from "../threadRoutes";
import { useClientSettings } from "../hooks/useSettings";
import { formatShortTimestamp } from "../timestampFormat";
import { DiffFilePathCopyButton } from "./DiffFilePathCopyButton";
import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell";
import { AnnotatableCodeView, type AnnotatableCodeViewHandle } from "./diffs/AnnotatableCodeView";
import { ToggleGroup, Toggle } from "./ui/toggle-group";
Expand Down Expand Up @@ -817,6 +818,9 @@ export default function DiffPanel({
sectionId={reviewSectionId}
sectionTitle={reviewSectionTitle}
composerDraftTarget={composerDraftTarget}
renderHeaderFilenameSuffix={(fileDiff) => (
<DiffFilePathCopyButton filePath={resolveFileDiffPath(fileDiff)} />
)}
renderHeaderPrefix={(fileDiff, fileKey, collapsed) => {
const filePath = resolveFileDiffPath(fileDiff);
return (
Expand Down
44 changes: 8 additions & 36 deletions apps/web/src/components/chat/MessageCopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,13 @@ import { CopyIcon, CheckIcon } from "lucide-react";
import { Button } from "../ui/button";
import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
import { cn } from "~/lib/utils";
import { anchoredToastManager } from "../ui/toast";
import {
ANCHORED_COPY_TOAST_TIMEOUT_MS,
showAnchoredCopyErrorToast,
showAnchoredCopySuccessToast,
} from "../ui/anchoredCopyToast";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";

const ANCHORED_TOAST_TIMEOUT_MS = 1000;
const onCopy = (ref: React.RefObject<HTMLButtonElement | null>) => {
if (ref.current) {
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
positionerProps: {
anchor: ref.current,
},
timeout: ANCHORED_TOAST_TIMEOUT_MS,
title: "Copied!",
});
}
};

const onCopyError = (ref: React.RefObject<HTMLButtonElement | null>, error: Error) => {
if (ref.current) {
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
positionerProps: {
anchor: ref.current,
},
timeout: ANCHORED_TOAST_TIMEOUT_MS,
title: "Failed to copy",
description: error.message,
});
}
};

export const MessageCopyButton = memo(function MessageCopyButton({
text,
size = "xs",
Expand All @@ -51,9 +23,9 @@ export const MessageCopyButton = memo(function MessageCopyButton({
}) {
const ref = useRef<HTMLButtonElement>(null);
const { copyToClipboard, isCopied } = useCopyToClipboard<void>({
onCopy: () => onCopy(ref),
onError: (error: Error) => onCopyError(ref, error),
timeout: ANCHORED_TOAST_TIMEOUT_MS,
onCopy: () => showAnchoredCopySuccessToast(ref),
onError: (error: Error) => showAnchoredCopyErrorToast(ref, error),
timeout: ANCHORED_COPY_TOAST_TIMEOUT_MS,
});

return (
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/components/diffs/AnnotatableCodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface AnnotatableCodeViewProps {
options: NonNullable<CodeViewProps<DiffCommentAnnotationGroup>["options"]>;
viewerRef?: Ref<AnnotatableCodeViewHandle>;
className?: string;
renderHeaderFilenameSuffix: (fileDiff: FileDiffMetadata) => ReactNode;
renderHeaderPrefix: (
fileDiff: FileDiffMetadata,
fileKey: string,
Expand All @@ -102,6 +103,7 @@ export function AnnotatableCodeView({
options,
viewerRef,
className,
renderHeaderFilenameSuffix,
renderHeaderPrefix,
}: AnnotatableCodeViewProps) {
const addReviewComment = useComposerDraftStore((store) => store.addReviewComment);
Expand Down Expand Up @@ -243,6 +245,9 @@ export function AnnotatableCodeView({
enableLineSelection: !hasOpenComment,
onLineSelectionEnd: beginComment,
}}
renderHeaderFilenameSuffix={(item) =>
item.type === "diff" ? renderHeaderFilenameSuffix(item.fileDiff) : null
}
renderHeaderPrefix={(item) =>
item.type === "diff"
? renderHeaderPrefix(item.fileDiff, item.id, item.collapsed === true)
Expand Down
33 changes: 33 additions & 0 deletions apps/web/src/components/ui/anchoredCopyToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { RefObject } from "react";
import { anchoredToastManager } from "./toast";

export const ANCHORED_COPY_TOAST_TIMEOUT_MS = 1000;

export function showAnchoredCopySuccessToast(ref: RefObject<HTMLButtonElement | null>) {
if (!ref.current) return;
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
positionerProps: {
anchor: ref.current,
},
timeout: ANCHORED_COPY_TOAST_TIMEOUT_MS,
title: "Copied!",
});
}

export function showAnchoredCopyErrorToast(ref: RefObject<HTMLButtonElement | null>, error: Error) {
if (!ref.current) return;
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
positionerProps: {
anchor: ref.current,
},
timeout: ANCHORED_COPY_TOAST_TIMEOUT_MS,
title: "Failed to copy",
description: error.message,
});
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
diff --git a/dist/editor/editor.js b/dist/editor/editor.js
index e8013fc6eb6f243a6c912facf3fc0319ac66a8d0..80c82df4cdeb828bd331f5ec2f443d216bedc304 100644
index a532eb8f1cfb4fa431b5304550bdfaf93d3d135e..c9763b6b10afaa27d44203055ef6203dadbbadf6 100644
--- a/dist/editor/editor.js
+++ b/dist/editor/editor.js
@@ -77,15 +77,12 @@ var Editor = class {
@@ -86,15 +86,12 @@ var Editor = class {
this.#options = options;
}
edit(component) {
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, diffStyle, lineHoverHighlight,...rest } = component.options;
+ const { useTokenTransformer, expandUnchanged, diffStyle,...rest } = component.options;
const isDiff = component.type === "file-diff";
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || lineHoverHighlight !== "disabled" || expandUnchanged !== true && isDiff || diffStyle === "unified" && isDiff) {
+ if (useTokenTransformer !== true || expandUnchanged !== true && isDiff || diffStyle === "unified" && isDiff) {
component.setOptions({
edit(fileInstance) {
- const { useTokenTransformer, enableGutterUtility, enableLineSelection, expandUnchanged, lineHoverHighlight = "disabled", ...rest } = fileInstance.options;
- if (useTokenTransformer !== true || enableGutterUtility === true || enableLineSelection === true || expandUnchanged !== true && fileInstance.type === "file-diff" || lineHoverHighlight !== "disabled") {
+ const { useTokenTransformer, expandUnchanged, ...rest } = fileInstance.options;
+ if (useTokenTransformer !== true || expandUnchanged !== true && fileInstance.type === "file-diff") {
fileInstance.setOptions({
...rest,
useTokenTransformer: true,
- enableGutterUtility: false,
- enableLineSelection: false,
- lineHoverHighlight: "disabled",
expandUnchanged: true,
diffStyle: "split"
- expandUnchanged: true,
- lineHoverHighlight: "disabled"
+ expandUnchanged: true
});
@@ -511,6 +508,7 @@ var Editor = class {
fileInstance.rerender();
}
@@ -636,6 +633,7 @@ var Editor = class {
return lineNumber - 1;
};
this.#editorEventDisposes.push(addEventListener(gutterEl, "pointerdown", (e) => {
+ if (this.#fileInstance?.options.enableLineSelection === true) return;
const textDocument = this.#textDocument;
const lineIndex = resolveEditableLine(resolveGutterTarget(e.composedPath()[0]));
if (lineIndex === void 0 || textDocument === void 0) return;
const gutterRow = resolveGutterTarget(e.composedPath()[0]);
if (gutterRow?.dataset.lineType === "change-deletion") {
const code = gutterRow.closest("[data-code]");
diff --git a/dist/react/utils/useFileInstance.js b/dist/react/utils/useFileInstance.js
index cb8e2026fb5d7a19f489c0a2402efbcb7dff3322..510fad6364d4a2214c7dd65fe2b114f1cce0c815 100644
index a616259c7272eb107a80dc1c5f749cbbcf60ba9a..6f801fbdd5ccbfd4d41dabd40829a2d2c542bf6e 100644
--- a/dist/react/utils/useFileInstance.js
+++ b/dist/react/utils/useFileInstance.js
@@ -92,10 +92,7 @@ function mergeFileOptions({ options, controlledSelection, contentEditable, hasCu
@@ -91,10 +91,7 @@ function mergeFileOptions({ options, controlledSelection, contentEditable, hasCu
};
if (needsEditorOptions) merged = {
...merged,
Expand All @@ -45,7 +46,7 @@ index cb8e2026fb5d7a19f489c0a2402efbcb7dff3322..510fad6364d4a2214c7dd65fe2b114f1
return merged;
}
diff --git a/package.json b/package.json
index d1558633de87044b7aa96cff09443db11f163cec..c0b16f0a0bec6fba2026f24f38b2c0a8fa06af7c 100644
index 42be7a415288f3900f6e794a3c6a48c7b236bb8d..8a44e012b21aa10c9891eef5fad8a60cfec904c9 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,18 @@
Expand Down
Loading
Loading