Запись архива

GitHub перестроил просмотр diff в Copilot app ради пул-реквестов на миллион строк

GitHub разделил геометрию строк кода и комментариев, чтобы Copilot app мог открывать огромные пул-реквесты без миллионов DOM-элементов и резких скачков прокрутки.

Просмотр большого пул-реквеста с комментариями в GitHub Copilot app
Просмотр большого пул-реквеста с комментариями в GitHub Copilot app
Изображение из исходного материала

GitHub переработал экран просмотра пул-реквестов в GitHub Copilot app, чтобы интерфейс оставался отзывчивым даже при работе с исключительно крупными изменениями. В опубликованном 23 сентября 2026 года инженерном разборе компания описала тест на открытом пул-реквесте, включавшем 2 200 файлов, более миллиона изменённых строк и свыше 400 встроенных комментариев.

Главная сложность оказалась не в отображении кода как такового, а в совмещении предсказуемых строк diff с комментариями, высота которых становится известна только после отрисовки. GitHub решил не сводить эти элементы к одной модели: строки кода и динамические блоки получили независимые системы расчёта высоты.

Это инженерное изменение важно для пользователей Copilot app, работающих с крупными миграциями, автоматическим обновлением зависимостей и широкими рефакторингами. При этом публикация GitHub — техническое описание собственной реализации, а не независимый тест производительности: компания не приводит сравнительных замеров времени загрузки, потребления памяти или частоты кадров.

Почему обычной виртуализации строк оказалось недостаточно

Браузер не может без серьёзных издержек создать DOM-элемент для каждой из миллиона строк. Поэтому большие списки обычно виртуализируют: приложение монтирует только видимую часть документа и небольшой запас вокруг неё. По данным GitHub, в рассматриваемой реализации одновременно реальными DOM-элементами становятся примерно 100 строк, хотя полоса прокрутки ведёт себя так, словно загружен весь diff.

Для кода такая схема сравнительно проста. Если высота строки известна заранее, приложение может вычислить:

  • полную высоту документа;
  • положение любой строки;
  • диапазон элементов, попадающих в окно просмотра;
  • координату перехода к конкретному месту в diff.

Расчёты выполняются без создания всех строк в DOM. GitHub называет это контрактом «все высоты известны до отрисовки». Он позволяет заранее построить таблицу высот и не пересчитывать её на каждом кадре.

Комментарии этому контракту не соответствуют. Их размер зависит от переноса Markdown-текста, раскрытых секций, формы ответа и изображений, которые могут загрузиться позднее. Даже один и тот же комментарий занимает разную высоту при изменении ширины панели.

Резервирование одинакового пространства под каждый комментарий тоже не решает проблему. Завышенная оценка оставляет пустые области, заниженная приводит к обрезанию содержимого или внутренней прокрутке. Если после отрисовки заменить примерную высоту на фактическую, все элементы ниже сместятся — пользователь увидит скачок страницы.

Две независимые системы геометрии

GitHub разделил общую высоту документа на три составляющие:

  • точную и заранее известную высоту строк кода;
  • сумму эффективных высот динамических блоков;
  • дополнительный запас прокрутки.

К динамическим блокам относятся ветки обсуждений, черновики и формы ответа. Они привязаны не к текущей пиксельной координате, а к стабильной позиции в структуре diff: файлу, строке и стороне изменения. Такая идентификация позволяет сохранить связь между комментарием и кодом после переразметки страницы.

Для каждого блока приложение учитывает отпечаток состояния: содержимое, открытые раскрывающиеся элементы и наличие активной формы ответа. Также сохраняется ширина, при которой выполнялось измерение. Ширину группируют по диапазонам, чтобы небольшое изменение размера окна не обнуляло весь кэш.

При выборе высоты действует последовательность приоритетов. Приложение использует актуальное измерение, если оно уже получено. Затем подходит сохранённое значение с совпадающими состоянием и диапазоном ширины. Если подходящего результата нет, берётся предварительная оценка.

За счёт такого разделения изменение одного комментария не заставляет перестраивать геометрию миллионов строк кода. Размер индекса динамических элементов зависит от числа обсуждений, а не от общего объёма diff. В тестовом пул-реквесте речь шла о сотнях комментариев против более чем миллиона изменённых строк.

Почему GitHub отказался от ResizeObserver для каждого комментария

Первоначально команда рассматривала отдельный `ResizeObserver` для каждого динамического блока. Этот браузерный API предназначен для отслеживания изменения размеров элементов; его поведение и ограничения описаны в документации MDN.

По объяснению GitHub, такая схема создавала нежелательную обратную связь. Наблюдатель фиксирует новый размер, приложение записывает его в модель раскладки, раскладка элемента снова меняется — и наблюдатель может запускаться повторно. Чем больше одновременно смонтировано блоков, тем дороже становится этот цикл.

В финальной реализации измерения объединены в один проход, запуск которого ограничен состоянием прокрутки и периодами простоя. Это позволяет не измерять все комментарии сразу при первом отображении и не выполнять тяжёлую работу непрерывно во время движения страницы. Сам принцип переноса второстепенной работы в свободный период браузера связан с механизмами наподобие `requestIdleCallback`, хотя GitHub описывает собственную дисциплину планирования, а не универсальный рецепт для любого интерфейса.

После уточнения высоты меняется арифметика полосы прокрутки. Чтобы экран не прыгал, коррекция привязывается к идентичности видимого содержимого, а не только к пиксельной позиции. Иными словами, система старается удерживать перед пользователем тот же логический участок diff, даже если выше него комментарий оказался выше или ниже первоначальной оценки.

Изменение ширины остаётся сложным сценарием

Отдельная проблема возникает при изменении ширины области diff. Например, открытие или закрытие дерева файлов оставляет коду больше или меньше горизонтального пространства. Если включён перенос длинных строк, изменение ширины влияет уже не только на комментарии: одна строка кода может занять несколько визуальных строк.

GitHub отмечает, что именно здесь проявилась ошибка в ранней логике. Защита от коррекции во время пользовательской прокрутки ориентировалась на время последнего события scroll, но программное перемещение страницы тоже обновляло этот показатель. В результате изменение боковой панели могло попасть в неверное состояние обработки.

Публикация показывает, почему крупный diff нельзя рассматривать как обычный виртуализированный список. У интерфейса есть по меньшей мере два класса содержимого: элементы с заранее известными размерами и блоки, параметры которых выясняются только после отрисовки. Попытка управлять ими через одну таблицу высот увеличивает объём перерасчётов и риск визуальных сдвигов.

Что проверить пользователям и разработчикам

Пользователям Copilot app стоит оценивать обновлённый просмотр на собственных репозиториях, особенно если рабочий процесс включает монолитные миграции, массовую генерацию кода или пул-реквесты с большим числом обсуждений. В первую очередь полезно проверить переход к конкретному комментарию, прокрутку через границы файлов, раскрытие длинных веток и изменение ширины панели.

Разработчикам виртуализированных интерфейсов разбор GitHub предлагает практический критерий: элементы с непредсказуемой высотой лучше учитывать отдельно от основной детерминированной геометрии. Кэш измерений при этом должен зависеть не только от идентификатора элемента, но и от содержимого, состояния и доступной ширины.

Не следует считать опубликованный пример гарантией одинаковой производительности на любом компьютере или в любом репозитории. GitHub не указал в статье аппаратную конфигурацию теста, браузерные метрики, расход памяти и точную версию приложения. Поэтому заявленный пул-реквест на миллион изменённых строк подтверждает масштаб внутренней проверки, но не заменяет измерение на реальной рабочей нагрузке. Базовый процесс просмотра изменений и обсуждений также можно сопоставить с официальной документацией GitHub по ревью пул-реквестов.

Источники