
TypeScript 3.9, Legacy-Decorators, ein aufgegebener Release Candidate als Dependency. Das macht langfristig nur mehr Probleme. vue-class-component blockierte den Weg zu TypeScript 5: Kein Tooling-Support mehr, inkompatibel mit modernem TypeScript, nie stabil released. Irgendwann muss man sowas angehen. Aber Zeit war da, und weil Claude Code so schnell arbeitet, ist das Zeitrisiko überschaubar.
Die Entscheidung: Options API statt Composition API
Der modernere Ansatz für Vue ist heute die Composition API mit <script setup>. Aber: Das hätte ein quasi
komplettes Umschreiben aller 82 Komponenten bedeutet. Die Options API mit defineComponent() ist den
Class Components viel ähnlicher. Weniger Arbeit, klar regelbar, automatisierbar. Und der Blocker ist trotzdem
weg. Alle weiteren Upgrades sind damit möglich. Also: Bestehende Komponenten erstmal zu Options API, später
dann stückweise weiter zu Composition API. Neue Komponenten werden ab sofort schon mit Composition API erstellt.
Beides lässt sich mischen, das ermöglicht eine schrittweise Weiter-Migration.
Wie wenig sich dabei tatsächlich ändert, zeigt ein Beispiel. Vorher:
<script lang="ts">
import {Options, Vue} from "vue-class-component"
@Options({
name: "PropertyFieldRow",
props: {
label: { type: String, required: true },
value: { type: Property, required: true },
},
components: { ... }
})
export default class PropertyFieldRow extends Vue {
label!: string
value!: Property
get isVisible(): boolean {
return this.value !== null
}
}
</script>
Nachher:
<script lang="ts">
import {defineComponent} from "vue"
export default defineComponent({
name: "PropertyFieldRow",
props: {
label: { type: String, required: true },
value: { type: Property, required: true },
},
components: { ... },
computed: {
isVisible(): boolean {
return this.value !== null
}
}
})
</script>
Das Template? Identisch, nicht eine Zeile geändert. Aber der gesamte @Options-Block und einige Imports waren
rot in der IDE, weil die Decorator-Syntax TypeScript-Fehler produzierte. Jetzt: Auto-Complete, alles grün.
Allein dafür hätte sich die Migration gelohnt.
Bevor Claude Code eine einzige Zeile ändern durfte: 3-4 Stunden Vorbereitung. Bestandsaufnahme: 82 Komponenten mit ~15.000 Zeilen Code, von einer 30-Zeilen-Greeting-Komponente bis zu ProjectView.vue mit 1.769 Zeilen, 50+ Gettern und 106 Methoden.
Zusammen mit Claude Code habe ich dann 16 Transformationsregeln festgelegt. Jede so konkret, dass ein Agent sie
ohne Rückfragen anwenden kann. Grundmuster: Klassenfelder werden zu data(), Getter zu computed. Die Tücken
stecken in den Details. Composables wie useRouter() dürfen nur in setup(), nicht in methods. Klassenfelder
mit this müssen zu computed, weil this in data() noch nicht vollständig initialisiert ist.
3-4 Stunden Planung für 2,5 Stunden Migration.
18 Subagents und ein Feedback-Loop
82 Dateien, da stellt sich die Frage nach Subagents gar nicht erst. Das macht man so. Erster Gedanke: ein Agent pro Datei. Maximale Parallelität, jede Komponente isoliert. Aber 82 Agents heißt auch: Jeder lädt die 16 Transformationsregeln separat, jeder hat Startup-Overhead, und 82 Rückmeldungen blähen den Kontext der Hauptsession auf. Also: 8 Batches mit je 2-3 Subagents, gruppiert nach Domäne und Komplexität.
Vorteil: Die Hauptsession blieb schlank. Schlank genug, um nebenbei alles Nötige für diesen Blogartikel zu dokumentieren. Nach der kompletten Migration und Dokumentation waren noch ~15% Context übrig bis zum Auto-Compact. Alles in derselben Konversation. Die gesamte Migration lief in einer einzigen Session, ohne das Context-Limit zu erreichen. Erst das API-Nutzungslimit bei der letzten Komponente erzwang eine Pause.
Eigentlich war geplant, die 3 Agents pro Batch parallel laufen zu lassen. In der Praxis startete jeder Agent sequenziell. Schlicht vergessen, alle in einer Nachricht zu starten. Im Nachhinein war das sogar ein Vorteil, dazu später mehr.
Nach jedem Batch ein vollständiger Build. Nach jeder Datei Diagnostics. Das Ergebnis:
| 82 | Komponenten mit ~15.000 Zeilen Code migriert |
| 18 | Subagents eingesetzt |
| ~2,5 Stunden | Reine Migrationszeit |
| ~1,4 Mio | Token verbraucht |
| 0 | Build-Fehler |
| 0 | Manuelle Code-Eingriffe |
| 5 | npm-Pakete entfernt |
| -19 KB | Bundle-Größe reduziert |
| ~50+ | Code-Smells nebenbei behoben |
~1,4 Millionen Token bei einem Context-Limit von ~200k. Ohne Subagents wäre das in einer Session nicht möglich gewesen. Jeder Subagent arbeitet in seinem eigenen Context und liefert nur eine kompakte Zusammenfassung zurück. 0 Build-Fehler und 0 manuelle Eingriffe klingt zu gut? Genau dafür war die Planung da. Klare Regeln, Verifikation nach jeder Datei, Build nach jedem Batch.
Im Regelfall lief ein Batch unspektakulär ab. 9 kleine Controls wie Checkbox, DatePicker und Dropdown, verteilt auf 3 Agents, nach 16 Minuten fertig, Build grün. Mechanisch, vorhersehbar, genau das was die Regeln abdecken.
Nebenbei haben die Agents auch offensichtliche Fehler im bestehenden Code gefunden und ohne Rückfrage behoben. Copy-Paste-Artefakte in Klassennamen, Watchers die nie gefeuert haben, unused Imports. Über 50 solcher Code-Smells quer über alle Batches. Und auch die größten Komponenten waren kein Problem. LeafletMap mit 15 Watchern und 30 Data-Feldern? 9 Minuten.
Diese Korrekturen waren keine Einbahnstraße. Es hat sich ein selbstkorrigierender Feedback-Loop gebildet, ungeplant:
- Ein Agent transformiert eine Datei nach den Regeln
- getDiagnostics meldet einen Fehler, den die Regeln nicht abdecken
- Der Agent löst das Problem selbstständig für seine Datei
- Claude Code fasst die Erkenntnis als ★ Insight zusammen
- Die Hauptsession kodifiziert es als neue Regel für alle folgenden Agents
Die ★ Insights entstanden durch den “Explanatory” Output Style in der Claude Code Konfiguration. Nach jedem Batch fasste Claude Code zusammen, was die Agents an unerwarteten Patterns gefunden und gelöst hatten. Das war das Bindeglied: Ohne diese strukturierte Rückmeldung hätte die Hauptsession die Erkenntnisse nicht als Regeln formalisieren können.
Einer der Insights aus der Session:
★ Insight: Batch 2d highlight — this.$t() in class fields: A subtle class component antipattern:
fieldTypeOptions = [{ label: this.$t('Textfeld'), ... }]. In class components this works because the constructor runs after Vue injects $t. In defineComponent(), data() runs before this is fully set up for i18n. Solution: move to computed — which also makes translations reactive. This is actually an improvement over the original code!
So kamen über die Batches hinweg drei neue Regeln dazu: this.$t() in Klassenfeldern muss nach computed statt
data(), ref<T>(null) aus @vue/reactivity muss durch this.$refs ersetzt werden, und mehrere Watcher auf
dasselbe Property brauchen Array-Syntax. Keine davon war in der ursprünglichen Planung enthalten. Die frühen
Agents waren Pfadfinder, die späteren profitierten von deren Erkenntnissen. Und genau hier hat sich die
sequenzielle Ausführung ausgezahlt: Wenn Agent 1 ein Problem findet und zurückmeldet, bekommen Agent 2 und 3 die
neue Regel schon mit. Bei paralleler Ausführung hätten alle drei dasselbe Problem unabhängig gelöst,
möglicherweise unterschiedlich.
Fazit
Ich bin alleine im Projekt. Normalerweise hätte ich das nie angefasst, will ich mir nicht antun. Aber Zeit war da. Wenn es klappt, super. Wenn nicht, wird der Branch weggeschmissen und ich habe selber quasi keine Zeit verloren außer der Planung. Und selbst das ist nicht wirklich verlorene Zeit: Die Infos sind im Zweifel hilfreich und bilden den Grundstein für die Migration, auch wenn mein Versuch fehlschlagen sollte. Der Rest lief im Auto-Accept Mode. Den würde ich normalerweise beim Coding nicht anfassen. Aber das hier war kein Coding. Es waren in den meisten Fällen klare Dinge, die einfach ausgetauscht werden mussten. In solchen Fällen ist der Auto-Accept Mode für mich ausnahmsweise genau richtig. Klare Regeln, mechanische Ersetzungen, automatische Verifikation. Da muss und will ich nicht jede Änderung einzeln absegnen. Hinzu kam die zufällige Entdeckung der Emergenz, was eine coole Überraschung war und das Schreiben dieses Blogartikels sehr viel interessanter gemacht hat. Diesmal war es ungeplant, aber das sollte im Prompt für so eine Migration festgehalten werden: Selbstverbesserung und Erweiterung der Regeln, wenn Fehler auftreten. Das merk ich mir. Viel, viel, viel, viel Fleißarbeit für einen Menschen, 2,5 Stunden für eine KI, während ich mir Abendbrot gemacht habe.
