
How I used GitHub Copilot Chat to build a ReactJS gallery prototype
GitHub Copilot Chat can help developers create prototypes, understand code, make UI changes, troubleshoot errors, make code more accessible, and generate unit tests.
The comparison graph is the centerpiece of GitHub Desktop. It both drives the interaction with your branch and shows you the effects of your changes relative to a base branch.…
The comparison graph is the centerpiece of GitHub Desktop. It both drives the interaction with your branch and shows you the effects of your changes relative to a base branch.
It’s easily the most sophisticated piece of user interface in the app, with explanatory animations revealing the effects of commits, syncs, and merges.
With separate code bases for OS X & Windows converging on a single design, we knew that sharing code would be essential going forward. Sophisticated as the graph is, implementing it twice would have been a significant burden.
Cross-platform code isn’t entirely new to us: both code bases use git
& libgit2, for example. Sharing UI in a way that respects the conventions of each platform is a different matter, however.
Fortunately, the comparison graph had been prototyped as a reusable web component within an Electron app. We knew that we could host that implementation within a native web view on OS X, and experiments in Windows proved promising as well.
Here’s how we did it.
Both of the GitHub Desktop implementations reference the comparison graph as a submodule. This brings in the full HTML/CoffeeScript/SASS sources for the comparison graph and the tutorial, as well as the tests and build scripts.
Much of that is unnecessary to running the app, so a build script compiles the sources down to distribution-ready HTML/JavaScript/CSS files which we embed in the apps.
On OS X, we use Apple’s WebView
in the UI to integrate the comparison graph. On Windows, our initial explorations with the .NET WebBrowser
control found it to be unworkable, so we embed CefSharp and use its ChromiumWebBrowser
. While this adds to our download size, both WebView
and ChromiumWebBrowser
are derived from WebKit, which makes it vastly simpler for us to develop and test changes to the comparison graph. In general someone working on the comparison graph on OS X doesn’t have to worry about whether it will work on Windows, and vice versa.
We do have to consider the comparison graph’s API on both platforms, however, since breaking changes there will need to be integrated in both places. Since breaking changes are a result of prior discussion, this is a matter of calling them out in the pull request and making sure that someone familiar with the other platform has had a chance to comment.
We’ve also managed this sort of churn by making a conscious effort to keep the comparison graph API narrow and focused. The general flow is something like this:
The comparison graph can also draw a single branch, or a pull request. These differ somewhat in effect, but the overall flow is very similar.
There are a few additional interactions which the apps need to support:
Since both apps have to implement their side of this flow, they also share the rough architecture surrounding it. Broadly, this involves:
Although the overall flow and architecture are shared, the implementations are often quite different. For example, although both platforms use WebKit, the bridging APIs are very different.
On OS X, the JavaScriptCore APIs allow us to pass arbitrary objects, functions, and arrays between Objective-C/Swift and JavaScript. Native code can call methods on JavaScript objects, and JavaScript code can call methods on native objects, without any special handling. We can also define protocols to specify which properties of our objects should be bridged.
CefSharp, on the other hand, does not bridge arrays or functions. JavaScript objects can call methods on native objects, but not on the properties of native objects. Native objects cannot call methods on JavaScript objects; you ask the ChromiumWebBrowser
to evaluate a string of JavaScript source instead. Since the comparison graph is largely asynchronous, requiring both callbacks and arrays, this motivates a set of JavaScript shims baked into the comparison graph at build time.
On load, the shims set themselves up as the comparison graph’s delegate. When the comparison graph asks for more commits, it passes a callback which the commits should be passed to. The shims add this callback to a private table under a computed key, and call into the app, passing that key. Once the app has loaded the commits, it calls the callback, passing the same key and an array of commits.
shims.getCommitsBefore = function (name, sha, callback) {
callbacks[key(name, sha)] = callback;
window.native.getCommitsBefore(name, sha);
}
Since CefSharp doesn’t bridge arrays, this involves serializing the app’s model objects representing the commits into the JSON representation which the comparison graph expects. Compared to the process on OS X (where arrays of commit objects can be bridged directly), this is slightly less convenient, but has the advantage that the comparison graph and the app are mutually insulated from any changes to state that the other might wish to make.
Browser.RunJsAsync("shims.didGetCommitsBefore("
+ ToJson(name) + ", "
+ ToJson(sha) + ", "
+ ToJson(commits)
+ ")");
Finally, the app calls into the shims, passing the serialized array of commits, and the shims forward the commits to the comparison graph.
shims.didGetCommitsBefore = function (name, sha, commits) {
callbacks[key(name, sha)](commits);
delete callbacks[key(name, sha)];
}
We also employ CSS shims to adjust the comparison graph’s appearance to suit the look and feel of the host platform. For example, on Windows, the buttons have a flat appearance, while on OS X they have a slight gradient. Likewise, text is in Segoe UI on Windows, but Helvetica Neue on OS X. This helps us to ensure that sharing effort between the platform doesn’t result in a lowest common denominator experience.
Beyond the comparison graph, the tutorial, and some shared architecture, most of the implementation remains distinct on both platforms. Perhaps the most significant benefit of the approach we took was that it enabled us to converge GitHub for Mac and GitHub for Windows into GitHub Desktop iteratively, rather than as a ground-up rewrite. That in turn enabled us to keep shipping updates while working on GitHub Desktop without having to divide our attention overmuch.
Having iterated our way here, we’re going to iterate onwards. There’s lots more in store for the comparison graph, and lots more that we’d like to share between the platforms. Here’s to 1.1!