Airplan
Eine interne Projekt- und Portfoliomanagement-Anwendung für eine Unternehmens-IT-Organisation — Intake, Scoring, Quartalsplanung, Roadmapping, Sprint-Ausführung und Berichte für die Geschäftsleitung über 31 Seiten — direkt auf der Datenbank aufgebaut, die das Unternehmen bereits im Einsatz hatte, sodass sie dieselben Datensätze liest und schreibt, die alle anderen auch nutzen.

- 31
- Seiten
- 74,486
- Zeilen, Frontend
- 29
- Reine Module
- 517
- Tests
Die Daten waren in Ordnung. Es fehlte nur die Vordertür.
Die IT-Organisation führte ihr Portfolio bereits in einer relationalen Datenbank: Projekte, Bedarfsanfragen, Aufgaben, Sprints, Meilensteine, Teams, Scoring-Datensätze. Nichts fehlte. Aber die einzige Möglichkeit, sie zu nutzen, war das Öffnen von Tabellen.
Also baute sich jeder seinen eigenen Weg drum herum. Priorisierung fand in einer Tabelle statt, die herauskopiert und wieder zurückeingefügt wurde. Anfragende schrieben E-Mails, um zu fragen, wo ihre Anfrage geblieben war, weil es nirgends nachzuschauen gab. Jedes Quartal wurde die wiederkehrende Run-the-Business-Arbeit von Hand neu eingetippt. Und die drei Fragen, die ein Portfolio beantworten soll — wem gehört das, wann kommt es, was blockiert es — brauchten vier Tabellen und eine Vermutung.
Airplan ersetzt die Tabellen durch eine Oberfläche. Kein Dashboard über einer Kopie der Daten: eine echte Lese-Schreib-Anwendung innerhalb derselben Datenbank, sodass es genau einen Datensatzbestand gibt und keine Synchronisation, die veralten kann.
Ein Planungstool, geplant
Zwei Releases im Mai, dann acht stille Wochen, in denen das Fundament darunter neu aufgebaut wurde — und zwölf Releases in den darauffolgenden neunzehn Tagen. Der lange Balken ist keine Lücke in der Arbeit; er ist die Arbeit, die den Rest davon schnell gemacht hat.
Fünf Probleme, fünf Entscheidungen
- 01
Das Raster bewegte sich, während man bewertete
Die Priorisierung sortierte sich bei jedem Tastenanschlag neu, sodass die Zeile, die man gerade bewertete, unter dem Cursor wegrutschte. Die Leute bewerteten stattdessen in einer Tabelle.
Fix:Das Ranking wurde explizit. Bewertungen werden sofort gespeichert; die Reihenfolge bleibt für die Sitzung eingefroren. Ein
Refresh ranking-Steuerelement markiert sich selbst als aktualisiert, wenn die aktuellen Bewertungen eine andere Reihenfolge nahelegen, und man ordnet neu, wenn man bereit ist. - 02
Anfragen verschwanden im Nichts
Eine neue Anfrage war eine Zeile ohne Prüfstatus. Niemand konnte eine ungelesene Anfrage von einer bereits gesichteten unterscheiden, und die Person, die sie eingereicht hatte, hatte nichts zum Nachschauen.
Fix:Eine Intake-Warteschlange und eine Anfragenden-Seite. Eingehende Anfragen werden in muss geprüft werden und geprüft aufgeteilt, mit Vermerk von wem und wann. Eine eigene Seite zeigt jeder anfragenden Person jedes Projekt, das sie angefragt hat, mit Stand und Alter.
- 03
Wiederkehrende Arbeit wurde jedes Quartal neu eingetippt
Jeder Run-the-Business-Strang brauchte pro Quartal ein neues Projekt mit einem Dutzend Pflichtfeldern, und jede unfertige Aufgabe musste von Hand übertragen werden.
Fix:Das Quartal rollt sich selbst über. Die Projekte für das nächste Quartal werden 15 Tage vor Ende des aktuellen erstellt, vollständig ausgefüllt, und jede noch offene Aufgabe folgt ihrem Strang über die Grenze hinweg.
- 04
"Ist dieser Meilenstein startklar?"
Die Abhängigkeiten eines Meilensteins waren eine durch Kommas getrennte Liste von Aufgabennamen. Sie zeigte, welche Aufgaben verknüpft waren, aber nichts darüber, ob eine davon fertig war.
Fix:Abhängigkeiten als echte Tabelle. Status, Verantwortliche und Fälligkeitsdatum pro Abhängigkeit, sortierbar und filterbar — und jede davon öffnet sich in einer Seitenleiste, ohne den Meilenstein zu verlassen.
- 05
Jede Liste bekam ihre eigenen Filter-Chips
Jede Seite fügte Chips hinzu, bis der Toolbar der Platz ausging, und keine zwei Seiten filterten auf die gleiche Weise.
Fix:Ein Filter-Button, überall. Ein einziger Button öffnet ein Panel mit allen Facetten dieser Seite. Die Toolbar behält ihren Platz für Suche und Aktionen, und der Button nimmt die Akzentfarbe an, sobald irgendetwas angewendet ist.
Die Entscheidungen, die es wert sind, gezeigt zu werden
Ranking ist eine Entscheidung, kein Nebeneffekt
Eine Zeile, die durch eine Bearbeitung aus dem aktiven Filter herausfällt, bleibt stehen, mit einem Hinweis, warum — bis man wegnavigiert. Ein Datensatz, der in dem Moment verschwindet, in dem man ihn berührt, ist der schnellste Weg, Vertrauen in ein Tool zu zerstören.
Jede Facette hinter einem einzigen Steuerelement
Auf allen 31 Seiten, mit is / is not bei jeder. Der Button zeigt eine Anzahl und
nimmt die Akzentfarbe an, wenn etwas angewendet ist, sodass eine unerwartet kurze Liste
sich selbst erklärt.
Eine Komponente, drei Meilenstein-Oberflächen
Preview, Editor und Erstellung teilen sich diese Komponente, sodass sie nicht auseinanderlaufen können. Das Öffnen einer Abhängigkeit schiebt eine Seitenleiste über den Meilenstein, statt wegzunavigieren, und diese Leiste verwendet dasselbe gemeinsame Aufgabenformular, das überall sonst genutzt wird.
Jede Zahl öffnet die Zeilen dahinter
Ein KPI, den man nicht hinterfragen kann, ist eine Zahl, über die man streitet; ein KPI, der seine eigene Datensatzliste öffnet — sortierbar, durchsuchbar, direkt bearbeitbar — beendet den Streit mit demselben Klick.
"Offen" hat genau eine Definition
Alles außer erledigt und wird nicht gemacht — und dieselbe Funktion, die entscheidet, was der Übergang mitnimmt, erzeugt auch die Zahl offener Punkte, die auf jeder Strang-Zeile angezeigt wird. Die Zahl auf dem Bildschirm ist ein Versprechen darüber, was passieren wird, keine separate Berechnung, die davon abweichen kann.
Nutzbar als jemand anderes
Eine zweistufige Allow-List erlaubt einer autorisierten Admin-Person, das gesamte Produkt durch die Berechtigungen einer anderen Person zu betrachten, und ein einziger Hook liefert die effektive Identität an den gesamten Baum — sodass Zugriffsregeln testbar sind, indem man sie einfach durchläuft.
Wie es gebaut wurde
Airplan läuft als maßgeschneiderte Interface-Extension: eine React-19-Anwendung, die innerhalb der Datenbankplattform geladen wird und Live-Tabellenreferenzen hält statt eines kopierten Datensatzes. Es gibt kein separates Backend und kein ETL. Das erkauft Echtzeit-Korrektheit und kostet jedes Sicherheitsnetz, das ein Server geboten hätte — also floss der größte Teil der Ingenieursarbeit in den Wiederaufbau dieser Netze.
// Seiten — 31 Dateien
- Eine Datei pro Seite, jede abonniert nur die Tabellen, die sie braucht
- Eine Wrapper-→-Inner-Aufteilung: der Wrapper löst die Tabellen auf und besitzt den Ladezustand, die innere Komponente erhält Referenzen, die garantiert nicht null sind
- Tabellen werden per ID aufgelöst, nicht per Name, sodass eine Umbenennung in der Datenbank die App nicht lahmlegt
// Gemeinsame Oberfläche — Design-System
- Tokens, Primitives und jedes übergreifende Verhalten an einem Ort
- Helle/dunkle Zwillingspaletten, größenveränderbare Seitenleisten, die sich die Breite jeder Person merken
- Tabellen, die seitlich schwenken, statt Spalten zu schmalen Streifen zu quetschen
- Verankerte Popovers, ein Rich-Text-Editor, ein Notizen-Panel mit Threads, und ein einziges Filter-Steuerelement
// Domänenmodule — 29 Dateien, null Abhängigkeiten
- Die gesamte Logik, über die es sich zu streiten lohnt, lebt in reinen ES-Modulen ohne React und ohne Plattform-SDK
- Geschäftsjahr-Quartalsordnung, der Quartalsübergang, Abhängigkeitsgraphen und Zyklenerkennung, Planungsbäume, Template-Anwendung, Filter-Adapter, Popover-Platzierung, Markdown-Konvertierung
- Daten rein, Daten raus — genau diese eine Grenze macht das Ganze testbar
// Sicherer Zugriff — die iframe-Realität
- Jeder Feld-Zugriff läuft über einen abgesicherten Accessor, und eine Error Boundary umschließt die Wurzel
- Eine fehlende Tabelle wird als fehlend dargestellt, nicht als Null
- Ein Schema, das abgedriftet ist, darf niemals ein leeres Portfolio als gesundes melden
Wie es gebaut bleibt
517 Tests — reine Logik, ganz einfach getestet
Weil die Domänenmodule nichts importieren, läuft die Suite auf dem eigenen Test-Runner der Sprache — ohne Framework, ohne Bundler und ohne Browser. Rund 7.900 Zeilen Tests für 8.900 Zeilen reine Logik.
Contracts — Tests, die den Quellcode lesen
Interface-Regeln, die sich nicht per Unit-Test prüfen lassen, werden stattdessen gegen den Quelltext geprüft. Ein Contract zählt jedes verankerte Popover namentlich auf und prüft die Anzahl — sodass ein neues Dropdown, das von einem Scroll-Container abgeschnitten würde, den Build zum Scheitern bringt, statt als „dieses Feld hat keine Optionen" ausgeliefert zu werden.
Lint-Delta — schlägt nur bei neuen Problemen fehl
Eine erfasste Baseline bestehender Befunde lässt den Build nur bei neuem Lint scheitern, während eine 74.000-Zeilen-Codebasis strikte Regeln schrittweise übernimmt. Strenge kommt ohne einen Big-Bang-Cleanup, für den niemand Zeit hat.
Parse-Gate — Syntax kommt nicht bis in Produktion
Jede Quelldatei wird vor dem Release transformiert, um Syntaxfehler frühzeitig abzufangen. In einer Umgebung ohne Type-Checker ist ein Parse-Gate der günstigste mögliche Sicherheitsboden.
Release — nichts wird über das Netzwerk aufgelöst
Abhängigkeiten werden als geprüftes Archiv committet, und das Publish-Tool ist versionsfest gepinnt, sodass ein Release nichts installiert, was nicht bereits geprüft wurde. Ein Manifest listet die Nachweise, die ein Release mitbringen muss: Freigabereferenz, Quell-Revision, Notizen und eine verifizierte Live-Version.
Docs — Dokumentation als Daten
Die In-App-Hilfe ist ein strukturierter Datensatz, gerendert von einem kleinen Block-Renderer, der über einen Launcher auf jeder Seite geöffnet wird. Sie zu aktualisieren ist Teil des Shippings, kein Nachtrag — sodass die Hilfe nie das Produkt vom letzten Monat beschreibt.
Drei Dinge, die ich zum nächsten Projekt mitnehmen würde
- 01
Die Grenze dort setzen, wo die Tests sie brauchen
Jede Regel in abhängigkeitsfreie Module zu ziehen war die Entscheidung mit der größten Hebelwirkung im Projekt — nicht weil es elegant war, sondern weil es eine nicht testbare iframe-Anwendung in 517 schnelle Tests verwandelte.
- 02
Eine Interface-Regel ist nur real, wenn etwas sie erzwingt
Der Bug, der einen Picker leer aussehen ließ, war schon einmal in einem anderen Picker behoben worden. Er kam zurück, weil die Guard-Prüfung vier Komponenten namentlich aufzählte und eine fünfte geschrieben wurde. Contracts müssen zählen, nicht aufzählen.
- 03
Konsistenz ist ein Feature, das man spürt
Ein Filter-Steuerelement, ein Aufgabenformular, eine Definition von „offen". An jeder Stelle, an der diese auseinanderlaufen durften, fand irgendjemand irgendwann die Nahtstelle — und vertraute der Zahl auf dem Bildschirm nicht mehr.
Damit gebaut
Stack
- React 19
- Airtable Blocks SDK
- Plain ES modules
- node:test
Zeitleiste
v0.1.0 Mai 2026 → v0.7.0 August 2026 · 14 Releases · allein entworfen und gebaut. Die Interface-Screens in der ursprünglichen Case Study sind Nachbildungen, gezeichnet mit erfundenen Projekten und Personen.