GitHub перестроила интерфейс отображения запросов на слияние в приложении GitHub Copilot для обработки крайнего случая: запроса на слияние, включающего 2 200 файлов, более миллиона изменённых строк и свыше 400 встроенных комментариев к проверке. Цель заключалась не только в ускорении отображения большого различия, но и в сохранении удобства прокрутки и проверки после того, как сама переписка добавляет документу переменные измерения.
Традиционный интерфейс для больших различий опирается на виртуализацию, то есть сохраняет рядом с областью просмотра только видимые элементы и повторно использует элементы DOM во время прокрутки. Эта модель относительно легко работает, когда каждый элемент является строкой кода известной высоты. Однако комментарии не обладают этим свойством: их высота меняется в зависимости от переноса Markdown, открытия разделов <details>, появления редактора ответа, изображений, предлагаемых изменений и различных состояний взаимодействия.
Две геометрии вместо одной таблицы высот
GitHub сохранила фиксированную геометрию строк кода, используя предварительные расчёты позиций и высот и не перестраивая их при изменении комментария. В то же время комментарии, редакторы ответов и динамические блоки были помещены в отдельный индекс. Каждый блок получает стабильный ключ, связанный с файлом, строкой и стороной, а также отпечаток содержимого, состояние открытых разделов и последнее измеренное значение ширины.
Измеренная высота используется, когда измерения действительны, либо временно сохранённое значение, пока отпечаток и ширина остаются совместимыми; в противном случае интерфейс использует временную оценку. Благодаря этому растягивание одного комментария не приводит к пересчёту геометрии миллионов строк.
Измерение вне пути прокрутки
GitHub отказалась от первоначального дизайна, который использовал отдельный ResizeObserver для каждого блока и записывал измерение непосредственно в разметку. Такой подход может создать петлю обратной связи, поскольку изменение разметки повторно запускает наблюдение, а его стоимость растёт вместе с числом смонтированных блоков.
В поставленном компанией дизайне используется один цикл измерений, связанный с периодами бездействия и моментом после завершения прокрутки. Обычно измеряются только блоки, находящиеся примерно в пределах 2 400 пикселей от области просмотра, а удалённые блоки сохраняют свои оценки, пока не приблизятся. Измерения видимых элементов считываются одной пакетной операцией, чтобы избежать повторных перерасчётов потока.
Наблюдатели по-прежнему существуют для отслеживания таких изменений, как загрузка изображения или ввод текста в редактор ответа, но они помечают блок, чтобы следующий цикл измерений считал его заново, вместо непосредственного изменения высоты. Исключением является вызванное пользователем изменение в видимом блоке, например открытие раздела или редактора ответа; тогда в том же кадре можно применить одну синхронную корректировку, предотвращающую появление заметного шага между растягиванием комментария и перемещением расположенного под ним кода.
Корректировка разметки без потери положения чтения
Когда фактическая высота отличается от оценки, интерфейс корректирует положение не только на основе количества пикселей. Он сохраняет идентификатор элемента, который читает пользователь, будь то строка или блок комментария, определяет смещение внутри него, затем применяет разницу высот и заново рассчитывает положение того же элемента. Таким образом, закреплённый элемент остаётся примерно на том же месте.
Обычно корректировки не выполняются во время прокрутки пользователя или по инерции, а изменения, вызванные содержимым ниже экрана, не используются для перемещения области просмотра. Команда столкнулась с ошибкой, когда программная прокрутка, вызванная изменением ширины панели, была принята за прокрутку пользователя, что приводило к потере корректировки и смещению просматриваемого файла. Это исправили, разделив взаимодействие пользователя и изменения, вызываемые самим интерфейсом.
Конвейер данных и автоматизированные тесты
Отзывчивость не зависит только от виртуализации. Сначала передаются структура различий и данные файлов, чтобы дерево файлов и метаданные отображались во время загрузки остальной части документа, а такие операции, как подсветка синтаксиса и построение подробного Markdown, откладываются до приближения элементов к области просмотра. Интерфейс также временно сохраняет несколько последних использованных различий, удаляя остальные для снижения потребления памяти.
Чтобы выявлять дефекты, проявляющиеся только при глубокой прокрутке, GitHub добавила постоянные сигналы измерений и автоматизированные тесты, отслеживающие количество строк и смонтированных блоков, время кадра, величину корректировок прокрутки, утечки наблюдателей и наличие незаполненных областей. Команда запускала автоматизированные сценарии в настольном приложении и на фактическом движке отображения, включая такие случаи, как открытие разделов, запуск редакторов ответов, изменение размера окна и навигация внутри большого списка файлов.
Почему этот дизайн важен?
Заявленный результат заключается в том, что запрос на слияние с миллионом строк и сотнями комментариев ведёт себя как обычный запрос: комментарии отображаются полностью, а не обрезаются внутри вложенной полосы прокрутки; раскрытие раздела перемещает расположенный под ним код без случайных скачков; кроме того, при возвращении к запросу на слияние сохраняется положение чтения. Наиболее важный инженерный вывод состоит в том, что GitHub не пыталась сделать всё содержимое однородным: она сохранила быстроту детерминированной части и отделила её от содержимого, размер которого невозможно узнать до отображения.
При этом в материале не утверждается, что все большие запросы на слияние стали простыми для проверки с точки зрения понимания или управления рисками. Исправлены производительность и поведение интерфейса отображения во время измерений и изменений. Кроме того, результаты связаны с приложением GitHub Copilot и его внутренним дизайном и не содержат независимых данных о потреблении памяти или показателях производительности на разных устройствах и движках.