Versionierung & Stabilität
Semantic Versioning
Alle @mittwald/flow-*-Packages teilen sich eine gemeinsame Version. Dieselbe
Zusage gilt damit einheitlich für jedes Package. Es gibt keine
Package-spezifischen Sonderregeln.
Seit Version 1.0.0 ist aus einer guten Absicht eine verlässliche Zusage
geworden: Wir halten uns an Semantic Versioning und gehen
Änderungen planbar und nachvollziehbar an.
- Major – enthält Breaking Changes. Ein Update kann Anpassungen in deinem Code erfordern. Major-Versionen bleiben bewusst selten.
- Minor – fügt neue Funktionen hinzu und ist abwärtskompatibel.
- Patch – enthält Bugfixes und ist abwärtskompatibel.
Entscheidend ist die Grenze: Welche Änderung erzwingt eine neue Major-Version, und welche darf in einem Minor oder Patch erscheinen? Die folgenden Abschnitte beschreiben diese Grenze aus Sicht der Entwickler.
Worauf du dich verlassen kannst
Diese Bereiche sind durch die Versionierung geschützt. Eine inkompatible Änderung daran erscheint nur in einer neuen Major-Version:
- Runtime Public API von
public.ts– welche Components und Exports existieren und welche Props sie zur Laufzeit akzeptieren, sowie das dokumentierte Verhalten dokumentierter Funktionen. Die Type-Ebene ist hiervon ausdrücklich ausgenommen (siehe unten). - Components aus
@mittwald/flow-remote-react-components– die in mStudio-Extensions verwendete API. Ihre Props sind der Vertrag mit Extension-Entwicklern und unterliegen derselben Zusage. - Veröffentlichte Icons – ein Icon zu entfernen oder umzubenennen ist ein Breaking Change. Icons werden ohnehin nie entfernt, sondern nur deprecated.
- Das Remote-Protokoll – die versionierte Verbindungsschicht zwischen Extension und Host bleibt kompatibel, solange dies möglich ist.
Wenn ein Teil der öffentlichen API entfernt werden soll, wird er zunächst als deprecated gekennzeichnet und nicht sofort entfernt. Der alte Pfad bleibt erhalten und wird zur Laufzeit über eine Deprecation-Warnung angekündigt, bevor er in einer Major-Version entfernt wird. Das betrifft die gesamte öffentliche API, nicht nur die in Extensions verwendeten Components.
Node- und React-Unterstützung
Node und React sind echte Runtime-Voraussetzungen. Auch hier gibt es klare Regeln, welche Änderung eine neue Major-Version erzwingt.
Node
- Der garantierte Node-Floor ist die aktiv unterstützte Node-LTS, aktuell
node >=24, einheitlich über alle Packages. Er wird nur bei konkretem Bedarf angehoben, nicht um neuen Releases hinterherzulaufen. - Eine Node-Version fallen zu lassen, die noch in ihrem LTS-/Maintenance-Fenster liegt, ist ein Breaking Change (→ Major). Eine bereits End-of-Life-Version fallen zu lassen, darf in einem Minor erscheinen.
- Strenger sind die Node-Runtime-Packages
@mittwald/ext-bridgeund@mittwald/flow-remote-core: Für sie ist jedes Anheben des Node-Floors ein Breaking Change (→ Major), unabhängig von EOL, und ihr Floor kann konservativer sein als der der übrigen Packages.
React
- Das Erweitern der akzeptierten Range (z. B.
^19→^19 || ^20) ist abwärtskompatibel (Minor). - Das Anheben der Mindestversion oder das Fallenlassen einer React-Major ist ein Breaking Change (→ Major).
Was nicht garantiert ist
Damit sich das Design System weiterentwickeln kann, sind die folgenden Bereiche bewusst nicht durch die Versionierung abgedeckt. Sie können sich in jedem Release ändern, auch in einem Minor oder Patch:
- Alle Änderungen auf Type-Ebene (TypeScript). Die Typen sind best-effort und nicht durch Semantic Versioning geschützt. Auch das Entfernen oder Umbenennen eines exportierten Typs oder das Verengen eines Prop-Typs ist für sich genommen kein Breaking Change. Nennenswerte Type-Änderungen werden dennoch im Changelog erwähnt.
- Das visuelle Erscheinungsbild.
- Die interne DOM-Struktur.
- CSS-Klassennamen.
- Namen und Werte von Design Tokens.
So schützt du dich
Weil die oben genannten Bereiche bewusst nicht garantiert sind, gibt es zwei Regeln, an die du dich als Entwickler halten solltest.
Style nicht gegen interne CSS-Klassen
Die CSS-Klassennamen der Components sind ein internes Implementierungsdetail und können sich in jedem Release ändern. Verlasse dich für eigenes Styling nicht darauf, dass eine bestimmte interne Klasse existiert oder gleich heißt. Andernfalls kann bereits ein Patch dein Styling brechen.
Behandle TypeScript-Typen als best-effort
Flows TypeScript-Typen folgen nicht Semantic Versioning. Ein tsc-Fehler kann
daher theoretisch schon in einem Patch auftreten. Wenn ein solcher Bruch für
dein Projekt teuer wäre, pinne exakte Versionen (statt ^-Ranges), sodass ein
Update bewusst und kontrolliert erfolgt. Zu einem bewusst herbeigeführten
Type-Breaking-Change gehört eine Migrationsnotiz in den Release Notes – so
bekommst du einen konkreten Upgrade-Pfad, auch wenn die Änderung in einem Minor
oder Patch erscheint.
Migration bei einer Breaking Change
Jede Änderung, die eine Anpassung in deinem Code erfordert, steht mit Vorher-Nachher-Beispiel im Migrationsleitfaden des betroffenen Packages:
- Migrationsleitfaden
@mittwald/flow-react-components– deckt auch@mittwald/flow-remote-react-componentsab - Migrationsleitfaden
@mittwald/ext-bridge
Die Einträge sind nach Version absteigend sortiert und nennen jeweils die Version, ab der die Änderung greift. Suche die Version, von der du kommst, und arbeite dich nach oben durch.
Nutze die Codemod-CLI
Ein Befehl hebt alle Flow-Abhängigkeiten auf die Zielversion, installiert und führt den Codemod jeder Migration bis zu dieser Version aus:
Ohne Argument geht es auf die nächste Minor innerhalb deiner Major.
upgrade patch bleibt auf deiner Minor, upgrade major überquert eine
Major-Grenze, und eine exakte Version oder ein Dist-Tag (next) gehen genau
dorthin.
Der Befehl ändert Dateien direkt und bricht auf einem unsauberen Git-Stand ab.
Der Befehl kennt keine untere Grenze: er listet auch Migrationen, die vor deiner aktuellen Version erschienen sind. Nichts hält fest, welche davon dein Projekt schon durchgeführt hat – und ein zweiter Durchlauf eines Codemods ändert nichts.
Ein Codemod, der durchgelaufen ist, ist allerdings kein Beweis: jeder Codemod ist bewusst eng gefasst und lässt liegen, was er aus dem Code nicht sicher entscheiden kann – ein gespreadetes Prop, eine Component, die über einen eigenen Wrapper re-exportiert wird, ein dynamischer Zugriff. Einen Fehler meldet er dafür nicht. Prüfe nach einem Upgrade deshalb auch die Migrationen mit Codemod am Code nach, nicht nur die ohne.
Er ersetzt den Migrationsleitfaden nicht: die meisten Einträge haben keinen
Codemod. Welche das für deinen Bereich sind, listet der Befehl am Ende auf –
oder vorab, ohne etwas zu verändern, mit list und derselben Revision:
list ohne Argument zeigt den gesamten Katalog, offline. Mit einer Revision
liest es dieselbe Version aus deinem package.json und zeigt genau den Bereich,
den upgrade mit dieser Revision anfassen würde – ein echter Trockenlauf.
Einen einzelnen Codemod führst du über seine ID aus:
Lass einen Agenten das Upgrade machen
Was die CLI offen lässt, kann ein Coding-Agent übernehmen: die Migrationen ohne
Codemod – das sind die meisten – und die Stellen, die ein Codemod übersprungen
hat. list --json liefert ihm dafür pro Migration das Feld apply: die
Anweisung, was zu ändern ist, knapp genug für einen Katalog und präzise genug
zum Ausführen.
Kopiere den folgenden Prompt in deinen Agenten. Er sucht zuerst, wo Flow
überhaupt deklariert ist – in einem Monorepo ist das meist mehr als ein Package,
und list wie upgrade lesen immer nur die package.json des Verzeichnisses,
in dem sie laufen. Dann klärt er auf, welche Zielversionen zur Wahl stehen und
was jede davon an Arbeit bedeutet, und fragt dich, welche es sein soll. Danach
geht er jede Migration im Bereich am Code nach – auch die mit Codemod – und
ändert nur, wo er das alte Muster wirklich findet.
Was dabei offen bleibt, gibt er dir nicht als Hausaufgabe zurück. Genau die Punkte, die eine Entscheidung brauchen, sind die, bei denen er den Diff gelesen hat und du nicht – deshalb legt er sie dir gesammelt vor, jeweils mit den Optionen und einer Empfehlung, setzt deine Antwort um und lässt die Checks erneut laufen. Das wiederholt sich, bis nichts mehr offen ist. Er hört vorher nur auf, wenn etwas wirklich nicht hier entschieden werden kann – eine Design-Entscheidung, eine Freigabe, ein Zugang – und sagt dann, worauf es wartet und wer es tun muss.
Der Prompt ist englisch, weil alles, worauf er zeigt, englisch ist: die
CLI-Ausgabe, die apply-Felder und der Migrationsleitfaden.
Zwei Dinge, die der Prompt bewusst nicht tut: Er lässt den Agenten nicht auf
einem unsauberen Git-Stand starten, damit ein missglückter Lauf ein
git checkout bleibt. Und er lässt ihn nichts an deinen peerDependencies
ändern – was deine eigenen Consumer installieren dürfen, ist deine Entscheidung.
Optional: erzähl uns, was nicht getragen hat
Ganz unabhängig vom Upgrade: der folgende Prompt ist ein zweiter, eigener Baustein. Du kopierst ihn – wenn du magst – direkt nach dem Upgrade in dieselbe Session. Der Agent hat den Lauf dann noch im Kontext und schreibt daraus einen Bericht auf zwei Ebenen. Nicht kopieren ist die Absage; am Upgrade ändert das nichts.
Der Prompt selbst. Ein Schritt in der falschen Reihenfolge. Eine Annahme, die auf dein Projekt nicht zutrifft. Eine Stelle, an der der Agent raten musste oder zwei Anweisungen sich widersprachen. Eine Situation, zu der der Prompt schweigt. Eine Anweisung, die Arbeit gekostet und nichts geändert hat.
Die einzelne Migration. Ein apply, das ohne Raten nicht ausführbar war.
Eine Stelle, die ein Codemod liegen gelassen hat, obwohl er sie hätte
entscheiden können. Eine Migration ohne Codemod, die mechanisch entscheidbar
wäre. Ein apply, das eine Umbenennung beschreibt, obwohl sich auch die
Signatur geändert hat. Ein Codemod, der abgestürzt ist. Und der wertvollste
Fall: ein Bruch, den deine Checks gefunden haben und für den es gar keinen
Katalogeintrag gibt.
Beide Ebenen sehen wir sonst nicht. Ob eine Migration verständlich beschrieben ist und ob der Upgrade-Prompt trägt, zeigt sich erst an einer echten Codebase – nicht an unserer. Ein Bericht als Issue hilft uns deshalb wirklich weiter: er fließt in den Katalog und in den Prompt zurück, und das nächste Upgrade ist für alle etwas weniger Handarbeit. Ein Paste genügt, und eine Antwort schuldest du niemandem.
Der Agent reicht ihn nicht selbst ein. Er legt ihn dir vor und sagt dir, was du damit tun kannst – du entscheidest, was rausgeht. Code-Beispiele reduziert er auf die kleinste anonymisierte Form, aber prüfen musst du das selbst.
Deprecation-Warnungen sind der Vorlauf
Wird ein Pfad deprecated, bleibt er funktionsfähig und meldet sich zur Laufzeit
per console.warn. Diese Warnungen sind die Vorwarnzeit vor der nächsten
Major-Version – behandle sie als Aufgabenliste, nicht als Rauschen. Um sie
zentral einzusammeln, etwa im Error-Tracking, umschließe deine Anwendung mit
einem DeprecationWarningProvider und gib ihm einen onWarning-Handler:
Direkt nach einem Release
Ein Release veröffentlicht die Packages nacheinander, nicht gleichzeitig.
Für einige Minuten liegt in der npm-Registry deshalb für die schon
veröffentlichten Packages die neue Version, für die restlichen noch die alte.
Beim Release 1.0.6 lagen zwischen dem ersten und dem letzten Package rund 25
Minuten.
Du triffst dieses Fenster leicht, denn ein Update betrifft immer mehrere
Packages: alle @mittwald/flow-*-Packages teilen sich eine gemeinsame Version,
und @mittwald/flow-react-components fordert @mittwald/flow-icons-pro als
Peer-Dependency in exakt derselben Version. Ziehst du in diesem Fenster alle
Flow-Abhängigkeiten auf die neue Version, schlägt die Installation für das
Package fehl, das noch nicht dran war:
pnpm meldet dasselbe als ERR_PNPM_NO_MATCHING_VERSION.
Das ist keine Lücke im Release: sobald der Release-Lauf durch ist, haben alle Packages dieselbe Version. Direkt nach einem Release ist dieser Fehler fast immer das Veröffentlichungsfenster und nichts anderes.
Component Lifecycle
Der Vertrag gilt nicht für jede Component gleich. Jede Component hat einen Lifecycle-Status, der die obigen Regeln überschreibt:
- Beta – von der Breaking-Change-Zusage ausgenommen. Die API kann sich auch außerhalb einer Major-Version ändern.
- Stable (Standard) – vollständig durch die obigen Regeln gebunden.
- Deprecated – bis zur Entfernung in einer Major-Version weiter abgesichert und mit einem Migrationspfad versehen.
Das vollständige Lifecycle-Modell beschreibt ADR 0003.