Der Auslöser war kein Audit, sondern ein Störgefühl: Die Oberfläche wirkte unruhig. Beim ersten Hinsehen fällt so etwas nicht auf, beim zweiten und dritten aber schon — ein Zeitstempel hier eine Spur größer als dort, ein Dialog-Titel minimal kleiner als im Nachbar-Dialog. Von Briefen und Lebensläufen kennt jeder die Disziplin: gleiche Schriftart, gleiche Schriftgröße, gleiche Ausrichtung. Für eine Anwendungsoberfläche gilt dasselbe, nur zeigt sich der Verstoß dort nicht an einer Stelle, sondern als diffuse Unruhe über viele Ansichten hinweg.
Erst dieses Störgefühl führte zum Zähl-Kommando über das eigene Frontend, und das lieferte die Erklärung: 799 Treffer für rohe Pixel-Schriftgrößen wie text-[13px], verteilt über 25 verschiedene Pixel-Werte in 74 Dateien. Dabei hatte das Projekt längst eine dokumentierte Schriftgrößen-Skala mit sechs Tokens. Sie wurde 263-mal benutzt und rund 1180-mal umgangen. Das ist kein Schlamperei-Befund aus einem gewachsenen Legacy-Projekt, sondern der Zustand einer Codebase, die von Anfang an KI-gestützt mit Claude Code entwickelt wurde. KI Code Drift sieht genau so aus: Jeder einzelne Vorschlag ist lokal plausibel, und in Summe entsteht die Unruhe, die man sieht, bevor man sie messen kann.
Dieser Artikel ist der Erfahrungsbericht dazu — mit den echten Zahlen, der Regel, die das Problem beendet hat, und der ehrlichen Einsicht, dass eine Konvention, die nur in Prosa lebt, gegen ein Sprachmodell verliert.
Das Wichtigste vorab:
- Der Befund: 799 rohe Pixel-Schriftgrößen gegen 263 Token-Nutzungen, obwohl die Skala dokumentiert war. Das Verhältnis ist die Aussage, nicht die Einzelzahl.
- Die größte Drift-Quelle liegt zwischen den Tokens: Werte wie
11pxund12px, für die es gar keinen Token gab, stellen mit rund 400 Vorkommen die Mehrheit. - Kein Code-Review fängt das, weil jeder Diff für sich harmlos ist.
- Bessere Prompts heben die Trefferquote, beseitigen die Drift aber nicht — bei hohem Generierungs-Volumen wird jede Restquote sichtbar.
- Was hält: eine 6-Stufen-Skala, ein festes Element-zu-Token-Mapping, eine eindeutige Einrast-Regel für alle Zwischenwerte und ein Linter auf
error-Level.
Voraussetzung: Das Beispiel nutzt Tailwind CSS und ESLint in einem Next.js-Projekt. Das Muster gilt für jede Codebase mit Design-Tokens, unabhängig vom Framework — und, wie am Ende gezeigt, genauso für SQL-Konventionen.
Inhalt
- Der Fund: 799 gegen 263
- Warum es niemand bemerkte
- Die Drift liegt zwischen den Tokens
- Der Denkfehler „bessere Prompts“
- Was funktioniert: Skala, Kategorie-Mapping, Snap-Regel
- Auch die frische Regel driftete
- Durchsetzung: ein Linter auf error
- Was der Linter nicht kann
- Dasselbe Muster in SQL
- FAQ
- Verwandte Artikel
Der Fund: 799 gegen 263
Das Projekt hinter den Zahlen ist DI², ein ETL-Generator auf Next.js und PostgreSQL, dessen Code fast vollständig KI-gestützt entstanden ist. Für Schriftgrößen existierte dort eine klare Konvention: sechs benannte Tokens von text-di-h1 (18px) bis text-di-label (10px), definiert in der Tailwind-Konfiguration und beschrieben in einer Brand-Regel-Datei, die der Agent bei Frontend-Aufgaben lädt. Alles Folgende ist damit die Messung eines einzelnen Projekts, ein Datenpunkt und kein Beweis. Was den Fall über das Projekt hinaus interessant macht, ist der Mechanismus dahinter — und der ist, wie sich zeigen wird, nicht projektspezifisch.
Die Inventur am 25. Juni 2026 ergab über alle src/**/*.tsx-Dateien drei Kategorien:
| Deklarations-Stil | Vorkommen | Dateien | Bewertung |
|---|---|---|---|
Roh-Pixel text-[Xpx] | 799 | 74 | Drift, 25 verschiedene Pixel-Werte |
Tailwind-Defaults text-xs/text-sm/text-base … | 383 | 85 | Drift im App-Bereich |
Kanonische Tokens text-di-* | 263 | 43 | das Ziel-Muster |
Die drei Zeilen unterscheiden drei Arten, dieselbe Schriftgröße zu deklarieren. text-[13px] ist Tailwinds Freihand-Syntax: Der Pixel-Wert steht wörtlich in eckigen Klammern und wirkt wie ein Inline-font-size — jeder beliebige Wert ist möglich, und so entstehen 25 verschiedene. text-xs, text-sm und text-base sind dagegen benannte Größenstufen, allerdings aus der mitgelieferten Standard-Skala des Frameworks (12, 14 und 16 Pixel). Das sieht diszipliniert aus, weil es einer Skala folgt. Es ist nur die falsche: die generische von Tailwind statt der eigenen des Projekts, deren sechs Tokens diese drei Werte nicht einmal enthalten. Die dritte Zeile schließlich ist die projekteigene Skala selbst — das Ziel-Muster. Von oben nach unten gelesen ist die Tabelle eine Leiter: Freihand-Wert, fremde Skala, eigene Skala.
Dass zwei Skalen nebeneinander existieren, ist dabei kein Versehen, sondern die Voreinstellung des Frameworks. Tailwind bringt seine komplette Größen-Leiter in jedem Projekt mit, und die eigenen Tokens kamen über theme.extend dazu — extend heißt wörtlich erweitern, nicht ersetzen. Nach diesem Schritt kompiliert jedes text-sm genauso anstandslos wie jedes text-di-body. Es gibt keinen Moment, in dem das Projekt der fremden Skala zustimmt, und keinen, in dem sie sich bemerkbar macht. Sie ist einfach da, vom ersten Tag an, als die statistisch nächstliegende Wahl für jeden, der kleinen Text braucht — Mensch wie Modell.
Ein zweiter Begriff braucht eine Erklärung, weil er im Rest des Artikels immer wieder auftaucht. Der App-Bereich ist die Anwendung hinter dem Login: die Dashboard- und Verwaltungsseiten samt der Komponenten, die von dort gerendert werden. Nur dort gilt die Token-Skala. Außerhalb liegen zwei Zonen mit eigenem Recht — die öffentlichen Seiten (Landing, Impressum, Login), die bewusst größere Formate tragen, und die eingebundenen UI-Basiskomponenten, die intern mit Tailwind-Defaults arbeiten. Ein text-sm ist deshalb nicht pauschal falsch, auf einer Marketing-Seite ist es legitim. Deshalb steht in der zweiten Zeile „Drift im App-Bereich„: Von den 383 Vorkommen zählt nur der Teil als Drift, der innerhalb der Anwendung liegt.
Die Konvention wurde also im Verhältnis von rund 1:4,5 ignoriert. Wer die Inventur im eigenen Projekt nachstellen will, braucht dafür nur ein Suchwerkzeug wie ripgrep (die Beispiele sind in PowerShell notiert):
1: # Rohe Pixel-Schriftgrößen: Gesamtzahl der Vorkommen
2: rg --no-filename -o 'text-\[[0-9.]+px\]' src | Measure-Object -Line
3:
4: # Verteilung: welcher Pixel-Wert kommt wie oft vor?
5: rg --no-filename -o 'text-\[[0-9.]+px\]' src | Group-Object | Sort-Object Count -Descending
6:
7: # Zum Vergleich: die kanonischen Tokens
8: rg --no-filename -o 'text-di-(h1|h2|body|meta|micro|label)' src | Measure-Object -Line
Eine Pointe liefert die Zeitachse gleich mit. Zwischen dem Erstbefund und der Nachzählung drei Wochen später, kurz vor dem Start der Aufräum-Migration, wuchs der Bestand von 799 auf 813 Vorkommen und von 25 auf 26 verschiedene Werte. Die Drift wuchs also weiter, während ihre Beseitigung bereits geplant wurde. Eine Konvention, die nicht durchgesetzt wird, verliert nicht nur einmal, sondern jeden Tag ein bisschen mehr.
Warum es niemand bemerkte
Der erste Reflex bei so einer Zahl lautet: Wie konnte das durchrutschen? Die Antwort ist unbequem, weil sie kein Versäumnis beschreibt, sondern eine strukturelle Lücke.
Ein text-[12px] ist in keinem einzelnen Diff falsch. Es rendert korrekt, es sieht in der Vorschau gut aus, es bricht keinen Test. Ein Reviewer, der den Diff eines neuen Dialogs liest, prüft die Logik, die Zustände und die Zugänglichkeit. Er vergleicht nicht, ob die Schriftgröße in dieser Datei mit der in 73 anderen Dateien konsistent ist. Die Drift lebt nicht in einer Datei, sondern zwischen den Dateien.
Sichtbar war sie trotzdem, nur nicht als Fehler, sondern als das Störgefühl vom Anfang. Eine unruhige Oberfläche zeigt das Symptom, aber nicht die Stelle: Auf welche der 74 Dateien soll man zeigen, wenn keine Zeile für sich falsch ist? So blieb der Eindruck lange folgenlos. Er ließ sich keinem Diff zuordnen, und was sich keinem Diff zuordnen lässt, landet in keinem Review-Kommentar und in keinem Ticket. Auch Visual-Regression-Tests erkennen diese Form der Inkonsistenz in der Regel nicht, denn sie vergleichen jede Ansicht mit ihrer eigenen Baseline. Zwei Ansichten, die gleichzeitig neu entstehen, haben keine gemeinsame Baseline, gegen die der Unterschied auffallen könnte. Erst die Inventur machte aus dem Gefühl einen Befund mit Zahlen — und damit etwas, das sich beheben lässt.
Dazu kommt das Tempo. Ein Mensch, der einen Dialog pro Tag baut, erzeugt eine Handvoll Schriftgrößen-Entscheidungen pro Woche, und sein Muskelgedächtnis hält sie halbwegs stabil. Ein Agent, der in derselben Woche zwanzig Komponenten anlegt, trifft dieselbe Entscheidung hundertfach — und jede einzelne ist lokal optimiert, nicht auf Konsistenz mit allen vorigen ausgelegt. Die Inkonsistenz entsteht nicht aus Nachlässigkeit, sondern aus der schieren Menge unabhängiger Einzelentscheidungen. Das dichteste Einzelfile der Inventur brachte es allein auf 70 rohe Pixel-Angaben.
Menschen erzeugen dieselbe Drift, das gehört zur Ehrlichkeit dazu. Der Unterschied ist nicht die Art des Fehlers, sondern Tempo und Volumen. Was ein Team in zwei Jahren an Wildwuchs ansammelt, schafft KI-gestützte Entwicklung in einem Quartal.
Die Drift liegt zwischen den Tokens
Der aufschlussreichste Teil der Inventur ist die Verteilung der 25 Pixel-Werte. Sie zerfällt in drei Klassen, und die mittlere ist die interessante:
- Exakte Token-Treffer: Werte, die einem Token entsprechen, nur eben roh geschrieben —
13pxstatttext-di-body(166 Vorkommen),11.5pxstatttext-di-meta(46), dazu die übrigen Token-Werte. Diese Klasse ist mechanisch reparierbar und optisch folgenlos. - Off-Token-Werte: rund 400 Vorkommen auf Werten, für die gar kein Token existiert — allen voran
11pxmit 196 und12pxmit 143 Vorkommen, dazu12.5px(42) und14px(19). - Bewusste Ausnahmen: Marketing- und Rechtsseiten mit eigenen Großformaten wie
22pxoder44px, die absichtlich außerhalb der App-Skala liegen.
Die zweite Klasse verdient den zweiten Blick. 11px und 12px liegen beide neben demselben Token text-di-meta (11,5px) und wurden später auch beide dorthin migriert. Zusammen sind das 339 Stellen, an denen zwei verschiedene Pixel-Werte dieselbe semantische Rolle spielten — Meta-Text, Zeitstempel, Hilfszeilen. Niemand hat je entschieden, dass es beide Werte geben soll. Es gibt keinen Commit mit der Botschaft „wir führen 11px als Alternative zu 12px ein“. Beide Werte sind einfach entstanden, Vorschlag für Vorschlag, weil jeder für sich vernünftig aussah.
Das unterscheidet diese Drift von einem Abschreibfehler. Wer einen dokumentierten Wert falsch abtippt, produziert einen findbaren Defekt. Wer hundertfach plausible Werte in eine Lücke der Skala setzt, produziert eine schleichende Zweit-Skala, die nirgends beschlossen wurde und deshalb auch nirgends auffällt. Das ist KI-Code-Drift in Reinform.
Der Denkfehler „bessere Prompts“
Die naheliegende Reaktion auf den Befund wäre gewesen, die Konvention deutlicher zu formulieren. Die Brand-Regel um ein Kapitel erweitern, die Tokens im Prompt wiederholen, den Agenten eindringlicher instruieren.
Das trägt nicht weit, und der Grund liegt in der Funktionsweise des Modells. Ein Sprachmodell ist auf riesigen Mengen öffentlichen Codes trainiert, darunter unzählige Tailwind-Projekte, und dort ist text-sm oder text-[12px] die überwältigend häufigste Schreibweise für kleinen Text. Eine projekteigene Konvention wie text-di-meta ist dagegen genau eine Datei in einem Regelverzeichnis. Bei jeder einzelnen Generierung konkurriert die Regel gegen dieses Übergewicht, und sie gewinnt oft — aber nicht immer. Bei tausend Entscheidungen lässt eine Trefferquote von 90 Prozent hundert Drift-Stellen zurück, und auch eine deutlich höhere Quote läuft bei genügend Volumen auf Dutzende hinaus.
Regeln in Prosa verbessern die Quote, und begründete Regeln verbessern sie weiter. Wie ein Regelwerk entsteht, das ein Agent tatsächlich befolgt, beschreibt der Methodik-Artikel SQL-Konventionen mit Claude Code ableiten. Aber eine Quote unter 100 Prozent bedeutet bei hohem Volumen zwangsläufig Drift. Für eine Konvention, die ausnahmslos gelten soll, ist der Prompt das falsche Werkzeug. Sie braucht eine Prüfung, die nicht müde wird.
Was funktioniert: Skala, Kategorie-Mapping, Snap-Regel
Die Aufräum-Aktion bestand aus drei Bausteinen, die zusammen erst ihre Wirkung entfalten.
Erstens die Skala selbst. Sechs Stufen, definiert an genau einer Stelle in der Tailwind-Konfiguration, jede mit eingebauter Zeilenhöhe:
1: // tailwind.config.ts — die Skala als Single Source of Truth
2: fontSize: {
3: 'di-h1': ['18px', { lineHeight: '23.4px' }],
4: 'di-h2': ['15px', { lineHeight: '21px' }],
5: 'di-body': ['13px', { lineHeight: '18.85px' }],
6: 'di-meta': ['11.5px', { lineHeight: '15px' }],
7: 'di-micro': ['10.5px', { lineHeight: '13.65px' }],
8: 'di-label': ['10px', { lineHeight: '12px', letterSpacing: '0.05em' }],
9: },
Wichtig war die Entscheidung, die Skala bei sechs Stufen zu belassen. Die verlockende Alternative wäre gewesen, für 11px und 12px neue Tokens anzulegen und den Ist-Zustand damit zu legalisieren. Dann wäre aus der Drift eine offizielle Acht-Stufen-Skala geworden, und der nächste Zwischenwert hätte wieder eine Lücke gefunden.
Zweitens das Element-Kategorie-Mapping. Eine Tabelle in der Regel-Datei legt fest, welche Art von Element welchen Token trägt. Sektions-Überschriften bekommen di-h1, Dialog-Titel di-h2, Tabellen-Zellen und Buttons di-body, Zeitstempel und Hilfetexte di-meta, Zähler-Pills di-micro, Großbuchstaben-Labels di-label. Damit ist die Frage „welche Größe braucht dieses Element?“ keine Geschmacksfrage mehr, sondern ein Nachschlagen.
Drittens die Snap-Regel. Der Name kommt vom englischen to snap, einrasten: Rund 400 Vorkommen standen auf Zwischenwerten wie 11px oder 12px, und jedes davon musste bei der Migration auf einen der sechs Tokens einrasten — wie ein Objekt, das man in einem Grafikprogramm ans Raster zieht. Welcher Token es wird, entscheidet die Regel in zwei Stufen. Zuerst zählt die Rolle des Elements aus dem Kategorie-Mapping: Ein Zeitstempel bekommt di-meta, weil er ein Meta-Text ist, ganz gleich, welcher Pixel-Wert vorher dastand. Nur wenn sich ein Vorkommen keiner Kategorie zuordnen lässt, entscheidet die numerische Nähe zum nächsten Token. Auch der einzige Gleichstand ist ausdrücklich geregelt: 14px liegt genau zwischen di-body (13px) und di-h2 (15px), als Standard gewinnt di-body. Dass die Rolle vor der Zahl kommt, ist dabei kein Formalismus. Ein 12px in einem fixierten Tabellen-Kopf gehört per Kategorie zu di-micro (10,5px), obwohl di-meta (11,5px) numerisch näher läge. Und weil die Regel keinen Ermessensspielraum lässt, lösen zwei Personen oder zwei Agenten-Läufe denselben Roh-Wert garantiert identisch auf.
Mit diesen drei Bausteinen war die Migration selbst unspektakulär: 80 Dateien im App-Bereich, kategorieweise umgestellt, mit Verschiebungen unter einem Pixel. Genau eine bewusste Ausnahme blieb stehen, ein großer Seitentitel außerhalb der App-Skala, markiert mit einem Inline-Kommentar samt Begründung.
Ein Randbefund aus derselben Aufräum-Aktion sei der Vollständigkeit halber erwähnt: Die Tokens mussten in der tailwind-merge-Konfiguration eigens als Schriftgrößen-Klassen registriert werden, weil die Bibliothek unbekannte text-*-Klassen sonst als Textfarbe einordnet und im Konfliktfall stillschweigend verwirft. Das ist allerdings eine gewöhnliche Falle bei der Einführung eigener Tokens und hat mit KI nichts zu tun. Wer sie kennt, sichert sie mit einem kleinen Test ab und ist fertig.
Auch die frische Regel driftete
Der lehrreichste Befund der ganzen Geschichte stammt aus der Qualitätssicherung der Migration, und er geht gegen den Autor selbst.
Die Kategorie-Regel — „die Element-Kategorie gewinnt über die Pixel-Nähe“ — wurde im selben Commit in die Regel-Datei geschrieben, in dem auch migriert wurde. Und noch in diesem Commit wurde sie unterlaufen: Die Datenzellen der dichtesten Tabellen-Register, vorher 12px, gehörten per Kategorie-Tabelle zu di-body (13px). Migriert wurden sie aber numerisch nach di-meta (11,5px), weil das der Dichte-Absicht dieser Ansichten entsprach. Die frisch formulierte Regel und ihre erste Anwendung widersprachen sich, und aufgefallen ist das erst im nachgelagerten QA-Durchlauf, der es als Dokumentations-Inkonsistenz einstufte.
Man kann diesen Befund klein finden, ein halbes Pixel in dichten Tabellen. Sein Wert liegt woanders: Er zeigt, dass auch eine sorgfältig formulierte, druckfrische Regel im Moment ihrer Anwendung driftet, wenn nur Menschen und Prosa sie tragen. Nicht aus Ignoranz, sondern weil im konkreten Fall eine zweite legitime Überlegung dazwischenkam und niemand den Abgleich mit dem Wortlaut machte. Dieser regelbasierte Abgleich gehört zu den Aufgaben, die eine Maschine zuverlässiger erledigt als jeder Beteiligte.
Durchsetzung: ein Linter auf error
Der vierte Baustein macht die Konvention deshalb maschinell prüfbar. Die Werkzeugklasse ist dabei zweitrangig: Dieselbe Rolle kann ein Compiler-Check, ein Tailwind-Plugin oder ein CI-Skript übernehmen. Für dieses Projekt war eine eigene ESLint-Regel die praktikabelste Form — sie meldet jede rohe Pixel-Schriftgröße und jeden Tailwind-Größen-Default im App-Bereich als Fehler, nicht als Warnung. Der Kern der Regel passt auf eine Seite:
1: // eslint-rules/no-raw-font-size.mjs — der Kern der Regel
2: const PX_RE = /text-\[(\d+(?:\.\d+)?)px\]/g
3: const DEFAULT_RE = /(?<![\w-])text-(xs|sm|base)(?![\w-])/g
4:
5: // Snap-Tabelle: Roh-Wert -> kanonischer Token (aus der Regel-Datei)
6: const PX_SNAP = {
7: '11': 'text-di-meta',
8: '12': 'text-di-meta',
9: '12.5': 'text-di-body',
10: '13': 'text-di-body',
11: '14': 'text-di-body',
12: '15': 'text-di-h2',
13: }
14:
15: export default {
16: meta: { type: 'problem', messages: {
17: rawPx: 'Rohe Pixel-Schriftgröße `{{match}}`. Nutze {{target}}.',
18: } },
19: create(context) {
20: const check = (node, raw) => {
21: for (const m of raw.matchAll(PX_RE)) {
22: context.report({ node, messageId: 'rawPx', data: {
23: match: m[0], target: PX_SNAP[m[1]] ?? 'einen Skala-Token',
24: } })
25: }
26: // DEFAULT_RE analog
27: }
28: return {
29: Literal(node) { if (typeof node.value === 'string') check(node, node.value) },
30: TemplateElement(node) { check(node, node.value?.cooked ?? '') },
31: }
32: },
33: }
Drei Entscheidungen daran haben sich bewährt:
- Die Regel prüft alle String-Literale und Template-Bausteine, nicht nur JSX-Attribute (Zeilen 29 und 30). Klassen-Strings entstehen in der Praxis auch in
cn()-Argumenten, in.join(" ")-Hilfskonstrukten und in exportierten Konstanten — eine Regel, die nurclassName="…"sieht, hätte dort blinde Flecken. - Die Fehlermeldung nennt das Snap-Ziel (Zeile 23). Wer den Fehler sieht, sieht auch die Korrektur und muss die Regel-Datei nicht erst suchen. Das gilt für menschliche Leser genauso wie für den Agenten, der auf den Linter-Fehler reagiert.
errorstattwarn. Eine Warnung ist eine Zahl in einer Zusammenfassung, ein Fehler bricht den Build. Nur die zweite Variante ist eine Durchsetzung. Der halbe Weg — Warnung mit gelegentlichem Aufräumen — reproduziert am Ende den Zustand, der zur Drift geführt hat.
Eine naheliegende Alternative verdient die Erwähnung, weil sie einfacher aussieht, als sie ist: die Tailwind-Standard-Skala gleich aus der Konfiguration entfernen, indem man fontSize ohne extend definiert. Dann gäbe es text-sm schlicht nicht mehr. Das scheitert an zwei Stellen. Ein unbekanntes text-sm erzeugt in Tailwind keinen Fehler, sondern gar kein CSS — der Text fiele stumm auf die Browser-Standardgröße zurück, und so ein stiller Ausfall ist schwerer zu finden als die Drift, die er verhindern soll. Und die eingebundenen UI-Basiskomponenten bauen ebenso wie die Marketing-Seiten intern auf diesen Default-Klassen auf. Die Radikallösung würde also die Zonen brechen, die legitim von der Standard-Skala leben. Ein Linter mit Geltungsbereich kann das ausdrücken, eine globale Konfiguration nicht.
Die bewussten Ausnahmen leben deshalb nicht in der Regel, sondern in der ESLint-Konfiguration: Eine Pfadliste nimmt Marketing-, Rechts- und Login-Seiten aus, deren Großformate absichtlich außerhalb der App-Skala liegen. Damit ist der Geltungsbereich der Konvention maschinenlesbar an einer Stelle hinterlegt, deckungsgleich mit dem Geltungsbereich in der Regel-Datei. Bemerkenswert ist die Richtung der Definition: Der App-Bereich selbst steht nirgends als Liste, er ist schlicht alles, was nicht ausgenommen wurde. Eine neue öffentliche Seite, die niemand in die Ausnahme-Liste einträgt, behandelt der Guard deshalb als App-Bereich und meldet ihre Großformate als Fehler. Das ist die richtige Fehlerrichtung — ein vergessener Listeneintrag fällt als Fehlalarm im Build auf, statt als stille Drift durchzurutschen.
1: // eslint.config.mjs — Carve-out als Pfadliste (gekürzt)
2: const FONT_SIZE_CARVE_OUT = [
3: 'src/app/page.tsx', <em>// Landing: bewusst eigene Großformate</em>
4: 'src/app/impressum/**',
5: 'src/app/login/**',
6: 'src/**/*.test.{ts,tsx}', <em>// Tests referenzieren Klassen als Testdaten</em>
7: ]
8:
9: export default [
10: { files: ['src/**/*.{ts,tsx}'], rules: { 'di2/no-raw-font-size': 'error' } },
11: { files: FONT_SIZE_CARVE_OUT, rules: { 'di2/no-raw-font-size': 'off' } },
12: ]
Ein Ablauf-Detail zum Schluss: Der Guard wurde als letzter Schritt aktiviert, nachdem der App-Bereich leer migriert war. Andersherum hätte jede unfertige Datei eine temporäre Ausnahmeliste gebraucht, und temporäre Ausnahmelisten haben die Tendenz, dauerhaft zu werden.
Was der Linter nicht kann
Damit der Erfahrungsbericht ehrlich bleibt, gehören die Grenzen dazu.
Der Guard prüft die Schreibweise, nicht die Zuordnung. Dass ein Element überhaupt einen Token trägt, erzwingt er zuverlässig. Ob es der richtige Token für die Element-Kategorie ist, kann er nicht wissen — in dieser Lücke lebte der Regel-Widerspruch aus dem QA-Befund, und dort kann er wieder entstehen. Die Kategorie-Zuordnung bleibt eine Regel in Prosa, mit allen beschriebenen Schwächen, nur auf deutlich kleinerer Angriffsfläche.
Zwei Begrenzungen sind bewusst gewählt. Größere Tailwind-Defaults ab text-lg meldet die Regel nicht, denn ein auffällig großer Text in einer App-Datei ist ein Fall für ein Design-Review und kein Kandidat für mechanisches Umbiegen. Und dynamisch zusammengesetzte Klassen-Strings, etwa aus Variablen konkatenierte Werte, erfasst eine statische Regel nur teilweise. Beide Lücken sind dokumentiert statt verschwiegen, denn eine Prüfung, deren Auslassungen niemand kennt, erzeugt falsche Sicherheit.
Und schließlich: Der Linter konserviert die Skala, er begründet sie nicht. Ob sechs Stufen die richtigen sind und welche Kategorie welchen Token verdient, bleibt eine Designentscheidung, die dem Werkzeug vorausgeht.
Dasselbe Muster in SQL
Der Fall ist mit Absicht ein Frontend-Fall, denn dort war die Drift messbar. Das Muster dahinter ist an kein Framework gebunden. Die Übertragung auf SQL ist deshalb eine begründete Analogie, kein zweiter Messwert — aber eine, die sich aus demselben Mechanismus ergibt.
Eine SQL-Konvention — Singular-Tabellennamen, snake_case-Parameter, ein festes Prozedur-Skelett — ist dieselbe Klasse von Regel wie eine Schriftgrößen-Skala: eine projekteigene Festlegung, die gegen die statistisch häufigere Schreibweise aus fremden Codebasen konkurriert. Ein Agent, der PL/pgSQL generiert, driftet dort aus demselben Grund wie bei den Pixel-Werten, und die Antwort hat dieselbe Struktur. Die Konvention steht versioniert in einer Regel-Datei, mit Begründung und Negativbeispielen, so wie es die Postgres-Tabellen-Konventionen, die PL/pgSQL-Prozeduren-Konventionen und die PL/pgSQL-Funktions-Konventionen vormachen. Und was maschinell prüfbar ist, prüft ein Werkzeug: für SQL-Layout etwa sqlfluff mit projekteigener Konfiguration, für Struktur-Konventionen ein Skript im CI-Gate.
Die Arbeitsteilung ist in beiden Welten gleich. Die Prosa-Regel erklärt das Warum und hebt die Trefferquote. Das Werkzeug schließt die Lücke zwischen hoher Trefferquote und Ausnahmslosigkeit. Denn das Grundmuster bleibt dasselbe, ob im Frontend oder in der Datenbank: Lokale Plausibilität erzeugt globale Inkonsistenz. Wie das Zusammenspiel aus Rules, Skills und Agenten insgesamt aufgebaut ist, beschreibt der Pillar-Artikel KI-gestützte SQL-Entwicklung mit Claude Code.
FAQ
Oft meldet sie sich zuerst als Eindruck: Die Oberfläche wirkt unruhig, ohne dass sich eine Stelle benennen ließe. Greifbar wird sie durch eine Zähl-Inventur: ein Suchmuster für die kanonische Schreibweise, eines für die Umgehungen, und die beiden Zahlen ins Verhältnis setzen. Für Design-Tokens sind das Ausdrücke wie text-\[[0-9.]+px\] gegen die Token-Namen. Aufschlussreicher als die Gesamtzahl ist die Verteilung der Werte, denn Häufungen auf Werten ohne Token zeigen, wo die Skala eine Lücke hat oder wo eine inoffizielle Zweit-Konvention entstanden ist.
Eine dokumentierte Regel verbessert die Trefferquote, garantiert sie aber nicht — bei hohem Generierungs-Volumen wird aus jeder Restquote messbare Drift. Regeln in Prosa und maschinelle Prüfung sind deshalb keine Alternativen, sondern zwei Hälften: Die Regel erklärt das Warum, der Linter erzwingt das Was. Wo Regeln in Claude Code leben und was sie an Kontext kosten, erklärt Skills vs. Rules in Claude Code.
Weil die Fehlermeldung das projekteigene Snap-Ziel nennen soll und die Prüfung auch Klassen-Strings außerhalb von JSX-Attributen erfassen muss. Generische Ansätze wie no-restricted-syntax können das Muster zwar flaggen, aber keine kontextuelle Korrektur vorschlagen — und gerade die macht den Fehler für Mensch und Agent sofort behebbar.
Nein. Jede projekteigene Konvention, die gegen eine verbreitetere Standard-Schreibweise konkurriert, driftet unter KI-gestützter Entwicklung nach demselben Mechanismus — SQL-Naming, Datei-Strukturen, Fehlerbehandlungs-Muster. Die Gegenmaßnahme ist überall gleich: dokumentieren mit Begründung, und den maschinell prüfbaren Teil in ein Werkzeug mit Fehler-Level gießen.
Doch, und das ist der ehrliche Kern. Der Unterschied liegt im Tempo: Ein Agent trifft in einer Woche so viele Einzelentscheidungen wie ein Team in Monaten, und er trifft jede ohne Erinnerung an die vorige. Konventionen, die im menschlichen Tempo leidlich hielten, brechen unter diesem Volumen sichtbar — was auch bedeutet, dass die KI-Drift nur ein altes Problem schneller sichtbar macht.
Verwandte Artikel
Weiterführend:
- SQL-Konventionen mit Claude Code ableiten — der Generate-Refine-Derive-Loop — wie eine Konvention entsteht, bevor man sie durchsetzen kann.
- KI-gestützte SQL-Entwicklung mit Claude Code — Rules, Skills und Agenten — der Pillar: das Durchsetzungs-System im Ganzen.
- Skills vs. Rules in Claude Code — wo Konventionen leben und was sie an Kontext kosten.
Konventions-Spokes:
- Postgres-Tabellen-Konventionen — Naming, Keys und Audit-Spalten
- PL/pgSQL-Prozeduren-Konventionen
- PL/pgSQL-Funktions-Konventionen
Starter-Kit:
- Das offene DI²-Starter-Kit auf GitHub — eine Projekt-Vorlage mit der hier beschriebenen Rules-Struktur; der Schriftgrößen-Guard aus diesem Artikel liegt dort als wiederverwendbare Vorlage bei.