Zurück zur Übersicht
Flutter, Desktop, Open Source, Architektur

Ein Fehler in macos_ui, mein Fix und neun Monate dependency_overrides

Titelbild

Zwei Zeitspuren auf dunklem Grund: die obere endet an einem Merge-Punkt, die untere mit der Beschriftung dependency_overrides läuft darüber hinaus aus dem Bild, darüber die Zeile „Gemerged ist nicht released“

Das Wichtigste in Kürze

  • Die SMI Testsoftware läuft auf macOS und Windows aus einer Codebasis. Die Oberfläche kommt auf beiden Systemen aus macos_ui, weil Flutter selbst rendert und die Widget-Bibliothek damit keine Vorgabe des Betriebssystems ist.
  • Eine Sidebar übernahm ihre konfigurierte Hintergrundfarbe nicht. Das Upstream-Issue beschrieb eine fehlende Property, die Ursache lag zwei Ebenen tiefer im Code der Bibliothek. Behoben mit einer Typänderung.
  • Der Fix ging als Pull Request upstream, nicht nur in den eigenen Fork. Bis zum Merge beliefert dependency_overrides die App aus dem Fork, mit reproduzierbarem Stand über die Lockdatei und einer dokumentierten Abbruchbedingung.
  • Falsch eingeschätzt habe ich die Dauer: erwartet waren Wochen, es wurden neun Monate. Seit dem 22.08.2026 ist der Fix gemerged, auf pub.dev steht aber weiter 2.2.2.

Zum Beitrag

Veröffentlicht
31. Aug. 2026
Lesezeit
5 Min.
Autor
Ruben Röhner

Der SMI e.V. (Standard Motor Interface) trägt einen herstellerübergreifenden Standard für Antriebe in Rollladen- und Sonnenschutzanlagen. Für die Zertifizierung dieser Antriebe habe ich eine Desktop-Anwendung für macOS und Windows gebaut, in Flutter, aus einer Codebasis. Anforderungen und Arc42-Dokumentation verantwortete die Agentur Vertical Meters, Architektur und Implementierung lagen bei mir. Den ganzen Projektbogen erzählt die Fallstudie zur SMI Testsoftware.

Hier geht es um eine einzige Abhängigkeit: einen Fehler in einer fremden Bibliothek, den Fix dafür und die Entscheidung, eine ausgelieferte Anwendung neun Monate lang aus einem eigenen Fork zu beliefern.


Eine Oberfläche für beide Systeme

Die Oberfläche kommt auf beiden Systemen aus macos_ui. Flutter bringt seine eigene Rendering-Engine mit, die Widget-Bibliothek ist also eine freie Entscheidung und keine Vorgabe des Betriebssystems. Eine Bibliothek für beide Ziele heißt: ein Widget-Baum, jeder Bildschirm einmal gebaut, einmal geändert, einmal geprüft. Die Alternative wäre fluent_ui für den Windows-Zweig gewesen, mit zwei Oberflächen, die dauerhaft parallel gepflegt werden müssten. Für ein MVP, auf dem noch drei Ausbaustufen aufsetzen sollen, wäre das der teurere Weg gewesen. 33 der gut 100 Dart-Dateien importieren macos_ui, MaterialApp kommt im Projekt nicht vor. Nach Betriebssystem unterschieden wird nur in der BLE-Schicht, da dort zwei verschiedene Packages nötig sind.


Wo die Farbe verloren ging

Die Anwendung setzt die Hintergrundfarbe ihrer Sidebar über deren decoration, so wie die API es vorsieht. Auf macOS kam davon nichts an.

Das Upstream-Issue beschrieb den Fall als fehlende Property: Der Melder fand in Doku und Beispielen keinen Weg, die Farbe zu setzen. Ich habe stattdessen den Code der Bibliothek gelesen, zwei Ebenen tiefer, und dort stand der Grund:

DecoratedBox(
  decoration: const BoxDecoration(
    color: Color.fromRGBO(0, 0, 0, 1.0),
    backgroundBlendMode: BlendMode.clear,
  ),

BlendMode.clear trägt keine Farbe auf, sondern löscht die Pixel der Ebene. Das ist beabsichtigt: Unter der Flutter-Ebene sitzt auf macOS eine native NSVisualEffectView, und sie scheint nur durch, wenn Flutter an dieser Stelle nichts setzt. Nur lief das Löschen bedingungslos und nahm die Farbe mit, die eine Ebene darüber korrekt gesetzt worden war.

Diese View war selbst einmal ein Fix, aus einem Pull Request vom Januar 2024 gegen falsche Sidebar-Farben. Sie hat ein Problem gelöst und dabei den Weg zugemauert, über den die API eigentlich funktionieren sollte. Deshalb lese ich bei fremden Bibliotheken den Code und nicht die Änderungsnotizen.

Der Fix ist eine Typänderung. Vorher gab es „keine Farbe“ nicht als Zustand, nur transparent, und transparent ist trotzdem eine gefüllte Fläche:

final Color? sidebarBackgroundColor =
    sidebar?.decoration?.color ?? (kIsWeb ? theme.canvasColor : null);

Erst Color? trennt „diese Farbe setzen“ von „hier nichts setzen“. Darunter bekommt der innere DecoratedBox die decoration des Aufrufers durchgereicht statt der Konstanten.


Warum ein Override und kein Warten

Fünfzehn Minuten nach dem Fix-Commit stand er in der Anwendung, am selben Vormittag ging der Pull Request raus:

dependency_overrides:
  macos_ui:
    git:
      url: https://github.com/rubenroehner/macos_ui.git
      ref: fix/sidebar-background-color

Drei Dinge daran sind bewusst so gewählt.

Der Fix ging upstream, nicht nur in den Fork. Ein privater Fork hätte auch funktioniert, aber ihn hätte niemand geprüft. Das Review des Projekts hat einen Edgecase gefunden, den ich nicht auf dem Schirm hatte: Bei halbtransparenten Farben wurde das Alpha zweimal berücksichtigt. Der Fall ist behoben und durch einen Regressionstest abgedeckt, den es im Fork nie gegeben hätte.

Der Verweis zeigt auf einen Branch, nicht auf einen Commit. Damit fließen Nachbesserungen am Fork nach, ohne die pubspec.yaml anzufassen, und welcher Commit tatsächlich gebaut wird, hält die Lockdatei fest (resolved-ref: eebc642…). Builds bleiben also reproduzierbar. Der Preis dafür: Ein pub upgrade verschiebt den Stand, ohne dass sich eine Versionsnummer bewegt. Wer das nicht will, nimmt einen festen Commit und zieht ihn von Hand nach.

Die App liegt bis heute zwei Commits hinter dem Fork, und auch das ist Absicht. Die beiden Nachbesserungen betreffen halbtransparente Farben, die Anwendung übergibt eine deckende. Eine ausgelieferte, signierte Desktop-Anwendung für einen Fall neu zu bauen, den sie nicht auslösen kann, wäre Aufwand und Risiko ohne Gegenwert. Sobald der Fix mit dem nächsten Release erscheint, fällt der Override weg, und die beiden Nachbesserungen kommen mit dem Paket mit.

Zwei Wege habe ich verworfen. Den Code in die Anwendung zu kopieren löst die Abhängigkeit vom fremden Konto und schafft dafür eine Kopie, die niemand mehr nachzieht. Einen Patch beim Installieren gibt es in Dart nicht als etablierten Weg.

Der Override hat einen Preis, und der ist benannt: Jeder Release holt das Paket zur Bauzeit von einem privaten GitHub-Konto. Übergeben ist die Anwendung dagegen mit einer Arc42-Dokumentation, damit der Verein unabhängig vom ursprünglichen Entwicklungsteam bleibt. Genau deshalb steht in dieser Dokumentation auch die Abbruchbedingung: entfernen, sobald die Version mit dem Fix auf pub.dev steht.


Was ich falsch eingeschätzt habe

Eine Sache war keine Abwägung, sondern eine Fehleinschätzung: Ich bin davon ausgegangen, dass ein kleiner, klar begründeter Fix in einem aktiv gepflegten Projekt eine Sache von Wochen ist. Es wurden neun Monate. 119 Tage vergingen, bis überhaupt jemand hineinsah, und inhaltlich geprüft hat den Code in diesen neun Monaten nur der Copilot-Reviewer, den der Maintainer angefordert hatte. Nach einer Nachfrage im Pull-Request am 19.08. wurde er drei Tage später gemerged.

Die Konsequenz ist nicht, keine Fixes mehr einzureichen. Sie ist, eine Übergangslösung so zu bauen, dass sie die lange Variante aushält und nicht die erhoffte. Der Takt eines fremden Release-Zweigs ist keine Zusage, an der sich planen lässt.


Was Coding-Agents daran ändern werden

Dass der einzige inhaltliche Befund an diesem Pull Request von einem Tool kam, ist inzwischen kein Einzelfall, und ich bin gespannt, was Coding-Agents mit Open Source machen.

Die optimistische Lesart: Es entstehen mehr Projekte, kleine Fixes wie dieser werden billiger, und Wartezeiten wie meine neun Monate werden seltener. Meine Sorge liegt in der Asymmetrie dahinter. Einen Pull Request zu schreiben wird durch KI deutlich billiger, ihn zu prüfen bleibt teuer, und geprüft wird von Maintainern, die das meist unbezahlt neben ihrer Arbeit tun. Wenn die Zahl der Beiträge schneller wächst als die Zahl der Menschen, die sie durchsehen, steigt der Druck genau dort, wo ohnehin am wenigsten Kapazität ist.

Ein Code-Review gehört für mich in die Hand eines Menschen. Unterstützen darf KI dabei gerne, und in diesem Fall hat sie es sinnvoll getan und einen echten Edgecase gefunden. Die Entscheidung, ob eine Änderung in eine Library gehört, die tausende Projekte einbinden, gibt man deshalb trotzdem nicht an ein Tool ab.


Heute

Seit dem 22.08.2026 liegt der Fix im Upstream, auf dem Entwicklungszweig. Auf pub.dev steht weiter 2.2.2 vom Oktober 2025, der Override bleibt also, bis 2.2.3 erscheint. Meine Rolle dort ist genau eine: Contributor mit einem gemergten Pull Request.

Die Anwendung liefert auf macOS und Windows aus einer Codebasis, die Sidebar wird auf beiden Systemen korrekt eingefärbt, und der Architekturkern trägt die drei geplanten Ausbaustufen ohne Neubau.


FAQ

Warum nicht auf die veröffentlichte Version warten, statt einen Fork einzubinden?

Weil die Anwendung ausgeliefert werden musste und der Fehler die Navigation betraf, also nichts Kosmetisches am Rand. Zwischen dem Fund und der bis heute ausstehenden Veröffentlichung liegen mehr als neun Monate. Ein Projekt, das darauf wartet, wartet unkalkulierbar lange.

Branch-Verweis oder fester Commit im dependency_overrides?

Beides ist vertretbar, solange die Entscheidung bewusst fällt. Ein Branch nimmt Nachbesserungen automatisch mit, den tatsächlich gebauten Stand hält dann die Lockdatei. Ein fester Commit macht jede Bewegung zu einer sichtbaren Änderung an der pubspec.yaml, kostet dafür einen manuellen Schritt pro Nachbesserung. Was in beiden Fällen dazugehört: der Grund im Commit und die Bedingung, unter der der Eintrag wieder verschwindet.

Was passiert mit dem Override bei einer Übergabe?

Er wandert mit, deshalb steht er in der Arc42-Dokumentation und nicht nur in der pubspec.yaml. Dokumentiert sind der Grund, der verwendete Stand und die Abbruchbedingung: entfernen, sobald die Version mit dem Fix auf pub.dev veröffentlicht ist.

Weiterlesen war's?

Desktop-App aus einer Codebasis?

Ob macOS, Windows oder beides: Ich schaue mir Ihr Vorhaben an und sage Ihnen ehrlich, was eine gemeinsame Codebasis trägt und wo sie Sie etwas kostet.

Antwort in der Regel binnen 48 Stunden.