GitHub hat die Oberfläche zur Anzeige von Pull Requests in der GitHub-Copilot-Anwendung neu aufgebaut, um einen Extremfall zu bewältigen: einen Pull Request mit 2.200 Dateien, mehr als einer Million geänderter Zeilen und über 400 Inline-Review-Kommentaren. Ziel war nicht nur, die Anzeige eines großen Diffs zu beschleunigen, sondern auch Scrollen und Überprüfen nutzbar zu halten, nachdem die Unterhaltung selbst dem Dokument variable Dimensionen hinzufügt.
Die herkömmliche Oberfläche für große Diffs setzt auf Lazy Loading, also darauf, nur die nahe am Viewport sichtbaren Elemente beizubehalten und DOM-Elemente während des Scrollens wiederzuverwenden. Dieses Modell funktioniert relativ einfach, wenn jedes Element eine Codezeile mit bekannter Höhe ist. Kommentare besitzen diese Eigenschaft jedoch nicht; ihre Höhe verändert sich abhängig vom Umbruch von Markdown, dem Öffnen von <details>-Abschnitten, dem Erscheinen des Antworteditors, Bildern, vorgeschlagenen Änderungen und verschiedenen Interaktionszuständen.
Zwei Geometrien statt einer Höhentabelle
GitHub behielt eine feste Geometrie für Codezeilen bei, die vorab berechnete Positions- und Höhenwerte verwendet und nicht neu aufgebaut wird, wenn sich ein Kommentar verändert. Kommentare, Antworteditoren und dynamische Blöcke wurden dagegen in einen unabhängigen Index aufgenommen. Jeder Block besitzt einen stabilen Schlüssel, der mit Datei, Zeile und Seite verknüpft ist, sowie einen Fingerabdruck des Inhalts, den Zustand geöffneter Abschnitte und die Breite der letzten Messung.
Die gemessene Höhe wird verwendet, wenn die Messwerte gültig sind, oder ein zwischengespeicherter Wert, solange Fingerabdruck und Breite übereinstimmen. Andernfalls verwendet die Oberfläche eine vorläufige Schätzung. Dadurch führt die Ausdehnung eines einzelnen Kommentars nicht zu einer Neuberechnung der Geometrie von Millionen von Zeilen.
Messung außerhalb des Scrollpfads
GitHub gab ein frühes Design auf, das auf einem separaten ResizeObserver für jeden Block beruhte und die Messung direkt in das Layout schrieb. Dieses Muster kann eine Rückkopplungsschleife erzeugen, weil eine Layoutänderung die Überwachung erneut auslöst. Außerdem steigen seine Kosten mit der Zahl der eingebundenen Blöcke.
Das ausgelieferte Design des Unternehmens verwendet einen einzigen Messzyklus, der an Ruhephasen und das Ende des Scrollens gebunden ist. Üblicherweise werden nur Blöcke innerhalb von etwa 2.400 Pixeln des Viewports gemessen, während weiter entfernte Blöcke ihre Schätzungen behalten, bis sie näher kommen. Die Messwerte sichtbarer Elemente werden gesammelt gelesen, um wiederholte Reflows zu vermeiden.
Die Observer bleiben bestehen, um Änderungen wie das Laden eines Bildes oder das Schreiben im Antworteditor zu erkennen. Sie markieren den Block jedoch lediglich, damit der nächste Messzyklus ihn erneut liest, anstatt die Höhe direkt zu ändern. Eine Ausnahme ist eine vom Benutzer verursachte Änderung in einem sichtbaren Block, etwa das Öffnen eines Abschnitts oder eines Antworteditors. In diesem Fall kann noch im selben Frame eine einzige synchrone Korrektur angewendet werden. So wird verhindert, dass zwischen der Ausdehnung des Kommentars und der Bewegung des darunterliegenden Codes ein sichtbarer Zwischenschritt erscheint.
Layoutkorrektur ohne Verlust der Leseposition
Wenn die tatsächliche Höhe von der Schätzung abweicht, korrigiert die Oberfläche die Position nicht allein anhand der Pixelzahl. Sie bewahrt die Identität des Elements, das der Benutzer gerade liest, unabhängig davon, ob es sich um eine Zeile oder einen Kommentarblock handelt, bestimmt den Versatz innerhalb dieses Elements, wendet anschließend die Höhendifferenzen an und berechnet die Position desselben Elements neu. Dadurch bleibt das fixierte Element ungefähr an derselben Stelle.
Korrekturen werden normalerweise weder während des Scrollens noch während der Scrollbewegung des Benutzers durchgeführt. Änderungen, die aus Inhalten unterhalb des Bildschirms entstehen, werden ebenfalls nicht verwendet, um die Ansicht zu verschieben. Das Team stieß auf einen Fehler, als programmgesteuertes Scrollen infolge einer Änderung der Panelbreite als Benutzerscrollen betrachtet wurde. Dadurch ging die Korrektur verloren und die Position der gelesenen Datei driftete ab. Behoben wurde dies durch die Trennung zwischen Benutzerinteraktion und von der Oberfläche selbst verursachten Änderungen.
Datenfluss und automatisierte Tests
Die Reaktionsfähigkeit beruht nicht allein auf der virtuellen Darstellung. Die Struktur der Diffs und die Dateidaten werden zuerst gesendet, damit der Dateibaum und die Metadaten angezeigt werden können, während der übrige Teil des Dokuments geladen wird. Arbeiten wie Syntaxhervorhebung und der detaillierte Aufbau von Markdown werden zurückgestellt, bis sich die Elemente dem Viewport nähern. Außerdem behält die Oberfläche vorübergehend die letzten wenigen verwendeten Diffs und entfernt alles darüber hinaus, um den Speicherverbrauch zu reduzieren.
Um Fehler aufzudecken, die erst beim tiefen Scrollen sichtbar werden, fügte GitHub dauerhafte Messsignale und automatisierte Tests hinzu, die die Anzahl der Zeilen und eingebundenen Blöcke, die Frame-Zeit, die Größe von Scrollkorrekturen, Observer-Leaks und das Vorhandensein nicht gefüllter Bereiche überwachen. Das Team führte automatisierte Abläufe in der Desktopanwendung und unter der tatsächlichen Rendering-Engine aus, mit Fällen wie dem Öffnen von Abschnitten, dem Aktivieren von Antworteditoren, dem Ändern der Fenstergröße und der Navigation innerhalb einer großen Dateiliste.
Warum ist dieses Design wichtig?
Das erklärte Ergebnis ist, dass sich ein Pull Request mit einer Million Zeilen und Hunderten von Kommentaren wie ein gewöhnlicher Pull Request verhält: Kommentare werden vollständig angezeigt, statt innerhalb einer verschachtelten Bildlaufleiste abgeschnitten zu werden, und das Erweitern eines Abschnitts bewegt den darunterliegenden Code ohne zufällige Sprünge. Außerdem kann zu einem Pull Request zurückgekehrt werden, wobei die Leseposition erhalten bleibt. Der wichtigste technische Wert besteht darin, dass GitHub nicht versucht hat, alle Inhalte gleichartig zu machen. Stattdessen blieb der deterministische Teil schnell und wurde von Inhalten getrennt, deren Größe vor der Darstellung nicht bekannt sein kann.
Die Veröffentlichung behauptet jedoch nicht, dass alle großen Pull Requests hinsichtlich Verständnis oder Risikomanagement leicht zu überprüfen seien. Behandelt wurden die Leistung und das Verhalten der Anzeige während Messungen und Änderungen. Außerdem beziehen sich die Ergebnisse auf die GitHub-Copilot-Anwendung und ihr internes Design und liefern keine unabhängigen Zahlen zum Speicherverbrauch oder zu Leistungsindikatoren auf verschiedenen Geräten und Rendering-Engines.