Architektur
Ein React-Frontend unter KI-gestützter Entwicklung kohärent halten
Wie das Frontend dieses Portfolios kohärent bleibt, obwohl es sich mit KI-Coding-Agenten schnell verändert: ein externes Designsystem als Fundament, wenige geteilte Entscheidungen, lokale Verantwortung für alles andere, Tests, die Invarianten festhalten, Messungen im Browser und menschliches Urteil über das Produkt.
Veröffentlicht am 7. Oktober 2026
Dieses Portfolio hat nicht als React-Anwendung mit 60.000 Zeilen angefangen. Mit der Zeit kamen hinzu:
- eine Fotografie-Anwendung mit Galerie, Fokusansicht, Karte und einer interaktiven Konstellation von Fotografien;
- zwei Konversationsoberflächen;
- Admin-Werkzeuge;
- lange Journal- und Architekturseiten;
- lokale Spracheingabe;
- mehrere Rendering-Oberflächen, die nur im Browser existieren.
Eine weitere Komponente zu bauen war irgendwann nicht mehr das Schwierige. Schwierig wurde, dass sich das Ganze wie ein Produkt anfühlt, während es sich ständig weiter verändert. Diese Fallstudie handelt von der Frontend-Architektur, mit der ich das löse, und davon, wo sie an ihre Grenzen kommt.
Das Kohärenzproblem
Das Frontend ist eine Next.js-16-Anwendung mit App Router auf React 19. Der React Compiler ist seit dem ersten Commit aktiv. Die Anwendung hat 361 TSX-Dateien mit rund 61.000 Zeilen und 276 Client-Module. Allein die Fotografie macht rund 38.600 Zeilen Komponentencode aus. Jeder sichtbare Text existiert auf Englisch und Deutsch, und fast jede Oberfläche sieht auf dem Smartphone anders aus.
Um diese Zahlen geht es nicht. Sie beschreiben die Bedingungen, unter denen ein Frontend unbemerkt zerfällt:
- ein zweiter Button, der fast so ist wie der erste;
- eine Schriftgröße, die in einer neuen Datei noch einmal festgelegt wird;
- ein Modal, das den Fokus leicht anders einfängt;
- eine Hover-Interaktion, die nie auf einem Touchscreen ankommt.
Jedes davon ist für sich vernünftig. Zusammen ergeben sie ein Produkt, das zusammengesetzt statt gestaltet wirkt.
Tempo verschärft das. Wenn eine Änderung eine Stunde statt eines Tages dauert, gibt es mehr Änderungen, und jede ist eine Gelegenheit, etwas neu zu entscheiden, das längst entschieden war. Ein großer Teil dieses Frontends ist mit KI-Coding-Agenten im Arbeitsablauf entstanden, der Druck ist hier also real. Die Frage ist nicht, wie man React-Komponenten organisiert. Die Frage ist, wie ein Frontend kohärent bleibt, wenn die Umsetzung schneller ist, als sich ihre Entscheidungen neu herleiten lassen.
Meine Antwort hat vier Teile:
- Architektur: wo Code liegt und in welche Richtung Abhängigkeiten zeigen.
- Konventionen: die Entscheidungen des Produkts, einmal aufgeschrieben.
- Deterministische Belege: Typen, Lint, Tests, die Invarianten festhalten, und Messungen in einem echten Browser.
- Menschliches Urteil: Geschmack, Produktrichtung und die Frage, ob etwas seine Komplexität wert ist.
Das geschichtete Frontend
Abhängigkeiten zeigen in eine Richtung: von den Routen über die Produktfeatures zu einer kleinen gemeinsamen Schicht und ganz unten zu einer externen Komponentenbibliothek.
- Routen und LayoutsServer-Komponenten · Locale · gecachte Inhalte
- Serverseitig gerenderte SeitenJournal · Architektur · Vertrauensseiten
- Interaktive Feature-WurzelnStartseite · Fotografie · Konversationen · Admin
- Gemeinsame PrimitiveBedienelemente · Disclosure · Motion · Konversations-Shell
- Style-PolicyOberflächenkonstanten · Typo-Rollen · Breakpoints · Konversations-Chrome
- Tokens und Motion-VokabularRoot-Custom-Properties · gemeinsame Keyframes
- KERNexterne Komponenten und Tokens
- Standardmäßig Server. Das Locale-Layout lädt Übersetzungen, Seiteninhalte und den Seitenhintergrund auf dem Server. Inhalte werden über Nexts
"use cache"mit expliziten Lebensdauern und Cache-Tags gelesen, die Admin-Änderungen invalidieren. - Client an der Feature-Wurzel. Die Client-Grenze liegt dort, wo Interaktion beginnt: auf der Startseite, in der Fotografie-Anwendung, in den beiden Konversationsoberflächen und im Admin. Darunter verantwortet jedes Feature seinen eigenen State. Einen globalen Store gibt es nicht.
- Ein Shim für die Komponentenbibliothek. KERN wird als ein einziges Bundle ausgeliefert, das beim Laden einen React-Context erzeugt. Eine Server-Komponente, die es direkt importiert, stürzt deshalb ab. Serverseitig gerenderte Seiten erreichen KERN über ein kleines Client-Modul, das die Komponenten neu exportiert. Nur deshalb können die Journal- und Architekturseiten auf dem Server bleiben.
- Zwei Datenwege. Server-Komponenten lesen über die gecachte Schicht. Interaktive Features nutzen tRPC mit TanStack Query. Die beiden KI-Konversationsendpunkte laufen über einen Streaming-Link, damit Antworten in Phasen ankommen können.
- Schwere Laufzeiten hinter Lazy-Inseln. Ein Three.js-Avatar, eine MapLibre-Karte mit eigenem Worker, eine Canvas2D-Konstellation und der Fokus-Viewer der Startseite werden bei Bedarf und nur im Client geladen. Die WebGPU-Launcher-Icons fallen auf SVG zurück. Die lokale Spracherkennung läuft in einem Worker.
- Komponenten verantworten ihr Warten. Es gibt keine Lade- oder Fehlerdatei auf Routenebene. Lade- und Fehlerzustände gehören der Komponente, die weiß, worauf sie wartet.
Eine Fotografie-Komponente darf ein gemeinsames Primitiv verwenden. Ein gemeinsames Primitiv weiß nichts von der Fotografie. Für die Fotografie-Oberfläche sind diese Importrichtungen deklariert und werden maschinell geprüft. Im Rest des Frontends sind sie eine Konvention.
Kein weiteres Designsystem
Ich habe für dieses Portfolio kein zweites Designsystem gebaut. Das formale Fundament ist KERN, ein Open-Source-UX-Standard für die deutsche Verwaltung, hier über sein React-Kit eingebunden. 211 Komponentendateien importieren es direkt, für Typografie, Buttons, Hinweise, Eingabefelder, Dialoge und Karten.
Darüber liegt etwas bewusst Kleineres: eine Konventionsschicht für die Entscheidungen, die dieses Produkt trifft und KERN nicht. Dazu gehören:
- einige Produkt-Primitive;
- Typografie-Rollen;
- ein Motion-Vokabular;
- ein einziger Eigentümer für Breakpoints;
- die Geometrie schwebender Oberflächen;
- das Chrome, das sich die beiden Konversationsoberflächen teilen.
Es gibt keinen Katalog, keine Token-Pipeline und kein versioniertes Paket. Die meisten Regeln dieser Schicht sind aufgeschrieben, nicht durchgesetzt. Dieser Unterschied ist für alles Weitere wichtig.
Manche Primitive werden breit genutzt. Gezählt werden Dateien, die sie von außerhalb ihres eigenen Ordners importieren:
ChipButton, 28: Aktions- und Link-Chips, typisiert als Discriminated Union. Ein Chip ist also ein Button oder ein Link, nie eine unklare Mischung.IconActionButton, 16: Der zugängliche Name ist ein Pflicht-Prop.EditorialAnnotation, 13: Byline- und Metadatenzeilen.EyebrowLabel, 13: die kleine Überzeile in Großbuchstaben über einer Überschrift.MotionReveal, 11: ein Einblendeffekt beim Mounten, ganz ohne eigenen State.DisclosureGroup, 10: natives<details>, außerhalb von Admin-Formularen statt KERNs Accordion verwendet.
Andere haben wenige Nutzer, und das ist nicht automatisch ein Fehler. Die Konversations-Shell hat zwei Importeure und das gemeinsame Fragefeld fünf, weil es genau zwei Konversationsprodukte gibt und beide sie teilen. Section, eine Glaskarte mit Überschrift, hat seit dem Umbau der Startseite nur noch zwei. Das gemeinsame Dropdown hat einen einzigen Nutzer, den Sprachumschalter, der in der Desktop- und der mobilen Navigation erscheint. Eine niedrige Zahl sagt, wie viele Stellen eine Entscheidung brauchen, nicht, ob die Entscheidung einen Namen verdient.
Die Regel für diese Schicht ist kurz: Gemeinsame Entscheidungen werden hochgezogen, nicht wiederholte Zahlen. Wiederkehrende Pill-Radien von 999px, Abstände von 8px, Blur-Stärken und lokale z-Index-Werte wurden einzeln untersucht und bewusst gelassen. Es sind dieselben Zahlen, aber sie drücken unterschiedliche Entscheidungen aus. Ein Token für jede würde Oberflächen koppeln, die keinen Grund haben, sich gemeinsam zu ändern. Aus demselben Grund wurde ein z-Index-Register als unnötig verworfen.
Diese Primitive wurden überwiegend aus bestehenden Features herausgelöst statt vorab entworfen, und mehrere wurden später umbenannt oder umgebaut.
Lokale Verantwortung vor Abstraktion
Eine Entscheidung wandert nur dann in gemeinsamen Code, wenn zwei Oberflächen dieselbe Entscheidung treffen. Und dann nur so weit nach oben, wie beide es brauchen. Ich nenne das den nächsten gemeinsamen Eigentümer.
Geteilt: dieselbe Entscheidung, zweimal getroffen
Transcript-Chrome Besucherzug, Blase, 12/32px-Rhythmus
Konversations-Shell schwebendes Panel / mobiles Sheet
Fragefeld ein Eingabefeld für beide Produkte
GPU-Identitätsschicht ein Launcher-Motiv, zwei Produkte
Breakpoints ein Eigentümer für Gerätestufen
Typo-Rollen Überzeile, Gruppentitel, Pill-Label
Lokal: oberflächlich ähnlich, getrennt entschieden
Kurator-Typoskala 14px Text · 12px Metadaten
Engineering-Skala 15px Antwort · 13px Metadaten
Rollenlabel auch Überzeile der Fokusansicht
Domänen-Shells eine je Konversationsprodukt
Motion-Primitive unterschiedliche Lebenszyklen
999px · 8px · zIndex gleiche Zahlen, andere Gründe
Die beiden Konversationsoberflächen zeigen das am deutlichsten. Der Fotografie-Kurator antwortet in Prosa in Bildunterschriftsgröße: 14px Fließtext, 12px Metadaten. Engineering Ask beantwortet Fragen zu meiner Arbeitsweise. Dort ist die Antwort der eigentliche Inhalt, deshalb sitzt sie eine Stufe größer, bei 15px und 13px. Eine zusammengelegte Skala wäre für eine der beiden falsch gewesen.
Wo sie tatsächlich übereinstimmten, wanderten die Entscheidungen: Fünf wertgleiche Entscheidungen des Gesprächsverlaufs haben jetzt einen Eigentümer neben der Konversations-Shell. Das Rollenlabel wurde für denselben Schritt geprüft und verworfen, weil es im Kurator zugleich die Überzeile der Fokusansicht ist.
Dieselbe Überlegung hat zwei größere Abstraktionen verhindert:
- Kein universelles KI-Chat-Panel. Die Konversations-Shell war bereits die gemeinsame Schicht, und jedes Produkt behält seine eigene Shell für seinen eigenen State.
- Keine zusammengelegten Motion-Primitive. Ein Audit ergab, dass nicht ihre Anzahl das Problem war, sondern ihre uneinheitlich umgesetzten Lebenszyklen. Also wurde jeder Lebenszyklus repariert.
Die größte Feature-Komponente, der Fotografie-Assistent, erreichte in der Spitze 3.055 Zeilen und hat heute 2.303. Ein Teil davon ging auf einen späteren Durchgang zurück, der veraltete Kommentare entfernte. Die Zeilenzahl ist also Kontext, nicht das Ergebnis.
Das Ergebnis war eine Render-Grenze. Tippen im Kurator renderte früher die Galerie dahinter neu: 384 Renders von Galeriekarten bei vier Tastenanschlägen. Seit der Fragenentwurf und der Konversationskern in eine eigene Komponente und einen eigenen Hook gewandert sind, sind es null. Die Schnittstellen folgten dem, was neu rendern musste, nicht dem, was groß aussah. Die Host-Komponenten für Karte und Konstellation herauszulösen wurde erwogen und verworfen, weil jede neun oder zehn durchgereichte Props gebraucht hätte.
Styling als Architektur
Das Styling-Modell ist ungewöhnlich, und ich halte es nicht für allgemein richtig. Es ist eine Reihe von Zuständigkeitsentscheidungen, die zu dieser Codebasis passen:
- Standardmäßig Inline-Styles: rund 1.600 Inline-Style-Objekte in 248 Dateien. Abstände kommen aus einem
spacing()-Helfer mit 8px-Basis. - Ein CSS-Modul nur, wo Inline-Styles das CSS nicht ausdrücken können: Pseudo-Elemente,
:has(),@starting-style, Keyframes, Hover- und Focus-visible-Zustände sowie Media Queries für das eigene Layout einer Komponente. Es gibt 18 davon. Jedes gehört einer Komponente und beginnt mit einem Kommentar, der sagt, warum es existiert. - Globales CSS für die Grundlagen: Root-Tokens, darunter fünf für Motion-Dauer und Easing, außerdem Resets, der Auftritt des nativen Dialogs und ein gemeinsames Motion-Vokabular.
- KERN darunter, mit eigenen Styles und Tokens.
Das globale Stylesheet erreichte einmal 903 Zeilen. Eine einzige Migration verschob Regeln, die zu einzelnen Komponenten gehörten, in die ersten elf Module und brachte es auf 286. Heute hat es 542 Zeilen, bei 18 Modulen. Das ist keine Geschichte von schrumpfendem CSS, sondern eine davon, dass Regeln Eigentümer bekommen. Was global bleibt, ist grundlegend, und ein großer Teil seiner Länge ist Begründung, die direkt neben der jeweiligen Regel steht.
Der nützlichste Moment dieser Geschichte war ein Audit, das keinen einzigen Style geändert hat. Sein Ergebnis: Die Styling-Architektur war gesünder als ihre Dokumentation. Der Code war kohärent, die schriftliche Anleitung widersprach ihm:
- Ein Workflow verbot CSS-Module, die die Hauptanweisungen erlaubten.
- Die Anweisungen verwiesen auf einen Spacing-Helfer in einer Datei, die es nicht gab.
- Die Motion-Anleitung nannte die globale Reduced-Motion-Regel den einzigen Mechanismus, obwohl zweiundzwanzig Animationen bereits eigene Reduced-Motion-Zweige hatten. Wörtlich befolgt, hätte sie eine Endlosanimation ganz ohne Reduced-Motion-Behandlung durchgelassen.
Die erste Änderung betraf deshalb die Regeln, ohne Änderung am Produktionscode. Reduced Motion wurde zu einem Verfahren mit einer einzigen Eingangsfrage: Wird die Animation von einem Motion-Token getaktet? Wenn ja, deckt sie das globale Abschalten ab. Wenn nein, braucht sie eine eigene Media Query. Wenn JavaScript sie steuert, liest sie einen gemeinsamen Hook.
Erst danach folgten zwei schmale Zusammenführungen: das oben beschriebene Transcript-Chrome und ein einziger Eigentümer für Geräte-Breakpoints, die drei TypeScript-Dateien jeweils selbst deklariert hatten. Für die erste wurden 940 berechnete Style-Felder im Browser vorher und nachher gemessen, und sie waren identisch. Für die zweite lösten 50 von 50 Messungen beim frischen Laden die erwartete Gerätestufe auf, und kein Wert und kein Verhalten änderte sich. Die übrigen Kandidaten für Zusammenführungen abzulehnen gehörte zur Arbeit.
Performance-Grenzen
Ich halte zwei Arten von Performance-Aussagen getrennt: was gemessen wurde und was die Architektur beabsichtigt.
Gemessen:
- WebGPU-Launcher. Das dreidimensionale Launcher-Icon fügt dem initialen Pfad 1,2 KB gzip hinzu. Die übrigen 44 KB werden erst geladen, wenn das GPU-Motiv tatsächlich läuft. In CPU-Profilen im Leerlauf brauchte es weniger Main-Thread-Zeit als das CSS-animierte SVG, das es ersetzen kann.
- Eine Schleife, die nie aufhörte. Eine
requestAnimationFrame-Schleife in der gemeinsamen Launcher-Identität lief in jedem Zustand mit rund 577–613 Callbacks pro zehn Sekunden, auch wenn ihr Launcher gar nicht gemountet war. Sie wird jetzt an einen Zustand gekoppelt, von dem der Effekt ohnehin abhing, und läuft nur noch, wenn er etwas zu tun hat. Damit fiel die Zahl auf null, ohne sichtbare Änderung. - Layout-Verschiebung im Journal. Der Wechsel vom Index zu einem Artikel verschob bei gedrosselter Verbindung das Layout auf Smartphone-Breite um bis zu 0,1991 CLS. Die Lösung war kein besseres Skeleton: Der Platzhalter wurde entfernt, sodass ein Kapitel mit der ersten Serverantwort ankommt. CLS liegt jetzt bei beiden gemessenen Breiten bei 0.
- Einklappender Header. Ein erster Entwurf, der den mobilen Header beim Scrollen einklappte, maß CLS 0,046, weil die Insel zentriert ihre Breite änderte. Der spätere Entwurf verschiebt nichts über das Layout und misst 0.
- Tippen im Kurator. 384 Renders von Galeriekarten bei vier Tastenanschlägen wurden zu null (siehe oben).
Strukturelle Vereinfachung, also weniger bewegliche Teile statt gemessener Geschwindigkeit: Die Reveal-Observer der Startseite gingen von 17 auf 9 zurück. Vier State-Variablen und ein Timer, die nur der Animation dienten, wurden entfernt.
Architektonische Absicht, nicht gemessen:
- Server-Rendering und gecachte Lesezugriffe;
- schwere Laufzeiten hinter Lazy-Grenzen, die nur im Client laden;
- Fotografien in vier Breiten als WebP und AVIF, mit
sizespro Foto in der Fokusansicht; - der React Compiler, den ich nie gegen seine Abwesenheit gemessen habe.
Ich halte diese Entscheidungen für richtig. Ich habe aber keine Zahlen, die zeigen, dass sie die Seite schneller gemacht haben, also behaupte ich das nicht.
Barrierefreiheit verändert die Architektur
Die wichtigsten Entscheidungen zur Barrierefreiheit betrafen, auf welchem Element oder Primitiv ein Feature aufgebaut ist.
- Native Elemente statt eigener Widgets. Das mobile Konversations-Sheet ist ein natives
<dialog>, geöffnet mitshowModal(). Fokusfalle, Escape und ein inerter Hintergrund kommen damit vom Browser. Disclosure nutzt<details>. Ein eigener Navigations-Drawer wurde durch KERNs Dialog ersetzt. - Zugängliche Namen im Typvertrag. Der Icon-Button und der Ladeindikator für Karte und Konstellation lassen sich nicht ohne Label rendern; es ist ein Pflicht-Prop.
- Versteckt heißt inert. Visuell verstecktes Chrome ist auch
inert. Deckkraft ist nie der Zustand für Barrierefreiheit. - Inhalte sichtbar ohne JavaScript. Ohne JavaScript war die Startseite unterhalb des Hero-Bereichs früher leer: alle sieben Szenen bei Deckkraft null, dauerhaft. Einblendungen starten jetzt von sichtbarem Server-HTML und scharf schalten sie sich nur für Inhalte, die wirklich außerhalb des Bildschirms liegen.
- Ein Eingabefeld, das seine Identität behält. Auf dem Smartphone wurde das Textfeld der Konversation bei jedem Wechsel der Layoutdichte zerstört und neu erzeugt und verlor mitten im Tippen den Fokus. Jetzt ist es ein dauerhaftes Element.
Zwei Defekte, die beim Messen der gerenderten Seite gefunden wurden, zeigen, warum das im Browser geprüft werden muss.
- Verschachtelte Interaktion in der Galerie. Alle 72 Galeriekarten verschachtelten interaktive Elemente, ein schwerer axe-Verstoß. Nach der Korrektur gab es keinen mehr.
- Ein verdeckter Footer-Link. Bei einer gängigen Desktopgröße verdeckte der schwebende Konversations-Launcher den Footer-Link zur KI-Transparenz. Keiner von fünf Trefferpunkten auf dem Link war mit dem Zeiger erreichbar, nach der Korrektur alle fünf. Die Korrektur leitet den Abstand des Launchers aus dem Modul ab, das die Abstände und die Stapelung schwebender Oberflächen bereits verantwortet, statt einen weiteren lokalen Versatz hinzuzufügen.
Das ist keine Aussage über WCAG-Konformität; ein Konformitätsaudit gab es nicht. Die hier beschriebenen automatischen Prüfungen, auch axe, laufen lokal im Entwicklungsablauf, nicht als CI-Gate.
Responsives Design verändert Zuständigkeiten
Manchmal braucht ein Smartphone kleinere Maße. Manchmal braucht es einen anderen Eigentümer für die Interaktion.
- Navigation. Der Desktop hat eine Pill-Navigationsleiste. Unter 1280px wird der Header zu einer schwebenden Insel, deren Menü sich als Bottom Sheet mit eigener Schließgeste öffnet. Beim Bau kamen drei bestehende Defekte zum Vorschein:
- Der Scroll-Lock blieb nach einem Sprachwechsel hängen.
- Escape in der Sprachliste schloss das ganze Menü.
- Klassische Scrollbars verschoben die Seite seitlich.
- Konversation. Die Konversations-Shell ist auf dem Desktop ein schwebendes Panel, auf dem Smartphone ein natives modales Sheet. Auf dem Smartphone belegte das feste Chrome der Konversation 249px des Sheets, die Tastatur nur 49px. Deshalb entscheidet jetzt ein Dichtemodell, wie viel Chrome jede Phase einer Konversation zeigt.
- Hover. Der Hover-Text auf Galeriekarten wurde entfernt. Er zeigte einen Titel, der in Produktion immer nur die ID des Fotos war, dazu Angaben, die sich in der Fokusansicht wiederholten. Die Aktionen der Karten sind geblieben.
- Sichere Bereiche. Bedienelemente am Bildschirmrand berücksichtigen den Safe-Area-Inset dieses Rands, nur in CSS, ohne Erkennung per JavaScript. Die Seite mit
viewport-fit=coverunter die Notch zu ziehen wurde ausprobiert und zurückgenommen. - Bilder. Die
sizes-Angaben der Fotos werden aus demselben TypeScript-Eigentümer für Breakpoints gebaut, den das CSS spiegelt, und ein Test prüft, dass die Spiegelungen übereinstimmen.
Responsive Logik hat drei legitime Eigentümer: CSS-Media-Queries für das Layout, einen Hook für Render-Entscheidungen, die JavaScript brauchen, und HTML-sizes-Angaben. Nicht alles läuft über den Hook, und das ist Absicht.
All das wurde mit Touch-Emulation in Chrome geprüft. Mehrere mobile Verhaltensweisen, darunter die iOS-Tastatur und die Behandlung sicherer Bereiche, sind noch nicht auf einem physischen Gerät verifiziert.
Wo der Agent ins Spiel kommt
Alles bisher Beschriebene ist das Frontend. KI-Coding-Agenten arbeiten darin. Die sinnvolle Frage ist, wie ein Agent eine Änderung machen kann, ohne jede der Entscheidungen oben neu zu entdecken. Die Antwort ist zum größten Teil Kontext:
- Eine Anweisungsdatei mit den Styling-Regeln (Reihenfolge der Schichten, Inline zuerst, die Ausnahme für CSS-Module, Entscheidungen statt Zahlen, progressive und reduzierte Bewegung), den KERN-Regeln und einer Liste bestehender Komponenten, die vor dem Bau neuer Komponenten wiederverwendet werden sollen.
- Frontend-Wissensdateien zu Architektur, Motion, Barrierefreiheit, KI-Interaktionsoberflächen und der Server-Client-Grenze von KERN, dazu eine kurze schriftliche Frontend-Verfassung.
- Workflow-Skills für das Hinzufügen einer Komponente, das Hinzufügen von Motion und das Hinzufügen lokalisierter Texte. Weitere Skills prüfen eine UI-Änderung im Browser, belegen eine Performance-Aussage mit einer Messung und führen React Doctor auf den geänderten Dateien aus.
- Ein Code-Graph, um bestehende Symbole und ihre Aufrufer zu finden, bevor neue geschrieben werden.
- Modulkommentare, die sagen, warum eine Datei existiert.
Die repositoryweite Seite davon, mit Kontext-Routing, Code-Graph und Validatoren, beschreibt Architektur des KI-nativen Repositorys. Dieser Artikel behandelt nur, was sie für die Frontend-Arbeit bedeutet.
- Aufgabeeine Anfrage zu einer Oberfläche oder einem Verhalten
- KontextAnweisungen · Frontend-Wissen · Skills
- Herausfinden, was existiertCode-Graph · bestehende Primitive · Modulkommentare
- Begrenzte Änderungin der zuständigen Schicht
- TestsTypen · Lint · Unit- und Source-Pin-Tests
- Belege aus dem BrowserPrüfungen des gerenderten Zustands · Messung
- Menschliche PrüfungGeschmack · Produktrichtung · Komplexität
- Behalten, ändern oder löschen
Ich habe die Arbeit gesteuert, geprüft und committet. KI-Coding-Agenten wurden innerhalb dieses Ablaufs für Repository-Analyse, Unterstützung bei der Umsetzung, Messungen und das Schreiben von Berichten eingesetzt. Die Git-Historie hält nicht fest, welche Zeilen ein Agent geschrieben hat, und ich betrachte nichts davon als autonome Frontend-Entwicklung.
Was der Agent nicht entscheiden kann
Dass ein Agent mehr messen kann, macht nicht jede Frage objektiv. Die Frontend-Berichte in diesem Repository halten Entscheidungen fest, die kein Test, keine Messung und kein Agent klären konnte.
-
Der WebGPU-Launcher. Ein dreidimensionales Launcher-Icon bestand alle Plattformkriterien:
- Es renderte im echten Launcher.
- Es fiel bei jedem Fehler auf SVG zurück.
- Es kostete im Leerlauf weniger.
Es war trotzdem das schlechtere Icon. Bei einer Pixeldichte von 1, also auf gewöhnlichen Desktop-Monitoren, verschwamm es zu graublauen Punkten und verlor die Identität des bestehenden Symbols. Das Urteil lautete, beim SVG zu bleiben. Später wurde ein dunkleres Material in drei Paletten gerendert und eine davon bei der Prüfung ausgewählt. Sie hob den inneren Kontrast des Icons von 1,44:1 auf 4,44:1. Die beiden anderen wurden aus dem Code entfernt, statt umschaltbar zu bleiben.
-
Das Kamera-Motiv. Das Kamera-Motiv des Kurators wurde von 71,5 % auf 79,2 % seines Rahmens vergrößert, gemessen und bei der Prüfung wieder zurückgenommen.
-
Der einklappende Header. Der beim Scrollen einklappende Header wurde gebaut und gemessen und dann auf meine Anweisung entfernt, weil die Kontaktaktionen jederzeit auf der Insel stehen sollten. Am nächsten Tag habe ich diese Entscheidung revidiert. Der neue Entwurf verschob nichts über das Layout, und CLS ging von 0,046 auf 0.
-
Hover in der Galerie. Den Hover-Text zu entfernen ließ sich leicht mit Daten begründen. Auch die Aktionen der Karten zu entfernen stand zur Debatte, und das habe ich abgelehnt.
-
Tiefe und Atmosphäre der Konstellation. Zwei Experimente für die Konstellation funktionierten technisch.
- Informationstragende Tiefe bestand 16 neue Tests und alle 185 Konstellationstests, aber im echten Browser war ihr Effekt bei Produktionswerten nicht wahrnehmbar.
- Eine volumetrische Atmosphäre rechtfertigte ihre Komplexität nicht.
Beide wurden entfernt.
-
Die iOS-Tastatur. Das dauerhafte Eingabefeld belegte, dass das Textfeld nicht mehr neu gemountet wird. Ob die Tastatur auf einem iPhone erscheint und offen bleibt, braucht ein echtes Gerät, und diese Frage ist noch offen.
Agent Deterministisch Mensch
────────────────────── ───────────────────── ──────────────────────
Repository-Analyse Typen visueller Geschmack
Umsetzungshilfe Lint Produktrichtung
Messung Tests, Source-Pins Komplexität wert?
Konsistenzprüfung Importgrenzen physische Geräte
Berichtsentwürfe Render-Prüfungen was ausgeliefert wird
Der Agent liefert Belege. Deterministische Software macht aus einem Teil davon Prüfungen für jede Änderung. Ich entscheide, was das Produkt ist.
Testen ohne großen DOM-Test-Stack
Diesen Teil der Architektur würde ich am vorsichtigsten beschreiben, weil er ein Kompromiss ist und keine Empfehlung.
Das Repository hat 866 Testdateien. Rund 94 davon testen Frontend-Code, indem sie den Quelltext der Komponenten lesen und seine Struktur prüfen. Andere testen reine Policy-Module, und einer rendert Server-Markup und prüft das HTML. Folgendes gibt es nicht:
- jsdom;
- Testing Library;
- eine Browser-End-to-End-Suite in CI;
- eine dauerhafte Baseline für visuelle Regressionen.
Tragfähig wird das durch den Ort, an dem Entscheidungen liegen. Ein großer Teil des Frontend-Verhaltens steckt in kleinen Modulen ohne React: Konversationsdichte, Header-Kompaktierung, Panel-Layout, Bildgrößen der Fokusansicht, Breakpoints. Sie lassen sich wie gewöhnliche Funktionen testen. Source-Pin-Tests sichern die architektonische Form:
MotionRevealhat keinen React-State, keinen Effect und keinen Timer.- Jedes Schließen der Fokusansicht läuft über einen einzigen abgesicherten Pfad.
- Die Behandlung sicherer Bereiche bleibt in CSS.
- Breakpoints haben einen Eigentümer, und jede CSS-Spiegelung stimmt mit ihm überein.
Diese Tests laufen bei jedem Push und in CI. Echte Interaktion decken sie schlecht ab: Tastaturreihenfolge, Fokus, der an die richtige Stelle zurückkehrt, Gesten und alles, was davon abhängt, wie ein Browser tatsächlich layoutet und zeichnet.
Einen Teil dieser Lücke deckt die Prüfung des gerenderten Zustands ab. Ein lokaler Befehl steuert einen echten Browser über deklarierte Oberflächen und Viewports und prüft sechs Invarianten:
- Der beabsichtigte Zustand wurde erreicht.
- Kein interaktives Element wird von einem anderen verdeckt.
- axe zeigt keine Regression gegenüber einer Baseline.
- Keine rohen Übersetzungsschlüssel sind sichtbar.
- Nichts läuft horizontal über.
- Kein Bild ist kaputt.
Diese Prüfung hat beide Barrierefreiheitsdefekte von oben gefunden. Sie hängt von einem Browser-Werkzeug ab, das ein frischer Checkout nicht hat. Deshalb läuft sie im Entwicklungsablauf, nicht in CI.
Ich habe auch ein multimodales Modell als visuellen Prüfer evaluiert. Es schnitt von den getesteten Ansätzen am besten ab (F1 84,2 gegenüber 74,7 für den Pixelvergleich) und verfehlte trotzdem zwei seiner fünf vorab festgelegten Kriterien. Es kann bei der Vorauswahl helfen. Über das Bestehen einer Änderung entscheidet es nicht.
CI: bei jedem Pull Request und erneut vor dem Deployment
TypeScript, strict
Biome, empfohlene Lint-Regeln
Unit-, Policy-Modul- und Source-Pin-Tests
Importgrenzen der Fotografie-Oberfläche
─────────────────────────────────────────── CI-Grenze
Lokal: im Entwicklungsablauf
React Doctor auf den geänderten Dateien
Prüfung des gerenderten Zustands, inkl. axe
Menschliche visuelle und Produktprüfung
Scheitern als Beleg
Schnelles Iterieren ist nur nützlich, wenn das Verwerfen eines Experiments billig ist. Fünf Verwerfungen, die dieses Frontend geprägt haben:
- Eine WebGPU-Kugel wäre das bessere Launcher-Icon. Beleg: Alle Plattformkriterien wurden erfüllt, aber bei einer Pixeldichte von 1 verlor das Icon seine Identität. Entscheidung: vorerst beim SVG bleiben.
- Ein universelles KI-Chat-Panel würde Duplikate beseitigen. Beleg: Die Konversations-Shell enthielt die gemeinsamen Entscheidungen bereits. Entscheidung: keine weitere Abstraktion.
- Informationstragende Tiefe würde die Konstellation lesbarer machen. Beleg: 185 Tests grün, Effekt im Browser nicht wahrnehmbar. Entscheidung: entfernen, samt Modul und Tests.
- Eine Fragment-Ref aus React 19.3 könnte einen Fokus-Wrapper ersetzen. Beleg: Ein kompilierender Typtest zeigte, dass die Fragment-Instanz kein Gegenstück zu
.contains()hat, das die Blur-Behandlung braucht. Entscheidung: den Wrapper behalten. - Wiederholte Werte sollten Tokens werden. Beleg: Dieselben Werte drückten unterschiedliche Entscheidungen aus. Entscheidung: lokal lassen und die Regel aufschreiben.
Verwerfen hieß meist Löschen: das Tiefenmodul samt Tests, die beiden nicht gewählten Paletten, eine ältere Scroll-Reveal-Komponente. Kaum etwas überlebt als schlafendes Feature-Flag.
Der Entwicklungsablauf
Eine größere Frontend-Änderung durchläuft hier meist denselben Ablauf:
- ein Audit des Bereichs;
- die Untersuchung, was das Problem bereits verantwortet;
- eine begrenzte Änderung bei diesem Eigentümer;
- Unit- und Source-Pin-Tests für ihre Invariante;
- eine Messung in einem echten Browser;
- eine Produktprüfung durch mich;
- danach: behalten, ändern oder löschen.
Das ist etwas anderes, als ein Stück UI per Prompt anzufordern und auszuliefern, was zurückkommt. Die Styling-Arbeit ist das klarste Beispiel: ein Audit, eine Korrektur der schriftlichen Regeln ohne Änderung am Produktionscode, dann zwei schmale Zusammenführungen, die beide im Browser gemessen nichts Sichtbares veränderten.
Nicht jede Änderung folgte dem Ablauf so sauber. Manche Audits landeten im selben Commit wie ihre Korrektur, die ursprünglichen Ticket-Prompts sind nicht gespeichert, und eine menschliche Entscheidung ist nur dort belegt, wo ein Bericht sie festhält.
Grenzen
Die aktuellen Grenzen:
- Kein formales internes Designsystem. Eine Konventionsschicht über KERN, keine eigene Komponentenbibliothek.
- Viele Konventionen sind Prosa. Nichts erzwingt maschinell nur KERN-Komponenten, Inline-Styling zuerst, die richtige Stelle für
"use client", die Wiederverwendung eines bestehenden Primitivs oder Parität zwischen der englischen und der deutschen Übersetzungsdatei. - Keine Frontend-Routing-Kategorie. Das Kontext-Routing des Repositorys hat keine Kategorie für Frontend-Aufgaben. Das Frontend-Wissen muss gezielt geöffnet werden.
- Das Komponenteninventar kann veralten. Die Liste bestehender Komponenten ist Prosa, die nichts gegen den Code prüft.
- Die Browser-Prüfung ist lokal. Prüfungen des gerenderten Zustands und React Doctor sind Teil des Arbeitsablaufs, nicht von CI.
- Keine Interaktionstest-Suite und keine dauerhafte visuelle Baseline.
- Unvollständige Gerätetests. Mehrere mobile Verhaltensweisen wurden nur in der Emulation geprüft.
- Urheberschaft ist nicht erfasst. Git kann nicht zeigen, welche Zeilen ein KI-Agent geschrieben hat.
- Auch Dokumentation veraltet. Die schriftliche Anleitung ist dem Code schon mehrfach hinterhergelaufen, und das wird wieder passieren.
Fazit
Das Kohärenzproblem verschwindet nicht, wenn ein Agent den Code schreiben kann. Es wird schärfer, weil jeden Tag mehr Entscheidungen berührt werden.
Diese Architektur soll einen Agenten nicht jede Frontend-Entscheidung treffen lassen. Sie soll verringern, wie viele Entscheidungen während der Umsetzung neu erfunden werden müssen:
- Gemeinsame Primitive tragen die Entscheidungen, die das Produkt bereits mehr als einmal getroffen hat.
- Lokale Verantwortung lässt eine Entscheidung bei ihrer Oberfläche, bis eine zweite Oberfläche dieselbe trifft.
- Tests halten die Invarianten fest, die nicht abdriften sollen.
- Browser-Messungen prüfen, was die gerenderte Seite tatsächlich tut.
- Ein Mensch bleibt dafür verantwortlich, ob das Produkt gut ist.
KI hat vergrößert, wie viel sich an diesem Frontend an einem Tag ändern ließ. Ob diese Änderungen ein Produkt ergaben oder Entropie, wurde anderswo entschieden: dadurch, wem welche Entscheidung gehörte, was gemessen wurde und wer Nein gesagt hat.
Weiterlesen
Das repositoryweite System dahinter, mit Kontext-Routing, Validatoren und Agentenrollen, beschreibt Architektur des KI-nativen Repositorys. Die Fallstudien zum Fotografie-Assistenten und zur Fotografie-Suche behandeln die Features, deren Oberflächen hier immer wieder vorkommen. Architektur der Sprachinteraktion behandelt die lokale Spracheingabe.