Keep large diff reviews responsive (#3422)

* perf(app): bound large diff row mounting

Keep the existing vertical and horizontal scroll owners so virtualization does not change gesture or scrolling behavior. Reserve exact offscreen row heights while mounting only the visible window, and retain the full renderer when wrapping or editing reviews requires dynamic geometry.

* perf(app): keep large diff reviews responsive

Opening an inline editor changed the whole review action object, reconciled large syntax-token trees, and disabled row virtualization. Keep the expensive token subtree stable and preserve deterministic row geometry while the editor is open.\n\nBound server-side highlighting for pathological single lines and remove the unused client highlighter so the actual diff pipeline owns the limit.

* test(app): check mounted diff typography

Row virtualization can unmount a fixed diff row after scrolling. Assert the configured font size against a currently mounted row so the test continues to verify rendered typography without depending on window position.

* test(app): stabilize Mermaid streaming coverage
This commit is contained in:
Mohamed Boudra
2026-08-16 15:38:12 +08:00
committed by GitHub
parent 4748aad103
commit 7f8a485a77
15 changed files with 755 additions and 930 deletions
+14 -7
View File
@@ -270,7 +270,16 @@ describe("git diff inline review helpers", () => {
expect(rowState?.left).toBeNull();
expect(rowState?.right?.comments).toEqual([rightComment]);
expect(rowState?.height).toBe(210);
expect(rowState?.height).toBe(226);
});
it("includes thread padding in the inline editor height", () => {
const reviewTarget = target();
const actions = buildReviewActions({
editor: { target: reviewTarget, commentId: null, body: "" },
});
expect(getInlineReviewThreadState({ reviewTarget, reviewActions: actions })?.height).toBe(148);
});
it("pins no-wrap review threads to the visible diff viewport", () => {
@@ -412,13 +421,13 @@ describe("InlineReviewEditor", () => {
expect(onSave).toHaveBeenCalledWith("ready");
});
it("shows shared shortcut hints while focused on a fine-pointer screen", () => {
it("does not show shortcut hints in the action buttons", () => {
window.matchMedia = vi.fn().mockReturnValue({
matches: true,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
});
const { getByTestId, getByText, queryByText } = render(
const { getByTestId, queryByText } = render(
<InlineReviewEditor
initialBody="ready"
onCancel={vi.fn()}
@@ -428,11 +437,9 @@ describe("InlineReviewEditor", () => {
);
const input = getByTestId("editor-input");
expect(getByText("Esc")).toBeTruthy();
expect(getByText(/(?:⌘⏎|Ctrl\+⏎)/)).toBeTruthy();
fireEvent.blur(input);
fireEvent.focus(input);
expect(queryByText("Esc")).toBeNull();
expect(queryByText(/(?:⌘⏎|Ctrl\+⏎)/)).toBeNull();
});
});
+4 -19
View File
@@ -14,12 +14,9 @@ import {
} from "react-native";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import { Button } from "@/components/ui/button";
import { Shortcut } from "@/components/ui/shortcut";
import { isWeb } from "@/constants/platform";
import { useHasFinePointer } from "@/hooks/use-fine-pointer";
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
import type { Theme } from "@/styles/theme";
import type { ShortcutKey } from "@/utils/format-shortcut";
import { useWorkspaceFocusRestoration } from "@/workspace/focus";
import { useReviewDraftComments, useReviewDraftStore, type ReviewDraftComment } from "./store";
import { buildReviewableDiffTargetKey, type ReviewableDiffTarget } from "@/utils/diff-layout";
@@ -50,9 +47,8 @@ function getWebTextInputElement(input: TextInput | null): HTMLElement | null {
export const INLINE_REVIEW_COMMENT_HEIGHT = 72;
export const INLINE_REVIEW_EDITOR_HEIGHT = 132;
const INLINE_REVIEW_GAP = 6;
const INLINE_REVIEW_VERTICAL_PADDING = 8;
export const SMALL_ACTION_HIT_SLOP = 8;
const REVIEW_CANCEL_SHORTCUT_KEYS: ShortcutKey[] = ["Esc"];
const REVIEW_SAVE_SHORTCUT_KEYS: ShortcutKey[] = ["mod", "Enter"];
const foregroundMutedIconColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
const destructiveIconColorMapping = (theme: Theme) => ({ color: theme.colors.destructive });
const accentForegroundIconColorMapping = (theme: Theme) => ({
@@ -222,7 +218,8 @@ export function getInlineReviewThreadState(input: {
const height =
visibleCommentCount * INLINE_REVIEW_COMMENT_HEIGHT +
editorCount * INLINE_REVIEW_EDITOR_HEIGHT +
Math.max(0, visibleBlockCount - 1) * INLINE_REVIEW_GAP;
Math.max(0, visibleBlockCount - 1) * INLINE_REVIEW_GAP +
INLINE_REVIEW_VERTICAL_PADDING * 2;
return { comments, hasEditor, editingCommentId, height };
}
@@ -507,12 +504,10 @@ export function InlineReviewEditor({
const { t } = useTranslation();
const inputRef = useRef<TextInput | null>(null);
const focus = useWorkspaceFocusRestoration();
const canShowKeyboardHints = useHasFinePointer();
const [body, setBody] = useState(initialBody);
const [isFocused, setIsFocused] = useState(false);
const trimmedBody = body.trim();
const canSave = trimmedBody.length > 0;
const showKeyboardHints = isFocused && canShowKeyboardHints;
useEffect(() => {
inputRef.current?.focus();
@@ -527,14 +522,6 @@ export function InlineReviewEditor({
focus.restore();
}, [focus]);
const handleSave = useCallback(() => onSave(trimmedBody), [onSave, trimmedBody]);
const cancelShortcut = useMemo(
() => (showKeyboardHints ? <Shortcut keys={REVIEW_CANCEL_SHORTCUT_KEYS} /> : null),
[showKeyboardHints],
);
const saveShortcut = useMemo(
() => (showKeyboardHints ? <Shortcut keys={REVIEW_SAVE_SHORTCUT_KEYS} /> : null),
[showKeyboardHints],
);
useEffect(() => {
const element = getWebTextInputElement(inputRef.current);
@@ -598,7 +585,6 @@ export function InlineReviewEditor({
onPress={onCancel}
variant="ghost"
size="xs"
trailing={cancelShortcut}
>
{t("review.comment.cancel")}
</Button>
@@ -610,7 +596,6 @@ export function InlineReviewEditor({
onPress={handleSave}
variant="default"
size="xs"
trailing={saveShortcut}
>
{t("review.comment.save")}
</Button>
@@ -658,7 +643,7 @@ const styles = StyleSheet.create((theme) => ({
flex: 1,
minWidth: 0,
gap: INLINE_REVIEW_GAP,
paddingVertical: theme.spacing[2],
paddingVertical: INLINE_REVIEW_VERTICAL_PADDING,
paddingHorizontal: theme.spacing[3],
},
commentBlock: {