Projekt

gbar bauen: GitHub-Menüleisten-App für macOS

Ich hatte genug davon, GitHub ständig für Pull Requests, Reviews und CI zu öffnen. Ich wollte gespeicherte Suchen, das volle Postfach, mehrere Accounts, Enterprise und schnelle Aktionen in einem nativen macOS-Begleiter ohne eigenes Backend.

9 Min. Lesezeit15.07.2026Justin LanfermannQuellcodeLiveDownload
Das gbar-App-Icon neben dem Text GitHub mit einem Blick auf einem dunklen violetten Hintergrund

GitHub hat ein besonderes Talent dafür, dass ich denselben Browser-Tab immer wieder öffne. Ein Pull Request wartet auf ein Review, ein Check wird rot, jemand erwähnt mich oder ein Repository veröffentlicht eine neue Version. Nichts davon verdient den ganzen Tag meine volle Aufmerksamkeit. Trotzdem unterbricht jeder prüfende Blick das, woran ich gerade arbeite. E-Mails kommen spät und mischen das wichtige Signal mit allem anderen. Ein angepinnter Tab kennt den kompletten Zustand, aber erst, nachdem ich ihn aktiv aufgesucht habe.

PullBar zeigte mir, wie nützlich diese dauerhaft sichtbare Fläche sein kann, konzentriert sich aber bewusst auf Pull Requests. Mein Arbeitsalltag auf GitHub hört dort nicht auf. Dazu gehören auch Issues, das volle Benachrichtigungs-Postfach, Workflow-Läufe, Releases, gespeicherte Suchen, mehrere Accounts, Enterprise und die kleinen Aktionen, die eine Warteschlange leeren. Aus dieser Lücke entstand gbar, eine native GitHub-Menüleisten-App für macOS, die den wichtigen Zustand mit einem Blick zeigt und die eigentliche Arbeit auf GitHub lässt.

Eine allgemeine GitHub-Leiste, kein winziges GitHub

Der Name ist gleichzeitig das Produkt-Briefing. gbar soll eine allgemeine GitHub-Leiste sein, kein vollständiger Desktop-Client, der in 400 Pixel gequetscht wurde. Die App soll zwei Fragen schnell beantworten: Wo wartet Arbeit auf mich, und was kann ich erledigen, ohne ein weiteres Fenster zu öffnen? Daraus entsteht eine bewusst breite, aber flache Oberfläche:

  • Pull Requests und Issues. Beide zeigen Alter, Autor, Repository und den Grund, aus dem sie Aufmerksamkeit brauchen. Pull Requests ergänzen Freigaben und Diff-Größe.
  • Checks und schnelle Aktionen. Jeder PR zeigt seine einzelnen CI-Checks sowie Aktionen zum Freigeben, Mergen und Öffnen im Browser, sobald der aktuelle Zustand sie erlaubt.
  • Ein echtes Benachrichtigungs-Postfach. Benachrichtigungen lassen sich nach ihrem Grund filtern und einzeln oder gesammelt als gelesen markieren.
  • Watchlists und gespeicherte Abfragen. Ausgewählte Repositories speisen die Tabs für Actions und Releases. Jede GitHub-Suchabfrage für Pull Requests oder Issues kann außerdem zu einem eigenen Bereich werden.
  • Mehr als ein GitHub. Mehrere Accounts, Organisationen und GitHub-Enterprise-Instanzen teilen sich ein Menü, ohne dass die Herkunft eines Eintrags verschwindet.

Das Menüleisten-Badge ist die kleinste Fassung dieser Idee. Es kann die Anzahl der PRs zeigen, die auf mein Review warten, ohne irgendetwas zu öffnen. Ein Klick erweitert den Kontext. Freigeben öffnet einen Editor im Menü, Mergen zuerst die Auswahl der Methode. Erst danach wird die Aktion gesendet. Muss ich eine Diskussion lesen, einen Diff prüfen oder einen Workflow debuggen, tritt gbar zur Seite und öffnet die passende GitHub-Seite. Diese Grenze ist Absicht.

Damit bleibt das Produkt ehrlich. Das Repository und die App sind source-available, aber gbar versucht weder GitHubs gesamte Oberfläche nachzubauen noch einen zweiten Ort zu schaffen, an dem Arbeit lebt. Es ist eine Statusfläche mit wenigen präzisen Aktionen. Das beste Ergebnis ist oft, dass ich zwei Sekunden hinschaue und gar nichts tue.

Die Menüleiste wehrte sich

Die erste Version nutzte den naheliegenden Stack: Swift 6, SwiftUI, macOS 14, Tuist und MenuBarExtra. Die App läuft als LSUIElement-Agent, also ohne Dock-Icon und ohne normales Hauptfenster. Mit SwiftUI ließen sich Zeilen, Tabs, Filter und Einstellungen angenehm bauen. Dann stieß ich auf die Eigenheiten einer echten Menüleisten-App. Ich wollte ein Kontextmenü zum Beenden, exakte Kontrolle über das Schließen und ein Popover, das auf dem Bildschirm erscheint, dessen Menüleiste ich tatsächlich angeklickt hatte.

Die aktuelle Implementierung besitzt deshalb ein AppKit-NSStatusItem und ein NSPopover und bettet darin das SwiftUI-Menü ein. Ein Linksklick schaltet das Popover um, ein Rechtsklick öffnet ein temporäres Menü. Globale und lokale Event-Monitore schließen es beim Wegklicken. Ein Beobachter für den Aktivitätswechsel der App fängt Wechsel per Command-Tab oder Spaces ab. Das ist mehr Code als MenuBarExtra, aber das Verhalten ist deterministisch statt nur fast richtig.

Die Einstellungen brauchten eine weitere Brücke. Ein Menüleisten-Agent besitzt kein gewöhnliches Fenster, aus dem SwiftUI openWindow auflösen könnte. Deshalb hält gbar eine versteckte Ein-Pixel-Szene als Öffner am Leben. Beim Anzeigen der Einstellungen wird die App kurz zu einer regulären Anwendung hochgestuft und nach dem Schließen des letzten sichtbaren Fensters wieder zum Accessory. Diese Mechanik soll niemand bemerken. Wenn sie funktioniert, fühlt sich gbar einfach so an, als gehöre es in die Menüleiste.

GitHub-Zustand ist keine Liste

Ein Suchergebnis abzurufen ist einfach. Fünfzig Pull Requests aktuell zu halten, ohne das API-Budget zu verschwenden, ist es nicht. gbar führt zunächst die GitHub-Suchabfragen der aktivierten Bereiche aus. Pull Requests brauchen danach mehr Details: Reviews, Merge-Regeln, den Head-Commit und seine einzelnen Check-Läufe. Eine naive REST-Implementierung macht daraus bei jedem Poll mehrere Anfragen pro Zeile. Genau dieser unsichtbare Fan-out funktioniert in der Entwicklung und bricht bei einem vollen Account auseinander.

Der aktuelle Client bündelt die PR-Anreicherung über GitHub GraphQL. Sein Query Builder enthält per Alias bis zu 25 Repository- und PR-Knoten in einer Anfrage, lädt Reviews sowie Check-Rollups und übersetzt die Antwort zurück in dieselben Modelle, die bereits der REST-Pfad verwendet. Entscheidend ist der Fallback: Fehlt einem Enterprise-Server ein angefragtes GraphQL-Feld oder lässt sich ein Knoten nicht auflösen, wechselt gbar für den Account oder PR zurück zu REST, statt das komplette Menü scheitern zu lassen.

swift
1static let graphQLBatchSize = 25
2
3for chunk in refs.chunked(into: Self.graphQLBatchSize) {
4 let payload = GitHubGraphQL.batchQuery(for: chunk)
5 let request = try makeGraphQLRequest(url: endpoint, body: payload)
6 let data = try await execute(request)
7 let bundles = try GitHubGraphQL.decodeBatch(data, for: chunk)
8 result.merge(bundles) { _, new in new }
9}

Auch Polling braucht Zurückhaltung. Ein 16 MiB großer, rein flüchtiger URL-Cache validiert unveränderte REST-Antworten mit ETags. Unveränderte Daten können dadurch als 304 Not Modified zurückkehren, ohne das normale GitHub-Rate-Limit zu belasten. Nichts aus privaten Repositories wird auf die Festplatte geschrieben. Meldet GitHub ein primäres oder sekundäres Rate-Limit, wartet der nächste Poll bis zur angegebenen Reset- oder Retry-Zeit, statt dieselbe API im gewählten Takt weiter abzufragen.

Im REST-Fallback merkt sich der Store außerdem, welchen PR-Zustand er bereits angereichert hat. Hat sich GitHubs Suchzeitstempel nicht geändert, kann die nächste Welle nur die Checks aktualisieren und Gate sowie Head-Commit wiederverwenden. Merge-fähige PRs sind bewusst von dieser Abkürzung ausgenommen. Eine Änderung am Basis-Branch kann sie veralten lassen, ohne den PR selbst zu aktualisieren. Die Optimierung ist nur dann nützlich, wenn der Merge-Button weiterhin die Wahrheit sagt.

Der Button muss die Wahrheit sagen

Freigeben und Mergen sind kleine Buttons mit großen Folgen. gbar leitet ein Aktions-Gate aus PR-Zustand, letztem Review des Nutzers, Repository-Rechten, erlaubten Merge-Strategien und GitHubs Mergeability-Ergebnis ab. Nur eindeutig ungültige Aktionen verschwinden; fehlende oder noch nicht geladene Signale bleiben verfügbar, bis GitHub eine klare Antwort liefert. Der unangenehme Fall beginnt nach einer Freigabe: GitHub berechnet Branch-Protection asynchron neu. Der erste erneute Abruf kann deshalb weiterhin blocked melden, obwohl genau diese Freigabe die letzte Anforderung erfüllt hat.

Statt einen vollständigen Refresh zu erzwingen oder das Menü neu öffnen zu lassen, startet gbar im Hintergrund einen kurzen Readiness-Poll mit begrenztem Backoff. Rund achtzehn Sekunden lang wird das Gate erneut geprüft und Merge erscheint, sobald GitHub nachgezogen hat. Der PR bleibt dabei sichtbar, selbst wenn er nach der Freigabe aus einer Suche nach angefragten Reviews verschwinden würde. So lässt sich der komplette Review-und-Merge-Ablauf dort beenden, wo er begonnen hat.

Dieser Poll legte einen interessanteren Fehler frei: Eine ältere vollständige Hydration konnte nach dem gezielten Poll fertig werden und dessen neueres Gate überschreiben. Die Lösung ist eine Write-Clock zum Zeitpunkt der Anfrage. Jeder Gate-Abruf erhält vor dem Netzwerkaufruf eine Sequenznummer. Nur eine neuere Sequenz darf den Live-Zustand ersetzen. Ein Menü, das wie eine Liste aussieht, koordiniert in Wahrheit Suchen, GraphQL-Batches, REST-Fallbacks, CI-Updates und Nutzeraktionen, die alle in anderer Reihenfolge enden können.

Kein Backend, mit Absicht

gbar spricht direkt mit der GitHub API. Der primäre Login nutzt GitHubs OAuth-Device-Flow-Implementierung. Dafür braucht es eine öffentliche Client-ID, aber kein Client-Secret und keinen Callback-Server. Ein Personal Access Token bleibt für selbst gebaute oder ungewöhnliche Setups verfügbar. Die API-Basis-URL kann auf GitHub Enterprise zeigen. Zwischen der App und GitHub existiert kein separater gbar-Account und kein Backend, das eine zweite Kopie der Daten sammelt.

Tokens liegen im macOS-Schlüsselbund. Ein korrekt signierter Build nutzt den Data-Protection-Keychain und eine Access Group. Das verhindert die wiederkehrenden Berechtigungsdialoge, die Ad-hoc-Signaturen nach einem Rebuild auslösen können. Teamlose Source-Builds dürfen dieses Entitlement nicht tragen und fallen deshalb transparent auf den dateibasierten Schlüsselbund zurück. Beim Logout werden beide möglichen Kopien entfernt, damit kein Token aus einem älteren Build unbemerkt bestehen bleibt.

Deshalb ist auch der kostenlose Build keine abgespeckte Edition. Es gibt keine bezahlten Feature-Gates, keine ausgehende Produkttelemetrie und keinen separaten gehosteten gbar-Account, für den man zahlen müsste. Nutzer laden die signierte App, melden sich mit ihrem GitHub-Konto an und verwenden alle Funktionen. Die Architektur bleibt einfacher, weil Datenschutzversprechen und Produktmodell in dieselbe Richtung zeigen.

Auch eine winzige App macht beim Veröffentlichen viel Arbeit

Die App lebt hinter einem 18 Pixel breiten Status-Item. Eine native macOS-App zu verteilen bedeutet trotzdem Signaturidentitäten, Entitlements, Provisioning Profiles, Hardened Runtime, Notarisierung, Stapling, ein DMG und aktualisierbare Installationswege. gbar befördert für ein Release stage nach main, liest die Version aus dem Tuist-Manifest, baut die App, signiert und notarisiert das DMG, lädt einen stabilen Download hoch und aktualisiert den Homebrew-Cask.

  • Keine Drittanbieter-Abhängigkeiten im App-Target. Das Tuist-Target hängt nur von Apples Frameworks ab. GitHub-Client, Store, Schlüsselbund-Schicht, Designsystem und Menüverhalten liegen im Repository.
  • Fünfundzwanzig PRs pro GraphQL-Batch. Die Grenze hält jede Antwort unter GitHubs Node-Limits und reduziert gleichzeitig den REST-Fan-out.
  • Vierzehn GitHub-Releases in sechs Tagen. Release-Einträge von v0.1.0 bis v0.6.0 wurden zwischen dem 1. und 6. Juli 2026 veröffentlicht, während Signierung und die Details des Produkts weiter ausgereift wurden.

Sogar das Icon beeinflusste die CI. gbar verwendet Apples neueres Icon-Composer-Bundle statt eines klassischen Asset Catalogs. Xcode 16 kopierte dieses Bundle nur als rohe Ressource und erzeugte eine App ohne das vorgesehene Icon. Deshalb wechselte der Release-Job auf macOS 26 mit Xcode 26, wo actool das Format versteht. Ein quadratisches Bild kann erstaunlich weit in eine Build-Pipeline hineinreichen.

Das sichtbare Ergebnis ist angenehm langweilig: ein signiertes und notarisiertes DMG aus den GitHub Releases laden oder den Cask mit einem Befehl installieren, und macOS öffnet die App ohne Gatekeeper-Umweg. Bis dahin brauchte es mehrere Release-Fixes für Signierung, Schlüsselbund-Entitlements, Credentials und das gebrandete DMG. Veröffentlichen war nicht bloß der Schritt nach dem Build. Es war ein weiteres Feature.

Source-available mit Absicht

Die gesamte App darf unter der PolyForm Shield License für nicht konkurrierende Zwecke gelesen, gebaut, selbst betrieben, verändert und weitergegeben werden. Damit ist gbar source-available, aber nicht Open Source im Sinne der OSI. Der Unterschied ist bewusst gewählt: Menschen können prüfen, was ihren GitHub-Token berührt, und das Werkzeug anpassen. Sie dürfen denselben Code aber nicht in ein konkurrierendes Produkt oder einen konkurrierenden Dienst verwandeln.

Ich benenne diese Grenze lieber klar, statt das freundlichere Label zu verwenden und die Ausnahme zu verstecken. Bei einem Entwickler-Tool mit Zugriff auf private Repositories zählt Vertrauen mehr als ein uneindeutiges Badge. Der Quellcode zeigt die direkten API-Aufrufe, die Schlüsselbund-Behandlung und die fehlende Telemetrie. Die Lizenz beschreibt, was mit diesem Code erlaubt ist. Beides gehört zum Produktvertrag.

Ein Blick, dann zurück an die Arbeit

gbar macht GitHub nicht kleiner. Die App macht den Teil früher sichtbar, der sich zwischen zwei Besuchen verändert. Reviews, fehlgeschlagene Checks, Erwähnungen und Releases können in der Menüleiste warten, ohne zu einem dauerhaften Browser-Ritual zu werden. Braucht ein Eintrag echte Aufmerksamkeit, führt ein Klick weiterhin zu GitHub mit seinem vollständigen Kontext. Diese Grenze ist keine fehlende Funktion. Sie hält das Menü nützlich.

Es bleiben Kanten. Polling kann nicht so unmittelbar sein wie Webhooks, Enterprise-Installationen unterscheiden sich und jede neue Schnellaktion erzeugt einen weiteren Zustand, der unter Nebenläufigkeit ehrlich bleiben muss. Das sind bessere Probleme als die Behauptung, ein Menü könne eine vollständige Kollaborationsplattform ersetzen. Der Test für jedes Feature bleibt einfach: Spart es einen Kontextwechsel, ohne ein zweites System zu schaffen, das verwaltet werden muss?

bash
1brew install --cask jaylann/tap/gbar

Die App lebt auf gbar.lanfermann.dev. Den vollständigen Quellcode und den Issue-Tracker gibt es im GitHub-Repository, den neuesten signierten Build als direkten DMG-Download oder unter Releases. Nach der Installation sollte gbar den größten Teil seines Lebens genau das tun, was der Titel verspricht: ruhig warten, bis ein Blick genügt.