perf(diff): Pierre diff optimizations (#419)

* perf: implement virtualized rendering for diff viewer

- Enables efficient rendering of large diffs by only rendering visible content
- Uses shared virtualizer cache to optimize memory across multiple diff viewers
- Configures virtual scrolling with 24px line height for consistent layout

* fix: improve diff viewer line selection and annotation rendering

* fix: normalize line ranges to prevent selection bugs

* chore: upgrade @opencode-ai/sdk to v1.1.65
This commit is contained in:
Bohdan Triapitsyn
2026-02-13 19:05:31 +02:00
committed by GitHub
parent 2f09d375d3
commit 523eafdf65
8 changed files with 301 additions and 59 deletions
+2 -2
View File
@@ -37,8 +37,8 @@
"@fontsource/ibm-plex-sans": "^5.1.1",
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
"@opencode-ai/sdk": "^1.1.53",
"@pierre/diffs": "^1.0.5",
"@opencode-ai/sdk": "^1.1.65",
"@pierre/diffs": "1.1.0-beta.13",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -38,24 +38,62 @@ export function InlineCommentInput({
}
}, [lineRange]);
const normalizeRange = (range?: { start: number; end: number; side?: 'additions' | 'deletions' }) => {
if (!range) return undefined;
const start = Math.min(range.start, range.end);
const end = Math.max(range.start, range.end);
return { ...range, start, end };
};
const displayRange = normalizeRange(lineRange);
// Focus on mount (desktop only) or when becoming visible
useEffect(() => {
if (!isMobile && textareaRef.current) {
textareaRef.current.focus();
// Move cursor to end
const len = textareaRef.current.value.length;
textareaRef.current.setSelectionRange(len, len);
} else if (isMobile && textareaRef.current) {
// Scroll into view on mobile
textareaRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
const textarea = textareaRef.current;
if (!textarea) return;
const scrollContainer = textarea.closest('.overlay-scrollbar-container') as HTMLElement | null;
const prevScrollTop = scrollContainer?.scrollTop ?? window.scrollY;
const prevScrollLeft = scrollContainer?.scrollLeft ?? window.scrollX;
if (isMobile) {
textarea.scrollIntoView({ behavior: 'auto', block: 'nearest' });
try {
textarea.focus({ preventScroll: true });
} catch {
textarea.focus();
}
return;
}
try {
textarea.focus({ preventScroll: true });
} catch {
textarea.focus();
}
const len = textarea.value.length;
try {
textarea.setSelectionRange(len, len);
} catch (err) {
void err;
}
requestAnimationFrame(() => {
if (scrollContainer) {
scrollContainer.scrollTop = prevScrollTop;
scrollContainer.scrollLeft = prevScrollLeft;
} else {
window.scrollTo({ top: prevScrollTop, left: prevScrollLeft });
}
});
}, [isMobile]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault();
if (text.trim()) {
onSave(text, stableRangeRef.current);
onSave(text, normalizeRange(stableRangeRef.current));
}
} else if (e.key === 'Escape') {
e.preventDefault();
@@ -67,7 +105,7 @@ export function InlineCommentInput({
// Stop propagation to prevent parent selection clearing before save
e.stopPropagation();
if (text.trim()) {
onSave(text, stableRangeRef.current);
onSave(text, normalizeRange(stableRangeRef.current));
}
};
@@ -90,7 +128,7 @@ export function InlineCommentInput({
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground mb-2">
{fileLabel && <span className="truncate max-w-[200px]">{fileLabel}</span>}
{fileLabel && lineRange && <span>•</span>}
{lineRange && <span>Lines {lineRange.start}-{lineRange.end}</span>}
{displayRange && <span>Lines {displayRange.start}-{displayRange.end}</span>}
</div>
)}
@@ -1082,6 +1082,8 @@ export const DiffView: React.FC = () => {
outerClassName="flex-1 min-h-0 h-full"
className="pr-2"
disableHorizontal
data-diff-virtual-root
data-diff-virtual-content
>
<div className="flex flex-col gap-3">
{changedFiles.map((file, index) => (
@@ -1144,7 +1146,7 @@ export const DiffView: React.FC = () => {
}
return (
<div className="flex flex-1 min-h-0 overflow-hidden px-3 py-3 relative">
<div className="flex flex-1 min-h-0 overflow-hidden px-3 py-3 relative" data-diff-virtual-root data-diff-virtual-content>
{renderSelectedDiffViewer()}
{isCurrentFileLoading && !hasCurrentDiff && (
<div className="absolute inset-0 flex items-center justify-center gap-2 text-sm text-muted-foreground">
@@ -1,6 +1,16 @@
import React, { useMemo, useRef, useState, useCallback, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { FileDiff as PierreFileDiff, type FileContents, type FileDiffOptions, type SelectedLineRange, type DiffLineAnnotation, type AnnotationSide } from '@pierre/diffs';
import {
FileDiff as PierreFileDiff,
VirtualizedFileDiff,
Virtualizer,
type FileContents,
type FileDiffOptions,
type SelectedLineRange,
type DiffLineAnnotation,
type AnnotationSide,
type VirtualFileMetrics,
} from '@pierre/diffs';
import { InlineCommentCard, InlineCommentInput } from '@/components/comments';
import { useOptionalThemeSystem } from '@/contexts/useThemeSystem';
@@ -120,8 +130,10 @@ const extractSelectedCode = (original: string, modified: string, range: Selected
const lines = content.split('\n');
// Ensure bounds
const startLine = Math.max(1, range.start);
const endLine = Math.min(lines.length, range.end);
const from = Math.min(range.start, range.end);
const to = Math.max(range.start, range.end);
const startLine = Math.max(1, from);
const endLine = Math.min(lines.length, to);
if (startLine > endLine) return '';
@@ -138,6 +150,82 @@ type AnnotationData =
| { type: 'saved' | 'edit'; draft: InlineCommentDraft }
| { type: 'new'; selection: SelectedLineRange };
type SharedVirtualizer = {
virtualizer: Virtualizer;
release: () => void;
};
type VirtualizerTarget = {
key: Document | HTMLElement;
root: Document | HTMLElement;
content: HTMLElement | undefined;
};
type VirtualizerEntry = {
virtualizer: Virtualizer;
refs: number;
};
const virtualizerCache = new WeakMap<Document | HTMLElement, VirtualizerEntry>();
const VIRTUAL_METRICS: Partial<VirtualFileMetrics> = {
lineHeight: 24,
hunkSeparatorHeight: 24,
fileGap: 0,
};
function resolveVirtualizerTarget(container: HTMLElement): VirtualizerTarget {
const root = container.closest('[data-diff-virtual-root]');
if (root instanceof HTMLElement) {
const content = root.querySelector('[data-diff-virtual-content]');
return {
key: root,
root,
content: content instanceof HTMLElement ? content : undefined,
};
}
return {
key: document,
root: document,
content: undefined,
};
}
function acquireSharedVirtualizer(container: HTMLElement): SharedVirtualizer | null {
if (typeof document === 'undefined') return null;
const target = resolveVirtualizerTarget(container);
let entry = virtualizerCache.get(target.key);
if (!entry) {
const virtualizer = new Virtualizer();
virtualizer.setup(target.root, target.content);
entry = { virtualizer, refs: 0 };
virtualizerCache.set(target.key, entry);
}
entry.refs += 1;
let released = false;
return {
virtualizer: entry.virtualizer,
release: () => {
if (released) return;
released = true;
const current = virtualizerCache.get(target.key);
if (!current) return;
current.refs -= 1;
if (current.refs > 0) return;
current.virtualizer.cleanUp();
virtualizerCache.delete(target.key);
},
};
}
export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
original,
modified,
@@ -161,46 +249,54 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
const removeDraft = useInlineCommentDraftStore((state) => state.removeDraft);
const allDrafts = useInlineCommentDraftStore((state) => state.drafts);
const currentSessionId = useSessionStore((state) => state.currentSessionId);
const newSessionDraftOpen = useSessionStore((state) => state.newSessionDraft?.open);
const getSessionKey = useCallback(() => {
return currentSessionId ?? (newSessionDraftOpen ? 'draft' : null);
}, [currentSessionId, newSessionDraftOpen]);
return currentSessionId ?? 'draft';
}, [currentSessionId]);
const [selection, setSelection] = useState<SelectedLineRange | null>(null);
const [commentText, setCommentText] = useState('');
const [editingDraftId, setEditingDraftId] = useState<string | null>(null);
const selectionRef = useRef<SelectedLineRange | null>(null);
const editingDraftIdRef = useRef<string | null>(null);
// Use a ref to track if we're currently applying a selection programmatically
// to avoid loop with onLineSelected callback
const isApplyingSelectionRef = useRef(false);
const lastAppliedSelectionRef = useRef<SelectedLineRange | null>(null);
useEffect(() => {
selectionRef.current = selection;
}, [selection]);
useEffect(() => {
editingDraftIdRef.current = editingDraftId;
}, [editingDraftId]);
const handleSelectionChange = useCallback((range: SelectedLineRange | null) => {
// Ignore callbacks while we're programmatically applying selection
if (isApplyingSelectionRef.current) {
return;
}
const prevSelection = selectionRef.current;
// Mobile tap-to-extend: if selection exists and new tap is on same side, extend range
if (isMobile && selection && range && range.side === selection.side) {
const start = Math.min(selection.start, range.start);
const end = Math.max(selection.end, range.end);
if (isMobile && prevSelection && range && range.side === prevSelection.side) {
const start = Math.min(prevSelection.start, range.start);
const end = Math.max(prevSelection.end, range.end);
setSelection({ ...range, start, end });
return;
} else {
setSelection(range);
}
setSelection(range);
// Clear editing state when selection changes user-driven
if (range) {
// Don't clear if we're just updating the selection for the same draft?
// For now, simple behavior: new selection = new comment flow
if (!editingDraftId) {
if (!editingDraftIdRef.current) {
setCommentText('');
}
}
}, [editingDraftId, isMobile, selection]);
}, [isMobile]);
const handleCancelComment = useCallback(() => {
setCommentText('');
@@ -258,6 +354,14 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
const targetRange = rangeOverride ?? selection;
if (!targetRange || !textToSave.trim()) return;
const normalizedStart = Math.min(targetRange.start, targetRange.end);
const normalizedEnd = Math.max(targetRange.start, targetRange.end);
const normalizedRange: SelectedLineRange = {
...targetRange,
start: normalizedStart,
end: normalizedEnd,
};
const sessionKey = getSessionKey();
if (!sessionKey) {
toast.error('Select a session to save comment');
@@ -267,16 +371,16 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
// Pierre selection range: { start, end, side }
// Store needs { startLine, endLine, side: 'original'|'modified' }
// Pierre side: 'additions' (right) | 'deletions' (left)
const storeSide = targetRange.side === 'deletions' ? 'original' : 'modified';
const storeSide = normalizedRange.side === 'deletions' ? 'original' : 'modified';
// Use deterministic code extraction instead of instance.getSelectedText()
const selectedText = extractSelectedCode(original, modified, targetRange);
const selectedText = extractSelectedCode(original, modified, normalizedRange);
if (editingDraftId) {
updateDraft(sessionKey, editingDraftId, {
fileLabel: fileName || 'unknown',
startLine: targetRange.start,
endLine: targetRange.end,
startLine: normalizedRange.start,
endLine: normalizedRange.end,
side: storeSide,
code: selectedText,
language: language,
@@ -287,8 +391,8 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
sessionKey,
source: 'diff',
fileLabel: fileName || 'unknown',
startLine: targetRange.start,
endLine: targetRange.end,
startLine: normalizedRange.start,
endLine: normalizedRange.end,
side: storeSide,
code: selectedText,
language: language,
@@ -325,6 +429,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
const diffRootRef = useRef<HTMLDivElement | null>(null);
const diffContainerRef = useRef<HTMLDivElement | null>(null);
const diffInstanceRef = useRef<PierreFileDiff<unknown> | null>(null);
const sharedVirtualizerRef = useRef<SharedVirtualizer | null>(null);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const workerPool = useWorkerPool();
@@ -417,6 +522,9 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
hunkSeparators: 'line-info' as const,
// Perf: disable intra-line diff (word-level) globally.
lineDiffType: 'none' as const,
maxLineDiffLength: 1000,
maxLineLengthForHighlighting: 1000,
expansionLineCount: 20,
overflow: wrapLines ? ('wrap' as const) : ('scroll' as const),
disableFileHeader: true,
enableLineSelection: true,
@@ -469,7 +577,12 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
return anns;
}, [allDrafts, getSessionKey, fileName, editingDraftId, selection]);
// Imperative render (like upstream OpenCode): avoids `parseDiffFromFile` on main thread.
const lineAnnotationsRef = useRef(lineAnnotations);
useEffect(() => {
lineAnnotationsRef.current = lineAnnotations;
}, [lineAnnotations]);
useEffect(() => {
if (typeof window === 'undefined') return;
@@ -480,9 +593,21 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
// Dispose previous instance
diffInstanceRef.current?.cleanUp();
diffInstanceRef.current = null;
sharedVirtualizerRef.current?.release();
sharedVirtualizerRef.current = null;
container.innerHTML = '';
const instance = new PierreFileDiff(options as unknown as FileDiffOptions<unknown>, workerPool);
const sharedVirtualizer = acquireSharedVirtualizer(container);
sharedVirtualizerRef.current = sharedVirtualizer;
const instance = sharedVirtualizer
? new VirtualizedFileDiff(
options as unknown as FileDiffOptions<unknown>,
sharedVirtualizer.virtualizer,
VIRTUAL_METRICS,
workerPool,
)
: new PierreFileDiff(options as unknown as FileDiffOptions<unknown>, workerPool);
diffInstanceRef.current = instance;
lastAppliedSelectionRef.current = null;
@@ -502,7 +627,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
instance.render({
oldFile,
newFile,
lineAnnotations,
lineAnnotations: lineAnnotationsRef.current,
containerWrapper: container,
});
@@ -514,9 +639,29 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
if (diffInstanceRef.current === instance) {
diffInstanceRef.current = null;
}
sharedVirtualizer?.release();
if (sharedVirtualizer && sharedVirtualizerRef.current === sharedVirtualizer) {
sharedVirtualizerRef.current = null;
}
container.innerHTML = '';
};
}, [diffThemeKey, fileName, language, modified, options, original, workerPool, lineAnnotations]);
}, [diffThemeKey, fileName, language, modified, options, original, workerPool]);
useEffect(() => {
const instance = diffInstanceRef.current;
if (!instance) return;
instance.setLineAnnotations(lineAnnotations);
requestAnimationFrame(() => {
if (diffInstanceRef.current !== instance) return;
try {
instance.rerender();
} catch (err) {
void err;
}
forceUpdate();
});
}, [lineAnnotations]);
useEffect(() => {
const instance = diffInstanceRef.current;
@@ -545,6 +690,64 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
}
}, [selection]);
useEffect(() => {
const container = diffContainerRef.current;
if (!container) return;
let rafId: number | null = null;
let cleanup = () => {};
const setup = () => {
const host = container.querySelector('diffs-container');
const shadowRoot = host?.shadowRoot;
if (!shadowRoot) {
rafId = requestAnimationFrame(setup);
return;
}
const onClickCapture = (event: Event) => {
if (!(event instanceof MouseEvent) || event.button !== 0) return;
if (!(event.target instanceof Element)) return;
const numberCell = event.target.closest('[data-column-number]');
if (!(numberCell instanceof HTMLElement)) return;
const lineRaw = numberCell.getAttribute('data-column-number');
const lineNumber = lineRaw ? parseInt(lineRaw, 10) : NaN;
if (Number.isNaN(lineNumber)) return;
const lineType =
numberCell.closest('[data-line-type]')?.getAttribute('data-line-type')
?? numberCell.getAttribute('data-line-type');
const side: AnnotationSide = lineType === 'change-deletion' ? 'deletions' : 'additions';
handleSelectionChange({
start: lineNumber,
end: lineNumber,
side,
});
event.preventDefault();
event.stopPropagation();
};
shadowRoot.addEventListener('click', onClickCapture, true);
cleanup = () => {
shadowRoot.removeEventListener('click', onClickCapture, true);
};
};
setup();
return () => {
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
cleanup();
};
}, [diffThemeKey, fileName, handleSelectionChange]);
// MutationObserver to trigger re-renders when annotation DOM nodes are added/removed
useEffect(() => {
const container = diffContainerRef.current;
@@ -669,12 +872,13 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
if (layout === 'fill') {
return (
<div className={cn("flex flex-col relative", "size-full")}>
<div className={cn("flex flex-col relative", "size-full")} data-diff-virtual-root>
<div className="flex-1 relative min-h-0">
<ScrollableOverlay
outerClassName="pierre-diff-wrapper size-full"
disableHorizontal={false}
fillContainer={true}
data-diff-virtual-content
>
<div ref={diffRootRef} className="size-full relative">
<div ref={diffContainerRef} className="size-full" />
@@ -696,5 +900,3 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
</div>
);
};
+1 -1
View File
@@ -229,7 +229,7 @@
},
"dependencies": {
"@openchamber/ui": "workspace:*",
"@opencode-ai/sdk": "^1.1.53",
"@opencode-ai/sdk": "^1.1.65",
"adm-zip": "^0.5.16",
"jsonc-parser": "^3.3.1",
"react": "^19.1.1",
+1 -1
View File
@@ -26,7 +26,7 @@
"@fontsource/ibm-plex-sans": "^5.1.1",
"@ibm/plex": "^6.4.1",
"@octokit/rest": "^22.0.1",
"@opencode-ai/sdk": "^1.1.53",
"@opencode-ai/sdk": "^1.1.65",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",