authorgravatar for 32248437+ShpetimA@users.noreply.github.comShpetim <32248437+ShpetimA@users.noreply.github.com> 2026-09-04 21:37:26+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 12:37:26-07:00
log13427ecd8ba5bfa7892fc90d1e3f0ef59514870f
treeaa1096c8761e88d2166a20eb484ce003e8fddfbc
parentf6db4206258b0ef30e8dd8949627acfd209bf338
signature Signed by PGP key B5690EEEBB952194

fix(web): restore file comment focus in editable preview (#9061)


2 files changed, 55 insertions(+), 38 deletions(-)

apps/web/src/components/diffs/DiffCommentAnnotation.tsx+15-3
......@@ -1,5 +1,5 @@
11import { MessageCircle, Trash2 } from "lucide-react";
2import { useState, type ReactNode } from "react";
2import { useLayoutEffect, useRef, useState, type ReactNode } from "react";
33
44import { Button } from "~/components/ui/button";
55import { Textarea } from "~/components/ui/textarea";
......@@ -25,6 +25,7 @@ interface DiffCommentAnnotationProps {
2525 submitLabel?: string;
2626 pending?: boolean;
2727 secondaryAction?: DiffCommentSecondaryAction;
28 focusOnMount?: boolean;
2829}
2930
3031/** The shared inline comment treatment for file previews, thread diffs, and pull-request diffs. */
......@@ -40,10 +41,20 @@ export function DiffCommentAnnotation({
4041 submitLabel = "Comment",
4142 pending = false,
4243 secondaryAction,
44 focusOnMount = true,
4345}: DiffCommentAnnotationProps) {
4446 const [localDraftText, setLocalDraftText] = useState("");
4547 const displayedText = kind === "draft" && !onTextChange ? localDraftText : text;
4648 const trimmedText = displayedText.trim();
49 const textareaRef = useRef<HTMLTextAreaElement | null>(null);
50
51 useLayoutEffect(() => {
52 if (kind !== "draft" || !focusOnMount) return;
53 const frame = window.requestAnimationFrame(() => {
54 textareaRef.current?.focus({ preventScroll: true });
55 });
56 return () => window.cancelAnimationFrame(frame);
57 }, [focusOnMount, kind]);
4758
4859 if (kind === "comment") {
4960 return (
......@@ -78,9 +89,10 @@ export function DiffCommentAnnotation({
7889 onPointerDown={(event) => event.stopPropagation()}
7990 >
8091 <Textarea
81 autoFocus
92 ref={textareaRef}
93 autoFocus={focusOnMount}
8294 unstyled
83 className="relative inline-flex w-full rounded-md border border-border/50 bg-background/20 font-sans text-foreground transition-colors focus-within:border-border/70 [&_[data-slot=textarea]]:min-h-12 [&_[data-slot=textarea]]:cursor-text [&_[data-slot=textarea]]:px-2.5 [&_[data-slot=textarea]]:py-1.5 [&_[data-slot=textarea]]:font-sans [&_[data-slot=textarea]]:text-xs [&_[data-slot=textarea]]:leading-5 max-sm:[&_[data-slot=textarea]]:min-h-12"
95 className="relative inline-flex w-full rounded-md border border-border/50 bg-background/20 font-sans text-foreground transition-colors focus-within:border-border/70 [&_[data-slot=textarea]]:min-h-12 [&_[data-slot=textarea]]:cursor-text [&_[data-slot=textarea]]:caret-foreground [&_[data-slot=textarea]]:px-2.5 [&_[data-slot=textarea]]:py-1.5 [&_[data-slot=textarea]]:font-sans [&_[data-slot=textarea]]:text-xs [&_[data-slot=textarea]]:leading-5 max-sm:[&_[data-slot=textarea]]:min-h-12"
8496 size="sm"
8597 value={displayedText}
8698 placeholder={placeholder}
apps/web/src/components/files/FilePreviewPanel.tsx+40-35
......@@ -774,42 +774,47 @@ function EditableFileSurface({
774774 ],
775775 );
776776
777 const beginComment = useCallback((range: SelectedLineRange) => {
778 const { startLine, endLine } = normalizeFileCommentRange(range);
779 const draftEntry: FileCommentAnnotationEntry = {
780 id: nextFileCommentId(),
781 kind: "draft",
782 startLine,
783 endLine,
784 text: "",
785 };
786 setLineAnnotations((current) => {
787 const withoutDraft = current.flatMap((annotation) => {
788 const entries = annotation.metadata.entries.filter((entry) => entry.kind !== "draft");
789 return entries.length > 0 ? [{ ...annotation, metadata: { entries } }] : [];
777 const beginComment = useCallback(
778 (range: SelectedLineRange) => {
779 editor.setSelections([]);
780 editor.blur();
781 const { startLine, endLine } = normalizeFileCommentRange(range);
782 const draftEntry: FileCommentAnnotationEntry = {
783 id: nextFileCommentId(),
784 kind: "draft",
785 startLine,
786 endLine,
787 text: "",
788 };
789 setLineAnnotations((current) => {
790 const withoutDraft = current.flatMap((annotation) => {
791 const entries = annotation.metadata.entries.filter((entry) => entry.kind !== "draft");
792 return entries.length > 0 ? [{ ...annotation, metadata: { entries } }] : [];
793 });
794 const existingIndex = withoutDraft.findIndex(
795 (annotation) => annotation.lineNumber === endLine,
796 );
797 if (existingIndex < 0) {
798 return [
799 ...withoutDraft,
800 {
801 lineNumber: endLine,
802 metadata: { entries: [draftEntry] },
803 },
804 ];
805 }
806 return withoutDraft.map((annotation, index) =>
807 index === existingIndex
808 ? {
809 ...annotation,
810 metadata: { entries: [...annotation.metadata.entries, draftEntry] },
811 }
812 : annotation,
813 );
790814 });
791 const existingIndex = withoutDraft.findIndex(
792 (annotation) => annotation.lineNumber === endLine,
793 );
794 if (existingIndex < 0) {
795 return [
796 ...withoutDraft,
797 {
798 lineNumber: endLine,
799 metadata: { entries: [draftEntry] },
800 },
801 ];
802 }
803 return withoutDraft.map((annotation, index) =>
804 index === existingIndex
805 ? {
806 ...annotation,
807 metadata: { entries: [...annotation.metadata.entries, draftEntry] },
808 }
809 : annotation,
810 );
811 });
812 }, []);
815 },
816 [editor],
817 );
813818 const hasOpenCommentForm = lineAnnotations.some((annotation) =>
814819 annotation.metadata.entries.some((entry) => entry.kind === "draft"),
815820 );