В работе с крупными PR часто сталкиваюсь с проблемой: браузер зависает, интерфейс тормозит, а код ревью превращается в пытку. Когда в проекте появляются миллионные строки изменений и сотни инлайн-комментариев — рендерить такой дифф становится серьёзной задачей. Недавно GitHub поделился, как они решили эту проблему в своем Copilot app. Мне показалось полезным разобрать их подход и понять, что можно взять в свои проекты.
Проблема: масштаб и интерактивность одновременно
Обычные GitHub PR — это в среднем сотни, максимум несколько тысяч строк. С ними справляются штатные средства. Но что если у тебя миллион строк кода в одном PR? И при этом сотни inline-комментариев, которые сохраняют контекст ревью.
В web-интерфейсе это — потеря интерактивности, огромная нагрузка на DOM, и, как следствие, троттлинг браузера. Особенно когда нужно быстро скроллить, искать, отвечать на ревью — UX начинает страдать.
GitHub Copilot app столкнулся с задачей открыть такой PR и сделать его удобным для пользователя, который хочет одновременно читать, анализировать и править код с помощью AI-помощников.
Новый подход к рендерингу диффа
Вместо классического подхода с рендерингом всего диффа целиком, команда GitHub Copilot app построила потоковую прорисовку и ленивую загрузку частей диффа.
Виртуализация и подгрузка чанков
Идея не нова — виртуализация списка давно применяется в таблицах и списках с большим количеством элементов. Но здесь сложнее:
- Дифф — это не просто список строк, а сложная структура: добавленные/удалённые строки, inline-комментарии, foldable блоки.
- Комментариев сотни, они могут быть разбросаны по всему файлу.
- Нужно поддерживать скролл, переходы по комментариям и быстро переключаться между участками диффа.
Решение — разбить весь миллион строк на небольшие чанки и рендерить только видимые и близкие к видимым чанки. При скролле подгружаются новые части, старые отрисованные удаляются из DOM.
Оптимизация рендеринга комментариев
Комментарии — отдельная головная боль. Их нельзя просто “выкинуть” на время, так как они критичны для ревью.
Copilot app оптимизировал управление комментариями:
- Комментарии сгруппированы по чанкам.
- При загрузке чанка одновременно загружаются только те комментарии, которые относятся к этому участку.
- Для сотен комментариев используется мемоизация и минимизация обновлений, чтобы не перерисовывать один и тот же комментарий несколько раз.
Сложности с foldable блоками и синхронизацией диффа
В больших PR часто встречаются foldable блоки (свернутые участки кода). Управление состоянием этих блоков усложняется на миллионной длине.
В Copilot app сделали:
- Централизованное хранилище состояния раскрытия блоков.
- Lazy loading для скрытых участков — код подгружается только при раскрытии.
- Синхронизация с UI — чтобы быстро менять состояние без лагов.
Пример: простая виртуализация диффа с комментариями
Вот упрощённый пример, как можно реализовать виртуализацию списка строк с комментариями на React:
import React, { useState, useRef, useEffect } from 'react';
interface Line {
number: number;
content: string;
comment?: string;
}
interface Props {
lines: Line[];
chunkSize?: number;
height: number;
lineHeight: number;
}
export function VirtualDiff({ lines, chunkSize = 100, height, lineHeight }: Props) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const totalHeight = lines.length * lineHeight;
const visibleCount = Math.ceil(height / lineHeight);
const startIndex = Math.max(0, Math.floor(scrollTop / lineHeight) - chunkSize);
const endIndex = Math.min(lines.length, startIndex + visibleCount + 2 * chunkSize);
const visibleLines = lines.slice(startIndex, endIndex);
const offsetY = startIndex * lineHeight;
function onScroll(e: React.UIEvent<HTMLDivElement>) {
setScrollTop(e.currentTarget.scrollTop);
}
return (
<div ref={containerRef} onScroll={onScroll} style={{ height, overflowY: 'auto', fontFamily: 'monospace' }}>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)`, position: 'absolute', top: 0, left: 0, right: 0 }}>
{visibleLines.map(line => (
<div key={line.number} style={{ height: lineHeight, borderBottom: '1px solid #ddd', display: 'flex' }}>
<div style={{ width: 50, backgroundColor: '#f7f7f7', textAlign: 'right', paddingRight: 8 }}>
{line.number}
</div>
<pre style={{ margin: 0, flex: 1 }}>{line.content}</pre>
{line.comment && (
<div style={{ marginLeft: 10, backgroundColor: '#ffe', padding: '0 5px', borderRadius: 3 }}>
{line.comment}
</div>
)}
</div>
))}
</div>
</div>
</div>
);
}
Этот компонент не решит все проблемы GitHub Copilot app, но наглядно показывает, как можно подгружать и рендерить только видимую часть диффа с комментариями.
Что важнее — UX или скорость?
Вся эта история с Copilot app — пример tradeoff между производительностью и удобством работы.
Можно отдать предпочтение скорости — показать только часть кода и прятать всё остальное. Но тогда теряется контекст и ревью становится хуже.
Можно грузить всё — но тогда интерфейс будет виснуть и тормозить.
GitHub попытался сбалансировать: виртуализация + ленивый рендер + оптимизация комментариев + сохранение интерактивности. Это похоже на vibe coding в большом масштабе — подстройка под состояние пользователя и контекст, чтобы UI не мешал, а помогал.
Где это пригодится тебе?
Если ты работаешь с большими кодовыми базами и сталкиваешься с громоздкими диффами, задумайся о подобных подходах:
- Виртуализация и lazy loading — must have для гигантских списков.
- Оптимизация интерактивных элементов (комментарии, foldable блоки) — чтобы не тупить UI.
- Синхронизация состояния с минимальными перерисовками.
Если в твоём стеке есть AI ассистенты (например, Copilot, Claude Code) — важно, чтобы UI не тормозил и быстро реагировал на запросы и подсказки.
В агентных workflow (agentic development) с несколькими AI агентами и большим количеством контента оптимальный рендеринг — одна из базовых задач.
Что попробовать дальше?
- Поэкспериментируй с виртуализацией в React — React Window, React Virtualized, но добавь свои оптимизации под сложную структуру диффа.
- Посмотри, как реализовать memo и селекторы для комментариев, чтобы не рендерить весь список при каждом обновлении.
- Пробуй интегрировать AI-помощников так, чтобы UI не блокировался при их ответах.
- Если работаешь с RAG или MCP (retrieval augmented generation, multi-context processing) — оптимизируй UI для работы с большими объемами информации.
GitHub Copilot app показал, что даже с миллионом строк в одном PR можно сделать удобный и отзывчивый интерфейс. Главное — не пытаться рендерить всё сразу, а работать с данными потоково и адаптивно.
В итоге, если хочешь держать vibe в ревью и не перегружать браузер — начинай с виртуализации и оптимизации комментариев. А дальше уже можно пробовать agentic workflow и AI-интеграции, без страха потерять UX.
Попробуй сам: Cursor — AI-редактор для разработчиков.